这个栏目写一些自定义控件相关的思想,对于基础知识,如画图、动画、事件分发等不会做深入探讨,我只去做一些实战使用,下方会给出一些好的文章链接供大家参考使用。这篇是第一篇关于自定义控件的文章,先写个简单的压压惊。事实上自定义控件没那么难,只是大家想的太难,太过于恐惧了,自定义控件我们就看做是我们在设计一个控件,我们在根据现有的资源(指android的现有控件)进行拓展和延伸。很多情况下我们都会继承已有的view去实现我们想要的效果,也可以使用多个控件进行组合使用。自定义控件范围很大,几篇文章说不完,我只能说希望带大家入个门,解决一些你的恐惧心理。当你的思想已经具备了,你再看看画图、动画、事件分发、view绘制原理等等,你就不会感到这个很难了,今天我们先拿圆形进度条来说说事。
先上个图:
如上图,我们需要完成这个控件的绘制应该如何做到呢?我们需要画一个圆环,需要画一个弧度。中间需要直接写两行文字,这样就能完成了。这里自定义属性我只对文字和进度进行设置,其他的不去做,如果有需要可以自己去添加。自定义属性代码和初始化如图:
<declare-styleable name="CirclePlan">
<attr name="planCount" format="float"></attr>
<attr name="planText" format="string"></attr>
</declare-styleable>
TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.CirclePlan);
//文字
planText=array.getString(R.styleable.CirclePlan_planText);
//进度
planCount=array.getFloat(R.styleable.CirclePlan_planCount,10);
这里自定义属性我就不做过多解释了,大家可以参考
此链接,这里清晰的解释了format的是个属性值。
下面我们直接看onDraw方法我们解释这个方法下如何画这个进度条
@Override
protected void onDraw(Canvas canvas) {
// Bitmap bitmap= Bitmap.createBitmap(getWidth(),getHeight(),Bitmap.Config.ARGB_8888);
// canvas=new Canvas(bitmap);
width=getWidth();
height=getHeight();
radius=height/3;
RectF oval=new RectF(width/2-radius,height/2-radius,width/2+radius,height/2+radius);
Path path=new Path();
path.addArc(oval,0,360);
canvas.drawPath(path,paint_d);
Path path1=new Path();
path1.addArc(oval,-90,planCount*3.6f);
canvas.drawPath(path1,paint_b);
//此处应是planCount+"%"
canvas.drawText(planCount+"%",width/2,height/2+fm.descent-(fm.descent-fm.ascent)/2,paint_t);
// paint_t.setColor(context.getResources().getColor(R.color.black));
// paint_t.setTextSize(35);
//此处应是planText
canvas.drawText(planText,width/2,height/2-fm.descent+(fm.descent-fm.ascent),paint_t);
}
代码就这么多,开始的两行可以看自己需求,这个可以帮助你把你所画的图形全部放进bitmap里面,事实上我们推荐这么做的,因为你需要设置背景的时候就需要这个bitmap,当然我们不需要,所以我就把它注释了,这里用来展示一下给大家看一下如何使用。
这里我们获取了控件的宽高,事实上这个控件就是我们目前所在的类,所以可以直接getWidth就能获取到控件的宽度,自定义view是不需要measure和layout,对自己测量和设置是没有任何意义的,在自定义ViewGroup时measure也并不一定是必须的,但是layout是必须的。我们必须设置子控件的位置才行,但不需要测量,因为可以覆盖ViewGroup。
下面我们进入正题,我们写了一个RectF后面用了path路径,这里我们为什么不用drawCircle呢,不是说好了用这个的么,那是因为我们后续还会用到这个的嘛,而且他可以保证不会说你使用drawCircle再使用drawArc那个进度就跑的看不见了,我经常就遇到这个情况,所以干脆就使用这一个RectF,至少他肯定不会变。我们只需要对设置0-360度即可画一个圆,后面我们在根据planCount进度数去画一个弧度,这样就能完成了进度。
下面我们看drawText方法,这个fm可能大家不知道是干嘛的。很多情况下我们在画文本的时候,上下不能居中,那是因为画文字的时候总是有个空白的地方,比方说textview他的文字不会贴到控件边上,总是会有一点距离,paint画文字的时候也是这样,距离上面的叫做ascent,距离下面的叫做descent。我们看下图,这个图是我拷过来的,也感谢这位朋友提供的图片,我就直接拿过来用了,这个我们看到这有个上边距和下边距,除了我们上面讲到的,还有个base。我们画文字的时候需要怎么做呢。我们已经有了圆心坐标,基本可以定在height/2左右,我们应当还要向上移动半个文字的高度,而文字高度就等于ascent+descent,那么我们可以拿到这个值除以2再用height/2减去这个值就是我们画文字的位置了。因为我画了两行,所以我的位置不是这个,大家可以自行考虑自己文字的位置。关于FontMetrics可以参考此链接。到此基本讲完了,末尾附上全部代码及使用方式。
全部代码:
package xiancdm.boneng.com.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;
import xiancdm.boneng.com.xian_cdm.R;
/**
* 自定义圆形进度条
* Created by Administrator on 2016/9/13.
*/
public class CirclePlan extends View {
private String planText;
private float planCount;
private Context context;
private int width,height;
private int radius;
private Paint.FontMetrics fm;
private Paint paint_b,paint_d,paint_t;
public CirclePlan(Context context, AttributeSet attrs) {
super(context, attrs);
this.context=context;
TypedArray array=context.obtainStyledAttributes(attrs, R.styleable.CirclePlan);
//文字
planText=array.getString(R.styleable.CirclePlan_planText);
//进度
planCount=array.getFloat(R.styleable.CirclePlan_planCount,10);
initPaint();
}
public void initPaint(){
//进度画笔
paint_b=new Paint();
paint_b.setColor(context.getResources().getColor(R.color.cirle_plan_proceed));
paint_b.setStyle(Paint.Style.STROKE);
paint_b.setStrokeWidth(10);
paint_b.setAntiAlias(true);
//圆形画笔
paint_d=new Paint();
paint_d.setColor(context.getResources().getColor(R.color.cirle_plan_max));
paint_d.setStyle(Paint.Style.STROKE);
paint_d.setStrokeWidth(10);
paint_d.setAntiAlias(true);
//文字画笔
paint_t=new Paint();
paint_t.setColor(context.getResources().getColor(R.color.black));
paint_t.setAntiAlias(true);
paint_t.setTextAlign(Paint.Align.CENTER);
paint_t.setTextSize(35);
//计算文字大小,用于文字居中
fm=paint_t.getFontMetrics();
}
@Override
protected void onDraw(Canvas canvas) {
// Bitmap bitmap= Bitmap.createBitmap(getWidth(),getHeight(),Bitmap.Config.ARGB_8888);
// canvas=new Canvas(bitmap);
width=getWidth();
height=getHeight();
radius=height/3;
RectF oval=new RectF(width/2-radius,height/2-radius,width/2+radius,height/2+radius);
Path path=new Path();
path.addArc(oval,0,360);
canvas.drawPath(path,paint_d);
Path path1=new Path();
path1.addArc(oval,-90,planCount*3.6f);
canvas.drawPath(path1,paint_b);
//此处应是planCount+"%"
canvas.drawText(planCount+"%",width/2,height/2+fm.descent-(fm.descent-fm.ascent)/2,paint_t);
// paint_t.setColor(context.getResources().getColor(R.color.black));
// paint_t.setTextSize(35);
//此处应是planText
canvas.drawText(planText,width/2,height/2-fm.descent+(fm.descent-fm.ascent),paint_t);
// canvas.drawText(planText,width/2,height/2-(fm.ascent+fm.descent)/2,paint_t);
}
public String getPlanText() {
return planText;
}
public void setPlanText(String planText) {
this.planText = planText;
}
public float getPlanCount() {
return planCount;
}
public void setPlanCount(float planCount) {
this.planCount = planCount;
postInvalidate();
}
}
使用方式:
<xxx.xxx.xxx.xxx.CirclePlan
android:id="@+id/cp_ThatDay"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="match_parent" />