Android仿支付寶笑臉?biāo)⑿录虞d動(dòng)畫的實(shí)現(xiàn)代碼
看到支付寶的下拉刷新有一個(gè)笑臉的動(dòng)畫,因此自己也動(dòng)手實(shí)現(xiàn)一下。效果圖如下:

一、總體思路
1、靜態(tài)部分的笑臉。
這一部分的笑臉就是一個(gè)半圓弧,加上兩顆眼睛,這部分比較簡(jiǎn)單,用于一開始的展示。
2、動(dòng)態(tài)笑臉的實(shí)現(xiàn)。
2.1、先是從底部有一個(gè)圓形在運(yùn)動(dòng),運(yùn)動(dòng)在左眼位置時(shí)把左眼給繪制,同時(shí)圓形繼續(xù)運(yùn)動(dòng),運(yùn)動(dòng)到右眼位置時(shí)繪制右眼,圓形繼續(xù)運(yùn)動(dòng)到最右邊的位置。
2.2、當(dāng)上面的圓形運(yùn)動(dòng)到最右邊時(shí)候,開始不斷繪制臉,從右向左,臉不斷增長(zhǎng),這里臉設(shè)置為接近半個(gè)圓形的大小。
2.3、當(dāng)臉畫完的時(shí)候,開始讓臉旋轉(zhuǎn)起來(lái),就是一邊在增長(zhǎng)的同時(shí),另一邊是在縮短的。
2.4、最后臉的部分是慢慢縮為一個(gè)點(diǎn)的,此時(shí)動(dòng)畫結(jié)束。
2.5、時(shí)間可以看做時(shí)最底部的那個(gè)圓形運(yùn)動(dòng)了兩周,因此可以用分?jǐn)?shù)來(lái)表示每一部分的運(yùn)動(dòng),如從底部開始到左眼睛的位置,用時(shí)比例為(1/4+1/8),最終控制每一部分的動(dòng)畫比例的和加起來(lái)為2即可。
大概是這樣的時(shí)間比例:(1/4+1/8) + (1/4) +(1/8) +(1/2) +(1/4) +(1/4+1/4) ,其中1/4代表1/4個(gè)圓弧,也是1/4的時(shí)間,其它的類似。
二、代碼實(shí)現(xiàn)
1、重寫onMeasure()方法
處理為wrap_content情況,那么它的specMode是AT_MOST模式,在這種模式下它的寬/高等于spectSize,這種情況下view的spectSize是parentSize,而parentSize是父容器目前可以使用大小,就是父容器當(dāng)前剩余的空間大小, 就相當(dāng)于使用match_parent一樣 的效果,因此我們可以設(shè)置一個(gè)默認(rèn)的值。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpectMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpectSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpectMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpectSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpectMode == MeasureSpec.AT_MOST
&& heightSpectMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, mHeight);
} else if (widthSpectMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(mWidth, heightSpectSize);
} else if (heightSpectMode == MeasureSpec.AT_MOST) {
setMeasuredDimension(widthSpectSize, mHeight);
}
}
2、在構(gòu)造函數(shù)中調(diào)用init()方法
進(jìn)行初始化,之所以看到運(yùn)動(dòng)中圓弧能夠在右邊增長(zhǎng)的同時(shí),左邊的也在減少是使用PathMeasure類中的getSegment方法來(lái)截取任意一段長(zhǎng)度的路徑。
private void init(Context context, AttributeSet attrs) {
drawFilter = new PaintFlagsDrawFilter(0, Paint.ANTI_ALIAS_FLAG
| Paint.FILTER_BITMAP_FLAG);
lineWidth = dip2px(context, lineWidth);
radius = dip2px(context, radius);
path = new Path();
pathCircle = new Path();
pathCircle2 = new Path();
//在path中添加一個(gè)順時(shí)針的圓,這時(shí)候路徑的起點(diǎn)和終點(diǎn)在最后邊
//在畫前半部分的臉和運(yùn)動(dòng)中的臉,起點(diǎn)在最右邊比較方便的計(jì)算,但在最后那部分,運(yùn)動(dòng)的終點(diǎn)
//是在圓形的底部,這樣把路徑圓進(jìn)行轉(zhuǎn)換到底部,方便計(jì)算
pathCircle.addCircle(0, 0, radius, Direction.CW);
pathCircle2.addCircle(0, 0, radius, Direction.CW);
//利用Matrix,讓pathCircle中的圓旋轉(zhuǎn)90度,這樣它的路徑的起點(diǎn)和終點(diǎn)都在底部了
Matrix m = new Matrix();
m.postRotate(90);
pathCircle.transform(m);
//畫臉的筆
paint = new Paint();
//畫眼睛的筆
eyePaint = new Paint();
paint.setColor(blue);
eyePaint.setColor(blue);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth(lineWidth);
eyePaint.setStrokeWidth(lineWidth);
//設(shè)置畫臉的筆的端點(diǎn)為圓角(即起點(diǎn)和終點(diǎn)都是圓角)
paint.setStrokeCap(Paint.Cap.ROUND);
//使用PathMeasure計(jì)算路徑的信息
pm = new PathMeasure();
pm.setPath(pathCircle, false);
pm2 = new PathMeasure();
pm2.setPath(pathCircle2, false);
//路徑的長(zhǎng)度,兩個(gè)路徑都是圓形,因此只計(jì)算其中一個(gè)即可
length = pm.getLength();
eyeRadius = (float)(lineWidth/2.0+lineWidth/5.0);
}
3、畫靜態(tài)笑臉
pm2.getSegment()方法可以獲取指定長(zhǎng)度的路徑,然后保存在path中,在第二步已經(jīng)把一個(gè)圓加到path中去,并初始化了pm2了。
/**靜態(tài)的笑臉
* @param canvas
*/
private void first(Canvas canvas) {
pm2.getSegment(10, length / 2-10, path, true);
canvas.drawPath(path, paint);
path = new Path();
drawEye(canvas);
}
/**一開始畫的眼睛
* @param canvas
*/
public void drawEye(Canvas canvas) {
float x = (float) ((radius) * Math.cos(Math.PI * 45 / 180));
float y = x;
canvas.drawCircle(-x, -y, eyeRadius , eyePaint);
canvas.drawCircle(x, -y, eyeRadius , eyePaint);
}
4、實(shí)現(xiàn)運(yùn)動(dòng)的圓形的方法,即動(dòng)畫開始部分。
這里記得要進(jìn)行角度轉(zhuǎn)換,π=180
/**從底部開始畫一個(gè)在運(yùn)動(dòng)的圓,運(yùn)動(dòng)時(shí)間為0-3/4
* 即從270度開始,逆時(shí)針到0度
* @param canvas
*/
private void drawCircle(Canvas canvas) {
float degree = 270 - 270 * 4 / 3 * fraction;
float x = (float) ((radius ) * Math.cos(Math.PI * degree/180));
float y = -(float) ((radius ) * Math.sin(Math.PI * degree/ 180));
canvas.drawCircle(x, y, eyeRadius, eyePaint);
}
5、在圓形運(yùn)動(dòng)的同時(shí)畫眼睛
在圓形運(yùn)動(dòng)到左眼的位置時(shí),同時(shí)繪制左眼,時(shí)間為1/4+1/8=3/8
運(yùn)動(dòng)到右眼位置時(shí)繪制右眼,時(shí)間為1/4+1/8+1/4=5/8
兩個(gè)眼睛的位置都設(shè)為45度
/* @param canvas
* @param pos 0代表畫左眼,1代表畫右眼
*/
public void drawOneEye(Canvas canvas, int pos) {
float x = (float) ((radius) * Math.cos(Math.PI * 45 / 180));
float y = x;
if (pos == 0) {
canvas.drawCircle(-x, -y, eyeRadius, eyePaint);
}else if(pos==1){
canvas.drawCircle(x, -y, eyeRadius , eyePaint);
}
}
6、動(dòng)畫進(jìn)行之后繪制笑臉
笑臉部分是半個(gè)圓,因此截取的最大長(zhǎng)度是length/2
用的時(shí)間是1/2,畫完之后fraction應(yīng)該到了5/4的時(shí)間了,1/4+1/8+1/4+1/8+1/2=5/4
public void drawFace(Canvas canvas){
//需要重新給path賦值
path=null;
path = new Path();
//根據(jù)時(shí)間來(lái)截取一定長(zhǎng)度的路徑,保存到path中,取值范圍(0,length/2)
pm2.getSegment(0, (float) (length*(fraction-0.75)), path, true);
canvas.drawPath(path, paint);
}
7、笑臉繪制完成后,把它動(dòng)起來(lái)。
把圓臉部分逆時(shí)針旋轉(zhuǎn)起來(lái),截取最大長(zhǎng)度還是length/2, 用的是這個(gè)方法pm2.getSegment(),運(yùn)動(dòng)的時(shí)間為1/4時(shí)間,需要不斷改變起點(diǎn)和終點(diǎn),這樣圓臉才會(huì)動(dòng)起來(lái)
public void rotateFace(Canvas canvas){
path=null;
path = new Path();
pm2.getSegment((float)(length*(fraction-5.0/4)), (float)(length*(fraction-5.0/4)+length*0.5), path, true);
canvas.drawPath(path, paint);
}
8、最后那部分動(dòng)畫的實(shí)現(xiàn)。
剩下的1/4時(shí)間,就用另外一個(gè)PathMeasure,這個(gè)圓的路徑起點(diǎn)是底部的,在初始化時(shí)候已經(jīng)進(jìn)行轉(zhuǎn)換,因?yàn)檫@樣設(shè)置比較方便的計(jì)算它的終點(diǎn)位置。
public void drawLastFact(Canvas canvas){
path = null;
path = new Path();
//從起點(diǎn)的1/4長(zhǎng)度開始(即最左邊的圓點(diǎn)),到圓的路徑的終點(diǎn)(即底部)
pm.getSegment((float)(1.0/4*length+3.0/2*(fraction-3.0/2)*length), (float)(length/2.0+length/8.0+(fraction-3.0/2)*length), path, true);
canvas.drawPath(path, paint);
}
9、屬性動(dòng)畫的實(shí)現(xiàn)
public void performAnim() {
//上面計(jì)算的時(shí)間比例,加起來(lái)就是2,是運(yùn)動(dòng)了兩周,因此這里設(shè)置為(0,2)
val = ValueAnimator.ofFloat(0, 2);
val.setDuration(duration);
val.addUpdateListener(new AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator arg0) {
fraction = (float) arg0.getAnimatedValue();
postInvalidate();
}
});
val.setRepeatCount(repeaCount);
val.start();
val.setRepeatMode(ValueAnimator.RESTART);
}
10 、在onDraw()方法中調(diào)用一上方法。
這里的fraction的范圍是[0,2],每個(gè)片段就用分?jǐn)?shù)表示,最后它們的和剛好是2。
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
super.onLayout(changed, left, top, right, bottom);
if (changed) {
if (changed) {
mWidth = right - left;
mHeight = bottom - top;
}
}
}
@Override
protected void onDraw(Canvas canvas) {
//從畫布上去除鋸齒
canvas.setDrawFilter(drawFilter);
canvas.translate(mWidth / 2, mHeight / 2);
if (fraction == -1||!val.isRunning())
first(canvas);
//從底部開始畫一個(gè)在運(yùn)動(dòng)的圓,運(yùn)動(dòng)時(shí)間為0-3/4
if (0 < fraction && fraction < 0.75) {
drawCircle(canvas);
}
//畫左眼
if (fraction > 1.0 * 3 / 8&&fraction<1.0*6/4) {
drawOneEye(canvas,0);
}
//畫右眼
if(fraction>1.0*5/8&&fraction<1.0*6/4){
drawOneEye(canvas, 1);
}
//畫臉
if(fraction>=0.75&&fraction<=5.0/4){
drawFace(canvas);
}
//把臉運(yùn)動(dòng)起來(lái)
if(fraction>=5.0/4&&fraction<=(5.0/4+1.0/4)){
rotateFace(canvas);
}
if(fraction>=6.0/4){
drawLastFact(canvas);
}
}
11、其它的方法和字段的定義
/**
* 根據(jù)手機(jī)的分辨率從 dp 的單位 轉(zhuǎn)成為 px(像素)
*/
public int dip2px(Context context, float dpValue) {
final float scale = context.getResources().getDisplayMetrics().density;
return (int) (dpValue * scale + 0.5f);
}
//字段
private final int blue = 0xff4aadff;
private int mWidth = 200;
private int mHeight = 200;
private int radius = 20;
private int lineWidth = 5;
private float eyeRadius;
Paint paint,eyePaint;
DrawFilter drawFilter;
Path path, pathCircle,pathCircle2;
PathMeasure pm,pm2;
float length;// 圓周長(zhǎng)
float fraction = -1;
long duration = 2000;
int repeaCount = 8;
float x = 0, y = 0;
ValueAnimator val;
11、自定義控件的使用
//在布局中的設(shè)置 <com.example.test22.view.SmileView android:id="@+id/smile" android:layout_width="match_parent" android:layout_height="100dp"/>
在Activity中,
SmileView smile; smile = (SmileView)findViewById(R.id.smile); //設(shè)置動(dòng)畫執(zhí)行時(shí)間,重復(fù)的次數(shù)。 smile.setDuration(2000); smile.setRepeaCount(8); //執(zhí)行動(dòng)畫 smile.performAnim(); //停止動(dòng)畫 smile.cancelAnim();
三、總結(jié)
我覺得難點(diǎn)在于運(yùn)動(dòng)中圓弧的一邊增長(zhǎng)的同時(shí),另一邊在縮短的控制,計(jì)算的不好就很容易出現(xiàn)從一個(gè)片段到另外一個(gè)片段時(shí)候跳躍十分明顯,在這里我用到兩個(gè)路徑的圓,一個(gè)圓的起點(diǎn)在最右邊,一個(gè)圓起點(diǎn)在底部,這樣在處理最后那部分,片段的終點(diǎn)需要回到底部時(shí)候比較方便。重點(diǎn)在于PathMeasure類的getSegment()方法的運(yùn)用,同時(shí)會(huì)改變默認(rèn)路徑的起點(diǎn)。
以上所述是小編給大家介紹的Android仿支付寶笑臉?biāo)⑿录虞d動(dòng)畫的實(shí)現(xiàn)代碼,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- android實(shí)現(xiàn)加載動(dòng)畫對(duì)話框
- Android 自定義加載動(dòng)畫Dialog彈窗效果的示例代碼
- android自定義波浪加載動(dòng)畫的實(shí)現(xiàn)代碼
- Android 使用 Path 實(shí)現(xiàn)搜索動(dòng)態(tài)加載動(dòng)畫效果
- Android自定義帶加載動(dòng)畫效果的環(huán)狀進(jìn)度條
- Android自定義view利用Xfermode實(shí)現(xiàn)動(dòng)態(tài)文字加載動(dòng)畫
- Android帶數(shù)字或紅點(diǎn)的底部導(dǎo)航攔和聯(lián)網(wǎng)等待加載動(dòng)畫示例
- Android Glide圖片加載(加載監(jiān)聽、加載動(dòng)畫)
- Android實(shí)現(xiàn)跳動(dòng)的小球加載動(dòng)畫效果
- Android實(shí)現(xiàn)仿微軟系統(tǒng)加載動(dòng)畫效果
相關(guān)文章
Android?中TextureView和SurfaceView的屬性方法及示例說(shuō)明
這篇文章主要介紹了Android?中TextureView和SurfaceView的屬性方法及示例說(shuō)明,文章圍繞主題展開詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的小伙伴可以參考一下2022-06-06
Android實(shí)現(xiàn)四級(jí)聯(lián)動(dòng)地址選擇器
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)四級(jí)聯(lián)動(dòng)地址選擇器,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10
Android中超大圖片無(wú)法顯示的問(wèn)題解決
最近在工作中發(fā)現(xiàn)一個(gè)問(wèn)題,在做圖片瀏覽的時(shí)候發(fā)現(xiàn)超大圖圖片無(wú)法顯示,無(wú)奈只能上網(wǎng)找解決方法,后來(lái)通過(guò)測(cè)試找到了解決的方法,下面這篇文章就主要介紹了Android中超大圖無(wú)法顯示的問(wèn)題解決方法,需要的朋友可以參考借鑒。2017-01-01
Android自定義View實(shí)現(xiàn)掃描效果
這篇文章主要為大家詳細(xì)介紹了Android自定義View實(shí)現(xiàn)掃描效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-04-04
Android webview手動(dòng)校驗(yàn)https證書(by 星空武哥)
有些時(shí)候由于Android系統(tǒng)的bug或者其他的原因,導(dǎo)致我們的webview不能驗(yàn)證通過(guò)我們的https證書,最明顯的例子就是華為手機(jī)mate7升級(jí)到Android7.0后,手機(jī)有些網(wǎng)站打不開了,而更新了webview的補(bǔ)丁后就沒(méi)問(wèn)題了2017-09-09
Android基于MLKit實(shí)現(xiàn)條形碼掃碼的代碼示例
這篇文章將借助開源庫(kù)?MLKit?實(shí)現(xiàn)條形碼掃描,對(duì)于商品條形碼也可以很好地識(shí)別成功,該庫(kù)的使用內(nèi)容非常豐富,除了條碼識(shí)別,還有文字識(shí)別、圖像標(biāo)記、人臉檢測(cè)等等,本文篇文章就只介紹最基本的條形碼掃描使用,需要的朋友可以參考下2023-08-08
OpenGL Shader實(shí)例分析(3)等待標(biāo)識(shí)效果
這篇文章主要介紹了OpenGL Shader實(shí)例分析第3篇,等待標(biāo)識(shí)效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-02-02

