细谈Android 中的attributes 属性标志

96
Jomeslu
2016.08.21 00:23* 字数 975

本文将讲解在attributes位标志的实现方式,总结位标志符的开发思想。本文从位标识符的基础、分析Android中 attributes中的flag实现、总结进行讲解。

在Android开发中,应该很常用attribute属性, 平时自己写layout 或者view的时候一般会这么写的

attribute = "option1" | "option2"

这个 | 符号 不仅仅是个 option 的分离器,它还是一个运算符,将两个option值合并在一起。

下面 我讲讲 什么是位标识符(Bit Flags) 以及它在我们自定义xml属性时系统是如何解析的。

位标志(Bit Flags)基础

一般来说,位标志为数字,代表存储在单一的布尔值。
在二进制的系统中,一个位有两种状态: 开和关。通常我们用1和0表示。我们可以通过一组的位标志组合,存储不同的状态,获取不同的值。

我们读取下面一组状态

   110

我们是从右到左读取的顺序, 第一位 0 ,低电平,这意味着是关闭。第二位第三位为1 高电平,表示开启。那么它的值是多少? 每个整数的值在二进制系统中都会有一组状态值表示,反之亦然。运算规则以下所示

0 = 0*2³ + 0*2² + 0*2¹ + 0*2⁰ = 0000
1 = 0*2³ + 0*2² + 0*2¹ + 1*2⁰ = 0001
2 = 0*2³ + 0*2² + 1*2¹ + 0*2⁰ = 0010
4 = 0*2³ + 1*2² + 0*2¹ + 0*2⁰ = 0100
8 = 1*2³ + 0*2² + 0*2¹ + 0*2⁰ = 1000

算出110的值为4+2+0=6。那么我们想想,是不是可以把每一个位标志(Bit Flag )的值作为一个整数来存储,他们的运算通过位运算符来运算。

讲了基础后,我们讲讲本文的重点。

attributes flag分析

我们用例子来讲解,比如 我们现在要自定义个View ,那么这个自定义View 需要绘制 一个边框,通过使用XML的flag属性来确定它的具体位置(top 、right、bottom、left)。下面讲讲 flages是如何工作的。

首先在values/attrs.xml文件里定义一个新的属性,代码如下

<resources>
   <declare-styleable name="MyView">
       <attr name="drawBorder">
           <flag name="none" value="0" />
           <flag name="top" value="1" />
           <flag name="right" value="2" />
           <flag name="bottom" value="4" />
           <flag name="left" value="8" />
           <flag name="all" value="15" />
       </attr>
       ...
   </declare-styleable>
</resources>

为每一个属性设定一个固定的值,基本选项为(top 、right、bottom、left),另外两项分别是不选或者是全选。细心的同学可能会有疑问,值为什么这么设置(0、1、2、4、8、15)。假如我在xml中选择4个值(top、right、bottom、left) 那么在二进制系统中,跟all的属性值对应的上
看看下图的对应关系

jomeslu.png

看看我们之前自定义的View

   <cn.androidblog.MyView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:drawBorder="bottom|top" />

看看自定义View的代码,里面定义option的常量值,在xml设置flag,通过TypedArray 中的 getInteger可以获取。如代码所示

public MyView extends View{
   //  flags 值
   private static final int BORDER_NONE_DEFAULT = 0;
   private static final int BORDER_TOP = 1;
   private static final int BORDER_RIGHT = 2;
   private static final int BORDER_BOTTOM = 4;
   private static final int BORDER_LEFT = 8;
   // 当前值
   private int mDrawBorder = BORDER_NONE_DEFAULT;
   public MyView(Context context){
       this(context, null);
   }
   public MyView(Context context, AttributeSet attrs){
       this(context, attrs, 0); 
   }
   public MyView(Context context, AttributeSet attrs,
           int defStyleAttr){
       super(context, attrs, defStyleAttr);
       // 读取 attributes
       TypedArray a = context.getTheme().obtainStyledAttributes(
               attrs, R.styleable.PieChart);
       try {
           mDrawBorder = a.getInteger(
                   R.styleable.MyView_drawBorder,
                   BORDER_NONE_DEFAULT);
       } finally {
           a.recycle();
       }
   }
   ...
}
java```


我们可以通过已经定义好的常量值,通过位运算计算出 我们在xml文件里选择了哪个选项。
简单介绍位运算的例子:

| (按位逻辑或运算)
Example: 100 | 001 = 101
只要一个标志位有1,运算结果肯定是1。

& (按位逻辑与运算)
Example: 100 & 101 = 100
只有两个标志位同时为1,运算结果才为1

~ (取反运算)
Example: ~100 = 011
标志位1变0,0变1

^ (按位异或)
Example: 100^101 = 001
两个标志位相同的为0,不相同的为1

好了,了解基础后,我们看看他是怎么获取到相关的属性值的。下面这个例子虽然很奇怪,但是技术上是正确的。

Example 1: app:drawBorder="none|top"

**分析**: none(0=000) ,top(1=001)。他们之间是用逻辑或运算。那么最终的值合并为(001),也就相当top的效果。

Example 2: app:drawBorder="bottom|all"

**分析**:bottom(3=0011),all(15=1111), 同理得 ,通过逻辑或合并后的值为(1111=15),也就相当于all效果。


通过上面的两个例子,那么我们可以在代码中可以写一些比较有意思的东西了。

检查一个Flag的标志,代码如下:

private boolean containsFlag(int flagSet, int flag){
return (flagSet|flag) == flagSet;
}
// 方法 调用
boolean drawBorderTop = containsFlag(mDrawBorder, BORDER_TOP);

添加一个Flag的标志,代码如下:

private int addFlag(int flagSet, int flag){
return flagSet|flag;
}
// 方法 调用
mDrawBorder = addFlag(mDrawBorder, BORDER_LEFT);

切换一个Flag 代码如下:

private int toggleFlag(int flagSet, int flag){
return flagSet^flag;
}
// 方法 调用
mDrawBorder = toggleFlag(mDrawBorder, BORDER_LEFT);

//分析步骤
110^010 = 100 (二进制)
6 ^ 2 = 4 (十进制)
100^010 = 110 (二进制)
4 ^ 2 = 6 (十进制)

移除一个flag 代码如下:

private int removeFlag(int flagSet, int flag){
return flagSet&(~flag);
}
mDrawBorder = removeFlag(mDrawBorder, BORDER_LEFT);

//分析步骤
110&(~010) = 110&101 = 100 (Binary)
6 &(~ 2 ) = 6 & 5 = 4 (Decimal)


###结语
如果你有很多布尔值,特别是想以某种方式存储时,选择为标志符是个很好的选择。它能帮你代替大量的布尔值,你只需要保存一个整形的值。Android系统源码中大量的才用这样的实现编写方式,使代码更加简洁。好了,写完了。希望对你有帮助。

日记本
Web note ad 1