前言
今天朋友看了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)匠童。
具體分析
最外面的藍(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
下面確定小手上的四點位置格二,因為在Android上有坐標(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ù)字一個一個繪制出來夜涕。
因為繪制文本內(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ù)