CoordinatorLayout自定義behavior(仿20CM動畫效果)
阿新 • • 發佈:2019-01-24
首先上一張效果圖:
動畫分為三部分: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();
}
相關文章:點選開啟連結 點選開啟連結