Android中PathMeasure仿支付寶支付動(dòng)畫
前言
在 Android 自定義 View 中,Path 可能用的比較多,PathMeasure 可能用的比較少,就我而言,以前也沒有使用過 PathMeasure 這個(gè) api,看到別人用 PathMeasure 和 ValueAnimator 結(jié)合在一起完成了很好的動(dòng)畫效果,于是我也學(xué)習(xí)下 PathMeasure ,此處記錄下。
PathMeasure
構(gòu)造器:

forceClosed 含義:
// 創(chuàng)建一個(gè) 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); // 繪制線段路徑
}
當(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
效果:

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

