前言
像我這樣迷茫的人菊霜,像我這樣尋找的人,像我這樣碌碌無(wú)為的人脊另,你還見(jiàn)過(guò)多少人 ...
詮釋了我的內(nèi)心真實(shí)想法
PathMeasure之直播間送愛(ài)心
Path(路徑)导狡,在繪制自定義控件中占著舉足輕重的地位。稍微復(fù)雜點(diǎn)的控件都會(huì)有著它的身影偎痛。是不是有時(shí)候還在為求控件某點(diǎn)的坐標(biāo)而犯愁呢旱捧?反正當(dāng)時(shí)我還在傻傻的計(jì)算控件路徑的公式去求坐標(biāo)。那么踩麦,如何來(lái)定位任意一個(gè)給定Path的任意一個(gè)點(diǎn)的坐標(biāo)呢枚赡?
Android SDK提供了一個(gè)非常有用的API來(lái)幫助開發(fā)者解決以上的難題,這個(gè)類就是 PathMeasure 谓谦,它的中文意思路徑測(cè)量贫橙,英文學(xué)得不好,只能翻譯這個(gè)淺顯的含義茁计。
先來(lái)揪一揪最終實(shí)現(xiàn)的效果圖:
簡(jiǎn)單控制了愛(ài)心的縮放料皇,速率以及透明度。源碼也非常的簡(jiǎn)單易懂星压,若有什么疑問(wèn)請(qǐng)留言践剂。
文章結(jié)尾會(huì)附上源碼,如果對(duì)你有所幫助娜膘,還望動(dòng)手點(diǎn)一點(diǎn)star
那下面重點(diǎn)來(lái)看一看 PathMeasure
PathMeasure的那些事
首先來(lái)看下 PathMeasure 的 API:
PathMeasure 的 API 非常簡(jiǎn)單逊脯,基本都是望文生義,紅框圈住是比較常用的方法竣贪。接著我們挨著來(lái)揪一揪各個(gè)方法的用法以及含義军洼。
初始化
PathMeasure 的初始化可以直接 new 一個(gè) PathMeasure
pathMeasure = new PathMeasure();
初始化 PathMeasure 后,可以通過(guò)以下的方式將 Path 和 PathMeasure 進(jìn)行綁定:
pathMeasure.setPath(path, false);
當(dāng)然還可以將上面兩步結(jié)合在一起完成有參的構(gòu)造方法來(lái)進(jìn)行初始化:
PathMeasure(Path path, boolean forceClosed)
參數(shù) path 就是需要計(jì)算演怎,測(cè)量的路徑匕争;那么 forceClosed 又代表什么含義呢,字面上強(qiáng)制關(guān)閉
爷耀,接下來(lái)通過(guò)一個(gè)案例來(lái)加深對(duì)它的理解甘桑。
forceClosed參數(shù)
先看下面一段代碼,繪制了兩條線段,分別改變 forceClosed 的值來(lái)獲取 PathMeasure.getLength()
的值:
mPathMeasure = new PathMeasure();
mPath = new Path();
//水平繪制長(zhǎng)為600的線段
mPath.moveTo(800, 200);
mPath.lineTo(200, 200);
//繪制長(zhǎng)為800的字段
mPath.lineTo(200, 1000);
mPathMeasure.setPath(mPath, false);
forceClosed 為 false跑杭,true 的效果圖如下:
可以得出以下的結(jié)論铆帽,forceClosed 為 false 為兩條線段的長(zhǎng)度(600+800),forceClosed 為 ture 為路徑閉合的總長(zhǎng)度(600+800+1000)德谅。簡(jiǎn)單的說(shuō)爹橱,forceClosed 就是 Path 最終是否需要閉合,如果為 ture 的話窄做,則不管關(guān)聯(lián)的 Path 是否是閉合的愧驱,計(jì)算的時(shí)候都會(huì)按閉合來(lái)計(jì)算。
但是這個(gè)參數(shù)對(duì) Path 和 PathMeasure 的影響是需要解釋下的:
forceClosed 參數(shù)對(duì)綁定的 Path 不會(huì)產(chǎn)生任何影響浸策,例如一個(gè)折線段的 Path冯键,本身是沒(méi)有閉合的,forceClosed 設(shè)置為 ture 的時(shí)候庸汗, PathMeasure 計(jì)算的 Path 是閉合的惫确,但 Path 本身繪制出來(lái)是不會(huì)閉合的。
forceClosed 參數(shù)對(duì) PathMeasure 的測(cè)量結(jié)果有影響蚯舱,還是例如前面說(shuō)的一個(gè)折線段的 Path改化,本身沒(méi)有閉合,forceClosed 設(shè)置為 ture 枉昏, PathMeasure 的計(jì)算就會(huì)包含最后一段閉合的路徑陈肛,與原來(lái)的 Path 不同。
還有一點(diǎn)需要注意一下兄裂,如果你繪制的路徑是直線路徑句旱,則設(shè)置 forceClosed 失效。
getLength
PathMeasure.getLength() 的使用非常廣泛晰奖,其作用就是獲取計(jì)算的路徑長(zhǎng)度谈撒。
getSegment
getSegment 用于獲取 Path 的一個(gè)片段,方法如下:
getSegment(float startD, float stopD, Path dst, boolean startWithMoveTo)
參數(shù) | 作用 | 備注 |
---|---|---|
返回值(boolean) | 判斷截取是否成功 | true 表示截取成功匾南,結(jié)果存入dst中啃匿,false 截取失敗,不會(huì)改變dst中內(nèi)容 |
startD | 開始截取位置距離 Path 起點(diǎn)的長(zhǎng)度 | 取值范圍: 0~getLength |
stopD | 結(jié)束截取位置距離 Path 起點(diǎn)的長(zhǎng)度 | 取值范圍: 0~getLength |
dst | 截取的 Path 將會(huì)添加到 dst 中 | 注意: 是添加蛆楞,而不是替換 |
startWithMoveTo | 起始點(diǎn)是否使用 moveTo | 用于保證截取的 Path 第一個(gè)點(diǎn)位置不變 |
-
4.4或者之前的版本溯乒,在默認(rèn)開啟硬件加速的情況下,更改 dst 的內(nèi)容后可能繪制會(huì)出現(xiàn)問(wèn)題豹爹,請(qǐng)關(guān)閉硬件加速或者給 dst 添加一個(gè)單個(gè)操作裆悄,例如:
dst.lineTo(0, 0)
通過(guò)以下案例來(lái)加深對(duì) getSegment 的理解,效果圖如下:
其原理就是通過(guò) getSegment 來(lái)不斷截取 Path 片段臂聋,從而不斷繪制完整的路徑灯帮,代碼如下:
public class CircleView extends View {
Paint mPaint;
Path mPath;
Path mDstPath;
PathMeasure mPathMeasure;
float mPathLength;
float mAnimatedValue;
public CircleView(Context context) {
this(context, null);
}
public CircleView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(2);
mPaint.setColor(Color.RED);
mPathMeasure = new PathMeasure();
mPath = new Path();
mDstPath = new Path();
//圓弧路徑
mPath.addArc(new RectF(200, 200, 600, 600), 0, 359.6f);
//繪制五角星
for (int i = 1; i < 6; i++) {
Point p = getPoint(200, -144 * i);
mPath.lineTo(400 + p.x, 400 + p.y);
}
mPath.close();
mPathMeasure.setPath(mPath, false);
mPathLength = mPathMeasure.getLength();
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1.0f);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.setDuration(3000);
animator.setRepeatCount(-1);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAnimatedValue = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
animator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mDstPath.reset();
// 硬件加速的BUG
mDstPath.lineTo(0, 0);
//獲取到路徑片段
mPathMeasure.getSegment(0, mAnimatedValue * mPathLength, mDstPath, true); //注意startWithMoveTo一般使用true,如果使用false所有的軌跡會(huì)連接上原點(diǎn)崖技。
canvas.drawPath(mDstPath, mPaint);
}
private Point getPoint(float radius, float angle) {
float x = (float) ((radius) * Math.cos(angle * Math.PI / 180f));
float y = (float) ((radius) * Math.sin(angle * Math.PI / 180f));
Point p = new Point(x, y);
return p;
}
private class Point {
private float x;
private float y;
private Point(float x, float y) {
this.x = x;
this.y = y;
}
}
}
代碼中有相應(yīng)的注釋加以說(shuō)明
nextContour
nextContour() 方法用的比較少,比較大部分情況下都只會(huì)有一個(gè) Path 而不是多個(gè)钟哥,畢竟這樣會(huì)增加 Path 的復(fù)雜度,但是如果真有一個(gè) Path瞎访,包含了多個(gè) Path腻贰,那么通過(guò) nextContour 這個(gè)方法,就可以進(jìn)行切換扒秸,同時(shí)播演,默認(rèn)的 API,例如 getLength伴奥,獲取的也是當(dāng)前的這段 Path 所對(duì)應(yīng)的長(zhǎng)度写烤,而不是所有的 Path 的長(zhǎng)度,同時(shí)拾徙,nextContou r獲取 Path 的順序洲炊,與 Path 的添加順序是相同的,這里就不再舉例說(shuō)明了尼啡。
getPosTan
getPosTan(float distance, float[] pos, float[] tan)
這個(gè) API 非常強(qiáng)大暂衡,直播愛(ài)心的實(shí)現(xiàn)就用到了該方法。意思就是獲取路徑上某點(diǎn)的坐標(biāo)及其切線的坐標(biāo)
各個(gè)參數(shù)的含義如下表:
參數(shù) | 作用 | 備注 |
---|---|---|
返回值(boolean) | 判斷截取是否成功 | 數(shù)據(jù)會(huì)存入 pos 和 tan 中崖瞭,false 表示失敗狂巢,pos 和 tan 不會(huì)改變 |
distance | 距離 Path 起點(diǎn)的長(zhǎng)度 | 取值范圍: 0~getLength |
pos | 保存該點(diǎn)的坐標(biāo)值 | 坐標(biāo)值: (x, y) |
tan | 保存該點(diǎn)的正切值 | 正切值: (x, y) |
接著來(lái)看以下案例,效果圖如下:
通常我們按照以下的方式來(lái)轉(zhuǎn)換切線的角度:
float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);
以下是模擬圓上箭頭圍繞圓的運(yùn)動(dòng)趨勢(shì)代碼:
public class ArrowView extends View {
Paint mPaint;
Path mPath;
PathMeasure mPathMeasure;
float mPathLength;
float mAnimatedValue;
float[] pos = new float[2];
float[] tan = new float[2];
public ArrowView(Context context) {
this(context, null);
}
public ArrowView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ArrowView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(6);
mPaint.setColor(Color.RED);
mPathMeasure = new PathMeasure();
mPath = new Path();
mPath.addCircle(0, 0, 200, Path.Direction.CW);
mPathMeasure.setPath(mPath, false);
mPathLength = mPathMeasure.getLength();
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1.0f);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.setDuration(3000);
animator.setRepeatCount(-1);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAnimatedValue = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
animator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mPathMeasure.getPosTan(mAnimatedValue * mPathLength, pos, tan);
float degrees = (float) (Math.atan2(tan[1], tan[0]) * 180.0 / Math.PI);
canvas.save();
canvas.translate(400, 400);
mPaint.setColor(Color.RED);
canvas.drawPath(mPath, mPaint);
//繪制箭頭
Path path = new Path(); //不建議在onDraw中直接new一個(gè)對(duì)象
path.moveTo(40 * (float) Math.cos(-30 * Math.PI / 180f), 200 + 40 * (float) Math.sin(-30 * Math.PI / 180f));
path.lineTo(0, 200);
path.lineTo(40 * (float) Math.cos(30 * Math.PI / 180f), 200 + 40 * (float) Math.sin(30 * Math.PI / 180f));
canvas.rotate(degrees);
mPaint.setColor(Color.GREEN);
canvas.drawPath(path, mPaint);
canvas.restore();
}
}
接著我們來(lái)看一看后一個(gè) API
getMatrix
getMatrix 方法用于獲取路徑上某一長(zhǎng)度的位置以及位置的正切值的矩陣书聚,方法體如下:
getMatrix(float distance, Matrix matrix, int flags)
參數(shù) | 作用 | 備注 |
---|---|---|
返回值(boolean) | 判斷獲取是否成功 true表示成功 | 數(shù)據(jù)會(huì)存入matrix中唧领,false 失敗,matrix內(nèi)容不會(huì)改變 |
distance | 距離 Path 起點(diǎn)的長(zhǎng)度 | 取值范圍: 0~getLength |
matrix | 根據(jù) falgs 標(biāo)記轉(zhuǎn)換成matrix | 會(huì)根據(jù) flags 的設(shè)置而存入不同的矩陣 |
flags | 規(guī)定哪些內(nèi)容會(huì)存入到matrix中 | POSITION_MATRIX_FLAG(位置) TANGENT_MATRIX_FLAG(正切) |
參數(shù)的含義:
參數(shù) | 作用 | 備注 |
---|---|---|
返回值(boolean) | 判斷獲取是否成功 true表示成功 | 數(shù)據(jù)會(huì)存入matrix中雌续,false 失敗斩个,matrix內(nèi)容不會(huì)改變 |
distance | 距離 Path 起點(diǎn)的長(zhǎng)度 | 取值范圍: 0~getLength |
matrix | 根據(jù) falgs 標(biāo)記轉(zhuǎn)換成matrix | 會(huì)根據(jù) flags 的設(shè)置而存入不同的矩陣 |
flags | 規(guī)定哪些內(nèi)容會(huì)存入到matrix中 | POSITION_MATRIX_FLAG(位置) TANGENT_MATRIX_FLAG(正切) |
flags 選項(xiàng)可以選擇 位置
或者 正切
,如果我們兩個(gè)選項(xiàng)都想選擇怎么辦?
可以將兩個(gè)選項(xiàng)之間用 |
連接起來(lái)西雀,如下:
pathMeasure.getMatrix(distance, matrix, PathMeasure.TANGENT_MATRIX_FLAG| PathMeasure.POSITION_MATRIX_FLAG);
以下是內(nèi)切圓的案例萨驶,效果圖如下:
代碼如下:
public class MatrixView extends View {
Paint mPaint;
Path mPath;
PathMeasure mPathMeasure;
Matrix mMatrix;
float mPathLength;
float mAnimatedValue;
Bitmap mBitmap;
public MatrixView(Context context) {
this(context, null);
}
public MatrixView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public MatrixView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(2);
mPaint.setColor(Color.RED);
mPathMeasure = new PathMeasure();
mPath = new Path();
mMatrix = new Matrix();
mBitmap = BitmapFactory.decodeResource(getResources(), R.mipmap.ic_matrix);
mPath.addCircle(0, 0, 200, Path.Direction.CW);
mPathMeasure.setPath(mPath, false);
mPathLength = mPathMeasure.getLength();
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1.0f);
animator.setInterpolator(new AccelerateDecelerateInterpolator());
animator.setDuration(3000);
animator.setRepeatCount(-1);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
mAnimatedValue = (float) valueAnimator.getAnimatedValue();
invalidate();
}
});
animator.start();
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
mMatrix.reset();
mPathMeasure.getMatrix(mAnimatedValue * mPathLength, mMatrix,
PathMeasure.POSITION_MATRIX_FLAG | PathMeasure.TANGENT_MATRIX_FLAG);
canvas.translate(400, 400);
canvas.drawBitmap(mBitmap, mMatrix, mPaint);
canvas.drawPath(mPath, mPaint);
}
}
相信 PathMeasure 的強(qiáng)大會(huì)讓你愛(ài)不釋手。
PathMeasure送愛(ài)心
熟悉了 PathMeasure 相關(guān)的 API 艇肴,那么理解以下的代碼就非常容易了腔呜。原理非常簡(jiǎn)單繪制三階貝塞爾曲線根據(jù) getPosTan 獲取曲線上坐標(biāo),最后根據(jù)坐標(biāo)繪制愛(ài)心 Bitmap
再悼,具體代碼如下:
public class HeartView extends View {
private SparseArray<Bitmap> mBitmapSparseArray = new SparseArray<>();
private SparseArray<Heart> mHeartSparseArray;
private Paint mPaint;
private int mWidth;
private int mHeight;
private Matrix mMatrix;
//動(dòng)畫時(shí)長(zhǎng)
private int mDuration;
//最大速率
private int mMaxRate;
//是否控制速率
private boolean mRateEnable;
//是否控制透明度速率
private boolean mAlphaEnable;
//是否控制縮放
private boolean mScaleEnable;
//動(dòng)畫時(shí)長(zhǎng)
private final static int DURATION_TIME = 3000;
//最大速率
private final static int MAX_RATE = 2000;
public HeartView(Context context) {
this(context, null);
}
public HeartView(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public HeartView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context);
}
private void init(Context context) {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mHeartSparseArray = new SparseArray<>();
mMatrix = new Matrix();
mDuration = DURATION_TIME;
mMaxRate = MAX_RATE;
mRateEnable = true;
mAlphaEnable = true;
mScaleEnable = true;
initBitmap(context);
}
private void initBitmap(Context context) {
Bitmap bitmap1 = BitmapFactory.decodeResource(context.getResources(), R.mipmap.live_heart1);
Bitmap bitmap2 = BitmapFactory.decodeResource(context.getResources(), R.mipmap.live_heart2);
Bitmap bitmap3 = BitmapFactory.decodeResource(context.getResources(), R.mipmap.live_heart3);
Bitmap bitmap4 = BitmapFactory.decodeResource(context.getResources(), R.mipmap.live_heart4);
Bitmap bitmap5 = BitmapFactory.decodeResource(context.getResources(), R.mipmap.live_heart5);
Bitmap bitmap6 = BitmapFactory.decodeResource(context.getResources(), R.mipmap.live_heart6);
Bitmap bitmap7 = BitmapFactory.decodeResource(context.getResources(), R.mipmap.live_heart7);
mBitmapSparseArray.put(HeartType.BLUE, bitmap1);
mBitmapSparseArray.put(HeartType.GREEN, bitmap2);
mBitmapSparseArray.put(HeartType.YELLOW, bitmap3);
mBitmapSparseArray.put(HeartType.PINK, bitmap4);
mBitmapSparseArray.put(HeartType.BROWN, bitmap5);
mBitmapSparseArray.put(HeartType.PURPLE, bitmap6);
mBitmapSparseArray.put(HeartType.RED, bitmap7);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
//wrap_content情況-默認(rèn)高度為200寬度100
int defaultWidth = (int) dp2px(100);
if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(defaultWidth, defaultWidth * 3);
} else if (widthSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(defaultWidth, heightSpecSize);
} else if (heightSpecMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpecSize, defaultWidth * 3);
} else {
setMeasuredDimension(widthSpecSize, heightSpecSize);
}
}
public void addHeart(int arrayIndex) {
if (arrayIndex < 0 || arrayIndex > (mBitmapSparseArray.size() - 1)) return;
Path path = new Path();
final PathMeasure pathMeasure = new PathMeasure();
final float pathLength;
final Heart heart = new Heart();
final int bitmapIndex = arrayIndex;
//繪制三階貝塞爾曲線
PointF start = new PointF();//起點(diǎn)位置
PointF control1 = new PointF(); //貝塞爾控制點(diǎn)
PointF control2 = new PointF(); //貝塞爾控制點(diǎn)
PointF end = new PointF(); //貝塞爾結(jié)束點(diǎn)
initStartAndEnd(start, end);
initControl(control1, control2);
path.moveTo(start.x, start.y);
path.cubicTo(control1.x, control1.y, control2.x, control2.y, end.x, end.y);
pathMeasure.setPath(path, false);
pathLength = pathMeasure.getLength();
ValueAnimator animator = ValueAnimator.ofFloat(0f, 1.0f);
//先加速后減速
animator.setInterpolator(new AccelerateDecelerateInterpolator());
//動(dòng)畫的長(zhǎng)短來(lái)控制速率
animator.setDuration(mDuration + (mRateEnable ? (int) (Math.random() * mMaxRate) : 0));
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
float fraction = valueAnimator.getAnimatedFraction();
float[] pos = new float[2];
pathMeasure.getPosTan(fraction * pathLength, pos, new float[2]);
heart.setX(pos[0]);
heart.setY(pos[1]);
heart.setProgress(fraction);
invalidate();
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mHeartSparseArray.remove(heart.hashCode());
}
@Override
public void onAnimationStart(Animator animation) {
super.onAnimationStart(animation);
mHeartSparseArray.put(heart.hashCode(), heart);
heart.setIndex(bitmapIndex);
}
});
animator.start();
}
public void addHeart() {
addHeart(new Random().nextInt(mBitmapSparseArray.size() - 1));
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (mHeartSparseArray == null || mHeartSparseArray.size() == 0) return;
canvasHeart(canvas);
}
private void canvasHeart(Canvas canvas) {
for (int i = 0; i < mHeartSparseArray.size(); i++) {
Heart heart = mHeartSparseArray.valueAt(i);
//設(shè)置畫筆透明度
mPaint.setAlpha(mAlphaEnable ? (int) (255 * (1.0f - heart.getProgress())) : 255);
//會(huì)覆蓋掉之前的x,y數(shù)值
mMatrix.setTranslate(0, 0);
//位移到x,y
mMatrix.postTranslate(heart.getX(), heart.getY());
mMatrix.postScale(dealToScale(heart.getProgress()), dealToScale(heart.getProgress()),
mWidth / 2, mHeight);
if (heart != null) {
canvas.drawBitmap(mBitmapSparseArray.get(heart.getIndex()), mMatrix, mPaint);
}
}
}
private float dealToScale(float fraction) {
if (fraction < 0.1f && mScaleEnable) {
return 0.5f + fraction / 0.1f * 0.5f;
}
return 1.0f;
}
public void initControl(PointF control1, PointF control2) {
control1.x = (float) (Math.random() * mWidth);
control1.y = (float) (Math.random() * mHeight);
control2.x = (float) (Math.random() * mWidth);
control2.y = (float) (Math.random() * mHeight);
if (control1.x == control2.x && control1.y == control2.y) {
initControl(control1, control2);
}
}
public void initStartAndEnd(PointF start, PointF end) {
start.x = mWidth / 2;
start.y = mHeight;
end.x = mWidth / 2;
end.y = 0;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
mWidth = w;
mHeight = h;
}
@Override
protected void onDetachedFromWindow() {
cancel();
super.onDetachedFromWindow();
}
/**
* 取消已有動(dòng)畫核畴,釋放資源
*/
public void cancel() {
//回收bitmap
for (int i = 0; i < mBitmapSparseArray.size(); i++) {
if (mBitmapSparseArray.valueAt(i) != null) {
mBitmapSparseArray.valueAt(i).recycle();
}
}
}
public int getDuration() {
return mDuration;
}
public void setDuration(int duration) {
mDuration = duration;
}
public int getMaxRate() {
return mMaxRate;
}
public void setMaxRate(int maxRate) {
mMaxRate = maxRate;
}
public boolean getRateEnable() {
return mRateEnable;
}
public void setRateEnable(boolean rateEnable) {
mRateEnable = rateEnable;
}
public boolean getAlphaEnable() {
return mAlphaEnable;
}
public void setAlphaEnable(boolean alphaEnable) {
mAlphaEnable = alphaEnable;
}
public boolean getScaleEnable() {
return mScaleEnable;
}
public void setScaleEnable(boolean scaleEnable) {
mScaleEnable = scaleEnable;
}
private float dp2px(float dp) {
return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, getResources().getDisplayMetrics());
}
}
源代碼
源碼已上傳到Github