Android 自定義View 跳動的水果和文字

開頭

這是自定義View和動畫的第二篇,第一篇是Android drawPath實現(xiàn)QQ拖拽泡泡,主要介紹了drawPath 繪制二次貝塞爾曲線的過程。

話不多說,還是先上效果圖吧!(今天手賤升級了Genymotion,就成這個傻逼樣子了!)

效果圖

全局配置

根據(jù)效果圖,再來說說實現(xiàn)的基本過程。上面的Bitmap 的動畫就是使用了屬性動畫ObjectAnimator,而下面的那個跳動的文字,主要就是使用了drawTextOnPath的方法,其實也是基于第一篇講解的drawPath來實現(xiàn)的!所以總的來說就是屬性動畫+drawTextViewPath

動畫介紹

這里一共定義了三個屬性動畫:

private ObjectAnimator distanceDownAnimator;//圖片下降的動畫
private ObjectAnimator distanceUpAnimator;//圖片上升的動畫
private ObjectAnimator offsetAnimator;//文字偏移動畫

動畫這里還要隨便提一嘴動畫插補器Interpolator

 private DecelerateInterpolator decelerateInterpolator = new DecelerateInterpolator();//減速插補器
    private LinearInterpolator linearInterpolator = new LinearInterpolator();//加速插補器
    private LinearOutSlowInInterpolator linearOutSlowInInterpolator = new LinearOutSlowInInterpolator();
    private FastOutSlowInInterpolator fastOutSlowInInterpolator = new FastOutSlowInInterpolator();
    private BounceInterpolator bounceInterpolator = new BounceInterpolator();//反彈插補器  

詳細(xì)的請看這個兄弟的博客,有配圖,很形象直觀的!

這里的話,圖片下落肯定是一個重力加速的過程 使用了LinearInterpolator,而上升的話,肯定是一個減速的過程,使用了DecelerateInterpolator,而文字的跳動,那就非BounceInterpolator 莫屬了!

到這里,動畫的基礎(chǔ)講解暫告一段落。

drawTextOnPath 方法使用介紹

Draw the text, with origin at (x,y), using the specified paint, along the specified path. The paint's Align setting determins where along the path to start the text.

其實這個方法就是drawText() 的方法的基礎(chǔ)上,沿著指定的路徑來繪制對應(yīng)的文字!

drawTextOnPath熱身

對應(yīng)的代碼:

 path.reset();
 path.moveTo(100, 100);
 path.lineTo(300, 200);
 path.lineTo(700, 600);
 canvas.translate(0, 100);
 paint.setStyle(Paint.Style.FILL);
 canvas.drawText(TEST, 0, 0, paint);//直接畫文字 第一個
 canvas.translate(0, 300);
 canvas.drawTextOnPath(TEST, path, 0, 0, paint);//第二組
 paint.setStyle(Paint.Style.STROKE);
 canvas.drawPath(path, paint);
 path.reset();
 path.moveTo(0, 500);
 path.quadTo(400, 800, 800, 500);
 paint.setStyle(Paint.Style.FILL);
 canvas.drawTextOnPath(TEST, path, 0, 0, paint);//第三組,這個也差不多就是后面需要實現(xiàn)的效果了!
 paint.setStyle(Paint.Style.STROKE);
 canvas.drawPath(path, paint);

啦啦啦,通過熱身,可以清楚的看到,要想實現(xiàn)跳動的文字其實很簡單啦,就是動態(tài)的改變Path的路徑,然后在這個路徑上不斷繪制出文字就好了!原理說著都是枯燥的,直接擼上代碼!

OffsetAnimator && OffsetProperty

如上面的介紹,這個Animator就是來控制Path的繪制的。

offsetAnimator = ObjectAnimator.ofFloat(this, mOffsetProperty, 0);
offsetAnimator.setDuration(300);
offsetAnimator.setInterpolator(bounceInterpolator);

這里使用了自定義的屬性OffsetProperty,這個是什么鬼呢?其實就是一個自己定義的屬性啦!

 private Property<PathTextView, Float> mOffsetProperty = new Property<PathTextView, Float>(Float.class, "offset") {
    @Override
    public Float get(PathTextView object) {
        return object.getCurrentOffset();
    }

    @Override
    public void set(PathTextView object, Float value) {

        object.setCurrentOffset(value);
    }
};

 public void setCurrentOffset(Float currentOffset) {
    this.currentOffset = currentOffset;
    invalidate();
}

就是通過屬性動畫,得到新的currentOffset,然后再調(diào)用 invalidate() 不停的重畫!在onDraw() 方法里,有一下代碼片段來更新path,然后根據(jù)path繪制文字!!

if (currentOffset != -1) {
        path.quadTo(dXXX == 0 ? radioCenterX : radioCenterX + dXXX, currentOffset, textWidth, defaultY);
    } else {
        path.lineTo(textWidth, defaultY);
    }
...
canvas.drawTextOnPath(TEST, path, 0, 0, textPaint);

嗯,說到這里,其實今天要講的跳動的問題,其實跳動的文字基本上就OK啦,但是水果忍者的話,就是接下來的重點實現(xiàn)了。


水果忍者

我們這里一共有三個動畫:

private ObjectAnimator distanceDownAnimator;//圖片下降的動畫
private ObjectAnimator distanceUpAnimator;//圖片上升的動畫
private ObjectAnimator offsetAnimator;//文字偏移動畫

動畫的流程

distanceDownAnimator.start ---> distanceDownAnimator.onEnd ---> distanceUpAnimator.start && offsetAnimator.start ---> distanceUpAnimator.end ---> distanceDownAnimator.start

順便提一嘴動畫的回調(diào)監(jiān)聽:

  distanceUpAnimator.addListener(new SimpleAnimatorListener() {
        @Override
        public void onAnimationStart(Animator animation) {
            isUp = true;
            left = !left;
        }

        @Override
        public void onAnimationEnd(Animator animation) {
            distanceDownAnimator.start();
        }
    });


 distanceDownAnimator.addListener(new SimpleAnimatorListener() {

        @Override
        public void onAnimationStart(Animator animation) {
            isUp = false;
            dXXX = 0;
            if (++currentIndex >= bitmaps.size()) {
                currentIndex = 0;
            }
            currentBitmap = bitmaps.get(currentIndex);
            radioCenterY = currentBitmap.getHeight() / 2.0f;

        }

        @Override
        public void onAnimationEnd(Animator animation) {
            offsetAnimator.cancel();
            offsetAnimator.setDuration(200);
            offsetAnimator.setFloatValues(defaultY, defaultY + amplitude, defaultY);
            offsetAnimator.start();

            distanceUpAnimator.start();

        }
    });

效果圖可以看到,目前我一共設(shè)計了三種水果的動畫,先從簡單的豎直方向掉落又上升說起吧!

這里面其實就是兩個動畫,一個Y軸的平移,一個是自身的旋轉(zhuǎn)。

  • Y軸旋轉(zhuǎn),在動畫里面直接指定Y軸的相關(guān)起點為終點,這個就可以實現(xiàn)了!
  • 自身的旋轉(zhuǎn): 這里其實Bitmap自己根本沒有旋轉(zhuǎn),我是旋轉(zhuǎn)了畫布,從而達(dá)到了讓水果看起來自己在旋轉(zhuǎn)的情況。

相關(guān)問題明確

Q1: 水平方向中心怎么確定?

其實就是確認(rèn)布局的寬度,布局的寬度就是文字的寬度

    @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    textHeight = textPaint.getFontMetrics().bottom - textPaint.getFontMetrics().top;
    widthMeasureSpec = MeasureSpec.makeMeasureSpec((int) textPaint.measureText(TEST), MeasureSpec.EXACTLY);//強制使用精準(zhǔn)的測量模式
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}

Q2: 豎直方向起始位置和終點位置怎么確認(rèn)?

其實就是確認(rèn)文字的高度(下落的終點),(圖片的高度(下落的起點))

     @Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    Log.i(TAG, "onSizeChanged: size改變了!!!!");
    super.onSizeChanged(w, h, oldw, oldh);
    currentHeight = h;
    initAnim(h);
}

 private void initAnim(int currentHeight) {
    textHeight = textPaint.getFontMetrics().bottom - textPaint.getFontMetrics().top;//文字的高度獲取
    defaultY = currentHeight - textHeight; //默認(rèn)的最低處,到文字的頂部
    offsetAnimator.setFloatValues(defaultY, defaultY + amplitude, defaultY);
    radioCenterY = currentBitmap.getHeight() / 2.0f;//初始化默認(rèn)高度
    distanceDownAnimator.setFloatValues(radioCenterY, defaultY);
    ....
    }

Q3:旋轉(zhuǎn)的動畫沒有對應(yīng)的Animator,如果控制?

直接獲取 distanceDownAnimator 或者 distanceUpAnimator 的動畫執(zhí)行百分比:

distanceDownAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            fraction = animation.getAnimatedFraction();
        }
    });

Q4:旋轉(zhuǎn)的中心點怎么確認(rèn)?

圖片寬高的一半(如果有水平方向移動也要加上水平偏移量)

float dX = (left ? radioCenterX * fraction : radioCenterX * fraction * -1.0f);//相對于中心點 0 的水平偏移量
radioCenterX = (defaultX + textWidth) / 2.0f;
radioCenterY = currentBitmap.getHeight() / 2.0f;
    
canvas.rotate(360 * fraction, radioCenterX + dX, radioCenterY);

Q5:圖片切換如何實現(xiàn)的?

使用一個集合管理了所有的Bitmap,在Down動畫開始執(zhí)行的時候,去更新當(dāng)前的圖片!

final ArrayList<Bitmap> bitmaps = new ArrayList<>();
bitmaps.add(BitmapFactory.decodeResource(getResources(),       R.drawable.fruit1));
bitmaps.add(BitmapFactory.decodeResource(getResources(), R.drawable.fruit2));
bitmaps.add(BitmapFactory.decodeResource(getResources(), R.drawable.fruit3));
...
 if (++currentIndex >= bitmaps.size()) {
                currentIndex = 0;
            }
currentBitmap = bitmaps.get(currentIndex);

到這里,起點位置終點位置以及旋轉(zhuǎn)中心點位置已經(jīng)確認(rèn)完畢了!無論是下降,還是上升的動畫,都是不斷在改變radioCenterY 的值,原理同之前介紹的offset相同

    private Property<PathTextView, Float> mDistanceProperty = new Property<PathTextView, Float>(Float.class, "distance") {
    @Override
    public Float get(PathTextView object) {
        return object.getCurrentDistance();
    }

    @Override
    public void set(PathTextView object, Float value) {
        object.setCurrentDistance(value);

    }
};


    public void setCurrentDistance(Float currentDistance) {
    this.radioCenterY = currentDistance;
    invalidate();
}

三種動畫切換

首先必須明確的是,這三種動畫,都是修改UpAnimator的相關(guān)邏輯,跳動模式還需要OffsetAnimator的配合(這個稍后說!),其他兩種無外乎就是修改了對應(yīng)的動畫執(zhí)行時間以及一個透明度的效果,而透明度和之前說的旋轉(zhuǎn)效果一直,都是通過fraction這個參數(shù)來控制的!

面向狀態(tài)編程:

 switch (Mode) {
        case Default://默認(rèn)模式
            distanceDownAnimator.setDuration(1000);
            distanceUpAnimator.setDuration(1000);
            distanceUpAnimator.setInterpolator(decelerateInterpolator);
            distanceUpAnimator.setFloatValues(defaultY - textHeight, radioCenterY);
            break;
        case Oblique://曲線模式
            distanceDownAnimator.setDuration(500);

            distanceUpAnimator.setDuration(1000);
            distanceUpAnimator.setInterpolator(decelerateInterpolator);
            distanceUpAnimator.setFloatValues(defaultY - textHeight, radioCenterY + currentBitmap.getHeight());//到達(dá)不了最高處
            break;
        case Bounce://跳動模式
         
            distanceDownAnimator.setDuration(1000);

            distanceUpAnimator.setDuration(2000);
            distanceUpAnimator.setInterpolator(linearOutSlowInInterpolator);
         
            distanceUpAnimator.setFloatValues(defaultY - textHeight , defaultY - 4 * textHeight, (int) (defaultY - textHeight + density * 1.5f), defaultY - 2 * textHeight);
            break;

最后一個模式中,是需要在radioCenterY在移動到最低處去開始執(zhí)行Offset的動畫的,但是這里就有一個問題:根據(jù)fraction沒法去判斷什么時候執(zhí)行到了最低處所以這里我就讓在這種模式的時候,我在setFloatValues()的方法中,第二次到達(dá)的最低點(defaultY - textHeight)的基礎(chǔ)上再向下移動了2dp! 所以 所以 所以,重要的說三遍!,這個是有點兒不精準(zhǔn)滴!影響就是可能它不會跳第二下!哈哈哈。。。

addUpdateListener()中:如果是跳動模式,那么就去獲取對應(yīng)的偏移量,并且重置offsetAnimator的一些參數(shù)!

 if (Mode == Bounce && (int) (defaultY - textHeight + density) == (int) f && !offsetAnimator.isRunning()) {

                dXXX = (left ? radioCenterX * fraction : radioCenterX * fraction * -1.0f);
                offsetAnimator.cancel();
                offsetAnimator.setDuration(300);
                offsetAnimator.setFloatValues(defaultY, defaultY + 50, defaultY);
                offsetAnimator.start();
                Log.i(TAG, "onAnimationUpdate: YY" + (int) f);
                Log.i(TAG, "onAnimationUpdate: XX" + (left ? radioCenterX * fraction : radioCenterX * fraction * -1.0f));
            }

詳細(xì)代碼請移步 Github_Circle
這個倉庫都是自定義View onDraw相關(guān)的!目前正在建設(shè)中!喜歡請記得start fork!!!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,250評論 6 530
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 97,923評論 3 413
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,041評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,475評論 1 308
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,253評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 54,801評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,882評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,023評論 0 285
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,530評論 1 331
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,494評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,639評論 1 366
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,177評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 43,890評論 3 345
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,289評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,552評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,242評論 3 389
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,626評論 2 370

推薦閱讀更多精彩內(nèi)容

  • 系列文章之 Android中自定義View(一)系列文章之 Android中自定義View(二)系列文章之 And...
    YoungerDev閱讀 2,190評論 0 4
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,574評論 25 707
  • 在iOS中隨處都可以看到絢麗的動畫效果,實現(xiàn)這些動畫的過程并不復(fù)雜,今天將帶大家一窺iOS動畫全貌。在這里你可以看...
    F麥子閱讀 5,129評論 5 13
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫、插件、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,142評論 4 61
  • 昨夜喝了八瓶啤酒,看了兩集電視劇 早上突然發(fā)現(xiàn)往常賣早餐的大叔沒有出現(xiàn) 翻出了多年前和朋友的合影,突然覺得當(dāng)時的自...
    羌淄泫閱讀 328評論 0 2