Czy potrzebuję wszystkich trzech konstruktorów dla niestandardowego widoku systemu Android?

142

Podczas tworzenia widoku niestandardowego zauważyłem, że wiele osób robi to w ten sposób:

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

Moje pierwsze pytanie brzmi: co z konstruktorem MyView(Context context, AttributeSet attrs, int defStyle)? Nie jestem pewien, gdzie jest używany, ale widzę to w super klasie. Czy go potrzebuję i gdzie jest używany?

Jest jeszcze jedna część tego pytania .

Micah Hainline
źródło

Odpowiedzi:

144

Jeśli dodasz swój niestandardowy również Viewz xml:

 <com.mypack.MyView
      ...
      />

będziesz potrzebować konstruktora public MyView(Context context, AttributeSet attrs), w przeciwnym razie otrzymasz plik, Exceptiongdy Android spróbuje nadmuchać plik View.

Jeśli dodasz swoje Viewźródło xmli określisz również android:styleatrybut taki jak:

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

Drugi konstruktor również zostanie wywołany i będzie miał domyślny styl MyCustomStyleprzed zastosowaniem jawnych atrybutów XML.

Trzeci konstruktor jest zwykle używany, gdy chcesz, aby wszystkie widoki w aplikacji miały ten sam styl.

Ovidiu Latcu
źródło
3
kiedy więc użyć pierwszego konstruktora?
Android Killer,
@OvidiuLatcu czy możesz pokazać przykład trzeciego CTOR (z 3 parametrami)?
programista Androida,
czy mogę dodać dodatkowe parametry do konstruktora i jak z nich korzystać?
Mohammed Subhi Sheikh Quroush
24
Jeśli chodzi o trzeciego konstruktora, jest to całkowicie błędne . XML zawsze wywołuje konstruktor dwuargumentowy. Te trzy argument (i czterech argumentów ) konstruktorzy są nazywane przez podklasy , jeśli chcą, aby określić atrybut zawierający domyślny styl, albo domyślny styl bezpośrednio (w przypadku konstruktora cztery argument)
imgx64
Właśnie przesłałem poprawkę, aby odpowiedź była poprawna. Poniżej zaproponowałem również alternatywną odpowiedź.
mbonnin
117

Jeśli zastąpisz wszystkie trzy konstruktory, NIE NALEŻY POŁĄCZEŃ this(...)KASKADOWYCH. Zamiast tego powinieneś to zrobić:

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
}

Przyczyną jest to, że klasa nadrzędna może zawierać atrybuty domyślne we własnych konstruktorach, które można przypadkowo przesłonić. Na przykład to jest konstruktor dla 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);
}

Gdybyś nie wywołał super(context), nie ustawiłbyś poprawnie R.attr.textViewStylejako atrybutu stylu.

Jin
źródło
12
Jest to niezbędna rada podczas rozszerzania ListView. Jako (poprzedni) fan powyższego kaskadowania, przypominam sobie, że spędziłem godziny na poszukiwaniu subtelnego błędu, który zniknął, gdy wywołałem poprawną super metodę dla każdego konstruktora.
Groovee60
BTW @Jin Użyłem kodu w tej odpowiedzi: stackoverflow.com/a/22780035/294884, który wydaje się być oparty na twojej odpowiedzi - ale pamiętaj, że pisarz obejmuje użycie Inflatora?
Fattie
1
Myślę, że nie jest konieczne wywoływanie init we wszystkich konstruktorach, ponieważ kiedy podążasz za hierarchią wywołań, i tak znajdziesz się w domyślnym konstruktorze do tworzenia widoków programowych Widok (kontekst kontekstowy) {}
Marian Klühspies
robię to samo, ale nie udało mi się ustawić wartości w widoku
tekstu,
1
Jak mogłem tego nigdy nie wiedzieć?
Suragch
49

MyView (kontekst kontekstowy)

Używane podczas programowego tworzenia widoków.

MyView (kontekst kontekstowy, atrybuty zestawu atrybutów)

Używane przez the LayoutInflaterdo stosowania atrybutów xml. Jeśli jeden z tych atrybutów jest nazwany style, atrybuty będą przeszukiwane w stylu przed wyszukaniem jawnych wartości w pliku XML układu.

MyView (kontekst kontekstowy, atrybuty AttributeSet, int defStyleAttr)

Załóżmy, że chcesz zastosować domyślny styl do wszystkich widżetów bez konieczności określania ich stylew każdym pliku układu. Na przykład ustaw domyślnie wszystkie pola wyboru na różowo. Możesz to zrobić za pomocą defStyleAttr, a framework wyszuka domyślny styl w twoim motywie.

Zauważ, że jakiś czas temu defStyleAttrzostał nieprawidłowo nazwany defStylei jest dyskusja na temat tego, czy ten konstruktor jest naprawdę potrzebny, czy nie. Zobacz https://code.google.com/p/android/issues/detail?id=12683

MyView (kontekst kontekstowy, atrybuty AttributeSet, int defStyleAttr, int defStyleRes)

Trzeci konstruktor działa dobrze, jeśli masz kontrolę nad motywem podstawowym aplikacji. To działa dla Google, ponieważ wysyłają swoje widżety obok domyślnych motywów. Ale załóżmy, że piszesz bibliotekę widżetów i chcesz, aby domyślny styl został ustawiony bez konieczności modyfikowania motywu przez użytkowników. Możesz to teraz zrobić defStyleRes, ustawiając wartość domyślną w dwóch pierwszych konstruktorach:

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

W sumie

Jeśli implementujesz własne widoki, tylko dwa pierwsze konstruktory powinny być potrzebne i mogą być wywoływane przez framework.

Jeśli chcesz, aby Twoje widoki były rozszerzalne, możesz zaimplementować czwarty konstruktor dla elementów podrzędnych Twojej klasy, aby móc używać stylów globalnych.

Nie widzę prawdziwego przypadku użycia trzeciego konstruktora. Może to skrót, jeśli nie podasz domyślnego stylu widgetu, ale nadal chcesz, aby użytkownicy mogli to robić. Nie powinno się tak często dziać.

mbonnin
źródło
7

Kotlin wydaje się usuwać wiele z tego bólu:

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

@JvmOverloads wygeneruje wszystkie wymagane konstruktory (zobacz dokumentację tej adnotacji ), z których każdy prawdopodobnie wywoła super (). Następnie po prostu zamień swoją metodę inicjalizacji na blok Kotlin init {}. Zniknął kod schematu!

jules
źródło
1

Trzeci konstruktor jest dużo bardziej skomplikowany, posłużę się przykładem.

SwitchCompactPakiet Support-v7 obsługuje thumbTinti trackTintatrybut od wersji 24, podczas gdy wersja 23 ich nie obsługuje. Teraz chcesz je obsługiwać w wersji 23 i jak zamierzasz to osiągnąć?

Zakładamy, że będziemy używać niestandardowych SupportedSwitchCompactrozszerzeń widoku 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();
}

To tradycyjny styl kodu. Zauważ, że przekazujemy 0 do trzeciego parametru tutaj . Kiedy uruchomisz kod, zauważysz, że getThumbDrawable()zawsze zwraca wartość null, jakie to dziwne, ponieważ ta metoda getThumbDrawable()jest metodą swojej superklasy SwitchCompact.

Jeśli przejdziesz R.attr.switchStyledo trzeciego parametru, wszystko pójdzie dobrze, więc dlaczego?

Trzeci parametr to prosty atrybut. Atrybut wskazuje na zasób stylu, w powyższym przypadku system znajdzie switchStyleatrybut w aktualnym motywie na szczęście system go znajdzie.

W frameworks/base/core/res/res/values/themes.xmlzobaczysz:

<style name="Theme">
    <item name="switchStyle">@style/Widget.CompoundButton.Switch</item>
</style>
CoXier
źródło
-2

Jeśli musisz uwzględnić trzy konstruktory, takie jak ten omawiany teraz, możesz to również zrobić.

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
źródło
2
@Jin To dobry pomysł w wielu przypadkach, ale w wielu przypadkach jest to również bezpieczne (np .: RelativeLayout, FrameLayout, RecyclerView itp.). Więc powiedziałbym, że jest to prawdopodobnie wymaganie indywidualne dla każdego przypadku, a klasę bazową należy sprawdzić przed podjęciem decyzji o kaskadzie lub nie. Zasadniczo, jeśli konstruktor 2-param w klasie bazowej po prostu wywołuje this (context, attrs, 0), to można to bezpiecznie zrobić również w niestandardowej klasie widoku.
ejw
@IanWong oczywiście zostanie wywołany, ponieważ pierwsza i druga metoda wywołują trzecią.
CoolMind