Apakah saya memerlukan ketiga konstruktor untuk tampilan khusus Android?

142

Saat membuat tampilan khusus, saya perhatikan bahwa banyak orang tampaknya melakukannya seperti ini:

public MyView(Context context) {
  super(context);
  // this constructor used when programmatically creating view
  doAdditionalConstructorWork();
}

public MyView(Context context, AttributeSet attrs) {
  super(context, attrs);
  // this constructor used when creating view through XML
  doAdditionalConstructorWork();
}

private void doAdditionalConstructorWork() {

  // init variables etc.
}

Pertanyaan pertama saya adalah, bagaimana dengan konstruktor MyView(Context context, AttributeSet attrs, int defStyle)? Saya tidak yakin di mana itu digunakan, tetapi saya melihatnya di kelas super. Apakah saya membutuhkannya, dan di mana itu digunakan?

Ada bagian lain dari pertanyaan ini .

Micah Hainline
sumber

Jawaban:

144

Jika Anda akan menambahkan kustom Viewdari xmljuga suka:

 <com.mypack.MyView
      ...
      />

Anda akan memerlukan konstruktor public MyView(Context context, AttributeSet attrs), jika tidak, Anda akan mendapatkan Exceptionketika Android mencoba mengembang Anda View.

Jika Anda menambahkan Viewdari xmldan juga menentukan android:styleatribut seperti:

 <com.mypack.MyView
      style="@styles/MyCustomStyle"
      ...
      />

konstruktor kedua juga akan dipanggil dan default gaya MyCustomStylesebelum menerapkan atribut XML eksplisit.

Konstruktor ketiga biasanya digunakan ketika Anda ingin semua Tampilan dalam aplikasi Anda memiliki gaya yang sama.

Ovidiu Latcu
sumber
3
kapan harus menggunakan konstruktor pertama?
Android Killer
@OvidiuLatcu dapatkah Anda menunjukkan contoh CTOR ketiga (dengan 3 parameter)?
Pengembang android
dapatkah saya menambahkan parameter tambahan ke konstruktor dan Bagaimana cara menggunakannya?
Mohammed Subhi Sheikh Quroush
24
Mengenai konstruktor ketiga, ini sebenarnya sepenuhnya salah . XML selalu memanggil konstruktor dua argumen. Konstruktor tiga argumen (dan empat argumen ) dipanggil oleh subclass jika mereka ingin menentukan atribut yang berisi gaya default, atau gaya default langsung (dalam kasus konstruktor empat argumen)
imgx64
Saya baru saja mengirimkan hasil edit untuk membuat jawaban menjadi benar. Saya juga telah mengusulkan jawaban alternatif di bawah ini.
mbonnin
118

Jika Anda mengesampingkan ketiga konstruktor, mohon JANGAN this(...)PANGGILAN CASCADE . Anda seharusnya melakukan ini:

public MyView(Context context) {
    super(context);
    init(context, null, 0);
}

public MyView(Context context, AttributeSet attrs) {
    super(context,attrs);
    init(context, attrs, 0);
}

public MyView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init(context, attrs, defStyle);
}

private void init(Context context, AttributeSet attrs, int defStyle) {
    // do additional work
}

Alasannya adalah bahwa kelas induk mungkin menyertakan atribut default di konstruktornya sendiri yang mungkin Anda sengaja timpa. Misalnya, ini adalah konstruktor untuk TextView:

public TextView(Context context) {
    this(context, null);
}

public TextView(Context context, @Nullable AttributeSet attrs) {
    this(context, attrs, com.android.internal.R.attr.textViewStyle);
}

public TextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    this(context, attrs, defStyleAttr, 0);
}

Jika Anda tidak menelepon super(context), Anda tidak akan ditetapkan dengan benar R.attr.textViewStylesebagai style attr.

Jin
sumber
12
Ini adalah saran penting ketika memperpanjang ListView. Sebagai penggemar (sebelumnya) dari cascading di atas, saya ingat menghabiskan berjam-jam melacak bug halus yang hilang ketika saya memanggil metode super yang benar untuk setiap konstruktor.
Groovee60
Btw @ jin Saya menggunakan kode dalam jawaban ini: stackoverflow.com/a/22780035/294884 yang tampaknya didasarkan pada jawaban Anda - tetapi perhatikan bahwa penulis termasuk penggunaan Inflator?
Fattie
1
Saya pikir itu tidak perlu untuk memanggil init di semua konstruktor, karena ketika Anda mengikuti hierarki panggilan, Anda akan berakhir di konstruktor default untuk pembuatan tampilan terprogram tetap melihat (Konteks konteks) {}
Marian Klühspies
saya melakukan hal yang sama tetapi gagal untuk menetapkan nilai dalam tampilan teks yang tersedia di tampilan kustom saya, saya ingin menetapkan nilai dari aktivitas
Erum
1
Bagaimana saya tidak pernah tahu ini?
Suragch
49

MyView (Konteks konteks)

Digunakan ketika instanciating Views secara pemrograman.

MyView (Konteks konteks, attret Atribut)

Digunakan oleh LayoutInflateruntuk menerapkan atribut xml. Jika salah satu dari atribut ini dinamai style, atribut akan mencari gaya sebelum mencari nilai eksplisit dalam file tata letak xml.

MyView (Konteks konteks, attrs AtributSet, int defStyleAttr)

Misalkan Anda ingin menerapkan gaya default ke semua widget tanpa harus menentukan styledi setiap file tata letak. Sebagai contoh, buat semua kotak centang berwarna merah muda secara default. Anda dapat melakukan ini dengan defStyleAttr dan kerangka kerja akan mencari gaya default di tema Anda.

Catatan yang defStyleAttrsalah disebutkan defStylebeberapa waktu lalu dan ada beberapa diskusi tentang apakah konstruktor ini benar-benar diperlukan atau tidak. Lihat https://code.google.com/p/android/issues/detail?id=12683

MyView (Konteks konteks, attret AttributeSet, int defStyleAttr, int defStyleRes)

Konstruktor ke-3 berfungsi dengan baik jika Anda memiliki kontrol atas tema dasar aplikasi. Itu bekerja untuk google karena mereka mengirim widget mereka di samping Tema default. Tapi misalkan Anda sedang menulis perpustakaan widget dan Anda ingin mengatur gaya default tanpa pengguna Anda perlu mengubah tema mereka. Anda sekarang dapat melakukan ini defStyleResdengan mengaturnya ke nilai default di 2 konstruktor pertama:

public MyView(Context context) {
  super(context, null, 0, R.style.MyViewStyle);
  init();
}

public MyView(Context context, AttributeSet attrs) {
  super(context, attrs, 0, R.style.MyViewStyle);
  init();
}

Semua seutuhnya

Jika Anda menerapkan pandangan Anda sendiri, hanya 2 konstruktor pertama yang diperlukan dan dapat dipanggil oleh framework.

Jika Anda ingin Tampilan Anda dapat diperluas, Anda dapat menerapkan konstruktor ke-4 untuk anak-anak kelas Anda agar dapat menggunakan gaya global.

Saya tidak melihat kasus penggunaan nyata untuk konstruktor ke-3. Mungkin jalan pintas jika Anda tidak memberikan gaya default untuk widget Anda, tetapi tetap ingin pengguna Anda dapat melakukannya. Seharusnya tidak banyak terjadi.

mbonnin
sumber
7

Kotlin tampaknya menghilangkan banyak rasa sakit ini:

class MyView
@JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0)
    : View(context, attrs, defStyle)

@JvmOverloads akan menghasilkan semua konstruktor yang diperlukan (lihat dokumentasi anotasi itu ), yang masing-masing mungkin memanggil super (). Kemudian, cukup ganti metode inisialisasi Anda dengan blok init {} Kotlin. Kode pelat boiler hilang!

jules
sumber
1

Konstruktor ketiga jauh lebih rumit. Izinkan saya memberi contoh.

Paket dukungan-v7 SwitchCompactmendukung thumbTintdan trackTintatribut sejak 24 versi sementara 23 versi tidak mendukung mereka. Sekarang Anda ingin mendukung mereka dalam versi 23 dan bagaimana Anda akan mencapai ini?

Kita asumsikan menggunakan kustom View SupportedSwitchCompactmeluas SwitchCompact.

public SupportedSwitchCompat(Context context) {
    this(context, null);
}

public SupportedSwitchCompat(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public SupportedSwitchCompat(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    init();
}

private void init(){
    mThumbDrawable = getThumbDrawable();
    mTrackDrawable = getTrackDrawable();
    applyTint();
}

Ini gaya kode tradisional. Perhatikan bahwa kita meneruskan 0 ke param ketiga di sini . Ketika Anda menjalankan kode, Anda akan menemukan getThumbDrawable()selalu mengembalikan null betapa anehnya karena metode getThumbDrawable()ini adalah metode kelas supernya SwitchCompact.

Jika Anda lolos R.attr.switchStyleke param ketiga, semuanya berjalan dengan baik. Jadi mengapa?

Param ketiga adalah atribut sederhana. Atribut menunjuk ke sumber daya gaya. Dalam kasus di atas, sistem akan menemukan switchStyleatribut dalam tema saat ini untungnya sistem menemukannya.

Di frameworks/base/core/res/res/values/themes.xml, Anda akan melihat:

<style name="Theme">
    <item name="switchStyle">@style/Widget.CompoundButton.Switch</item>
</style>
KoXier
sumber
-2

Jika Anda harus menyertakan tiga konstruktor seperti yang sedang dibahas sekarang, Anda dapat melakukannya juga.

public MyView(Context context) {
  this(context,null,0);
}

public MyView(Context context, AttributeSet attrs) {
  this(context,attrs,0);
}

public MyView(Context context, AttributeSet attrs, int defStyle) {
  super(context, attrs, defStyle);
  doAdditionalConstructorWork();

}
arTsmarT
sumber
2
@ Jin Itu ide yang bagus dalam banyak kasus, tetapi ini juga aman dalam banyak kasus (misalnya: RelativeLayout, FrameLayout, RecyclerView, dll.). Jadi, saya akan mengatakan ini mungkin persyaratan kasus per kasus dan kelas dasar harus diperiksa sebelum membuat keputusan untuk kaskade atau tidak. Pada dasarnya, jika konstruktor 2-param di kelas dasar hanya memanggil ini (konteks, attrs, 0), maka aman untuk melakukannya di kelas tampilan kustom juga.
ejw
@ WangWong, tentu saja, itu akan dipanggil, karena metode pertama dan kedua memanggil ketiga.
CoolMind