效果:

需要知識(shí):
1. 二次貝塞爾曲線
2. 動(dòng)畫知識(shí)
3. 基礎(chǔ)自定義view知識(shí)
先來解釋下什么叫阻尼運(yùn)動(dòng)
阻尼振動(dòng)是指,由于振動(dòng)系統(tǒng)受到摩擦和介質(zhì)阻力或其他能耗而使振幅隨時(shí)間逐漸衰減的振動(dòng),又稱減幅振動(dòng)、衰減振動(dòng)。[1] 不論是彈簧振子還是單擺由于外界的摩擦和介質(zhì)阻力總是存在,在振動(dòng)過程中要不斷克服外界阻力做功,消耗能量,振幅就會(huì)逐漸減小,經(jīng)過一段時(shí)間,振動(dòng)就會(huì)完全停下來。這種振幅隨時(shí)間減小的振動(dòng)稱為阻尼振動(dòng).因?yàn)檎穹c振動(dòng)的能量有關(guān),阻尼振動(dòng)也就是能量不斷減少的振動(dòng).阻尼振動(dòng)是非簡(jiǎn)諧運(yùn)動(dòng).阻尼振動(dòng)系統(tǒng)屬于耗散系統(tǒng)。這里的阻尼是指任何振動(dòng)系統(tǒng)在振動(dòng)中,由于外界作用或系統(tǒng)本身固有的原因引起的振動(dòng)幅度逐漸下降的特性,以及此一特性的量化表征。

本例中文字部分凹陷就是這種效果,當(dāng)然這篇文章知識(shí)帶你簡(jiǎn)單的使用.
跳動(dòng)的水果效果實(shí)現(xiàn)
剖析:從上面的效果圖中很面就是從頂端向下掉落然后再向上 期間旋轉(zhuǎn)即可.
那么我們首先自定義一個(gè)view繼承FrameLayout
public class My extends FrameLayout {public My(Context context) {super(context);}public My(Context context, AttributeSet attrs) {super(context, attrs);}}需要素材如下三張圖片:

也許有人會(huì)問我看到你效果圖到頂部或者底部就變成向上或者向下了.你三張夠嗎?
答:到頂部或者底部旋轉(zhuǎn)180度即可
我們現(xiàn)在自定義中定義幾個(gè)變量
//用于記錄當(dāng)前圖片使用數(shù)組中的哪張int indexImgFlag = 0;//下沉圖片 前面三個(gè)圖片的idint allImgDown [] = {R.mipmap.p2,R.mipmap.p4,R.mipmap.p6,R.mipmap.p8};//動(dòng)畫效果一次下沉或上彈的時(shí)間 animationDuration*2=一次完整動(dòng)畫時(shí)間int animationDuration = 1000;//彈起來的圖片ImageView iv;//圖片下沉高度(即從最高點(diǎn)到最低點(diǎn)的距離)int downHeight = 2;//掉下去的動(dòng)畫private Animation translateDown;//彈起動(dòng)畫private Animation translateUp;//旋轉(zhuǎn)動(dòng)畫private ObjectAnimator rotation;我們?cè)賮砜纯闯跏蓟瘎?dòng)畫的方法(此方法使用了遞歸思想,實(shí)現(xiàn)無限播放動(dòng)畫,大家可以看看哪里不理解)
//初始化彈跳動(dòng)畫public void MyAnmation(){//下沉效果動(dòng)畫translateDown = new TranslateAnimation(Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,downHeight);translateDown.setDuration(animationDuration);//設(shè)置一個(gè)插值器 動(dòng)畫將會(huì)播放越來越快 模擬重力translateDown.setInterpolator(new AccelerateInterpolator());//上彈動(dòng)畫translateUp = new TranslateAnimation(Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,downHeight,Animation.RELATIVE_TO_SELF,0);translateUp.setDuration(animationDuration);////設(shè)置一個(gè)插值器 動(dòng)畫將會(huì)播放越來越慢 模擬反重力translateUp.setInterpolator(new DecelerateInterpolator());//當(dāng)下沉動(dòng)畫完成時(shí)播放啟動(dòng)上彈translateDown.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {iv.setImageResource(allImgDown[indexImgFlag]);rotation = ObjectAnimator.ofFloat(iv, "rotation", 180f, 360f);rotation.setDuration(1000);rotation.start();}@Overridepublic void onAnimationEnd(Animation animation) {iv.startAnimation(translateUp);}@Overridepublic void onAnimationRepeat(Animation animation) {}});//當(dāng)上移動(dòng)畫完成時(shí) 播放下移動(dòng)畫translateUp.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {indexImgFlag = 1+indexImgFlag>=allImgDown.length?0:1+indexImgFlag;iv.setImageResource(allImgDown[indexImgFlag]);rotation = ObjectAnimator.ofFloat(iv, "rotation", 0.0f, 180f);rotation.setDuration(1000);rotation.start();}@Overridepublic void onAnimationEnd(Animation animation) {//遞歸iv.startAnimation(translateDown);}@Overridepublic void onAnimationRepeat(Animation animation) {}});}以上代碼知識(shí):
插值器:會(huì)讓一個(gè)動(dòng)畫在播放時(shí)在某一時(shí)間段加快動(dòng)畫或者減慢
//設(shè)置一個(gè)插值器 動(dòng)畫將會(huì)播放越來越快 模擬重力
1.translateDown.setInterpolator(new AccelerateInterpolator());
這個(gè)插值器 速率表示圖:

可以從斜率看到使用此插值器 動(dòng)畫將越來越快.意義在于模仿下落時(shí)重力的影響
////設(shè)置一個(gè)插值器 動(dòng)畫將會(huì)播放越來越慢 模擬反重力
2. translateUp.setInterpolator(new DecelerateInterpolator());
速率圖:

最后我們初始化下圖片控件到我們的自定義view
private void init() {//初始化彈跳圖片 控件iv = new ImageView(getContext());ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);iv.setLayoutParams(params);iv.setImageResource(allImgDown[0]);this.addView(iv);iv.measure(0,0);iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {if (!flagMeure){flagMeure =true;//由于畫文字是由基準(zhǔn)線開始path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2, textHeight+iv.getHeight()+downHeight*iv.getHeight());//計(jì)算最大彈力maxElasticFactor = (float) (textHeight / elastic);//初始化貝塞爾曲線path.rQuadTo(textWidth / 2, 0, textWidth, 0);//初始化上彈和下沉動(dòng)畫MyAnmation();iv.startAnimation(translateDown);}}});}上面的知識(shí):
1. iv.measure(0,0);主動(dòng)通知系統(tǒng)去測(cè)量此控件 不然iv.getwidth = 0;//下面這個(gè)是同理 等iv測(cè)量完時(shí)回調(diào) 不然iv.getwidth = 0;2. iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener(){ … }原因:TextView tv = new TextView() 或者 LayoutInflat 填充布局都是
異步所以你在new出來或者填充時(shí)直接獲取自然返回0
到現(xiàn)在為止你只需要在自定義view 的onSizeChanged回調(diào)方法中調(diào)用init()即可看到動(dòng)畫的彈動(dòng)
@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);init();}此方法會(huì)在onmesure方法執(zhí)行完成后回調(diào) 這樣你就可以在此方法獲得自定義view的寬高了
效果圖:

畫文字成u形
首先你得知道如下知識(shí)
貝塞爾曲線:具體學(xué)習(xí)
這里我們用到2此貝塞爾曲線
我們看看大概是什么叫2次貝塞爾曲線
我們看看 三個(gè)點(diǎn) p0 p1 p2 我們 把p0 稱為 開始點(diǎn) p1 為控制點(diǎn) p2 結(jié)束點(diǎn),那么可以用貝塞爾公式畫出如圖曲線
這里大家沒必要深究怎么畫出來. 也不需要你懂 這個(gè)要數(shù)學(xué)基礎(chǔ)的
那么我們?cè)诎沧恐性趺串嬆?#63;
Path path = new Path();//p0的x y坐標(biāo)path.moveTo(p0.x,y);path.rQuadTo(p1.x,p1.y,p2.x,p2.y);
這就是API調(diào)用方法是不是很簡(jiǎn)單?那么你又會(huì)問那么怎么畫出來呢?
很簡(jiǎn)單在 dispatchDraw方法 或者onDraw中 調(diào)用
@Overrideprotected void dispatchDraw(Canvas canvas) {super.dispatchDraw(canvas);canvas.drawPath(path,paint);}那么你畫出來的效果應(yīng)該和在Ps用鋼筆畫出來的差不多 ps中鋼筆工具就是二次貝塞爾曲線
(借用下圖片)
如果你的三個(gè)點(diǎn)的位置如剛開的圖片 p0 p1 p2 (p1在p0右上方并且 p1在p2左上方)一樣那么在屏幕中的顯示效果如下
這里隨擴(kuò)張下dispatchDraw和ondraw的區(qū)別
如果你的自定義view 是繼承view 那么 會(huì)先調(diào)用 ondraw->>dispatchDraw
如果你的自定義view 是繼承viewgroup那么會(huì)跳過ondraw方法直接調(diào)用dispathchDraw這里特別注意!!我們這個(gè)案例中繼承的是FrameLayout, 而frameLayout又是繼承自viewgroup所以….
那么我們回到主題 如何畫一個(gè)U形文字?簡(jiǎn)單就是說按照我們畫出來的曲線在上面寫字 如: 文字是”CSDN開源中國(guó)” 如何讓這幾個(gè)字貼著我們的曲線寫出來?
這里介紹一個(gè)API
canvas.drawTextOnPath()
第一個(gè)參數(shù):文字 類型為字符串
第二個(gè)參數(shù):路徑 也就是我們前面的二次貝塞爾曲線
第三個(gè)參數(shù):沿著路徑文字開始的位置 說白了偏移量
第四個(gè)參數(shù):貼著路徑的高度的偏移量
hOffset:
The distance along the path to add to the text's starting position
vOffset:
The distance above(-) or below(+) the path to position the text
//ok我們看看他可以畫出什么樣子的文字

這種看大家對(duì)貝塞爾曲線的理解,你理解的越深那么你可以畫出的圖像越多,當(dāng)然不一定要用貝塞爾曲線
確定貝塞爾曲線的起點(diǎn)
我們?cè)诨剡^頭來看看我們的效果圖

我們可以看到文字應(yīng)該是在iv(彈跳的圖片中央位置且正好在 iv彈到底部的位置)
這里我們先補(bǔ)充知識(shí)在考慮計(jì)算
我們來學(xué)習(xí)一下文字的測(cè)量我們來看幅圖

我們調(diào)用畫文字的API時(shí)
canvas.drawTextOnPath或者canvas.drawText 是從基準(zhǔn)線開始畫的也就是說途中的baseline開始畫.
如:
canvas.drawText(“FMY”,0,0,paint);
那么你將看不到文字 只能在屏幕看到文字底部如下圖:

另一個(gè)同理API drawTextOnPath 也是
再看看幾個(gè)簡(jiǎn)單的API
1 . paint.measureText(“FMY”);返回在此畫筆paint下寫FMY文字的寬度
下面的API會(huì)把文字的距離左邊left 上邊top 右邊right 底部的bottom的值寫入此矩形 那么
rect.right-rect.left=文字寬度 rect.bottom-rect.top=文字高度//矩形Rect rect = new Rect();//將文字畫入矩形目的是為了測(cè)量高度paint.getTextBounds(printText, 0, printText.length(), rect);
那么請(qǐng)看:
private void init() {//初始化彈跳圖片 控件iv = new ImageView(getContext());ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);iv.setLayoutParams(params);iv.setImageResource(allImgDown[0]);this.addView(iv);//畫筆的初始化paint = new Paint();paint.setStrokeWidth(1);paint.setColor(Color.CYAN);paint.setStyle(Paint.Style.FILL);paint.setTextSize(50);paint.setAntiAlias(true);//矩形Rect rect = new Rect();//將文字畫入矩形目的是為了測(cè)量高度paint.getTextBounds(printText, 0, printText.length(), rect);//文本寬度textWidth = paint.measureText(printText);//獲得文字高度textHeight = rect.bottom - rect.top;//初始化路徑path = new Path();iv.setX(getWidth()/2);iv.measure(0,0);iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {if (!flagMeure){flagMeure =true;//由于畫文字是由基準(zhǔn)線開始path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2, textHeight+iv.getHeight()+downHeight*iv.getHeight());//計(jì)算最大彈力maxElasticFactor = (float) (textHeight / elastic);//初始化貝塞爾曲線path.rQuadTo(textWidth / 2, 0, textWidth, 0);//初始化上彈和下沉動(dòng)畫MyAnmation();iv.startAnimation(translateDown);}}});}我們現(xiàn)在寫一個(gè)類當(dāng)iv圖片(彈跳圖)碰到文字頂部時(shí)設(shè)置一個(gè)監(jiān)聽器 時(shí)間正好是彈圖向上到頂部的時(shí)間 期間不斷讓文字凹陷在恢復(fù)正常
//用于播放文字下沉和上浮動(dòng)畫傳入的數(shù)值必須是 圖片下沉和上升的一次時(shí)間public void initAnimation(int duration){//這里為什maxElasticFactor/4 好看...另一個(gè)同理 這個(gè)數(shù)值大家自行調(diào)整ValueAnimator animator = ValueAnimator.ofFloat(maxElasticFactor/4, (float) (maxElasticFactor / 1.5),0);animator.setDuration(duration/2);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {calc();//重新畫路徑nowElasticFactor= (float) animation.getAnimatedValue();postInvalidate();}});animator.start();}再來一個(gè)重新繪畫路徑計(jì)算的方法
public void calc(){//重置路徑path.reset();//由于畫文字是由基準(zhǔn)線開始path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2, textHeight+iv.getHeight()+downHeight*iv.getHeight());//畫二次貝塞爾曲線path.rQuadTo(textWidth / 2, nowElasticFactor, textWidth, 0);}好了到這里我們看看完整源碼吧:
package com.example.administrator.myapplication;import android.animation.ObjectAnimator;import android.animation.ValueAnimator;import android.content.Context;import android.graphics.Canvas;import android.graphics.Color;import android.graphics.Paint;import android.graphics.Path;import android.graphics.Rect;import android.util.AttributeSet;import android.view.ViewGroup;import android.view.ViewTreeObserver;import android.view.animation.AccelerateInterpolator;import android.view.animation.Animation;import android.view.animation.DecelerateInterpolator;import android.view.animation.TranslateAnimation;import android.widget.FrameLayout;import android.widget.ImageView;public class My extends FrameLayout {//畫筆private Paint paint;//路徑private Path path;//要輸入的文本private String printText = "正在加載";//文本寬private float textWidth;//文本高private float textHeight;//測(cè)量文字寬高的時(shí)候使用的矩形private Rect rect;//最大彈力系數(shù)private float elastic = 1.5f;//最大彈力private float maxElasticFactor;//當(dāng)前彈力private float nowElasticFactor;//用于記錄當(dāng)前圖片使用數(shù)組中的哪張int indexImgFlag = 0;//下沉圖片int allImgDown [] = {R.mipmap.p2,R.mipmap.p4,R.mipmap.p6,R.mipmap.p8};//動(dòng)畫效果一次下沉或上彈的時(shí)間 animationDuration*2=一次完整動(dòng)畫時(shí)間int animationDuration = 1000;//彈起來的圖片ImageView iv;//圖片下沉高度(即從最高點(diǎn)到最低點(diǎn)的距離)int downHeight = 2;private Animation translateDown;private Animation translateUp;private ObjectAnimator rotation;public My(Context context) {super(context);}public My(Context context, AttributeSet attrs) {super(context, attrs);}@Overrideprotected void dispatchDraw(Canvas canvas) {super.dispatchDraw(canvas);canvas.drawTextOnPath(printText, path, 0, 0, paint);}//用于播放文字下沉和上浮動(dòng)畫傳入的數(shù)值必須是 圖片下沉和上升的一次時(shí)間public void initAnimation(int duration){//這里為什maxElasticFactor/4為什么ValueAnimator animator = ValueAnimator.ofFloat(maxElasticFactor/4, (float) (maxElasticFactor / 1.5),0);animator.setDuration(duration/2);animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {calc();nowElasticFactor= (float) animation.getAnimatedValue();postInvalidate();}});animator.start();}public void calc(){//重置路徑path.reset();//由于畫文字是由基準(zhǔn)線開始path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2, textHeight+iv.getHeight()+downHeight*iv.getHeight());//畫二次貝塞爾曲線path.rQuadTo(textWidth / 2, nowElasticFactor, textWidth, 0);}//初始化彈跳動(dòng)畫public void MyAnmation(){//下沉效果動(dòng)畫translateDown = new TranslateAnimation(Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,downHeight);translateDown.setDuration(animationDuration);//設(shè)置一個(gè)插值器 動(dòng)畫將會(huì)播放越來越快 模擬重力translateDown.setInterpolator(new AccelerateInterpolator());//上彈動(dòng)畫translateUp = new TranslateAnimation(Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,0,Animation.RELATIVE_TO_SELF,downHeight,Animation.RELATIVE_TO_SELF,0);translateUp.setDuration(animationDuration);////設(shè)置一個(gè)插值器 動(dòng)畫將會(huì)播放越來越慢 模擬反重力translateUp.setInterpolator(new DecelerateInterpolator());//當(dāng)下沉動(dòng)畫完成時(shí)播放啟動(dòng)上彈translateDown.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {iv.setImageResource(allImgDown[indexImgFlag]);rotation = ObjectAnimator.ofFloat(iv, "rotation", 180f, 360f);rotation.setDuration(1000);rotation.start();}@Overridepublic void onAnimationEnd(Animation animation) {iv.startAnimation(translateUp);initAnimation(animationDuration);}@Overridepublic void onAnimationRepeat(Animation animation) {}});//當(dāng)上移動(dòng)畫完成時(shí) 播放下移動(dòng)畫translateUp.setAnimationListener(new Animation.AnimationListener() {@Overridepublic void onAnimationStart(Animation animation) {indexImgFlag = 1+indexImgFlag>=allImgDown.length?0:1+indexImgFlag;iv.setImageResource(allImgDown[indexImgFlag]);rotation = ObjectAnimator.ofFloat(iv, "rotation", 0.0f, 180f);rotation.setDuration(1000);rotation.start();}@Overridepublic void onAnimationEnd(Animation animation) {//遞歸iv.startAnimation(translateDown);}@Overridepublic void onAnimationRepeat(Animation animation) {}});}boolean flagMeure;@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);init();}private void init() {//初始化彈跳圖片 控件iv = new ImageView(getContext());ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,ViewGroup.LayoutParams.WRAP_CONTENT);iv.setLayoutParams(params);iv.setImageResource(allImgDown[0]);this.addView(iv);//畫筆的初始化paint = new Paint();paint.setStrokeWidth(1);paint.setColor(Color.CYAN);paint.setStyle(Paint.Style.FILL);paint.setTextSize(50);paint.setAntiAlias(true);//矩形rect = new Rect();//將文字畫入矩形目的是為了測(cè)量高度paint.getTextBounds(printText, 0, printText.length(), rect);//文本寬度textWidth = paint.measureText(printText);//獲得文字高度textHeight = rect.bottom - rect.top;//初始化路徑path = new Path();iv.setX(getWidth()/2);iv.measure(0,0);iv.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {@Overridepublic void onGlobalLayout() {if (!flagMeure){flagMeure =true;//由于畫文字是由基準(zhǔn)線開始path.moveTo(iv.getX()-textWidth/2+iv.getWidth()/2, textHeight+iv.getHeight()+downHeight*iv.getHeight());//計(jì)算最大彈力maxElasticFactor = (float) (textHeight / elastic);//初始化貝塞爾曲線path.rQuadTo(textWidth / 2, 0, textWidth, 0);//初始化上彈和下沉動(dòng)畫MyAnmation();iv.startAnimation(translateDown);}}});}}小人奉上源碼一封供大家 參考github源碼下載地址
以上所述是小編給大家介紹的Android自定義view實(shí)現(xiàn)阻尼效果的加載動(dòng)畫,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)武林網(wǎng)網(wǎng)站的支持!
新聞熱點(diǎn)
疑難解答
圖片精選