自定义一个广告倒计时View
2016-10-13 22:38 阅读(227)

今天打开迅雷手机客户端准备看片的时候,无意间发现这个自定义View,感觉很好看的,实现起来也不麻烦,就尝试着模仿了一下,花了一天,最后终于搞出来了。因为技术比较菜,所以时间有点长,总之慢慢来吧。


迅雷截图


自定义View效果图

  1. 自定义属性
    底盘的颜色
    进度条的颜色
    进度条粗细
    文字内容
    文字颜色
    文字大小

<declare-styleable name="CountDownView">
     <attr name="background_color" format="color" />
     <attr name="border_width" format="dimension" />
     <attr name="border_color" format="color" />
     <attr name="text" format="string" />
     <attr name="text_size" format="dimension" />
     <attr name="text_color" format="color" />
 </declare-styleable>

自定义一个CountDownView,继承View

public class CountDownView extends View {

 private static final String TAG = CountDownView.class.getSimpleName();
 private static final int BACKGROUND_COLOR = 0x50555555;
 private static final float BORDER_WIDTH = 15f;
 private static final int BORDER_COLOR = 0xFF6ADBFE;
 private static final String TEXT = "跳过广告";
 private static final float TEXT_SIZE = 50f;
 private static final int TEXT_COLOR = 0xFFFFFFFF;

 private int backgroundColor;
 private float borderWidth;
 private int borderColor;
 private String text;
 private int textColor;
 private float textSize;

 private Paint circlePaint;
 private TextPaint textPaint;
 private Paint borderPaint;

 private float progress = 135;
 private StaticLayout staticLayout;

 private CountDownTimerListener listener;

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

 public CountDownView(Context context, AttributeSet attrs) {
     super(context, attrs);
     TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CountDownView);
     backgroundColor = ta.getColor(R.styleable.CountDownView_background_color, BACKGROUND_COLOR);
     borderWidth = ta.getDimension(R.styleable.CountDownView_border_width, BORDER_WIDTH);
     borderColor = ta.getColor(R.styleable.CountDownView_border_color, BORDER_COLOR);
     text = ta.getString(R.styleable.CountDownView_text);
     if (text == null) {
         text = TEXT;
     }
     textSize = ta.getDimension(R.styleable.CountDownView_text_size, TEXT_SIZE);
     textColor = ta.getColor(R.styleable.CountDownView_text_color, TEXT_COLOR);
     ta.recycle();
     init();
 }

 private void init() {
     circlePaint = new Paint();
     circlePaint.setAntiAlias(true);
     circlePaint.setDither(true);
     circlePaint.setColor(backgroundColor);
     circlePaint.setStyle(Paint.Style.FILL);

     textPaint = new TextPaint();
     textPaint.setAntiAlias(true);
     textPaint.setDither(true);
     textPaint.setColor(textColor);
     textPaint.setTextSize(textSize);
     textPaint.setTextAlign(Paint.Align.CENTER);

     borderPaint = new Paint();
     borderPaint.setAntiAlias(true);
     borderPaint.setDither(true);
     borderPaint.setColor(borderColor);
     borderPaint.setStrokeWidth(borderWidth);
     borderPaint.setStyle(Paint.Style.STROKE);
 }
}

  1. 重写了两个构造方法,然后对自定义属性进行了初始化

  2. 重写onMeasure方法

@Override
 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
     int width = MeasureSpec.getSize(widthMeasureSpec);
     int widthMode = MeasureSpec.getMode(widthMeasureSpec);
     int height = MeasureSpec.getSize(heightMeasureSpec);
     int heightMode = MeasureSpec.getMode(heightMeasureSpec);
     if (widthMode != MeasureSpec.EXACTLY) {
         width = staticLayout.getWidth();
     }
     if (heightMode != MeasureSpec.EXACTLY) {
         height = staticLayout.getHeight();
     }
     setMeasuredDimension(width, height);
 }
@Override
 protected void onDraw(Canvas canvas) {
     int width = getMeasuredWidth();
     int height = getMeasuredHeight();
     int min = Math.min(width, height);
     //画底盘
     canvas.drawCircle(width / 2, height / 2, min / 2, circlePaint);
     //画边框
     RectF rectF;
     if (width > height) {
         rectF = new RectF(width / 2 - min / 2 + borderWidth / 2, 0 + borderWidth / 2, width / 2 + min / 2 - borderWidth / 2, height - borderWidth / 2);
     } else {
         rectF = new RectF(borderWidth / 2, height / 2 - min / 2 + borderWidth / 2, width - borderWidth / 2, height / 2 - borderWidth / 2 + min / 2);
     }
     canvas.drawArc(rectF, -90, progress, false, borderPaint);
     //画居中的文字
     canvas.translate(width / 2, height / 2 - staticLayout.getHeight() / 2);
     staticLayout.draw(canvas);
 }
  1. 这里有必要提一下的是StaticLayout这个类。如果我们用canvas.drawText这个方法,也是可以的,但是有个问题,这个方法写出来的文字是单行的,不会回行显示,但是迅雷中的“跳过广告”4个字是分两行显示的,这个时候我们就需要用到StaticLayout这个类了。这个类使用起来也很简单,具体的使用方法请参照其它博客。

其实到这里,整个控件已经写完了,但是我们希望这个控件在开始计时的时候给我们一个提示,在结束的时候再给我们一个提示,好让我们进行额外的操作。
我们的解决办法是给外界暴露一个接口,直接看代码吧!

public void start() {
        if (listener != null) {
            listener.onStartCount();
        }
        CountDownTimer countDownTimer = new CountDownTimer(3600, 36) {
            @Override
            public void onTick(long millisUntilFinished) {
                progress = ((3600 - millisUntilFinished) / 3600f) * 360;
                Log.d(TAG, "progress:" + progress);
                invalidate();
            }

            @Override
            public void onFinish() {
                progress = 360;
                invalidate();
                if (listener != null) {
                    listener.onFinishCount();
                }
            }
        }.start();
    }

    public void setCountDownTimerListener(CountDownTimerListener listener) {
        this.listener = listener;
    }

    public interface CountDownTimerListener {

        void onStartCount();

        void onFinishCount();
    }

我们定义了一个接口,里面有两个方法,onStartCount()和onFinishCount()
public void start()这个方法是用来启动计时器的,调用这个方法之后,计时程序就会开始了,开始的时候会调用onStartCount这个接口,然后计时的过程中会根据process(进度)不断地重绘整个View,达到动画效果,最后结束的时候会调用onFinishCount这个接口

看一下整体的代码吧:

package com.example.customview;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.CountDownTimer;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

/**
 * Created on 2016/10/8.
 */

public class CountDownView extends View {

    private static final String TAG = CountDownView.class.getSimpleName();
    private static final int BACKGROUND_COLOR = 0x50555555;
    private static final float BORDER_WIDTH = 15f;
    private static final int BORDER_COLOR = 0xFF6ADBFE;
    private static final String TEXT = "跳过广告";
    private static final float TEXT_SIZE = 50f;
    private static final int TEXT_COLOR = 0xFFFFFFFF;

    private int backgroundColor;
    private float borderWidth;
    private int borderColor;
    private String text;
    private int textColor;
    private float textSize;

    private Paint circlePaint;
    private TextPaint textPaint;
    private Paint borderPaint;

    private float progress = 0;
    private StaticLayout staticLayout;

    private CountDownTimerListener listener;

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

    public CountDownView(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.CountDownView);
        backgroundColor = ta.getColor(R.styleable.CountDownView_background_color, BACKGROUND_COLOR);
        borderWidth = ta.getDimension(R.styleable.CountDownView_border_width, BORDER_WIDTH);
        borderColor = ta.getColor(R.styleable.CountDownView_border_color, BORDER_COLOR);
        text = ta.getString(R.styleable.CountDownView_text);
        if (text == null) {
            text = TEXT;
        }
        textSize = ta.getDimension(R.styleable.CountDownView_text_size, TEXT_SIZE);
        textColor = ta.getColor(R.styleable.CountDownView_text_color, TEXT_COLOR);
        ta.recycle();
        init();
    }

    private void init() {
        circlePaint = new Paint();
        circlePaint.setAntiAlias(true);
        circlePaint.setDither(true);
        circlePaint.setColor(backgroundColor);
        circlePaint.setStyle(Paint.Style.FILL);

        textPaint = new TextPaint();
        textPaint.setAntiAlias(true);
        textPaint.setDither(true);
        textPaint.setColor(textColor);
        textPaint.setTextSize(textSize);
        textPaint.setTextAlign(Paint.Align.CENTER);

        borderPaint = new Paint();
        borderPaint.setAntiAlias(true);
        borderPaint.setDither(true);
        borderPaint.setColor(borderColor);
        borderPaint.setStrokeWidth(borderWidth);
        borderPaint.setStyle(Paint.Style.STROKE);

        int textWidth = (int) textPaint.measureText(text.substring(0, (text.length() + 1) / 2));
        staticLayout = new StaticLayout(text, textPaint, textWidth, Layout.Alignment.ALIGN_NORMAL, 1F, 0, false);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int width = MeasureSpec.getSize(widthMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int height = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        if (widthMode != MeasureSpec.EXACTLY) {
            width = staticLayout.getWidth();
        }
        if (heightMode != MeasureSpec.EXACTLY) {
            height = staticLayout.getHeight();
        }
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        int width = getMeasuredWidth();
        int height = getMeasuredHeight();
        int min = Math.min(width, height);
        //画底盘
        canvas.drawCircle(width / 2, height / 2, min / 2, circlePaint);
        //画边框
        RectF rectF;
        if (width > height) {
            rectF = new RectF(width / 2 - min / 2 + borderWidth / 2, 0 + borderWidth / 2, width / 2 + min / 2 - borderWidth / 2, height - borderWidth / 2);
        } else {
            rectF = new RectF(borderWidth / 2, height / 2 - min / 2 + borderWidth / 2, width - borderWidth / 2, height / 2 - borderWidth / 2 + min / 2);
        }
        canvas.drawArc(rectF, -90, progress, false, borderPaint);
        //画居中的文字
//       canvas.drawText("稍等片刻", width / 2, height / 2 - textPaint.descent() + textPaint.getTextSize() / 2, textPaint);
        canvas.translate(width / 2, height / 2 - staticLayout.getHeight() / 2);
        staticLayout.draw(canvas);
    }

    public void start() {
        if (listener != null) {
            listener.onStartCount();
        }
        CountDownTimer countDownTimer = new CountDownTimer(3600, 36) {
            @Override
            public void onTick(long millisUntilFinished) {
                progress = ((3600 - millisUntilFinished) / 3600f) * 360;
                Log.d(TAG, "progress:" + progress);
                invalidate();
            }

            @Override
            public void onFinish() {
                progress = 360;
                invalidate();
                if (listener != null) {
                    listener.onFinishCount();
                }
            }
        }.start();
    }

    public void setCountDownTimerListener(CountDownTimerListener listener) {
        this.listener = listener;
    }

    public interface CountDownTimerListener {

        void onStartCount();

        void onFinishCount();
    }
}

这次比较懒,没有写什么注释
最后,我们来看看如何使用这个自定义View,我们现在布局文件中引用这个布局

<com.example.customview.CountDownView
        android:id="@+id/countDownView"
        android:layout_width="50dp"
        android:layout_height="50dp"
        app:background_color="#22000000"
        app:border_color="#55B8E2"
        app:border_width="2dp"
        app:text_size="12dp" />

定义了宽度,高度,背景色,边框颜色,边框粗细,文字大小
然后到MainActivity中去使用这个自定义View

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private long lastTime;

    private CountDownView count_down_view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
    }

    private void initView() {
        count_down_view = (CountDownView) findViewById(R.id.countDownView);
        count_down_view.setCountDownTimerListener(new CountDownView.CountDownTimerListener() {
            @Override
            public void onStartCount() {
                Toast.makeText(getApplicationContext(),"开始计时",Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onFinishCount() {
                Toast.makeText(getApplicationContext(),"计时结束",Toast.LENGTH_SHORT).show();
            }
        });
        count_down_view.setOnClickListener(this);
    }

    //连按两次退出应用程序
    @Override
    public void onBackPressed() {
        long currentTime = System.currentTimeMillis();
        if (currentTime - lastTime < 2 * 1000) {
            super.onBackPressed();
        } else {
            Toast.makeText(this, "请再按一次", Toast.LENGTH_SHORT).show();
            lastTime = currentTime;
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.countDownView:
                count_down_view.start();
                break;
        }
    }
}

中间穿插着一些连按两次Back键退出应用程序的代码,所以代码量比较多,其实使用起来还是很方便的

效果图已经在文章开头发了,gif我就不发了,因为不会弄。