从源码学习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++编程规范.目 ...
随机推荐
- 配置Pouch镜像
镜像下载.域名解析.时间同步请点击阿里云开源镜像站 一.pouch镜像简介 阿里巴巴正式开源了基于Apache 2.0协议的容器技术Pouch.Pouch是一款轻量级的容器技术,拥有快速高效.可移植性 ...
- django之model,crm操作
一.字段 AutoField(Field) - int自增列,必须填入参数 primary_key=True BigAutoField(AutoField) - bigint自增列,必须填入参数 pr ...
- kernel热补丁
kernel正在运行的函数,如何实现地址替换的,高并发情况下,如何打热补丁
- MSSQL得知密码后getshell
本文用了 sql server 2000和sql server 2008 MSSQL连接 连接MSSQL 2000 新建连接: 填写目的IP.目的端口.用户名.密码: 一直下一步,完成后,数据库导航窗 ...
- 利用多个sem信号量在线程通讯
直接上代码,主要用到sem_trywait & sem_post #include<stdio.h> #include<pthread.h> #include<s ...
- Java9的模块化是什么
Java9新特性中的模块化到底是什么 Java9中的一个重大特性是增加了一种新型的程序设计组件 - 模块. 官方对模块的定义为:一个被命名的,代码和数据的自描述集合.( the module, whi ...
- Java的重载以及与重写的区别
一.什么是方法重载 方法的重载就是在同一个类中,有着若干个名字相同的方法.在具体调用这些方法的时候,通过传递参数的不同来调用这些重载方法. 二.为什么需要方法重载 方法名的定义需要做到见名知意,功能类 ...
- Java有没有goto?
goto是Java中的保留字,暂时还不是Java的关键字.
- Java 中如何将字符串转换为整数?
String s="123"; int i; 第一种方法:i=Integer.parseInt(s); 第二种方法:i=Integer.valueOf(s).intValue();
- Spring-boot-菜鸟-配置-简介
SpringBoot使用一个全局的配置文件,配置文件名是固定的: •application.properties •application.yml 配置文件的作用:修改SpringBoot自动配置的默 ...