自定义控件其实很简单2/3
Android 打造形形色色的进度条 实现可以如此简单
分类:【Android 精彩案例】【Android 自定义控件实战】2015-02-02 09:22262人阅读评论(10)收藏举报Android Progressbar 自定义 NumberProgressBar目录(?)[+]
转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/43371299,本文出自: 【张鸿洋的博客】1、概述
最近需要用进度条,秉着不重复造轮子的原则,上github上搜索了一番,看了几个觉得比较好看的ProgressBar,比如:daimajia的等。简单看了下代码,基本都是继承自View,彻彻底底的自定义了一个进度条。盯着那绚丽滚动条,忽然觉得,为什么要通过View去写一个滚动条,系统已经提供了ProgressBar以及属于它的特性,我们没必要重新去构建一个,但是系统的又比较丑,不同版本变现还不一定一样。那么得出我们的目标:改变系统ProgressBar的样子。
对没错,我们没有必要去从0打造一个ProgressBar,人家虽然长的不好看,但是特性以及稳定性还是刚刚的,我们只需要为其整下容就ok了。
说到整容,大家都知道我们的控件是通过onDraw()画出来的,那么我们只需要去覆盖它的onDraw()方法,自己写下就ok 。
对了,我创建了一个微信公众号,欢迎关注,左边栏目上扫一扫即可。
接下来,我们贴下效果图:
2、效果图
1、横向的进度条
2、圆形的进度条
没错,这就是我们的进度条效果,横向的模仿了daimajia的进度条样子。不过我们继承子ProgressBar,简单的为其整个容,代码清晰易懂 。为什么说,易懂呢?
横向那个进度条,大家会drawLine()和drawText()吧,那么通过getWidth()拿到控件的宽度,再通过getProgress()拿到进度,按比例控制绘制线的长短,字的位置还不是分分钟的事。
github源码地址:Android-ProgressBarWidthNumber欢迎大家star or fork 。
3、实现
横向的滚动条绘制肯定需要一些属性,比如已/未到达进度的颜色、宽度,文本的颜色、大小等。
本来呢,我是想通过系统ProgressBar的progressDrawable,从里面提取一些属性完成绘制需要的参数的。但是,最终呢,反而让代码变得复杂。所以最终还是改用自定义属性。 说道自定义属性,大家应该已经不陌生了。
1、HorizontalProgressBarWithNumber
1、自定义属性
values/attr_progress_bar.xml:
- <?xmlversion="1.0"encoding="utf-8"?>
- <resources>
- <declare-styleablename="HorizontalProgressBarWithNumber">
- <attrname="progress_unreached_color"format="color"/>
- <attrname="progress_reached_color"format="color"/>
- <attrname="progress_reached_bar_height"format="dimension"/>
- <attrname="progress_unreached_bar_height"format="dimension"/>
- <attrname="progress_text_size"format="dimension"/>
- <attrname="progress_text_color"format="color"/>
- <attrname="progress_text_offset"format="dimension"/>
- <attrname="progress_text_visibility"format="enum">
- <enumname="visible"value="0"/>
- <enumname="invisible"value="1"/>
- </attr>
- </declare-styleable>
- <declare-styleablename="RoundProgressBarWidthNumber">
- <attrname="radius"format="dimension"/>
- </declare-styleable>
- </resources>
2、构造中获取
[java] view plain copy- publicclassHorizontalProgressBarWithNumberextendsProgressBar
- {
- privatestaticfinalintDEFAULT_TEXT_SIZE=10;
- privatestaticfinalintDEFAULT_TEXT_COLOR=0XFFFC00D1;
- privatestaticfinalintDEFAULT_COLOR_UNREACHED_COLOR=0xFFd3d6da;
- privatestaticfinalintDEFAULT_HEIGHT_REACHED_PROGRESS_BAR=2;
- privatestaticfinalintDEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR=2;
- privatestaticfinalintDEFAULT_SIZE_TEXT_OFFSET=10;
- /**
- *painterofalldrawingthings
- */
- protectedPaintmPaint=newPaint();
- /**
- *colorofprogressnumber
- */
- protectedintmTextColor=DEFAULT_TEXT_COLOR;
- /**
- *sizeoftext(sp)
- */
- protectedintmTextSize=sp2px(DEFAULT_TEXT_SIZE);
- /**
- *offsetofdrawprogress
- */
- protectedintmTextOffset=dp2px(DEFAULT_SIZE_TEXT_OFFSET);
- /**
- *heightofreachedprogressbar
- */
- protectedintmReachedProgressBarHeight=dp2px(DEFAULT_HEIGHT_REACHED_PROGRESS_BAR);
- /**
- *colorofreachedbar
- */
- protectedintmReachedBarColor=DEFAULT_TEXT_COLOR;
- /**
- *colorofunreachedbar
- */
- protectedintmUnReachedBarColor=DEFAULT_COLOR_UNREACHED_COLOR;
- /**
- *heightofunreachedprogressbar
- */
- protectedintmUnReachedProgressBarHeight=dp2px(DEFAULT_HEIGHT_UNREACHED_PROGRESS_BAR);
- /**
- *viewwidthexceptpadding
- */
- protectedintmRealWidth;
- protectedbooleanmIfDrawText=true;
- protectedstaticfinalintVISIBLE=0;
- publicHorizontalProgressBarWithNumber(Contextcontext,AttributeSetattrs)
- {
- this(context,attrs,0);
- }
- publicHorizontalProgressBarWithNumber(Contextcontext,AttributeSetattrs,
- intdefStyle)
- {
- super(context,attrs,defStyle);
- setHorizontalScrollBarEnabled(true);
- obtainStyledAttributes(attrs);
- mPaint.setTextSize(mTextSize);
- mPaint.setColor(mTextColor);
- }
- /**
- *getthestyledattributes
- *
- *@paramattrs
- */
- privatevoidobtainStyledAttributes(AttributeSetattrs)
- {
- //initvaluesfromcustomattributes
- finalTypedArrayattributes=getContext().obtainStyledAttributes(
- attrs,R.styleable.HorizontalProgressBarWithNumber);
- mTextColor=attributes
- .getColor(
- R.styleable.HorizontalProgressBarWithNumber_progress_text_color,
- DEFAULT_TEXT_COLOR);
- mTextSize=(int)attributes.getDimension(
- R.styleable.HorizontalProgressBarWithNumber_progress_text_size,
- mTextSize);
- mReachedBarColor=attributes
- .getColor(
- R.styleable.HorizontalProgressBarWithNumber_progress_reached_color,
- mTextColor);
- mUnReachedBarColor=attributes
- .getColor(
- R.styleable.HorizontalProgressBarWithNumber_progress_unreached_color,
- DEFAULT_COLOR_UNREACHED_COLOR);
- mReachedProgressBarHeight=(int)attributes
- .getDimension(
- R.styleable.HorizontalProgressBarWithNumber_progress_reached_bar_height,
- mReachedProgressBarHeight);
- mUnReachedProgressBarHeight=(int)attributes
- .getDimension(
- R.styleable.HorizontalProgressBarWithNumber_progress_unreached_bar_height,
- mUnReachedProgressBarHeight);
- mTextOffset=(int)attributes
- .getDimension(
- R.styleable.HorizontalProgressBarWithNumber_progress_text_offset,
- mTextOffset);
- inttextVisible=attributes
- .getInt(R.styleable.HorizontalProgressBarWithNumber_progress_text_visibility,
- VISIBLE);
- if(textVisible!=VISIBLE)
- {
- mIfDrawText=false;
- }
- attributes.recycle();
- }
嗯,看起来代码挺长,其实都是在获取自定义属性,没什么技术含量。
3、onMeasure
刚才不是出onDraw里面写写就行了么,为什么要改onMeasure呢,主要是因为我们所有的属性比如进度条宽度让用户自定义了,所以我们的测量也得稍微变下。
[java] view plain copy- @Override
- protectedsynchronizedvoidonMeasure(intwidthMeasureSpec,
- intheightMeasureSpec)
- {
- intheightMode=MeasureSpec.getMode(heightMeasureSpec);
- if(heightMode!=MeasureSpec.EXACTLY)
- {
- floattextHeight=(mPaint.descent()+mPaint.ascent());
- intexceptHeight=(int)(getPaddingTop()+getPaddingBottom()+Math
- .max(Math.max(mReachedProgressBarHeight,
- mUnReachedProgressBarHeight),Math.abs(textHeight)));
- heightMeasureSpec=MeasureSpec.makeMeasureSpec(exceptHeight,
- MeasureSpec.EXACTLY);
- }
- super.onMeasure(widthMeasureSpec,heightMeasureSpec);
- }
宽度我们不变,所以的自定义属性不涉及宽度,高度呢,只考虑不是EXACTLY的情况(用户明确指定了,我们就不管了),根据padding和进度条宽度算出自己想要的,如果非EXACTLY下,我们进行exceptHeight封装,传入给控件进行测量高度。
测量完,就到我们的onDraw了~~~
4、onDraw
[java] view plain copy- @Override
- protectedsynchronizedvoidonDraw(Canvascanvas)
- {
- canvas.save();
- //画笔平移到指定paddingLeft,getHeight()/2位置,注意以后坐标都为以此为0,0
- canvas.translate(getPaddingLeft(),getHeight()/2);
- booleannoNeedBg=false;
- //当前进度和总值的比例
- floatradio=getProgress()*1.0f/getMax();
- //已到达的宽度
- floatprogressPosX=(int)(mRealWidth*radio);
- //绘制的文本
- Stringtext=getProgress()+"%";
- //拿到字体的宽度和高度
- floattextWidth=mPaint.measureText(text);
- floattextHeight=(mPaint.descent()+mPaint.ascent())/2;
- //如果到达最后,则未到达的进度条不需要绘制
- if(progressPosX+textWidth>mRealWidth)
- {
- progressPosX=mRealWidth-textWidth;
- noNeedBg=true;
- }
- //绘制已到达的进度
- floatendX=progressPosX-mTextOffset/2;
- if(endX>0)
- {
- mPaint.setColor(mReachedBarColor);
- mPaint.setStrokeWidth(mReachedProgressBarHeight);
- canvas.drawLine(0,0,endX,0,mPaint);
- }
- //绘制文本
- if(mIfDrawText)
- {
- mPaint.setColor(mTextColor);
- canvas.drawText(text,progressPosX,-textHeight,mPaint);
- }
- //绘制未到达的进度条
- if(!noNeedBg)
- {
- floatstart=progressPosX+mTextOffset/2+textWidth;
- mPaint.setColor(mUnReachedBarColor);
- mPaint.setStrokeWidth(mUnReachedProgressBarHeight);
- canvas.drawLine(start,0,mRealWidth,0,mPaint);
- }
- canvas.restore();
- }
- @Override
- protectedvoidonSizeChanged(intw,inth,intoldw,intoldh)
- {
- super.onSizeChanged(w,h,oldw,oldh);
- mRealWidth=w-getPaddingRight()-getPaddingLeft();
- }
其实核心方法就是onDraw了,但是呢,onDraw也很简单,绘制线、绘制文本、绘制线,结束。
还有两个简单的辅助方法:
[java] view plain copy- /**
- *dp2px
- *
- *@paramdpVal
- */
- protectedintdp2px(intdpVal)
- {
- return(int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
- dpVal,getResources().getDisplayMetrics());
- }
- /**
- *sp2px
- *
- *@paramspVal
- *@return
- */
- protectedintsp2px(intspVal)
- {
- return(int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
- spVal,getResources().getDisplayMetrics());
- }
好了,到此我们的横向进度就结束了,是不是很简单~~如果你是自定义View,你还得考虑progress的更新,考虑状态的销毁与恢复等等复杂的东西。
接下来看我们的RoundProgressBarWidthNumber圆形的进度条。
2、RoundProgressBarWidthNumber
圆形的进度条和横向的进度条基本变量都是一致的,于是我就让RoundProgressBarWidthNumber extendsHorizontalProgressBarWithNumber 了。
然后需要改变的就是测量和onDraw了:
完整代码:
[java] view plain copy- packagecom.zhy.view;
- importandroid.content.Context;
- importandroid.content.res.TypedArray;
- importandroid.graphics.Canvas;
- importandroid.graphics.Paint.Cap;
- importandroid.graphics.Paint.Style;
- importandroid.graphics.RectF;
- importandroid.util.AttributeSet;
- importcom.zhy.library.view.R;
- publicclassRoundProgressBarWidthNumberextends
- HorizontalProgressBarWithNumber{
- /**
- *mRadiusofview
- */
- privateintmRadius=dp2px(30);
- publicRoundProgressBarWidthNumber(Contextcontext){
- this(context,null);
- }
- publicRoundProgressBarWidthNumber(Contextcontext,AttributeSetattrs){
- super(context,attrs);
- mReachedProgressBarHeight=(int)(mUnReachedProgressBarHeight*2.5f);
- TypedArrayta=context.obtainStyledAttributes(attrs,
- R.styleable.RoundProgressBarWidthNumber);
- mRadius=(int)ta.getDimension(
- R.styleable.RoundProgressBarWidthNumber_radius,mRadius);
- ta.recycle();
- mTextSize=sp2px(14);
- mPaint.setStyle(Style.STROKE);
- mPaint.setAntiAlias(true);
- mPaint.setDither(true);
- mPaint.setStrokeCap(Cap.ROUND);
- }
- @Override
- protectedsynchronizedvoidonMeasure(intwidthMeasureSpec,
- intheightMeasureSpec){
- intheightMode=MeasureSpec.getMode(heightMeasureSpec);
- intwidthMode=MeasureSpec.getMode(widthMeasureSpec);
- intpaintWidth=Math.max(mReachedProgressBarHeight,
- mUnReachedProgressBarHeight);
- if(heightMode!=MeasureSpec.EXACTLY){
- intexceptHeight=(int)(getPaddingTop()+getPaddingBottom()
- +mRadius*2+paintWidth);
- heightMeasureSpec=MeasureSpec.makeMeasureSpec(exceptHeight,
- MeasureSpec.EXACTLY);
- }
- if(widthMode!=MeasureSpec.EXACTLY){
- intexceptWidth=(int)(getPaddingLeft()+getPaddingRight()
- +mRadius*2+paintWidth);
- widthMeasureSpec=MeasureSpec.makeMeasureSpec(exceptWidth,
- MeasureSpec.EXACTLY);
- }
- super.onMeasure(heightMeasureSpec,heightMeasureSpec);
- }
- @Override
- protectedsynchronizedvoidonDraw(Canvascanvas){
- Stringtext=getProgress()+"%";
- //mPaint.getTextBounds(text,0,text.length(),mTextBound);
- floattextWidth=mPaint.measureText(text);
- floattextHeight=(mPaint.descent()+mPaint.ascent())/2;
- canvas.save();
- canvas.translate(getPaddingLeft(),getPaddingTop());
- mPaint.setStyle(Style.STROKE);
- //drawunreadedbar
- mPaint.setColor(mUnReachedBarColor);
- mPaint.setStrokeWidth(mUnReachedProgressBarHeight);
- canvas.drawCircle(mRadius,mRadius,mRadius,mPaint);
- //drawreachedbar
- mPaint.setColor(mReachedBarColor);
- mPaint.setStrokeWidth(mReachedProgressBarHeight);
- floatsweepAngle=getProgress()*1.0f/getMax()*360;
- canvas.drawArc(newRectF(0,0,mRadius*2,mRadius*2),0,
- sweepAngle,false,mPaint);
- //drawtext
- mPaint.setStyle(Style.FILL);
- canvas.drawText(text,mRadius-textWidth/2,mRadius-textHeight,
- mPaint);
- canvas.restore();
- }
- }
首先获取它的专有属性mRadius,然后根据此属性去测量,测量完成绘制;
绘制的过程呢?
先绘制一个细一点的圆,然后绘制一个粗一点的弧度,二者叠在一起就行。文本呢,绘制在中间~~~总体,没什么代码量。
好了,两个进度条就到这了,是不是发现简单很多。总体设计上,存在些问题,如果抽取一个BaseProgressBar用于获取公共的属性;然后不同样子的进度条继承分别实现自己的测量和样子,这样结构可能会清晰些~~~
4、使用
布局文件
[html] view plain copy- <ScrollViewxmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- xmlns:zhy="http://schemas.android.com/apk/res-auto"
- android:layout_width="match_parent"
- android:layout_height="match_parent">
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:orientation="vertical"
- android:padding="25dp">
- <com.zhy.view.HorizontalProgressBarWithNumber
- android:id="@+id/id_progressbar01"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="50dip"
- android:padding="5dp"/>
- <com.zhy.view.HorizontalProgressBarWithNumber
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="50dip"
- android:padding="5dp"
- android:progress="50"
- zhy:progress_text_color="#ffF53B03"
- zhy:progress_unreached_color="#ffF7C6B7"/>
- <com.zhy.view.RoundProgressBarWidthNumber
- android:id="@+id/id_progress02"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="50dip"
- android:padding="5dp"
- android:progress="30"/>
- <com.zhy.view.RoundProgressBarWidthNumber
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="50dip"
- android:padding="5dp"
- android:progress="50"
- zhy:progress_reached_bar_height="20dp"
- zhy:progress_text_color="#ffF53B03"
- zhy:radius="60dp"/>
- </LinearLayout>
- </ScrollView>
MainActivity
[java] view plain copy- packagecom.zhy.sample.progressbar;
- importandroid.app.Activity;
- importandroid.os.Bundle;
- importandroid.os.Handler;
- importcom.zhy.annotation.Log;
- importcom.zhy.view.HorizontalProgressBarWithNumber;
- publicclassMainActivityextendsActivity{
- privateHorizontalProgressBarWithNumbermProgressBar;
- privatestaticfinalintMSG_PROGRESS_UPDATE=0x110;
- privateHandlermHandler=newHandler(){
- @Log
- publicvoidhandleMessage(android.os.Messagemsg){
- intprogress=mProgressBar.getProgress();
- mProgressBar.setProgress(++progress);
- if(progress>=100){
- mHandler.removeMessages(MSG_PROGRESS_UPDATE);
- }
- mHandler.sendEmptyMessageDelayed(MSG_PROGRESS_UPDATE,100);
- };
- };
- @Log
- @Override
- protectedvoidonCreate(BundlesavedInstanceState){
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- mProgressBar=(HorizontalProgressBarWithNumber)findViewById(R.id.id_progressbar01);
- mHandler.sendEmptyMessage(MSG_PROGRESS_UPDATE);
- }
- }
最后,本篇博客的目的呢?就是为了说下,类似ProgressBar这样的控件,如果你只是想去改变显示的样子,完全没必要从0去创建,复写onDraw即可,当然是个人观点,提出供大家参考。
源码点击下载
群号:423372824
博主部分视频已经上线,如果你不喜欢枯燥的文本,请猛戳(初录,期待您的支持):
视频目录地址:本人录制的视频教程
更多相关文章
- android安装后控件拖不动问题解答
- 【Android】Android中 Paint 字体、粗细等属性的一些设置
- 安卓笔记:安卓控件属性大全
- android layout属性 .
- Android 常用控件(三)学习笔记