完全搞懂CoordinatorLayout Behavior之实战一

完全搞懂CoordinatorLayout Behavior 你能做些什么
完全搞懂CoordinatorLayout Behavior 系列之API讲解
完全搞懂CoordinatorLayout Behavior之源码学习
完全搞懂CoordinatorLayout Behavior之实战一

之前我们已经讲解了CoordinatorLayout Behavior 之间的关系以及与NestedScrollView 是如何联系 进行通知回调等操作的,还结合源码讲解了 Behavior相关的几个方法参数的。
废话不多说,说说我们今天实战的效果,下图是我之前完成的一个半成品,今天我将继续完善。
在这里插入图片描述
最终的效果图
补上最后的效果图

一、界面分析

在这里插入图片描述
上面一共有几个观察者?分别是标题栏、 天气图标以及背景图。 说明我们这三个布局都需要添加一个Behavior观察NestedScrollView ,如下xml文件所示。

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    <ImageView
        android:id="@+id/img_header"
        android:layout_width="match_parent"
        android:layout_height="@dimen/img_header_height"
        app:layout_behavior=".ImageHeaderBehavior"
        android:background="@mipmap/home_top_bg"/>
    <RelativeLayout
        android:layout_width="match_parent"
        android:background="@color/blue"
        app:layout_behavior=".TitleBarBehavior"
        android:layout_height="@dimen/comm_title_bar_height">
        <EditText
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:layout_marginLeft="80dp"
            android:layout_centerVertical="true"
            android:layout_marginRight="60dp"
            android:paddingLeft="20dp"
            android:paddingEnd="20dp"
            android:hint="请输入关键字"
            android:background="@drawable/shape_edit_bg"/>
        <ImageView
            android:layout_width="40dp"
            android:layout_height="40dp"
            android:layout_marginRight="10dp"
            android:layout_centerVertical="true"
            android:layout_alignParentRight="true"
            android:src="@mipmap/scan"/>
    </RelativeLayout>
    <RelativeLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_behavior=".WeatherBehavior">
        <ImageView
            android:id="@+id/img_weather"
            android:layout_width="35dp"
            android:layout_height="35dp"
            android:layout_centerVertical="true"
            android:src="@mipmap/weather_sunny" />
        <TextView
            android:id="@+id/txt_weather"
            android:layout_width="40dp"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@+id/img_weather"
            android:gravity="center_horizontal"
            android:paddingTop="8dp"
            android:textStyle="bold"
            android:text="晴天"
            android:textColor="@color/white"
            android:textSize="16dp" />
        <TextView
            android:layout_width="40dp"
            android:layout_height="wrap_content"
            android:layout_below="@+id/txt_weather"
            android:layout_toRightOf="@+id/img_weather"
            android:gravity="center_horizontal"
            android:text="13℃"
            android:textStyle="bold"
            android:textColor="@color/white"
            android:textSize="14dp" />
    </RelativeLayout>

    <android.support.v4.widget.NestedScrollView
        android:id="@+id/scroll_content"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:background="@color/orange"/>
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:background="@color/aqua"/>
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:background="@color/yellow"/>
            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="300dp"
                android:background="@color/blue"/>
        </LinearLayout>
    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

二、监听对象以及初始位置设置

在构造方法中 先得到titlebar 和 HeaderImageView的高度。

public ImageHeaderBehavior(Context context, AttributeSet attrs) {
    super(context, attrs);
    mTitleBarHeight = context.getResources().getDimension(R.dimen.comm_title_bar_height);
    mImgHeaderHeight = context.getResources().getDimension(R.dimen.img_header_height);

 }

以ImageHeaderBehavior 为例,我们首先需要让ImageView可以能够观察到NestedScrollView的变化。

@Override
public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
    if (dependency instanceof NestedScrollView) {
        // 记录监听的NestedScrollView实例,方便初始化位置
        mDependency = dependency;
        return true;
    }
    return false;
}

@Override
public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull View child, int layoutDirection) {
    Log.d(TAG, "onLayoutChild: child = " + child.getHeight());
    //mDependency 
    mDependency.layout(0, (int) mImgHeaderHeight, parent.getWidth(), (int) (parent.getHeight() + mImgHeaderHeight));
    return super.onLayoutChild(parent, child, layoutDirection);
}

mDependency就是我们观察到的NestedScrollView,拿到实例对象引用给它一个初始化位置, 让他正好在ImgHeader 下面。所以top设置成mImgHeaderHeight ,同时bottom 也加一个mImgHeaderHeight。

三、监听NestedScrollView滚动

我们需要达到的目的是NestedScrollView 滚动多少,HeaderImageView也跟着滚动多少。 同时也需要距离边界值处理。

1、给CoordinatorLayout监听分配滚动的权限
    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

完全搞懂CoordinatorLayout Behavior 系列之API讲解 有讲到,只有当这个返回true时,后面的监听嵌套滚动的方法才会得到调用。 这里我们当滑动方向是SCROLL_AXIS_VERTICAL的时候就返回true。

2、设置滚动监听
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                           @NonNull View child,
                           @NonNull View target,
                           int dxConsumed, int dyConsumed,
                           int dxUnconsumed, int dyUnconsumed, int type) {

先解释下参数,coordinatorLayout自然不用说就是根部局元素;
child 就是app:layout_behavior 这个属性设置的元素;
target 就是目标元素,也就是我们的被监听者NestedScrollView;
dxConsumed X轴滑动的距离,竖向滑动时它一直为0;
dyConsumed Y轴滑动的距离,如果大于零代表向上滑动,小于零就是向下滑动。
dxUnconsumeddyUnconsumed 代表未消耗的距离,就是目标滑动距离减实际消耗距离。

@Override
public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                           @NonNull View child,
                           @NonNull View target,
                           int dxConsumed, int dyConsumed,
                           int dxUnconsumed, int dyUnconsumed, int type) {
    //super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);
	// ImageView 高度减去 titlebar高度,得到的差值就是 NestedScrollView可以滑动的最大距离
    float diff = child.getHeight() - mTitleBarHeight;

    //向上滑动
    if (dyConsumed > 0) {
    	// 注释 1
    	//获取 NestedScrollView 滑动距离
        float translationY = -target.getScrollY();
        Log.d(TAG, "onNestedScroll:向上 translationY = " + translationY+" ; diff = "+diff);
        if (target.getScrollY() <= diff) {
        	//NestedScrollView 和ImageView同时向上移动
            target.setTranslationY(translationY);
            child.setTranslationY(translationY);
        }else{
        	//如果target.getScrollY() > diff 就永远固定在diff位置。
        	//如果不加这一行效果会有小瑕疵
            target.setTranslationY(-diff);
            child.setTranslationY(-diff);
        }
    }

    if (dyConsumed < 0) {
    	//注释 2
    	// child.getY() 获取ImageView的Y坐标   target.getY() 获取NestedScrollView的Y坐标
    	// child.getY() 小于零代表ImageView 的top在屏幕外面,如果等于零刚好贴住屏幕的最上边  相对的NestedScrollView 就是紧跟着Image  如果大于child.getHeight() 就与Image 分开了
        if (child.getY() <= 0 && target.getY() <= child.getHeight()) {
            float translationY = -target.getScrollY();
            //本身上面的条件就是符合要求的  会有数值跳动导致不准
            if (target.getScrollY() <= diff) { //最大能滑动的宽度是 header图片的宽度 减去 title高度
                target.setTranslationY(translationY);
                child.setTranslationY(translationY);
            }
        }
    }
}

上面我额外添加了很多注释,便于大家理解。可能你们有更好的算法逻辑。我这里是根据dyConsumed判断方向然后单独分析,下面我用一个图解释下。
在这里插入图片描述

四 、根据监听View变化设置ImageView的变化

根据动画效果图我们可以看到,在NestedScrollView滑动的时候, HeaderImageView 有一个放大缩小 以及透明度大小的变化。 如何设置呢?onDependentViewChanged 当NestedScrollView 大小或者位置发生变化是都会回调这个方法。 原理部分我们有说到,它是通过一个ViewTreeObserver 监听绘制的发方法,通过比较上一次和这一次的Rect 。决定onDependentViewChanged是否调用,看一下我是如何使用的。

@Override
public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
    //return super.onDependentViewChanged(parent, child, dependency);

    //ImageView 滑动的距离
    float translationY = child.getTranslationY();
	//通过平移的距离translationY,它的大小已经被固定死了只能是 0 ~ mIgHeaderHeight - mTitleBarHeight 之间。 这里计算出一个比例。
    float progress = 1f - (Math.abs(translationY) / (mIgHeaderHeight - mTitleBarHeight));
	// 0.2 只是一个放大缩小的系数, 让变化更加缓和一些
    float scale = 1 + 0.2f * (1.f - progress);
    child.setScaleX(scale);
    child.setScaleY(scale);

    if (progress < 0.3) {
        child.setAlpha(0.3f);
    } else {
        child.setAlpha(progress);
    }
    return true;
}

五、另外两个Behavior 源码

如果理解了上面那个Behavior,那么这两个Behavior就非常好理解,一共就两步,第一步、设置初始化位置;第二步根据平移大小计算比例,进行相关位置的计算。
TitleBarBehavior.java

public class TitleBarBehavior extends CoordinatorLayout.Behavior {

    private static final String TAG = "TitleBarBehavior";

    private final float mTitleBarHeight;
    private final float mImgHeaderHeight;

    public TitleBarBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
        mTitleBarHeight = context.getResources().getDimension(R.dimen.comm_title_bar_height);
        mImgHeaderHeight = context.getResources().getDimension(R.dimen.img_header_height);
    }


    @Override
    public boolean layoutDependsOn(@NonNull CoordinatorLayout parent,
                                   @NonNull View child,
                                   @NonNull View dependency) {
        if (dependency instanceof NestedScrollView) {
            return true;
        }
        return super.layoutDependsOn(parent, child, dependency);
    }

    @Override
    public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull View child, int layoutDirection) {
    	//设置初始位置的平移,让它完全平移到屏幕外
        child.setTranslationY(-mTitleBarHeight);
        return  super.onLayoutChild(parent, child, layoutDirection);
    }

    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

    @Override
    public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child,
                               @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        //super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);

        // int scrollY = target.getScrollY();
        // Log.d(TAG, "onNestedScroll: scrollY"+ scrollY);
        float translationY = target.getTranslationY();
        //target可以滑动的范围距离
        float totalDistance = mImgHeaderHeight - mTitleBarHeight;
        float progress = Math.abs(translationY) / totalDistance;
        float titleBarTranslationY = -mTitleBarHeight * (1 - progress);
        Log.d(TAG, "onNestedScroll: titleBarTranslationY = "+titleBarTranslationY+"  ; mTitleBarHeight = "+mTitleBarHeight);
        child.setTranslationY(titleBarTranslationY);
        child.setAlpha(progress);
    }
}

WeatherBehavior.java

public class WeatherBehavior extends CoordinatorLayout.Behavior {

    private static final String TAG = "WeatherBehavior";
    private float mTitleBarHeight;
    private float mWeatherTopMargin;
    private float mWeatherLeftMargin;
    private float mImgHeaderHeight;

    public WeatherBehavior() {
    }

    public WeatherBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);

        //得到Weather布局的宽高
        mTitleBarHeight = context.getResources().getDimension(R.dimen.comm_title_bar_height);
        mImgHeaderHeight = context.getResources().getDimension(R.dimen.img_header_height);
        mWeatherTopMargin = context.getResources().getDimension(R.dimen.weather_top_margin);
        mWeatherLeftMargin = context.getResources().getDimension(R.dimen.weather_left_margin);
    }

    @Override
    public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {

        if (dependency instanceof NestedScrollView){
            return true;
        }
        return false;
    }

    @Override
    public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull View child, int layoutDirection) {

        CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
        layoutParams.topMargin = (int) mWeatherTopMargin;
        layoutParams.leftMargin = (int) mWeatherLeftMargin;

        return super.onLayoutChild(parent, child, layoutDirection);
    }

    @Override
    public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull View child, @NonNull View dependency) {
        return super.onDependentViewChanged(parent, child, dependency);
    }

    @Override
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        return (axes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
    }

    @Override
    public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout,
                               @NonNull View child, @NonNull View target,
                               int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        //super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type);

        //target可以滑动的总距离
        float totalDistance = mImgHeaderHeight - mTitleBarHeight;

        float translationY = target.getTranslationY();
        float progressY = translationY / totalDistance;

        float watherTranslationY = mWeatherTopMargin * progressY;

        float translationX = target.getTranslationY();
        float progressX = translationX / totalDistance;

        float watherTranslationX = mWeatherLeftMargin * progressX;


        Log.d(TAG, "onNestedScroll: watherTranslationY = "+ watherTranslationY+ " translationY = "+ translationY);
        child.setTranslationY(watherTranslationY);
        child.setTranslationX(watherTranslationX);

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值