UETool - 源码解析
UETool 仓库地址:https://github.com/eleme/UETool ,UETool 的功能很多,这里主要是分析 catch 功能
起步分析
- 图 1 中圈住的控制面板对应
UETMenu
文件,当点击 catch 的时候会启动一个透明的 Activity - TransparentActivity,注意这里无痕迹的启动动画。 - TransparentActivity 启动需要传递一个参数:type,你想使用控制面板中的哪一个功能就传递哪一个 type,TransparentActivity 会根据 type 来选择初始化具体的
CollectViewsLayout
子类实例。 - 以 catch 功能为例,传递的 type 是 TransparentActivity.Type.TYPE_EDIT_ATTR,就会初始化
EditAttrLayout
实例,并加入 TransparentActivity 的布局中。 - TransparentActivity 启动之前,
UETool
单例也会保存一份对当前顶层 Activity 。 - 不论什么 type 传入 TransparentActivity,都会初始化一个
BoardTextView
,对应使用功能后在屏幕左下角的信息板。
除了 catch 能力外,其他能力的逻辑也是这样的
为什么需要初始化一个 CollectViewsLayout
子类实例
CollectViewsLayout
会重写 onAttachedToWindow 方法,在这个方法中会根据保存在UETool
单例中的顶层 Activity ,反射拿到其持有的DecorView根据 DecorView 就可以拿到对应的控件树,会一层层遍历拿到所有的 View。将 View 封装成
Element
,并将它们按照面积从大到小排序。这些封装好的Element
最终都会被加到一个 List 中。CollectViewsLayout
提供获取指定坐标下Element
的方法。选择 catch 后,选中一个 View 弹出的 dialog 展示的信息就是来自这个方法获取的Element
。1
2Element getTargetElement(float x, float y);
List<Element> getTargetElements(float x, float y);CollectViewsLayout
还提供对指定位置绘制线条和文本。选择 catch 后,选中一个 View ,那个 View 周围的红线和宽高数据就是通过这个方法绘制的。1
2protected void drawText(Canvas canvas, String text, float x, float y)
protected void drawLineWithText(Canvas canvas, int startX, int startY, int endX, int endY, int endPointSpace)
详细说说 EditAttrLayout
之前说到初始化后的 EditAttrLayout
实例,会被加入到 TransparentActivity 的布局中。所以,我们的触摸事件都会被 EditAttrLayout
接收到。因此,我们重点看下 EditAttrLayout
的 onTouchEvent 方法:
1 | private Element targetElement; |
通过阅读我们知道,移动和抬起会触发 IMode
的 triggerActionMove 和 triggerActionUp 方法,并且重绘的时候也会触发 IMode
的 onDraw 方法。而默认的 IMode
是 ShowMode
实例,所以接下来先看 ShowMode
实现:
1 | class ShowMode implements IMode { |
阅读之后,我们可以发现当用户选择 catch 功能,点击屏幕抬起后。我们会根据触摸的坐标去调用在 CollectViewsLayout
中提供的获取指定坐标下 Element
的方法,如果可以获取到就会弹出一个AttrsDialog
用来展示这个 View 对应的数据。并且会调用 invalidate 方法刷新界面,这样 ShowMode
的 onDraw 也会被调用,处理的事情就是调用 CollectViewsLayout
提供的绘制线条和文本能力进行绘制。
聊一聊 UI 相关的 AttrsDialog
AttrsDialog
的内容很简单,就是一个 RecyclerView,只不过它有着各种 ViewHolder。
BaseViewHolder - Item
我们先看 BaseViewHolder
以及 Item
:
1 | public static abstract class BaseViewHolder<T extends Item> extends RecyclerView.ViewHolder { |
TitleViewHolder
1 | class TitleViewHolder extends BaseViewHolder<TitleItem> |
效果:
TextViewHolder
1 | class TextViewHolder extends BaseViewHolder<TextItem> |
效果:
EditTextViewHolder
1 | class EditTextViewHolder<T extends EditTextItem> extends BaseViewHolder<T> |
效果:
AddMinusEditViewHolder
1 | // 和 EditTextViewHolder 几乎一致,只是交互不同而已 |
效果:
SwitchViewHolder
1 | class SwitchViewHolder extends BaseViewHolder<SwitchItem> |
效果:
BitmapInfoViewHolder
1 | class BitmapInfoViewHolder extends BaseViewHolder<BitmapItem> |
效果:
BriefDescViewHolder
1 | class BriefDescViewHolder extends BaseViewHolder<BriefDescItem> |
效果:
怎么管理 ViewHolder
AttrsDialogItem…
我们给每一个 ViewHolder 实例都创建一个 AttrsDialogItemViewBinder
实例,例如 TitleViewHolder
对应的 TitleItemBinder
。
1 | public abstract class AttrsDialogItemViewBinder<T extends Item, VH extends AttrsDialog.Adapter.BaseViewHolder<T>> implements ItemViewBinder<T, VH> { |
TitleItemBinder
最终会把工作交给 TitleViewHolder
处理。
1 | public class TitleItemBinder extends AttrsDialogItemViewBinder<TitleItem, AttrsDialog.Adapter.TitleViewHolder> { |
仅仅这样只是产生同样数量的 AttrsDialogItemViewBinder
子类,我们还需要一个 AttrsDialogMultiTypePool
。
AttrsDialogMultiTypePool
AttrsDialogMultiTypePool
随着 UETool
单例的创建而创建,并会自动将注册所有的 AttrsDialogItemViewBinder
实例:
1 | public class UETool { |
在 Adapter 中使用
1 | public static class Adapter extends RecyclerView.Adapter { |
AttrDialogCallback
1 | public interface AttrDialogCallback { |
这个 callbck 设置的时机来自于 EditAttrLayout
中 ShowMode
的 triggerActionUp 方法被触发的时候,创建 AttrsDialog
的时候设置的。
- enableMove:用于切换 EditAttrLayout 的 IMode
- showValidViews:调用 CollectViewsLayout 提供获取指定坐标下 Element 的方法,不过获取的结果是一个集合,然后显示它们
- selectView:跳转到另一个 Element
获取数据的 IAttrs
1 | public void notifyDataSetChanged(Element element) { |
查看 notifyDataSetChanged 方法,可以发现数据来自 IAttrs
实例。IAttrs
实例之一是 UETCore
,它首先会先获取通用的数据,然后它还会从两个内部类 UETTextView
、UEImageView
中获取数据。其中从 View 中获取的值转化成显示的值都借助 Util
实现。
1 | public class UETCore implements IAttrs { |