「Do.023」为啥用XML定义的虚线显示成了实线

栏目: Android · 发布时间: 5年前

内容简介:今天刚好是《千与千寻》在中国首映的日子。所以放一张《千与千寻》的海报,我也没想到过去这么久了,其实都都已经看过很多遍了。但还是想要在大屏幕上再看一次。小时候看动画片,妈妈会说,这动画片有什么好看的,你能看一辈子呀!真是一语成真,估计这辈子是逃不出动漫的坑了。(杠精就别纠结动画片和动漫的不同了,在妈妈眼里,那都是一样一样的!),好了,娱乐休闲之前先学习一波儿!前几天做一个界面的时候,再一次出现了虚线显式成实现的问题,我没出息的又去搜解决方案了,为了记忆深刻,狠了狠心,深挖了一下。一起来看看吧:我在xml布局文

今天刚好是《千与千寻》在中国首映的日子。所以放一张《千与千寻》的海报,我也没想到过去这么久了,其实都都已经看过很多遍了。但还是想要在大屏幕上再看一次。小时候看动画片,妈妈会说,这动画片有什么好看的,你能看一辈子呀!真是一语成真,估计这辈子是逃不出动漫的坑了。(杠精就别纠结动画片和动漫的不同了,在妈妈眼里,那都是一样一样的!),好了,娱乐休闲之前先学习一波儿!

正文

前几天做一个界面的时候,再一次出现了虚线显式成实现的问题,我没出息的又去搜解决方案了,为了记忆深刻,狠了狠心,深挖了一下。一起来看看吧:

我在xml布局文件中通过给一个View设置一个背景画了一条虚线分割线代码如下:

<View
    android:id="@+id/view3"
    android:layout_width="match_parent"
    android:layout_height="2dp"
    android:background="@drawable/common_line_draw_dash"/>
复制代码

view中引用的 common_line_draw_dash.xml 代码如下:

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="line">
    <stroke
        android:width="1dp"
        android:color="#DDDDDD"
        android:dashGap="2dp"
        android:dashWidth="4dp"/>
</shape>
复制代码

可是设备上的实际效果也预期效果并不一致,见下图:

「Do.023」为啥用XML定义的虚线显示成了实线

可能很多开发小伙伴都碰到过这个问题,也知道只需要在View的xml布局中添加如下一行代码即可解决问题。 android:layerType="software"

古话说“知其然知其所以然”

从谷歌官方提供的关于硬件加速的资料显示:

从Android 3.0(API级别11)开始,Android 2D渲染管道支持硬件加速,这意味着在View的画布上执行的所有绘图操作都使用GPU。 由于启用硬件加速所需的资源增加,您的应用程序将消耗更多RAM。

并且文章中还讲到Android 4.0(API级别14)开始默认开启了硬件加速;但是

再往上找了一些关于硬件加速的文章比如这篇《关于硬件加速那点事儿》 地址: www.jianshu.com/p/9cd7097a4… 这是一篇Android硬件加速官方文章的译文 里面介绍了不支持硬件加速的一些操作:

「Do.023」为啥用XML定义的虚线显示成了实线

在里面看了一下之后就去扫View的源码了,果然不出五分钟就找了上图中倒数第三的方法saveLayer();

「Do.023」为啥用XML定义的虚线显示成了实线

看到 background.draw(canvas) 方法时,你可能想问我怎么知道这个虚线xml文件,最后实例化之后的Drawble对象对应的GradientDrawble的对象。看下面这张图,你就明白了:

「Do.023」为啥用XML定义的虚线显示成了实线

按照平常,文章写道这里就已经结束了,一切都已经说通了。但是作为一名程序员,严谨是一种良好职业操守,于是我又去了google官网看了英文原版关于硬件加速的介绍。 地址: developer.android.com/guide/topic…

「Do.023」为啥用XML定义的虚线显示成了实线

然后这一看,就看出问题了。当我看到上面这张图的时候,我特么就尴尬了。怎么和之前我看到的那张图不一样了?saveLayer方法支持硬件加速?还是不支持?后来又用google搜索了一些资料,确实没有一项有力的证件证明saveLayer方法是不支持硬件加速的;

既然这样我们就需要重新去须按照其他的证据来证明 View在画虚线时,无法使用硬件加速 ,虽然上面图中找到的 saveLayer 方法不能证明是需要关闭硬件加速的原因。但是这个View绘制背景的流程是没有问题的,并且我们知道用标签定义的xml文件在膨胀后得到的是一个GradientDrawable对象。

既然在绘制背景流程中没有可疑的不支持硬件加速的方法,那么在生成Drawable对象的过程是否能找得到一些可疑的方法呢?上面流程图中的mBackground对象又是怎么来的?

带着这两个问题,思考了一下。这个View是通过xml膨胀生成的,那么应该会调用View的2各参数或3个参数的构造方法,然后我就顺着这思路找了一下mBackground对象是怎么生成:

「Do.023」为啥用XML定义的虚线显示成了实线

一路找下去找到了,上图中蓝色框部分的代码

final Drawable.ConstantState cs;
    if (isColorDrawable) {
        cs = sPreloadedColorDrawables.get(key);
    } else {
        cs = sPreloadedDrawables[mConfiguration.getLayoutDirection()].get(key);
    }
    Drawable dr;
    boolean needsNewDrawableAfterCache = false;
    if (cs != null) {
        if (TRACE_FOR_DETAILED_PRELOAD) {
            // Log only framework resources
            if (((id >>> 24) == 0x1) && (android.os.Process.myUid() != 0)) {
                final String name = getResourceName(id);
                if (name != null) {
                    Log.d(TAG_PRELOAD, "Hit preloaded FW drawable #"
                            + Integer.toHexString(id) + " " + name);
                }
            }
        }
        dr = cs.newDrawable(wrapper);
    } else if (isColorDrawable) {
        dr = new ColorDrawable(value.data);
    } else {
        dr = loadDrawableForCookie(wrapper, value, id, density, null);
    }
复制代码

然后大概可以看明白当cs不为null时,Drawable是通过cs.newDrawable()方法生成的。cs在上面代码中的第一行已经定义了,它是一个Drawable的静态内部类Drawable.ConstantState;

既然前面我们已经知道用标签定义的虚线XML文件膨胀后是一个GradientDrawable对象。那么GradientDrawable也应该有自己的ConstantState内部类。点进GradientDrawable的源码拉到底部,果然就看到了一个继承自ConstantState的GradientState类。借着又是一招顺藤摸瓜:

「Do.023」为啥用XML定义的虚线显示成了实线

GradientState类重写了ConstantState的newDrawable方法,在该方法中通过调用GradientDrawable的构造方法构建了一个GradientDrawable实例。

在GradientDrawable构造方法中则进行了一些初始动作,其中调用的updateLocalState方法中的一段代码(上图蓝色框)引起了我注意,最引人注目的还是那段红色框里的的方法。这个方法好像在不支持硬件加速的操作表中。喜极而泣!明了,明了...。

这里再粘一下这段代码:

if (state.mStrokeDashWidth != 0.0f) {
    final DashPathEffect e = new DashPathEffect(
        new float[] { state.mStrokeDashWidth, state.mStrokeDashGap }, 0);
    mStrokePaint.setPathEffect(e);
}
复制代码

它判断了StrokeDashWidth是否有值,如果有这则根据虚线的的两个重要属性 state.mStrokeDashWidthstate.mStrokeDashGap 构造一个DashPathEffect对象,然后在通过Paint的setPathEffect(e)方法来绘制虚线。

到这里一切都水落石出了。之所以虚线会在大部分手机上绘制成实现是因为就是因为Paint的setPathEffect()方法不支持硬件加速。其实通过上表可以看到,在Android 9(API级别28)以后,就可以不用关闭硬件加速也可以绘制XML定义的虚线了。

后记

好了这篇文章就写到这里吧,大家在追源码的时候给大家一个提醒:

晚上10点以后不宜阅读源,因为你根本停不下来。[Facepalm][Facepalm][Facepalm]

虽然有些滑稽,但却是真是的哈哈。

另外,如果你是初学者,到没有必要花费太多精力去探索。毕竟初学者完成一个App,对知识和技术的广度认知比深度要重要的多。但是到了一定时候(我也说不清是什么时候,你自己应该会有感觉)——我个人觉得是做了两三年吧,当你遇到问题时,就不应仅仅只停留在解决问题的层面,还应该深入了解一下为什么。哪怕一开始你就知道导致这个问题的原因是某个方法。但是找出这个方法,这一探索,研究的过程才是这个阶段你成长的最大助力。与君共勉!

如果有想法可以留言;觉得有帮助,还请右下角**「在看」**走一波!


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Effective JavaScript

Effective JavaScript

David Herman / Addison-Wesley Professional / 2012-12-6 / USD 39.99

"It's uncommon to have a programming language wonk who can speak in such comfortable and friendly language as David does. His walk through the syntax and semantics of JavaScript is both charming and h......一起来看看 《Effective JavaScript》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具