Android仿《最美有物》笑臉點贊效果

繼最美有物之后,又在餓了么上看到了類似的控件。果然需求這個東西是做不完的,以為功能做好就OK了嗎,不要停,這里還差一個玩出花來的點贊效果!
直接上動圖,看看兩家的實現(xiàn)效果:
最美有物

餓了么

這樣可愛的點贊特效,不得不給個好評啊, 有沒有!
當(dāng)然光點贊好評是不行的,今天得自己動手?jǐn)]一個這樣的笑臉點贊控件。
主要流程和實現(xiàn)分析
以最美有物的點贊效果為模板(相較餓了么更豐富),從整體的操作流程來分析:
點擊觸發(fā) :
顏色切換(選中黃,未選中白)
按比例拉伸(兩個笑臉同時升高,顯示點贊比例的高度)
展示數(shù)據(jù)文本(百分比數(shù)據(jù)顯示)
顯示選中部分的笑臉動畫(這一步與3文本數(shù)據(jù)同時進行,在拉伸至最高處停留后)
縮回至原始大小,保持選中狀態(tài)
通過這個流程的分析,我們可以把主要的功能點劃分為以下兩類:
1.動畫類 :臉部動畫,拉伸動畫
2.控制類: 顏色切換,數(shù)據(jù)顯示,拉伸比例
在自定義View的過程中,動畫特效是最吸引人,也是最復(fù)雜的部分,雖然現(xiàn)在有高效炫酷的矢量動畫庫供我們選擇, 但是基礎(chǔ)動畫的組合也是相當(dāng)有用的,重點是發(fā)揮想象力。
其中,臉部動畫在解壓apk后找到相關(guān)圖片,不出所料是個幀動畫。
而主要的難點就在于如何進行拉伸操作。對于將一個圓形從中間拉伸成長條...
最開始想到的方案是拼接圖形,即通過圓形 + 矩形 + 圓形的方式疊加這個控件。通過調(diào)節(jié)中間矩形的高度,來控制拉伸操作。但是這種方式結(jié)構(gòu)略復(fù)雜,需要在一小塊地方擺上三個圖形,還要帶上最外層的笑臉動畫,還沒寫代碼就感覺應(yīng)該是性能低下的方案,另外一個致命的問題就是,不能有描邊!大家可以參考餓了么的實現(xiàn)效果,在有描邊的情況下,形狀拼接的方案明顯不可行。遂放棄。
而最終采用的則是使用圓角矩形作為外層Layout背景,通過控制內(nèi)部笑臉的marginBottom,來動態(tài)的調(diào)節(jié)Layout的高度。這樣,即可以保持笑臉始終處于控件的上部,同時也能控制相對簡單的結(jié)構(gòu)。
拉伸操作的實現(xiàn)
首先我們先簡單的模擬下通過marginBottom控制拉伸的效果。
在布局里設(shè)置一個LinearLayout ,里面只有一個ImageView。用一個seekBar來模擬效果。
Layout:
<LinearLayoutandroid:id="@+id/backGround"android:layout_width="wrap_content"android:layout_height="wrap_content"android:padding="10dp"android:background="@drawable/yellow_background"android:layout_above="@+id/seekBar"android:layout_centerHorizontal="true"><ImageViewandroid:id="@+id/smileFace"android:layout_width="40dp"android:layout_height="40dp"android:src="@drawable/like_1"/></LinearLayout>
這里L(fēng)inearlayout中設(shè)置了一個背景,是自定義的圓角矩形shape,通過調(diào)大圓角,使其顯示為一個正圓。
Activity:
@Overridepublic void onProgressChanged(SeekBar seekBar, int i, boolean b) {LinearLayout.LayoutParams layoutParams = (LinearLayout.LayoutParams)smileFace.getLayoutParams();layoutParams.bottomMargin = i*3;smileFace.setLayoutParams(layoutParams);}
通過獲取SmileFace的LayoutParams,通過Seekbar設(shè)置其下邊距bottomMargin,來控制高度。
效果如下所示:

這樣拉伸的原理就很清楚了。
我們需要在自定義控件中完成上述操作,并用屬性動畫替換掉seekBar。
自定義控件的封裝
子控件的布局
考慮到實現(xiàn)目標(biāo)里有兩個并排的笑臉控件,這里采用繼承LinearLayout的方式,可以把兩個控件及中間的分割線直接擺放進去。
首先初始化兩個臉部動畫的ImageView及動畫資源,以及兩個顯示點贊比例的數(shù)字及文本的TextView。
在初始化的時候設(shè)置好相關(guān)參數(shù),提取出默認(rèn)值并提供方法設(shè)置相關(guān)參數(shù)。然后把百分比,文字,包含笑臉的Layout,都添加到另外一個Linearlayout中,然后再將喜歡不喜歡添加到當(dāng)前自定義控件中。
//初始化圖片imageLike = new ImageView(getContext());//添加動畫資源 獲得幀動畫imageLike.setBackgroundResource(R.drawable.animation_like);animLike = (AnimationDrawable) imageLike.getBackground();//初始化文字likeNum = new TextView(getContext());likeNum.setText(like + "%");likeNum.setTextColor(defalutTextColor);TextPaint likeNumPaint = likeNum.getPaint();likeNumPaint.setFakeBoldText(true);likeNum.setTextSize(20f);likeText = new TextView(getContext());likeText.setText(defaultLike);likeText.setTextColor(defalutTextColor);.....disAll.addView(disNum, params);disAll.addView(disText, params);disAll.addView(disBack, params);likeAll.addView(likeNum, params);likeAll.addView(likeText, params);likeAll.addView(likeBack, params);
這里同時還要注意隱藏文字,以及默認(rèn)設(shè)置為未選中狀態(tài)。這一段代碼雖然挺多,其實也只是在View中做了xml里的事情,了解了整體結(jié)構(gòu)后其實非常簡單,實際上直接寫好XML再加載也是沒問題的。
整體流程和動畫分析
控件的事件其實只有兩個點擊事件,需要注意的是動畫流程的控制。
1.拉伸屬性動畫 》 2.表情幀動畫 》3.與2同時進行的平移動畫
直接在控件設(shè)置onClickListener。點擊開始執(zhí)行拉伸動畫。并在動畫開始后限制點擊事件,流程結(jié)束后釋放。避免重復(fù)點擊動畫錯亂。同時給屬性動畫設(shè)置監(jiān)聽,在拉伸執(zhí)行完畢后,繼續(xù)執(zhí)行面部的動畫。
通過屬性動畫,將喜歡不喜歡的數(shù)字比例設(shè)置為兩個笑臉的bottomMargin,這里由于Max是使用兩個數(shù)字和,顯示的高度會依據(jù)數(shù)字的大小有差別,也可以設(shè)置為一個固定值,完全暗戰(zhàn)比例來顯示高度,這個可以依據(jù)自己的數(shù)據(jù)源和需求修改。由于在屬性動畫中同時設(shè)置兩個高度,所以需要通過判斷限制高度,當(dāng)前magrin與達到數(shù)據(jù)要求時停止,從而有比例低的一方會停止拉伸。
拉伸的屬性動畫,與之對應(yīng)還有一個縮回的動畫,如果有需要還可以加上插值器,優(yōu)化彈起的效果。
//背景伸展動畫public void animBack() {//動畫執(zhí)行中不能點擊imageDis.setClickable(false);imageLike.setClickable(false);final int max = Math.max(like * 4, disLike * 4);animatorBack = ValueAnimator.ofInt(5, max);animatorBack.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {@Overridepublic void onAnimationUpdate(ValueAnimator animation) {int magrin = (int) animation.getAnimatedValue();LayoutParams paramsLike= (LayoutParams) imageLike.getLayoutParams();paramsLike.bottomMargin = magrin;if (magrin <= like * 4) {imageLike.setLayoutParams(paramsLike);}if (magrin <= disLike * 4) {imageDis.setLayoutParams(paramsLike);}}});isClose = false;animatorBack.addListener(this);animatorBack.setDuration(500);animatorBack.start();}
拉伸動畫結(jié)束后,幀動畫與平移動畫共同構(gòu)成了面部的表情動畫,通過補間動畫的配合使面部表情更加生動。同時也是用補間動畫的結(jié)束監(jiān)聽來繼續(xù)執(zhí)行動畫恢復(fù)原始狀態(tài)。下圖的animLike為幀動畫,objectX,Y為對應(yīng)軸的平移動畫。
拉伸和恢復(fù)動畫的結(jié)束監(jiān)聽,通過isClose區(qū)分.以及平移動畫。
@Overridepublic void onAnimationEnd(Animator animation) {//重置幀動畫animDis.stop();animLike.stop();//關(guān)閉時不執(zhí)行幀動畫if (isClose) {//收回后可點擊imageDis.setClickable(true);imageLike.setClickable(true);//隱藏文字setVisibities(GONE);//恢復(fù)透明setBackgroundColor(Color.TRANSPARENT);return;}isClose = true;if (type == 0) {animLike.start();objectY(imageLike);} else {animDis.start();objectX(imageDis);}}public void objectY(View view) {ObjectAnimator animator = ObjectAnimator.ofFloat(view, "translationY", -10.0f, 0.0f, 10.0f, 0.0f, -10.0f, 0.0f, 10.0f, 0);animator.setRepeatMode(ObjectAnimator.RESTART);//animator.setRepeatCount(1);animator.setDuration(1500);animator.start();animator.addListener(new AnimatorListenerAdapter() {@Overridepublic void onAnimationEnd(Animator animation) {setBackUp(); //執(zhí)行回彈動畫}});}
最終效果
完整的動畫流程實現(xiàn)后,我們的控件就基本完成了。在XML中直接使用,并使用setNum設(shè)置數(shù)字,基本實現(xiàn)了最美有物的點贊控件效果,簡單的擼了一遍。看下最終效果圖吧。
smileView = (SmileView) findViewById(R.id.smileView);smileView.setNum(60,40);

源碼地址:
https://github.com/zenglingchao/SmileView
到這里就結(jié)束啦。
