最近一段时间做了一个应用的项目,感觉一年没写应用,很多东西都忘记了,也遇到了很多问题,搞得焦头烂额的,以后还是会写一写应用小demo,练练手。前段时间做了一个简单的倒计时控件,效果图如下。

该控件由数字选择和时间倒计时两个界面组成,时间设置完毕点击 “开始” 后就会跳转到倒计时界面开始倒计时。

一、数字选择界面
由于是针对TV产品的,所以界面做了焦点的处理。针对子控件的焦点问题,统一将焦点给父布局,子布局不能获取焦点。
下图是小时的布局代码。这里采用我说的那种焦点处理方式:
1、父布局根节点 android:focusable="true" 2、子控件里能自动获取焦点的控件android:focusable="false" 3、子控件加上属性 android:duplicateParentState="true" 这个属性非常的重要,它可以跟随父控件的状态变化,这样子控件的颜色才会随焦点变化

布局的代码如下:

<?xml version="1.0" encoding="utf-8"?>
<com.jmgo.countdowntimerview.widget.SelectStateLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/cd_hour"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:gravity="center"
    android:focusable="true">

    <ImageView
        android:id="@+id/prev1"
        android:layout_width="16px"
        android:layout_height="16px"
        android:layout_centerVertical="true"
        android:src="@drawable/prev_icon1"
        android:duplicateParentState="true"
        android:visibility="invisible"/>

    <Button
        android:id="@+id/bt_hour"
        android:layout_width="151px"
        android:layout_height="87px"
        android:layout_marginLeft="18px"
        android:layout_toRightOf="@id/prev1"
        android:background="@drawable/bt_hour_bg_selector"
        android:focusable="false"
        android:text="0"
        android:textColor="@color/item_color_selector"
        android:duplicateParentState="true"/>

    <ImageView
        android:id="@+id/prev2"
        android:layout_width="16px"
        android:layout_height="16px"
        android:layout_centerVertical="true"
        android:layout_marginLeft="18px"
        android:layout_toRightOf="@id/bt_hour"
        android:src="@drawable/prev_icon2"
        android:duplicateParentState="true"
        android:visibility="invisible"/>

    <TextView
        android:id="@+id/tv_hour"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="28px"
        android:layout_toRightOf="@id/prev2"
        android:text="hour"
        android:textColor="@color/btn_focus_color"
        android:textSize="28px"
        android:duplicateParentState="true"/>

</com.jmgo.countdowntimerview.widget.SelectStateLayout>

二、数字选择界面逻辑代码
逻辑部分就很简单了,只有一个onkey 监听事件和一个接口将选择的数字时间给倒计时界面回调。

1、初始化部分

@Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        Log.d(TAG, "--onFinishInflate---");
        LayoutInflater.from(getContext()).inflate(R.layout.custome_coundown_timer, this, true);
        customCountHour = findViewById(R.id.hour);
        customCountHour.setOnKeyListener(this);
        customCountHour.setOnFocusChangeListener(this);
        mHour = (Button) findViewById(R.id.bt_hour);
        mHour.setText(String.valueOf(0));
        prv1 = (ImageView) findViewById(R.id.prev1);
        prv2 = (ImageView) findViewById(R.id.prev2);
        customCountMinute = findViewById(R.id.minute);
        customCountMinute.setOnKeyListener(this);
        customCountMinute.setOnFocusChangeListener(this);
        mMinute = (Button) findViewById(R.id.bt_minute);
        mMinute.setText(String.valueOf(0));
        prv3 = (ImageView) findViewById(R.id.prev3);
        prv4 = (ImageView) findViewById(R.id.prev4);

    }

2、Button 的OnKey监听时间,当小时到0或24的时候让显示 为24或0,这样有一个数字循环的效果。

@Override
    public boolean onKey(View v, int keyCode, KeyEvent event) {
        //Log.d(TAG,"v="+v+",keyCode="+keyCode+",event="+event);
        if (v == customCountHour) {
            int i = Integer.valueOf((String) mHour.getText());
            if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_DPAD_LEFT) {
                //Log.d(TAG, "左键被按下了"+",i="+i);
                if (i == 0) {
                    mHour.setText(String.valueOf(24));
                } else {
                    mHour.setText(String.valueOf(i - 1));
                }
                Log.d(TAG, "mHour.getText()=" + mHour.getText() + ",mMinute.getText()=" + mMinute.getText());
                pickTime.getPickTime(Integer.valueOf((String) mHour.getText()), Integer.valueOf((String) mMinute.getText()));
                return true;
            } else if (event.getAction() == KeyEvent.ACTION_DOWN && keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
                //Log.d(TAG, "右键被按下了");
                if (i == 24) {
                    mHour.setText(String.valueOf(0));
                } else {
                    mHour.setText(String.valueOf(i + 1));
                }
                Log.d(TAG,"onCustomPickTime=="+pickTime);
                Log.d(TAG, "mHour.getText()=" + mHour.getText() + ",mMinute.getText()=" + mMinute.getText());
                Log.d(TAG,"--"+ Integer.valueOf((String) mHour.getText())+",.... "+ Integer.valueOf((String) mMinute.getText()));
                pickTime.getPickTime(Integer.valueOf((String) mHour.getText()), Integer.valueOf((String) mMinute.getText()));
                return true;
            }

3、定义一个接口将设置的时间给倒计时界面回调

public interface OnCustomPickTime {
        void getPickTime(int hour, int minute);
    }

三、倒计时界面的逻辑代码
这里面同样留了一个接口用于倒计时结束后的操作回调

public class CountDownView extends Chronometer {
    private long mTime;
    private long mNextTime;
    private OnTimeCompleteListener mListener;
    private SimpleDateFormat mTimeFormat;

    public CountDownView(Context context) {
        super(context);
    }

    public CountDownView(Context context, AttributeSet attrs) {
        super(context, attrs);

        mTimeFormat = new SimpleDateFormat("hh:mm:ss");
        this.setOnChronometerTickListener(listener);
    }

    //重新启动计时
    public void reStart(long _time_s)
    {
        if (_time_s == -1)
        {
            mNextTime = mTime;
        }
        else
        {
            mTime = mNextTime = _time_s;
        }
        this.start();
    }

    public void reStart()
    {
        reStart(-1);
    }

    //不建议方法名用onResume()或onPause(),容易和activity生命周期混淆
    //继续计时
    public void onResume()
    {
        this.start();
    }

    //暂停计时
    public void onPause()
    {
        this.stop();
    }

    /**
     * 设置时间格式
     *
     * @param pattern 计时格式
     */
    public void setTimeFormat(String pattern)
    {
        mTimeFormat = new SimpleDateFormat(pattern);
    }

    public void setOnTimeCompleteListener(OnTimeCompleteListener l)
    {
        mListener = l;
    }

    OnChronometerTickListener listener = new OnChronometerTickListener()
    {
        @Override
        public void onChronometerTick(Chronometer chronometer)
        {
            if (mNextTime <= 0)
            {
                if (mNextTime == 0)
                {
                    CountDownView.this.stop();
                    if (null != mListener)
                        mListener.onTimeComplete();
                }
                mNextTime = 0;
                updateTimeText();
                return;
            }

            mNextTime--;

            updateTimeText();
        }
    };

    //初始化时间(秒)
    public void initTime(long _time_s)
    {
        mTime = mNextTime = _time_s;
        updateTimeText();
    }

    //初始化时间(分秒)
    public void initTime(long _time_h,long _time_m,long _time_s) {
        initTime(_time_h*3600+_time_m * 60 + _time_s);
    }

    private void updateTimeText()
    {
        this.setText(FormatMiss(mNextTime));
    }

    // 将秒转化成小时分钟秒
    public String FormatMiss(long miss){
        String hh=miss/3600>9?miss/3600+"":"0"+miss/3600;
        String mm=(miss % 3600)/60>9?(miss % 3600)/60+"":"0"+(miss % 3600)/60;
        String ss=(miss % 3600) % 60>9?(miss % 3600) % 60+"":"0"+(miss % 3600) % 60;
        return hh+":"+mm+":"+ss;
    }

    public interface OnTimeCompleteListener
    {
        void onTimeComplete();
    }
}

四、遇到的问题
我在做的时候遇到了一个很有意思的问题,这个倒计时控件我在初始化的的时候我是

customPickNumberView=new CustomPickNumberView(this);
而不是customPickNumberView=findViewById(R.id.customCountDownView);这么写的,结果一直报空指针
不知道有哪个同学可以解释下原因 ?

这里面介绍部分就是这样子的,代码我传到了GitHub ,有不足的地方欢迎指出,也可以Star 下哦,我以后也会多写一写开源项目。