分析原理;
1.所以我们不得不把它拆分为2个形状:
圆环
与圆
.
2.如何实现渐变
3.如何实现动画的效果
4.测量及自适应圆形进度条View的宽高
5.下载进度不会匀速到100%,相册下载进度
6.如何添加手动拖动进度
7.onSizeChanged
概述:
自定义带进度圆环思路主要可以分为以下几步:
1.自定义View属性
2.View 的测量
3.计算绘制 View 所需参数
4.圆弧的绘制及渐变的实现
5.文字的绘制
6.动画效果的实现
第一步:构造方法自定义styel中初始化变量
public FitnessVideoCircleProgressBar(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
paint = new Paint();
TypedArray mTypedArray = context.obtainStyledAttributes(attrs,
R.styleable.FitnessVideoCircleProgressBar);
boardColor = mTypedArray.getColor(R.styleable.FitnessVideoCircleProgressBar_boardCircleColor, Color.parseColor("#ffffff"));
circleProgressBgColor = mTypedArray.getColor(R.styleable.FitnessVideoCircleProgressBar_circleProgressBgColor, Color.parseColor("#597C7C7C"));
circleProgressColor = mTypedArray.getColor(R.styleable.FitnessVideoCircleProgressBar_circleProgressColor, Color.parseColor("#6AC5DC"));
innerCircleColor = mTypedArray.getColor(R.styleable.FitnessVideoCircleProgressBar_innerCircleColor, Color.parseColor("#4E6483"));
textColor = mTypedArray.getColor(R.styleable.FitnessVideoCircleProgressBar_textColor, Color.parseColor("#ffffff"));
textSize = mTypedArray.getDimension(R.styleable.FitnessVideoCircleProgressBar_textSize, dip2px(ShadowApp.context(), 21));
circleWidth = mTypedArray.getDimension(R.styleable.FitnessVideoCircleProgressBar_circleWidth, dip2px(context, 6));
max = mTypedArray.getInteger(R.styleable.FitnessVideoCircleProgressBar_max, 1000);
textIsDisplayable = mTypedArray.getBoolean(R.styleable.FitnessVideoCircleProgressBar_textIsDisplayable, true);
style = mTypedArray.getInt(R.styleable.FitnessVideoCircleProgressBar_style, 0);
mTypedArray.recycle();
}
第二步:重写ondraw方法
1.一个背景图片
2.里面画一个文字
3.外面画一个进度条,就是画圆弧
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
/**
* 画进度条的灰色背景,最外层的。画最外层的大圆环,
*/
centre = getWidth() / 2;
radius = (int) (centre - circleWidth / 2);
paint.setAntiAlias(true);
paint.setColor(circleProgressBgColor);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(circleWidth);
canvas.drawCircle(centre, centre, radius, paint);
/**
* 最里面的,画中间内层圆
* //半径减去空白的空隙
*/
paint.setColor(innerCircleColor);
paint.setStyle(Paint.Style.FILL);
canvas.drawCircle(centre, centre, (float) (radius - 16), paint);
/**
* 画中间外层圆,边线
*/
paint.setColor(boardColor);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(1);
canvas.drawCircle(centre, centre, (float) (radius - 30), paint);
/**
* 画圆弧 ,画圆环的进度
*/
paint.setStrokeWidth(circleWidth);
paint.setColor(circleProgressColor);
oval.set(centre - radius, centre - radius, centre + radius, centre + radius);
switch (style) {
case STROKE: {
paint.setStyle(Paint.Style.STROKE);
canvas.drawArc(oval, -90, 360 * progress / max, false, paint);
break;
}
case FILL: {
paint.setStyle(Paint.Style.FILL_AND_STROKE);
if (progress != 0)
canvas.drawArc(oval, -90, 360 * progress / max, true, paint);
break;
}
}
/**
* 画进度百分比文本
*/
paint.setStrokeWidth(0);
paint.setColor(textColor);
paint.setTextSize(textSize);
paint.setTypeface(Typeface.DEFAULT);
paint.setStyle(Paint.Style.FILL);
float textWidth = paint.measureText(remainSec);
if (textIsDisplayable && style == STROKE) {
canvas.drawText(remainSec, centre - textWidth / 2, centre + textSize / 2, paint);
}
}
第三步:进度有动画效果
set方法设置 或者更新的方法
用到补间动画,自定义RotateAnimation
public synchronized void setProgress(int progress, View view) {
if (progress < 0) {
throw new IllegalArgumentException("progress not less than 0");
}
if (progress * 10 < this.destProgress) {
oldProgress = 0;
destProgress = 0;
view.clearAnimation();
this.progress = 0;
mBeginPoint = 0;
}
if (progress > max) {
progress = max;
}
progress = progress * 10;
oldProgress = this.progress;
destProgress = progress;
int endPoint = (int) (((float) 360 / max) * progress);
view.startAnimation(pointRotationAnim(mBeginPoint, endPoint));
mBeginPoint = endPoint;
}
private Animation pointRotationAnim(float fromDegrees, float toDegrees) {
int initDegree = 54;// 进度点起始位置(图片偏移约54度)
RotateAnimation theRotateAnimation = new MyRotationAni(fromDegrees - initDegree,
toDegrees - initDegree, Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
theRotateAnimation.setDuration(1000);
theRotateAnimation.setRepeatCount(0);
theRotateAnimation.setFillAfter(true);// 设置动画结束后是否停留在结束位置
return theRotateAnimation;
}
private class MyRotationAni extends RotateAnimation {
int offset;
public MyRotationAni(float fromDegrees, float toDegrees, int pivotXType, float pivotXValue, int pivotYType, float pivotYValue) {
super(fromDegrees, toDegrees, pivotXType, pivotXValue, pivotYType, pivotYValue);
offset = destProgress - oldProgress;
}
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
progress = (int) (oldProgress + offset * interpolatedTime);
super.applyTransformation(interpolatedTime, t);
invalidate();
}
}
4.渐变来啦
使用Android提供的扫描渲染器
SweepGradient
实现需要的渐变
LinearGradient
//先创建一个渲染器
SweepGradient mSweepGradient = new SweepGradient(canvas.getWidth() / 2,
canvas.getHeight() / 2, //以圆弧中心作为扫描渲染的中心以便实现需要的效果
mMinColors, //这是我定义好的颜色数组,包含2个颜色:#35C3D7、#2894DD
null);
//把渐变设置到笔刷
mMinPaint.setShader(mSweepGradient);
5.在上面的代码中,View的宽高是由绘制圆弧的矩形区域大小决定的,直接写死在了init()方法中,
而我们的实际需求是View的宽高可以由我们在外部进行设置,且无论怎么设置宽高View都应该是一个正方形(宽高相等),因此我们需要重写View的onMeasure()方法
<span style="color:#000000"><span style="color:#cccccc"><code class="language-java"><span style="color:#cc99cd">private</span> <span style="color:#f8c555">RectF</span> mRectF;<span style="color:#999999">//绘制圆弧的矩形区域</span>
<span style="color:#cc99cd">private</span> <span style="color:#cc99cd">float</span> barWidth;<span style="color:#999999">//圆弧进度条宽度</span>
<span style="color:#cc99cd">private</span> <span style="color:#cc99cd">int</span> defaultSize;<span style="color:#999999">//自定义View默认的宽高</span>
<span style="color:#cc99cd">private</span> <span style="color:#cc99cd">void</span> <span style="color:#f08d49">init</span>(<span style="color:#f8c555">Context</span> context,<span style="color:#f8c555">AttributeSet</span> attrs){
<span style="color:#999999">//省略部分代码...</span>
defaultSize <span style="color:#67cdcc">=</span> <span style="color:#f8c555">DpOrPxUtils</span>.<span style="color:#f08d49">dip2px</span>(context,<span style="color:#f08d49">100</span>);
barWidth <span style="color:#67cdcc">=</span> <span style="color:#f8c555">DpOrPxUtils</span>.<span style="color:#f08d49">dip2px</span>(context,<span style="color:#f08d49">10</span>);
mRectF <span style="color:#67cdcc">=</span> <span style="color:#cc99cd">new</span> <span style="color:#f8c555">RectF</span>();
progressPaint.<span style="color:#f08d49">setStrokeWidth</span>(barWidth);
bgPaint.<span style="color:#f08d49">setStrokeWidth</span>(barWidth);
}
@Override
<span style="color:#cc99cd">protected</span> <span style="color:#cc99cd">void</span> <span style="color:#f08d49">onMeasure</span>(<span style="color:#cc99cd">int</span> widthMeasureSpec, <span style="color:#cc99cd">int</span> heightMeasureSpec) {
<span style="color:#cc99cd">super</span>.<span style="color:#f08d49">onMeasure</span>(widthMeasureSpec, heightMeasureSpec);
<span style="color:#cc99cd">int</span> height <span style="color:#67cdcc">=</span> <span style="color:#f08d49">measureSize</span>(defaultSize, heightMeasureSpec);
<span style="color:#cc99cd">int</span> width <span style="color:#67cdcc">=</span> <span style="color:#f08d49">measureSize</span>(defaultSize, widthMeasureSpec);
<span style="color:#cc99cd">int</span> min <span style="color:#67cdcc">=</span> <span style="color:#f8c555">Math</span>.<span style="color:#f08d49">min</span>(width, height);<span style="color:#999999">// 获取View最短边的长度</span>
<span style="color:#f08d49">setMeasuredDimension</span>(min, min);<span style="color:#999999">// 强制改View为以最短边为长度的正方形</span>
<span style="color:#cc99cd">if</span>(min <span style="color:#67cdcc">>=</span> barWidth<span style="color:#67cdcc">*</span><span style="color:#f08d49">2</span>){<span style="color:#999999">//这里简单限制了圆弧的最大宽度</span>
mRectF.<span style="color:#f08d49">set</span>(barWidth<span style="color:#67cdcc">/</span><span style="color:#f08d49">2</span>,barWidth<span style="color:#67cdcc">/</span><span style="color:#f08d49">2</span>,min<span style="color:#67cdcc">-</span>barWidth<span style="color:#67cdcc">/</span><span style="color:#f08d49">2</span>,min<span style="color:#67cdcc">-</span>barWidth<span style="color:#67cdcc">/</span><span style="color:#f08d49">2</span>);
}
}
<span style="color:#cc99cd">private</span> <span style="color:#cc99cd">int</span> <span style="color:#f08d49">measureSize</span>(<span style="color:#cc99cd">int</span> defaultSize,<span style="color:#cc99cd">int</span> measureSpec) {
<span style="color:#cc99cd">int</span> result <span style="color:#67cdcc">=</span> defaultSize;
<span style="color:#cc99cd">int</span> specMode <span style="color:#67cdcc">=</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.<span style="color:#f08d49">getMode</span>(measureSpec);
<span style="color:#cc99cd">int</span> specSize <span style="color:#67cdcc">=</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.<span style="color:#f08d49">getSize</span>(measureSpec);
<span style="color:#cc99cd">if</span> (specMode <span style="color:#67cdcc">==</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.EXACTLY) {
result <span style="color:#67cdcc">=</span> specSize;
} <span style="color:#cc99cd">else</span> <span style="color:#cc99cd">if</span> (specMode <span style="color:#67cdcc">==</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.AT_MOST) {
result <span style="color:#67cdcc">=</span> <span style="color:#f8c555">Math</span>.<span style="color:#f08d49">min</span>(result, specSize);
}
<span style="color:#cc99cd">return</span> result;
}</code></span></span>
<span style="color:#000000"><span style="color:#cccccc"><code class="language-java"><span style="color:#cc99cd">private</span> <span style="color:#cc99cd">int</span> <span style="color:#f08d49">measureSize</span>(<span style="color:#cc99cd">int</span> defaultSize,<span style="color:#cc99cd">int</span> measureSpec) {
<span style="color:#cc99cd">int</span> result <span style="color:#67cdcc">=</span> defaultSize;
<span style="color:#cc99cd">int</span> specMode <span style="color:#67cdcc">=</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.<span style="color:#f08d49">getMode</span>(measureSpec);
<span style="color:#cc99cd">int</span> specSize <span style="color:#67cdcc">=</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.<span style="color:#f08d49">getSize</span>(measureSpec);
<span style="color:#cc99cd">if</span> (specMode <span style="color:#67cdcc">==</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.EXACTLY) {
result <span style="color:#67cdcc">=</span> specSize;
} <span style="color:#cc99cd">else</span> <span style="color:#cc99cd">if</span> (specMode <span style="color:#67cdcc">==</span> <span style="color:#f8c555">View</span>.<span style="color:#f8c555">MeasureSpec</span>.AT_MOST) {
result <span style="color:#67cdcc">=</span> <span style="color:#f8c555">Math</span>.<span style="color:#f08d49">min</span>(result, specSize);
}
<span style="color:#cc99cd">return</span> result;
}</code></span></span>
得到SpecMode和SpecSize
<span style="color:#000000"><span style="color:#cccccc"><code class="language-java"><span style="color:#f08d49">setMeasuredDimension()方法</span></code></span></span>
以上是:
1)自定义组件时什么情况下需要实现onMeasure()方法,2)怎么实现onMeasure()方法
当MyView的宽和高设置为match_parent时,MyView会填充整个界面是毋庸置疑的,当将其宽和高设置为某个确定值时,
MyView的大小也会按这个确定的值显示。但是上面的例子中,我们把MyView的宽和高设置为wrap_content,它依然填充整个界面。问题就出在这里,所以我们需要重写onMeasure()方法控制其大小
2)。怎么重写onMeasure()方法
每个View的大小,不仅取决于自身,而且也受父视图的影响,普通的列子
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// TODO Auto-generated method stub
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int width = measureDimension(200, widthMeasureSpec);
int height = measureDimension(200, heightMeasureSpec);
setMeasuredDimension(width, height);
}
public int measureDimension(int defaultSize, int measureSpec){
int result;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
if(specMode == MeasureSpec.EXACTLY){
result = specSize;
}else{
result = defaultSize; //UNSPECIFIED
if(specMode == MeasureSpec.AT_MOST){
result = Math.min(result, specSize);
}
}
return result
查看View内的onMeasure源码:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
public static int getDefaultSize(int size, int measureSpec) {
int result = size;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);
switch (specMode) {
case MeasureSpec.UNSPECIFIED:
result = size;
break;
case MeasureSpec.AT_MOST:
case MeasureSpec.EXACTLY:
result = specSize;
break;
}
return result;
}
可以看到AT_MOST和EXACTLY返回的是相同的specSiz,
这也是使用wrap_content和match_parent会出现相同的结果的原因。
7.onSizeChanged:
在控件发生变化的时候 重新赋值宽高,以及重新为一些需要在控件大小发生变化时需要充值赋值的属性赋值。
<span style="color:#000000"><span style="color:#cccccc"><span style="color:#404040"><code class="language-java"> <span style="color:#999999">/**
* 第四部分,在size发生变化时,重新赋值宽高
*
* @param w
* @param h
* @param oldw
* @param oldh
*/</span>
@Override
<span style="color:#cc99cd">protected</span> <span style="color:#cc99cd">void</span> <span style="color:#f08d49">onSizeChanged</span>(<span style="color:#cc99cd">int</span> w, <span style="color:#cc99cd">int</span> h, <span style="color:#cc99cd">int</span> oldw, <span style="color:#cc99cd">int</span> oldh) {
<span style="color:#cc99cd">super</span>.<span style="color:#f08d49">onSizeChanged</span>(w, h, oldw, oldh);
mWidth <span style="color:#67cdcc">=</span> w;
mHeight <span style="color:#67cdcc">=</span> h;
<span style="color:#f8c555">LayerDrawable</span> background <span style="color:#67cdcc">=</span> (<span style="color:#f8c555">LayerDrawable</span>) <span style="color:#f08d49">getBackground</span>();
<span style="color:#f8c555">Drawable</span> drawable <span style="color:#67cdcc">=</span> background.<span style="color:#f08d49">getDrawable</span>(<span style="color:#f08d49">0</span>);
drawable.<span style="color:#f08d49">getPadding</span>(rectOfBackGround);
}</code></span></span></span>
进度条
渐变进度条
圆形进度条
自定义属性的时候,不要和系统的名字一样,否则会重复
Error:(139) Attribute "background" already defined with incompatible format.
=============================================