完全搞懂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);

    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章