CoordinatorLayout自定义behavior(仿20CM动画效果)

    xiaoxiao2024-11-20  2

    首先上一张效果图:

    动画分为三部分: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(); } 相关文章: 点击打开链接    点击打开链接

    源码地址:点击打开链接

    转载请注明原文地址: https://ju.6miu.com/read-1293834.html
    最新回复(0)