鸿洋AutoLayout代码分析(五):Attr相关类


回顾

上一节,我们分析了很多类,
其中,最重要的是 AutoLayoutHelper 类的2个方法
adjustChildren()getAutoLayoutInfo(Context context,AttributeSet attrs)
分别 生成 和 使用 AutoLayoutInfo 对象

而我们谈到,AutoLayoutInfo类 是一个容器类
AutoLayoutInfo

public class AutoLayoutInfo
{
    private List<AutoAttr> autoAttrs = new ArrayList<>();
    public void addAttr(AutoAttr autoAttr)
    {
        autoAttrs.add(autoAttr);
    }


    public void fillAttrs(View view)
    {
        for (AutoAttr autoAttr : autoAttrs)
        {
            autoAttr.apply(view);
        }
    }

    @Override
    public String toString()
    {
        return "AutoLayoutInfo{" +
                "autoAttrs=" + autoAttrs +
                '}';
    }
}

存储在 一个 ArrayList中
可以通过 addAttr添加对象
可以通过 fillAttrs(View view) 遍历对象的方法


AutoAttr类

作为容器中的对象,我们看一下 AutoAttr类

public abstract class AutoAttr {
    protected int pxVal;
    protected int baseWidth;
    protected int baseHeight;

    /*
    protected boolean isBaseWidth;
    protected boolean isBaseDefault;

    public AutoAttr(int pxVal)
    {
        this.pxVal = pxVal;
        isBaseDefault = true;
    }

    public AutoAttr(int pxVal, boolean isBaseWidth)
    {
        this.pxVal = pxVal;
        this.isBaseWidth = isBaseWidth;
    }
 */

    public AutoAttr(int pxVal, int baseWidth, int baseHeight) {
        this.pxVal = pxVal;
        this.baseWidth = baseWidth;
        this.baseHeight = baseHeight;
    }

    public void apply(View view) {

        boolean log = view.getTag() != null && view.getTag().toString().equals("auto");

        if (log) {
            L.e(" pxVal = " + pxVal + " ," + this.getClass().getSimpleName());
        }
        int val;
        if (useDefault()) {
            val = defaultBaseWidth() ? getPercentWidthSize() : getPercentHeightSize();
            if (log) {
                L.e(" useDefault val= " + val);
            }
        } else if (baseWidth()) {
            val = getPercentWidthSize();
            if (log) {
                L.e(" baseWidth val= " + val);
            }
        } else {
            val = getPercentHeightSize();
            if (log) {
                L.e(" baseHeight val= " + val);
            }
        }

        val = Math.max(val, 1);//for very thin divider
        execute(view, val);
    }

    protected int getPercentWidthSize() {
        return AutoUtils.getPercentWidthSizeBigger(pxVal);
    }

    protected int getPercentHeightSize() {
        return AutoUtils.getPercentHeightSizeBigger(pxVal);
    }


    protected boolean baseWidth() {
        return contains(baseWidth, attrVal());
    }

    protected boolean useDefault() {
        return !contains(baseHeight, attrVal()) && !contains(baseWidth, attrVal());
    }

    protected boolean contains(int baseVal, int flag) {
        return (baseVal & flag) != 0;
    }

    protected abstract int attrVal();

    protected abstract boolean defaultBaseWidth();

    protected abstract void execute(View view, int val);

    @Override
    public String toString() {
        return "AutoAttr{" +
                "pxVal=" + pxVal +
                ", baseWidth=" + baseWidth() +
                ", defaultBaseWidth=" + defaultBaseWidth() +
                '}';
    }
}

我们可以发现,它是抽象类,那容器中肯定是它的子类

Paste_Image.png

我们在类上, find usages 一下


Paste_Image.png

可以找到17个子类, 加上1个抽象类, 1个纯定义的static final的 Attrs接口
刚好是attr包中的19个类


AutoAttr类简单分析

代码上面已经贴了
大体看看总共方法

Paste_Image.png

3个属性,是通过构造传递
分别是 px值, baseWidth, baseHeight

这里px值,是从上面 LL的 属性数组TypeArray,
for循环,拿到值
(这里包括TypedValue.TYPE_DIMENSION, TypedValue.COMPLEX_UNIT_PX的判断,之前自己都没有接触过,所以这块略。作者应该也查了很多api,或者积累了很多代码量)
通过 AutoLayoutInfo的 addAttr 添加到 容器类中


这里baseWidth, baseHeight是在 AutoLayoutHelper 的getAutoLayoutInfo方法中,通过 TypedArray 的getInt 获取的
通过 AutoLayoutInfo的 addAttr 添加到 容器类中
我们跟一下这个自定义的属性,发现这个 declare-styleable 的 attr 下面是 flag 类型
具体就不多说了(flag这个类型自己之前也没有用过),可以参考
http://googlers.iteye.com/blog/1122585


3个抽象方法

我们先一起看一下对应的3个抽象方法


abstract int attrVal()

我们观察一下子类这个方法的实现
发现,都会返回 Attrs接口中的属性
我们看看 Attrs接口

public interface Attrs
{
    public static final int WIDTH = 1;
    public static final int HEIGHT = WIDTH << 1;
    public static final int TEXTSIZE = HEIGHT << 1;
    public static final int PADDING = TEXTSIZE << 1;
    public static final int MARGIN = PADDING << 1;
    public static final int MARGIN_LEFT = MARGIN << 1;
    public static final int MARGIN_TOP = MARGIN_LEFT << 1;
    public static final int MARGIN_RIGHT = MARGIN_TOP << 1;
    public static final int MARGIN_BOTTOM = MARGIN_RIGHT << 1;
    public static final int PADDING_LEFT = MARGIN_BOTTOM << 1;
    public static final int PADDING_TOP = PADDING_LEFT << 1;
    public static final int PADDING_RIGHT = PADDING_TOP << 1;
    public static final int PADDING_BOTTOM = PADDING_RIGHT << 1;
    public static final int MIN_WIDTH = PADDING_BOTTOM << 1;
    public static final int MAX_WIDTH = MIN_WIDTH << 1;
    public static final int MIN_HEIGHT = MAX_WIDTH << 1;
    public static final int MAX_HEIGHT = MIN_HEIGHT << 1;
}

这里每个变量都是上一个变量的位运算
个人猜测,应该和 自己写的
dispatchTouchEvent事件分发浅析(七)requestDisallowInterceptTouchEvent
中, boolean的Flag判断有关

我们来对比下Attrs接口 和 res/values/attrs.xml中AutoLayout_Layout的attr name="layout_auto_basewidth"的flag值

<declare-styleable name="AutoLayout_Layout">
    <attr name="layout_auto_basewidth">
        <flag name="width" value="1"></flag>
        <flag name="height" value="2"></flag>
        <flag name="textSize" value="4"></flag>
        <flag name="padding" value="8"></flag>
        <flag name="margin" value="16"></flag>
        <flag name="marginLeft" value="32"></flag>
        <flag name="marginTop" value="64"></flag>
        <flag name="marginRight" value="128"></flag>
        <flag name="marginBottom" value="256"></flag>
        <flag name="paddingLeft" value="512"></flag>
        <flag name="paddingTop" value="1024"></flag>
        <flag name="paddingRight" value="2048"></flag>
        <flag name="paddingBottom" value="4096"></flag>
    </attr>

    <attr name="layout_auto_baseheight">
        <flag name="width" value="1"></flag>
        <flag name="height" value="2"></flag>
        <flag name="textSize" value="4"></flag>
        <flag name="padding" value="8"></flag>
        <flag name="margin" value="16"></flag>
        <flag name="marginLeft" value="32"></flag>
        <flag name="marginTop" value="64"></flag>
        <flag name="marginRight" value="128"></flag>
        <flag name="marginBottom" value="256"></flag>
        <flag name="paddingLeft" value="512"></flag>
        <flag name="paddingTop" value="1024"></flag>
        <flag name="paddingRight" value="2048"></flag>
        <flag name="paddingBottom" value="4096"></flag>
        <flag name="minWidth" value="8192"></flag>
        <flag name="maxWidth" value="16384"></flag>
        <flag name="minHeight" value="32768"></flag>
        <flag name="maxHeight" value="65536"></flag>
    </attr>
</declare-styleable>

发现,都是17个属性,对应的值也是一样,并且子类也有17个
每个类的名字,和对应的属性名字也是一样
而 int attrVal() 方法,返回的的值也是 Attrs接口 对应的值


abstract boolean defaultBaseWidth()

我们可以发现对应的值,返回有true, 有false
所以可以简单归纳下这17个类

boolean defaultBaseWidth()
true:
    WidthAttr
    MarginLeftAttr
    MarginRightAttr
    MaxWidthAttr
    MinWidthAttr
    PaddingLeftAttr
    PaddingRightAttr

false:
    HeightAttr
    MarginAttr
    MarginBottomAttr
    MarginTopAttr
    MaxHeightAttr
    MinHeightAttr
    PaddingAttr
    PaddingBottomAttr
    PaddingTopAttr
    TextSizeAttr

我们可以发现,
和 左右,宽 等相关的, 都返回true
和 上下,高 等相关的,都返回false


abstract void execute(View view, int val)

这里17个类的实现都不一样,自己大体分了一下类
其实也挺好理解,我们先看一下分类
宽,高 相关

WidthAttr
HeightAttr
    设置ViewGrou.LayoutParams 对象的 lp.height 和 lp.width = val

Margin相关

MarginAttr
MarginLeftAttr
MarginRightAttr
MarginBottomAttr
MarginTopAttr
    instanceof判断类型后,强转ViewGroup.MarginLayoutParams后,设置对应的方向值
    例如: MarginTopAttr就是设置 lp.topMargin = val 等
            MarginAttr 设置 lp.leftMargin = lp.rightMargin = lp.topMargin = lp.bottomMargin = val

Min,Max宽高相关

MaxWidthAttr
MaxHeightAttr
MinWidthAttr
MinHeightAttr
    通过反射,拿到对象的方法,在执行对象的方法
    Method method = view.getClass().getMethod("方法名", 参数类型.class)
    method.invoke(view, val);

Padding相关

PaddingAttr
PaddingLeftAttr
PaddingRightAttr
PaddingBottomAttr
PaddingTopAttr
    拿到四个方向的Padding值,四个方向获取
    int l = view.getPaddingLeft();
    int t = view.getPaddingTop();
    int r = view.getPaddingRight();
    int b = view.getPaddingBottom();
    
    如果PaddingLeft,l = val;(Padding 则四个方向都为 val)
    依此类推
    最后设置, view.setPadding(l,t,r,b);
    也就是设置属性的方向,用转换后的px值去替换

其他单独

TextSizeAttr
    设置TextSize,px值

boolean类型判断方法

再一起看一下和boolean相关的方法
因为这些方法和逻辑有关, 通常比较简单, 并且价值也比较高

boolean contains(int baseVal, int flag)
    protected boolean contains(int baseVal, int flag) {
        return (baseVal & flag) != 0;
    }

我们看一下,对应的usages


Paste_Image.png

可以发现就2个方法中使用到了,传递的值分别为:


Paste_Image.png

baseHeight或者 baseWidth, 再就是 上面提到的 抽象方法attrVal()


Paste_Image.png

这里 取与,也就是xml中的flag值 和 子类attrVal()的传递值,再某一位上是否有相同的1,如果对应的1都在同一位,(baseVal & flag) != 0 就为 true

boolean useDefault() 和 boolean baseWidth()

上面也说了,这2个方法,其实就只是简单调用了boolean contains(int baseVal, int flag) 的判断

boolean baseWidth()

    protected boolean baseWidth() {
        return contains(baseWidth, attrVal());
    }

这里baseWidth() 只是判断了 传入的 baseWidth和子类attrVal()值是否在同一位上有相同的1

boolean useDefault()

    protected boolean useDefault() {
        return !contains(baseHeight, attrVal()) && !contains(baseWidth, attrVal());
    }

这里useDefault() 只是分别判断了 传入的 baseWidth,baseHeight和子类attrVal()值是否都 没有相同的1


getPercentWidthSize() 和 getPercentHeightSize()

这里就只是简单的计算百分比之后,对应的值
代码比较简单,就一起贴了

    protected int getPercentWidthSize() {
        return AutoUtils.getPercentWidthSizeBigger(pxVal);
    }

    protected int getPercentHeightSize() {
        return AutoUtils.getPercentHeightSizeBigger(pxVal);
    }

这里,分别调用帮助类的静态方法

    public static int getPercentWidthSizeBigger(int val)
    {
        int screenWidth = AutoLayoutConifg.getInstance().getScreenWidth();
        int designWidth = AutoLayoutConifg.getInstance().getDesignWidth();

        int res = val * screenWidth;
        if (res % designWidth == 0)
        {
            return res / designWidth;
        } else
        {
            return res / designWidth + 1;
        }

    }

    public static int getPercentHeightSizeBigger(int val)
    {
        int screenHeight = AutoLayoutConifg.getInstance().getScreenHeight();
        int designHeight = AutoLayoutConifg.getInstance().getDesignHeight();

        int res = val * screenHeight;
        if (res % designHeight == 0)
        {
            return res / designHeight;
        } else
        {
            return res / designHeight + 1;
        }
    }

这里计算也比较简单
宽: 获得 屏幕宽度, design宽度, 按比例计算对应的px值, 能整出就整除,不能整除就+1
高: 获得 屏幕高度, design高度, 按比例计算对应的px值, 能整出就整除,不能整除就+1


void apply(View view)

最后一个方法,也是比较关键的方法,对应的逻辑方法
容器类AutoLayoutInfo添加属性之后,
遍历属性的时候,都需要调用这个方法

Paste_Image.png

void apply(View view)

    public void apply(View view) {

        boolean log = view.getTag() != null && view.getTag().toString().equals("auto");

        if (log) {
            L.e(" pxVal = " + pxVal + " ," + this.getClass().getSimpleName());
        }
        int val;
        if (useDefault()) {
            val = defaultBaseWidth() ? getPercentWidthSize() : getPercentHeightSize();
            if (log) {
                L.e(" useDefault val= " + val);
            }
        } else if (baseWidth()) {
            val = getPercentWidthSize();
            if (log) {
                L.e(" baseWidth val= " + val);
            }
        } else {
            val = getPercentHeightSize();
            if (log) {
                L.e(" baseHeight val= " + val);
            }
        }

        val = Math.max(val, 1);//for very thin divider
        execute(view, val);
    }

首先回顾下
useDefault(): baseWidth,baseHeight和子类attrVal()值是否都 没有相同的1,也就是和设置的属性不一样
baseWidth(): baseWidth 和 子类attrVal() 值是否都 没有相同的1,也就是只是宽的判断
大体就是(伪代码)

- 如果useDefault() 为 true
  - defaultBaseWidth()为true 就  val = getPercentWidthSize()
  - defaultBaseWidth()为false 就  val = getPercentHeightSize()
- 如果baseWidth() 为 true
  - val = getPercentWidthSize()
- 其他
  - val = getPercentHeightSize()

最后,会val = Math.max(val, 1);
防止值特别小的情况
最后执行 抽象方法 execute(view, val);
也就是上面分类的 方法,不同子类不同的实现
ps:
TextSizeAttr 对应的实现是设置 字体大小, 也就是用 AutoLayout为什么字体需要设置为px的原因了


下一篇我们可以了解鸿洋AutoLayout代码分析(六):AutoAttr子类补充

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343

推荐阅读更多精彩内容