前言
自定义View必学之路
需求说明
为了满足自己项目的需求,我们往往需要通过自己实现一些效果。比如自定义控件,然而自定义控件都是直接或者的继承android View类,当然也可以直接继承ViewGroup或者一个Button。如果你想做的效果主要是视觉上的附加效果而不是功能上的大改。比如下拉刷新的listView 或者gradView,他们本来的功能不变只是加了一个可以下拉并且显示相应的head。所以这种就只需要继承Listview本身或者gradview本身就Ok。但是如果一个控件的效果根本不可能从现有控件的基础上面修改得来,那就需要深度的定制。今天就是一个最基础的深度定制View的制作——CircleProgressBar 。一个圆形的加载进度条,我们在很多的应用上面看到过类似的,比如墨迹天气,今天我们自己来实现一个
在写这个View之前需要了解的知识点是:
1)View基本的绘制流程(onMeasure onLayout onDraw)。
2) Canvas的API的调用(drawCircle 、drawtext 、drawArc) 。
3) 自定义属性。
view的绘制流程基本就是括号里面的三个,当然View的实际生命周期的回调方法远不止这三个,只是这三个是主导而已。onMeasure 会根据父布局建议的测量模式(AT_MOST,EXACTLY,UNSPECIFIED)来计算出自己实际的需要的尺寸,随后在调用setMeasuredDimension 方法设置测量的宽高即可(注意如果是ViewGroup的话要需要在此方法中测量出子View的宽高、同理如果是ViewGroup需要在onLayout直接对子View进行布局 今天我们是直接继承View所以我们的layou就交个我们的上层ViewGroup去完成吧),这个效果最重要的就是在onDraw(顾名思义我们的所有的视图能够显示出来都是靠这个方法绘制出来的)这个回调方法里面做文章 。onDraw接受一个Canvas对象的参数,Canvas提供了非常丰富的api。虽然没有直接的这种效果,但是我们可以通过组合的方式实现。比如首先绘制一个圆形然后再绘制一段弧形再绘制一个小的圆形作为填充最后在把文字画绘制上去,就基本OK了。最后最重要的一点就是为了达到这种动态的效果 我们需要向外暴露一个方法,通过调用这个方法来达到重绘(onDraw是View的回调方法不能开发者自己去调用而是系统来调用,我们可以通过调用invalidate方法通知系统让他重绘View)
自定义View
接下来贴上部分代码
public CircleProgressBar(Context context, AttributeSet attrs) {
super(context, attrs);
mContext=context;
TypedArray attributes = mContext.getTheme().obtainStyledAttributes(attrs, R.styleable.CircleProgressBar, 0, 0);
mCompleteColor=attributes.getColor(R.styleable.CircleProgressBar_progress_complete_color, DEFAULT_COMPLETE_PAINT_COLOR);
mUncompleteColor=attributes.getColor(R.styleable.CircleProgressBar_progress_uncomplete_color, DEFAULT_UNCOMPLETE_PAINT_COLOR);
mTextColor=attributes.getColor(R.styleable.CircleProgressBar_progress_text_color,DEFAULT_TEXT_COLOR);
mFillBlockRaidus=attributes.getDimension(R.styleable.CircleProgressBar_progress_fill_radius, 0);
mTextSize=attributes.getDimension(R.styleable.CircleProgressBar_progress_text_size, sptopx(12));
mFillColor=attributes.getInt(R.styleable.CircleProgressBar_progress_fill_color, DEFAULT_FILL_COLOR);
setMaxProgress(attributes.getInt(R.styleable.CircleProgressBar_progress_maxprogress, 100));
setProgress(attributes.getInt(R.styleable.CircleProgressBar_progress_progress, 0));
isDrawText=attributes.getInt(R.styleable.CircleProgressBar_progress_text_visibility, 1)==1;
attributes.recycle();
initializePainters();
}
private void initializePainters(){
mCompletePaint=new Paint();
mCompletePaint.setColor(mCompleteColor);
mCompletePaint.setAntiAlias(true);
mUncompletePaint=new Paint();
mUncompletePaint.setColor(mUncompleteColor);
mUncompletePaint.setAntiAlias(true);
mFillPaint=new Paint();
mFillPaint.setColor(mFillColor);
mFillPaint.setAntiAlias(true);
mTextPaint=new Paint();
mTextPaint.setColor(mTextColor);
mTextPaint.setTextSize(mTextSize);
mTextPaint.setAntiAlias(true);
}
初始化
Paint对象和获取自定义属性
private void calcuateBeforeDraw(){
mCX=getMeasuredWidth()/2;
mCY=getMeasuredHeight()/2;
mTotalRaidus=getMeasuredWidth()/2;
if(mFillBlockRaidus<=0||mFillBlockRaidus>=mTotalRaidus){
mFillBlockRaidus=mTotalRaidus*0.8f;
Log.w(TAG, "fillblockraidus is inconformity " +mFillBlockRaidus);
}
// change textsize
while( mTextPaint.measureText("100%")>=mFillBlockRaidus*2){
mTextPaint.setTextSize(mTextPaint.getTextSize()-2);
}
}
在绘制之前的计算,主要是我们文字的大小是可以自定义的 如果文字的宽度比中间填充的圆形直径都还大的话就不好了。所以需要检测 (Paint 的measureText是测量文字的宽度的)
private void drawUnCompleteCircle(Canvas canvas){
canvas.drawCircle(mCX, mCY, mTotalRaidus, mUncompletePaint);
}
private void drawCompleteCircle(Canvas canvas){
RectF oval=new RectF(0, 0, getWidth(), getHeight());
float sweepAngle=360*(getProgress()/(getMaxProgress()*1.0f));
canvas.drawArc(oval, -90, sweepAngle, true,mCompletePaint); //如果是0从三点钟开始 -90 表示从12点钟方向开始
}
private void drawFillCircleBlock(Canvas canvas){
canvas.drawCircle(mCX , mCY, mFillBlockRaidus, mFillPaint);<span style="white-space:pre"> </span>
}
主要是绘制圆形、弧形、和中间的填充圆形区域了。 canvas.drawArc() 的第一个参数是一个矩形区域表示的是弧形的扫描区域所在的所处的范围,主要是定义弧的形状和大小。如果oval表示的是一个正方形 arc就最终会表现为一个圆形 如果oval是长方形 arc就会表现为一个椭圆 。
private void drawCenterText(Canvas canvas){
String text=(int)(100*((getProgress()/(1.0f*getMaxProgress()))))+"%";
Rect textBound=new Rect();
mTextPaint.getTextBounds(text, 0, text.length(), textBound);
float textwidth = mTextPaint.measureText(text);
float textheight=textBound.height();
canvas.drawText(text, mCX-(textwidth/2.0f), mCY+(textheight/2.0f), mTextPaint);
}
这个方法也很重要 为了让中间的文字能够位于正中间。必须测量他的宽度和高度,宽度刚刚已经知道了可以用measureText来获取 但是高度就不行了 必须通过一个Rect来获取。
完成了这几部大功告成 ,最后希望各位同仁看到了勿喷毕竟小弟水平有限而且又是第一次写博客。
最后给出下载地址