1. 程式人生 > >自定義控制元件 | 仿《最美有物》點贊效果

自定義控制元件 | 仿《最美有物》點贊效果

前言

最近在跟著Hencode學習自定義控制元件,一直想著自己能夠照著別人寫的demo寫一個好看的View,就科學上網看看了別人的一些demo,看到了一位大神寫的模仿《最美有物》的點贊效果,覺得勝似喜歡,於是也跟著寫了一個demo。主要是看著流程分析自己擼出來的,之前用了自己的寫法,然後看到了大神demo有更好的解決方案,後面也改過來了,所以在程式碼實現方式都差不多。

效果圖

這裡寫圖片描述

動畫分析

1.向上拉伸是一個屬性動畫;
2.笑臉(不開心臉)動畫是一個幀動畫和屬性動畫組合;
3.背景回收也是一個屬性動畫。
(對動畫不熟的需要可以去看看郭神的部落格)

實現分析

點選觸發 :

1.顏色切換(選中黃,未選中白)
2.按比例拉伸(兩個笑臉同時升高,顯示點贊比例的高度)
3.展示資料文字(百分比資料顯示)
4.顯示選中部分的笑臉動畫(這一步與3文字資料同時進行,在拉伸至最高處停留後)
5.縮回至原始大小,保持選中狀態

通過這個流程的分析,我們可以把主要的功能點劃分為以下兩類:
​ 1.動畫類 : 臉部動畫,拉伸動畫

​ 2.控制類: 顏色切換,資料顯示,拉伸比例

在自定義View的過程中,動畫特效是最吸引人,也是最複雜的部分,雖然現在有高效炫酷的向量動畫庫供我們選擇, 但是基礎動畫的組合也是相當有用的,重點是發揮想象力。

主要的難點就在於如何進行拉伸操作。對於將一個圓形從中間拉伸成長條…

最開始想到的方案是拼接圖形,即通過圓形 + 矩形 + 圓形的方式疊加這個控制元件。通過調節中間矩形的高度,來控制拉伸操作。但是這種方式結構略複雜,需要在一小塊地方擺上三個圖形,還要帶上最外層的笑臉動畫,還沒寫程式碼就感覺應該是效能低下的方案,另外一個致命的問題就是,不能有描邊!大家可以參考餓了麼的實現效果,在有描邊的情況下,形狀拼接的方案明顯不可行。遂放棄。

而最終採用的則是使用圓角矩形作為外層Layout背景,通過控制內部笑臉的marginBottom,來動態的調節Layout的高度。這樣,即可以保持笑臉始終處於控制元件的上部,同時也能控制相對簡單的結構。

拉伸操作的實現

首先我們先簡單的模擬下通過marginBottom控制拉伸的效果。

在佈局裡設定一個LinearLayout ,裡面只有一個ImageView。用一個seekBar來模擬效果。

Layout:

  <LinearLayout
        android: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/sb_smile" android:layout_centerHorizontal="true">
<ImageView android:id="@+id/iv_smile_face" android:layout_width="40dp" android:layout_height="40dp" android:src="@drawable/like_1"/> </LinearLayout> <SeekBar android:id="@+id/sb_smile" android:layout_width="match_parent" android:layout_height="50dp" />

這裡Linearlayout中設定了一個背景,是自定義的圓角矩形shape,通過調大圓角,使其顯示為一個正圓。

Activity:

  seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
            @Override
            public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
                LinearLayout.LayoutParams layoutParams= (LinearLayout.LayoutParams) imageView.getLayoutParams();
                layoutParams.bottomMargin=progress*3;
                Log.i(">>>>>",progress+"");
                imageView.setLayoutParams(layoutParams);

            }

通過獲取SmileFace的LayoutParams,通過Seekbar設定其下邊距bottomMargin,來控制高度。

效果如下所示:

這裡寫圖片描述

這樣拉伸的原理就很清楚了。

我們需要在自定義控制元件中完成上述操作,並用屬性動畫替換掉seekBar。

自定義控制元件的封裝

1.子控制元件的佈局

考慮到實現目標裡有兩個並排的笑臉控制元件,這裡採用繼承LinearLayout的方式,可以把兩個控制元件及中間的分割線直接擺放進去。

首先是一個水平排列的線性佈局,裡面又包含了三個子佈局,點贊線性總佈局,中間豎線,差評線性總佈局,點贊線性總佈局是一個垂直排列的的佈局,包含了兩個TextView和一個線性佈局的點贊。
我們先將初始化這些佈局和控制元件。

程式碼如下:

 //初始化文字--點贊
        likeText=new TextView(getContext());
        likeText.setText("喜歡");
        likeText.setTextSize(18);
        likeText.setTextColor(Color.WHITE);
        likeNumText=new TextView(getContext());

        likeNumText.setTextSize(14);
        likeNumText.setText(likeNum+"%");
        likeNumText.setTextColor(Color.WHITE);

        //初始化文字--無感
        disLikeText=new TextView(getContext());
        disLikeText.setText("無感");
        disLikeText.setTextSize(18);
        disLikeText.setTextColor(Color.WHITE);
        disLikeNumText=new TextView(getContext());
        disLikeNumText.setTextSize(14);
        disLikeNumText.setText(disLikeNum+"%");
        disLikeNumText.setTextColor(Color.WHITE);


        //差評線性佈局
        disLikeImageView=new ImageView(getContext());
        disLikeImageView.setImageResource(R.drawable.dislike_1);
        disLikeBack=new LinearLayout(getContext());
        LayoutParams disLikeBackLP=new LayoutParams(defalutSize, defalutSize);
        //disLikeBackLP.gravity=Gravity.BOTTOM;
        disLikeBack.addView(disLikeImageView,disLikeBackLP);
        disLikeBack.setBackgroundResource(R.drawable.white_background);
        disLikeBack.setOrientation(VERTICAL);


        //點贊線性佈局
        likeImageView=new ImageView(getContext());
        likeImageView.setImageResource(R.drawable.like_1);
        LayoutParams likeBackLP=new LayoutParams(defalutSize, defalutSize);
        //likeBackLP.gravity=Gravity.BOTTOM;
        likeBack=new LinearLayout(getContext());
        likeBack.addView(likeImageView,likeBackLP);
        likeBack.setBackgroundResource(R.drawable.white_background);
        likeBack.setOrientation(VERTICAL);


        //點贊總佈局
        LayoutParams textLP=new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        textLP.gravity=Gravity.CENTER_HORIZONTAL;
        LayoutParams backLP = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        backLP.gravity = Gravity.CENTER;
        likeAll =new LinearLayout(getContext());
        likeAll.setOrientation(VERTICAL);
        likeAll.addView(likeNumText,textLP);
        likeAll.addView(likeText,textLP);
        likeAll.addView(likeBack,backLP);


        //差評總佈局
        disLikeAll=new LinearLayout(getContext());
        disLikeAll.setOrientation(VERTICAL);
        disLikeAll.addView(disLikeNumText,textLP);
        disLikeAll.addView(disLikeText,textLP);
        disLikeAll.addView(disLikeBack,backLP);



        //中間豎線
        ImageView centerLineImageView=new ImageView(getContext());
        centerLineImageView.setBackground(new ColorDrawable(Color.GRAY));
        LayoutParams centerLineLP=new LayoutParams(3,160);
        centerLineLP.setMargins(40, 10, 40, 50);
        centerLineLP.gravity= Gravity.BOTTOM;

        //初始化總佈局
        LayoutParams lp=new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        lp.bottomMargin=50;
        lp.gravity=Gravity.BOTTOM;
        addView(disLikeAll,lp);
        addView(centerLineImageView,centerLineLP);
        addView(likeAll,lp)

2.背景拉伸動畫

我們點選點贊線性佈局時,佈局背景變成黃色,同時背景開始拉伸,之前用seekbar模擬了背景拉伸動畫,我們這邊直接應用過來。
因為這邊是同時兩個一起拉伸,之前準備建立兩個ValueAnimator物件,分別控制動畫,但是別人用一個物件就能控制,具體實現看程式碼,應該很好理解。
程式碼如下:

/**
    *背景拉伸
     */
    private void scaleAnim() {
        float disLikePercent=disLikeNum/(disLikeNum+likeNum);
        float likePercent=(likeNum/(likeNum+disLikeNum));
        //計算點贊百分比*300
        final int likeMax=Math.round(likePercent*300);
        //計算差評百分比*300
        final int disLikeMax=Math.round(disLikePercent*300);

        int max=Math.max(likeMax,disLikeMax);
        /**
         * 本來想建立兩個ValueAnimator物件,不過這種方法建立一個物件一樣可以達到目的
         */
        animationBack=ValueAnimator.ofInt(0,max);
        animationBack.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int margin= (int) animation.getAnimatedValue();
                LayoutParams layoutParams= (LayoutParams) likeImageView.getLayoutParams();
                layoutParams.bottomMargin=margin;
                if (margin<=likeMax)
                {
                    likeImageView.setLayoutParams(layoutParams);
                }
                if (margin<=disLikeMax)
                {
                    disLikeImageView.setLayoutParams(layoutParams);
                }
                invalidate();
            }
        });
        likeBack.setClickable(false);
        disLikeBack.setClickable(false);
        isClose=false;
        animationBack.setDuration(1000);
        animationBack.start();
        animationBack.addListener(this);
    }

3.笑臉動畫
笑臉動畫是一個幀動畫和屬性動畫一起組成的。
幀動畫不解釋,比較簡單。屬性動畫是通過設定控制元件的translationX或者translationY屬性從而讓他左右或者上下移動。
程式碼如下:

/**
     * 點贊補充屬性動畫
     * @param likeImageView
     */
    private void translationY(ImageView likeImageView) {
        ObjectAnimator objectAnimator=ObjectAnimator.ofFloat(likeImageView,"translationY",-5.0f, 0.0f, 5.0f, 0.0f, -5.0f, 0.0f, 5.0f, 0);
        objectAnimator.setRepeatMode(ValueAnimator.RESTART);
        objectAnimator.setDuration(1500);
        objectAnimator.start();
        objectAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                super.onAnimationEnd(animation);
                setBackUpAnimation();
            }
        });
    }

4.背景回收動畫
與拉伸類似,用的屬性動畫:
程式碼如下:

  /**
     * 背景收回動畫
     */
    private void setBackUpAnimation() {
        float disLikePercent=disLikeNum/(disLikeNum+likeNum);
        float likePercent=(likeNum/(likeNum+disLikeNum));
        //計算點贊百分比*300
        final int likeMax=Math.round(likePercent*300);
        //計算差評百分比*300
        final int disLikeMax=Math.round(disLikePercent*300);

        int max=Math.max(likeMax,disLikeMax);

        animationBack=ValueAnimator.ofInt(max,0);
        animationBack.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int margin = (int) animation.getAnimatedValue();
                LayoutParams lp= (LayoutParams) disLikeImageView.getLayoutParams();
                lp.bottomMargin=margin;
                if (margin<likeMax)
                {
                    likeImageView.setLayoutParams(lp);
                }
                if (margin<disLikeMax)
                {

                    disLikeImageView.setLayoutParams(lp);
                }
                Log.i(">>>>>>>","margin"+margin);

                invalidate();
            }
        });
        animationBack.addListener(this);
        animationBack.setDuration(500);
        animationBack.start();
    }

這樣就完成了自定義控制元件的封裝了,最後在佈局檔案引用這個控制元件就行。

傳送門

最後附上demo地址。喜歡的可以Star OR Fork。