博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
饿了么UETool原理初探
阅读量:7073 次
发布时间:2019-06-28

本文共 6967 字,大约阅读时间需要 23 分钟。

最近看见一个好库

1.饿了么UETool效果图&&用途

自从我把这个工具给我们设计同学安利之后,她们就爱的不要不要的,用过安卓系统开发者选项的同学知道,有一个查看边界的按钮,但是有时候呢,觉得功能不够,因为开发者选项显示边距的和android studio的Layout Inspector 或者DDMS 的uiautomator工具

一样都是静态显示view边距以及相关状态,但是!有了UEtool,不仅能看见view的各种属性,你还能动态改!也就是说你不仅能看你还能摸,有了这个工具,我再也不用为了改1,2个dp的边距我再编译运行了!设计同学也能更好调整UI了。

2.饿了么UETool原理初探

以UETool官方Demo的捕捉控件功能例吧。

如何快速分析一个我们完全陌生的app呢,那上工具,第一步先看看目前activity是谁

adb shell dumpsys window w | findstr mCurrent或者
adb shell dumpsys window w | grep mCurrent 该命令能区分activity和popupwindow (win用findstr mac/linux用 grep)

好目标就是me.ele.uetool.TransparentActivity

第二步,看目标View

这就是捕捉控件功能对应的特殊ViewGroup了,它有一个成员变量叫AttrsDialog是一个自定义Dialog,展示的就是View属性列表的RecyclerView,我们重点看它的adapter

public static class Adapter extends RecyclerView.Adapter {        private List
items = new ItemArrayList<>(); private AttrDialogCallback callback; public void setAttrDialogCallback(AttrDialogCallback callback) { this.callback = callback; } public void notifyDataSetChanged(Element element) { items.clear(); for (String attrsProvider : UETool.getInstance().getAttrsProvider()) { try { IAttrs attrs = (IAttrs) Class.forName(attrsProvider).newInstance(); items.addAll(attrs.getAttrs(element)); } catch (Exception e) { e.printStackTrace(); } } notifyDataSetChanged(); }复制代码

好来一个下一个断点,我们跳过复杂的封装逻辑,直接看运行的函数看调用栈

这里我们看见了数据源其实就是element,这个element存有一个View成员变量

public class UETCore implements IAttrs {    @Override    public List
getAttrs(Element element) { List
items = new ArrayList<>(); View view = element.getView(); items.add(new SwitchItem("Move", element, SwitchItem.Type.TYPE_MOVE)); IAttrs iAttrs = AttrsManager.createAttrs(view); if (iAttrs != null) { items.addAll(iAttrs.getAttrs(element)); } items.add(new TitleItem("COMMON")); items.add(new TextItem("Class", view.getClass().getName())); items.add(new TextItem("Id", Util.getResId(view))); items.add(new TextItem("ResName", Util.getResourceName(view.getResources(), view.getId()))); items.add(new TextItem("Clickable", Boolean.toString(view.isClickable()).toUpperCase())); items.add(new TextItem("Focused", Boolean.toString(view.isFocused()).toUpperCase())); items.add(new AddMinusEditItem("Width(dp)", element, EditTextItem.Type.TYPE_WIDTH, px2dip(view.getWidth()))); items.add(new AddMinusEditItem("Height(dp)", element, EditTextItem.Type.TYPE_HEIGHT, px2dip(view.getHeight()))); items.add(new TextItem("Alpha", String.valueOf(view.getAlpha()))); Object background = Util.getBackground(view); if (background instanceof String) { items.add(new TextItem("Background", (String) background)); } else if (background instanceof Bitmap) { items.add(new BitmapItem("Background", (Bitmap) background)); } items.add(new AddMinusEditItem("PaddingLeft(dp)", element, EditTextItem.Type.TYPE_PADDING_LEFT, px2dip(view.getPaddingLeft()))); items.add(new AddMinusEditItem("PaddingRight(dp)", element, EditTextItem.Type.TYPE_PADDING_RIGHT, px2dip(view.getPaddingRight()))); items.add(new AddMinusEditItem("PaddingTop(dp)", element, EditTextItem.Type.TYPE_PADDING_TOP, px2dip(view.getPaddingTop()))); items.add(new AddMinusEditItem("PaddingBottom(dp)", element, EditTextItem.Type.TYPE_PADDING_BOTTOM, px2dip(view.getPaddingBottom()))); return items; }复制代码

有View对象当然dialog显示View各个属性没问题,那么很奇怪,这个view是MainActivity的,这个新开的TransparentActivity是怎么拿到数据源的呢 莫慌,看这个函数栈,注意到EditAttrLayout 类的triggerActionUp方法的element

@Override        public void triggerActionUp(MotionEvent event) {            final Element element = getTargetElement(event.getX(), event.getY());            if (element != null) {                EditAttrLayout.this.element = element;                invalidate();                if (dialog == null) {                    dialog = new AttrsDialog(getContext());                    dialog.setAttrDialogCallback(new AttrsDialog.AttrDialogCallback() {                        @Override                        public void enableMove() {                            mode = new MoveMode();                            dialog.dismiss();                        }                    });                    dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {                        @Override                        public void onDismiss(DialogInterface dialog) {                            element.reset();                            invalidate();                        }                    });                }                dialog.show(element);            }        }复制代码

好发现关键句getTargetElement(event.getX(), event.getY()); 点进去会跳到父类CollectViewsLayout的getTargetElement方法

protected Element getTargetElement(float x, float y) {        Element target = null;        for (int i = elements.size() - 1; i >= 0; i--) {            final Element element = elements.get(i);            if (element.getRect().contains((int) x, (int) y)) {                if (element != childElement) {                    childElement = element;                    parentElement = element;                } else if (parentElement != null) {                    parentElement = parentElement.getParentElement();                }                target = parentElement;                break;            }        }        if (target == null) {            Toast.makeText(getContext(), getResources().getString(R.string.uet_target_element_not_found, x, y), Toast.LENGTH_SHORT).show();        }        return target;    }复制代码

element和elements有直接关系再看到elements List<Element> elements怎么来的呢?list数据填充无非2种常用的要么add要么addAll直接command +f或者ctrl + f搜索elements.add就发现了数据源设置的函数

private void traverse(View view) {        if (UETool.getInstance().getFilterClasses().contains(view.getClass().getName())) return;        if (view.getAlpha() == 0 || view.getVisibility() != View.VISIBLE) return;        if (getResources().getString(R.string.uet_disable).equals(view.getTag())) return;        elements.add(new Element(view));        if (view instanceof ViewGroup) {            ViewGroup parent = (ViewGroup) view;            for (int i = 0; i < parent.getChildCount(); i++) {                traverse(parent.getChildAt(i));            }        }    }复制代码

来搞个断点看调用栈,或者就在element 构造方法下断点就能省掉上面从element到elemnets的分析 来看下图

UETool拿到targetActivity也就是MainActivity,然后反射拿到decoreView,然后调用 EditAttrLayout类的父类CollectViewsLayout类traverse方法。

至此按时间顺序总结一下,CollectViewsLayout类的onAttachedToWindow通过反射拿到目标MainActivity的decoreView,去给CollectViewsLayout的成员变量List<Element> elements add包装了decoreView的Element,然后用户也就是我,点了UETool的操作控件按钮,UP事件的时候AttrsDialog的show方法调用adapter.notifyDataSetChanged(element);给Adapter设置被UETCore解开element各种属性的List<Item>作为数据源

好了,原理简单初探到这里。饿了么大神的代码就这么大概摸完了,代码封装的很不错,建议有兴趣的同学可以看看,学习一下。

转载地址:http://ylkml.baihongyu.com/

你可能感兴趣的文章
构建自己的PHP框架--构建模版引擎(1)
查看>>
Linux之一次性安装开发工具:yum groupinstall Development tools
查看>>
dos.orm的事务处理
查看>>
Odoo 二次开发教程(五)-新API的介绍与应用
查看>>
VC++ 一个简单的Log类
查看>>
Java内存模型深度解析:重排序 --转
查看>>
CentOS防火墙iptables的配置方法详解
查看>>
webpack搭建前端一条龙服务
查看>>
1.ASP.NET MVC使用EPPlus,导出数据到Excel中
查看>>
nxn随机矩阵乘以概率向量依旧是概率向量
查看>>
【转载】TCP协议要点和难点全解
查看>>
mysql修改表、字段、库的字符集
查看>>
realm vs. domain
查看>>
关闭Spring Boot的Jsckson的FAIL_ON_EMPTY_BEANS
查看>>
Oracle 切割字符查询
查看>>
结构体内存对齐具体解释
查看>>
Cocos Code IDE里xcodeprojectlua脚本更新
查看>>
LEARN SWIFT
查看>>
jenkins指定具体项目具体分支进行构建部署
查看>>
关于音频文件的上传
查看>>