朋友圈实现原理
我们使用 Android Device Monitor 来分析朋友圈发布图片的界面实现原理。如果需要分析其他应用的界面实现也是采用这种方法哦。
打开 Android Device Monitor,选择DDMS,连接上真机,区域2 就会显示出当前手机正在运行的应用,再点击 区域1,然后新窗口就会显示出当前页面分析的结果,点击 区域3 中的相应控件,区域4 中就会选中对应的控件。区域4 中以菜单的层级关系显示出各控件的关系。
区域4中各参数意义:
eg:(0)FrameLayout[0,0][720,1280]
对应意义:(序号)控件名[x位置,y位置][宽度,高度]
我们这里主要分析与图片拖拽相关的布局实现!通过 4 中的控件层级关系,我们可以画出这样一个大概布局图:
从图中可以看出,微信团队使用了自定义的 GridView 实现了图片的拖拽和删除功能。
准备
以上分析得出微信使用的是自定义 GridView 实现 item 拖拽和删除功能。xxx,现在都什么年代了,还使用 GridView 吗?当然是使用流行的 RecyclerView 实现啦。如果只是单纯地使用 RecyclerView 就能实现的话,那世界就和平。这里还需要使用 ItemTouchHelper 对拖拽进行处理。
ItemTouchHelper
官方解释:
This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView
一个让 RecyclerView 支持滑动删除和拖拽的实用工具类
- 主要方法
- 使用
自定义一个类继承并实现 ItemTouchHelper.Callback 接口,以下方法必须实现:
实现化 ItemTouchHelper 并关联 RecyclerView
ItemTouchHelper.Callback
ItemTouchHelper 在拖拽和滑动删除的过程中会回调 ItemTouchHelper.Callback 的相关方法:
- 主要方法
实现过程
先放上最终效果图:
布局文件
自定义 MyCallBack 类继承并实现 ItemTouchHelper.Callbackr 的以下方法:
设置 item 只能处理拖拽事件,并能够向左、右、上、下拖拽
刷新 item
关联 RecyclerView
调整布局
现在运行程序,界面如下:
手动黑人问号??? 仔细研究布局,原来 recyclerView 的高度设置成了wrap_content,因此无论怎么拖动,超出recyclerView的边界的部分就是不会显示滴!那就把高度设置成match_parent咯,重新调整后的布局如下:
再运行一次,我xx,还是不行。这是什么鬼,我不是已经设置了match_parent了吗?这一定有黑科技!经过一番思索,想起了一个不是经常使用的属性:clipToPadding;设置android:clipToPadding=”false”,果然可以。
居然还有这种操作!
android:clipToPadding
Defines whether the ViewGroup will clip its children and resize (but not clip) any EdgeEffect to its padding, if padding is not zero. This property is set to true by default
设置ViewGroup是否剪切其子View,通俗点讲就是:是否允许ViewGroup在padding中绘制子View。默认情况下,此属性设置为true,即不允许;
另外一个和 clipToPadding 比较相似的属性是:clipChildren;
android:clipChildren
Defines whether a child is limited to draw inside of its bounds or not. This is useful with animations that scale the size of the children to more than 100% for instance. In such a case, this property should be set to false to allow the children to draw outside of their bounds. The default value of this property is true
一句话概括就是:是否允许子View超出父View
使用场景有:比如说外卖里的购物车效果
很明显画红色的地方是超出黑色的 View,可以将黑色 View 设置android:clipChildren=”false”,这样子 View 红色部分就可以超出父 View 大小。
还发现了一个比较诡异的问题,地址如下:
http://blog.csdn.net/u013231041/article/details/73603891
设置最后一个 item 不能移动
由以上分析可知,ItemTouchHelper.Callback 的 isLongPressDragEnabled() 可以设置是否支持长按拖拽,默认是 true ,即支持长按拖拽。现在我们要自定义指定哪些 item 可以拖拽,哪些不可以,因此我们需要重写 isLongPressDragEnabled():
取消了支持长按拖拽,就要自己处理 RecyclerView 的触摸事件,具体实现请参考源代码。
在长按事件触发的时候调用以下代码:
最后还需要在 ItemTouchHelper.Callback 的 onMove() 中添加以下代码
还有要在 clearView() 方法里去 notifyDataSetChanged,不然 item 的 position 是没有交换的
设实现拖拽到底部放手删除功能
item 从拖动到放手的主要处理流程图如下:
那么我们应该在哪个地方去判断item是否到达删除区域呢?在前面介绍的方法,有这么个方法:onChildDraw,这个方法就会在item拖拽的过程不断回调并且返回item的偏移量。有了偏移量之后我们就很容易去判断item是否到达删除区域了。
偏移量满足以下条件时,就到达删除区域:
item偏移量>=RecyclerView的高-item底部到RecyclerView顶边的距离-TextView的高
问题来了,我们怎样判断用户在拖动后放手呢?
我们用 boolean up 来标记,当 up 为 true 时手指抬起,false 为初始状态。在getAnimationDuration() 中设置其为true。记得需要在 clearView() 中恢复初始值 false ;
还需要在 ItemTouchHelper.Callback 中暴露个接口 DragListener 给外部,用来提示通知外部什么时候显示删除区域,以及 item 进入删除区域时的文字提示。
相关代码由于过长,这里省略,有兴趣的可以通过原文链接去学习。
图片相关
一看到图片或者说看到 Bitmap,会联想到什么?没错就是OOM!!!对于Bitmap导致的OOM要解决它是很有套路可言的。我们都知道图片压缩分为两种:质量压缩和尺寸压缩。可能有些人对这两个概念不是很清楚!他们的区别是质量压缩并不会改变图片的尺寸,而尺寸压缩则会改变图片的尺寸。当把以文件形式存在在硬盘上的图片,以Bitmap的形式加载到内存中的时候,我就必须进行尺寸压缩,尺寸压缩可以减少内存占用,这样就解决了OOM。详细原理参考文章,地址如下:
http://blog.csdn.net/tyk0910/article/details/53462728
对于质量压缩?主要用在图片传输上,用于提高传输速率,就像我们朋友圈发布图片时,它会对图片质量进行压缩,然后再传到后台,所以你在朋友圈看到的图片都不是原图片,都是经过压缩过的。这里推荐一个质量压缩效果和微信差不多的开源库,地址如下:
https://github.com/Curzibn/Luban
文末demo已实现图片尺寸压缩,即解决了OOM,也解决了拖拽不流畅问题。
总结
有时候我们看到一个觉得酷炫的功能,很想去实现他。这时候不必匆匆下手。我们自己可以先想一下要是我自己会怎样去实现,然后再用下 Android Device Monitor 大概分析下,更深一步可反编译一下看下源码,看他是怎样实现的,分析两者的优劣。对于这种情形:一个 View1 根据其他 View2 操作的手势或状态的改变而改变的,可以根据这样的套路去想:这个 View2 是否有提供这种状态反馈的方法什么的,我是否能把这个反馈传到 View1等。在不确定这个 View2 是否有你想要的方法时,请查看官方文档,请查看官方文档,请查看官方文档,重要的事件说三遍!比如我的删除区域是根据item的拖拽状态和距离去改变的,那么你就要去找 ItemTouchHelper.Callback 给我提供了什么回调啊,哪些方法可以返回这些东东啊,最后我就写个接口给通知外部。
demo地址:
https://github.com/kuyue/WeChatPublishImagesDrag