本文将讲解在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文件里定义一个新的属性,代码如下
...
为每一个属性设定一个固定的值,基本选项为(top 、right、bottom、left),另外两项分别是不选或者是全选。细心的同学可能会有疑问,值为什么这么设置(0、1、2、4、8、15)。假如我在xml中选择4个值(top、right、bottom、left) 那么在二进制系统中,跟all的属性值对应的上。
看看下图的对应关系
看看我们之前自定义的View
看看自定义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系统源码中大量的才用这样的实现编写方式,使代码更加简洁。好了,写完了。希望对你有帮助。