Android 炫动滑动 卡片层叠布局,仿探探、人人影视订阅界面 简单&优雅:LayoutManager+ItemTouchHelper
来源:互联网 发布:c 数组拷贝 编辑:程序博客网 时间:2024/04/27 16:47
转载请标明出处:
http://blog.csdn.net/zxt0601/article/details/53730908
本文出自:【张旭童的博客】(http://blog.csdn.net/zxt0601)
代码传送门:喜欢的话,随手点个star。多谢
https://github.com/mcxtzhang/ZLayoutManager
概述
前几天看有人实现了仿人人美剧的订阅界面,不过在细节之处以及实现方式我个人认为都不是最佳的姿势。
于是我也动手撸了一个,还顺带撸了个探探的界面,先看GIF:
这里吐个槽,探探这种设计真的像皇帝翻牌子的感觉,不喜欢左滑,喜欢右滑。
人人影视版特点(需求):
- 动画:最多可见的这四层,在顶层卡片滑动时,每一层都会位移&放大动画,有种补充到顶层的感觉。
- 动画:松手时,如果未被判定为删除,则会有顶层以下每一层卡片收缩回原位的动画。
- 无限循环:模仿人人影视,顶层卡片被删除后,补充到最底层。
除上述动画特点,探探版特点(需求):
- Roate的变化:左右滑动时,顶层卡片会慢慢旋转,到阈值max大概十五度。
- Alpha的变化:左滑时顶层卡片的删除按钮会慢慢显现,右滑时爱心按钮会慢慢显现。
- 显然,松手时,以上动画也需要复位。
我们的效果,基本上和原版一致了,写起来怎么样呢?
我不是标题党,如标题所说:
- 简单:思路简单清晰易理解
- 优雅:性能没有任何隐患,
LayoutManager
只会加载显示屏幕上可见的数量的View。 - 快速:利用
ItemTouchHelper
处理拖拽&滑动删除逻辑,核心代码不超过50行。且经过封装,四行代码就可以用。
伸手党福利:
如果懒得看这么多文字只想用,直接移步gayhub,gradle导入相关文件or复制。然后如下,搞定。
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
而且我将一些参
数都以变量形式计算,这样就做到了可配置,假如老板让你一开始多显示几层卡片,例如6层,你只需要修改一个参数即可,效果如图:
正确的姿势
正确的姿势就是:
- 利用
LayoutManager
实现卡片层叠布局,值得注意的是,只layout出界面上可能会看见的那些View。 - 搭配
ItemTouchHelper
,它本身实现了拖拽&滑动删除逻辑,我们只需要在onChildDraw()
中绘制动画和onSwiped()
中处理数据集(循环or删除)。
所以本文也算是填了LayoutManger系列的坑,实现了一个酷炫效果的布局。
Let’s Go!
LayoutManager的实现卡片层叠
其实本例中的LayoutManager
十分简单,因为ItemTouchHelper
的存在,LayoutManager
根本不需要处理它的滑动事件,而LayoutManager
中最难写的就是在滑动时的View
回收和复用,以及layout
新View
的处理。
关于LayoutManager的基础知识和铺垫,我就不再赘述,可参考我以前的文章:LayoutManager实现流式布局
唯一注意事项
但是即便如此,还是有一个唯一的注意事项。我们只layout
出界面上可能会看见的那些View
即可。
因为考虑到动画,所以是可能会看见。
我们看人人美剧的界面:
初始化时,界面上可见三个View
,我们分别起名:TopView,Top-1View,Top-2View
。其中TopView
完全可见,Top-1View,Top-2View
只有下边缘可见。
如文首GIF,滑动TopView
时,Top-1View,Top-2View
开始慢慢放大,并且向上位移,直至填充至它们各自上层的View。这时候露出了Top-3View
。
所以我们在书写LayoutManager
的onLayoutChildren()
方法时,只要layout
出当前数据集最后四个View即可。
前文提到的参数配置如下:
包括一些配置
- 界面最多显示几个View
- 每一级View之间的Scale差异、translationY等等
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
LayoutManager全部代码如下,布满注释,如果看不懂,建议阅读前置文章LayoutManger系列:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
撸到这里,我们的静态界面已经成型,下面让我们动起来:
ItemTouchHelper实现炫动滑动:
ItemTouchHelper
的基础知识,建议大家自行学习,网上文章很多,我简单介绍一下,
This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.
It works with a RecyclerView and a Callback class, which configures what type of interactions
are enabled and also receives events when user performs these actions.
Depending on which functionality you support, you should override
{@link Callback#onMove(RecyclerView, ViewHolder, ViewHolder)} and / or
{@link Callback#onSwiped(ViewHolder, int)}.
翻译 + 总结:
这货是一个工具类,为RecyclerView扩展滑动消失(删除)和drag & drop效果的。
它需要和RecyclerView、Callback 一起工作。Callback 类里定义了 允许哪些交互,并且会接收到对应的交互事件
根据你需要哪种功能(滑动消失(删除)和drag & drop),你需要重写
Callback#onMove(RecyclerView, ViewHolder, ViewHolder)—–drag & drop
Callback#onSwiped(ViewHolder, int) 方法。 —–滑动消失(删除)
总结一下入门级用法如下,三个步骤:
定义一个Callback:
ItemTouchHelper.Callback callback = new ItemTouchHelper.SimpleCallback(int,int)
,这两个int分别代表要 监听哪几个方向上的拖拽、滑动事件。 常用:ItemTouchHelper.DOWN | ItemTouchHelper.UP | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT
将Callback传给ItemTouchHelper:
ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
关联ItemTouchHelper和RecyclerView:
itemTouchHelper.attachToRecyclerView(mRv)
这三个步骤做完后,ItemTouchHelper就会自动帮我们完成 滑动消失(删除)和drag & drop 的功能。
滑动删除
我们本例中,需要的是滑动消失(删除) ,所以我们的Callback
不需要关注onMove()
方法。
且我们需要上下左右滑动都可以删除的效果。
则如下构造Callback,传入上下左右:
- 1
- 2
- 1
- 2
onSwiped()
方法,是滑动删除动作已经发生后回调的,即,我们先滑动卡片,然后松手,此时ItemTouchHelper
判断我们的手势是删除手势,会自动对这个卡片执行丢出屏幕外的动画,同时回调onSwiped()
方法。
所以我们需要在其中如下写:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 1
- 2
- 3
- 4
- 5
- 6
- 7
在这里我们完成了循环的操作:
- 利用当前被删除的
View
的ViewHolder
拿到Position
- 删除数据集中对应
Position
的数据源 - 同时将该数据源插入数据集中的首位。
- 调用
notifyDataSetChanged()
,通知列表刷新
如此我们便完成了,循环列表的需求。
这里提一下为什么我们要调用notifyDataSetChanged()
。
看官方文档:
ItemTouchHelper moves the items’ translateX/Y properties to reposition them
即ItemTouchHelper实现的滑动删除,其实只是隐藏了这个滑动的View。并不是真的删除了。
在LayoutManager实现流式布局一文第五节中,我们已经提到,notifyDataSetChanged()
会回调onLayoutChildren()
这个函数,而在这个函数中,我们会重新布局,即真正的移除(不再layout)滑动掉的View,同时会补充进新的最底层的View。
嗯,JavaBean也看一眼吧,没亮点:
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
我们写到这里已经完成了滑动删除的功能,其实我们什么都没有写是吧,复杂的判断都由ItemTouchHelper帮我们处理掉了,例如速度、滑动距离是否到达删除阈值,删除成功移除的动画、取消删除复位的动画等等。
所以我说利用ItemTouchHelper才是正确的姿势,因为很简单&快速。
下面我们来实现滑动时的动画。
滑动时动画
我们需要重写Callback
的onChildDraw()
方法,这个方法参数较多:
* @param c The canvas which RecyclerView is drawing its children * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to * @param viewHolder The ViewHolder which is being interacted by the User or it was interacted and simply animating to its original position * @param dX The amount of horizontal displacement caused by user's action * @param dY The amount of vertical displacement caused by user's action * @param actionState 是拖拽还是滑动事件 The type of interaction on the View. Is either {@link #ACTION_STATE_DRAG} or {@link #ACTION_STATE_SWIPE}. * @param isCurrentlyActive 事件是用户产生还是动画产生的 True if this view is currently being controlled by the user or false it is simply animating back to its original state.
对我们比较有用的有dX dX
,可以判断滑动方向,以及计算滑动的比例,从而控制缩放、位移动画的程度。
本文如下编写,对View的缩放、位移,其实是对LayoutManager里的操作的逆操作,值得注意的是最后一层,即top-3View
在Y轴上是保持不变的:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
getThreshold(viewHolder)
函数,返回是否可以被回收掉的阈值,关于它为什么这么写,我是从源码里找到的,本末会讲解:
- 1
- 2
- 3
- 4
- 1
- 2
- 3
- 4
探探效果的实现
一开始文章撸到这里应该结束了,群里出来一个马小跳,告诉我探探和这略有不同,希望我一并实现。
嗯,好吧。表示没听说过探探,那我先去下载一个看看吧。
loading-install-open……..
哎哟呵,十分钟过去了,我还在滑动看美女 忘记了要干什么,被女票看到胖揍了我一顿。
好的,我捂着脸继续分析。
探探和人人影视有两点不同:
- Roate的变化:左右滑动时,顶层卡片会慢慢旋转,到阈值max大概十五度。
- Alpha的变化:左滑时顶层卡片的删除按钮会慢慢显现,右滑时爱心按钮会慢慢显现。
感觉也是炒鸡简单,来吧。五分钟撸完吃外卖。修改点:
- 在layout布局添加『 X 』&『 爱心 』。
- 在
onChildDraw()
里,按比例修改TopView的Rotate & Alpha
监听方向
还有一点小不同,上滑下滑不再能删除,所以我们构造时只传入左右即可:
- 1
- 2
- 1
- 2
布局添加两个按钮
略
onChildDraw()
在上文人人影视的基础上扩展,上文的效果,对TopView
是不做任何操作的。这里只需要再对TopView
做额外操作即可:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
实现完后,我以为结束了,结果比我们想象的还要复杂一丢丢。因为此时删除后,notifyDataSetChanged()
刷新界面,而TopView
还是倾斜的,爱心、删除图标也是出现的。这显然与预期不符。所以我们需要在onSwiped()
里将其复位:
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
Ok,大功告成。效果和文首一样,尽情去跟产品UI嘚瑟吧。
阈值的寻找之路
阈值的寻找,花费了我一些时间,因为我想做到和系统的行为保持一致。
即,当删除、喜欢图标全显,当Top-1View
显示完毕时,松手 TopView
会回收。
这就决定了我们的缩放、位移的阈值不能随便定,所以我们必须去源代码里找答案。
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
因为滑动删除操作是touch事件导致的,且应该是ACTION_UP时,触发的,
所以在ItemTouchHelper
源码里,搜索onTouch字样:
定位到:mOnItemTouchListener
,->
继续定位其中的onTouchEvent()
,-> case MotionEvent.ACTION_UP:
,-> void select(ViewHolder selected, int actionState)
->
在这里我注意到有一句代码:animationType = ANIMATION_TYPE_SWIPE_SUCCESS;
这说明删除成功,它的触发条件是:if (swipeDir > 0)
-> swipeDir
的值: final int swipeDir = prevActionState == ACTION_STATE_DRAG ? 0
->
: swipeIfNecessary(prevSelected);int swipeIfNecessary(ViewHolder viewHolder)
->
- 1
- 2
- 3
- 1
- 2
- 3
如此返回1的话,则->checkHorizontalSwipe(viewHolder, flags)
->
在其中终于找到源码里阈值的获取之处:
- 1
- 2
- 1
- 2
于是我就直接复制出来。
总结
代码传送门:喜欢的话,随手点个star。多谢
https://github.com/mcxtzhang/ZLayoutManager
本文利用LayoutManager
加载显示屏幕上可见的数量的View,搭配ItemTouchHelper
处理拖拽&滑动删除逻辑,核心代码不超过50行。且经过封装,四行代码就可以用。
记住LayoutManager
,我们写,只layout出界面上可能会看见的那些View即可。
关于ItemTouchHelper
,它本身实现了拖拽&滑动删除逻辑,我们只需要在onChildDraw()
中绘制动画和onSwiped()
中处理数据集(循环or删除)即可。
以后老板让你做这种效果,你只需要:
- 1
- 2
- 3
- 4
- 5
- 1
- 2
- 3
- 4
- 5
如果需要定制特殊的参数,例如显示6层:
- Android 炫动滑动 卡片层叠布局,仿探探、人人影视订阅界面 简单&优雅:LayoutManager+ItemTouchHelper
- 五行代码实现 炫动滑动 卡片层叠布局,仿探探、人人影视订阅界面 简单&优雅:LayoutManager+ItemTouchHelper
- 五行代码实现 炫动滑动 卡片层叠布局,仿探探、人人影视订阅界面 简单&优雅:LayoutManager+ItemTouchHelper
- 五行代码实现 炫动滑动 卡片层叠布局,仿探探、人人影视订阅界面 简单&优雅
- 五行代码实现 炫动滑动 卡片层叠布局,仿探探、人人影视订阅界面 简单&优雅
- LayoutManagerDemo:仿探探、人人影视 卡片层叠 炫动滑
- wap简单的卡片层叠布局 滑动
- 自定义viewGroup+ViewDragHelper: 仿探探主页卡片式滑动,层叠布局
- 自定义LayoutManager实现android-pile-layout滑动卡片堆叠效果
- Android 卡片层叠效果
- 实现卡片布局,左右滑动
- Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
- Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
- Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
- Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
- Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
- Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
- Android 界面滑动实现---Scroller类 从源码和开发文档中学习(让你的布局动起来)
- 解决fullPage.js中每屏未铺满的方法
- xUtils获取json用gson解析json
- 有了ERP,还缺什么?
- BZOJ 2038 莫队
- powershell studio Offline keys blocked
- Android 炫动滑动 卡片层叠布局,仿探探、人人影视订阅界面 简单&优雅:LayoutManager+ItemTouchHelper
- Java实现MD5
- lucene3.0多个索引库的合并
- 48 leetcode - Combination Sum IV
- 在OpenCV中图像边界扩展 copyMakeBorder 的实现
- 22. Generate Parentheses
- OpenCV2.4.13+VS2012开发环境配置与实例
- 12.20
- makefile用法及作用