Material Design系列 - 自定义Behavior实

作者: AndroidRookie | 来源:发表于2018-09-04 20:47 被阅读24次

    引言

    CoordinatorLayout+CollapsingToolbarLayout+Behavior真是一个好东西,很多复杂的UI交互效果都可以通过Behavior来实现,用了Behavior之后腰也不疼了,再也不会对设计师说这个实现不了了,只要给我时间我就实现给你看!今天带来第一个自定义Behavior:实现一个伸缩的标题栏。

    效果图如下

    Behavior效果图

    实现思路

    1. 监听CollapsingToolbarLayout滚动的Y轴距离,和CollapsingToolbarLayout的总高度进行百分比计算得出当前滑动的百分比,再不断的计算顶部图标的宽高进行百分比缩减。整个按钮的X轴坐标跟随百分比减少。
    2. 整个View的宽度除以4,得出每个menu所占的宽度,用item的的下标乘以menu的宽度得出每个menu的X轴。
    3. 当滑动的时候改变文字的透明度,大于0.4则隐藏文字。

    开始编码

    引入相关依赖

    dependencies{
            implementation 'com.android.support:design:26.0.2'
    }
    

    创建相关View

    创建xml,CollapsingToolbarLayout定义高度和滚动模式,内部放一个View作为滑动的坐标参考。而menu是一个垂直的LinearLayout,上面一个ImageView,下面一个TextView,下面是相关内容:

    <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=".AlipayBehaviorActivity">
        <android.support.design.widget.AppBarLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:elevation="2dp">
    
                <android.support.design.widget.CollapsingToolbarLayout
                    android:layout_width="match_parent"
                    android:layout_height="135dp"
                    android:background="@color/colorPrimary"
                    app:layout_scrollFlags="scroll|exitUntilCollapsed">
                    <!--滚动模式-->
                    
                    <!--用来做背景坐标参考-->
                    <FrameLayout
                        android:id="@+id/flScroll"
                        android:layout_width="match_parent"
                        android:layout_height="match_parent"
                        app:layout_collapseMode="parallax"
                        app:layout_collapseParallaxMultiplier="0.9" />
    
                    <android.support.v7.widget.Toolbar
                        android:layout_width="match_parent"
                        android:layout_height="48dp"
                        android:background="@color/colorPrimary"
                        app:layout_anchor="@id/flScroll"
                        app:layout_collapseMode="pin"
                        app:title="" />
                        <!--Toolbar的layout_collapseMode设置为pin,代表toolbar一直固定在顶部-->
    
                </android.support.design.widget.CollapsingToolbarLayout>
            </android.support.design.widget.AppBarLayout>
    
            <!--menu布局-->
            <LinearLayout
                style="@style/ServerShortcutMenuLineStyle"
                android:gravity="center">
    
                <ImageView
                    style="@style/ServerShortcutMenuImageStyle"
                    android:src="@mipmap/icon_server_app_door" />
    
                <TextView
                    style="@style/ServerShortcutMenuTextStyle"
                    android:text="门禁" />
            </LinearLayout>
    </android.support.design.widget.CoordinatorLayout>
    

    为了完成后方便复制,我将样式抽取出来了,样式文件如下:

     <style name="ServerShortcutMenuLineStyle">
            <!--快捷菜单行样式-->
            <item name="android:layout_width">wrap_content</item>
            <item name="android:layout_height">70dp</item>
            <item name="android:background">?selectableItemBackground</item>
            <item name="android:clickable">true</item>
            <item name="android:focusable">true</item>
            <item name="android:gravity">center_horizontal|bottom</item>
            <item name="android:orientation">vertical</item>
            <item name="android:elevation">5dp</item>
        </style>
    
        <style name="ServerShortcutMenuTextStyle">
            <item name="android:layout_width">wrap_content</item>
            <item name="android:layout_height">wrap_content</item>
            <item name="android:layout_marginTop">4dp</item>
            <item name="android:textColor">#fff</item>
            <item name="android:textSize">14sp</item>
        </style>
    
        <style name="ServerShortcutMenuImageStyle">
            <item name="android:layout_width">30dp</item>
            <item name="android:layout_height">30dp</item>
        </style>
    
    

    先运行起来看看效果吧:

    View1

    确定第一个menu的位置

    创建AlipayBehavior继承至CoordinatorLayout.Behavior<LinearLayout>,按照思路,先确定第0个item的X和Y轴。核心代码如下:

    @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, LinearLayout child, View dependency) {
            //计算出每个View的宽度
            mViewWidth = dependency.getWidth() / 4/*四个View*/;
            //高度
            mViewHeight = child.getHeight();
            //重新规划 宽度
            ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
            if (layoutParams != null) {
                layoutParams.width = mViewWidth;
            }
            child.setLayoutParams(layoutParams);
    
            //设置X轴坐标
            child.setX(0);
    
            // 设置Y轴坐标
            child.setY(mViewHeight/2);
    
            return true;
        }
    

    在xml中进行引用:

    <LinearLayout
            style="@style/ServerShortcutMenuLineStyle"
            android:gravity="center"
            app:layout_anchor="@id/flScroll"
            app:layout_behavior="android.of.road.com.behavior.AlipayBehavior">
    
            <ImageView
                style="@style/ServerShortcutMenuImageStyle"
                android:src="@mipmap/icon_server_app_door" />
    
            <TextView
                style="@style/ServerShortcutMenuTextStyle"
                android:text="门禁" />
        </LinearLayout>
    

    运行起来看看效果吧:

    View2

    可以看到,第0个View的距离已经确定下来,下一步就需要开始跟随滑动而更改menu的位置了。

    监听滑动,更改menu的X轴位置

    • 滑动的百分比为dependency的Y轴位置除以dependency的高度。
    • 为了能让menu滑动到最小后又能滑动到最大位置,需要用两个变量mViewMaxX和mViewMaxY存储最大值。
    • 跟随监听更改menu的(mViewMaxX和mViewMaxY)乘以百分比的X轴和Y轴坐标。

    代码实现如下:

    @Override
    public boolean onDependentViewChanged(CoordinatorLayout parent, LinearLayout child, View dependency) {
        //计算出每个View的宽度
        mViewWidth = dependency.getWidth() / 4/*四个View*/;
        //高度
        mViewHeight = child.getHeight();
    
        //计算居中X轴,第一个  随便给的默认值
        mViewMaxX = 50;
        //计算Y轴 坐标系参考View的高度二分之一减去menu的高度除以2,刚好居中
        mViewMaxY = dependency.getHeight() / 2 - mViewHeight / 2;
        //重新规划 宽度
        ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
        if (layoutParams != null) {
            layoutParams.width = mViewWidth;
        }
        child.setLayoutParams(layoutParams);
    
        //计算百分比  当前的百分比其实是没有减去状态栏的
        float mPercent = dependency.getY() / (dependency.getHeight());
        if (mPercent >= 1f)
            mPercent = 1;
    
        //更改 内部文字的透明底
        View mTextTitleView = child.getChildAt(1);
        if (mTextTitleView != null) {
            mTextTitleView.setAlpha(1 - (mPercent > 0.4 ? 1 : mPercent));
        }
    
        // 更改内部imageView的大小
        View mImageTitleView = child.getChildAt(0);
        if (mImageTitleView != null) {
            mImageTitleView.setScaleX(1 - (0.4f * mPercent));
            mImageTitleView.setScaleY(1 - (0.4f * mPercent));
        }
    
        //设置X轴坐标
        child.setX(mViewMaxX - mViewMaxX * mPercent);
        // 设置Y轴坐标
        child.setY(mViewMaxY - (mViewMaxY * 1.4f) * mPercent);
    
        return true;
    }
    

    效果:

    初步完成监听

    分配到每个menu上

    现在已经完成了,现在需要的是为每个menu进行配置Behavior,实现思路如下:

    • 这里的实现思路是为每个menu加一个tag,而tag就是menu的下标
    • Behavior中获取menu的下标,根据下标来确定x和y轴的位置

    xml代码如下:

     <android.support.design.widget.AppBarLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:elevation="2dp">
    
            <android.support.design.widget.CollapsingToolbarLayout
                android:layout_width="match_parent"
                android:layout_height="135dp"
                android:background="@color/colorPrimary"
                app:layout_scrollFlags="scroll|exitUntilCollapsed">
    
                <!--用来做背景坐标参考-->
                <FrameLayout
                    android:id="@+id/flScroll"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    app:layout_collapseMode="parallax"
                    app:layout_collapseParallaxMultiplier="0.9" />
    
    
                <android.support.v7.widget.Toolbar
                    android:layout_width="match_parent"
                    android:layout_height="48dp"
                    android:background="@color/colorPrimary"
                    app:layout_anchor="@id/flScroll"
                    app:layout_collapseMode="pin"
                    app:title="" />
    
            </android.support.design.widget.CollapsingToolbarLayout>
        </android.support.design.widget.AppBarLayout>
    
        <include layout="@layout/layout_apay_content" />
    
        <LinearLayout
            style="@style/ServerShortcutMenuLineStyle"
            android:gravity="center"
            android:tag="0"
            app:layout_anchor="@id/flScroll"
            app:layout_behavior="android.of.road.com.behavior.AlipayBehavior">
    
            <ImageView
                style="@style/ServerShortcutMenuImageStyle"
                android:src="@mipmap/icon_server_app_door" />
    
            <TextView
                style="@style/ServerShortcutMenuTextStyle"
                android:text="门禁" />
        </LinearLayout>
    
        <LinearLayout
            style="@style/ServerShortcutMenuLineStyle"
            android:gravity="center"
            android:tag="1"
            app:layout_anchor="@id/flScroll"
            app:layout_behavior="android.of.road.com.behavior.AlipayBehavior">
    
            <ImageView
                style="@style/ServerShortcutMenuImageStyle"
                android:src="@mipmap/icon_server_app_scanf" />
    
            <TextView
                style="@style/ServerShortcutMenuTextStyle"
                android:text="扫一扫" />
        </LinearLayout>
    
        <LinearLayout
            style="@style/ServerShortcutMenuLineStyle"
            android:gravity="center"
            android:tag="2"
            app:layout_anchor="@id/flScroll"
            app:layout_behavior="android.of.road.com.behavior.AlipayBehavior">
    
            <ImageView
                style="@style/ServerShortcutMenuImageStyle"
                android:src="@mipmap/icon_server_app_card" />
    
            <TextView
                style="@style/ServerShortcutMenuTextStyle"
                android:text="停车月卡" />
        </LinearLayout>
    
        <LinearLayout
            style="@style/ServerShortcutMenuLineStyle"
            android:gravity="center"
            android:tag="3"
            app:layout_anchor="@id/flScroll"
            app:layout_behavior="android.of.road.com.behavior.AlipayBehavior">
    
            <ImageView
                style="@style/ServerShortcutMenuImageStyle"
                android:src="@mipmap/icon_server_app_wallet" />
    
            <TextView
                style="@style/ServerShortcutMenuTextStyle"
                android:text="钱包" />
        </LinearLayout>
    </android.support.design.widget.CoordinatorLayout>
    

    完整Java代码如下:

    public class AlipayBehavior extends CoordinatorLayout.Behavior<LinearLayout> {
    
        /**
         * 下标
         */
        private int mPosition = -1;
        /**
         * X轴坐标
         */
        private float mViewMaxX = 0;
    
        /**
         * View的宽度
         */
        private int mViewWidth;
        /**
         * View的高度
         */
        private int mViewHeight;
    
        /**
         * Y轴的最大高度
         */
        private int mViewMaxY = 0;
    
    
        public AlipayBehavior(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        public boolean layoutDependsOn(CoordinatorLayout parent, LinearLayout child, View dependency) {
            return dependency instanceof Toolbar;
        }
    
        @Override
        public boolean onDependentViewChanged(CoordinatorLayout parent, LinearLayout child, View dependency) {
            if (mPosition == -1) {//未初始化
                mPosition = Integer.parseInt((String) child.getTag());
                //计算出每个View的宽度
                mViewWidth = dependency.getWidth() / 4/*四个View*/;
                //高度
                mViewHeight = child.getHeight();
                //重新规划 宽度
                ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
                if (layoutParams != null) {
                    layoutParams.width = mViewWidth;
                }
                child.setLayoutParams(layoutParams);
    
                // 总宽度 除以四。得出每一个子View的宽度,居中为
                //计算居中X轴
                mViewMaxX = mViewWidth * mPosition;
                //计算Y轴
                mViewMaxY = (int) (child.getY() + DensityUtils.dp2px(parent.getContext(), 50f));
            }
    
            //计算百分比  当前的百分比其实是没有减去状态栏的
            float mPercent = dependency.getY() / (dependency.getHeight()/* - ScreenUtils.getStatusHeight(parent.getContext())*/);
            if (mPercent >= 1f)
                mPercent = 1;
    
            // 动态更改 View的高度
            ViewGroup.LayoutParams layoutParams = child.getLayoutParams();
            if (layoutParams != null) {
                layoutParams.height = (int) (mViewHeight - (mViewHeight /** 0.8f*/) * mPercent);
                layoutParams.width = (int) (mViewWidth - (mViewWidth * mPercent));
                child.setLayoutParams(layoutParams);
            }
    
            //更改 内部文字的透明底
            View mTextTitleView = child.getChildAt(1);
            if (mTextTitleView != null) {
                mTextTitleView.setAlpha(1 - (mPercent > 0.4 ? 1 : mPercent));
            }
    
            // 更改内部imageView的大小
            View mImageTitleView = child.getChildAt(0);
            if (mImageTitleView != null) {
                mImageTitleView.setScaleX(1 - (0.4f * mPercent));
                mImageTitleView.setScaleY(1 - (0.4f * mPercent));
            }
    
    
            //设置X轴坐标//没有计算状态栏的情况之下,滑动并不是完整的
            child.setX(mViewMaxX - (mViewMaxX - 50/*左边的距离*/) * mPercent);
    
    
            // 设置Y轴坐标
            child.setY(mViewMaxY - (mViewMaxY * 1.4f) * mPercent);
    
    
            return true;
        }
    }
    

    查看效果图:

    完成监听

    最后

    未完待续、敬请期待!

    FullScreenDeveloper

    源码地址

    相关文章

      网友评论

        本文标题:Material Design系列 - 自定义Behavior实

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