自定义控件之仿启动页右上角的圆形跳过按钮倒计时功能

说明下:此文有用到严振杰大神的部分代码,加上自己对其代码的探究。最后来写的这边博文。这篇博文初学自定义控件的工程师可以看看。里面讲解的很详细,也算是自己对自定义控件的一个总结探索吧。如有讲解不对地方,望大家留言指出。大笑大笑


最终效果图:



自定义控件步骤:

1:自定义属性

2:获取自定义属性,测量,画图(自定义控件也分很多种类型,有继承控件,继承view,组合控件)

3:xml中添加命名空间并引用


第一步:在values文件夹下新建attrs.xml

[java]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8" ?>  
  2. <resources>  
  3.     <declare-styleable name="CountDownProgress">  
  4.   
  5.         <!-- 圆实心的颜色 -->  
  6.         <attr name="circSolidColor" format="color"></attr>  
  7.   
  8.         <!-- 圆边框的颜色 -->  
  9.         <attr name="circFrameColor" format="color"></attr>  
  10.   
  11.         <!-- 进度条的颜色 -->  
  12.         <attr name="progressColor" format="color"></attr>  
  13.   
  14.         <!-- 文字的颜色 -->  
  15.         <attr name="textColor" format="color"></attr>  
  16.   
  17.     </declare-styleable>  
  18. </resources>  

第二步:新建java类继承view或textview(这里我直接继承textview)

[java]  view plain  copy
  1. package cn.lemon.countdownview;  
  2.   
  3. import android.content.Context;  
  4. import android.content.res.TypedArray;  
  5. import android.graphics.Canvas;  
  6. import android.graphics.Color;  
  7. import android.graphics.Paint;  
  8. import android.graphics.Rect;  
  9. import android.graphics.RectF;  
  10. import android.util.AttributeSet;  
  11. import android.util.Log;  
  12. import android.view.MotionEvent;  
  13. import android.widget.TextView;  
  14.   
  15. import java.util.Timer;  
  16.   
  17. /** 
  18.  * Created by W61 on 2016/12/12. 
  19.  */  
  20.   
  21. public class CountDownProgressView extends TextView{  
  22.     //TODO 圆实心的颜色  
  23.     private int circSolidColor;  
  24.   
  25.     //TODO 圆边框的颜色  
  26.     private int circFrameColor;  
  27.   
  28.     //TODO 圆边框的宽度  
  29.     private int circFrameWidth = 4;  
  30.   
  31.     //TODO 圆的半径  
  32.     private int circRadius;  
  33.   
  34.     //TODO 进度条的颜色  
  35.     private int progressColor;  
  36.   
  37.     //TODO 进度条的宽度  
  38.     private int progressWidth = 4;  
  39.   
  40.     //TODO 文字的颜色  
  41.     private int textColor;  
  42.   
  43.   
  44.     private Rect mBounds;  
  45.     private Paint mPaint;  
  46.     private RectF mArcRectF;  
  47.   
  48.     private int mCenterX;  
  49.     private int mCenterY;  
  50.   
  51.     private String mText = "跳过";  
  52.   
  53.   
  54.     //TODO 进度倒计时时间  
  55.     private long timeMillis = 3000;  
  56.   
  57.     //TODO 进度条通知  
  58.     private OnProgressListener mProgressListener;  
  59.   
  60.     //TODO 进度默认100  
  61.     private int progress = 100;  
  62.   
  63.     //TODO 进度条类型  
  64.     private ProgressType mProgressType = ProgressType.COUNT_BACK;  
  65.   
  66.     /** 
  67.      * 进度条类型(方便做一个扩展,这里默认的是倒数进度条) 
  68.      */  
  69.     public enum ProgressType {  
  70.         /** 
  71.          * 顺数进度条,从0-100; 
  72.          */  
  73.         COUNT,  
  74.   
  75.         /** 
  76.          * 倒数进度条,从100-0; 
  77.          */  
  78.         COUNT_BACK;  
  79.     }  
  80.   
  81.     /** 
  82.      * 设置进度条类型。 
  83.      * 
  84.      * @param progressType {@link ProgressType}. 
  85.      */  
  86.     public void setProgressType(ProgressType progressType) {  
  87.         this.mProgressType = progressType;  
  88.         resetProgress();  
  89.         /** 
  90.          * 请求重绘View 
  91.          * 1,直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。 
  92.          * 2,setSelection()方法 :请求重新draw(),但只会绘制调用者本身。 
  93.          * 3,setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法,继而绘制该View 
  94.          * 4 ,setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。 
  95.          */  
  96.         invalidate();  
  97.     }  
  98.   
  99.     /** 
  100.      * 进度监听。 
  101.      */  
  102.     public interface OnProgressListener {  
  103.   
  104.         /** 
  105.          * 进度通知 
  106.          * 
  107.          * @param progress 进度值。 
  108.          */  
  109.         void onProgress(int progress);  
  110.     }  
  111.   
  112.     public CountDownProgressView(Context context) {  
  113.         super(context);  
  114.         init();  
  115.     }  
  116.   
  117.     /** 
  118.      * 获取attrs。xml文件中的自定义属性 
  119.      * @param context 
  120.      * @param attrs 
  121.      */  
  122.     public CountDownProgressView(Context context, AttributeSet attrs) {  
  123.         super(context, attrs);  
  124.         init();  
  125.         TypedArray typedArray =  getContext().obtainStyledAttributes(attrs,R.styleable.CountDownProgress);  
  126.         if (typedArray != null){  
  127.             //TODO 圆实心的颜色属性  
  128.             if (typedArray.hasValue(R.styleable.CountDownProgress_circSolidColor)){  
  129.                 //有这个属性索引 获取用户设置的颜色  
  130.                 circSolidColor = typedArray.getColor(R.styleable.CountDownProgress_circSolidColor,0);  
  131.             }else{  
  132.                 //没有这个属性索引 默认是浅灰色  
  133.                 circSolidColor = typedArray.getColor(R.styleable.CountDownProgress_circSolidColor,Color.parseColor("#D3D3D3"));  
  134.             }  
  135.   
  136.             //TODO 园边框颜色属性  
  137.             if (typedArray.hasValue(R.styleable.CountDownProgress_circFrameColor)){  
  138.                 //有这个属性索引 获取用户设置的颜色  
  139.                 circFrameColor = typedArray.getColor(R.styleable.CountDownProgress_circFrameColor,0);  
  140.             }else{  
  141.                 //没有这个属性索引 默认是深灰色  
  142.                 circFrameColor = typedArray.getColor(R.styleable.CountDownProgress_circFrameColor,Color.parseColor("#A9A9A9"));  
  143.             }  
  144.   
  145.             //TODO 文字颜色属性  
  146.             if (typedArray.hasValue(R.styleable.CountDownProgress_textColor)){  
  147.                 //有这个属性索引 获取用户设置的颜色  
  148.                 textColor = typedArray.getColor(R.styleable.CountDownProgress_textColor,0);  
  149.             }else{  
  150.                 //没有这个属性索引 默认是白色  
  151.                 textColor = typedArray.getColor(R.styleable.CountDownProgress_textColor,Color.parseColor("#ffffff"));  
  152.             }  
  153.   
  154.             //TODO 进度条颜色属性  
  155.             if (typedArray.hasValue(R.styleable.CountDownProgress_progressColor)){  
  156.                 //有这个属性索引 获取用户设置的颜色  
  157.                 progressColor = typedArray.getColor(R.styleable.CountDownProgress_progressColor,0);  
  158.             }else{  
  159.                 //没有这个属性索引 默认是蓝色  
  160.                 progressColor = typedArray.getColor(R.styleable.CountDownProgress_progressColor,Color.parseColor("#0000FF"));  
  161.             }  
  162.   
  163.             /** 
  164.              * 这里千万不要忘记回收 
  165.              */  
  166.             typedArray.recycle();  
  167.         }  
  168.   
  169.     }  
  170.   
  171.     /** 
  172.      * 初始化画笔画布等 
  173.      */  
  174.     public void init() {  
  175.         mPaint = new Paint();  
  176.         mBounds = new Rect();  
  177.         mArcRectF = new RectF();  
  178.     }  
  179.   
  180.   
  181.     /** 
  182.      * 
  183.      * 重写测量方法 
  184.      * @param widthMeasureSpec 
  185.      * @param heightMeasureSpec 
  186.      */  
  187.     @Override  
  188.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  189.         super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  190.         int width = getMeasuredWidth();  
  191.         int height = getMeasuredHeight();  
  192.         if (width > height) {  
  193.             height = width;  
  194.         } else {  
  195.             width = height;  
  196.         }  
  197.         circRadius = width / 2;  
  198.         setMeasuredDimension(width, height);  
  199.     }  
  200.   
  201.     /** 
  202.      * 绘制过程 
  203.      *  注意:canvas在绘制过程中是一层层覆盖的。所以绘制顺序不要写反了。如果先绘制实心圆在绘制文字在绘制空心圆。这时你会发现文字是看不到了,被覆盖了。 
  204.      * @param canvas 
  205.      */  
  206.     @Override  
  207.     protected void onDraw(Canvas canvas) {  
  208.         super.onDraw(canvas);  
  209.         /** 
  210.          *  获取view的边界 
  211.          */  
  212.         getDrawingRect(mBounds);  
  213.   
  214.         mCenterX = mBounds.centerX();  
  215.         mCenterY = mBounds.centerY();  
  216.   
  217.         //TODO 画实心圆  
  218.         mPaint.setAntiAlias(true); //设置抗锯齿  
  219.         mPaint.setStyle(Paint.Style.FILL); //实心填充style  
  220.         mPaint.setColor(circSolidColor);  
  221.         canvas.drawCircle(mBounds.centerX(), mBounds.centerY(), circRadius, mPaint);  
  222.   
  223.   
  224.         //TODO 画外边框(空心圆,即园边框)  
  225.         mPaint.setAntiAlias(true);//设置抗锯齿  
  226.         mPaint.setStyle(Paint.Style.STROKE);//空心style  
  227.         mPaint.setStrokeWidth(circFrameWidth);//设置空心线宽度  
  228.         mPaint.setColor(circFrameColor);  
  229.         canvas.drawCircle(mBounds.centerX(), mBounds.centerY(), circRadius - circFrameWidth, mPaint);  
  230.   
  231.         //TODO 画文字  
  232.         Paint text_paint = getPaint(); //注意:如果是继承的view,这里是没有这个getPaint()方法的。大家可以看到它是Textview包下的方法  
  233.         text_paint.setColor(textColor);  
  234.         text_paint.setAntiAlias(true);  
  235.         text_paint.setTextAlign(Paint.Align.CENTER);  
  236.         float textY = mCenterY - (text_paint.descent() + text_paint.ascent()) / 2;  
  237.         canvas.drawText(mText, mCenterX, textY, text_paint);  
  238.   
  239.   
  240.         //TODO 画进度条  
  241.         mPaint.setColor(progressColor);  
  242.         mPaint.setStyle(Paint.Style.STROKE);  
  243.         mPaint.setStrokeWidth(progressWidth);  
  244.         mPaint.setStrokeCap(Paint.Cap.ROUND);  //当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式,如圆形样式  Cap.ROUND,或方形样式Cap.SQUARE  
  245.         mArcRectF.set(mBounds.left + progressWidth, mBounds.top + progressWidth, mBounds.right - progressWidth, mBounds.bottom - progressWidth);  
  246.         canvas.drawArc(mArcRectF, -90360 * progress / 100false, mPaint); //这里的-90.是方向,大家可以改成0,90,180,-90等就可以看到效果区别  
  247.   
  248.   
  249.     }  
  250.   
  251.   
  252.     /** 
  253.      * 开始。 
  254.      */  
  255.     public void start() {  
  256.         stop();  
  257.         post(progressChangeTask);  
  258.     }  
  259.     /** 
  260.      * 停止。 
  261.      */  
  262.     public void stop() {  
  263.         removeCallbacks(progressChangeTask);  
  264.     }  
  265.   
  266.     /** 
  267.      * 重新开始。 
  268.      */  
  269.     public void reStart() {  
  270.         resetProgress();  
  271.         start();  
  272.     }  
  273.   
  274.   
  275.     /** 
  276.      * 重置进度。 
  277.      */  
  278.     private void resetProgress() {  
  279.         switch (mProgressType) {  
  280.             case COUNT:  
  281.                 progress = 0;  
  282.                 break;  
  283.             case COUNT_BACK:  
  284.                 progress = 100;  
  285.                 break;  
  286.         }  
  287.     }  
  288.   
  289.     /** 
  290.      * 进度更新task 
  291.      */  
  292.     private Runnable progressChangeTask = new Runnable() {  
  293.         @Override  
  294.         public void run() {  
  295.             removeCallbacks(this);  
  296.             switch (mProgressType) {  
  297.                 case COUNT:  
  298.                     progress += 1;  
  299.                     break;  
  300.                 case COUNT_BACK:  
  301.                     progress -= 1;  
  302.                     break;  
  303.             }  
  304.             if (progress >= 0 && progress <= 100) {  
  305.                 if (mProgressListener != null)  
  306.                     mProgressListener.onProgress(progress);  
  307.                 invalidate();  
  308.                 postDelayed(progressChangeTask, timeMillis / 60);  
  309.             } else  
  310.                 progress = validateProgress(progress);  
  311.         }  
  312.     };  
  313.   
  314.     /** 
  315.      * 验证进度。 
  316.      * 
  317.      * @param progress 你要验证的进度值。 
  318.      * @return 返回真正的进度值。 
  319.      */  
  320.     private int validateProgress(int progress) {  
  321.         if (progress > 100)  
  322.             progress = 100;  
  323.         else if (progress < 0)  
  324.             progress = 0;  
  325.         return progress;  
  326.     }  
  327.   
  328.   
  329.     /** 
  330.      * 设置文字,默认为"跳过" 
  331.      * @param text 
  332.      */  
  333.     public void setText(String text) {  
  334.         this.mText = text;  
  335.     }  
  336.   
  337.   
  338.     /** 
  339.      * 设置倒计时总时间,默认3000 
  340.      * 
  341.      * @param timeMillis 毫秒。 
  342.      */  
  343.     public void setTimeMillis(long timeMillis) {  
  344.         this.timeMillis = timeMillis;  
  345.         invalidate();  
  346.     }  
  347.   
  348.     /** 
  349.      * 设置进度监听 
  350.      * 
  351.      * @param mProgressListener 监听器。 
  352.      */  
  353.     public void setProgressListener(OnProgressListener mProgressListener) {  
  354.         this.mProgressListener = mProgressListener;  
  355.     }  
  356.   
  357.   
  358.     /** 
  359.      * 注意:由于我们是继承的textview,而textview本身就有setOnClickListener方法,所以可以直接在activity监听点击事件即可 
  360.      *       这里重写onTouchEvent方法来判断点击是否在园内,是方便有需要或刚接触自定义控件的人学习的。 
  361.      *       这2种方式都有一个小缺陷就是它计算的其实是矩形范围内。也就是你稍微偏离园外4个角点击都是可以响应的。 
  362.      * @param event 
  363.      * @return 
  364.      */  
  365.     @Override  
  366.     public boolean onTouchEvent(MotionEvent event) {  
  367.         switch (event.getAction()){  
  368.             case MotionEvent.ACTION_DOWN:  
  369.                 int x = (int) event.getX();  
  370.                 int y = (int) event.getY();  
  371.                 if (Math.abs(x-(mBounds.centerX()))<=(circRadius)*2 && Math.abs(y -  (mBounds.centerY())) <=(circRadius)*2   ){  
  372.                     Log.e("countDownProgressView""-----------------onTouchEvent---------------------");  
  373.                 }  
  374.                 break;  
  375.             default:  
  376.         }  
  377.         return super.onTouchEvent(event);  
  378.     }  
  379. }  

第三步:

[java]  view plain  copy
  1. <?xml version="1.0" encoding="utf-8"?>  
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  3.     xmlns:app="http://schemas.android.com/apk/res-auto"  
  4.     android:id="@+id/activity_main"  
  5.     android:orientation="vertical"  
  6.     android:padding="10dp"  
  7.     android:layout_width="match_parent"  
  8.     android:layout_height="match_parent">  
  9.   
  10.     <cn.lemon.countdownview.CountDownProgressView  
  11.         android:id="@+id/countdownProgressView"  
  12.         android:layout_width="50dp"  
  13.         android:layout_height="50dp"  
  14.         android:textSize="15sp"  
  15.         app:circSolidColor = "#4541"/>  
  16.   
  17.   
  18.     <Button  
  19.         android:id="@+id/start_btn"  
  20.         android:layout_width="wrap_content"  
  21.         android:layout_height="wrap_content"  
  22.         android:text="开始"/>  
  23.   
  24.   
  25.     <Button  
  26.         android:id="@+id/stop_btn"  
  27.         android:layout_width="wrap_content"  
  28.         android:layout_height="wrap_content"  
  29.         android:text="停止"/>  
  30.   
  31.     <Button  
  32.         android:id="@+id/restart_btn"  
  33.         android:layout_width="wrap_content"  
  34.         android:layout_height="wrap_content"  
  35.         android:text="重新开始"/>  
  36.   
  37. </LinearLayout>  

[java]  view plain  copy
  1. countDownProgressView = (CountDownProgressView) findViewById(R.id.countdownProgressView);  
  2.        countDownProgressView.setProgressListener( new CountDownProgressView.OnProgressListener() {  
  3.            @Override  
  4.            public void onProgress(int progress) {  
  5.   
  6.            }  
  7.        });  


代码中注释非常详细了,相信大家都能理解,当然这个自定义的控件还可以做很多扩展,比如一些进度条颜色宽度都可以暴露出来在activity让开发者自己设置或者自定义这些属性,让开发者在xml中设置都可以。最后,希望对刚接触自定义控件的朋友来有点帮助大笑大笑


附上一些关于paint方法介绍的地址:点击打开链接

猜你喜欢

转载自blog.csdn.net/fengyenom1/article/details/79670302