联系我们
简单又实用的WordPress网站制作教学
当前位置:网站首页 > 程序开发学习 > 正文

DoKit控件检查功能优化

作者:小教学发布时间:2023-09-24分类:程序开发学习浏览:78


导读:最近,公司的设计同学提出了一个需求,希望有个工具可以方便他们走查客户端的UI。因为我们项目中有集成DoKit,我就告诉他可以用这个里面的“控件检查”功能。但她使用下来,提出了一些问...

最近,公司的设计同学提出了一个需求,希望有个工具可以方便他们走查客户端的UI。因为我们项目中有集成DoKit,我就告诉他可以用这个里面的“控件检查”功能。但她使用下来,提出了一些问题:

  • 看到的控件宽高,字体大小都是具体的像素值,不同设备也不一样,不直观。希望可以看到dp值。
  • 无法看出具体的字体。因为项目中的文字都是自定义字体,比如我是看不出bold和black字体的区别。其实这点还好,因为设计都能看出来,只是有的话更方便。
  • 选中不准,有时滑块放在文字上面,但是需要切换几次才能选中。

比如我下面这张图片,我的滑块选中的是“12”,但是默认信息是外层view,需要再点一下右边的箭头按钮才能正常选中。

请添加图片描述

另外我还发现在Dialog上不能使用,滑块选中的还是Dialog下面的页面。那么基于这些问题,我开始根据我们的需求进行定制化(本篇基于版本3.7.11优化)。

1.宽高问题

首先复制出相关源码ViewCheckerKit,ViewCheckDoKitView,ViewCheckDrawDoKitView,ViewCheckInfoDoKitView类,通过DoKit.Builder里的customKits方法添加自定义模块。

宽高,字体大小问题其实很简单,只需要将代码里的的px值转为dp就行了。

2.字体问题

字体无法像字号,字色这些可以通过TextVew的方法直接获取。好在我们自定义字体都统一用自定义TextVew实现的。所以可以将字体的类型值设置进setTag,然后展示信息时用getTag取出来。

3.选中问题

首先看一下选中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);
}

看下截止目前优化后的效果:

请添加图片描述

4.Dialog问题

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);

最终效果正常。

请添加图片描述

5.其他

1.内边距,外边距条件优化。目前源码中需要四边边距都不为0时才会显示:

在这里插入图片描述
这样严格的条件会导致很多只有一两边有间距的控件信息无法正常展示,所以这里我将&& 改为 ||

2.后面计划将view的圆角,选中色,渐变等信息也进行展示,实现思路和上面处理字体一样。


参考

  • Android对话框Dialog,PopupWindow,Toast的实现机制
  • 如何判断 Activity 上有弹窗




程序开发学习排行
最近发表
网站分类
标签列表