Cara menambahkan status tombol khusus

132

Misalnya, tombol default memiliki dependensi berikut antara status dan gambar latar belakangnya:

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

Bagaimana saya bisa mendefinisikan status kustom saya sendiri (seperti apa android:state_custom), sehingga saya bisa menggunakannya untuk secara dinamis mengubah tampilan visual tombol saya?

Vit Khudenko
sumber
Saya ingin status tambahan untuk tampilan EditText untuk menentukan kapan dua kotak kata sandi cocok untuk menunjukkan tanda centang kecil.
Nathan Schwermann

Jawaban:

275

Solusi yang ditunjukkan oleh @ (Ted Hopp) berfungsi, tetapi membutuhkan sedikit koreksi: di pemilih, status item memerlukan awalan "app:", jika tidak, inflater tidak akan mengenali namespace dengan benar, dan akan gagal secara diam-diam; setidaknya inilah yang terjadi pada saya.

Izinkan saya melaporkan di sini seluruh solusi, dengan beberapa perincian lebih lanjut:

Pertama, buat file "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>

Kemudian tentukan kelas khusus Anda. Misalnya, itu mungkin kelas "FoodButton", berasal dari kelas "Tombol". Anda harus mengimplementasikan konstruktor; mengimplementasikan yang ini, yang tampaknya menjadi yang digunakan oleh inflater:

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

Di atas kelas turunan:

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

Juga, variabel status Anda:

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

Dan beberapa setter:

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

Kemudian timpa fungsi "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;
}

Akhirnya, bagian paling rumit dari teka-teki ini; pemilih mendefinisikan StateListDrawable yang akan Anda gunakan sebagai latar belakang untuk widget Anda. Ini adalah file "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>

Perhatikan awalan "app:", sedangkan dengan status android standar Anda akan menggunakan awalan "android:". Namespace XML sangat penting untuk interpretasi yang benar oleh inflater dan tergantung pada jenis proyek di mana Anda menambahkan atribut. Jika itu adalah aplikasi, ganti com.mydomain.mypackage dengan nama paket aplikasi Anda yang sebenarnya (tidak termasuk nama aplikasi). Jika perpustakaan, Anda harus menggunakan "http://schemas.android.com/apk/res-auto" (dan menggunakan Tools R17 atau yang lebih baru) atau Anda akan mendapatkan kesalahan runtime.

Beberapa catatan:

  • Tampaknya Anda tidak perlu memanggil fungsi "refreshDrawableState", setidaknya solusinya berfungsi dengan baik, dalam kasus saya

  • Untuk menggunakan kelas khusus Anda dalam file xml tata letak, Anda harus menentukan nama yang sepenuhnya memenuhi syarat (mis. Com.mydomain.mypackage.FoodButton)

  • Anda dapat sebagai negara campuran standar (mis. Android: ditekan, android: diaktifkan, android: dipilih) dengan status khusus, untuk mewakili kombinasi keadaan yang lebih rumit

Giorgio Barchiesi
sumber
3
Pembaruan: jika kelas khusus berasal dari TextView, bukan Button, panggilan ke refreshDrawableState tampaknya diperlukan, jika tidak, tampilan widget tidak diperbarui. Panggilan akan dilakukan di setter. Saya belum mencoba kelas lain. Tes dilakukan pada perangkat froyo.
Giorgio Barchiesi
17
Ini refreshDrawableStatejelas penting. Saya tidak begitu yakin kapan itu benar-benar dibutuhkan. Tetapi dalam kasus saya itu diperlukan ketika mengatur negara secara terprogram. Saya kira itu mungkin dipanggil dari kelas View secara otomatis di onTouchEvent. Saya lebih baik menambahkannya dalam metode setSelected.
buergi
1
GiorgioBarchiesi, saya memiliki dua Tombol kustom, dan ketika saya mencoba mengubah status kedua tombol dari acara onClick dari satu tombol, hanya tombol yang diklik akan berubah, saya pikir @buergi benar bahwa metode refreshDrawableState dipanggil di onClickEvent. Terima kasih lagi untuk tutorial Anda yang luar biasa :)
Bolton
2
Tetapi bagaimana Anda bisa menggunakan status khusus yang bukan boolean? Atau apakah penyeleksi hanya beroperasi pada boolean?
Peterdk
2
Bagaimana cara kerjanya? Maksud saya, bagaimana atribut diperbarui untuk menyatakan benar / salah? Siapa yang memperbaruinya? Apakah menggabungkan drawablestate, hanya jika variabel lokal benar, memperbarui status atau nilai atribut? Kode mana yang tepatnya akan memperbarui R.attr.state_fried?
kAmol
10

Utas ini menunjukkan cara menambahkan status khusus ke tombol dan sejenisnya. (Jika Anda tidak dapat melihat grup Google baru di browser Anda, ada salinan utas di sini .)

Ted Hopp
sumber
+1 terima kasih banyak, Ted! Saat ini asal masalah sudah jadi saya tidak sampai ke implementasi yang sebenarnya. Namun jika pelanggan saya kembali ke sini lagi saya akan mencoba cara Anda menunjuk saya.
Vit Khudenko
Tampak persis seperti yang saya butuhkan, namun drawable daftar negara bagian untuk negara kustom saya tidak berubah. Saya pasti melewatkan sesuatu ...
Nathan Schwermann
Apakah Anda memanggil refreshDrawableState ()?
Ted Hopp
Tautan sudah mati.
Mitch
@ Nyonya - Yah, itu terlalu buruk. Saya akan melihat apakah saya dapat menemukan beberapa tautan pengganti. Jika tidak, saya akan menghapus jawaban ini, karena pada dasarnya tidak ada gunanya. Sementara itu, jawaban yang diterima memiliki semua informasi yang dibutuhkan.
Ted Hopp
6

Tolong jangan lupa untuk menelepon di refreshDrawableStatedalam utas UI:

mHandler.post(new Runnable() {
    @Override
    public void run() {
        refreshDrawableState();
    }
});

Butuh banyak waktu saya untuk mencari tahu mengapa tombol saya tidak mengubah kondisinya meskipun semuanya terlihat benar.

Nishant Soni
sumber
di mana atau kapan saya harus memposting penangan ini?
Arthur Melo