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();
    }

相关文章: 点击打开链接    点击打开链接

源码地址:点击打开链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值