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


145

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

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

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

Ответы:


146

Если вы добавите свой обычай Viewиз xmlтакже как:

 <com.mypack.MyView
      ...
      />

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

Если вы добавляете свой Viewfrom, xmlа также указываете android:styleатрибут, например:

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

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

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


3
когда тогда использовать первый конструктор?
Android Killer

@OvidiuLatcu, не могли бы вы показать пример третьего CTOR (с тремя параметрами)?
разработчик Android

Могу ли я добавить в конструктор дополнительные параметры и как их использовать?
Мохаммед Субхи Шейх Куруш

25
Что касается третьего конструктора, на самом деле это совершенно неверно . XML всегда вызывает конструктор с двумя аргументами. Конструкторы с тремя аргументами (и с четырьмя аргументами ) вызываются подклассами, если они хотят напрямую указать атрибут, содержащий стиль по умолчанию или стиль по умолчанию (в случае конструктора с четырьмя аргументами)
imgx64

Я только что отправил правку, чтобы ответить правильно. Я также предложил альтернативный ответ ниже.
mbonnin 01

120

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


12
Это важный совет при расширении ListView. Как (предыдущий) поклонник вышеупомянутого каскадирования this, я вспоминаю, как потратил часы на отслеживание тонкой ошибки, которая исчезла, когда я вызвал правильный супер-метод для каждого конструктора.
Groovee60

Кстати @Jin Я использовал код в этом ответе: stackoverflow.com/a/22780035/294884, который, похоже, основан на вашем ответе, но обратите внимание, что автор включает использование Inflator?
Fattie

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

Я делаю то же самое, но не могу установить значения в текстовом представлении, которое доступно в моем пользовательском представлении, я хочу установить значение из активности
Эрум

1
Как я этого не узнал?
Suragch

51

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, установив для него значение по умолчанию в двух первых конструкторах:

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-й конструктор для дочерних элементов вашего класса, чтобы иметь возможность использовать глобальный стиль.

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


7

Котлин, кажется, снимает большую часть этой боли:

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

@JvmOverloads сгенерирует все необходимые конструкторы (см. Документацию к этой аннотации ), каждый из которых предположительно вызывает super (). Затем просто замените свой метод инициализации блоком 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()всегда обнаружите, что возвращает null, как это ни странно, потому что метод 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 и т. Д.). Итак, я бы сказал, что это, вероятно, требование для каждого случая, и базовый класс должен быть проверен, прежде чем принимать решение о каскаде или нет. По сути, если конструктор с двумя параметрами в базовом классе просто вызывает this (context, attrs, 0), то это безопасно и в пользовательском классе представления.
ejw

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