Android自定义View之可随时暂停、开启的圆形下载进度条
来源:互联网 发布:淘宝美工做什么工作的 编辑:程序博客网 时间:2024/06/05 19:26
请尊重个人劳动成果,转载注明出处,谢谢!
http://blog.csdn.net/xiaxiazaizai01/article/details/52355558
这是一个一言不合就手撸一个自定义View的任性时代,因此最近一段时间一直在学习自定义View相关的知识,也看了很多与此相关的博客,有句话叫做不要重复造轮子,别人写好的直接拿过来改吧改吧,能用就行,但是,要想像那些任性的大牛一样,分分钟撸一个自定义View,就得不断的重复造轮子,学习大神们的设计思路, 站在牛人的肩膀上不断前行,每篇开篇之前都要啰嗦半天,急性子的童鞋可以直接跳过。看到yissan大牛写了一篇自定义圆形进度条,思路很清晰,就照着也撸了一遍,果然是酸爽啊,在这里非常感谢yissan大牛,哈哈。。。为了让大家能一遍就看懂,我会把注释写的非常非常详细,秒懂哦,,什么??你不能秒懂。。注释都写的辣么详细了,面壁思过去。。哈哈
下面看下效果图:
1、首先创建View
(1)设置自定义View属性,通常做法是在res/values里面创建一个attrs文件夹,来写我们的自定义属性,一般我们设置属性的name时,一般习惯性的将我们自定义的类名作为name
<?xml version="1.0" encoding="utf-8"?><resources> <!-- 自定义圆形进度条,属性设置 --> <declare-styleable name="CustomCircleProgress"> <!-- 默认圆的颜色 --> <attr name="progress_default_color" format="color"/> <!-- 进度圆的颜色 --> <attr name="progress_reached_color" format="color"/> <!-- 进度条的高度 --> <attr name="progress_reached_height" format="dimension"/> <!-- 无进度时(默认圆)的边框高 --> <attr name="progress_default_height" format="dimension"/> <!-- 圆的半径 --> <attr name="circle_radius" format="dimension"/> </declare-styleable></resources>
(2)设置完了自定义属性,下一步当然是在我们的自定义View类中去获取。(我们都习惯在参数多的构造方法中去获取自定义属性,其他构造方法则去通过this去调用,注意这里是this而不是super,super的话则指向的是父类,这里我犯了一个常识性错误,一键生成几个构造方法,忘了将super改成this,导致获取属性的方法没有被调用执行,大家在调用的时候可以打断点试试)
public CustomCircleProgress(Context context) { this(context,null); } public CustomCircleProgress(Context context, AttributeSet attrs) { this(context, attrs,0); } public CustomCircleProgress(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); //获取自定义属性的值 TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.CustomCircleProgress); //默认圆的颜色 mDefaultColor = array.getColor(R.styleable.CustomCircleProgress_progress_default_color, PROGRESS_DEFAULT_COLOR); //进度条的颜色 mReachedColor = array.getColor(R.styleable.CustomCircleProgress_progress_reached_color, PROGRESS_REACHED_COLOR); //默认圆的高度 mDefaultHeight = (int) array.getDimension(R.styleable.CustomCircleProgress_progress_default_height, mDefaultHeight); //进度条的高度 mReachedHeight = (int) array.getDimension(R.styleable.CustomCircleProgress_progress_reached_height, mReachedHeight); //圆的半径 mRadius = (int) array.getDimension(R.styleable.CustomCircleProgress_circle_radius, mRadius); //最后不要忘了回收 TypedArray array.recycle(); //设置画笔(new画笔的操作一般不要放在onDraw方法中,因为在绘制的过程中onDraw方法会被多次调用) setPaint();
我们在new我们的画笔时一般不要在onDraw()方法中去new,因为view在不断的绘制过程中onDraw()方法会不断的被调用,这样就会造成不停的new我们的画笔实例。
//设置画笔private void setPaint() { mPaint = new Paint(); //下面是设置画笔的一些属性 mPaint.setAntiAlias(true);//抗锯齿 mPaint.setDither(true);//防抖动,绘制出来的图要更加柔和清晰 mPaint.setStyle(Paint.Style.STROKE);//设置填充样式 /** * Paint.Style.FILL :填充内部 * Paint.Style.FILL_AND_STROKE :填充内部和描边 * Paint.Style.STROKE :仅描边 */ mPaint.setStrokeCap(Paint.Cap.ROUND);//设置画笔笔刷类型 }
2、处理View的布局,即测量onMeasure( )
当我们在xml文件中给这个view设置android:layout_width=”“android:layout_height=”“属性为固定值、wrap_parent、match_parent 时,表明开发者向ViewGroup沟通表明我需要的空间。ViewGroup收到了开发者对View大小的说明,然后ViewGroup会综合考虑自己的空间大小以及开发者的请求,然后生成两个MeasureSpec对象(width与height)传给View,这两个对象是ViewGroup向子View提出的要求,就相当于告诉子View:“我已经与你的使用者(开发者)商量过了,现在把我们商量确定的结果告诉你,你的宽度不能违反width MeasureSpec对象的要求,你的高度不能违反height MeasureSpec对象的要求,现在,你赶紧根据这个要求确定下自己要多大空间,只许少,不许多哦。”对于超过ViewGroup为我们分配的空间时,就需要进行测量处理,然后再将处理后的结果反馈给ViewGroup,如果不是很了解的话可以点击查看上一篇博客,有详细的说明
/** * 使用onMeasure方法是因为我们的自定义圆形View的一些属性(如:进度条宽度等)都交给用户自己去自定义了,所以我们需要去测量下 * 看是否符合要求 * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); int paintHeight = Math.max(mReachedHeight, mDefaultHeight);//比较两数,取最大值 if(heightMode != MeasureSpec.EXACTLY){ //如果用户没有精确指出宽高时,我们就要测量整个View所需要分配的高度了,测量自定义圆形View设置的上下内边距+圆形view的直径+圆形描边边框的高度 int exceptHeight = getPaddingTop() + getPaddingBottom() + mRadius*2 + paintHeight; //然后再将测量后的值作为精确值传给父类,告诉他我需要这么大的空间,你给我分配吧 heightMeasureSpec = MeasureSpec.makeMeasureSpec(exceptHeight, MeasureSpec.EXACTLY); } if(widthMode != MeasureSpec.EXACTLY){ //这里在自定义属性中没有设置圆形边框的宽度,所以这里直接用高度代替 int exceptWidth = getPaddingLeft() + getPaddingRight() + mRadius*2 + paintHeight; widthMeasureSpec = MeasureSpec.makeMeasureSpec(exceptWidth, MeasureSpec.EXACTLY); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); }
我们需要考虑开发者有时会给View设置一些padding属性
3、绘制View,即onDraw()
(1)这里我们需要绘制默认的内部圆以及表示进度的外层圆弧,根据进度值的变化来绘制圆弧。在绘制外层表示进度的圆弧时,需要首先确定圆弧的外接矩形(进度也就成了内切圆)的坐标,如下图所示
@Override protected synchronized void onDraw(Canvas canvas) { super.onDraw(canvas); /** * 这里canvas.save();和canvas.restore();是两个相互匹配出现的,作用是用来保存画布的状态和取出保存的状态的 * 当我们对画布进行旋转,缩放,平移等操作的时候其实我们是想对特定的元素进行操作,但是当你用canvas的方法来进行这些操作的时候,其实是对整个画布进行了操作, * 那么之后在画布上的元素都会受到影响,所以我们在操作之前调用canvas.save()来保存画布当前的状态,当操作之后取出之前保存过的状态, * (比如:前面元素设置了平移或旋转的操作后,下一个元素在进行绘制之前执行了canvas.save();和canvas.restore()操作)这样后面的元素就不会受到(平移或旋转的)影响 */ canvas.save(); //为了保证最外层的圆弧全部显示,我们通常会设置自定义view的padding属性,这样就有了内边距,所以画笔应该平移到内边距的位置,这样画笔才会刚好在最外层的圆弧上 //画笔平移到指定paddingLeft, getPaddingTop()位置 canvas.translate(getPaddingLeft(),getPaddingTop()); mPaint.setStyle(Paint.Style.STROKE); //画默认圆(边框)的一些设置 mPaint.setColor(mDefaultColor); mPaint.setStrokeWidth(mDefaultHeight); canvas.drawCircle(mRadius,mRadius,mRadius,mPaint); //画进度条的一些设置 mPaint.setColor(mReachedColor); mPaint.setStrokeWidth(mReachedHeight); //根据进度绘制圆弧 float sweepAngle = getProgress() * 1.0f / getMax() * 360; canvas.drawArc(new RectF(0, 0, mRadius * 2, mRadius *2), 0, sweepAngle, false, mPaint);//drawArc:绘制圆弧 canvas.restore(); }
我们做个定时器,让进度条动起来
public class MainActivity extends AppCompatActivity { private CustomCircleProgress circleProgress; private int progress; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); circleProgress = (CustomCircleProgress) findViewById(R.id.circleProgress); Timer timer = new Timer(); TimerTask task = new TimerTask() { @Override public void run() { if(progress >= 100){ progress = 0; circleProgress.setProgress(0); }else{ progress = circleProgress.getProgress(); circleProgress.setProgress(++progress); } } }; timer.schedule(task,0,100); }}
这样得到的效果图是这样的
有的伙计该说了,为什么进度条的起始位置不是从最上面开始的,因为这里我设置的canvas.drawArc(new RectF(0, 0, mRadius * 2, mRadius 2), 0, sweepAngle, false, mPaint);中我设置的参数为0,表示圆弧的起始位置从0开始,即X轴的正方向。这里我们只需将圆弧的起始位置设置成-90度即可,canvas.drawArc(new RectF(0, 0, mRadius 2, mRadius *2), -90, sweepAngle, false, mPaint);我们再来看下效果图
完美,,哈哈,,,,
//绘制圆public void drawCircle (float cx, float cy, float radius, Paint paint)//参数说明/*** cx:圆心的x坐标。 cy:圆心的y坐标。 radius:圆的半径。 paint:绘制时所使用的画笔。*/
(2)接下来,我们开始绘制里面的暂停(完成)状态时的三角形,以及开启状态时的两条竖线,首先我们通过枚举的方式定义这两种状态,并提供set/get方法供外界调用。首先我们需要Path mPath = new Path();然后通过mPath.moveTo()确定三角形的第一个点的坐标,然后通过mPath.lineTo()链接其他几个点的坐标,如果当我们设置画笔的样式为mPaint.setStyle(Paint.Style.STROKE);则我们需要执行close形成封闭的三角形,或者你也可以直接再来一条mPath.lineTo()再将第一个点的坐标给连接起来,这样也形成了一个封闭的三角形。
//通过path路径绘制三角形 mPath = new Path(); //让三角形的长度等于圆的半径(等边三角形) triangleLength = mRadius; //绘制三角形,首先我们需要确定三个点的坐标 float firstX = (float) ((mRadius*2 - Math.sqrt(3.0) / 2 * mRadius) / 2);//左上角第一个点的横坐标,根据勾三股四弦五定律,Math.sqrt(3.0)表示开方 //为了显示的好看些,这里微调下firstX横坐标 float mFirstX = (float)(firstX + firstX*0.2); float firstY = mRadius - triangleLength / 2; //同理,依次可得出第二个点(左下角)第三个点的坐标 float secondX = mFirstX; float secondY = (float) (mRadius + triangleLength / 2); float thirdX = (float) (mFirstX + Math.sqrt(3.0) / 2 * mRadius); float thirdY = mRadius; mPath.moveTo(mFirstX,firstY); mPath.lineTo(secondX,secondY); mPath.lineTo(thirdX,thirdY); mPath.lineTo(mFirstX,firstY);
然后我们在onDraw()方法中去判断绘制不同状态下的view
//有了path之后就可以在onDraw中绘制三角形的End和Starting状态了 if(mStatus == Status.End){//未开始状态,画笔填充三角形 mPaint.setStyle(Paint.Style.FILL); //设置颜色 mPaint.setColor(Color.parseColor("#01A1EB")); //画三角形 canvas.drawPath(mPath,mPaint); }else{//正在进行状态,画两条竖线 mPaint.setStyle(Paint.Style.STROKE); mPaint.setStrokeWidth(dp2px(5)); mPaint.setColor(Color.parseColor("#01A1EB")); canvas.drawLine(mRadius*2/3, mRadius*2/3, mRadius*2/3, 2*mRadius*2/3, mPaint); canvas.drawLine(2*mRadius - (mRadius*2/3), mRadius*2/3, 2*mRadius - (mRadius*2/3), 2*mRadius*2/3, mPaint); }
4、处理与用户的交互
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.customview.MainActivity"> <com.example.customview.CustomCircleProgress android:id="@+id/circleProgress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:padding="10dp" /></RelativeLayout>
最后是我们的MainActivity类
public class MainActivity extends AppCompatActivity { private CustomCircleProgress circleProgress; private int progress; private Handler handler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case PROGRESS_CIRCLE_STARTING: progress = circleProgress.getProgress(); circleProgress.setProgress(++progress); if(progress >= 100){ handler.removeMessages(PROGRESS_CIRCLE_STARTING); progress = 0; circleProgress.setProgress(0); circleProgress.setStatus(CustomCircleProgress.Status.End);//修改显示状态为完成 }else{ //延迟100ms后继续发消息,实现循环,直到progress=100 handler.sendEmptyMessageDelayed(PROGRESS_CIRCLE_STARTING, 100); } break; } } }; public static final int PROGRESS_CIRCLE_STARTING = 0x110; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); circleProgress = (CustomCircleProgress) findViewById(R.id.circleProgress); circleProgress.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if(circleProgress.getStatus() == CustomCircleProgress.Status.Starting){//如果是开始状态 //点击则变成关闭暂停状态 circleProgress.setStatus(CustomCircleProgress.Status.End); //注意,当我们暂停时,同时还要移除消息,不然的话进度不会被停止 handler.removeMessages(PROGRESS_CIRCLE_STARTING); }else{ //点击则变成开启状态 circleProgress.setStatus(CustomCircleProgress.Status.Starting); Message message = Message.obtain(); message.what = PROGRESS_CIRCLE_STARTING; handler.sendMessage(message); } } }); }}
最后,希望对你能有所帮助,有问题欢迎留言,大家一块探讨,写博客确实挺累的。。。有需要源码的,可以点击下载源码
- Android自定义View之可随时暂停、开启的圆形下载进度条
- 自定义view之实现圆形进度条加载,颜色渐变,可暂停,可循环加载
- Android自定义View之圆形进度条
- Android-自定义View之圆形进度条总结
- Android自定义view之圆形进度条
- 自定义view之圆形进度条
- 自定义view之圆形进度条
- 自定义view之圆形进度条
- android 圆形进度条 自定义view
- android 自定义view 圆形进度条
- Android自定义View---圆形进度条
- Android-------自定义View圆形进度条
- Android自定义view(圆形进度条)
- Android自定义view圆形进度条
- Android自定义view圆形进度条
- Android自定义View-圆形进度条
- Android 自定义View制作随时间增长的平滑进度条
- Android自定义View绘制圆形、方形、弧形、球形四种形态的模仿下载进度条
- ccf201604-3路径解析
- 一个会转的图案(绕右侧中点/绕一侧)
- css3-多列布局
- 第四章 Generics - 泛型
- 《Java源码分析》:PriorityQueue
- Android自定义View之可随时暂停、开启的圆形下载进度条
- web项目实战实现焦点图轮播
- gpiolib及gpio操作
- 最优化导论1
- Java解析json——Jackson
- Spring Integration
- 特征选择与特征学习
- Java中synchronized使用的一点见解
- MogileFS与FastDFS的个人见解