您好,登錄后才能下訂單哦!
前言
在 Android 自定義 View 中,Path 可能用的比較多,PathMeasure 可能用的比較少,就我而言,以前也沒有使用過 PathMeasure 這個(gè) api,看到別人用 PathMeasure 和 ValueAnimator 結(jié)合在一起完成了很好的動(dòng)畫效果,于是我也學(xué)習(xí)下 PathMeasure ,此處記錄下。
PathMeasure
構(gòu)造器:
forceClosed 含義:
// 創(chuàng)建一個(gè) Path 對(duì)象 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); // 繪制線段路徑 }
當(dāng) pathMeasure.setPath(path,false) 時(shí):
當(dāng) pathMeasure.setPath(path,true) 時(shí):
可以看到:當(dāng) forceClosed = true 時(shí), path 進(jìn)行了閉合,相應(yīng)的 path 長度也變長了,即 算上了斜邊的長度。
仿支付寶支付動(dòng)畫 View - LoadingView
效果:
思路:
繪制對(duì)號(hào),叉號(hào),主要是通過 ValueAnimator 結(jié)合 getSegment() 不斷繪制新的弧形段,其中,叉號(hào)由兩個(gè) path 組成,在第一個(gè) path 繪制完成時(shí),需要調(diào)用 pathMeasure.nextContour() 跳轉(zhuǎn)到另一個(gè) path。
getSegment() 將獲取的片段填充到 destPath 中,在 Android 4.4 及以下版本中,不能繪制,需要調(diào)用 destPath.reset(),destPath.line(0,0)
LoadingView 完整代碼:
public class LoadingView extends View { private final int DEFAULT_COLOR = Color.BLACK; // 默認(rèn)圓弧顏色 private final int DEFAULT_STROKE_WIDTH = dp2Px(2); // 默認(rèn)圓弧寬度 private final boolean DEFAULT_IS_SHOW_RESULT = false; // 默認(rèn)不顯示加載結(jié)果 private final int DEFAULT_VIEW_WIDTH = dp2Px(50); // 控件默認(rèn)寬度 private final int DEFAULT_VIEW_HEIGHT = dp2Px(50); // 控件默認(rèn)高度 private int color; // 圓弧顏色 private int strokeWidth; // 圓弧寬度 private boolean isShowResult; // 是否顯示加載結(jié)果狀態(tài) 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; // 上方頂點(diǎn) 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()); } /** * 測(cè)量控件的寬高,當(dāng)測(cè)量模式不是精確模式時(shí),設(shè)置默認(rèn)寬高 * * @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);//旋轉(zhuǎn)rotateDelta=4的弧長 canvas.drawArc(rectF, startAngle, endAngle, false, paint); // endAngle += 6 放在 drawArc()后面,是防止剛進(jìn)入時(shí),突兀的顯示了一段圓弧 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, // 加載成功,顯示對(duì)號(hào) LOAD_FAILED // 加載失敗,顯示叉號(hào) } /** * 創(chuàng)建畫筆 * * @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 轉(zhuǎn)換成 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
以上就是本文的全部內(nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持億速云。
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請(qǐng)聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。