从源码学习UEToll
从源码学习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
的数据是从哪里来的了.
- 先是获取到element的view
- 通过
AttrsManager.createAttrs(view)
获取自定义view的属性,并且进行填充 - 针对不同的属性填充不同的item,对应的值从view中获取
- 最后返回一个总属性的
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
,然后循环找到当前Activity
的decorView
,
然后递归traverse
方法获取到所有的子view
,填充到elements
中,最后被getTargetElement
获取出来.
然后基本上就这样了.
总结-流程
UETMenu.show()
在window
层面展示一个悬浮框- 点击第一个按钮
捕获控件
,跳转到TransparentActivity
- 根据
type
在Activity
的vContainer
添加EditAttrLayout
- 在
EditAttrLayout
的MotionEvent.ACTION_UP
事件触发的时候就执行ShowModel
的triggerActionUp
,弹出AttrsDialog
,用于展示相关View
的信息 View
的详细信息在UETCore
中获取- 这时候注意到
adapter
的items
的数据是由show(element)
传递进来的 show
的element
是getTargetElement()
方法提供的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
中的逻辑
- 初始化时
doubleNotNull
返回false
,直接跳过onDraw
的逻辑. - 当点击第一个
View
时.走标记为(1)
处代码,为View
绘画边框线和虚线,同时doubleNotNull
返回false
,跳过onDraw
. - 当有两个
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的更多相关文章
- Java集合专题总结(1):HashMap 和 HashTable 源码学习和面试总结
2017年的秋招彻底结束了,感觉Java上面的最常见的集合相关的问题就是hash--系列和一些常用并发集合和队列,堆等结合算法一起考察,不完全统计,本人经历:先后百度.唯品会.58同城.新浪微博.趣分 ...
- jQuery源码学习感想
还记得去年(2015)九月份的时候,作为一个大四的学生去参加美团霸面,结果被美团技术总监教育了一番,那次问了我很多jQuery源码的知识点,以前虽然喜欢研究框架,但水平还不足够来研究jQuery源码, ...
- MVC系列——MVC源码学习:打造自己的MVC框架(四:了解神奇的视图引擎)
前言:通过之前的三篇介绍,我们基本上完成了从请求发出到路由匹配.再到控制器的激活,再到Action的执行这些个过程.今天还是趁热打铁,将我们的View也来完善下,也让整个系列相对完整,博主不希望烂尾. ...
- MVC系列——MVC源码学习:打造自己的MVC框架(三:自定义路由规则)
前言:上篇介绍了下自己的MVC框架前两个版本,经过两天的整理,版本三基本已经完成,今天还是发出来供大家参考和学习.虽然微软的Routing功能已经非常强大,完全没有必要再“重复造轮子”了,但博主还是觉 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(二:附源码)
前言:上篇介绍了下 MVC5 的核心原理,整篇文章比较偏理论,所以相对比较枯燥.今天就来根据上篇的理论一步一步进行实践,通过自己写的一个简易MVC框架逐步理解,相信通过这一篇的实践,你会对MVC有一个 ...
- MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)
前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...
- 我的angularjs源码学习之旅2——依赖注入
依赖注入起源于实现控制反转的典型框架Spring框架,用来削减计算机程序的耦合问题.简单来说,在定义方法的时候,方法所依赖的对象就被隐性的注入到该方法中,在方法中可以直接使用,而不需要在执行该函数的时 ...
- ddms(基于 Express 的表单管理系统)源码学习
ddms是基于express的一个表单管理系统,今天抽时间看了下它的代码,其实算不上源码学习,只是对它其中一些小的开发技巧做一些记录,希望以后在项目开发中能够实践下. 数据层封装 模块只对外暴露mod ...
- leveldb源码学习系列
楼主从2014年7月份开始学习<>,由于书籍比较抽象,为了加深思考,同时开始了Google leveldb的源码学习,主要是想学习leveldb的设计思想和Google的C++编程规范.目 ...
随机推荐
- vue学习过程总结(01)- 开发环境的搭建
1.本地vue开发环境的搭建 1.1.下载NodeJs.下载地址:https://nodejs.org/en/download/ node.js的相关结束以及教程:https://www.runoob ...
- Redis快速入门:初识Redis
[IT168 专稿]在之前的文章中介绍了<Redis快速入门:选择Key-Value Store>,今天给大家介绍Redis的入门知识.Redis是一个开源的使用ANSI C语言编写.支持 ...
- 有限差分法(Finite Difference Method)解方程:边界和内部结点的控制方程
FDM解常微分方程 问题描述 \[\frac{d^2\phi}{dx^2}=S_{\phi} \tag{1} \] 这是二阶常微分方程(second-order Ordinary Differenti ...
- 从零开始,开发一个 Web Office 套件(13):删除、替换已选中文字
这是一个系列博客,最终目的是要做一个基于 HTML Canvas 的.类似于微软 Office 的 Web Office 套件(包括:文档.表格.幻灯片--等等). 博客园:<从零开始, 开发一 ...
- java LinkedList (详解)
Java 链表(LinkedList) 一.链表简介 1.链表 (Linked List) 是一种常见的基础数据结构,是一种线性表,但是链表不会按线性表的顺序存储数据,而是每个节点里存到下一个节点的地 ...
- 【freertos】004-任务创建与删除及其实现细节
前言 后面都是已动态内存任务为例来分析. 注意: 由于当前学习是在linux上跑的freertos,对于freertos底层相关接口,从demo工程来看,都是posix标准相关. 鉴于freertos ...
- 用TLS/SSL保证EMQ的网络传输安全
作为基于现代密码学公钥算法的安全协议,TLS/SSL能在计算机通讯网络上保证传输安全,EMQ的MQTT broker支持TLS,也可以用这种方式来确保传输安全. 参考官网:https://www.em ...
- WzwJDBC 自定义工具类(获取连接,释放资源)
package wzwUtil;import java.io.IOException;import java.io.InputStream;import java.sql.*;import java. ...
- USART_GetITStatus()和USART_GetFlagStatus()的区别
USART_GetITStatus()和USART_GetFlagStatus()的区别 都是访问串口的SR状态寄存器,唯一不同是,USART_GetITStatus()会判断中断是否开启,如果没开启 ...
- React+Webpack+ES6 兼容低版本浏览器(IE9)解决方案
虽然过了兼容IE6的噩梦时代,IE依旧阴魂不散,因为你可能还要兼容IE9.在ES6已经普及的今天,用ES6写react已经成了标配.但是babel编译的js语法,由于某些不规范的写法,可能在IE9下不 ...