啥都不说看,先贴一个图看看最终要实现的效果
首先,这个是根据项目需求写出来的一个自定义View,很简单,全是画出来的~~~
好吧,对于一个刚出来的应届毕业生,这是个挑战啊,加上之前的工作很紧张,项目进度太赶,然后Android开发只有我一个人,我能不能说我是个挑大梁的人额。。
哈哈~
好了,废话不多说。。现在开始整题:
1.一开始看到原型的时候,这个控件我就在想能不能用View嵌套View去实现,答案是可以的,但是后来试了一下,发现View嵌套View的形式好像很难控制,所以还是决定自己画出来
2.既然要画,那首先要去了解一下自定义View的介绍吧,然后就去看了一下鸿洋大神的自定义View有了一点理解,然后开始着手去写了~
3.一开始要确定这个View有哪些方法需要抛出来,然后做了一下整理
(1)自定义属性,这个必须的嘛,有些属性不需要在代码中写可以在布局中固定就好了,所以我写了这些自定义的属性:
<declare-styleable name="WorkCircleView">
<attr name="BorderRadiu" format="dimension"/>
<attr name="RingRadiu" format="dimension"/>
<attr name="RingWidth" format="dimension"/>
<attr name="BorderColor" format="color"/>
<attr name="RingColor" format="color"/>
<attr name="ScheduleColor" format="color"/>
<attr name="UpTextSize" format="dimension"/>
<attr name="ImageWidth" format="dimension"/>
<attr name="DownBigTextSize" format="dimension"/>
<attr name="DownTextSize" format="dimension"/>
<attr name="ImageHeight" format="dimension"/>
<attr name="Image" format="reference"/>
</declare-styleable>
这个自定义属性应该还是能看懂的吧,简单说一下 :
declare-styleable 是给自定义控件添加自定义属性用的
然后declare-styleable后面的name就是这个自定义属性的明明,需要在获取自定义属性时根据这个name去调用的,所以这个名字最好可以和自定义控件名称一样,这样方便查看
attr这个就是自定义属性的标签,里面的name就是自定属性的名称,这个name可以在布局中使用控件后使用app:xxxx=”“这样去调用,想想还是挺简单的,最后说一下这个format这个是自定义属性的类型,至于类型常用的可能是dimension尺寸,可以用dp或者sp,就是说可以定义字体和大小的,然后是reference这个是引用资源的,可以获取资源文件的,然后是color这个是引用的颜色值的,其实也还有其他的类型,需要的可以去网上找找哈~
解释了一大堆,接下来还是说说我的实现思路吧,下面多数会贴代码:
一、新建一个Class集成自View,然后实现里面的3个构造函数,因为用到了自定义属性,所以必须要实现3个构建函数,代码如下:
public WorkCircleView(Context context) {
this(context, null);
}
public WorkCircleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public WorkCircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.WorkCircleView, 0, 0);
mBorderRadiu = a.getDimensionPixelOffset(R.styleable.WorkCircleView_BorderRadiu, 100);
mRingRadiu = a.getDimensionPixelOffset(R.styleable.WorkCircleView_RingRadiu, 70);
mRingWidth = a.getDimensionPixelOffset(R.styleable.WorkCircleView_RingWidth, 10);
mBorderColor = a.getColor(R.styleable.WorkCircleView_BorderColor, Color.parseColor("#eaf1f9"));
mRingColor = a.getColor(R.styleable.WorkCircleView_RingColor, Color.GRAY);
mScheduleColor = a.getColor(R.styleable.WorkCircleView_ScheduleColor, Color.parseColor("#1cad59"));
mUpTextSize = a.getDimensionPixelOffset(R.styleable.WorkCircleView_UpTextSize,
DensityUtil.sp2px(context,14));
mImageWidth = a.getDimensionPixelOffset(R.styleable.WorkCircleView_ImageWidth, 30);
mDownBigSize = a.getDimensionPixelOffset(R.styleable.WorkCircleView_DownBigTextSize, 40);
mDownSize = a.getDimensionPixelSize(R.styleable.WorkCircleView_DownTextSize,30);
mImageHeight = a.getDimensionPixelOffset(R.styleable.WorkCircleView_ImageHeight,30);
a.recycle();
init();
}
前两个构造函数直接引用第三个构造函数就可以了,然后在第三个构造函数中获取自定义属性,还有初始化画笔,画笔还是一开始初始化完比较好。
二、初始化画笔,把画笔先初始化好先,我这里用了挺多画笔的 =- =,不想在下面清除画笔然后重新定义画笔,是不是很懒,O(∩_∩)O哈哈~,好了,代码如下:
private void init() {
//初始化画笔
//边框画笔------start
mBorderPaint = new Paint();
mBorderPaint.setAntiAlias(true);
mBorderPaint.setStyle(Paint.Style.STROKE);
mBorderPaint.setColor(mBorderColor);
//-------------end
//圆环画笔------start
mRingPaint = new Paint();
mRingPaint.setAntiAlias(true);
mRingPaint.setStyle(Paint.Style.STROKE);
mRingPaint.setColor(mRingColor);
//-------------end
//进度画笔------start
mSchedulePaint = new Paint();
mSchedulePaint.setAntiAlias(true);
mSchedulePaint.setStyle(Paint.Style.STROKE);
//-------------end
//初始化文字画笔---start
mUpTextPaint = new Paint();
mUpTextPaint.setAntiAlias(true);
mUpTextPaint.setTextSize(mUpTextSize);
mUpTextPaint.setTypeface(Typeface.DEFAULT_BOLD);//粗体
mDownBigPaint = new Paint();
mDownBigPaint.setAntiAlias(true);
mDownBigPaint.setTextSize(mDownBigSize);
mDownBigPaint.setTypeface(Typeface.DEFAULT_BOLD);
mDownPaint = new Paint();
mDownPaint.setAntiAlias(true);
mDownPaint.setTextSize(mDownSize);
//----------------end
//初始化RectF
mRectf = new RectF();
}
三、好了,画笔也初始化完了,现在该考虑的是控件的测量问题了,这个控件不能太小,一小了就会变形,而且什么都没有看到的,所以我们给它一个最小的大小,圆形嘛,宽高一致,在自定义属性那里已经写好了的,所以我们只需要测量就好了:
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
int width;
int height;
if (widthMode == MeasureSpec.EXACTLY) {
width = widthSize;
} else {
width = mBorderRadiu * 2 + getPaddingRight() + getPaddingLeft() + 2;
}
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = mBorderRadiu * 2 + getPaddingTop() + getPaddingBottom()+ 2;
}
int chang = Math.min(width, height);
mWidth = chang;
mBorderRadiu = chang / 2;
mRingRadiu = (int) (mBorderRadiu * 0.87);
setMeasuredDimension(chang, chang);
}
这里测量了最大的宽度,然后宽度和高度也是一样的。
四、测量完了,确保了控件能正常显示了,我们要做得就是开始利用Paint(画笔)了,此外,我们要用画笔需要重写onDraw这个方法,onDraw的参数有canvas,这个canvas是什么东东,这个很好解释,有了画笔,我们还需要什么东西?一是画布,二是画家,知道这两个东西了,画布其实就是这个控件本身,而画家嘛,当然是canvas了,canvas里面提供了很多的画法,有画圆弧,圆,矩形,直线,文字。。等等,这个画家还可以嘛,哈哈~,好了,代码贴一下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int center = mWidth / 2;
mBorderRadiu = mBorderRadiu - getPaddingLeft() - getPaddingRight();
if (mBorderRadiu < mRingRadiu || mBorderRadiu - mRingRadiu <= 20) {
mRingRadiu = (int) (mBorderRadiu * 0.87);
}
if (mRingWidth * 3 >= mBorderRadiu) {
mRingWidth = mBorderRadiu / 4;
}
/**
* 调整圆环的宽度
*/
mRingPaint.setStrokeWidth(mRingWidth);
mSchedulePaint.setStrokeWidth(mRingWidth);//跟圆环宽度同宽
/**
* 调整进度条颜色
*/
mSchedulePaint.setColor(mScheduleColor);
/**
* 调整文字的颜色
*/
mUpTextPaint.setColor(Color.BLACK);
mDownBigPaint.setColor(mScheduleColor);
mDownPaint.setColor(mScheduleColor);
//圆环的间隔
mInterval = mBorderRadiu - mRingRadiu ;
canvas.drawCircle(center, center, mBorderRadiu, mBorderPaint);
canvas.drawCircle(center, center, mRingRadiu, mRingPaint);
//进度的位置
mRectf.left = mInterval;
mRectf.top = mInterval;
mRectf.right = mWidth - mInterval;
mRectf.bottom = mWidth - mInterval;
canvas.drawArc(mRectf, -90, -mProgress, false, mSchedulePaint);
//当图片的宽度小于圆环的半径则画出来
if (mImageWidth < mBorderRadiu) {
drawImage(canvas,center);//画图片
drawUpText(canvas,center);//画上层文字
drawDownText(canvas,center);//画下层文字
}
}
我相信我的注释应该很清晰明了,真正开始画我是另外写了方法的,这样可读性好一点,而且容易找到错误点,所以,每个方法都需要传一个canvas(画家)进去,然后加上算好的位置穿进去,这样就可以画出一个自定义控件了,我觉得还是贴一下代码吧:
//画图片
private void drawImage(Canvas canvas,int center){
int imgWidth = Math.min(mImageWidth,mImageHeight);
if (imgWidth >= mBorderRadiu / 2){
imgWidth = (int)(mBorderRadiu / 2);
}
//画图片
int imgLeft = center - imgWidth - (mBorderRadiu / 5);
int imgTop = center - imgWidth;
int imgRight = center - (mBorderRadiu / 5);
int imgBottom = center;
mDrawable.setBounds(imgLeft, imgTop - 5, imgRight, imgBottom - 5);
mDrawable.draw(canvas);
}
//画上层文字
private void drawUpText(Canvas canvas,int center){
int textX = center - (mImageWidth / 4);
int textY = center;
//画上层大字体,根据图片位置判断字体位置
canvas.drawText(mUpText, textX, textY - 10 , mUpTextPaint);
}
//画下层文字
private void drawDownText(Canvas canvas,int center){
//下层字体位置计算
float DownBigTextWidth = mDownBigPaint.measureText(mDownBigText);
float downX = center - DownBigTextWidth + (mImageWidth / 5);
float downY = center + mDownBigSize - (mDownBigSize / 4);
//画下层大字体
canvas.drawText(mDownBigText, downX, downY, mDownBigPaint);
//画下层字体
float downX2 = center + mImageWidth / 5;
float downy2 = center + mDownBigSize - (mDownBigSize / 4);
canvas.drawText(mDownText,downX2 + 5,downy2,mDownPaint);
}
五、画完了这个之后,就改考虑要抛出的方法了,因为是圆环进度嘛,进度肯定是由用户自己控制的啦,所以,我们把这个进度的控制方法抛出去吧(爸爸不要你了~~):
/**
* 设置进度
*
* @param current 当前值
* @param total 总值
*/
public void setProgress(int current, int total) {
float pro = (float) current / (float) total;
pro = pro * 360;
this.mProgress = pro;
postInvalidate();
}
/**
* 调整颜色进度框
*
* @param current 当前值
* @param total 总值
* @param colorRes 颜色值
*/
public void setProgress(int current, int total, int colorRes) {
float pro = (float) current / (float) total;
pro = pro * 360;
this.mProgress = pro;
this.mScheduleColor = colorRes;
postInvalidate();
}
六、到了这里,这个控件就完成了,我们只需要在布局中用起来就好了,还是挺简单的,加班加到奔溃,还是学到挺多的,写个博客激励一下自己,嘿嘿~