Android之嵌套聯動<四>:自定義Behavior

在前兩篇文章中,都使用了默認的Behavior,那麼默認的Behavior是什麼呢?

首先看一下佈局代碼:

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".MainActivity"
    android:background="#cccccc">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="visible"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:contentScrim="?attr/colorPrimaryDark"
            app:title="CollapsingToolbarLayout演示"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:collapsedTitleGravity="center"
            app:expandedTitleGravity="bottom"
            app:scrimAnimationDuration="500"
            app:toolbarId="@+id/toolbar">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@mipmap/che4"
                android:visibility="visible" />

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="@android:color/transparent"
                app:title="我是Toolbar"
                app:navigationIcon="@mipmap/back"
                app:layout_collapseMode="parallax"
                app:layout_collapseParallaxMultiplier="0.7"  />

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="visible"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

</android.support.design.widget.CoordinatorLayout>

在RecyclerView標籤中有一個配置

app:layout_behavior="@string/appbar_scrolling_view_behavior" 

該string的值爲:

<string name="appbar_scrolling_view_behavior" translatable="false">android.support.design.widget.AppBarLayout$ScrollingViewBehavior</string>

可以發現,默認的Behavior其實就是AppBarLayout控件的內部類ScrollingViewBehaviorScrollingViewBehavior也有父類,它的頂級父類是CoordinatorLayout.Behavior,本章將詳細說明抽象類CoordinatorLayout.Behavior,其源碼如下:

public abstract static class Behavior<V extends View> {
    public Behavior() {
    }

    public Behavior(Context context, AttributeSet attrs) {
    }

    public void onAttachedToLayoutParams(@NonNull CoordinatorLayout.LayoutParams params) {
    }

    public void onDetachedFromLayoutParams() {
    }

    public boolean onInterceptTouchEvent(@NonNull CoordinatorLayout parent, @NonNull V child, @NonNull MotionEvent ev) {
        return false;
    }

    public boolean onTouchEvent(@NonNull CoordinatorLayout parent, @NonNull V child, @NonNull MotionEvent ev) {
        return false;
    }

    @ColorInt
    public int getScrimColor(@NonNull CoordinatorLayout parent, @NonNull V child) {
        return -16777216;
    }

    @FloatRange(
        from = 0.0D,
        to = 1.0D
    )
    public float getScrimOpacity(@NonNull CoordinatorLayout parent, @NonNull V child) {
        return 0.0F;
    }

    public boolean blocksInteractionBelow(@NonNull CoordinatorLayout parent, @NonNull V child) {
        return this.getScrimOpacity(parent, child) > 0.0F;
    }

    public boolean layoutDependsOn(@NonNull CoordinatorLayout parent, @NonNull V child, @NonNull View dependency) {
        return false;
    }

    public boolean onDependentViewChanged(@NonNull CoordinatorLayout parent, @NonNull V child, @NonNull View dependency) {
        return false;
    }

    public void onDependentViewRemoved(@NonNull CoordinatorLayout parent, @NonNull V child, @NonNull View dependency) {
    }

    public boolean onMeasureChild(@NonNull CoordinatorLayout parent, @NonNull V child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {
        return false;
    }

    public boolean onLayoutChild(@NonNull CoordinatorLayout parent, @NonNull V child, int layoutDirection) {
        return false;
    }

    public static void setTag(@NonNull View child, @Nullable Object tag) {
        CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)child.getLayoutParams();
        lp.mBehaviorTag = tag;
    }

    @Nullable
    public static Object getTag(@NonNull View child) {
        CoordinatorLayout.LayoutParams lp = (CoordinatorLayout.LayoutParams)child.getLayoutParams();
        return lp.mBehaviorTag;
    }

    /** @deprecated */
    @Deprecated
    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, int axes) {
        return false;
    }

    public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        return type == 0 ? this.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes) : false;
    }

    /** @deprecated */
    @Deprecated
    public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, int axes) {
    }

    public void onNestedScrollAccepted(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) {
        if (type == 0) {
            this.onNestedScrollAccepted(coordinatorLayout, child, directTargetChild, target, axes);
        }

    }

    /** @deprecated */
    @Deprecated
    public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target) {
    }

    public void onStopNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int type) {
        if (type == 0) {
            this.onStopNestedScroll(coordinatorLayout, child, target);
        }

    }

    /** @deprecated */
    @Deprecated
    public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {
    }

    public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) {
        if (type == 0) {
            this.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);
        }

    }

    /** @deprecated */
    @Deprecated
    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed) {
    }

    public void onNestedPreScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, int dx, int dy, @NonNull int[] consumed, int type) {
        if (type == 0) {
            this.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);
        }

    }

    public boolean onNestedFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, float velocityX, float velocityY, boolean consumed) {
        return false;
    }

    public boolean onNestedPreFling(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull View target, float velocityX, float velocityY) {
        return false;
    }

    @NonNull
    public WindowInsetsCompat onApplyWindowInsets(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull WindowInsetsCompat insets) {
        return insets;
    }

    public boolean onRequestChildRectangleOnScreen(@NonNull CoordinatorLayout coordinatorLayout, @NonNull V child, @NonNull Rect rectangle, boolean immediate) {
        return false;
    }

    public void onRestoreInstanceState(@NonNull CoordinatorLayout parent, @NonNull V child, @NonNull Parcelable state) {
    }

    @Nullable
    public Parcelable onSaveInstanceState(@NonNull CoordinatorLayout parent, @NonNull V child) {
        return BaseSavedState.EMPTY_STATE;
    }

    public boolean getInsetDodgeRect(@NonNull CoordinatorLayout parent, @NonNull V child, @NonNull Rect rect) {
        return false;
    }
}

由於自定義Behavior,總是直接或間接集成CoordinatorLayout的內部類Behavior,所以CoordinatorLayout+Behavior就可以實現嵌套聯動

(1)Behavior的基本構造方法

當自定義Behavior時,必須寫全兩個基本構造方法,否則Behavior的初始化可能會失敗,代碼如下:

public class MyCustomBehavior extends CoordinatorLayout.Behavior {

    public MyCustomBehavior() {
    }

    public MyCustomBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
}
(2)整理下佈局
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".MainActivity"
    android:background="#cccccc">

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_scrollFlags="scroll|exitUntilCollapsed"
        android:background="@mipmap/che4"
        android:visibility="visible"
        app:layout_behavior="@string/mycustombehavior"/>

    <Button
        android:id="@+id/textview"
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:text="請讓我上下移動"
        android:layout_marginTop="400dp"
        android:textSize="20sp"
        android:background="#00ffff"
        android:gravity="center" />

</android.support.design.widget.CoordinatorLayout>

其中,mycustombehavior已在string資源文件中配置好值了

<resources>

    <string name="mycustombehavior">com.xxx.ooo.recycleview.MyCustomBehavior</string>

</resources>

當然,也可以直接應用對應的類

    <ImageView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:layout_scrollFlags="scroll|exitUntilCollapsed"
        android:background="@mipmap/che4"
        android:visibility="visible"
        app:layout_behavior=".MyCustomBehavior"/>
(3)layoutDependsOn和onDependentViewChanged實現簡單聯動
  • layoutDependsOn:確定使用Behavior的View要依賴的View的類型
  • onDependentViewChanged:當被依賴的View狀態改變時回調

實現簡單聯動需要滿足幾個前提:

[第一] Behavior必須在CoordinatorLayout下才能生效
[第二] 必須確定Behavior的View要依賴的View的類型(layoutDependsOn)
[第三] 被依賴的View狀態必須不停的改變才能使聯動生效。

本例中,Button是依賴View,ImageView配置layout_behavior與Button形成依賴關係,通過觸摸移動來保證Button的狀態不停的改變

    findViewById(R.id.textview).setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()){

                case MotionEvent.ACTION_MOVE:
                    //v.setX(event.getRawX()-v.getWidth()/2);
                    v.setY(event.getRawY()-v.getHeight()/2);
                    break;
            }
             return false;
        }
    });

MyCustomBehavior代碼如下:

public class MyCustomBehavior extends CoordinatorLayout.Behavior {

    private float buttonY;

    public MyCustomBehavior() {
    }

    public MyCustomBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency instanceof Button;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        if (buttonY == 0) {
            //按鈕的初始高度
            buttonY = dependency.getY();
        }
        child.setTranslationY(dependency.getY() - buttonY);
        return true;
    }
}

效果如下:

以上效果不是很友好,接下來的例子中,將結合AppBarLayout+CollapsingToolbarLayout再演示一遍效果

代碼如下:

<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    tools:context=".MainActivity"
    android:background="#cccccc">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/app_bar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:visibility="visible"
        android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/collapsing_toolbar_layout"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:contentScrim="#00ffffff"
            app:title="CollapsingToolbarLayout演示"
            app:layout_scrollFlags="scroll|exitUntilCollapsed"
            app:collapsedTitleGravity="center"
            app:expandedTitleGravity="bottom">

            <ImageView
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="centerCrop"
                android:src="@mipmap/che4"
                android:visibility="visible" />

            <!--<android.support.v7.widget.Toolbar-->
                <!--android:id="@+id/toolbar"-->
                <!--android:layout_width="match_parent"-->
                <!--android:layout_height="?attr/actionBarSize"-->
                <!--android:background="@android:color/transparent"-->
                <!--app:title="我是Toolbar"-->
                <!--app:navigationIcon="@mipmap/back"-->
                <!--app:layout_collapseMode="parallax"-->
                <!--app:layout_collapseParallaxMultiplier="0.7"  />-->

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="visible"
        app:layout_behavior="@string/appbar_scrolling_view_behavior" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="#E66C6C"
        android:text="我是一個文本"
        android:gravity="center"
        android:textColor="#ffffff"
        android:textSize="20sp"
        app:layout_behavior=".MyCustomBehavior"/>

</android.support.design.widget.CoordinatorLayout>


public class MyCustomBehavior extends CoordinatorLayout.Behavior {

    private float deltaY;

    public MyCustomBehavior() {
    }

    public MyCustomBehavior(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency instanceof RecyclerView;
    }

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        if (deltaY == 0) {
            deltaY = dependency.getY() - child.getHeight();
        }
        float dy = dependency.getY() - child.getHeight();
        dy = dy < 0 ? 0 : dy;
        float alpha = 1 - (dy / deltaY);
        child.setAlpha(alpha);
        return true;
    }
}

[本章完...]

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