自定义一个广告倒计时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>