1. 程式人生 > >Android自定義View教你一步一步實現即刻點贊效果

Android自定義View教你一步一步實現即刻點贊效果

前言

今天朋友看了HenCoder的自定義View後說,HenCoder對自定義View講的不錯。實踐中仿寫即刻的點贊你有思路嗎,你不實現一下?二話不說,看了朋友手機效果,對他說:實現不難,用到了位移,縮放,漸變動畫和自定義View的基礎用法,好,那我實現一下,剛好加深對自定義View的理解。

素材準備

把即刻app下載後,以解壓包的方式解壓,發現點贊效果有三張圖,一張是沒有點讚的小手圖片,一張是點贊後的紅色小手圖片,最後一張是點贊後,點贊手指上的四點如下圖:

點贊效果資源

實踐思路

效果圖

即刻點贊效果
先仔細看上面即刻的點贊效果圖,點贊後:灰色小手縮小了一下,並消失變成紅色小手,紅色小手放大了一些,並且手指上有四點出現,下面描述四點影象或者高亮都是指它。另外中間有一圈淡紅色的圓形擴散放大效果,右邊的數是一個一個字元第跳動,並不是整個數一起在跳動中被新的數替換掉,好像是數字輪表。如上面:3往上移並漸漸消失,4從下面出來並漸漸清晰出現。取消點贊後:高亮的四點消失,紅色小手變成灰色小手,字元往下移,整個效果和點贊效果相反,下面準備用一個View來完成以上的實現。

具體分析

區域分析
最外面的藍色矩形就是這個自定義View的區域,裡面有五個矩形,最上面的綠色矩形就是手指頭距離本View頂部的範圍,設定10px,下面綠色矩形也是小手底部距離本View底部的範圍,設定10px。最左邊的紅色矩形是小手最左邊距離本View左邊緣的範圍,設定10px,中間的紅色矩形是小手距離數字文字顯示的範圍,距離10px,最右邊的紅色矩形是數字文字距離本View右邊緣的範圍,設定10px。 這樣整個View的寬和高算出來了: View的寬度 = 小手影象的寬度 + 數字文字寬度 + 30px View的高度 = 小手影象的高度 (因為手指高度比數字文字高度高)+ 20px
高亮四點座標設定
下面確定小手上的四點位置,因為在Android上有座標系這個概念,畫出一張圖只要確定左上角就可以了。
確定高亮四點左上角座標
上圖綠色矩形就是素材點贊後小手上的四點影象區域,綠色的點就是要我們要算出來,X座標10px + 幾px,10px是手指最左邊距離座標系Y軸的距離,為什麼還要加幾px呢,因為四點影象並不是和小手右邊對齊,而是往裡一點,所以還有加多4 - 5px。Y座標其實不能非常準確算出來,我是用整個View的高度減去小手的高度再除2減去高亮四點影象的高度再加上15-17px的距離。 四點影象的左上角座標: X = (15px) Y = (整個影象高度 - 小手影象高度)/ 2 - 高亮影象的高度 + 17px
小手的和手指上的四點位置繪製了,下面繪製數字文字,因為數字文字比較特殊,打算將整形轉換為String型別,再通過字元陣列將數字一個一個繪製出來。
數字文字座標顯示
因為繪製文字內容都是通過**drawText(String text,float x,float y,Paint paint)**這個方法實現的,方法的引數很簡單,text是文字內容,x和y分別是文字的座標,但是這個座標並不是文字的左上角和描繪影象有區別的,先說x引數,x引數其實也並不是文字內容的左邊位置,而是比文字內容的左邊再往左一點點,因為在絕大多數字符,它們的寬度是要略微大於實際顯示的寬度,也就是字元的左右兩邊會留一部分空隙,其實就是文字與文字之間,文字與邊框之間的間隔。y引數,y引數是指文字的基線,就是用一條線讓所有文字互相對齊的基準線,不同的語言文字,每個字元的高度和上下位置都是不一樣的,讓不同的文字並排顯示的時候整體看起來很整齊,這個對齊不是頂部對齊或者底部對齊,而是重心對齊。這裡先講到這,具體詳細自行網上查詢。 因為數字字元是一個一個繪製的。 textX = 小手的寬度 + 20px + 右邊字元的的寬度 textY = View的高度 + 文字區域的高度的一半下面實際用法不同,因為座標原點不在左上角 畫完一個字元後,下一個字元的x座標加上上一個字元的寬度即可,高度是不變的,所以不用管。 點贊過程和取消點贊再結合動畫實現就可以了。 上面只是粗略的把影象元素定位了,下面具體實現:

具體實現

初始化

在values下的attrs檔案下新增屬性集合如下:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--name為宣告的屬性集合,可以隨意取,最好是和自定義View一樣的名稱,這樣方便管理-->
    <declare-styleable name="JiKeLikeView">
        <!-- 宣告屬性,名稱為like_number,取值是整形-->
        <attr name="like_number" format="integer"/>
    </declare-styleable>

</resources>
複製程式碼

因為點贊只涉及到數字,所以宣告和定義整形即可。 新建一個類繼承View,並在建構函式中,讀取attrs檔案下配置屬性:

public JiKeLikeView(Context context) {
        this(context, null);
    }

    public JiKeLikeView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public JiKeLikeView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        //獲取attrs檔案下配置屬性
        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.JiKeLikeView);
        //點贊數量 第一個引數就是屬性集合裡面的屬性 固定格式R.styleable+自定義屬性名字
        //第二個引數,如果沒有設定這個屬性,則會取設定的預設值
        likeNumber = typedArray.getInt(R.styleable.JiKeLikeView_like_number, 1999);
        //記得把TypedArray物件回收
        typedArray.recycle();
        init();
    }
複製程式碼

init方法是初始化一些畫筆,文字顯示範圍

private void init() {
        //建立文字顯示範圍
        textRounds = new Rect();
        //點贊數暫時8位
        widths = new float[8];
        //Paint.ANTI_ALIAS_FLAG 屬性是點陣圖抗鋸齒
        //bitmapPaint是影象畫筆
        bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //這是繪製原來數字的畫筆 加入沒點贊之前是45 那麼點贊後就是46 點贊是46 那麼沒點贊就是45
        textPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        oldTextPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //文字顏色大小配置 顏色灰色 字型大小為14
        textPaint.setColor(Color.GRAY);
        textPaint.setTextSize(SystemUtil.sp2px(getContext(), 14));
        oldTextPaint.setColor(Color.GRAY);
        oldTextPaint.setTextSize(SystemUtil.sp2px(getContext(), 14));
        //圓畫筆初始化 Paint.Style.STROKE只繪製圖形輪廓
        circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        circlePaint.setColor(Color.RED);
        circlePaint.setStyle(Paint.Style.STROKE);
        //設定輪廓寬度
        circlePaint.setStrokeWidth(SystemUtil.dp2px(getContext(), 2));
        //設定模糊效果 第一個引數是模糊半徑,越大越模糊,第二個引數是陰影的橫向偏移距離,正值向下偏移 負值向上偏移
        //第三個引數是縱向偏移距離,正值向下偏移,負值向上偏移 第四個引數是畫筆的顏色
        circlePaint.setShadowLayer(SystemUtil.dp2px(getContext(), 1), SystemUtil.dp2px(getContext(), 1), SystemUtil.dp2px(getContext(), 1), Color.RED);

    }
複製程式碼

在onAttachedToWindow方法上建立Bitmap物件

    /**
     * 這個方法是在Activity resume的時候被呼叫的,Activity對應的window被新增的時候
     * 每個view只會呼叫一次,可以做一些初始化操作
     */
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        Resources resources = getResources();
        //構造Bitmap物件,通過BitmapFactory工廠類的static Bitmap decodeResource根據給定的資源id解析成點陣圖
        unLikeBitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_message_unlike);
        likeBitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_message_like);
        shiningBitmap = BitmapFactory.decodeResource(resources, R.drawable.ic_message_like_shining);
    }
複製程式碼

至於為什麼要在這個方法構建而不寫在init方法,上面程式碼附帶了解釋。 另外要在onDetachedFromWindow方法回收bitmap

/**
     * 和onAttachedToWindow對應,在destroy view的時候呼叫
     */
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        //回收bitmap
        unLikeBitmap.recycle();
        likeBitmap.recycle();
        shiningBitmap.recycle();
    }
複製程式碼

構造了三個Bitmap物件,上面分析很清楚了,一個是小手上的四點,一個是點贊小手,最後一個是沒點讚的小手。

計算寬高

    /**
     * 測量寬高
     * 這兩個引數是由父檢視經過計算後傳遞給子檢視
     * @param widthMeasureSpec 寬度
     * @param heightMeasureSpec 高度
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //MeasureSpec值由specMode和specSize共同組成,onMeasure兩個引數的作用根據specMode的不同,有所區別。
        //當specMode為EXACTLY時,子檢視的大小會根據specSize的大小來設定,對於佈局引數中的match_parent或者精確大小值
        //當specMode為AT_MOST時,這兩個引數只表示了子檢視當前可以使用的最大空間大小,而子檢視的實際大小不一定是specSize。所以我們自定義View時,重寫onMeasure方法主要是在AT_MOST模式時,為子檢視設定一個預設的大小,對於佈局引數wrap_content。
        //高度預設是bitmap的高度加上下margin各10dp
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(unLikeBitmap.getHeight() + SystemUtil.dp2px(getContext(), 20), MeasureSpec.EXACTLY);
        //寬度預設是bitmap的寬度加左右margin各10dp和文字寬度和文字右側10dp likeNumber是文字數字
        String textnum = String.valueOf(likeNumber);
        //得到文字的寬度
        float textWidth = textPaint.measureText(textnum, 0, textnum.length());
        //計算整個View的寬度 小手寬度 + 文字寬度 + 30px
        widthMeasureSpec = MeasureSpec.makeMeasureSpec(((int) (unLikeBitmap.getWidth() + textWidth + SystemUtil.dp2px(getContext(), 30))), MeasureSpec.EXACTLY);
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }
複製程式碼

至於上面為什麼用MeasureSpec.EXACTLY,上面已經解釋很清楚了。

繪製onDraw

繪製小手

        super.onDraw(canvas);
        //獲取正個View的高度
        int height = getHeight();
        //取中心
        int centerY = height / 2;
        //小手根據有沒有點贊進行改變
        Bitmap handBitmap = isLike ? likeBitmap : unLikeBitmap;
        //得到影象寬度
        int handBitmapWidth = handBitmap.getWidth();
        //得到影象高度
        int handBitmapHeight = handBitmap.getHeight();

        //畫小手
        int handTop = (height - handBitmapHeight) / 2;
        //先儲存畫布的狀態
        canvas.save();
        //根據bitmap中心進行縮放
        canvas.scale(handScale, handScale, handBitmapWidth / 2, centerY);
        //畫bitmap小手,第一個是引數對應的bitmap,第二個引數是左上角座標,第三個引數上頂部座標,第四個是畫筆
        canvas.drawBitmap(handBitmap, SystemUtil.dp2px(getContext(), 10), handTop, bitmapPaint);
        //讀取之前沒有縮放畫布的狀態
        canvas.restore();
複製程式碼

這裡解釋一下為什麼用到canvas.save()和canvas.restore()呢,因為整個點贊效果是有動畫效果的,對畫布進行縮放,如果不儲存畫布之前的狀態,縮放後繼續繪製其他影象效果並不是你想要的。

畫小手上的四點高亮

//畫上面四點閃亮
        //先確定頂部
        int shiningTop = handTop - shiningBitmap.getHeight() + SystemUtil.dp2px(getContext(), 17);
        //根據隱藏係數設定點亮的透明度
        bitmapPaint.setAlpha((int) (255 * shiningAlpha));
        //儲存畫布狀態
        canvas.save();
        //畫布根據點亮的縮放係數進行縮放
        canvas.scale(shiningScale, shiningScale, handBitmapWidth / 2, handTop);
        //畫出點亮的bitmap
        canvas.drawBitmap(shiningBitmap, SystemUtil.dp2px(getContext(), 15), shiningTop, bitmapPaint);
        //恢復畫筆之前的狀態
        canvas.restore();
        //並且恢復畫筆bitmapPaint透明度
        bitmapPaint.setAlpha(255);
複製程式碼

注意只是用了bitmapPaint.setAlpha()方法設定這四點是否顯示和消失,設定上這四點都是存在畫布上的,點贊後設置setAlpha(255)出現,否則根據透明度來進行顯示,有個變化的趨勢。

畫數字文字區域和繪製點贊時圓圈擴散

這裡分兩種大情況,一種是不同位數的數字變化,另外一種是同位數數字變化

    //畫文字
        String textValue = String.valueOf(likeNumber);
        //如果點讚了,之前的數值就是點贊數-1,如果取消點贊,那麼之前數值(對比點贊後)就是現在顯示的
        String textCancelValue;
        if (isLike) {
            textCancelValue = String.valueOf(likeNumber - 1);
        } else {
            if (isFirst) {
                textCancelValue = String.valueOf(likeNumber + 1);
            } else {
                isFirst = !isFirst;
                textCancelValue = String.valueOf(likeNumber);
            }
        }
        //文字的長度
        int textLength = textValue.length();
        //獲取繪製文字的座標 getTextBounds 返回所有文字的聯合邊界
        textPaint.getTextBounds(textValue, 0, textValue.length(), textRounds);
        //確定X座標 距離手差10dp
        int textX = handBitmapWidth + SystemUtil.dp2px(getContext(), 20);
        //確定Y座標 距離 大影象的一半減去 文字區域高度的一半 即可得出 getTextBounds裡的rect引數得到數值後,
        // 檢視它的屬性值 top、bottom會發現top是一個負數;bottom有時候是0,有時候是正數。結合第一點很容易理解,因為baseline座標看成原點(0,0),
        // 那麼相對位置top在它上面就是負數,bottom跟它重合就為0,在它下面就為負數。像小寫字母j g y等,它們的bounds bottom都是正數,
        // 因為它們都有降部(在西文字型排印學中,降部指的是一個字型中,字母向下延伸超過基線的筆畫部分)。
        int textY = height / 2 - (textRounds.top + textRounds.bottom) / 2;
        //繪製文字 這種情況針對不同位數變化 如 99 到100 999到10000
        if (textLength != textCancelValue.length() || textMaxMove == 0) {
            //第一個引數就是文字內容,第二個引數是文字的X座標,第三個引數是文字的Y座標,注意這個座標
            //並不是文字的左上角 而是與左下角比較接近的位置
            //canvas.drawText(textValue,  textX, textY, textPaint);
            //點贊
            if (isLike) {
                //圓的畫筆根據設定的透明度進行變化
                circlePaint.setAlpha((int) (255 * shingCircleAlpha));
                //畫圓
                canvas.drawCircle(handBitmapWidth / 2 + SystemUtil.dp2px(getContext(), 10), handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 10), ((handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 2)) * shingCircleScale), circlePaint);
                //根據透明度進行變化
                oldTextPaint.setAlpha((int) (255 * (1 - textAlpha)));
                //繪製之前的數字
                canvas.drawText(textCancelValue, textX, textY - textMaxMove + textMoveDistance, oldTextPaint);
                //設定新數字的透明度
                textPaint.setAlpha((int) (255 * textAlpha));
                //繪製新數字(點贊後或者取消點贊)
                canvas.drawText(textValue, textX, textY + textMoveDistance, textPaint);

            } else {
                oldTextPaint.setAlpha((int) (255 * (1 - textAlpha)));
                canvas.drawText(textCancelValue, textX, textY + textMaxMove + textMoveDistance, oldTextPaint);
                textPaint.setAlpha((int) (255 * textAlpha));
                canvas.drawText(textValue, textX, textY + textMoveDistance, textPaint);
            }
            return;
        }
        //下面這種情況區別與99 999 9999這種 就是相同位數變化
        //把文字拆解成一個一個字元 就是獲取字串中每個字元的寬度,把結果填入引數widths
        //相當於measureText()的一個快捷方法,計算等價於對字串中的每個字元分別呼叫measureText(),並把
        //它們的計算結果分別填入widths的不同元素
        textPaint.getTextWidths(textValue, widths);
        //將字串轉換為字元陣列
        char[] chars = textValue.toCharArray();
        char[] oldChars = textCancelValue.toCharArray();

        for (int i = 0; i < chars.length; i++) {
            if (chars[i] == oldChars[i]) {
                textPaint.setAlpha(255);
                canvas.drawText(String.valueOf(chars[i]), textX, textY, textPaint);

            } else {
                //點贊
                if (isLike) {
                    circlePaint.setAlpha((int) (255 * shingCircleAlpha));
                    canvas.drawCircle(handBitmapWidth / 2 + SystemUtil.dp2px(getContext(), 10), handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 10), ((handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 2)) * shingCircleScale), circlePaint);
                    oldTextPaint.setAlpha((int) (255 * (1 - textAlpha)));
                    canvas.drawText(String.valueOf(oldChars[i]), textX, textY - textMaxMove + textMoveDistance, oldTextPaint);
                    textPaint.setAlpha((int) (255 * textAlpha));
                    canvas.drawText(String.valueOf(chars[i]), textX, textY + textMoveDistance, textPaint);
                } else {
                    oldTextPaint.setAlpha((int) (255 * (1 - textAlpha)));
                    canvas.drawText(String.valueOf(oldChars[i]), textX, textY + textMaxMove + textMoveDistance, oldTextPaint);
                    textPaint.setAlpha((int) (255 * textAlpha));
                    canvas.drawText(String.valueOf(chars[i]), textX, textY + textMoveDistance, textPaint);
                }
            }
			 //下一位數字x座標要加上前一位的寬度   	
            textX += widths[i];


        }

複製程式碼

我這裡用了textValue和textCancelValue分別記錄變化前後的數字,下面可能對確定y座標的程式碼有疑問,這裡解釋一下:

        int textY = height / 2 - (textRounds.top + textRounds.bottom) / 2;  
複製程式碼

這裡textRounds.top是負數,座標原點並不是在左上角,而是在文字的基線中,自己再查查相關資料和想想就明白了,上面程式碼也有解釋。 透明度變化就不詳細講了,這裡講講移動距離:

//點贊
            if (isLike) {
                //圓的畫筆根據設定的透明度進行變化
                circlePaint.setAlpha((int) (255 * shingCircleAlpha));
                //畫圓
                canvas.drawCircle(handBitmapWidth / 2 + SystemUtil.dp2px(getContext(), 10), handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 10), ((handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 2)) * shingCircleScale), circlePaint);
                //根據透明度進行變化
                oldTextPaint.setAlpha((int) (255 * (1 - textAlpha)));
                //繪製之前的數字
                canvas.drawText(textCancelValue, textX, textY - textMaxMove + textMoveDistance, oldTextPaint);
                //設定新數字的透明度
                textPaint.setAlpha((int) (255 * textAlpha));
                //繪製新數字(點贊後或者取消點贊)
                canvas.drawText(textValue, textX, textY + textMoveDistance, textPaint);

            } else {
                oldTextPaint.setAlpha((int) (255 * (1 - textAlpha)));
                canvas.drawText(textCancelValue, textX, textY + textMaxMove + textMoveDistance, oldTextPaint);
                textPaint.setAlpha((int) (255 * textAlpha));
                canvas.drawText(textValue, textX, textY + textMoveDistance, textPaint);
            }
複製程式碼

textMaxMove設定是20px,textMoveDistance設定是文字的高度14px

                //繪製之前的數字
                canvas.drawText(textCancelValue, textX, textY - textMaxMove + textMoveDistance, oldTextPaint);
                //繪製新數字(點贊後或者取消點贊)
                canvas.drawText(textValue, textX, textY + textMoveDistance, textPaint);
複製程式碼

這兩行就是繪製新數字,最主要就是y座標的變化,舉個例子應該很好理解:假如現在104,我現在點贊要變成105,textCancelValue是104,textValue是105.因為textMoveDistance是從20變化0逐漸減少的,那麼第一條公式是繪製105,textY - textMaxMove + textMoveDistance,y座標越來越小,所以5就會上移,同理textY + textMoveDistance 根據這條公式4也會上移,因為數值越來越小,還有就是將數字轉換為字串進行處理不難理解。 畫圓圈擴散主要是確定圓圈中心點,半徑大概確定就行:

canvas.drawCircle(handBitmapWidth / 2 + SystemUtil.dp2px(getContext(), 10), handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 10), ((handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 2)) * shingCircleScale), circlePaint);
複製程式碼

前兩個引數就是確定圓中心,我設定在小手影象中心。

觸控處理onTouchEvent

我是設定觸控就觸發點贊事件:

@Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                jump();
                break;
        }
        return super.onTouchEvent(event);
    }
複製程式碼

jump方法如下:

/**
     * 點贊事件觸發
     */
    private void jump() {
        isLike = !isLike;
        if (isLike) {
            ++likeNumber;
            setLikeNum();
            //自定義屬性 在ObjectAnimator中,是先根據屬性值拼裝成對應的set函式名字,比如下面handScale的拼裝方法就是
            //將屬性的第一個字母強制大寫後與set拼接,所以就是setHandScale,然後通過反射找到對應控制元件的setHandScale(float handScale)函式
            //將當前數字值做為setHandScale(float handScale)的引數傳入 set函式呼叫每隔十幾毫秒就會被用一次
            //ObjectAnimator只負責把當前運動動畫的數值傳給set函式,set函式怎麼來做就在裡面寫就行
            ObjectAnimator handScaleAnim = ObjectAnimator.ofFloat(this, "handScale", 1f, 0.8f, 1f);
            //設定動畫時間
            handScaleAnim.setDuration(duration);

            //動畫 點亮手指的四點 從0 - 1出現
            ObjectAnimator shingAlphaAnim = ObjectAnimator.ofFloat(this, "shingAlpha", 0f, 1f);
            // shingAlphaAnim.setDuration(duration);

            //放大 點亮手指的四點
            ObjectAnimator shingScaleAnim = ObjectAnimator.ofFloat(this, "shingScale", 0f, 1f);

            //畫中心圓形有內到外擴散
            ObjectAnimator shingClicleAnim = ObjectAnimator.ofFloat(this, "shingCircleScale", 0.6f, 1f);
            //畫出圓形有1到0消失
            ObjectAnimator shingCircleAlphaAnim = ObjectAnimator.ofFloat(this, "shingCircleAlpha", 0.3f, 0f);


            //動畫集一起播放
            AnimatorSet animatorSet = new AnimatorSet();
            animatorSet.playTogether(handScaleAnim, shingAlphaAnim, shingScaleAnim, shingClicleAnim, shingCircleAlphaAnim);
            animatorSet.start();


        } else {
            //取消點贊
            --likeNumber;
            setLikeNum();
            ObjectAnimator handScaleAnim = ObjectAnimator.ofFloat(this, "handScale", 1f, 0.8f, 1f);
            handScaleAnim.setDuration(duration);
            handScaleAnim.start();

            //手指上的四點消失,透明度設定為0
            setShingAlpha(0);


        }
    }
複製程式碼

上面用了幾個動畫函式,這裡運用了茲定於屬性,上面程式碼解釋很清楚了 動畫會觸發下面相應setXXXX()方法

/**
     * 手指縮放方法
     *
     * @param handScale
     */
    public void setHandScale(float handScale) {
        //傳遞縮放係數
        this.handScale = handScale;
        //請求重繪View樹,即draw過程,檢視發生大小沒有變化就不會呼叫layout過程,並且重繪那些“需要重繪的”檢視
        //如果是view就繪製該view,如果是ViewGroup,就繪製整個ViewGroup
        invalidate();
    }


    /**
     * 手指上四點從0到1出現方法
     *
     * @param shingAlpha
     */

    public void setShingAlpha(float shingAlpha) {
        this.shiningAlpha = shingAlpha;
        invalidate();
    }

    /**
     * 手指上四點縮放方法
     *
     * @param shingScale
     */
    @Keep
    public void setShingScale(float shingScale) {
        this.shiningScale = shingScale;
        invalidate();
    }


    /**
     * 設定數字變化
     */
    public void setLikeNum() {
        //開始移動的Y座標
        float startY;
        //最大移動的高度
        textMaxMove = SystemUtil.dp2px(getContext(), 20);
        //如果點讚了 就下往上移
        if (isLike) {
            startY = textMaxMove;
        } else {
            startY = -textMaxMove;
        }
        ObjectAnimator textInAlphaAnim = ObjectAnimator.ofFloat(this, "textAlpha", 0f, 1f);
        textInAlphaAnim.setDuration(duration);
        ObjectAnimator textMoveAnim = ObjectAnimator.ofFloat(this, "textTranslate", startY, 0);
        textMoveAnim.setDuration(duration);


        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(textInAlphaAnim, textMoveAnim);
        animatorSet.start();
    }


    /**
     * 設定數值透明度
     */

    public void setTextAlpha(float textAlpha) {
        this.textAlpha = textAlpha;
        invalidate();

    }

    /**
     * 設定數值移動
     */

    public void setTextTranslate(float textTranslate) {
        textMoveDistance = textTranslate;
        invalidate();
    }

    /**
     * 畫出圓形波紋
     *
     * @param shingCircleScale
     */
    public void setShingCircleScale(float shingCircleScale) {
        this.shingCircleScale = shingCircleScale;
        invalidate();
    }

    /**
     * 圓形透明度設定
     *
     * @param shingCircleAlpha
     */
    public void setShingCircleAlpha(float shingCircleAlpha) {
        this.shingCircleAlpha = shingCircleAlpha;
        invalidate();

    }
複製程式碼

效果如下:

效果

總結

這個簡單例子對一些自定義View的基本使用都涉及了,如繪製,canvas的一些基本用法等。 和即刻點贊效果還是有區別,可以通過加下動畫差值器優化。 專案程式碼:仿即刻點贊效果

微信公眾號
關注微信公眾號,一面技術一面藝術