赞
踩
最近,公司的设计同学提出了一个需求,希望有个工具可以方便他们走查客户端的UI。因为我们项目中有集成DoKit,我就告诉他可以用这个里面的“控件检查”功能。但她使用下来,提出了一些问题:
比如我下面这张图片,我的滑块选中的是“12”,但是默认信息是外层view,需要再点一下右边的箭头按钮才能正常选中。
另外我还发现在Dialog上不能使用,滑块选中的还是Dialog下面的页面。那么基于这些问题,我开始根据我们的需求进行定制化(本篇基于版本3.7.11优化)。
首先复制出相关源码ViewCheckerKit
,ViewCheckDoKitView
,ViewCheckDrawDoKitView
,ViewCheckInfoDoKitView
类,通过DoKit.Builder
里的customKits
方法添加自定义模块。
宽高,字体大小问题其实很简单,只需要将代码里的的px值转为dp就行了。
字体无法像字号,字色这些可以通过TextVew
的方法直接获取。好在我们自定义字体都统一用自定义TextVew
实现的。所以可以将字体的类型值设置进setTag
,然后展示信息时用getTag
取出来。
首先看一下选中view的遍历源码:
private void traverseViews(List<View> viewList, View view, int x, int y) { if (view == null) { return; } int[] location = new int[2]; //相对window的x y view.getLocationInWindow(location); int left = location[0]; int top = location[1]; int right = left + view.getWidth(); int bottom = top + view.getHeight(); // 深度优先遍历 if (view instanceof ViewGroup) { int childCount = ((ViewGroup) view).getChildCount(); if (childCount != 0) { for (int index = childCount - 1; index >= 0; index--) { traverseViews(viewList, ((ViewGroup) view).getChildAt(index), x, y); } } //noinspection DuplicateExpressions if (left < x && x < right && top < y && y < bottom) { viewList.add(view); } } else { //noinspection DuplicateExpressions if (left < x && x < right && top < y && y < bottom) { viewList.add(view); } } }
其实遍历的思路没有问题,深度优先,先处理ViewGroup
最后的view(一般来说就是最上层的view)。但是比如对于FrameLayout
,有时我会加一个加载中的view盖在上面,加载完成后隐藏。这时候不管怎么选,第一个都是这个加载中view。所以可以在遍历前判断一下view当前是否隐藏,隐藏时可以跳过。
另外对于ConstraintLayout
这类layout,里面的子view顺序并不能完全代表view的层级。所以在遍历完后,我还循环了一遍list,找出面积最小的view优先排在第一位。
// 取出最小的view放到最前面,便于查看
int size = Integer.MAX_VALUE;
View minSizeView = null;
for (View view : viewList) {
if (size > view.getHeight() * view.getWidth()) {
size = view.getHeight() * view.getWidth();
minSizeView = view;
}
}
if (minSizeView != null) {
viewList.remove(minSizeView);
viewList.add(0, minSizeView);
}
看下截止目前优化后的效果:
Dialog上之所以不能使用,是因为获取的Window是当前Activity的。
而Dialog或者PopupWindow都有自己的Window。通过Activity拿不到,所以遍历不到Dialog里面的view。
所以这里问题就变为了如何获取Dialog的Window。或者说是Activity上的弹窗。这里我就直接使用FloatingWindow里的getFloatWindowViewByToken方法:
object FloatingWindowManager { fun getFloatWindowViewByToken(activity: Activity): List<View> { val floatView = arrayListOf<View>() try { // 获取目标 Activity 的 decorView val targetDecorView = activity.window.decorView // 获取目标 Activity 的 子窗口的 token val targetSubToken = targetDecorView.windowToken // 拿到 mView 集合,找到目标 Activity 所在的 index 位置 val mView = Window.getViews().map { it }.toList() val targetIndex = mView.indexOfFirst { it == targetDecorView } // 获取 mParams 集合 val mParams = Window.getParams() // 根据目标 index 从 mParams 集合中找到目标 token val targetToken = mParams[targetIndex].token mParams.forEachIndexed { index, params -> val token = params.token // Activity 自身不参与 if (index != targetIndex) { if (token == targetSubToken || token == null || token == targetToken) { // 根据 index 拿到 mView 中的 View floatView.add(mView[index]) } } } } catch (e: Exception) { e.printStackTrace() } return floatView } }
文末参考链接有作者对此方法思路的详细说明,有兴趣的可以看看。
通过这个方法,我们获取了当前Activty上层的所有window,这其中也包括我们的滑块,信息展示框。所以需要过滤掉自身。这里可以简单通过包名处理。
List<View> views = FloatingWindowManager.INSTANCE.getFloatWindowViewByToken(mResumedActivity);
for (View view : views) {
if (!view.getClass().getName().contains("didichuxing")) {
traverseViews(viewList, view, mX, mY);
}
}
到此基本完成了,但此时发现了新的问题,选中的Y坐标有偏差。
滑块放到靠上的位置才能选中中间的按钮。我们看下坐标计算的代码:
这里的处理其实没有问题,getSystemLayoutParams().y
获取滑块的位置,加上一半的自身高度就是中心点位置。然后看有无状态栏修正位置。
Dialog的问题是因为弹出的位置并不是从页面的左上角开始的。而遍历方法traverseViews
使用的是相对Activity window的x, y。
int[] location = new int[2];
//相对window的x y
view.getLocationInWindow(location);
我找到了一张Dialog的window示意图:
两个Window不是一个范围,拿到的坐标也就不能直接使用,所以对于Dialog使用getLocationOnScreen
获取相对于整个屏幕上的坐标位置。
int[] location = new int[2];
view.getLocationOnScreen(location);
最终效果正常。
1.内边距,外边距条件优化。目前源码中需要四边边距都不为0时才会显示:
这样严格的条件会导致很多只有一两边有间距的控件信息无法正常展示,所以这里我将&&
改为 ||
。
2.后面计划将view的圆角,选中色,渐变等信息也进行展示,实现思路和上面处理字体一样。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。