首先上一张效果图:
动画分为三部分:1.视差效果:进入页面时,头图和列表同时向下移动,且列表有一个从0到1的alpha渐变。
2.向上推动列表到距离顶部的距离为0dp ~ 96dp(即头部有返回关注按钮的导航栏高度的2倍,此处可根据需要自己定义),导航栏会有一个颜色的渐变,且导航栏图标会有黑色和白色的变化,且在此过程中图片不动,列表上移。
3.列表滚动中,如果列表向下滚动,则隐藏导航栏,如果列表向上滚动,则显示导航栏。
实现思路:
1.采用CoordinatorLayout结合CollapsingToolbarLayout来实现列表上移时图片保持不动或者以不同速度移动的效果。
2.重写CoordinatorLayout.Behavior来监听列表距离顶部的距离,从而实现导航栏的颜色渐变及位移效果。
3.监听RecyclerView的滚动,来控制导航栏的显示和隐藏。
4.进入界面时,默认设置图片的margin值为负的自身高度,列表距离顶部距离为0,然后通过属性动画,来控制图片和列表的向下移动及透明度效果(基本这里就看自己想象力,各种动画效果都可以组合来实现想要显示的动画效果)。
源码分析:
1. XML布局:
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);//不在顶部
}
}
}
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();
}
}
//开场动画
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();
}
源码地址:点击打开链接