1.前言
本文由xygy8860原创,首发地址:http://blog.csdn.net/xygy8860/article/details/55101326,转载请注明。
在工作工程中,自己掌握的Android开发知识已经感觉到了瓶颈,Android初级知识没问题,写业务逻辑也没问题。但是作为一个Android开发工程师,并不能仅仅满足于现状,看到自身缺陷和瓶颈之后,需要对自身进行努力提高,以提升自身技能。
对于Android开发而言,自定义控件一直是Android开发进阶的一大方向,更何况,如今的APP已经不是简单的功能实现,用户对于APP的体验已经从功能实现转为用户体验,画面是否精美,转场动画是否别出心裁,UI是否美观大方?等等这些内容是必须考虑的。现如今同类APP越来越多,公司为了赢得客户,提高留存率,必然在体验和UI上下功夫。如果这时设计师设计出精美的UI,开发工程师说:I can't !,那么估计老板会说go out 了!
基于以上,开始了自定义View的学习。同时在学习过程中,记录下来,如果你也对自定义View有兴趣,我们也可以一同成长!
2.圆形百分比进度条UI分析
2.1为何会选择圆形百分比进度条?
现在下载进度条等已经高度自定义,传统的progressbar已经样式落后。在使用360手机助手等的过程中,圆形百分比进度条已经普遍而且非常精美,是常规下载组件的一部分。因此,圆形百分比进度条作为第一个学习对象。同时,在学习个过程中,完善一个自定义view的框架,可以很方便的集成各种自定义View。
在本文的学习过程中,学习参考了这篇文章http://blog.csdn.net/wingichoy/article/details/50334595,对于博主后面的自定义View文章,也准备系统的跟着学习下。在此感谢博主的文章,指出了自定义View学习的道路。
2.2 废话不说,上效果
录制的gif有点卡顿,不过在真机上还是很流畅的。
2.3 自定义view知识储备
我们都知道,在自定义view的时候一般都要重写三个方法:onMeasure(),onLayout()和onDraw()。
onDraw()必须有,用来绘制View图像
如果要改变View大小,需要重写onMeasure()
如果要改变View在父控件中的位置,需要重写onLayout()
view还有其他的方法,也一并了解下,对于自定义view有时候非常有用。
>> onFinishInflate(): 这是一个回调方法,当应用从XML布局文件加载该组件并利用它来构建界面之后,该方法将会被回调。
>> onMeasure(int,int):调用该方法来检测View组件及它所包含的所有子组件的大小。
>> onLayout(boolean,int,int,int,int):当该组件需要分配其自组件的位置、大小时,该方法就会被回调。
>> onSizeChanged(int,int,int,int):当该组件的大小被改变时回调该方法。
>> onDraw(Canvas):当该组件需要绘制它的内容时回调该方法进行绘制。
>> onKeyDown(int,KeyEvent):当某个键被按下时触发该方法。
>> onKeyUp(int,KeyEvent):当松开某个按键时触发该方法。
>> onTrackballEvent(MotionEvent):当发生轨迹球事件时触发该方法
>> onTouchEvent(MotionEvent):当发生触摸屏事件时触发该方法
>> onWindowFocusChanged(boolean):当该组件得到、失去焦点时触发该方法。
>> onAttachedToWindow():当把组件放入某个窗口时触发该方法
>> onDetachedFrowWindow():当把组件从某个窗口上分离时触发该方法。
>> onWindowVisibilityChanged(int):当包含该组件的窗口的可见性发生改变时触发该方法。
2.4 UI分析
在看到上面这个UI的时候,首先我们需要分析下UI的组成部分。任何高级UI都是有初级UI元素组成的,把一个高级UI拆解成一个个初级UI的组成部分,那么也就离我们实现它不远了。
从上面的gif图上我们可以看到,自定义圆形百分比进度条,含有三个元素:(1)一个大圆;(2)一个弧形色带;(3):百分比进度的文字。从上面的拆解我们可以发现,大圆好实现,文字也好实现,但是弧形色带怎么实现呢?
其实,经过分析我们也可以再次分解,将色带分解为:(1)一个圆弧;(2)一个中心小圆。这样实现是不是更加容易了呢?
最终,我们将UI拆解为四个部分:
(1)外层大圆;
(2)中间圆弧;
(3)内层小圆;
(4)中心文字。
其图层显示如下(图片来源于参考博客,感谢博主):
3.圆形百分比进度条代码编写
3.1 继承View并实现构造函数
// 将前面二个构造函数都指向第三个
public CircleProgressBar(Context context) {
this(context, null);
}
// XML中使用此构造函数
public CircleProgressBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleProgressBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 默认值,色带宽度,文字大小等
mStripeWidth = PxUtils.dpToPx(30, context);
mCenterTextSize = PxUtils.spToPx(20, context);
mRadius = PxUtils.dpToPx(100, context);
//绘制大圆画笔属性设置
bigCirclePaint = new Paint();
bigCirclePaint.setAntiAlias(true); // 抗锯齿
bigCirclePaint.setColor(mCircleColor); // 设置大圆颜色
//绘制小圆画笔属性设置
smallCirclePaint = new Paint();
smallCirclePaint.setAntiAlias(true);
smallCirclePaint.setColor(mCircleColor);
// 饼状图属性
rect = new RectF();
sectorPaint = new Paint();
sectorPaint.setColor(mAngleColor);
sectorPaint.setAntiAlias(true);
// 文字画笔属性
textPaint = new Paint();
textPaint.setColor(Color.WHITE);
}
3.2 onMeasure
在重写onMeasure()的时候,需要了解一些其他知识。我们都知道,在xml中我们需要设置layout_height和layout_width属性,那么,控件在onMeasure()的时候,就需要用到这些属性。根据这篇博客http://blog.csdn.net/lmj623565791/article/details/24252901的解读:
当我们设置明确的宽度和高度时,系统帮我们测量的结果就是我们设置的结果,当我们设置为WRAP_CONTENT,或者MATCH_PARENT系统帮我们测量的结果就是MATCH_PARENT的长度。
所以,当设置了WRAP_CONTENT时,我们需要自己进行测量,即重写onMesure方法”:重写之前先了解MeasureSpec的specMode,一共三种类型:
EXACTLY:一般是设置了明确的值或者是MATCH_PARENT
AT_MOST:表示子布局限制在一个最大值内,一般为WARP_CONTENT
UNSPECIFIED:表示子布局想要多大就多大,很少使用
MeasureSpec对象包含了测量的模式和大小。他是一个32位的int值,其中高两位为测量的模式,低30位是测量的大小。采用位运算和运行效率有关。所以可以从一个MeasureSpec对象分别获取模式和值 如:
//获取模式 值为 EXACTLY AT_MOST UNSPECIFIED
int specMode = MeasureSpec.getMode(measureSpec);
//获取测量值
int specSize = MeasureSpec.getSize(measureSpec);
了解了测量模式之后,我们就可以根据specMode 的值,来判断当前layout_height和layout_width的属性,从而计算出当前控件的实际宽高,此文中也即获得了大圆的半径。
@Override
protected 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);
// match_parent
if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {
mRadius = widthSize / 2;
x = widthSize / 2;
y = heightSize / 2;
mWidth = widthSize;
mHeight = heightSize;
}
// warp_content
// 如果是自适应,则取默认值
if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
mWidth = (int) (mRadius * 2);
mHeight = (int) (mRadius * 2);
x = mRadius;
y = mRadius;
}
setMeasuredDimension(mWidth, mHeight);
}
3.3 onDraw
@Override
protected void onDraw(Canvas canvas) {
mEndAngle = (int) (mCurPercent * 3.6);
// 大圆
canvas.drawCircle(x, y, mRadius, bigCirclePaint); // 通过canvas画圆
//饼状图
rect.right = mWidth;
rect.bottom = mHeight;
//参数说明见知识补充
canvas.drawArc(rect, 270, mEndAngle, true, sectorPaint);
// 小圆
canvas.drawCircle(x, y, mRadius - mStripeWidth, smallCirclePaint);
//绘制文本
String text = mCurPercent + "%";
textPaint.setTextSize(mCenterTextSize);
float textLength = textPaint.measureText(text);
canvas.drawText(text, x - textLength / 2, y, textPaint);
}
3.4 外部调用
// 外部设置百分比数
public void setPercent(int percent) {
if (percent > 100 || percent < 0) {
throw new IllegalArgumentException("percent must more than 0 and less than 100!");
}
mCurPercent = percent;
invalidate();
}
3.5 成果
然后在代码中findViewById即可得到实例,根据下载进度,持续调用setPercent()方法就能达到动画效果。
3.6 扩展
// 设置圆的颜色
public void setCircleColor(int mCircleColor) {
this.mCircleColor = mCircleColor;
smallCirclePaint.setColor(mCircleColor);
bigCirclePaint.setColor(mCircleColor);
// 此方法是为了使设置生效
invalidate();
}
// 设置圆弧的颜色
public void setAngleColor(int mAngleColor) {
this.mAngleColor = mAngleColor;
sectorPaint.setColor(mAngleColor);
invalidate();
}
// 设置色带宽度
public void setStripeWidth(float mStripeWidth) {
this.mStripeWidth = mStripeWidth;
invalidate();
}
// 设置字体颜色
public void setCenterTextColor(int color) {
textPaint.setColor(color);
invalidate();
}
// 设置字体大小
public void setmCenterTextSize(float mCenterTextSize) {
this.mCenterTextSize = mCenterTextSize;
invalidate();
}
4.源码
本文的源码已经上传github,地址:https://github.com/xygy8860/CustomView(求star),请各位自取,下载源码后可以按自己的风格自行修改