Android自定義View教你一步一步實現(xiàn)即刻點贊效果

前言

今天朋友看了HenCoder的自定義View后說,HenCoder對自定義View講的不錯榴鼎。實踐中仿寫即刻的點贊你有思路嗎,你不實現(xiàn)一下晚唇?二話不說巫财,看了朋友手機效果,對他說:實現(xiàn)不難哩陕,用到了位移平项,縮放,漸變動畫和自定義View的基礎(chǔ)用法萌踱,好葵礼,那我實現(xiàn)一下,剛好加深對自定義View的理解并鸵。

素材準(zhǔn)備

把即刻app下載后鸳粉,以解壓包的方式解壓,發(fā)現(xiàn)點贊效果有三張圖园担,一張是沒有點贊的小手圖片届谈,一張是點贊后的紅色小手圖片,最后一張是點贊后弯汰,點贊手指上的四點如下圖:


點贊效果資源

實踐思路

效果圖

即刻點贊效果

先仔細(xì)看上面即刻的點贊效果圖艰山,點贊后:灰色小手縮小了一下,并消失變成紅色小手咏闪,紅色小手放大了一些曙搬,并且手指上有四點出現(xiàn),下面描述四點圖像或者高亮都是指它。另外中間有一圈淡紅色的圓形擴散放大效果纵装,右邊的數(shù)是一個一個字符第跳動征讲,并不是整個數(shù)一起在跳動中被新的數(shù)替換掉,好像是數(shù)字輪表橡娄。如上面:3往上移并漸漸消失诗箍,4從下面出來并漸漸清晰出現(xiàn)。取消點贊后:高亮的四點消失挽唉,紅色小手變成灰色小手滤祖,字符往下移,整個效果和點贊效果相反瓶籽,下面準(zhǔn)備用一個View來完成以上的實現(xiàn)匠童。

具體分析

區(qū)域分析

最外面的藍(lán)色矩形就是這個自定義View的區(qū)域,里面有五個矩形棘劣,最上面的綠色矩形就是手指頭距離本View頂部的范圍俏让,設(shè)定10px,下面綠色矩形也是小手底部距離本View底部的范圍茬暇,設(shè)定10px。最左邊的紅色矩形是小手最左邊距離本View左邊緣的范圍寡喝,設(shè)定10px,中間的紅色矩形是小手距離數(shù)字文本顯示的范圍,距離10px蔫劣,最右邊的紅色矩形是數(shù)字文本距離本View右邊緣的范圍鞭盟,設(shè)定10px。
這樣整個View的寬和高算出來了:
View的寬度 = 小手圖像的寬度 + 數(shù)字文本寬度 + 30px
View的高度 = 小手圖像的高度 (因為手指高度比數(shù)字文本高度高)+ 20px
高亮四點坐標(biāo)設(shè)置

下面確定小手上的四點位置格二,因為在Android上有坐標(biāo)系這個概念劈彪,畫出一張圖只要確定左上角就可以了。
確定高亮四點左上角坐標(biāo)

上圖綠色矩形就是素材點贊后小手上的四點圖像區(qū)域顶猜,綠色的點就是要我們要算出來沧奴,X坐標(biāo)10px + 幾px,10px是手指最左邊距離坐標(biāo)系Y軸的距離长窄,為什么還要加幾px呢滔吠,因為四點圖像并不是和小手右邊對齊,而是往里一點挠日,所以還有加多4 - 5px疮绷。Y坐標(biāo)其實不能非常準(zhǔn)確算出來,我是用整個View的高度減去小手的高度再除2減去高亮四點圖像的高度再加上15-17px的距離嚣潜。
四點圖像的左上角坐標(biāo):
X = (15px)
Y = (整個圖像高度 - 小手圖像高度)/ 2 - 高亮圖像的高度 + 17px
小手的和手指上的四點位置繪制了冬骚,下面繪制數(shù)字文本,因為數(shù)字文本比較特殊,打算將整形轉(zhuǎn)換為String類型只冻,再通過字符數(shù)組將數(shù)字一個一個繪制出來夜涕。
數(shù)字文本坐標(biāo)顯示

因為繪制文本內(nèi)容都是通過drawText(String text,float x,float y,Paint paint)這個方法實現(xiàn)的,方法的參數(shù)很簡單属愤,text是文本內(nèi)容女器,x和y分別是文字的坐標(biāo),但是這個坐標(biāo)并不是文字的左上角和描繪圖像有區(qū)別的住诸,先說x參數(shù)驾胆,x參數(shù)其實也并不是文字內(nèi)容的左邊位置,而是比文字內(nèi)容的左邊再往左一點點贱呐,因為在絕大多數(shù)字符丧诺,它們的寬度是要略微大于實際顯示的寬度,也就是字符的左右兩邊會留一部分空隙奄薇,其實就是文字與文字之間驳阎,文字與邊框之間的間隔。y參數(shù)馁蒂,y參數(shù)是指文字的基線呵晚,就是用一條線讓所有文字互相對齊的基準(zhǔn)線,不同的語言文字沫屡,每個字符的高度和上下位置都是不一樣的饵隙,讓不同的文字并排顯示的時候整體看起來很整齊,這個對齊不是頂部對齊或者底部對齊沮脖,而是重心對齊金矛。這里先講到這,具體詳細(xì)自行網(wǎng)上查找勺届。
因為數(shù)字字符是一個一個繪制的驶俊。
textX = 小手的寬度 + 20px + 右邊字符的的寬度
textY = View的高度 + 文字區(qū)域的高度的一半下面實際用法不同,因為坐標(biāo)原點不在左上角
畫完一個字符后免姿,下一個字符的x坐標(biāo)加上上一個字符的寬度即可饼酿,高度是不變的,所以不用管养泡。
點贊過程和取消點贊再結(jié)合動畫實現(xiàn)就可以了嗜湃。
上面只是粗略的把圖像元素定位了,下面具體實現(xiàn):

具體實現(xiàn)

初始化

在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>

因為點贊只涉及到數(shù)字惩妇,所以聲明和定義整形即可。
新建一個類繼承View筐乳,并在構(gòu)造函數(shù)中歌殃,讀取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);
        //點贊數(shù)量 第一個參數(shù)就是屬性集合里面的屬性 固定格式R.styleable+自定義屬性名字
        //第二個參數(shù),如果沒有設(shè)置這個屬性蝙云,則會取設(shè)置的默認(rèn)值
        likeNumber = typedArray.getInt(R.styleable.JiKeLikeView_like_number, 1999);
        //記得把TypedArray對象回收
        typedArray.recycle();
        init();
    }

init方法是初始化一些畫筆氓皱,文本顯示范圍

private void init() {
        //創(chuàng)建文本顯示范圍
        textRounds = new Rect();
        //點贊數(shù)暫時8位
        widths = new float[8];
        //Paint.ANTI_ALIAS_FLAG 屬性是位圖抗鋸齒
        //bitmapPaint是圖像畫筆
        bitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        //這是繪制原來數(shù)字的畫筆 加入沒點贊之前是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);
        //設(shè)置輪廓寬度
        circlePaint.setStrokeWidth(SystemUtil.dp2px(getContext(), 2));
        //設(shè)置模糊效果 第一個參數(shù)是模糊半徑,越大越模糊勃刨,第二個參數(shù)是陰影的橫向偏移距離波材,正值向下偏移 負(fù)值向上偏移
        //第三個參數(shù)是縱向偏移距離,正值向下偏移身隐,負(fù)值向上偏移 第四個參數(shù)是畫筆的顏色
        circlePaint.setShadowLayer(SystemUtil.dp2px(getContext(), 1), SystemUtil.dp2px(getContext(), 1), SystemUtil.dp2px(getContext(), 1), Color.RED);

    }

在onAttachedToWindow方法上創(chuàng)建Bitmap對象

    /**
     * 這個方法是在Activity resume的時候被調(diào)用的廷区,Activity對應(yīng)的window被添加的時候
     * 每個view只會調(diào)用一次,可以做一些初始化操作
     */
    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        Resources resources = getResources();
        //構(gòu)造Bitmap對象贾铝,通過BitmapFactory工廠類的static Bitmap decodeResource根據(jù)給定的資源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);
    }

至于為什么要在這個方法構(gòu)建而不寫在init方法隙轻,上面代碼附帶了解釋。
另外要在onDetachedFromWindow方法回收bitmap

/**
     * 和onAttachedToWindow對應(yīng)垢揩,在destroy view的時候調(diào)用
     */
    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        //回收bitmap
        unLikeBitmap.recycle();
        likeBitmap.recycle();
        shiningBitmap.recycle();
    }

構(gòu)造了三個Bitmap對象玖绿,上面分析很清楚了,一個是小手上的四點水孩,一個是點贊小手镰矿,最后一個是沒點贊的小手。

計算寬高

    /**
     * 測量寬高
     * 這兩個參數(shù)是由父視圖經(jīng)過計算后傳遞給子視圖
     * @param widthMeasureSpec 寬度
     * @param heightMeasureSpec 高度
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //MeasureSpec值由specMode和specSize共同組成俘种,onMeasure兩個參數(shù)的作用根據(jù)specMode的不同,有所區(qū)別绝淡。
        //當(dāng)specMode為EXACTLY時宙刘,子視圖的大小會根據(jù)specSize的大小來設(shè)置,對于布局參數(shù)中的match_parent或者精確大小值
        //當(dāng)specMode為AT_MOST時牢酵,這兩個參數(shù)只表示了子視圖當(dāng)前可以使用的最大空間大小悬包,而子視圖的實際大小不一定是specSize。所以我們自定義View時馍乙,重寫onMeasure方法主要是在AT_MOST模式時布近,為子視圖設(shè)置一個默認(rèn)的大小,對于布局參數(shù)wrap_content丝格。
        //高度默認(rèn)是bitmap的高度加上下margin各10dp
        heightMeasureSpec = MeasureSpec.makeMeasureSpec(unLikeBitmap.getHeight() + SystemUtil.dp2px(getContext(), 20), MeasureSpec.EXACTLY);
        //寬度默認(rèn)是bitmap的寬度加左右margin各10dp和文字寬度和文字右側(cè)10dp likeNumber是文本數(shù)字
        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撑瞧,上面已經(jīng)解釋很清楚了。

繪制onDraw

繪制小手

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

        //畫小手
        int handTop = (height - handBitmapHeight) / 2;
        //先保存畫布的狀態(tài)
        canvas.save();
        //根據(jù)bitmap中心進行縮放
        canvas.scale(handScale, handScale, handBitmapWidth / 2, centerY);
        //畫bitmap小手显蝌,第一個是參數(shù)對應(yīng)的bitmap预伺,第二個參數(shù)是左上角坐標(biāo),第三個參數(shù)上頂部坐標(biāo),第四個是畫筆
        canvas.drawBitmap(handBitmap, SystemUtil.dp2px(getContext(), 10), handTop, bitmapPaint);
        //讀取之前沒有縮放畫布的狀態(tài)
        canvas.restore();

這里解釋一下為什么用到canvas.save()和canvas.restore()呢酬诀,因為整個點贊效果是有動畫效果的脏嚷,對畫布進行縮放,如果不保存畫布之前的狀態(tài)瞒御,縮放后繼續(xù)繪制其他圖像效果并不是你想要的父叙。

畫小手上的四點高亮

//畫上面四點閃亮
        //先確定頂部
        int shiningTop = handTop - shiningBitmap.getHeight() + SystemUtil.dp2px(getContext(), 17);
        //根據(jù)隱藏系數(shù)設(shè)置點亮的透明度
        bitmapPaint.setAlpha((int) (255 * shiningAlpha));
        //保存畫布狀態(tài)
        canvas.save();
        //畫布根據(jù)點亮的縮放系數(shù)進行縮放
        canvas.scale(shiningScale, shiningScale, handBitmapWidth / 2, handTop);
        //畫出點亮的bitmap
        canvas.drawBitmap(shiningBitmap, SystemUtil.dp2px(getContext(), 15), shiningTop, bitmapPaint);
        //恢復(fù)畫筆之前的狀態(tài)
        canvas.restore();
        //并且恢復(fù)畫筆bitmapPaint透明度
        bitmapPaint.setAlpha(255);

注意只是用了bitmapPaint.setAlpha()方法設(shè)置這四點是否顯示和消失,設(shè)置上這四點都是存在畫布上的肴裙,點贊后設(shè)置setAlpha(255)出現(xiàn)趾唱,否則根據(jù)透明度來進行顯示,有個變化的趨勢践宴。

畫數(shù)字文本區(qū)域和繪制點贊時圓圈擴散

這里分兩種大情況鲸匿,一種是不同位數(shù)的數(shù)字變化,另外一種是同位數(shù)數(shù)字變化

    //畫文字
        String textValue = String.valueOf(likeNumber);
        //如果點贊了阻肩,之前的數(shù)值就是點贊數(shù)-1带欢,如果取消點贊,那么之前數(shù)值(對比點贊后)就是現(xiàn)在顯示的
        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();
        //獲取繪制文字的坐標(biāo) getTextBounds 返回所有文本的聯(lián)合邊界
        textPaint.getTextBounds(textValue, 0, textValue.length(), textRounds);
        //確定X坐標(biāo) 距離手差10dp
        int textX = handBitmapWidth + SystemUtil.dp2px(getContext(), 20);
        //確定Y坐標(biāo) 距離 大圖像的一半減去 文字區(qū)域高度的一半 即可得出 getTextBounds里的rect參數(shù)得到數(shù)值后烤惊,
        // 查看它的屬性值 top乔煞、bottom會發(fā)現(xiàn)top是一個負(fù)數(shù);bottom有時候是0柒室,有時候是正數(shù)渡贾。結(jié)合第一點很容易理解,因為baseline坐標(biāo)看成原點(0,0)雄右,
        // 那么相對位置top在它上面就是負(fù)數(shù)空骚,bottom跟它重合就為0,在它下面就為負(fù)數(shù)擂仍。像小寫字母j g y等囤屹,它們的bounds bottom都是正數(shù),
        // 因為它們都有降部(在西文字體排印學(xué)中逢渔,降部指的是一個字體中肋坚,字母向下延伸超過基線的筆畫部分)。
        int textY = height / 2 - (textRounds.top + textRounds.bottom) / 2;
        //繪制文字 這種情況針對不同位數(shù)變化 如 99 到100 999到10000
        if (textLength != textCancelValue.length() || textMaxMove == 0) {
            //第一個參數(shù)就是文字內(nèi)容肃廓,第二個參數(shù)是文字的X坐標(biāo)智厌,第三個參數(shù)是文字的Y坐標(biāo),注意這個坐標(biāo)
            //并不是文字的左上角 而是與左下角比較接近的位置
            //canvas.drawText(textValue,  textX, textY, textPaint);
            //點贊
            if (isLike) {
                //圓的畫筆根據(jù)設(shè)置的透明度進行變化
                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);
                //根據(jù)透明度進行變化
                oldTextPaint.setAlpha((int) (255 * (1 - textAlpha)));
                //繪制之前的數(shù)字
                canvas.drawText(textCancelValue, textX, textY - textMaxMove + textMoveDistance, oldTextPaint);
                //設(shè)置新數(shù)字的透明度
                textPaint.setAlpha((int) (255 * textAlpha));
                //繪制新數(shù)字(點贊后或者取消點贊)
                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;
        }
        //下面這種情況區(qū)別與99 999 9999這種 就是相同位數(shù)變化
        //把文字拆解成一個一個字符 就是獲取字符串中每個字符的寬度盲赊,把結(jié)果填入?yún)?shù)widths
        //相當(dāng)于measureText()的一個快捷方法铣鹏,計算等價于對字符串中的每個字符分別調(diào)用measureText(),并把
        //它們的計算結(jié)果分別填入widths的不同元素
        textPaint.getTextWidths(textValue, widths);
        //將字符串轉(zhuǎn)換為字符數(shù)組
        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);
                }
            }
             //下一位數(shù)字x坐標(biāo)要加上前一位的寬度    
            textX += widths[i];


        }

我這里用了textValue和textCancelValue分別記錄變化前后的數(shù)字角钩,下面可能對確定y坐標(biāo)的代碼有疑問吝沫,這里解釋一下:

        int textY = height / 2 - (textRounds.top + textRounds.bottom) / 2;  

這里textRounds.top是負(fù)數(shù)呻澜,坐標(biāo)原點并不是在左上角,而是在文本的基線中惨险,自己再查查相關(guān)資料和想想就明白了羹幸,上面代碼也有解釋。
透明度變化就不詳細(xì)講了辫愉,這里講講移動距離:

//點贊
            if (isLike) {
                //圓的畫筆根據(jù)設(shè)置的透明度進行變化
                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);
                //根據(jù)透明度進行變化
                oldTextPaint.setAlpha((int) (255 * (1 - textAlpha)));
                //繪制之前的數(shù)字
                canvas.drawText(textCancelValue, textX, textY - textMaxMove + textMoveDistance, oldTextPaint);
                //設(shè)置新數(shù)字的透明度
                textPaint.setAlpha((int) (255 * textAlpha));
                //繪制新數(shù)字(點贊后或者取消點贊)
                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設(shè)置是20px栅受,textMoveDistance設(shè)置是文字的高度14px

                //繪制之前的數(shù)字
                canvas.drawText(textCancelValue, textX, textY - textMaxMove + textMoveDistance, oldTextPaint);
                //繪制新數(shù)字(點贊后或者取消點贊)
                canvas.drawText(textValue, textX, textY + textMoveDistance, textPaint);

這兩行就是繪制新數(shù)字,最主要就是y坐標(biāo)的變化恭朗,舉個例子應(yīng)該很好理解:假如現(xiàn)在104屏镊,我現(xiàn)在點贊要變成105,textCancelValue是104痰腮,textValue是105.因為textMoveDistance是從20變化0逐漸減少的而芥,那么第一條公式是繪制105,textY - textMaxMove + textMoveDistance膀值,y坐標(biāo)越來越小棍丐,所以5就會上移,同理textY + textMoveDistance 根據(jù)這條公式4也會上移沧踏,因為數(shù)值越來越小歌逢,還有就是將數(shù)字轉(zhuǎn)換為字符串進行處理不難理解。
畫圓圈擴散主要是確定圓圈中心點翘狱,半徑大概確定就行:

canvas.drawCircle(handBitmapWidth / 2 + SystemUtil.dp2px(getContext(), 10), handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 10), ((handBitmapHeight / 2 + SystemUtil.dp2px(getContext(), 2)) * shingCircleScale), circlePaint);

前兩個參數(shù)就是確定圓中心秘案,我設(shè)置在小手圖像中心。

觸摸處理onTouchEvent

我是設(shè)置觸摸就觸發(fā)點贊事件:

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

jump方法如下:

/**
     * 點贊事件觸發(fā)
     */
    private void jump() {
        isLike = !isLike;
        if (isLike) {
            ++likeNumber;
            setLikeNum();
            //自定義屬性 在ObjectAnimator中潦匈,是先根據(jù)屬性值拼裝成對應(yīng)的set函數(shù)名字阱高,比如下面handScale的拼裝方法就是
            //將屬性的第一個字母強制大寫后與set拼接,所以就是setHandScale茬缩,然后通過反射找到對應(yīng)控件的setHandScale(float handScale)函數(shù)
            //將當(dāng)前數(shù)字值做為setHandScale(float handScale)的參數(shù)傳入 set函數(shù)調(diào)用每隔十幾毫秒就會被用一次
            //ObjectAnimator只負(fù)責(zé)把當(dāng)前運動動畫的數(shù)值傳給set函數(shù)讨惩,set函數(shù)怎么來做就在里面寫就行
            ObjectAnimator handScaleAnim = ObjectAnimator.ofFloat(this, "handScale", 1f, 0.8f, 1f);
            //設(shè)置動畫時間
            handScaleAnim.setDuration(duration);

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

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

            //畫中心圓形有內(nèi)到外擴散
            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();

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


        }
    }

上面用了幾個動畫函數(shù)寒屯,這里運用了茲定于屬性,上面代碼解釋很清楚了
動畫會觸發(fā)下面相應(yīng)setXXXX()方法

/**
     * 手指縮放方法
     *
     * @param handScale
     */
    public void setHandScale(float handScale) {
        //傳遞縮放系數(shù)
        this.handScale = handScale;
        //請求重繪View樹黍少,即draw過程寡夹,視圖發(fā)生大小沒有變化就不會調(diào)用layout過程,并且重繪那些“需要重繪的”視圖
        //如果是view就繪制該view厂置,如果是ViewGroup,就繪制整個ViewGroup
        invalidate();
    }


    /**
     * 手指上四點從0到1出現(xiàn)方法
     *
     * @param shingAlpha
     */

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

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


    /**
     * 設(shè)置數(shù)字變化
     */
    public void setLikeNum() {
        //開始移動的Y坐標(biāo)
        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();
    }


    /**
     * 設(shè)置數(shù)值透明度
     */

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

    }

    /**
     * 設(shè)置數(shù)值移動
     */

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

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

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

    }

效果如下:


效果

總結(jié)

這個簡單例子對一些自定義View的基本使用都涉及了菩掏,如繪制,canvas的一些基本用法等昵济。
和即刻點贊效果還是有區(qū)別智绸,可以通過加下動畫差值器優(yōu)化野揪。
項目代碼:仿即刻點贊效果

微信公眾號

關(guān)注微信公眾號,一面技術(shù)一面藝術(shù)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末瞧栗,一起剝皮案震驚了整個濱河市斯稳,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌迹恐,老刑警劉巖挣惰,帶你破解...
    沈念sama閱讀 222,729評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異殴边,居然都是意外死亡憎茂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評論 3 399
  • 文/潘曉璐 我一進店門锤岸,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竖幔,“玉大人,你說我怎么就攤上這事是偷∪猓” “怎么了?”我有些...
    開封第一講書人閱讀 169,461評論 0 362
  • 文/不壞的土叔 我叫張陵晓猛,是天一觀的道長饿幅。 經(jīng)常有香客問我,道長戒职,這世上最難降的妖魔是什么栗恩? 我笑而不...
    開封第一講書人閱讀 60,135評論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮洪燥,結(jié)果婚禮上磕秤,老公的妹妹穿的比我還像新娘。我一直安慰自己捧韵,他們只是感情好市咆,可當(dāng)我...
    茶點故事閱讀 69,130評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著再来,像睡著了一般蒙兰。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上芒篷,一...
    開封第一講書人閱讀 52,736評論 1 312
  • 那天搜变,我揣著相機與錄音,去河邊找鬼针炉。 笑死挠他,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的篡帕。 我是一名探鬼主播殖侵,決...
    沈念sama閱讀 41,179評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼贸呢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了拢军?” 一聲冷哼從身側(cè)響起楞陷,我...
    開封第一講書人閱讀 40,124評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎朴沿,沒想到半個月后猜谚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,657評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡赌渣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,723評論 3 342
  • 正文 我和宋清朗相戀三年魏铅,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片坚芜。...
    茶點故事閱讀 40,872評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡览芳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出鸿竖,到底是詐尸還是另有隱情沧竟,我是刑警寧澤,帶...
    沈念sama閱讀 36,533評論 5 351
  • 正文 年R本政府宣布缚忧,位于F島的核電站悟泵,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏闪水。R本人自食惡果不足惜糕非,卻給世界環(huán)境...
    茶點故事閱讀 42,213評論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望球榆。 院中可真熱鬧朽肥,春花似錦、人聲如沸持钉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,700評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽每强。三九已至始腾,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間空执,已是汗流浹背窘茁。 一陣腳步聲響...
    開封第一講書人閱讀 33,819評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留脆烟,地道東北人。 一個月前我還...
    沈念sama閱讀 49,304評論 3 379
  • 正文 我出身青樓房待,卻偏偏與公主長得像邢羔,于是被迫代替她去往敵國和親驼抹。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,876評論 2 361

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