Hiểu về Flag Attributes trong Android

Chắc chắn khi làm việc với Android bạn sẽ thấy một cái gì đó tương tự bên dưới:

  attribute="option1|option2"

Hãy chú ý ký tự | giữa các giá trị options. Nó không phải ký tự phân tách ưa thích mà là toán tử bitwise dùng để kết hợp hai options lại thành một giá trị.

Bài viết này sẽ giải thích cho bạn hiểu Bit Flags là cái gì, làm cách nào để khai báo và sử dụng nó trong Android like a boss.

Bit Flags là gì?

Về cơ bản thì bạn có thể thấy Bit Flags là các giá trị boolean (options) được lưu giữ trong một số nguyên duy nhất.

Trong hệ nhị phân, một bit có 2 trạng thái "true" hoặc "false". Và bạn hãy tưởng tượng có 1 dãy các bits mà mỗi bit là một option. Khi mà một option được set thì bit tương ứng với nó có giá trị 1, ngược lại có giá trị 0;

110

Chúng ta sẽ đọc dãy bits trên từ phải sang trái. Bit đầu tiên là 0, điều đó có nghĩa option tương ứng với bit đó không được set. Trong khi đó bit thứ 2 và thứ 3 có giá trị 1 tức là các option tương ứng được set.

Mỗi số nguyên đều có thể được thể hiện trên hệ nhị phân và ngược lại:

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

Điều đó có nghĩa là chúng ta có thể lưu giữ giá trị Bit Flags bằng một số nguyên. Và chúng ta sẽ sử dụng các toán tử bitwise trực tiếp trên các giá trị số nguyên đó.

Khai báo XML Flag Attributes

Giả sử chúng ta muốn tạo ra một customview là MyView và có thể đặt các options vẽ border cho nó. Chúng ta có thể chỉ định cạnh sẽ vẽ border (top, right, bottom, left) trên XML bằng flag attribute.

Trong file values/attrs.xml chúng ta khai báo một attribute mới là drawBoder cho custom view MyView

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

Về bản chất chúng ta chỉ có 4 options (top, right, bottom, left). 2 option còn lại chỉ định không có option nào được set hoặc cả 4 options được set. Giá trị cho 4 options được chọn trước, sao cho khi viết chúng trong hệ nhị phân, thỏa mãi 2 điều kiện dưới đây:

  • Mỗi giá trị có chính xác 1 bit được set thành 1, Tất cả các bits khác là 0
  • Không có 2 giá trị có chung bit được set thành 1.

Option none được dùng để xác định rằng không có option nào trong 4 options trên được set. Option all là tổng của 4 options và xác định rằng tất cả các options được set.

Sau đó chúng ta có thể sử dụng custom flag attribute như sau trong XML:

<my.package.name.MyView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:drawBorder="bottom|top" />

Đọc XML Flag Attributes

Mình giải thích thêm về toán tử dịch bit << hay >>. Giả sử có 1 số nguyên giá trị là 1 trong hệ nhị phân là 001 khi ta dịch trái 1 bit (1<<1) có nghĩa là số nhị phân đổi thành 010. Khi chuyển ngược lại sang số nguyên thì nó là 2. tương tự 1<<2 thành 100 tương đương số 4 trong hệ thập phân. Toán tử dịch bit phải cũng tương tự nhé.

Hãy bắt đầu bằng class cơ bản cho custom view. Chúng ta nên khai báo một constant cho mỗi option bằng với giá trị khai báo phía trên. Và một biến int cho giá trị mà chúng ta lấy được từ XML.

public MyView extends View{
    // Constants for the flags
    private static final int BORDER_NONE_DEFAULT = 0;
    private static final int BORDER_TOP = 1;  //Thường đặt là 1<<0
    private static final int BORDER_RIGHT = 2; // 1<<1
    private static final int BORDER_BOTTOM = 4; //1<<2
    private static final int BORDER_LEFT = 8;  //1<<3
    // Variable for the current value
    private int mDrawBorder = BORDER_NONE_DEFAULT;
    public MyView(Context context){
        // Delegate to next constructor
        this(context, null);
    }
    public MyView(Context context, AttributeSet attrs){
        // Delegate to next constructor
        this(context, attrs, 0); 
    }
    public MyView(Context context, AttributeSet attrs,
            int defStyleAttr){
        super(context, attrs, defStyleAttr);
        // Read attributes
        TypedArray a = context.getTheme().obtainStyledAttributes(
                attrs, R.styleable.PieChart);
        try {
            mDrawBorder = a.getInteger(
                    R.styleable.MyView_drawBorder,
                    BORDER_NONE_DEFAULT);
        } finally {
            a.recycle();
        }
    }
    //...
}

Chúng ta sẽ đọc giá trị được gán trong attribute app:drawBorder vào trong biến mDrawBorder, nếu không có giá trị nào được set trong XML thì giá trị default là BORDER_NONE_DEFAULT

Làm việc với Bit Flags

Đến bây giờ thì làm sao chung ta biết được options nào đã được set khi mà chúng ta chỉ có duy nhất một giá trị mDrawBorder? Đó là lý do tại sao chúng ta định nghĩa các hằng số cho mỗi option.

Chúng ta sẽ sử dụng các toán tử bitwise để kiểm tra xem mDrawBoder có chứa option không và vẽ border tương ứng.

| (bitwise logically or)
Example: 100 | 001 = 101
Kết quả là 1 khi mà một trong 2 bit ở vị trí tương ứng là 1

& (bitwise logically and)
Example: 100 & 101 = 100
Kết quả là 1 khi cả 2 bit ở vị trí tương ứng là 1

~ (bitwise inverse)
Example: ~100 = 011
Tất cả các bits bị đảo ngược. 1 thành 0, 0 thành 1.

^ (bitwise exclusive or)
Example: 100^101 = 001
Kết quả là 1 khi có 1 bit là 1 trong khi bit còn lại ở vị trí đó là 0

Kiểm tra xem một Flag có được set hay không

Bây giờ chúng ta cần kiểm tra xem option nào đã được set vào mDrawBorder

private boolean containsFlag(int flagSet, int flag){
    return (flagSet|flag) == flagSet;
}
// Method call
boolean drawBorderTop = containsFlag(mDrawBorder, BORDER_TOP);

Chúng ta cố gắng add thêm flag cần kiểm tra vào biến chứa các flag đã set và nếu giá trị thu được không thay đổi, điều đó có nghĩa là flag cần kiểm tra đã được set.

Add một Flag

Khá đơn giản, chỉ cần dùng toán tử hoặc

private int addFlag(int flagSet, int flag){
    return flagSet|flag;
}
// Method call
mDrawBorder = addFlag(mDrawBorder, BORDER_LEFT);

Toggle một Flag

Toggle có nghĩa là bật nếu đang tắt hoặc tắt nếu đang bật (lol), dùng toán tử XOR.

private int toggleFlag(int flagSet, int flag){
    return flagSet^flag;
}
// Method call
mDrawBorder = toggleFlag(mDrawBorder, BORDER_LEFT);

Bởi vì XOR chỉ giữ lại các bit mà cùng vị trí nhưng giá trị khác nhau, nó sẽ loại bỏ bit đã set tương ứng flag cần bỏ và giữ các giá trị khác.

Loại bỏ một Flag

Loại bỏ một flag phức tạp hơn một chút, chúng ta cần sử dụng đến 2 toán tử bitwise.

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

Đầu tiên sẽ invert flag cần loại bỏ sau đó dùng toán tử và với flag-set. Có nghĩa là với giá trị cần remove, bit tương ứng giá trị đó được invert thành 0, các bit khác sẽ invert thành 1, sau đó & với flag-set thì bit cần remove sẽ về 0

110&(~010) = 110&101 = 100 (Binary)
 6 &(~ 2 ) =  6 & 5  =  4  (Decimal)

Kết luận

Bit Flags là một thay thế tốt nếu bạn có hàng tá các giá trị boolean và đặc biệt muốn lưu lại giá trị thì thay vì lưu hàng tá các boolean thì chỉ cần lưu duy nhất 1 số nguyên.

Bài dịch từ Flag Attributes in Android — How to Use Them - của tác giả Jakob Ulbrich từ Medium


All Rights Reserved