Android自定義控件——點(diǎn)贊效果(仿Twitter)

前言

通過自定義控件,意欲模仿Twitter的點(diǎn)贊效果。
主要涉及:
1.三次貝塞爾曲線應(yīng)用;
2.屬性動畫的綜合應(yīng)用;
3.自定義View流程.

拆解原效果

我們先看一下Twitter上的原版效果是怎樣的.
放大后:

好吧!原速的看不太清楚,逐幀延遲后:

因?yàn)檫@個效果有需要使用多個動畫雜糅而成,為了更確切得出沒個子動畫階段所占比例還是用PS大法把它打開,根據(jù)該階段的幀數(shù)以及總幀數(shù)來確定動畫時長如何分配。

實(shí)現(xiàn)

1.動畫控制

這里使用ValueAnimator并設(shè)置插值器為LinearInterpolator來獲得隨時間正比例變化的逐漸增大的整數(shù)值。這個整數(shù)值在這里有三個作用。

1.每監(jiān)聽到一個整數(shù)值變化重繪一次View.

2.根據(jù)整數(shù)值的大小范圍來劃分所處的不同階段,這里共劃分為五個狀態(tài).

  • 繪制心形并伴隨縮小和顏色漸變.
  • 繪制圓并伴隨放大和顏色漸變.
  • 繪制圓環(huán)并伴隨放大和顏色漸變.
  • 圓環(huán)減消失、心形放大、周圍環(huán)繞十四圓點(diǎn).
  • 環(huán)繞的十四圓點(diǎn)向外移動并縮小、透明度漸變、漸隱.

3.以整數(shù)值為基礎(chǔ)來實(shí)現(xiàn)其他動畫效果避免出現(xiàn)大量的ObjectAnimator.</li>

 /**
     * 展現(xiàn)View點(diǎn)擊后的變化效果
     */
    private void startViewMotion() {
        if (animatorTime != null && animatorTime.isRunning())
            return;
        resetState();
        animatorTime = ValueAnimator.ofInt(0, 1200);
        animatorTime.setDuration(mCycleTime);
        animatorTime.setInterpolator(new LinearInterpolator());//需要隨時間勻速變化
        animatorTime.start();
        animatorTime.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int animatedValue = (int) animation.getAnimatedValue();

                if (animatedValue == 0) {
                    if (animatorArgb == null || !animatorArgb.isRunning()) {
                        animatorArgb = ofArgb(mDefaultColor, 0Xfff74769, 0Xffde7bcc);
                        animatorArgb.setDuration(mCycleTime * 28 / 120);
                        animatorArgb.setInterpolator(new LinearInterpolator());
                        animatorArgb.start();
                    }
                } else if (animatedValue <= 100) {
                    float percent = calcPercent(0f, 100f, animatedValue);
                    mCurrentRadius = (int) (mRadius - mRadius * percent);
                    if (animatorArgb != null && animatorArgb.isRunning())
                        mCurrentColor = (int) animatorArgb.getAnimatedValue();
                    mCurrentState = HEART_VIEW;
                    invalidate();

                } else if (animatedValue <= 280) {
                    float percent = calcPercent(100f, 340f, animatedValue);//此階段未達(dá)到最大半徑
                    mCurrentRadius = (int) (2 * mRadius * percent);
                    if (animatorArgb != null && animatorArgb.isRunning())
                        mCurrentColor = (int) animatorArgb.getAnimatedValue();
                    mCurrentState = CIRCLE_VIEW;
                    invalidate();
                } else if (animatedValue <= 340) {
                    float percent = calcPercent(100f, 340f, animatedValue);//半徑接上一階段增加,此階段外環(huán)半徑已經(jīng)最大值
                    mCurrentPercent = 1f - percent + 0.2f > 1f ? 1f : 1f - percent + 0.2f;//用于計(jì)算圓環(huán)寬度,最小0.2,與動畫進(jìn)度負(fù)相關(guān)
                    mCurrentRadius = (int) (2 * mRadius * percent);
                    if (animatorArgb != null && animatorArgb.isRunning())
                        mCurrentColor = (int) animatorArgb.getAnimatedValue();
                    mCurrentState = RING_VIEW;
                    invalidate();
                } else if (animatedValue <= 480) {
                    float percent = calcPercent(340f, 480f, animatedValue);//內(nèi)環(huán)半徑增大直至消亡
                    mCurrentPercent = percent;
                    mCurrentRadius = (int) (2 * mRadius);//外環(huán)半徑不再改變
                    mCurrentState = RING_DOT__HEART_VIEW;
                    invalidate();
                } else if (animatedValue <= 1200) {
                    float percent = calcPercent(480f, 1200f, animatedValue);
                    mCurrentPercent = percent;
                    mCurrentState = DOT__HEART_VIEW;
                    if (animatedValue == 1200) {
                        animatorTime.cancel();
                        animatorTime.removeAllListeners();
                        state = true;
                    }
                    invalidate();

                }


            }
        });


    }

2.圖形繪制

心形

這里使用貝塞爾曲線來繪制心形,通過四組控制點(diǎn)的改變來擬合心形。當(dāng)然項(xiàng)目中為了方便此處的繪制可以用圖片代替。

    //繪制心形
    private void drawHeart(Canvas canvas, int radius, int color) {
        initControlPoints(radius);
        mPaint.setColor(color);
        mPaint.setAntiAlias(true);
        mPaint.setStyle(Paint.Style.FILL);
        Path path = new Path();
        path.moveTo(tPointB.x, tPointB.y);
        path.cubicTo(tPointC.x, tPointC.y, rPointA.x, rPointA.y, rPointB.x, rPointB.y);
        path.cubicTo(rPointC.x, rPointC.y, bPointC.x, bPointC.y, bPointB.x, bPointB.y);
        path.cubicTo(bPointA.x, bPointA.y, lPointC.x, lPointC.y, lPointB.x, lPointB.y);
        path.cubicTo(lPointA.x, lPointA.y, tPointA.x, tPointA.y, tPointB.x, tPointB.y);
        canvas.drawPath(path, mPaint);
    }

其他

還有一些 圓、圓點(diǎn)、圓環(huán)的繪制比較簡單這里不再列出,重點(diǎn)是這些圖形疊加交錯的動畫變化。

3.點(diǎn)擊事件

對外提供點(diǎn)擊事件監(jiān)聽,以便處理點(diǎn)贊與取消點(diǎn)贊的邏輯。

  @Override
    public boolean onTouchEvent(MotionEvent event) {

        int x = (int) event.getX();
        int y = (int) event.getY();
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                break;
            case MotionEvent.ACTION_MOVE:
                break;
            case MotionEvent.ACTION_UP:

                if (x + getLeft() < getRight() && y + getTop() < getBottom()) {//點(diǎn)擊在View區(qū)域內(nèi)
                    if (state) {
                        deselectLike();
                    } else {
                        startViewMotion();
                    }
                    if (mListener != null)
                        mListener.onClick(this);
                }
                break;
        }
        return true;
    }

對外提供設(shè)置監(jiān)聽的方法


    @Override
    public void setOnClickListener(@Nullable OnClickListener l) {
        mListener = l;
    }

獲取是否已點(diǎn)贊的狀態(tài)

  /**
     * Indicates whether this LikeView is  selected  or not.
     *
     * @return true if the LikeView is selected now, false is deselected
     */
    public boolean getState() {
        return this.state;
    }

4.最終效果

總結(jié)

這里大致實(shí)現(xiàn)了Twitter的點(diǎn)贊效果。雖然是根據(jù)原效果圖像幀比例來確定動畫應(yīng)分配時間的,放慢觀察似乎還是不太理想。另有些狀態(tài)確定不了是顏色漸變還是透明度變化,臨界消失時縮放有沒有伴隨移動,這些都從簡處理了。

需要強(qiáng)調(diào)一下的是這里用到了顏色漸變動畫,而這個方法系統(tǒng)是API21才提供的,
這里直接拷貝系統(tǒng)源碼的ArgbEvaluator到項(xiàng)目里了,其實(shí)就相當(dāng)于屬性動畫自定義TypeEvaluator,既然源碼里有,就不客氣了。

源碼:https://github.com/qkxyjren/LikeView

歡迎指正

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容