1. ホーム
  2. android

[解決済み] カスタムボタンの状態を追加する方法

2022-05-10 16:21:28

質問

例えば、デフォルトのボタンは、その状態と背景画像との間に以下のような依存関係があります。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_window_focused="false" android:state_enabled="true"
        android:drawable="@drawable/btn_default_normal" />
    <item android:state_window_focused="false" android:state_enabled="false"
        android:drawable="@drawable/btn_default_normal_disable" />
    <item android:state_pressed="true" 
        android:drawable="@drawable/btn_default_pressed" />
    <item android:state_focused="true" android:state_enabled="true"
        android:drawable="@drawable/btn_default_selected" />
    <item android:state_enabled="true"
        android:drawable="@drawable/btn_default_normal" />
    <item android:state_focused="true"
        android:drawable="@drawable/btn_default_normal_disable_focused" />
    <item
        android:drawable="@drawable/btn_default_normal_disable" />
</selector>

独自の状態を定義するにはどうすればよいのでしょうか (たとえば android:state_custom のような)独自の状態を定義し、それを使ってボタンの外観を動的に変更することはできますか?

どのように解決するのですか?

(Ted Hopp) によって示された解決策は機能しますが、少し修正が必要です。セレクタにおいて、項目の状態には "app:" というプレフィックスが必要で、さもなければインフレータは名前空間を正しく認識できず、静かに失敗します。少なくとも、これは私に起こったことです。

もう少し詳しく、解決策全体をここで報告することを許可してください。

まず、ファイル "res/values/attrs.xml" を作成します。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="food">
        <attr name="state_fried" format="boolean" />
        <attr name="state_baked" format="boolean" />
    </declare-styleable>
</resources>

次に、カスタムクラスを定義します。例えば、Buttonクラスから派生して、FoodButtonクラスとします。コンストラクタを実装する必要がありますが、これはインフレータが使用するものと思われますので、これを実装してください。

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

派生クラスの上に

private static final int[] STATE_FRIED = {R.attr.state_fried};
private static final int[] STATE_BAKED = {R.attr.state_baked};

また、状態変数も

private boolean mIsFried = false;
private boolean mIsBaked = false;

そして、いくつかのセッター。

public void setFried(boolean isFried) {mIsFried = isFried;}
public void setBaked(boolean isBaked) {mIsBaked = isBaked;}

次に、関数 "onCreateDrawableState"をオーバーライドします。

@Override
protected int[] onCreateDrawableState(int extraSpace) {
    final int[] drawableState = super.onCreateDrawableState(extraSpace + 2);
    if (mIsFried) {
        mergeDrawableStates(drawableState, STATE_FRIED);
    }
    if (mIsBaked) {
        mergeDrawableStates(drawableState, STATE_BAKED);
    }
    return drawableState;
}

最後に、このパズルの最も繊細な部分です。ウィジェットの背景として使用する StateListDrawable を定義するセレクタです。これは、ファイル "res/drawable/food_button.xml"です。

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res/com.mydomain.mypackage">
<item
    app:state_baked="true"
    app:state_fried="false"
    android:drawable="@drawable/item_baked" />
<item
    app:state_baked="false"
    app:state_fried="true"
    android:drawable="@drawable/item_fried" />
<item
    app:state_baked="true"
    app:state_fried="true"
    android:drawable="@drawable/item_overcooked" />
<item
    app:state_baked="false"
    app:state_fried="false"
    android:drawable="@drawable/item_raw" />
</selector>

標準的なアンドロイドの状態では、プレフィックス "android:" を使用していたのに対し、"app:" に注意してください。XML名前空間は、インフレータが正しく解釈するために重要であり、属性を追加するプロジェクトの種類に依存します。アプリケーションの場合は com.mydomain.mypackage をアプリケーションの実際のパッケージ名に置き換えてください(アプリケーション名は除く)。ライブラリの場合は、"http://schemas.android.com/apk/res-auto" を使用する必要があります (また、Tools R17 以降を使用していること)。さもなければ、ランタイム エラーが発生します。

2、3 の注意事項。

  • quot;refreshDrawableState" 関数を呼び出す必要はないようで、少なくとも私の場合、解決策はそのままでうまく動作します。

  • レイアウト xml ファイルでカスタム クラスを使用するには、完全修飾名 (例: com.mydomain.mypackage.FoodButton) を指定する必要があります。

  • より複雑な状態の組み合わせを表現するために、標準の状態(例:android:pressed, android:enabled, android:selected) とカスタム状態を混在させることができます。