Material Design系列 - 自定义Behavior实现伸缩标题栏
引言
CoordinatorLayout+CollapsingToolbarLayout+Behavior真是一个好东西,很多复杂的UI交互效果都可以通过Behavior来实现,用了Behavior之后腰也不疼了,再也不会对设计师说这个实现不了了,只要给我时间我就实现给你看!今天带来第一个自定义Behavior:实现一个伸缩的标题栏。
效果图如下
实现思路
- 监听CollapsingToolbarLayout滚动的Y轴距离,和CollapsingToolbarLayout的总高度进行百分比计算得出当前滑动的百分比,再不断的计算顶部图标的宽高进行百分比缩减。整个按钮的X轴坐标跟随百分比减少。
- 整个View的宽度除以4,得出每个menu所占的宽度,用item的的下标乘以menu的宽度得出每个menu的X轴。
- 当滑动的时候改变文字的透明度,大于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>
先运行起来看看效果吧:
确定第一个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>
运行起来看看效果吧:
可以看到,第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; } }
查看效果图:
最后
未完待续、敬请期待!
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
react native Android加载本地Html 问题
项目中有用到IP地址采集 用的是html5 webView的方式 请参考:react native 获取设备 真实ip地址 和 ip 映射的地理位置 这里留下的坑是 Android的release包 中获得ip是没反应的 一开始以为是Android WebView的缓存问题,后来改了还是没法获得,但是在开发中却可以拿到,很奇快..... 后来查了下原因原来是:由于React Native打包资源时不支持html资源的自动打包,所以我们得手动将html资源放到asset目录下,没有的话就自己创建 把静态资源放在assets目录下 image.png 使用的时候:ios正常引入Android改成一下方式取 <WebView onMessage={e => this.getDataFormWebview(e)} source={ Platform.OS === 'ios'? require('../components/ipadress.html'): {uri: 'file:///android_asset/ipadress.html'} } javaScriptEnabled...
- 下一篇
【2018年最新】 iOS面试题及答案
设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的事情。 1). MVC模式:Model View Control,把模型 视图 控制器 层进行解耦合编写。 2). MVVM模式:Model View ViewModel 把模型 视图 业务逻辑 层进行解耦和编写。 3). 单例模式:通过static关键词,声明全局变量。在整个进程运行期间只会被赋值一次。 4). 观察者模式:KVO是典型的通知模式,观察某个属性的状态,状态发生变化时通知观察者。 5). 委托模式:代理+协议的组合。实现1对1的反向传值操作。 6). 工厂模式:通过一个类方法,批量的根据已有模板生产对象。 MVC 和 MVVM 的区别 MVC是一种架构模式,M表示Model,V表示视图View,C表示控制器Controller: Model负责存储、定义、操作数据; View用来展示给用户,并且和用户进行交互; Controller是Model和View的协调者,Controller把Model中的数据拿过来给View使用。Controller可以直接与Mo...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
-
Docker使用Oracle官方镜像安装(12C,18C,19C)
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- CentOS8编译安装MySQL8.0.19
- Docker快速安装Oracle11G,搭建oracle11g学习环境
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- MySQL8.0.19开启GTID主从同步CentOS8
- CentOS7,8上快速安装Gitea,搭建Git服务器
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
推荐阅读
最新文章
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- CentOS6,CentOS7官方镜像安装Oracle11G
- SpringBoot2整合Redis,开启缓存,提高访问速度
- Jdk安装(Linux,MacOS,Windows),包含三大操作系统的最全安装
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- SpringBoot2整合MyBatis,连接MySql数据库做增删改查操作
- Hadoop3单机部署,实现最简伪集群
- MySQL8.0.19开启GTID主从同步CentOS8
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果