您好,登錄后才能下訂單哦!
前言
在 Android 自定義 View 中,Path 可能用的比較多,PathMeasure 可能用的比較少,就我而言,以前也沒有使用過 PathMeasure 這個 api,看到別人用 PathMeasure 和 ValueAnimator 結合在一起完成了很好的動畫效果,于是我也學習下 PathMeasure ,此處記錄下。
PathMeasure
構造器:
forceClosed 含義:
// 創建一個 Path 對象 path = new Path(); path.moveTo(20, 20); path.lineTo(200, 20); path.lineTo(200, 400);
在onDraw(Canvas canvas) 中繪制 path
@Override protected void onDraw(Canvas canvas) { destPath.reset(); destPath.lineTo(0, 0); pathMeasure.setPath(path, true); Log.e("debug", "PathMeasure.getLength() = " + pathMeasure.getLength()); pathMeasure.getSegment(0, pathMeasure.getLength() * curValue, destPath, true); canvas.drawPath(destPath, paint); // 繪制線段路徑 }
當 pathMeasure.setPath(path,false) 時:
當 pathMeasure.setPath(path,true) 時:
可以看到:當 forceClosed = true 時, path 進行了閉合,相應的 path 長度也變長了,即 算上了斜邊的長度。
仿支付寶支付動畫 View - LoadingView
效果:
思路:
繪制對號,叉號,主要是通過 ValueAnimator 結合 getSegment() 不斷繪制新的弧形段,其中,叉號由兩個 path 組成,在第一個 path 繪制完成時,需要調用 pathMeasure.nextContour() 跳轉到另一個 path。
getSegment() 將獲取的片段填充到 destPath 中,在 Android 4.4 及以下版本中,不能繪制,需要調用 destPath.reset(),destPath.line(0,0)
LoadingView 完整代碼:
public class LoadingView extends View { private final int DEFAULT_COLOR = Color.BLACK; // 默認圓弧顏色 private final int DEFAULT_STROKE_WIDTH = dp2Px(2); // 默認圓弧寬度 private final boolean DEFAULT_IS_SHOW_RESULT = false; // 默認不顯示加載結果 private final int DEFAULT_VIEW_WIDTH = dp2Px(50); // 控件默認寬度 private final int DEFAULT_VIEW_HEIGHT = dp2Px(50); // 控件默認高度 private int color; // 圓弧顏色 private int strokeWidth; // 圓弧寬度 private boolean isShowResult; // 是否顯示加載結果狀態 private Paint paint; // 畫筆 private int mWidth; // 控件寬度 private int mHeight; // 控件高度 private int radius; // 圓弧所在圓的半徑 private int halfStrokeWidth; // 畫筆寬度的一半 private int rotateDelta = 4; private int curAngle = 0; private int minAngle = -90; private int startAngle = -90; // 上方頂點 private int endAngle = 0; private RectF rectF; private StateEnum stateEnum = StateEnum.LOADING; private Path successPath; private Path rightFailPath; private Path leftFailPath; private ValueAnimator successAnimator; private ValueAnimator rightFailAnimator; private ValueAnimator leftFailAnimator; private PathMeasure pathMeasure; private float successValue; private float rightFailValue; private float leftFailValue; private Path destPath; private AnimatorSet animatorSet; public LoadingView(Context context) { this(context, null); } public LoadingView(Context context, AttributeSet attrs) { super(context, attrs); init(context, attrs); } private void init(Context context, AttributeSet attrs) { TypedArray typedArray = null; try { typedArray = context.obtainStyledAttributes(attrs, R.styleable.LoadingView); color = typedArray.getColor(R.styleable.LoadingView_color, DEFAULT_COLOR); strokeWidth = (int) typedArray.getDimension(R.styleable.LoadingView_storkeWidth, DEFAULT_STROKE_WIDTH); isShowResult = typedArray.getBoolean(R.styleable.LoadingView_isShowResult, DEFAULT_IS_SHOW_RESULT); } catch (Exception e) { e.printStackTrace(); } finally { if (typedArray != null) { typedArray.recycle(); } } paint = createPaint(color, strokeWidth, Paint.Style.STROKE); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); mWidth = w; mHeight = h; Log.i("debug", "getMeasureWidth() = " + getMeasuredWidth()); Log.i("debug", "getMeasureHeight() = " + getMeasuredHeight()); radius = Math.min(mWidth, mHeight) / 2; halfStrokeWidth = strokeWidth / 2; rectF = new RectF(halfStrokeWidth - radius, halfStrokeWidth - radius, radius - halfStrokeWidth, radius - halfStrokeWidth); // success path successPath = new Path(); successPath.moveTo(-radius * 2 / 3f, 0f); successPath.lineTo(-radius / 8f, radius / 2f); successPath.lineTo(radius / 2, -radius / 3); // fail path ,right top to left bottom rightFailPath = new Path(); rightFailPath.moveTo(radius / 3f, -radius / 3f); rightFailPath.lineTo(-radius / 3f, radius / 3f); // fail path, left top to right bottom leftFailPath = new Path(); leftFailPath.moveTo(-radius / 3f, -radius / 3f); leftFailPath.lineTo(radius / 3f, radius / 3f); pathMeasure = new PathMeasure(); destPath = new Path(); initSuccessAnimator(); initFailAnimator(); } private void initSuccessAnimator() { // pathMeasure.setPath(successPath, false); successAnimator = ValueAnimator.ofFloat(0, 1f); successAnimator.setDuration(1000); successAnimator.setInterpolator(new LinearInterpolator()); successAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { successValue = (float) animation.getAnimatedValue(); invalidate(); } }); } private void initFailAnimator() { // pathMeasure.setPath(rightFailPath, false); rightFailAnimator = ValueAnimator.ofFloat(0, 1f); rightFailAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { rightFailValue = (float) animation.getAnimatedValue(); invalidate(); } }); // pathMeasure.setPath(leftFailPath, false); leftFailAnimator = ValueAnimator.ofFloat(0, 1f); leftFailAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { @Override public void onAnimationUpdate(ValueAnimator animation) { leftFailValue = (float) animation.getAnimatedValue(); invalidate(); } }); animatorSet = new AnimatorSet(); animatorSet.play(leftFailAnimator).after(rightFailAnimator); animatorSet.setDuration(500); animatorSet.setInterpolator(new LinearInterpolator()); } /** * 測量控件的寬高,當測量模式不是精確模式時,設置默認寬高 * * @param widthMeasureSpec * @param heightMeasureSpec */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); if (widthMode == MeasureSpec.AT_MOST || widthMode == MeasureSpec.UNSPECIFIED) { widthMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_VIEW_WIDTH, MeasureSpec.EXACTLY); } if (heightMode == MeasureSpec.AT_MOST || heightMode == MeasureSpec.UNSPECIFIED) { heightMeasureSpec = MeasureSpec.makeMeasureSpec(DEFAULT_VIEW_HEIGHT, MeasureSpec.EXACTLY); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } @Override protected void onDraw(Canvas canvas) { canvas.save(); canvas.translate(mWidth / 2, mHeight / 2); destPath.reset(); destPath.lineTo(0, 0); // destPath if (stateEnum == StateEnum.LOADING) { if (endAngle >= 300 || startAngle > minAngle) { startAngle += 6; if (endAngle > 20) { endAngle -= 6; } } if (startAngle > minAngle + 300) { minAngle = startAngle; endAngle = 20; } canvas.rotate(curAngle += rotateDelta, 0, 0);//旋轉rotateDelta=4的弧長 canvas.drawArc(rectF, startAngle, endAngle, false, paint); // endAngle += 6 放在 drawArc()后面,是防止剛進入時,突兀的顯示了一段圓弧 if (startAngle == minAngle) { endAngle += 6; } invalidate(); } if (isShowResult) { if (stateEnum == StateEnum.LOAD_SUCCESS) { pathMeasure.setPath(successPath, false); canvas.drawCircle(0, 0, radius - halfStrokeWidth, paint); pathMeasure.getSegment(0, successValue * pathMeasure.getLength(), destPath, true); canvas.drawPath(destPath, paint); } else if (stateEnum == StateEnum.LOAD_FAILED) { canvas.drawCircle(0, 0, radius - halfStrokeWidth, paint); pathMeasure.setPath(rightFailPath, false); pathMeasure.getSegment(0, rightFailValue * pathMeasure.getLength(), destPath, true); if (rightFailValue == 1) { pathMeasure.setPath(leftFailPath, false); pathMeasure.nextContour(); pathMeasure.getSegment(0, leftFailValue * pathMeasure.getLength(), destPath, true); } canvas.drawPath(destPath, paint); } } canvas.restore(); } public void updateState(StateEnum stateEnum) { this.stateEnum = stateEnum; if (stateEnum == StateEnum.LOAD_SUCCESS) { successAnimator.start(); } else if (stateEnum == StateEnum.LOAD_FAILED) { animatorSet.start(); } } public enum StateEnum { LOADING, // 正在加載 LOAD_SUCCESS, // 加載成功,顯示對號 LOAD_FAILED // 加載失敗,顯示叉號 } /** * 創建畫筆 * * @param color 畫筆顏色 * @param strokeWidth 畫筆寬度 * @param style 畫筆樣式 * @return */ private Paint createPaint(int color, int strokeWidth, Paint.Style style) { Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); paint.setStrokeCap(Paint.Cap.ROUND); paint.setStrokeJoin(Paint.Join.ROUND); paint.setColor(color); paint.setStrokeWidth(strokeWidth); paint.setStyle(style); return paint; } /** * dp 轉換成 px * * @param dpValue * @return */ private int dp2Px(int dpValue) { return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, getResources().getDisplayMetrics()); } }
github : https://github.com/xing16/LoadingView
以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。