美文网首页自定义ViewMaterial DesignAndroid知识
CoordinatorLayout与Behavior的实际使用(

CoordinatorLayout与Behavior的实际使用(

作者: fodroid | 来源:发表于2017-03-12 23:43 被阅读432次

    前言

    CoordinatorLayout功能非常强大,而他的神奇之处就在于Behavior对象,CoordinatorLayout自己并不控制View,所有的控制权都在Behavior。前面我们讲了系统自带的Behavior使用,现在我们尝试着自定义Behavior,实现自己想要的效果。

    前段时间在项目中刚好用到了自定义Behavior,接下来就看看如何一步步实现的,首先我们看看要实现的效果。

    效果.gif

    要自己定义CoordinatorLayout Behavior,需要实现layoutDependsOn()onDependentViewChanged()两个方法。比如AppBarLayout.Behavior就定义了这两个关键方法。这个behavior用于当滚动发生的时候让AppBarLayout发生改变。

    public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {
        return dependency instanceof AppBarLayout;
    }
     
    public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {
        // check the behavior triggered
        android.support.design.widget.CoordinatorLayout.Behavior behavior = ((android.support.design.widget.CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();
        if (behavior instanceof AppBarLayout.Behavior) {
            // do stuff here
        }
    }
    

    实现

    首先我们分析一下上面的界面,这就是一个简单的用户信息界面,界面中用到了可折叠的Toolbar,随着界面上下滑动,用户的头像可以跟随着一起移动,并伴随着变大变小。根据前面了解的知识,要实现上面的效果就需要用到的有CoordinatorLayoutAppBarLayoutCollapsingToolbarLayout等。

    布局代码

    <?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"
        tools:context="me.shihao.coordinatorlayoutusage.MainActivity">
    
        <android.support.design.widget.AppBarLayout
            android:id="@+id/app_bar"
            android:layout_width="match_parent"
            android:layout_height="240dp"
            android:theme="@style/AppTheme.AppBarOverlay">
    
            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/toolbar_layout"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:contentScrim="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|exitUntilCollapsed">
    
                <ImageView
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:scaleType="centerCrop"
                    android:src="@drawable/ic_user_center_appbar_iv"
                    app:layout_collapseMode="parallax"/>
    
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    app:layout_collapseMode="pin"
                    app:navigationIcon="?attr/homeAsUpIndicator"
                    app:popupTheme="@style/AppTheme.PopupOverlay">
    
                    <RelativeLayout
                        android:layout_width="match_parent"
                        android:layout_height="?attr/actionBarSize">
    
                        <TextView
                            android:id="@+id/tv_title"
                            style="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_alignParentLeft="true"
                            android:layout_centerVertical="true"
                            android:text="个人信息"/>
                    </RelativeLayout>
                </android.support.v7.widget.Toolbar>
    
            </android.support.design.widget.CollapsingToolbarLayout>
        </android.support.design.widget.AppBarLayout>
    
        <ImageButton
            android:id="@+id/ibtn_ico"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:background="@drawable/usercenter_avator_bg"
            android:padding="2dp"
            android:scaleType="fitCenter"
            android:src="@drawable/avator_default"
            app:layout_behavior="me.shihao.coordinatorlayoutusage.UserInfoImageButtonBehavior"/>
    
        <TextView
            android:id="@+id/tv_title_nick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:maxLines="1"
            android:paddingTop="32dp"
            android:text="NickName"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            app:layout_anchor="@id/ibtn_ico"
            app:layout_anchorGravity="bottom|center_horizontal"/>
    
        <include layout="@layout/content_scrolling"/>
    
    </android.support.design.widget.CoordinatorLayout>
    
    

    自定义的behavior代码

    public class UserInfoImageButtonBehavior extends CoordinatorLayout.Behavior<ImageButton> {
        private String TAG = getClass().getSimpleName();
        private int maxScrollDistance;
        private float maxChildWidth;
        private float minChildWidth;
    
        private int toolbarHeight;
        private int statusBarHeight;
        private int appbarStartPoint;
        private int marginRight;
    
        public UserInfoImageButtonBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
            //计算出头像的最小宽度
            minChildWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 32, context.getResources()
                    .getDisplayMetrics());
            //计算出toolbar的高度
            toolbarHeight = context.getResources().getDimensionPixelSize(android.support.design.R.dimen
                    .abc_action_bar_default_height_material);
            //计算出状态栏的高度
            statusBarHeight = XStatusBarHelper.getStatusBarHeight(context);
            //计算出头像居右的距离
            marginRight = context.getResources().getDimensionPixelSize(R.dimen.activity_horizontal_margin);
        }
    
        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, ImageButton child, View dependency) {
    //        Log.d(TAG, "layoutDependsOn");
            //确定依赖关系,这里我们用作头像的ImageButton相依赖的是AppBarLayout,也就是ImageButton跟着AppBarLayout的变化而变化。
            return dependency instanceof AppBarLayout;
        }
    
        private int startX;
        private int startY;
    
        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, ImageButton child, View dependency) {
            //这里的dependency就是布局中的AppBarLayout,child即显示的头像
            if (maxScrollDistance == 0) {
                //也就是第一次进来时,计算出AppBarLayout的最大垂直变化距离
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
                    maxScrollDistance = dependency.getBottom() - toolbarHeight - statusBarHeight - statusBarHeight;
                else
                    maxScrollDistance = dependency.getBottom() - toolbarHeight;
            }
            //计算出appbar的开始的y坐标
            if (appbarStartPoint == 0)
                appbarStartPoint = dependency.getBottom();
            //计算出头像的宽度
            if (maxChildWidth == 0)
                maxChildWidth = Math.min(child.getWidth(), child.getHeight());
            //计算出头像的起始x坐标
            if (startX == 0)
                startX = (int) (dependency.getWidth() / 2 - maxChildWidth / 2);
            //计算出头像的起始y坐标
            if (startY == 0)
                startY = (int) (dependency.getBottom() - maxScrollDistance / 2 - maxChildWidth / 2 - toolbarHeight / 2);
            //计算出appbar已经变化距离的百分比,起始位置y减去当前位置y,然后除以最大距离
            float expandedPercentageFactor = (appbarStartPoint - dependency.getBottom()) * 1.0f /
                    (maxScrollDistance * 1.0f);
            //根据上面计算出的百分比,计算出头像应该移动的y距离,通过百分比乘以最大距离
            float moveY = expandedPercentageFactor * (maxScrollDistance - (appbarStartPoint - startY - toolbarHeight / 2
                    - minChildWidth / 2));
            //根据上面计算出的百分比,计算出头像应该移动的y距离
            float moveX = expandedPercentageFactor * (startX + maxChildWidth - marginRight - minChildWidth);
            //更新头像的位置
            child.setX(startX + moveX);
            child.setY(startY - moveY);
            //计算出当前头像的宽度
            float nowWidth = maxChildWidth - ((maxChildWidth - minChildWidth) * expandedPercentageFactor);
            //更新头像的宽高
            CoordinatorLayout.LayoutParams params = (CoordinatorLayout.LayoutParams) child.getLayoutParams();
            params.height = params.width = (int) nowWidth;
            child.setLayoutParams(params);
            return true;
        }
    }
    

    在代码中有详细的注释,在这里我们用作头像的是ImageButton,与其相依赖的是AppBarLayout,在layoutDependsOn我们定义了两者的依赖关系。在xml中使用时,我们通过app:layout_behavior来设置。

    app:layout_behavior="me.shihao.coordinatorlayoutusage.UserInfoImageButtonBehavior"
    

    这里注意一下,我们引用时使用的是全路径me.shihao.coordinatorlayoutusage.UserInfoImageButtonBehavior

    Activity代码

    public class MainActivity extends AppCompatActivity {
    
        private TextView tvTitle;
        private TextView tvTitleNick;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
            XStatusBarHelper.immersiveStatusBar(this);
            XStatusBarHelper.setHeightAndPadding(this, toolbar);
    
            tvTitle = (TextView) findViewById(R.id.tv_title);
            tvTitleNick = (TextView) findViewById(R.id.tv_title_nick);
            AppBarLayout appBar = (AppBarLayout) findViewById(R.id.app_bar);
            appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
                @Override
                public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                    float total = appBarLayout.getTotalScrollRange() * 1.0f;
                    //计算出滑动百分比
                    float p = Math.abs(verticalOffset) / total;
                    if (p > 0.5) {
                        tvTitle.setAlpha(1.0f / 0.5f * (p - 0.5f));
                        tvTitleNick.setAlpha(0);
                    } else {
                        tvTitle.setAlpha(0);
                        tvTitleNick.setAlpha(1.0f - 1.0f / 0.5f * p);
                    }
                }
            });
        }
    }
    

    这里我们通过设置AppBarLayoutaddOnOffsetChangedListener,来监听AppBarLayout的变化,然后通过变化来改变TitleNickName的显示隐藏。

    基本代码就是这样,接下里我们看看实际的效果。

    4.1上效果.gif 6.0上效果.gif

    实际测试中却发现了还有另外一个问题,通过对比4.1与6.0上的效果,发现在6.0上当推到最上面时,头像被隐藏了,而我们实际需要的是不隐藏。为了解决这个问题,我在toolbar中增加了一个ImageButton,用做最后显示头像,通过监听变化设置其显示隐藏。

    改变后的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"
        tools:context="me.shihao.coordinatorlayoutusage.MainActivity">
    
        <android.support.design.widget.AppBarLayout
            android:id="@+id/app_bar"
            android:layout_width="match_parent"
            android:layout_height="240dp"
            android:theme="@style/AppTheme.AppBarOverlay">
    
            <android.support.design.widget.CollapsingToolbarLayout
                android:id="@+id/toolbar_layout"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:contentScrim="?attr/colorPrimary"
                app:layout_scrollFlags="scroll|exitUntilCollapsed">
    
                <ImageView
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:scaleType="centerCrop"
                    android:src="@drawable/ic_user_center_appbar_iv"
                    app:layout_collapseMode="parallax"/>
    
                <android.support.v7.widget.Toolbar
                    android:id="@+id/toolbar"
                    android:layout_width="match_parent"
                    android:layout_height="?attr/actionBarSize"
                    app:layout_collapseMode="pin"
                    app:navigationIcon="?attr/homeAsUpIndicator"
                    app:popupTheme="@style/AppTheme.PopupOverlay">
    
                    <RelativeLayout
                        android:layout_width="match_parent"
                        android:layout_height="?attr/actionBarSize">
    
                        <TextView
                            android:id="@+id/tv_title"
                            style="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
                            android:layout_width="wrap_content"
                            android:layout_height="wrap_content"
                            android:layout_alignParentLeft="true"
                            android:layout_centerVertical="true"
                            android:text="个人信息"/>
    
                        <ImageButton
                            android:id="@+id/ibtn_title_ico"
                            android:layout_width="32dp"
                            android:layout_height="32dp"
                            android:layout_alignParentRight="true"
                            android:layout_centerVertical="true"
                            android:layout_marginRight="@dimen/activity_horizontal_margin"
                            android:background="@drawable/usercenter_avator_bg"
                            android:padding="2dp"
                            android:scaleType="fitCenter"
                            android:src="@drawable/avator_default"/>
                    </RelativeLayout>
                </android.support.v7.widget.Toolbar>
    
            </android.support.design.widget.CollapsingToolbarLayout>
        </android.support.design.widget.AppBarLayout>
    
        <ImageButton
            android:id="@+id/ibtn_ico"
            android:layout_width="70dp"
            android:layout_height="70dp"
            android:background="@drawable/usercenter_avator_bg"
            android:padding="2dp"
            android:scaleType="fitCenter"
            android:src="@drawable/avator_default"
            app:layout_behavior="me.shihao.coordinatorlayoutusage.UserInfoImageButtonBehavior"/>
    
        <TextView
            android:id="@+id/tv_title_nick"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:maxLines="1"
            android:paddingTop="32dp"
            android:text="NickName"
            android:textColor="@android:color/white"
            android:textSize="16sp"
            app:layout_anchor="@id/ibtn_ico"
            app:layout_anchorGravity="bottom|center_horizontal"/>
    
        <include layout="@layout/content_scrolling"/>
    
    </android.support.design.widget.CoordinatorLayout>
    
    

    改变后的Activity代码

    public class MainActivity extends AppCompatActivity {
    
        private TextView tvTitle;
        private TextView tvTitleNick;
        private ImageButton ibtnTitleIco;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
            XStatusBarHelper.immersiveStatusBar(this);
            XStatusBarHelper.setHeightAndPadding(this, toolbar);
    
            tvTitle = (TextView) findViewById(R.id.tv_title);
            tvTitleNick = (TextView) findViewById(R.id.tv_title_nick);
            ibtnTitleIco = (ImageButton) findViewById(R.id.ibtn_title_ico);
            AppBarLayout appBar = (AppBarLayout) findViewById(R.id.app_bar);
            appBar.addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() {
                @Override
                public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) {
                    float total = appBarLayout.getTotalScrollRange() * 1.0f;
                    //计算出滑动百分比
                    float p = Math.abs(verticalOffset) / total;
                    if (p > 0.5) {
                        tvTitle.setAlpha(1.0f / 0.5f * (p - 0.5f));
                        tvTitleNick.setAlpha(0);
                    } else {
                        tvTitle.setAlpha(0);
                        tvTitleNick.setAlpha(1.0f - 1.0f / 0.5f * p);
                    }
                    ibtnTitleIco.setVisibility(p == 1 ? View.VISIBLE : View.INVISIBLE);
                }
            });
        }
    }
    

    最终的效果如下:

    6.0上最终效果.gif

    勉强算是解决了吧,个人能力有限,如果你有更好的方法,欢迎再下面留言。


    项目中为了实现沉浸式状态栏使用了XStatusBarHelper,详细使用请前往查看。
    如何实现沉浸式状态栏:http://www.jianshu.com/p/00fed1371bb0

    https://github.com/fodroid/XStatusBarHelper


    Demo已经放在了Github上,传送门:CoordinatorLayoutUsage

    如果你觉得有用,请在Github不吝给我一个Star,非常感谢。


    写在最后的话:个人能力有限,欢迎大家在下面吐槽。喜欢的话就为我点一个赞吧。也欢迎 Fork Me On Github 。

    相关文章

      网友评论

      • YuTao_:小米手机有问题
      • 饿聋:为什么6.0会隐藏,什么原因没有说明
        fodroid:@饿聋 我也没具体研究了:joy::joy:

      本文标题:CoordinatorLayout与Behavior的实际使用(

      本文链接:https://www.haomeiwen.com/subject/gxxxnttx.html