Нужны ли все три конструктора для пользовательского представления Android?

При создании пользовательского представления я заметил, что многие люди, кажется, делают это так:

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)? Я не уверен, где это используется, но я вижу это в суперклассе. Нужно ли это, и где это используется?

Есть еще одна часть этого вопроса.

6 ответов

Решение

Если вы добавите свой кастом 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.

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

Если вы переопределяете все три конструктора, пожалуйста, НЕ КАСКАД 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 как стиль

MyView(контекстный контекст)

Используется при создании экземпляров Views программно.

MyView(контекстный контекст, атрибуты AttributeSet)

Используется LayoutInflater применять атрибуты XML. Если один из этого атрибута назван style, атрибуты будут искать стиль, прежде чем искать явные значения в XML-файле макета.

MyView(контекстный контекст, атрибуты AttributeSet, int defStyleAttr)

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

Обратите внимание, что defStyleAttr был неправильно назван defStyle Некоторое время назад и идет дискуссия о том, действительно ли нужен этот конструктор или нет. См. https://code.google.com/p/android/issues/detail?id=12683

MyView(контекстный контекст, атрибуты AttributeSet, int defStyleAttr, int defStyleRes)

Третий конструктор работает хорошо, если у вас есть контроль над основной темой приложений. Это работает для Google, потому что они отправляют свои виджеты вместе с темами по умолчанию. Но предположим, что вы пишете библиотеку виджетов и хотите, чтобы стиль по умолчанию был установлен без того, чтобы ваши пользователи не настраивали свою тему. Теперь вы можете сделать это с помощью defStyleRes установив его в значение по умолчанию в 2 первых конструкторах:

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

В общем

Если вы реализуете свои собственные представления, вам понадобятся только 2 первых конструктора, которые могут быть вызваны средой.

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

Я не вижу реального варианта использования 3-го конструктора. Может быть, ярлык, если вы не предоставляете стиль по умолчанию для своего виджета, но по-прежнему хотите, чтобы ваши пользователи могли это делать. Так не должно быть.

Котлин, кажется, убрал много этой боли:

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

@JvmOverloads сгенерирует все необходимые конструкторы (см. Документацию к этой аннотации), каждый из которых предположительно вызывает super(). Затем просто замените ваш метод инициализации на блок Kotlin init {}. Код Boilerplate исчез!

Третий конструктор намного сложнее. Позвольте мне привести пример.

Опорно-v7 SwitchCompact пакет поддерживает 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>

Если вам нужно включить три конструктора, такие как обсуждаемый сейчас, вы можете сделать это тоже.

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

}
Другие вопросы по тегам