Android自定义Behavior实现跟随手势滑动,显示隐藏标题栏、底部导航栏及悬浮按钮

Android Design包下的CoordinatorLayout是相当重要的一个控件,它让许多动画的实现变为可能,而且更加简便。按照官方解释CoordinatorLayout是用来协调子View交互动作的父view,Behavior可以看做CoordinatorLayout的子view实现交互的组件。

本篇博客主要用来实现仿知乎的Android客户端首页的滑动嵌套动画。首先附上项目的地址 (https://github.com/Lauzy/LBehavior) 。

先来一波效果图:

Activity中使用

BottomView+Fragment使用

效果实现思路:

判断手势

计算距离

触发动画

文章目录:

CoordinatorLayout及Behavior简介

自定义Behavior

仿知乎效果的动画实现及个性化

CoordinatorLayout和Behavior简介

Android滑动嵌套的原理及Behavior分析已经有很多大神讲解过了,推荐Loader大神的源码看CoordinatorLayout.Behavior原理。

这里简单介绍下,嵌套滑动时父View(需实现NestedScrollingParent接口)和子View(需实现NestedScrollingChild接口)之间的交互是由NestedScrolling两个接口控制,NestedScrollingParentHelper和NestedScrollingChildHelper两个辅助类分别处理了父布局和子View的大量逻辑。

滑动嵌套的简单流程为:控制子View(如RecyclerView)的onInterceptTouchEvent和onTouchEvent的事件分发 -> 调用NestedScrollingChildHelper不同的方法 -> 处理与NestedScrollingParent交互的逻辑 -> 父布局(如CoordinatorLayout)实现NestedScrollingParent处理具体的逻辑

(-> 而Behavior的事件处理方法则主要由CoordinatorLayout的各种事件处理方法来调用,返回值控制了父布局的事件消费情况)。

具体方法的调用大家可以再研读Loader大神的博客。下边简单介绍下自定义Behavior实现的具体方法Behavior官网。

方法

1.layoutDependsOn

确定提供的子视图是否具有另一个特定的兄弟视图作为布局依赖关系。即用来确定依赖关系,如果某个控件需要依赖控件,则重写该方法

如AppBarLayout

@Override

public boolean layoutDependsOn(CoordinatorLayout parent, View child, View dependency) {

return dependency instanceof AppBarLayout;

}

2.onDependentViewChanged

依赖视图的大小、位置发生变化时调用此方法,重写此方法可以处理child的响应。如常用的AppBarLayout,当其发生变化时,childView会根据重写的方法作出响应。

@Override

public boolean onDependentViewChanged(CoordinatorLayout parent, View child, View dependency) {

offsetChildAsNeeded(parent, child, dependency);

return false;

}

3.onStartNestedScroll

当CoordinatorLayout的子View开始嵌套滑动时(此处的滑动View必须实现NestedScrollingChild接口),触发此方法。添加Behavior的控件需要为CoordinatorLayout的直接子View,否则不会继续流程。

//判断是否垂直滑动

@Override

public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {

return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;

}

4.onNestedPreScroll

此方法中consumed,指的是父布局要消费的滚动距离,consumed[0]为水平方向消耗的距离,consumed[1]为垂直方向消耗的距离,可控制此参数作出相应的调整。

如垂直滑动时,若设置consumed[1]=dy,则代表父布局全部消耗了滑动的距离,类似AppBarLayout这种效果,当其由展开到折叠过渡时,通过consumed控制其中的嵌套滑动。

/**

* 触发滑动嵌套滚动之前调用的方法

*

* @param coordinatorLayout coordinatorLayout父布局

* @param child 使用Behavior的子View

* @param target 触发滑动嵌套的View(实现NestedScrollingChild接口)

* @param dx 滑动的X轴距离

* @param dy 滑动的Y轴距离

* @param consumed 父布局消费的滑动距离,consumed[0]和consumed[1]代表X和Y方向父布局消费的距离,默认为0

*/

@Override

public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target,

int dx, int dy, int[] consumed) {

super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);

}

5.onNestedScroll

此方法中dyConsumed代表TargetView消费的距离,如RecyclerView滑动的距离,可通过控制NestScrollingChild的滑动来指定一些动画,

本篇博客实现的效果主要就是重写此方法,若根据onNestedPreScroll中dy来判断,则当RecyclerView条目很少时,也会触发逻辑代码,故选择了重写此方法。

/**

* 滑动嵌套滚动时触发的方法

*

* @param coordinatorLayout coordinatorLayout父布局

* @param child 使用Behavior的子View

* @param target 触发滑动嵌套的View

* @param dxConsumed TargetView消费的X轴距离

* @param dyConsumed TargetView消费的Y轴距离

* @param dxUnconsumed 未被TargetView消费的X轴距离

* @param dyUnconsumed 未被TargetView消费的Y轴距离(如RecyclerView已经到达顶部或底部,

* 而用户继续滑动,此时dyUnconsumed的值不为0,可处理一些越界事件)

*/

@Override

public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target,

int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {

super.onNestedScroll(coordinatorLayout, child, target,

dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);

}

自定义Behavior

自定义Behavior主要有两种实现方式:

第一种为layoutDependsOn和onDependentViewChanged,child需要依赖于dependency,当dependency View发生变化时,onDependentViewChanged会被调用,child可作出相应的响应。

第二种为onStartNestedScroll 等嵌套滑动的流程,首先在onStartNestedScroll方法中判断是否垂直滑动等,然后在onNestedPreScroll、onNestedScroll等方法中实现效果。

由于第一种方式会导致child必须依赖于某个特定的View,这样就导致灵活性不太强,所以本文采用第二种实现方式。

具体实现

在嵌套滑动开始之前,可以判断是否垂直滑动,做一些初始化工作,比如获取childView的初始坐标。

//判断垂直滑动

@Override

public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, View child, View directTargetChild, View target, int nestedScrollAxes) {

if (isInit) {// 设置标记,防止new Anim导致的parent和child坐标变化

mCommonAnim = new LTitleBehaviorAnim(child);

isInit = false;

}

return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;

}

触发嵌套滑动之前,可以在此处判断一些滑动手势,以及父布局的消费情况。由于若根据此方法中dy来判断,则当RecyclerView条目很少时,也会触发逻辑代码,故本文只是在此方法中给动画做一些自定义操作。

@Override

public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dx, int dy, int[] consumed) {

if (mCommonAnim != null) {

mCommonAnim.setDuration(mDuration);

mCommonAnim.setInterpolator(mInterpolator);

}

super.onNestedPreScroll(coordinatorLayout, child, target, dx, dy, consumed);

}

滑动嵌套滚动时触发的方法,以Title(Toolbar)为例,若向上滑动,则隐藏Toolbar,反之显示。

@Override

public void onNestedScroll(CoordinatorLayout coordinatorLayout, View child, View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed) {

super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed);

if (dyConsumed < 0) {

if (isHide) {

mCommonAnim.show();

isHide = false;

}

} else if (dyConsumed > 0) {

if (!isHide) {

mCommonAnim.hide();

isHide = true;

}

}

}

仿知乎效果的动画实现及个性化

大家都知道知乎客户端的各种动画非常优雅,网上仿写其动画的博客也是层出不穷,之前利用空闲时间撸了一款干货集中营客户端,突然想到了采用知乎的首页效果,然后就拿起键盘,复制粘贴搞了起来。

开个玩笑,其实大致实现效果还是比较容易的,这里主要分享下实现的思路以及需要注意的细节。

首先大致流程就如上边几个方法介绍,动画效果的实现也非常简单,这里以显示和隐藏BottomView为例,直接上代码。

public LBottomBehaviorAnim(View bottomView) {

mBottomView = bottomView;

mOriginalY = mBottomView.getY();//因为Y值随动画会发生变化,嵌套滑动开始之前先记录初始的坐标。

}

@Override

public void show() {//显示

ValueAnimator animator = ValueAnimator.ofFloat(mBottomView.getY(), mOriginalY);

animator.setDuration(getDuration());

animator.setInterpolator(getInterpolator());

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator valueAnimator) {

mBottomView.setY((Float) valueAnimator.getAnimatedValue());

}

});

animator.start();

}

@Override

public void hide() {//隐藏

ValueAnimator animator = ValueAnimator.ofFloat(mBottomView.getY(), mOriginalY + mBottomView.getHeight());

animator.setDuration(getDuration());

animator.setInterpolator(getInterpolator());

animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

@Override

public void onAnimationUpdate(ValueAnimator valueAnimator) {

mBottomView.setY((Float) valueAnimator.getAnimatedValue());

}

});

animator.start();

}

整个大致流程这样其实已经结束了,但是还达不到我们预期的效果。再次打开知乎客户端,以很缓慢的速度滑一滑,这时候你会发现竟然没有触发动画,OK,先记录下这个问题;再以很缓慢的速度向下滑,突然又触发动画了。整体来看,知乎的动画有种分层嵌套的效果。

先来解决第一个问题,只用加一行代码,即dyConsumed距离大于一定值的时候才允许滑动。

if(Math.abs(dyConsumed) > minScrollY){

...//onNestedScroll里边的逻辑代码

}

对于第二个问题,我一开始想,滑动一定的距离,难道要根据判断RecyclerView滑动的距离来判断是否触发动画?其实思路是正确的,但是我们不可能再去实现addOnScrollListener的一系列方法。这时候再想一想嵌套滑动,dyConsumed不就是recyclerView消费的距离吗,想到这里,那就很好实现了,只用将dyConsumed相加,相加的和大于一定值,就触发动画,代码也是很简单,结合第一个问题,知乎的效果就实现了。

mTotalScrollY += dyConsumed;//累加消费的距离

if (Math.abs(dyConsumed) > minScrollY || Math.abs(mTotalScrollY) > scrollYDistance) {

...//onNestedScroll里边的逻辑代码

mTotalScrollY = 0;//动画执行完毕后重置

}

接下来我们可以自定义设置一些属性值。首先要获取这个Behavior对象。

public static CommonBehavior from(View view) {

ViewGroup.LayoutParams params = view.getLayoutParams();

if (!(params instanceof CoordinatorLayout.LayoutParams)) {

throw new IllegalArgumentException("The view is not a child of CoordinatorLayout");

}

CoordinatorLayout.Behavior behavior = ((CoordinatorLayout.LayoutParams) params).getBehavior();

if (!(behavior instanceof CommonBehavior)) {

throw new IllegalArgumentException("The view's behavior isn't an instance of CommonBehavior. Try to check the [app:layout_behavior]");

}

return (CommonBehavior) behavior;

}

然后可以设置对象的属性:

public CommonBehavior setDuration(int duration) {

mDuration = duration;

return this;

}

public CommonBehavior setInterpolator(Interpolator interpolator) {

mInterpolator = interpolator;

return this;

}

public CommonBehavior setMinScrollY(int minScrollY) {

this.minScrollY = minScrollY;

return this;

}

public CommonBehavior setScrollYDistance(int scrollYDistance) {

this.scrollYDistance = scrollYDistance;

return this;

}

至此,整个流程已经实现了,其他TitleView及悬浮按钮的动画也是类似的规则,我又给Behavior和动画设置了Common类剔除掉一些重复代码,这里就不贴出来了。具体可以参考我的Github

动画已经实现,但是写代码的时候坑貌似永远是填不完的。

当我使用写出来的动画时,就发现了一个问题,由于是CoordinatorLayout作为根布局,所以RecyclerView顶部的item被toolbar遮挡了,

我们再看看知乎,轻轻滑动一小段距离,发现他的顶部Toolbar遮挡的地方其实是空白,可以发现知乎其实也是有这个问题的,不过人家处理的很好,所以用户基本上不会发现。

不过这个问题还是可以解决的,比如判断item为第一个时,可以加一个View填充,个人采用的自定义ItemDecoration,判断下若为第一个item,outRect.set(0, titleHeight, 0, 0),设置titleHeight的大小即可。BottomView也是同理,解决方法也是有不少的。

还有一个问题是写demo的时候发现的,我用LinearLayout作为BottomView,发现浮动按钮竟然是在LinearLayout上层执行各种动画,看起来不太和谐,后来发现FloatingActionButton的elevation若大于BottomView的elevation,则FloatingActionButton动画覆盖在BottomView上层,反之则在下层。之前却一直没有注意。

此外,当知乎的RecyclerView滑动到底部的时候,BottomView是会自动显示的,个人觉得可以根据dyUnconsumed的值或者onStopNestedScroll来判断RecyclerView是否滑动到底部来处理,全部加载完毕后再处理最后一个item的ItemDecoration,本文并没有具体实现,只是提供思路。

我们再来整理下解决问题的思路:首先想好做什么,然后研究原理,选择方案,再初步实现,继续优化细节,最后应用到项目。我想我们写程序的时候都应该这样,知其然知其所以然,做到举一反三。

整个流程就是这样,实现后再封装,然后呢,抽离出来提交到Github。

allprojects {

repositories {

...

maven { url 'https://jitpack.io' }

}

}

dependencies {

compile 'com.github.Lauzy:LBehavior:1.0.1'

}

具体使用也很简单

参数

说明

@string/title_view_behavior

顶部标题栏

@string/bottom_view_behavior

底部导航栏

@string/fab_scale_behavior

浮动按钮(缩放)

@string/fab_vertical_behavior

浮动按钮(上下滑动)

自定义(均设有默认值,可不使用):

方法

参数

说明

setMinScrollY

int y

设置触发动画的最小滑动距离,如 setMinScrollY(10)为滑动10像素才可触发动画,默认为5.

setScrollYDistance

int y

设置触发动画的滑动距离,防止用户缓慢滑动时单次滑动距离一直小于setMinScrollY的最小滑动距离导致无法触发动画.如设置此值为100,则用户即便缓慢滑动,当滑动距离达到100时也可触发动画.默认为40.

setDuration

int duration

设置动画持续时间.默认为400ms.

setInterpolator

Interpolator interpolator

设置动画插补器,修饰动画效果.默认模式为LinearOutSlowInInterpolator. Interpolator官方文档

CommonBehavior.from(mFloatingActionButton)

.setMinScrollY(20)

.setScrollYDistance(100)

.setDuration(1000)

.setInterpolator(new LinearOutSlowInInterpolator());

最后再附上项目地址,戳 我的Github ,欢迎 star。

高仿知乎android,Android高仿知乎首页Behavior相关推荐

  1. android开发百度地图坐标偏差,利用百度地图Android sdk高仿微信发送位置功能及遇到的问题...

    接触了百度地图开发平台半个月了,这2天试着模仿了微信给好友发送位置功能,对百度地图的操作能力又上了一个台阶 我在实现这个功能的时候,遇到一些困难,可能也是别人将会遇到的困难,特在此列出 1.在微信发送 ...

  2. Android之高仿QQ6.6.0侧滑效果(背景动画、透明+沉浸式状态栏、渐变效果)

    根据需求实现类似QQ侧滑效果,之前看到过很多实现方式通过SlidingMenu,但是既然官方推出了自己的专属控件,那么使用DrawerLayout就是不二选择.且看下文. 一.先来看看官方文档解释 D ...

  3. Android之高仿墨迹天气桌面组件(AppWidgetProvider) .

    Android之高仿墨迹天气桌面组件(AppWidgetProvider) . 点击:382 发布时间:2012-10-03 更多0 相信墨迹天气,大家都见过,他在时间显示和天气界面上,很吸引人,今天 ...

  4. linux源码共享,Android之高仿飞鸽传书热点创建与共享源码

    这两天,无意中看到飞鸽传书这个小东东,然后又突然对他的wifi热点创建与共享比较感兴趣,于是乎把他的APK给反编译了,很蛋疼的还原了一下他的这一小模块代码,感觉效果差不多,所以跟大家分享一下,下面我们 ...

  5. android版高仿淘宝客户端源码V2.3

    android版高仿淘宝客户端源码V2.3,这个版本我已经更新到2.3了,源码也上传到源码天堂那里了,大家可以看一下吧,该应用实现了我们常用的购物功能了,也就是在手机上进行网购的流程的,如查看产品(浏 ...

  6. android仿疯狂猜图源码,Android开发实现高仿优酷的客户端图片左右滑动切换功能实例【附源码下载】...

    本文实例讲述了Android开发实现高仿优酷的客户端图片左右滑动切换功能.分享给大家供大家参考,具体如下: 本例是用ViewPager去做的实现,支持自动滑动和手动滑动,不仅优酷网,实际上有很多商城和 ...

  7. Android DrawerLayout 高仿QQ5.2双向侧滑菜单

    转载请标明出处: http://blog.csdn.net/lmj623565791/article/details/41531475,本文出自: [张鸿洋的博客] 1.概述 之前写了一个Androi ...

  8. android高仿奇艺影视,android仿爱奇艺加载动画实例

    本篇文章介绍了android仿爱奇艺加载动画实例,具体代码如下: 效果图: 用到的知识点: Path ValueAnimator 如果对Path和ValueAnimator还不熟悉推荐去看这几个大神的 ...

  9. 精仿苹果x开机android,800元高仿iPhone X以假乱真!更可怕的是奸商套路

    原标题:800元高仿iPhone X以假乱真!更可怕的是奸商套路 每一代iPhone产品都不乏仿冒者,而iPhone X这样的高(zhuang)贵(bi)巅峰之作,同样少不了被模仿.抄袭,而从目前的情 ...

最新文章

  1. (chap1 网络基础知识)一些概念
  2. 我的世界java和pe版_《我的世界》pe版不一样的方块世界
  3. MVC简捷调用EasyUI的datagrid
  4. 年审是当月还是当天_车检是按月份还是日期 审车日期以什么为准
  5. wordpress phpmyadmin_西部数码使用指南:虚拟主机WordPress部署SSL注意事项
  6. Python GUI篇——关不掉的小窗口
  7. QTableWidget item设置不可以选中和编辑状态
  8. python列表数据类型(一分钟读懂)
  9. 谈谈android不同尺寸,不同分辨率的布局
  10. .NET技术(Path)
  11. msp430中如何连续对位进行取反_【万泉河】论PLC编程中的高内聚与低耦合
  12. 三星服务器锁微信,三星手机微信支付设置指纹锁步骤
  13. JMeter 如何生成指定取值范围、指定小数位数的随机浮点数
  14. 《谷歌大数据经典论文读后感》
  15. 这16道题都能答上来?恭喜你,90%的面试都能通过!
  16. 【python学习】自定义三维向量类 加减乘除查看值和长度
  17. CrystalDiskInfo硬盘检测工具 标准版及萌妹版
  18. bitset简单用法
  19. 高级数据结构1—初识树状数组—快速求得前缀和和修改某一元素值
  20. jQuery的基本用法

热门文章

  1. 军事ar虚拟现实电子沙盘系统的功能
  2. mysql 按比例计算排名_计算MS SQL中的百分比排名
  3. 淘宝API接口系列,获取购买到的商品订单列表,订单详情,订单物流,收货地址列表,买家信息,买家token,卖出的商品订单列表
  4. holdpwd php,PHPMyWind后台管理界面的SQL注入漏洞 - 网站安全
  5. P3669 [USACO17OPEN]Paired Up S 贪心+双指针
  6. AVL树(LL,RR,LR,RL)
  7. 你平时都怎么记笔记?给好学的你安利10个最好用的记笔记神器!
  8. 打造一张万能Windows安装盘(转)
  9. 移动最快apn服务器,中国移动修改APN为CMTDS提高4G网速
  10. Android完美实现kitkat透明导航效果