最近遇到一个需求,需要绘制一个进度条的样式
目标效果1:
目标效果2:
完成效果:
步骤分析:
<declare-styleable name="CustomProgress">
<attr name="backgroud_color" format="color">attr>
<attr name="current_color" format="color">attr>
<attr name="progress_color" format="color">attr>
declare-styleable>
解析自定义属性
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomProgress);
backgroundColor = array.getColor(R.styleable.CustomProgress_backgroud_color, Color.WHITE);
currentColor = array.getColor(R.styleable.CustomProgress_current_color, Color.CYAN);
progressColor = array.getColor(R.styleable.CustomProgress_progress_color, Color.CYAN);
array.recycle();
}
}
测量宽高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, 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 = getResources().getDimensionPixelSize(R.dimen.x750);
}
//计算高
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = getResources().getDimensionPixelSize(R.dimen.x80);
}
viewWidth = width;
viewHeight = height;
setMeasuredDimension(width, height);
}
重点来了,绘制流程
1:绘制背景
根据设计图可知,背景是带圆角的矩形,因此可以用drawRoundRect来绘制
//step1:画背景
if (rectf_b == null) {
rectf_b = new RectF(0, getResources().getDimensionPixelSize(R.dimen.x15), viewWidth, viewHeight - getResources().getDimensionPixelSize(R.dimen.x15));
}
canvas.drawRoundRect(rectf_b, 10, 10, bPaint);
2:绘制进度
进度其实是对背景的覆盖,具体进度有接口调用进行动态绘制
//step2:画进度
if (progress > 0) {
rectf_p = new RectF(0, getResources().getDimensionPixelSize(R.dimen.x15), viewWidth * progress / 100, viewHeight - getResources().getDimensionPixelSize(R.dimen.x15));
} else {
rectf_p = new RectF(0, getResources().getDimensionPixelSize(R.dimen.x15), 0, viewHeight - getResources().getDimensionPixelSize(R.dimen.x15));
}
canvas.drawRoundRect(rectf_p, 10, 10, paint);
3:画滑动模块
分析设计图,滑动模块也是圆角矩形,位置在背景位置竖直居中
//step3:画滑块
if (progress > 0) {
rectf_c = new RectF(viewWidth * progress / 100 - getResources().getDimensionPixelSize(R.dimen.x50), 0, viewWidth * progress / 100, viewHeight);
} else {
rectf_c = new RectF(0, 0, getResources().getDimensionPixelSize(R.dimen.x50), viewHeight);
}
canvas.drawRoundRect(rectf_c, 10, 10, cPaint);
整体的绘制代码如下:
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//step1:画背景
if (rectf_b == null) {
rectf_b = new RectF(0, getResources().getDimensionPixelSize(R.dimen.x15), viewWidth, viewHeight - getResources().getDimensionPixelSize(R.dimen.x15));
}
canvas.drawRoundRect(rectf_b, 10, 10, bPaint);
//step2:画进度
if (progress > 0) {
rectf_p = new RectF(0, getResources().getDimensionPixelSize(R.dimen.x15), viewWidth * progress / 100, viewHeight - getResources().getDimensionPixelSize(R.dimen.x15));
} else {
rectf_p = new RectF(0, getResources().getDimensionPixelSize(R.dimen.x15), 0, viewHeight - getResources().getDimensionPixelSize(R.dimen.x15));
}
canvas.drawRoundRect(rectf_p, 10, 10, paint);
//step3:画滑块
if (progress > 0) {
rectf_c = new RectF(viewWidth * progress / 100 - getResources().getDimensionPixelSize(R.dimen.x50), 0, viewWidth * progress / 100, viewHeight);
} else {
rectf_c = new RectF(0, 0, getResources().getDimensionPixelSize(R.dimen.x50), viewHeight);
}
canvas.drawRoundRect(rectf_c, 10, 10, cPaint);
}
然后就该考虑滑动效果了,滑动效果我们用监听onTouchEvent来实现
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
x = event.getX();
y = event.getY();
if (x >= viewWidth) {
progress = 100;
} else if (x <= getResources().getDimensionPixelSize(R.dimen.x50)) {
progress = 0;
} else {
progress = x * 100 / viewWidth;
}
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
x = event.getX();
y = event.getY();
if (x >= viewWidth) {
progress = 100;
} else if (x <= getResources().getDimensionPixelSize(R.dimen.x50)) {
progress = 0;
} else {
progress = x * 100 / viewWidth;
}
}
progressListener.getProgress(progress);
invalidate();
return true;
}
我们监听ACTION_DOWN动作,拿到触摸点的坐标,如果x坐标大于view的宽度坐标,则设置进度为100,反之,如果小于间隔的设置值,就等于0,再者就根据计算来获取
移动监听ACTION_MOVE,同上面一样,重置换算的进度。invalidate重新绘制,更新ui则通过接口将进度值返回即可
通信接口
public interface ProgressListener {
void getProgress(float progress);
}
public ProgressListener progressListener;
public void setProgressListener(ProgressListener listener) {
progressListener = listener;
}
做完之后我们要是考虑做动画递增效果改怎么办呢?
public void startAnim(int count) {
ValueAnimator anim = ValueAnimator.ofFloat(0, count);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
progress = (Float) valueAnimator.getAnimatedValue();
invalidate();
}
});
anim.setDuration(2000);
anim.setInterpolator(new LinearInterpolator());
anim.start();
}
这里通过属性动画来实现即可。
全部代码:
package com.orangetimes.filter.widget;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import com.orangetimes.filter.R;
/**
* 任务详情积分进度
* Created by wangchang on 2017/9/27.
*/
public class CustomProgress extends View {
private int backgroundColor;//进度背景
private int currentColor;//当前进度
private int progressColor;//进度颜色
private Paint bPaint;//背景进度画笔
private Paint cPaint;//滑块进度画笔
private Paint paint;//进度画笔
private RectF rectf_b;//背景圆角矩形
private RectF rectf_c;//滑块圆角矩形
private RectF rectf_p;//进度圆角矩形
private int viewWidth, viewHeight;
private float progress = 0;//进度值
private float x, y;
public CustomProgress(Context context) {
this(context, null);
}
public CustomProgress(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
initPaint();
}
private void initPaint() {
bPaint = new Paint();
bPaint.setAntiAlias(true);
bPaint.setColor(backgroundColor);
bPaint.setStyle(Paint.Style.FILL);
bPaint.setStrokeCap(Paint.Cap.ROUND);
cPaint = new Paint();
cPaint.setAntiAlias(true);
cPaint.setColor(currentColor);
cPaint.setStyle(Paint.Style.FILL);
cPaint.setStrokeCap(Paint.Cap.ROUND);
paint = new Paint();
paint.setAntiAlias(true);
paint.setColor(progressColor);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeCap(Paint.Cap.ROUND);
}
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomProgress);
backgroundColor = array.getColor(R.styleable.CustomProgress_backgroud_color, Color.WHITE);
currentColor = array.getColor(R.styleable.CustomProgress_current_color, Color.CYAN);
progressColor = array.getColor(R.styleable.CustomProgress_progress_color, Color.CYAN);
array.recycle();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, 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 = getResources().getDimensionPixelSize(R.dimen.x750);
}
//计算高
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = getResources().getDimensionPixelSize(R.dimen.x80);
}
viewWidth = width;
viewHeight = height;
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
//step1:画背景
if (rectf_b == null) {
rectf_b = new RectF(0, getResources().getDimensionPixelSize(R.dimen.x15), viewWidth, viewHeight - getResources().getDimensionPixelSize(R.dimen.x15));
}
canvas.drawRoundRect(rectf_b, 10, 10, bPaint);
//step2:画进度
if (progress > 0) {
rectf_p = new RectF(0, getResources().getDimensionPixelSize(R.dimen.x15), viewWidth * progress / 100, viewHeight - getResources().getDimensionPixelSize(R.dimen.x15));
} else {
rectf_p = new RectF(0, getResources().getDimensionPixelSize(R.dimen.x15), 0, viewHeight - getResources().getDimensionPixelSize(R.dimen.x15));
}
canvas.drawRoundRect(rectf_p, 10, 10, paint);
//step3:画滑块
if (progress > 0) {
rectf_c = new RectF(viewWidth * progress / 100 - getResources().getDimensionPixelSize(R.dimen.x50), 0, viewWidth * progress / 100, viewHeight);
} else {
rectf_c = new RectF(0, 0, getResources().getDimensionPixelSize(R.dimen.x50), viewHeight);
}
canvas.drawRoundRect(rectf_c, 10, 10, cPaint);
}
public void startAnim(int count) {
ValueAnimator anim = ValueAnimator.ofFloat(0, count);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
progress = (Float) valueAnimator.getAnimatedValue();
invalidate();
}
});
anim.setDuration(2000);
anim.setInterpolator(new LinearInterpolator());
anim.start();
}
public void setProgress(int progress) {
this.progress = progress;
if (Looper.getMainLooper() == Looper.myLooper()) {
invalidate();
} else {
postInvalidate();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
x = event.getX();
y = event.getY();
if (x >= viewWidth) {
progress = 100;
} else if (x <= getResources().getDimensionPixelSize(R.dimen.x50)) {
progress = 0;
} else {
progress = x * 100 / viewWidth;
}
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
x = event.getX();
y = event.getY();
if (x >= viewWidth) {
progress = 100;
} else if (x <= getResources().getDimensionPixelSize(R.dimen.x50)) {
progress = 0;
} else {
progress = x * 100 / viewWidth;
}
}
progressListener.getProgress(progress);
invalidate();
return true;
}
public interface ProgressListener {
void getProgress(float progress);
}
public ProgressListener progressListener;
public void setProgressListener(ProgressListener listener) {
progressListener = listener;
}
}
你以为这样就完了吗?不可能的,后面又改了需求了,效果如下
wf,这是什么鬼,简直堪比世界上最丑的进度了,没办法,改吧。不过仍然可以引用上述的一些计算以及逻辑
放大分析效果:
背景要这样,呵呵,有点懵逼,看着像圆角矩形,但是首尾是什么鬼,仔细思考,觉得可以这么搞,背景用drawRoundRect,进度用drawBitmap,尾部白色,用drawRoundRect,滑块用drawBitmap,在原有的基础上稍作改动即可,我觉得可以试用大部分关于进度条样式的修改
重点主要在ondraw部分,其他部分不做修改
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG));//canvas抗锯齿
canvas.translate(0,(circleBitmap.getHeight()-progressBitmap.getHeight())/2);
//step1:画背景
if (rectf_b == null) {
rectf_b = new RectF(0, 0, viewWidth, progressBitmap.getHeight());
}
canvas.drawRoundRect(rectf_b, 10, 10, bPaint);
if (progress>0) {
canvas.drawBitmap(progressBitmap, null, rectf_b, paint);
}
//step2:画进度
if (progress > 0) {
rectf_p = new RectF(viewWidth * progress / 100-circleBitmap.getWidth()/2, 0,viewWidth,progressBitmap.getHeight());
} else {
rectf_p = new RectF(0,0, 0, viewHeight);
}
canvas.drawRoundRect(rectf_p, 10, 10, paint);
//step3:画滑块
canvas.translate(0,-(circleBitmap.getHeight()-progressBitmap.getHeight())/2);
if (progress > 0) {
rectf_c = new RectF(viewWidth * progress / 100 - circleBitmap.getWidth(), 0, viewWidth * progress / 100,circleBitmap.getWidth());
} else {
rectf_c = new RectF(0, 0, circleBitmap.getWidth(), circleBitmap.getWidth());
}
canvas.drawBitmap(circleBitmap, null, rectf_c, paint);
// canvas.drawRoundRect(rectf_c, 10, 10, cPaint);
}
这里第一步,我们需要画背景矩形,第二部,需要再次基础上画进度样式,第三部,我们在滑动的时候需要隐藏尾部一段,将其颜色重置为白色,这样模拟滑动效果,第四步绘制滑块,这里为了计算防止失真,采用bitmap的宽高
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, 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 = progressBitmap.getWidth();
}
//计算高
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = circleBitmap.getHeight();
}
viewWidth = width;
viewHeight = height;
setMeasuredDimension(width, height);
}
实现效果:
全部代码:
package com.orangetimes.filter.widget;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PaintFlagsDrawFilter;
import android.graphics.RectF;
import android.os.Looper;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import com.orangetimes.filter.R;
/**
* Created by wangchang on 2017/11/20 11:10
*
* @author wangchang
*/
public class LinearView extends View {
private int backgroundColor;//进度背景
private int currentColor;//当前进度
private int progressColor;//进度颜色
private Paint bPaint;//背景进度画笔
private Paint cPaint;//滑块进度画笔
private Paint paint;//进度画笔
private RectF rectf_b;//背景圆角矩形
private RectF rectf_c;//滑块圆角矩形
private RectF rectf_p;//进度圆角矩形
private int viewWidth, viewHeight;
private float progress = 0;//进度值
private float x, y;
private Bitmap circleBitmap,progressBitmap;
public LinearView(Context context) {
this(context, null);
}
public LinearView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public LinearView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
initPaint();
}
private void initPaint() {
bPaint = new Paint();
bPaint.setAntiAlias(true);
bPaint.setColor(backgroundColor);
bPaint.setStyle(Paint.Style.FILL);
bPaint.setStrokeCap(Paint.Cap.ROUND);
cPaint = new Paint();
cPaint.setAntiAlias(true);
cPaint.setColor(currentColor);
cPaint.setStyle(Paint.Style.FILL);
cPaint.setStrokeCap(Paint.Cap.ROUND);
paint = new Paint();
paint.setAntiAlias(true);
paint.setFilterBitmap(true);//paint抗锯齿
paint.setColor(progressColor);
paint.setStyle(Paint.Style.FILL);
paint.setStrokeCap(Paint.Cap.ROUND);
}
private void init(Context context, AttributeSet attrs) {
if (attrs != null) {
TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.CustomProgress);
backgroundColor = array.getColor(R.styleable.CustomProgress_backgroud_color, Color.WHITE);
currentColor = array.getColor(R.styleable.CustomProgress_current_color, Color.CYAN);
progressColor = array.getColor(R.styleable.CustomProgress_progress_color, Color.CYAN);
circleBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_circle_progress);
progressBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.icon_linear_progress);
array.recycle();
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, 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 = progressBitmap.getWidth();
}
//计算高
if (heightMode == MeasureSpec.EXACTLY) {
height = heightSize;
} else {
height = circleBitmap.getHeight();
}
viewWidth = width;
viewHeight = height;
setMeasuredDimension(width, height);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.setDrawFilter(new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG|Paint.FILTER_BITMAP_FLAG));//canvas抗锯齿
canvas.translate(0,(circleBitmap.getHeight()-progressBitmap.getHeight())/2);
//step1:画背景
if (rectf_b == null) {
rectf_b = new RectF(0, 0, viewWidth, progressBitmap.getHeight());
}
canvas.drawRoundRect(rectf_b, 10, 10, bPaint);
if (progress>0) {
canvas.drawBitmap(progressBitmap, null, rectf_b, paint);
}
//step2:画进度
if (progress > 0) {
rectf_p = new RectF(viewWidth * progress / 100-circleBitmap.getWidth()/2, 0,viewWidth,progressBitmap.getHeight());
} else {
rectf_p = new RectF(0,0, 0, viewHeight);
}
canvas.drawRoundRect(rectf_p, 10, 10, paint);
//step3:画滑块
canvas.translate(0,-(circleBitmap.getHeight()-progressBitmap.getHeight())/2);
if (progress > 0) {
rectf_c = new RectF(viewWidth * progress / 100 - circleBitmap.getWidth(), 0, viewWidth * progress / 100,circleBitmap.getWidth());
} else {
rectf_c = new RectF(0, 0, circleBitmap.getWidth(), circleBitmap.getWidth());
}
canvas.drawBitmap(circleBitmap, null, rectf_c, paint);
// canvas.drawRoundRect(rectf_c, 10, 10, cPaint);
}
public void startAnim(int count) {
ValueAnimator anim = ValueAnimator.ofFloat(0, count);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
progress = (Float) valueAnimator.getAnimatedValue();
invalidate();
}
});
anim.setDuration(2000);
anim.setInterpolator(new LinearInterpolator());
anim.start();
}
public void setProgress(int progress) {
this.progress = progress;
if (Looper.getMainLooper() == Looper.myLooper()) {
invalidate();
} else {
postInvalidate();
}
}
@Override
public boolean onTouchEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
x = event.getX();
y = event.getY();
if (x >= viewWidth) {
progress = 100;
} else if (x <= getResources().getDimensionPixelSize(R.dimen.x50)) {
progress = 0;
} else {
progress = x * 100 / viewWidth;
}
}
if (event.getAction() == MotionEvent.ACTION_MOVE) {
x = event.getX();
y = event.getY();
if (x >= viewWidth) {
progress = 100;
} else if (x <= getResources().getDimensionPixelSize(R.dimen.x50)) {
progress = 0;
} else {
progress = x * 100 / viewWidth;
}
}
progressListener.getProgress(progress);
invalidate();
return true;
}
public interface ProgressListener {
void getProgress(float progress);
}
public CustomProgress.ProgressListener progressListener;
public void setProgressListener(CustomProgress.ProgressListener listener) {
progressListener = listener;
}
}