自定义一个广告倒计时View
Generics
8年前
<p>今天打开迅雷手机客户端准备看片的时候,无意间发现这个自定义View,感觉很好看的,实现起来也不麻烦,就尝试着模仿了一下,花了一天,最后终于搞出来了。因为技术比较菜,所以时间有点长,总之慢慢来吧。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/58d612a3adc6db75cd92ad7910abed86.png"></p> <p style="text-align:center">迅雷截图</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/c9f793c95038b1a6c285de86875e2e9f.png"></p> <p style="text-align:center">自定义View效果图</p> <ol> <li> <h3><strong>自定义属性</strong></h3> <p>底盘的颜色</p> <p>进度条的颜色</p> <p>进度条粗细</p> <p>文字内容</p> <p>文字颜色</p> <p>文字大小</p> <pre> <code class="language-java"><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></code></pre> </li> <li> <h3><strong>自定义一个CountDownView,继承View</strong></h3> <pre> <code class="language-java">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); } }</code></pre> <p>重写了两个构造方法,然后对自定义属性进行了初始化</p> </li> <li> <h3><strong>重写onMeasure方法</strong></h3> <pre> <code class="language-java">@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); }</code></pre> <p>这个不多说,重写onMeasure方法是必须实现的,重写此方法的目的是测量控件的实际大小,因为有的时候用户填写的width和height是wrap_content,懂了吧,当wrap_content的时候,我们就需要测量控件的实际大小了</p> </li> <li> <h3><strong>重写onDraw方法</strong></h3> <pre> <code class="language-java">@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); }</code></pre> <p>这里有必要提一下的是StaticLayout这个类。如果我们用canvas.drawText这个方法,也是可以的,但是有个问题,这个方法写出来的文字是单行的,不会回行显示,但是迅雷中的“跳过广告”4个字是分两行显示的,这个时候我们就需要用到StaticLayout这个类了。这个类使用起来也很简单,具体的使用方法请参照其它博客。</p> </li> </ol> <p>其实到这里,整个控件已经写完了,但是我们希望这个控件在开始计时的时候给我们一个提示,在结束的时候再给我们一个提示,好让我们进行额外的操作。</p> <p>我们的解决办法是给外界暴露一个接口,直接看代码吧!</p> <pre> <code class="language-java">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(); }</code></pre> <p>我们定义了一个接口,里面有两个方法,onStartCount()和onFinishCount()</p> <p>public void start()这个方法是用来启动计时器的,调用这个方法之后,计时程序就会开始了,开始的时候会调用onStartCount这个接口,然后计时的过程中会根据process(进度)不断地重绘整个View,达到动画效果,最后结束的时候会调用onFinishCount这个接口</p> <p>看一下整体的代码吧:</p> <pre> <code class="language-java">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(); } }</code></pre> <p>这次比较懒,没有写什么注释</p> <p>最后,我们来看看如何使用这个自定义View,我们现在布局文件中引用这个布局</p> <pre> <code class="language-java"><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" /></code></pre> <p>定义了宽度,高度,背景色,边框颜色,边框粗细,文字大小</p> <p>然后到MainActivity中去使用这个自定义View</p> <pre> <code class="language-java">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; } } }</code></pre> <p>中间穿插着一些连按两次Back键退出应用程序的代码,所以代码量比较多,其实使用起来还是很方便的</p> <p> </p> <p> </p> <p>来自:http://www.jianshu.com/p/3db73ba78882</p> <p> </p>