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布局:



    
        
            
        
    

    
    
        
            
            
            
        

        
            
            
            
        

        
    


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 {

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

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

源码地址:点击打开链接

你可能感兴趣的:(Android干货分享)