Android中PathMeasure仿支付寶支付動(dòng)畫
前言
在 Android 自定義 View 中,Path 可能用的比較多,PathMeasure 可能用的比較少,就我而言,以前也沒(méi)有使用過(guò) 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 長(zhǎng)度也變長(zhǎng)了,即 算上了斜邊的長(zhǎng)度。
仿支付寶支付動(dòng)畫 View - LoadingView
效果:
思路:
繪制對(duì)號(hào),叉號(hào),主要是通過(guò) 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的弧長(zhǎng) 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
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- Android中RecyclerView布局代替GridView實(shí)現(xiàn)類似支付寶的界面
- Android波紋擴(kuò)散效果之仿支付寶咻一咻功能實(shí)現(xiàn)波紋擴(kuò)散特效
- Android app第三方支付寶支付接入教程
- Android支付寶和微信支付集成
- Android仿支付寶支付從底部彈窗效果
- Android支付寶支付封裝代碼
- 新版Android studio導(dǎo)入微信支付和支付寶官方Demo問(wèn)題解決大全
- Android開(kāi)發(fā)之實(shí)現(xiàn)GridView支付寶九宮格
- Android自定義View仿支付寶輸入六位密碼功能
- android仿微信支付寶的支付密碼輸入框示例
相關(guān)文章
Android 通過(guò)jni返回Mat數(shù)據(jù)類型方法
今天小編就為大家分享一篇Android 通過(guò)jni返回Mat數(shù)據(jù)類型方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-08-08Android 中ScrollView與ListView沖突問(wèn)題的解決辦法
這篇文章主要介紹了Android 中ScrollView與ListView沖突問(wèn)題的解決辦法的相關(guān)資料,希望通過(guò)本文能幫助到大家,讓大家掌握解決問(wèn)題的辦法,需要的朋友可以參考下2017-10-10Android RadioGroup和RadioButton控件簡(jiǎn)單用法示例
這篇文章主要介紹了Android RadioGroup和RadioButton控件簡(jiǎn)單用法,結(jié)合實(shí)例形式分析了Android單選按鈕控件的基本定義、布局與功能實(shí)現(xiàn)技巧,需要的朋友可以參考下2016-07-07Android實(shí)現(xiàn)帶動(dòng)畫效果的可點(diǎn)擊展開(kāi)TextView
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)帶動(dòng)畫效果的可點(diǎn)擊展開(kāi)TextView,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07如何使用Flutter實(shí)現(xiàn)手寫簽名效果
Flutter插件提供了用于繪制平滑簽名的簽名板,下面這篇文章主要給大家介紹了關(guān)于如何使用Flutter實(shí)現(xiàn)手寫簽名效果的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12Android形狀圖形與狀態(tài)列表圖形及九宮格圖片超詳細(xì)講解
這篇文章主要介紹了Android形狀圖形與狀態(tài)列表圖形及九宮格圖片的應(yīng)用方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-09-09kotlin協(xié)程之coroutineScope函數(shù)使用詳解
這篇文章主要為大家介紹了kotlin協(xié)程之coroutineScope函數(shù)使用詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09Android 滑動(dòng)監(jiān)聽(tīng)RecyclerView線性流+左右劃刪除+上下移動(dòng)
這篇文章主要介紹了Android 滑動(dòng)監(jiān)聽(tīng)RecyclerView線性流+左右劃刪除+上下移動(dòng)的相關(guān)資料,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2016-09-09Windows下Flutter+Idea環(huán)境搭建及配置
這篇文章介紹了Windows下Flutter+Idea環(huán)境搭建及配置的方法,對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-12-12