从源码学习UEToll

  • 捕获控件梳理
  • 相对位置功能梳理
  • 网格栅栏功能梳理

捕获代码分析

TransparentActivity

public @interface Type {
int TYPE_UNKNOWN = -1;
int TYPE_EDIT_ATTR = 1; // 捕获控件
int TYPE_SHOW_GRIDDING = 2; // 网格
int TYPE_RELATIVE_POSITION = 3; // 相对位置
} @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/// 省略代码
type = getIntent().getIntExtra(EXTRA_TYPE, TYPE_UNKNOWN); switch (type) {
case TYPE_EDIT_ATTR: // 捕获控件的入口
EditAttrLayout editAttrLayout = new EditAttrLayout(this);
editAttrLayout.setOnDragListener(new EditAttrLayout.OnDragListener() {
@Override
public void showOffset(String offsetContent) {
board.updateInfo(offsetContent); //更新左下角展示的view描述
}
});
vContainer.addView(editAttrLayout);
break;
}
}

EditAttrLayout

public class EditAttrLayout extends CollectViewsLayout {
private Element targetElement;
private AttrsDialog dialog;
private IMode mode = new ShowMode();
private float lastX, lastY;
private OnDragListener onDragListener; @Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (targetElement != null) {
canvas.drawRect(targetElement.getRect(), areaPaint);
mode.onDraw(canvas); // 进行mode的draw
}
} @Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
lastX = event.getX();
lastY = event.getY();
break;
case MotionEvent.ACTION_UP:
mode.triggerActionUp(event); // 调用ShowMode.triggerActionUp(默认),可更改为MoveMode.triggerActionUp
break;
case MotionEvent.ACTION_MOVE:
mode.triggerActionMove(event); // 移动view时触发的方法
break;
}
return true;
} class ShowMode implements IMode { @Override
public void onDraw(Canvas canvas) {
Rect rect = targetElement.getRect();
drawLineWithText(canvas, rect.left, rect.top - lineBorderDistance, rect.right, rect.top - lineBorderDistance);
drawLineWithText(canvas, rect.right + lineBorderDistance, rect.top, rect.right + lineBorderDistance, rect.bottom);
} @Override
public void triggerActionMove(MotionEvent event) { } @Override
public void triggerActionUp(final MotionEvent event) {
// 在按下抬起的时候 获取到event,并生成element
final Element element = getTargetElement(event.getX(), event.getY());
if (element != null) {
targetElement = element; // 赋值给当前元素
invalidate(); // 请求重绘View树,即draw()过程
if (dialog == null) {
dialog = new AttrsDialog(getContext());
dialog.setAttrDialogCallback(new AttrsDialog.AttrDialogCallback() {
// 里面的回调我们先不关心,现在只关心bottom弹出部分
});
}
dialog.show(targetElement); // 弹出展示页面
}
}
}
}

AttrsDialog

public class AttrsDialog extends Dialog {
private RecyclerView vList; // bottom view
private Adapter adapter = new Adapter(); public void show(Element element) {
show(); // Dialog.show()
// ... 省略些window配置
adapter.notifyDataSetChanged(element); // 更新bottom view
layoutManager.scrollToPosition(0);
}
}

到这里发现只是更新了adapter数据,那接下来就应该去adapter里面去找数据的来源了.

adapter代码太多了了截取了相关代码进行展示

AttrsDialog.Adapter

public static class Adapter extends RecyclerView.Adapter {

    private List<Item> items = new ItemArrayList<>();
private AttrDialogCallback callback; // 更新item数据
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();
}
}

可以发现数据都是通过IAttrs.getAttrs(element)获取的.接下来就Ctrl+Alt+鼠标左键找到了UETCore/UETTextView/UETImageView,

它们都实现了IAttrs类.由下面的代码就可以知道item的数据是从哪里来的了.

  1. 先是获取到element的view
  2. 通过AttrsManager.createAttrs(view)获取自定义view的属性,并且进行填充
  3. 针对不同的属性填充不同的item,对应的值从view中获取
  4. 最后返回一个总属性的List<item>

UETCore

public class UETCore implements IAttrs {

    @Override
public List<Item> getAttrs(Element element) {
List<Item> items = new ArrayList<>(); View view = element.getView(); items.add(new SwitchItem("Move", element, SwitchItem.Type.TYPE_MOVE));
items.add(new SwitchItem("ValidViews", element, SwitchItem.Type.TYPE_SHOW_VALID_VIEWS)); 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.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;
} static class AttrsManager { public static IAttrs createAttrs(View view) {
if (view instanceof TextView) {
return new UETTextView();
} else if (view instanceof ImageView) {
return new UETImageView();
}
return null;
}
} static class UETTextView implements IAttrs { @Override
public List<Item> getAttrs(Element element) {
List<Item> items = new ArrayList<>();
TextView textView = ((TextView) element.getView());
items.add(new TitleItem("TextView"));
items.add(new EditTextItem("Text", element, EditTextItem.Type.TYPE_TEXT, textView.getText().toString()));
items.add(new AddMinusEditItem("TextSize(sp)", element, EditTextItem.Type.TYPE_TEXT_SIZE, px2sp(textView.getTextSize())));
items.add(new EditTextItem("TextColor", element, EditTextItem.Type.TYPE_TEXT_COLOR, Util.intToHexColor(textView.getCurrentTextColor())));
List<Pair<String, Bitmap>> pairs = Util.getTextViewBitmap(textView);
for (Pair<String, Bitmap> pair : pairs) {
items.add(new BitmapItem(pair.first, pair.second));
}
items.add(new SwitchItem("IsBold", element, SwitchItem.Type.TYPE_IS_BOLD, textView.getTypeface() != null ? textView.getTypeface().isBold() : false));
return items;
}
} static class UETImageView implements IAttrs { @Override
public List<Item> getAttrs(Element element) {
List<Item> items = new ArrayList<>();
ImageView imageView = ((ImageView) element.getView());
items.add(new TitleItem("ImageView"));
items.add(new BitmapItem("Bitmap", Util.getImageViewBitmap(imageView)));
items.add(new TextItem("ScaleType", Util.getImageViewScaleType(imageView)));
return items;
}
}
}

那么element从哪里来的呢

    // AttrsDialog
public void show(Element element) {
show();
Window dialogWindow = getWindow();
WindowManager.LayoutParams lp = dialogWindow.getAttributes();
dialogWindow.setGravity(Gravity.LEFT | Gravity.TOP);
lp.x = element.getRect().left;
lp.y = element.getRect().bottom;
lp.width = getScreenWidth() - dip2px(30);
lp.height = getScreenHeight() / 2;
dialogWindow.setAttributes(lp);
adapter.notifyDataSetChanged(element); //这里传递给adapter的
layoutManager.scrollToPosition(0);
} // EditAttrLayout.ShowMode
public void triggerActionUp(final MotionEvent event) {
final Element element = getTargetElement(event.getX(), event.getY());
if (element != null) {
targetElement = element;
invalidate();
if (dialog == null) {
dialog = new AttrsDialog(getContext());
dialog.setAttrDialogCallback(new AttrsDialog.AttrDialogCallback() {
@Override
public void enableMove() {
mode = new MoveMode();
dialog.dismiss();
}
@Override
public void showValidViews(int position, boolean isChecked) {
int positionStart = position + 1;
if (isChecked) {
dialog.notifyValidViewItemInserted(positionStart, getTargetElements(lastX, lastY), targetElement);
} else {
dialog.notifyItemRangeRemoved(positionStart);
}
}
@Override
public void selectView(Element element) {
targetElement = element;
dialog.dismiss();
dialog.show(targetElement);
}
});
dialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
@Override
public void onDismiss(DialogInterface dialog) {
if (targetElement != null) {
targetElement.reset();
invalidate();
}
}
});
}
dialog.show(targetElement);
}
}

就会发现重点在getTargetElement方法里面,所有更新adapter的操作都走了这个方法.

跟踪上去

    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 (isParentNotVisible(element.getParentElement())) {
continue;
}
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;
}

发现跟elements这个数组有关系

    // CollectViewsLayout
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));
}
}
} @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
try {
Activity targetActivity = UETool.getInstance().getTargetActivity();
WindowManager windowManager = targetActivity.getWindowManager();
Field mGlobalField = Class.forName("android.view.WindowManagerImpl").getDeclaredField("mGlobal");
mGlobalField.setAccessible(true); if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.M) {
Field mViewsField = Class.forName("android.view.WindowManagerGlobal").getDeclaredField("mViews");
mViewsField.setAccessible(true);
List<View> views = (List<View>) mViewsField.get(mGlobalField.get(windowManager));
for (int i = views.size() - 1; i >= 0; i--) {
View targetView = getTargetDecorView(targetActivity, views.get(i));
if (targetView != null) {
traverse(targetView);
break;
}
}
} else {
Field mRootsField = Class.forName("android.view.WindowManagerGlobal").getDeclaredField("mRoots");
mRootsField.setAccessible(true);
List viewRootImpls;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
viewRootImpls = (List) mRootsField.get(mGlobalField.get(windowManager));
} else {
viewRootImpls = Arrays.asList((Object[]) mRootsField.get(mGlobalField.get(windowManager)));
}
for (int i = viewRootImpls.size() - 1; i >= 0; i--) {
Class clazz = Class.forName("android.view.ViewRootImpl");
Object object = viewRootImpls.get(i);
Field mWindowAttributesField = clazz.getDeclaredField("mWindowAttributes");
mWindowAttributesField.setAccessible(true);
Field mViewField = clazz.getDeclaredField("mView");
mViewField.setAccessible(true);
View decorView = (View) mViewField.get(object);
WindowManager.LayoutParams layoutParams = (WindowManager.LayoutParams) mWindowAttributesField.get(object);
if (layoutParams.getTitle().toString().contains(targetActivity.getClass().getName())
|| getTargetDecorView(targetActivity, decorView) != null) {
traverse(decorView);
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}

发现这个方法通过反射获取到应用的mViews,然后循环找到当前ActivitydecorView,

然后递归traverse方法获取到所有的子view,填充到elements中,最后被getTargetElement获取出来.

然后基本上就这样了.

总结-流程

  1. UETMenu.show()window层面展示一个悬浮框
  2. 点击第一个按钮捕获控件,跳转到TransparentActivity
  3. 根据typeActivityvContainer添加EditAttrLayout
  4. EditAttrLayoutMotionEvent.ACTION_UP事件触发的时候就执行ShowModeltriggerActionUp,弹出AttrsDialog,用于展示相关View的信息
  5. View的详细信息在UETCore中获取
  6. 这时候注意到adapteritems的数据是由show(element)传递进来的
  7. showelementgetTargetElement()方法提供的
  8. getTargetElement中可以看到有个elements,再找到他的源头->traverse->onAttachedToWindow

捕获代码并且展示的这一部分就差不多了.大致流程应该梳理的还算清楚了.

相对位置分析

TransparentActivity

    protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
switch (type) {
case TYPE_EDIT_ATTR:
...
case TYPE_RELATIVE_POSITION:
// 这个是入口,将一个RelativePositionLayout,添加到了
// R.id.container 里面
vContainer.addView(new RelativePositionLayout(this));
break;
case TYPE_SHOW_GRIDDING:
...
default:
...
}
...
}

RelativePositionLayout

    private final int elementsNum = 2; // 指定View个数为2个
// View
private Element[] relativeElements = new Element[elementsNum];
private int searchCount = 0; // 搜索次数,也就是你点击View的次数
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
break;
case MotionEvent.ACTION_UP:
// 在这里处理View的逻辑,被整除的在第一个,否则在第二个,同时
// searchCount累加
final Element element = getTargetElement(event.getX(), event.getY());
if (element != null) {
relativeElements[searchCount % elementsNum] = element;
searchCount++;
invalidate();
}
break;
}
return true;
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas); if (relativeElements == null) {
return;
} boolean doubleNotNull = true;
for (Element element : relativeElements) {
// (1)
if (element != null) {
Rect rect = element.getRect();
canvas.drawLine(0, rect.top, screenWidth, rect.top, dashLinePaint);
canvas.drawLine(0, rect.bottom, screenWidth, rect.bottom, dashLinePaint);
canvas.drawLine(rect.left, 0, rect.left, screenHeight, dashLinePaint);
canvas.drawLine(rect.right, 0, rect.right, screenHeight, dashLinePaint);
canvas.drawRect(rect, areaPaint);
} else {
doubleNotNull = false;
}
} if (doubleNotNull) {
// (2)
Rect firstRect = relativeElements[searchCount % elementsNum].getRect();
Rect secondRect = relativeElements[(searchCount - 1) % elementsNum].getRect(); if (secondRect.top > firstRect.bottom) {
int x = secondRect.left + secondRect.width() / 2;
drawLineWithText(canvas, x, firstRect.bottom, x, secondRect.top);
} if (firstRect.top > secondRect.bottom) {
int x = secondRect.left + secondRect.width() / 2;
drawLineWithText(canvas, x, secondRect.bottom, x, firstRect.top);
} if (secondRect.left > firstRect.right) {
int y = secondRect.top + secondRect.height() / 2;
drawLineWithText(canvas, secondRect.left, y, firstRect.right, y);
} if (firstRect.left > secondRect.right) {
int y = secondRect.top + secondRect.height() / 2;
drawLineWithText(canvas, secondRect.right, y, firstRect.left, y);
} drawNestedAreaLine(canvas, firstRect, secondRect);
drawNestedAreaLine(canvas, secondRect, firstRect);
}
}

onDraw中的逻辑

  1. 初始化时doubleNotNull返回false,直接跳过onDraw的逻辑.
  2. 当点击第一个View时.走标记为(1)处代码,为View绘画边框线和虚线,同时doubleNotNull返回false,跳过onDraw.
  3. 当有两个View时.走标记为(2)处代码,计算两个View的相对位置然后根据不同情况画线.

网格栅栏分析

TransparentActivity

    protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
switch (type) {
case TYPE_EDIT_ATTR:
...
case TYPE_RELATIVE_POSITION:
...
case TYPE_SHOW_GRIDDING:
// 这个是入口,将一个GriddingLayout,添加到了
// R.id.container 里面
vContainer.addView(new GriddingLayout(this));
board.updateInfo("LINE_INTERVAL: " + DimenUtil.px2dip(GriddingLayout.LINE_INTERVAL, true));
default:
...
}
...
}

这里就是简单的添加了一个间隔为5dp网格View.

从源码学习UEToll的更多相关文章

  1. Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结

    2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...

  2. jQuery源码学习感想

    还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码, ...

  3. MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)

    前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...

  4. MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)

    前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...

  5. MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)

    前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...

  6. MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...

  7. 我的angularjs源码学习之旅2——依赖注入

    依赖注入起源于实现控制反转的典型框架Spring框架,用来削减计算机程序的耦合问题.简单来说,在定义方法的时候,方法所依赖的对象就被隐性的注入到该方法中,在方法中可以直接使用,而不需要在执行该函数的时 ...

  8. ddms(基于 Express 的表单管理系统)源码学习

    ddms是基于express的一个表单管理系统,今天抽时间看了下它的代码,其实算不上源码学习,只是对它其中一些小的开发技巧做一些记录,希望以后在项目开发中能够实践下. 数据层封装 模块只对外暴露mod ...

  9. leveldb源码学习系列

    楼主从2014年7月份开始学习<>,由于书籍比较抽象,为了加深思考,同时开始了Google leveldb的源码学习,主要是想学习leveldb的设计思想和Google的C++编程规范.目 ...

随机推荐

  1. ansible 一 简介和部署

    一.Ansible的介绍 Ansible是新出现的自动化运维工具,基于Python开发,集合了众多运维工具(puppet.cfengine.chef.func.fabric)的优点.实现了批量系统配置 ...

  2. 4月27日 python学习总结 GIL、进程池、线程池、同步、异步、阻塞、非阻塞

    一.GIL:全局解释器锁 1 .GIL:全局解释器锁 GIL本质就是一把互斥锁,是夹在解释器身上的, 同一个进程内的所有线程都需要先抢到GIL锁,才能执行解释器代码 2.GIL的优缺点: 优点:  保 ...

  3. crash_for_windows_pkg远程代码执行漏洞

    漏洞详情 crash_for_windows_pkg由 Electron 提供支持.如果 XSS 负载以代理的名义,我们可以远程执行受害者计算机上的任何 JavaScript 代码. 受影响的冲突版本 ...

  4. 面试问题之C++语言:C++中指针和引用的区别

    转载于:https://blog.csdn.net/gcc2018/article/details/82285940 1.指针是一个变量,只不过这个变量存储的是一个地址,指向内存的一个存储单元:而引用 ...

  5. 什么是 zuul路由网关?

    (1)Zuul 包含了对请求的路由和过滤两个最主要的功能:其中 责将外部请求转发到具体的微服务实例上,是实现外部访问统一入口的基础而过滤器功能则负 请求的处理过程进行干预,是实现请求校验.服务聚合等功 ...

  6. jQuery--筛选【串联函数】

    串联函数简介 A.add(B) 将A和B组合成一个对象 A.children().andSelf() 将之前的对象添加到操作集合中 A.children().children().end() 回到最近 ...

  7. APScheduler定时任务框架

    1.简介 APScheduler是一个Python**定时任务框架**,提供了**基于日期**.**固定时间间隔**以及**crontab**类型的任务,并且可以**持久化任务**.基于这些功能,我们 ...

  8. 微信小程序黑客马拉松即将开始,来做最酷的 Mini Program Creators!

    微信小程序黑客马拉松正式启动 近日,小程序斩获一项世界级殊荣--作为一项全新的技术和应用创新,小程序首次获选世界互联网领先科技成果.目前小程序应用数量已超过 100 万,覆盖了 200 多个细分行业, ...

  9. 关于mui中一个页面有有多个页签进行切换的下拉刷新加搜索问题

    此图是最近做的项目中的一页,用的是mui结合vue,用了mui后,觉得是真心难用啊,先不说其他的,就光这个下拉刷新就让人奔溃了,问题层出不穷,不过最后经过努力还是摆平了哈. 1.每次切换到新的标签,都 ...

  10. idea 启动微服务 设置 run dashboard

    微服务如果很多,启动时如果在run窗口,会不是很方便,所以idea中配置了rundashboard,有时不自动出现时,需要进行配置: 配置操作如下: 我的idea版本2020.2 1.在父工程的.id ...