Magic Room

使用TextSwitcher和CountDownTimer实现简单的倒计时控件

前言

最近在做注册登录模块,里面发送验证码后,会有一个60秒计时后点击重新发送的按钮。看了同事之前实现类似功能是使用CountDownTimer来计时的,之前没用过这个类,了解了一下还是挺简单的。但是逻辑全放在了外面,秉着高内聚、低耦合的思想,打算小小封装一个控件,使外层的调用尽量简洁,刚好最近又看到了TextSwitcher实现文字切换添加动画的效果,于是打算将他们结合使用一下,CountDownTextView便应运而生..

效果图

废话不多说,先上图为敬

CountDownTextView.gif

CountDownTextView代码

public class CountDownTextView extends TextSwitcher
    implements TextSwitcher.ViewFactory, View.OnClickListener {

    private int TOTAL_MILLS;    //默认60s
    private int GAP;            //间隙
    private String tipString;   //提示文字

    private float TEXT_SIZE;
    private int TEXT_COLOR;

    private int BgEnableResId;
    private int BgDisableResId;
    private CountDownTimer timer;
    private onReSend mListener;

    private boolean countDownFinish;

    public CountDownTextView(Context context) {
        this(context, null);
    }

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

        checkAttrs(attrs);
        setBackgroundResource(BgEnableResId);

        configSwitcher();
        configTimer();
        setOnClickListener(this);
    }

    /**
    * 检查自定义属性
    */
    private void checkAttrs(AttributeSet attrs) {

        float defaultTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 16.0f,
            Resources.getSystem().getDisplayMetrics());

        TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.CountDownTextView);
        TEXT_SIZE = ta.getDimension(R.styleable.CountDownTextView_text_size, defaultTextSize);
        TEXT_COLOR = ta.getColor(R.styleable.CountDownTextView_text_color, Color.WHITE);
        BgEnableResId = ta.getResourceId(R.styleable.CountDownTextView_enable_background,
            R.drawable.shape_login_btn_bg);
        BgDisableResId = ta.getResourceId(R.styleable.CountDownTextView_disable_background,
            R.drawable.shape_login_btn_bg_disable);

        TOTAL_MILLS = ta.getInteger(R.styleable.CountDownTextView_total_time,60);
        GAP = ta.getInteger(R.styleable.CountDownTextView_gap_time,1);
        tipString = ta.getString(R.styleable.CountDownTextView_tip_text);

        checkAttrs();

        ta.recycle();
    }

    /**
    * 验证 自定义属性有效性
    * 非法将使用默认值
    */
    private void checkAttrs() {
        if (TextUtils.isEmpty(tipString)) {
            tipString = getContext().getString(R.string.click_to_resend);
        }

        if (GAP <= 0) {
            GAP = 1;
        }

        if (TOTAL_MILLS < GAP) {
            TOTAL_MILLS = 60;
        }
    }

    /**
    * 开始计时
    */
    public void start() {
        timer.start();
        setBackgroundResource(BgDisableResId);
    }

    /**
    * 取消定時器
    */
    public void cancel() {
        timer.cancel();
        timer.onFinish();
    }

    /**
    * 配置 CountDownTimer
    * 默认60s 间隙 1s
    */
    private void configTimer() {
        if (null == timer) {
            timer = new CountDownTimer(TOTAL_MILLS *1000, GAP*1000) {
                @Override
                public void onTick(long l) {
                    onGapCallback(l);
                }

                @Override
                public void onFinish() {
                    onCountDownFinish();
                }
            };
        }
    }

    /**
    * 计时完毕回调
    */
    private void onCountDownFinish() {

        countDownFinish = true;
        String show = tipString;
        setText(show);
        setBackgroundResource(BgEnableResId);
    }

    /**
    * 间隙的回调
    *
    * @param rest 剩余时间
    */
    private void onGapCallback(long rest) {

        countDownFinish = false;
        String show = rest / (GAP *1000) + "s";
        setText(show);
    }

    /**
    * 配置 TextSwitcher 切换动画
    */
    private void configSwitcher() {
        setFactory(this);
        setInAnimation(getContext(), android.R.anim.fade_in);
        setOutAnimation(getContext(), android.R.anim.fade_out);
    }

    public int px2sp(float pxValue) {
        final float fontScale = Resources.getSystem().getDisplayMetrics().scaledDensity;
        return (int) (pxValue / fontScale + 0.5f);
    }

    @Override
    public View makeView() {
        TextView textView = new TextView(getContext());
        textView.setMaxLines(1);
        textView.setEllipsize(TextUtils.TruncateAt.END);
        textView.setGravity(Gravity.CENTER);
        textView.setTextColor(TEXT_COLOR);
        textView.setTextSize(px2sp(TEXT_SIZE));
        int padding =
            TypedValue.complexToDimensionPixelSize(10, Resources.getSystem().getDisplayMetrics());
        textView.setPadding(padding, padding, padding, padding);

        LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        params.gravity = Gravity.CENTER;
        textView.setLayoutParams(params);
        return textView;
    }

    @Override
    public void onClick(View view) {
        if (countDownFinish && mListener != null) {
        mListener.onResend(view);
        }
    }

    public interface onReSend {
        void onResend(View view);
    }

    /**
    * 重发事件
    */
    public void setResendListener(onReSend listener) {
        this.mListener = listener;
    }
}

使用方法

1.在XML文件中引用

<com.magicalxu.library.CountDownTextView
    android:id="@+id/id_button_normal"
    android:layout_width="150dp"
    android:layout_height="50dp"
    android:layout_marginTop="40dp"
    magical:disable_background="@drawable/shape_login_btn_bg_disable"
    magical:enable_background="@drawable/shape_login_btn_bg"
    magical:gap_time="1"
    magical:text_color="@android:color/white"
    magical:text_size="16sp"
    magical:tip_text="点击重发"
    magical:total_time="3" />

2.开始计时调用 start() 方法

3.设置重发事件监听

mBtnNormal.setResendListener(new CountDownTextView.onReSend() {
    @Override
    public void onResend(View view) {
        //计时结束后 点击事件回调
    }
});

自定义切换动画

将源码中 configSwitcher() 方法中的动画替换

setInAnimation(getContext(), android.R.anim.fade_in);
setOutAnimation(getContext(), android.R.anim.fade_out);

源码地址

https://github.com/magical-xu/CountDownTextView