안드로이드 Selector에서 Custom States 만들기


안드로이드의 강력한 기능중  Selector States기능있다. 특정한 상태에 따른 리소스를 변경해준다. 예를 들어 버튼을 누를때의 색상과 포커스를 받았을때의 색상을 각각의 상태에 따라 다르게 처리할 수있다. 이것을 이용하면 Drawable또는 Color를 직접적으로 바꾸지 않고 자동으로 바뀌도록 관리 할 수 있다.

안드로이드에서는 기본적으로 다양한 상태를 기본으로 지원 해주고 있다. 흔하게 쓰이는 것은 다음과 같다.

  • android:state_pressed 버튼을 터치하는 시점
  • android:state_focused 트렉패드나 키보드로 포커스가 왔을때
  • android:state_selected 선택되어 있을때
  • android:state_checkable 체크가능한 상태에서 터치
  • android:state_checked 체크된 상태에서 터치

다음과 같이 color값을 만들어 사용 할 수 있다.

<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="true"
          android:color="#ffff0000"/> <!-- pressed -->
    <item android:state_focused="true"
          android:color="#ff0000ff"/> <!-- focused -->
    <item android:color="#ff000000"/> <!-- default -->
</selector>

버튼의 textColor에 위에서 만든 리소스를 지정한다.

<Button
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/button_text"
    android:textColor="@color/button_text" />

하지만 모든 레이아웃과 뷰에서는 기능들이 제한적으로 작동한다. 예를 들어 android:state_checkable이나 android:state_selected는 레이아웃에서 작동하지 않는다. android:state_checkable을이용 하기위해서는 뷰나 레이아웃을 상속받아 Checkable을 직접 구현해야 한다.

그럼 간단한 예를 들어보자. LinearLayout은 선택된 상태(state_selected)를 지원하지않는다. 이에 반해 Button은 지원하는데, LinearLayout도 지원하게 만들어보자.

package com.example.testcode;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.LinearLayout;

public class SelectableLinearLayout extends LinearLayout {

    private static final int[] STATE_SELECTED = { android.R.attr.state_selected };

    private boolean mIsSelected = false;

    public SelectableLinearLayout(Context context) {
        super(context);
    }

    public SelectableLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public int[] onCreateDrawableState(int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);

        if (mIsSelected)
            mergeDrawableStates(drawableState, STATE_SELECTED);

        return drawableState;
    }

    @Override
    public void setSelected(boolean selected) {
        super.setSelected(selected);

        if (mIsSelected != selected) {
            mIsSelected = selected;
            invalidate();
            refreshDrawableState();
        }
    }

}

 

코드는 의외로 간단하다. setSelected가 호출되면 refreshDrawableState()를 호출하게하여 mergeDrawableStates에 상태를 추가하면 된다. 이렇게 구현하면 레이아웃도 android:state_selected를 이용할 수 있게 된다.

LinearLayout에 state_selected기능을 넣게 되면 탭메뉴를 구현시 좀 더 쉽게 구현이 가능하다. 상태에 따라 백그라운드를 변경 하는것보다 레이아웃의 setSelected(boolean)메서드를 이용해서 state를 변경하는것이 훨씬 깔끔하다.

이렇게 안드로이드에서 레이아웃과 뷰에서 state를 지원하지 않아도 직접 구현해서 사용해도되고, 기본으로 제공되는 state외 custom으로도 만들수 있다.

http://sriramramani.wordpress.com/2012/11/17/custom-states 에서 custom으로 만든것을 참고하겠다.

1. 사용 할 state 이름을 attrs에 정의. (res/values/attrs.xml)

<resources>
    <declare-styleable name="PrivateBrowsing">
        <!-- name of the attribute that will be used in XML files -->
        <attr name="state_private_mode" format="boolean"/>
    </declare-styleable>
</resources>

2. attrs 정의한이름을 selector에서 사용하여 컬러값 지(res/drawable/bg.xml)

<selector xmlns:android="http://schemas.android.com/apk/res/android"
          xmlns:sirius="http://schemas.android.com/apk/res/com.sriramramani.just.another.app">
 
    <!-- custom mode -->
    <item sirius:state_private_mode="true" android:color="_some_dark_color_" />
 
    <!-- normal mode -->
    <item android:color="_some_light_color_"/>
 
</selector>

3. Button을 상속받아 state_private_mode를 사용가능하도록 메서드를 만들어 사용시 mergeDrawableStates에 상태를 추가하여 정의된 색상으로 변경되게 한다.

public class PrivateModeButton extends Button {
    private static final int[] STATE_PRIVATE_MODE = { R.attr.state_private_mode };
 
    private boolean mIsPrivate = false;
 
    public PrivateModeButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
 

    @Override
    public int[] onCreateDrawableState(int extraSpace) {
        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);

        if (mIsPrivate)
            mergeDrawableStates(drawableState, STATE_PRIVATE_MODE);
 
        return drawableState;
    }
 
    public void setPrivateMode(boolean isPrivate) {
        if (mIsPrivate != isPrivate) {
            mIsPrivate = isPrivate;
            refreshDrawableState();
        }
   }
}

안드로이드에서 제공되는 States뿐만 아니라  커스텀으로도 구성 할 수 있다. 이렇게 States를 이용하면 리소스를 코드에서 관리하지 않기 때문에 좀 더 명확한 코드가 될 것이다.