Чи потрібні всі три конструктори для користувацького перегляду Android?


143

Створюючи нестандартний вигляд, я помітив, що багато людей, здається, роблять це так:

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

Перше моє запитання: як щодо конструктора MyView(Context context, AttributeSet attrs, int defStyle)? Я не впевнений, де він використовується, але бачу це у суперкласі. Чи потрібен він мені, і де він використовується?

Є ще одна частина цього питання .

Відповіді:


145

Якщо ви додасте свій звичай Viewіз xmlтакож:

 <com.mypack.MyView
      ...
      />

вам знадобиться конструктор public MyView(Context context, AttributeSet attrs), інакше ви отримаєте, Exceptionколи Android намагатиметься надути вашу View.

Якщо ви додасте Viewвід , xmlа також вказати android:styleатрибут , як:

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

2-й конструктор також буде викликаний та за замовчуванням стиль до MyCustomStyleзастосування явних атрибутів XML.

Третій конструктор зазвичай використовується, коли ви хочете, щоб усі перегляди у вашій програмі мали однаковий стиль.


3
коли потім використовувати перший конструктор?
Android Killer

@OvidiuLatcu, чи можете ви показати приклад третього CTOR (з 3 параметрами)?
андроїд розробник

Чи можу я додати додаткові параметри до конструктора і як я можу їх використовувати?
Мухаммед Субхі Шейх Куроуш

24
Щодо третього конструктора, це насправді абсолютно неправильно . XML завжди викликає конструктор з двома аргументами. Конструктори з трьох аргументів (і чотири аргументи ) викликаються підкласами, якщо вони хочуть вказати атрибут, що містить стиль за замовчуванням, або стиль за замовчуванням безпосередньо (у випадку конструктора з чотирма аргументами)
imgx64

Я лише подав правки, щоб зробити відповідь правильною. Я також запропонував альтернативну відповідь нижче.
mbonnin

118

Якщо ви переможете всі три конструктори, будь ласка, НЕ ВЗАЄМНУЙТЕ this(...)НАЗВИ. Натомість слід робити це:

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
}

Причина полягає в тому, що батьківський клас може містити атрибути за замовчуванням у власних конструкторах, які ви, можливо, випадково перезамените. Наприклад, це конструктор для 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);
}

Якби ви не зателефонували super(context), ви не встановили б належним чином R.attr.textViewStyleстиль attr.


12
Це важлива порада при розширенні ListView. Як (попередній) шанувальник вищевказаного каскаду, я пригадую, що витрачав години на відстеження тонкої помилки, яка пішла, коли я зателефонував за правильним супер методом для кожного конструктора.
Groovee60

BTW @ Jin У цій відповіді я використав код: stackoverflow.com/a/22780035/294884, який, здається, базується на вашій відповіді - але зауважте, що автор включає використання Інфлятора?
Fattie

1
Я думаю, що не обов'язково викликати init у всіх конструкторах, тому що, дотримуючись ієрархії викликів, ви все-таки опинитесь у конструкторі за замовчуванням для створення програмного вигляду View (контекст контексту) {}
Marian Klühspies

я роблю те саме, але не вдалося встановити значення в перегляді тексту, який доступний у моєму власному перегляді. Я хочу встановити значення з діяльності
Erum

1
Як я ніколи цього не знав?
Сурагч

49

MyView (контекст контексту)

Використовується при інстанціюванні переглядів програмно.

MyView (контекст контексту, AttributeSet attrs)

Використовується LayoutInflaterатрибутами xml для застосування. Якщо один з цього атрибута названий style, атрибути будуть шукати стиль, перш ніж шукати явні значення у файлі xml-макета.

MyView (контекст контексту, AttributeSet attrs, int defStyleAttr)

Припустимо, ви хочете застосувати стиль за замовчуванням до всіх віджетів, не вказуючи styleв кожному файлі макета. Для прикладу зробіть усі прапорці рожевими за замовчуванням. Це можна зробити за допомогою defStyleAttr, і фреймворк знайде стиль за замовчуванням у вашій темі.

Зауважте, що це defStyleAttrбуло неправильно названо defStyleдеякий час тому, і є певна дискусія щодо того, чи потрібен цей конструктор чи ні. Див. Https://code.google.com/p/android/isissue/detail?id=12683

MyView (контекст контексту, AttributeSet attrs, int defStyleAttr, int defStyleRes)

3-й конструктор добре працює, якщо ви маєте контроль над базовою темою додатків. Це працює для google, оскільки вони доставляють свої віджети поряд із Темою за замовчуванням. Але припустимо, що ви пишете бібліотеку віджетів, і ви хочете встановити стиль за замовчуванням, без того, щоб ваші користувачі потребували налаштування теми. Тепер це можна зробити за defStyleResдопомогою встановлення його за замовчуванням у двох перших конструкторах:

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();
}

Загалом

Якщо ви реалізуєте власні погляди, потрібні лише два перших конструктори, які можуть бути викликані рамкою.

Якщо ви хочете, щоб ваш погляд був розширюваним, ви можете реалізувати 4-й конструктор для дітей вашого класу, щоб мати можливість використовувати глобальний стиль.

Я не бачу справжнього випадку використання для 3-го конструктора. Може бути ярлик, якщо ви не надаєте стиль віджету за замовчуванням, але все ж хочете, щоб ваші користувачі могли це зробити. Не повинно відбуватися так багато.


7

Котлін, здається, забирає багато цього болю:

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

@JvmOverloads генерує всі необхідні конструктори (див. Документацію анотації ), кожен з яких, імовірно, називає супер (). Потім просто замініть метод ініціалізації блоком Kotlin init {}. Код котла відпав!


1

Третій конструктор набагато складніший. Дозвольте мені взяти приклад.

SwitchCompactПакет Support-v7 підтримує thumbTintі trackTintатрибут з 24 версії, а 23 версія не підтримує їх. Тепер ви хочете підтримати їх у 23 версії і як ви будете робити це для досягнення?

Ми припускаємо використовувати спеціальні SupportedSwitchCompactрозширення перегляду 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();
}

Це традиційний стиль коду. Зауважте, ми передаємо тут 0 до третього параму . Запускаючи код, ви getThumbDrawable()завжди знайдете нульове значення, як це дивно, тому що метод getThumbDrawable()- це метод його суперкласу SwitchCompact.

Якщо перейти R.attr.switchStyleдо третього параму, все пройде добре. Так чому?

Третя парама - простий атрибут. Атрибут вказує на ресурс стилю. У наведеному вище випадку система знайде switchStyleатрибут у поточній темі, на щастя, система його знайде.

У frameworks/base/core/res/res/values/themes.xml, ви побачите:

<style name="Theme">
    <item name="switchStyle">@style/Widget.CompoundButton.Switch</item>
</style>

-2

Якщо вам доведеться включити три конструктори, як той, що обговорюється зараз, ви можете зробити це теж.

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();

}

2
@Jin Це гарна ідея у багатьох випадках, але це також безпечно у багатьох випадках (наприклад: RelativeLayout, FrameLayout, RecyclerView тощо). Отже, я б сказав, що це, мабуть, конкретна вимога, і базовий клас слід перевірити, перш ніж приймати рішення каскадувати чи ні. По суті, якщо 2-парам-конструктор у базовому класі просто викликає це (контекст, attrs, 0), то це безпечно робити і в користувацькому класі перегляду.
ejw

@IanWong, звичайно, буде називатися, тому що перший і другий методи викликають третій.
CoolMind
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.