1. 程式人生 > >CoordinatorLayout自定義behavior(仿20CM動畫效果)

CoordinatorLayout自定義behavior(仿20CM動畫效果)

首先上一張效果圖:

動畫分為三部分:1.視差效果:進入頁面時,頭圖和列表同時向下移動,且列表有一個從0到1的alpha漸變。

     2.向上推動列表到距離頂部的距離為0dp ~ 96dp(即頭部有返回關注按鈕的導航欄高度的2倍,此處可根據需要自己定義),導航欄會有一個顏色的漸變,且導航欄圖示會有黑色和白色的變化,且在此過程中圖片不動,列表上移。

     3.列表滾動中,如果列表向下滾動,則隱藏導航欄,如果列表向上滾動,則顯示導航欄。


實現思路:

1.採用CoordinatorLayout結合CollapsingToolbarLayout來實現列表上移時圖片保持不動或者以不同速度移動的效果。

2.重寫CoordinatorLayout.Behavior來監聽列表距離頂部的距離,從而實現導航欄的顏色漸變及位移效果。

3.監聽RecyclerView的滾動,來控制導航欄的顯示和隱藏。

4.進入介面時,預設設定圖片的margin值為負的自身高度,列表距離頂部距離為0,然後通過屬性動畫,來控制圖片和列表的向下移動及透明度效果(基本這裡就看自己想象力,各種動畫效果都可以組合來實現想要顯示的動畫效果)。

原始碼分析:

1. XML佈局:

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    android:id="@+id/mCoordinatorLayout"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
    <android.support.design.widget.AppBarLayout
        android:id="@+id/mAppbarLayout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:elevation="0dp"
        >
        <android.support.design.widget.CollapsingToolbarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            >
            <ImageView
                android:id="@+id/iv_head_img"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:src="@mipmap/ic_launcher"
                android:background="@android:color/holo_blue_bright"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="1"
                />
        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        android:id="@+id/mRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#fff"
        />
    <FrameLayout
        android:id="@+id/fr_title_head"
        app:layout_behavior="com.test.git.coordinatorlayout.Behavior.ShortContentsTitleVGBehavior"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <LinearLayout
            android:id="@+id/ll_header_float"
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:orientation="horizontal"
            android:gravity="center_vertical"
            android:alpha="1"
            >
            <ImageView
                android:id="@+id/iv_detail_back"
                android:paddingLeft="20dp"
                android:paddingRight="20dp"
                android:paddingTop="10dp"
                android:paddingBottom="10dp"
                android:layout_width="64dp"
                android:layout_height="44dp"
                android:scaleType="center"
                android:src="@drawable/ic_back_w"
                />
            <View
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                />
            <ImageView
                android:id="@+id/iv_detail_sub"
                android:paddingLeft="16dp"
                android:paddingRight="20dp"
                android:paddingTop="10dp"
                android:paddingBottom="10dp"
                android:layout_width="56dp"
                android:layout_height="44dp"
                android:scaleType="center"
                android:src="@drawable/ic_goods_collect_w"
                />
        </LinearLayout>

        <LinearLayout
            android:id="@+id/ll_header_float_b"
            android:layout_width="match_parent"
            android:layout_height="48dp"
            android:orientation="horizontal"
            android:gravity="center_vertical"
            android:background="#fff"
            android:alpha="0"
            >
            <ImageView
                android:id="@+id/iv_detail_back_b"
                android:paddingLeft="20dp"
                android:paddingRight="20dp"
                android:paddingTop="10dp"
                android:paddingBottom="10dp"
                android:layout_width="64dp"
                android:layout_height="44dp"
                android:scaleType="center"
                android:src="@drawable/ic_back"
                />
            <View
                android:layout_width="0dp"
                android:layout_height="match_parent"
                android:layout_weight="1"
                />
            <ImageView
                android:id="@+id/iv_detail_sub_b"
                android:paddingLeft="16dp"
                android:paddingRight="20dp"
                android:paddingTop="10dp"
                android:paddingBottom="10dp"
                android:layout_width="56dp"
                android:layout_height="44dp"
                android:scaleType="center"
                android:src="@drawable/ic_goods_collect"
                />
        </LinearLayout>

        <View android:layout_width="match_parent"
            android:layout_height="0.5dp"
            android:background="#dddddd"
            android:layout_gravity="bottom"
            />
    </FrameLayout>
</android.support.design.widget.CoordinatorLayout>

2
. 重寫CoordinatorLayout.Behavior
package com.test.git.coordinatorlayout.Behavior;

import android.content.Context;
import android.support.design.widget.CoordinatorLayout;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.FrameLayout;

import com.test.git.coordinatorlayout.Utils.Local;

/**
 * Created by lk on 16/8/2.
 */
public class ShortContentsTitleVGBehavior extends CoordinatorLayout.Behavior<FrameLayout> {

    private static final String TAG = "ShortContentsVGBehavior";
    private ViewGroup.MarginLayoutParams params;
    private int top = Local.dip2px(48);//導航欄高度
    private View child_w;
    private View child_b;
    private View child_line;
    private int count;

    public ShortContentsTitleVGBehavior() {
    }

    public ShortContentsTitleVGBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, FrameLayout child, View dependency) {
        //獲取View的params
        params = (ViewGroup.MarginLayoutParams) child.getLayoutParams();
        //獲取子View
        child_w = child.getChildAt(0);
        child_b = child.getChildAt(1);
        child_line = child.getChildAt(2);
        
        //依賴的View 這裡選擇RecyclerView
        return dependency instanceof RecyclerView;
    }

    /**
     * 依賴的View發生了變化
     * @param parent
     * @param child
     * @param dependency
     * @return
     */
    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, FrameLayout child, View dependency) {
        //依賴的View距離頂部的距離
        float y = dependency.getY();
        //圖片的高度為螢幕寬度,所以此處做一次判斷. 
        if(y == Local.getWidthPx()){
            //count的作用:由於開場動畫會設定列表距離頂部距離為0,此時應不做處理,所以用count來判斷是否開始執行導航欄動畫.
            if(count <= 1){
                count ++;
            }
        }

        Log.i(TAG, "y:" + y + "  count:" + count);
        if(count >= 1) {
            //出場動畫已經結束,可以開始監聽頂部距離來實現導航欄動畫
            changePostion(child, y);
        }

        return super.onDependentViewChanged(parent, child, dependency);
    }

    /**
     * 導航欄動畫
     * @param child
     * @param y
     */
    public void changePostion(FrameLayout child, float y) {
        if(y >= 0 && y <= top){//列表與導航欄接觸,導航欄隨著列表的移動而移動
            //計算導航欄上移距離
            float dy = y - top;
            params.topMargin = (int) dy;
            child.setLayoutParams(params);
            //顯示黑色icon的標題欄
            child_b.setAlpha(1);
            //隱藏白色icon的標題欄
            child_w.setAlpha(0);
            //顯示分割線
            child_line.setVisibility(View.VISIBLE);
        }else {//列表未接觸到導航欄(導航欄高度為top)
            if(y <= top * 2) {//列表距離頂部距離時導航欄高度兩倍時,開啟動畫(這裡可以自己決定)
                //top ~ top * 2過程中計算alpha值
                float f = 2 - y / top;
                child_b.setAlpha(f);
                child_w.setAlpha(1 - f);
            }else {
                child_b.setAlpha(0);
                child_w.setAlpha(1);
            }
            //還原導航欄的位置
            params.topMargin = 0;
            child.setLayoutParams(params);
            child_line.setVisibility(View.GONE);
        }

        //此處用來給RecyclerView滾動監聽用,只有列表在頂部,列表滾動時才執行導航欄的顯示和隱藏.列表不在頂部時,
        // 通過上面程式碼來控制導航欄動畫效果
        if(y <= 0) {
            child.setTag(true);//在頂部
        }else {
            child.setTag(false);//不在頂部
        }
    }
}

3.RecyclerView滾動監聽
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                //後去tag,如果為null,則返回
                if(fr_title_head.getTag() == null)return;
                //獲取列表距離頂部是否為0,true表示在頂部,列表可以自己控制動畫,否則由behavior來控制.
                boolean isIntop = (boolean) fr_title_head.getTag();
                //獲取列表第一個可見的item位置
                int firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();

                //列表滾動到距離頂部小於導航欄高度,隱藏導航欄(用來實現下拉導航欄一起下移,所以做的調整,這裡是邊緣case).
                if(firstVisibleItem == 0 && mLinearLayoutManager.findViewByPosition(firstVisibleItem).getTop() <= title_height){
                    Log.i("ShortContentsVGBehavior", "hide");
                    frParams.topMargin = - title_height;
                    fr_title_head.setLayoutParams(frParams);
                    return;
                }
                //isLoadingAnimate是一個標誌位,用來控制是否執行動畫,避免列表滾動中頻繁執行動畫,導致效果不好.
                if(!isLoadingAnimate && isIntop){//控制
                    if(dy > 0){//列表向下滾動,隱藏導航欄
                        AnimateOut();
                    }else{//列表向上滾動,顯示導航欄
                        AnimateIn();
                    }
                }

            }
        });

顯示動畫:
    //顯示動畫
    private void AnimateIn() {
        if(titleAnimateIn == null) {
            titleAnimateIn = new ValueAnimator().ofInt( - title_height, 0).setDuration(200);
            titleAnimateIn.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    frParams.topMargin = (int) valueAnimator.getAnimatedValue();
                    fr_title_head.setLayoutParams(frParams);
                }
            });
            titleAnimateIn.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animator) {
                    isLoadingAnimate = true;
                }

                @Override
                public void onAnimationEnd(Animator animator) {
                    isLoadingAnimate = false;
                }

                @Override
                public void onAnimationCancel(Animator animator) {

                }

                @Override
                public void onAnimationRepeat(Animator animator) {

                }
            });
        }
        if(frParams.topMargin == - title_height) {
            titleAnimateIn.start();
        }
    }

隱藏動畫:

   //隱藏動畫
    private void AnimateOut() {
        if(titleAnimateOut == null) {
            titleAnimateOut = new ValueAnimator().ofInt(0, - title_height).setDuration(200);
            titleAnimateOut.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator valueAnimator) {
                    frParams.topMargin = (int) valueAnimator.getAnimatedValue();
                    fr_title_head.setLayoutParams(frParams);
                }
            });
            titleAnimateOut.addListener(new Animator.AnimatorListener() {
                @Override
                public void onAnimationStart(Animator animator) {
                    isLoadingAnimate = true;
                }

                @Override
                public void onAnimationEnd(Animator animator) {
                    isLoadingAnimate = false;
                }

                @Override
                public void onAnimationCancel(Animator animator) {

                }

                @Override
                public void onAnimationRepeat(Animator animator) {

                }
            });
        }
        if(frParams.topMargin == 0) {
            titleAnimateOut.start();
        }
    }

4. 開場動畫:
    //開場動畫
    private void loadStartAnimation() {
        final ViewGroup.MarginLayoutParams rvParams = (ViewGroup.MarginLayoutParams) mRecyclerView.getLayoutParams();
        final ViewGroup.MarginLayoutParams rvParams1 = (ViewGroup.MarginLayoutParams) mAppbarLayout.getLayoutParams();
        rvParams.topMargin = - Local.getWidthPx();
        mRecyclerView.setLayoutParams(rvParams);

        rvParams1.topMargin = - Local.getWidthPx();
        mAppbarLayout.setLayoutParams(rvParams1);

        final ValueAnimator valueAnima = ValueAnimator.ofInt(-Local.getWidthPx(), 0);
        valueAnima.setDuration(600).addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator valueAnimator) {
                rvParams.topMargin = (int) valueAnimator.getAnimatedValue();
                mRecyclerView.setLayoutParams(rvParams);
                mRecyclerView.setAlpha(valueAnimator.getAnimatedFraction());

                rvParams1.topMargin = (int) valueAnimator.getAnimatedValue();
                mAppbarLayout.setLayoutParams(rvParams1);
            }
        });
        valueAnima.start();
    }

相關文章:點選開啟連結   點選開啟連結