package com.cui.rectprogress; import android.animation.ValueAnimator; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.PorterDuff; import android.graphics.PorterDuffXfermode; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.BitmapDrawable; import android.support.annotation.Nullable; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; /** * Created by CZH on 2017/6/15. * RectProgress */ public class RectProgress extends View { private final int VERTICAL = 1; private final int HORIZONTAL = 2; private final int defaultBgColor = 0xFf000000; private final int defaultProgressColor = 0xFFFF4081; private int bgColor = defaultBgColor; private int progressColor = defaultProgressColor; /*圆角弧度*/ private float rectRadius = 20f; /*画背景使用的Rect*/ private RectF bgRect = new RectF(); /*画进度使用的Rect*/ private RectF progressRect = new RectF(); /*背景画笔*/ private Paint bgPaint; /*进度画笔*/ private Paint progressPaint; /*进度方向*/ private int orientation = VERTICAL; private int max = 100; private int progress = 15; private Bitmap bitmap; /*icon显示区域Rect*/ private Rect srcRect; /*icon显示位置Rect*/ private Rect dstRect; private float iconPadding; /*进度百分比*/ private int percent = 0; public RectProgress(Context context) { super(context); init(context, null); } public RectProgress(Context context, @Nullable AttributeSet attrs) { super(context, attrs); init(context, attrs); } public RectProgress(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(context, attrs); } private void init(Context context, AttributeSet attrs) { //关闭硬件加速,不然setXfermode()可能会不生效 setLayerType(View.LAYER_TYPE_SOFTWARE, null); if (attrs != null) { TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.RectProgress); bgColor = typedArray.getColor(R.styleable.RectProgress_bgColor, defaultBgColor); progressColor = typedArray.getColor(R.styleable.RectProgress_progressColor, defaultProgressColor); progress = typedArray.getInteger(R.styleable.RectProgress_progressValue, progress); max = typedArray.getInteger(R.styleable.RectProgress_progressMax, max); if (max <= 0) throw new RuntimeException("Max 必须大于 0"); orientation = typedArray.getInteger(R.styleable.RectProgress_progressOrientation, VERTICAL); int imgSrc = typedArray.getResourceId(R.styleable.RectProgress_iconSrc, 0); iconPadding = typedArray.getDimensionPixelSize(R.styleable.RectProgress_iconPadding, 10); rectRadius = typedArray.getDimensionPixelSize(R.styleable.RectProgress_rectRadius, 20); if (max < progress) { progress = max; } typedArray.recycle(); if (imgSrc != 0) { bitmap = ((BitmapDrawable) getResources().getDrawable(imgSrc)).getBitmap(); } } bgPaint = new Paint(Paint.ANTI_ALIAS_FLAG); bgPaint.setColor(bgColor); progressPaint = new Paint(Paint.ANTI_ALIAS_FLAG); progressPaint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)); progressPaint.setColor(progressColor); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); bgRect.set(getPaddingLeft() , getPaddingTop() , getWidth() - getPaddingRight() , getHeight() - getPaddingBottom()); computeProgressRect(); if (bitmap != null) { srcRect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()); int iconSideLength; if (orientation == VERTICAL) { iconSideLength = (int) (bgRect.width() - iconPadding * 2); dstRect = new Rect((int) bgRect.left + (int) iconPadding , (int) (bgRect.bottom - iconSideLength - iconPadding) , (int) bgRect.right - (int) iconPadding , (int) bgRect.bottom - (int) iconPadding); } else { iconSideLength = (int) (bgRect.height() - iconPadding * 2); dstRect = new Rect((int) bgRect.left + (int) iconPadding , (int) (bgRect.bottom - iconPadding - iconSideLength) , (int) (bgRect.left + iconPadding + iconSideLength) , (int) (bgRect.bottom - iconPadding)); } } } @Override protected void onDraw(Canvas canvas) { int canvasWidth = canvas.getWidth(); int canvasHeight = canvas.getHeight(); int layerId = canvas.saveLayer(0, 0, canvasWidth, canvasHeight, null, Canvas.ALL_SAVE_FLAG); { bgPaint.setColor(bgColor); // draw the background of progress canvas.drawRoundRect(bgRect, rectRadius, rectRadius, bgPaint); // draw progress canvas.drawRect(progressRect, progressPaint); bgPaint.setXfermode(null); if (bitmap != null) { //draw icon canvas.drawBitmap(bitmap, srcRect, dstRect, bgPaint); } } canvas.restoreToCount(layerId); // TODO: 弄明白为什么在xml预览中,canvas.restoreToCount // TODO: 会导致后续的canvas对象为空 但canvas.restore方法则不会导致这个问题 // canvas.restore(); // canvas.save(); } @Override public boolean onTouchEvent(MotionEvent event) { //在家进度条内才执行操作 switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (bgRect.contains(event.getX(), event.getY())) { //按下时,在进度内才执行操作 handleTouch(event); invalidate(); return true; } break; case MotionEvent.ACTION_MOVE: handleTouch(event); invalidate(); break; } invalidate(); return super.onTouchEvent(event); } private void handleTouch(MotionEvent event) { if (orientation == VERTICAL) { if (event.getY() < bgRect.top) { //触点超出Progress顶部 progressRect.top = bgRect.top; } else if (event.getY() > bgRect.bottom) { //触点超过Progress底部 progressRect.top = bgRect.bottom; } else { progressRect.top = event.getY(); } int tmp = (int) ((progressRect.height() / bgRect.height()) * 100); if (percent != tmp) { percent = tmp; progress = percent * max / 100; if (changedListener != null) changedListener.onProgressChanged(progress, percent); } } else { if (event.getX() > bgRect.right) { //触点超出Progress右端 progressRect.right = bgRect.right; } else if (event.getX() < bgRect.left) { //触点超出Progress左端 progressRect.right = bgRect.left; } else { progressRect.right = event.getX(); } int tmp = (int) ((progressRect.width() / bgRect.width()) * 100); if (percent != tmp) { percent = tmp; progress = percent * max / 100; if (changedListener != null) changedListener.onProgressChanged(progress, percent); } } } private OnProgressChangedListener changedListener; public void setChangedListener(OnProgressChangedListener changedListener) { this.changedListener = changedListener; } public interface OnProgressChangedListener { void onProgressChanged(int currentValue, int percent); } public void setMax(int m) { if (max <= 0) throw new RuntimeException("Max 必须大于 0"); max = m; } public void setProgress(int p) { int oldProgress = progress; progress = p; if (max < progress) { progress = max; } else if (progress < 0) progress = 0; startProgressAnim(oldProgress); } private ValueAnimator valueAnimator; /**/ private void startProgressAnim(int oldProgress) { if (valueAnimator != null && valueAnimator.isRunning()) { valueAnimator.cancel(); } valueAnimator = ValueAnimator.ofInt(oldProgress, progress); valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { progress = (int) animation.getAnimatedValue(); computeProgressRect(); invalidate(); } }); valueAnimator.setDuration(1000); valueAnimator.start(); } /** * 计算进度Progress */ private void computeProgressRect() { if (orientation == VERTICAL) { progressRect.set(bgRect.left , bgRect.bottom - progress * bgRect.height() / max , bgRect.right , bgRect.bottom); } else { progressRect.set(bgRect.left , bgRect.top , bgRect.left + progress * bgRect.width() / max , bgRect.bottom); } } }