Benötige ich alle drei Konstruktoren für eine benutzerdefinierte Android-Ansicht?

142

Beim Erstellen einer benutzerdefinierten Ansicht ist mir aufgefallen, dass viele Benutzer dies anscheinend so tun:

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

Meine erste Frage ist, was ist mit dem Konstruktor MyView(Context context, AttributeSet attrs, int defStyle)? Ich bin nicht sicher, wo es verwendet wird, aber ich sehe es in der Superklasse. Brauche ich es und wo wird es verwendet?

Diese Frage hat noch einen weiteren Teil .

Micah Hainline
quelle

Antworten:

144

Wenn Sie Ihre benutzerdefinierte hinzufügen möchten Viewvon xmlauch wie:

 <com.mypack.MyView
      ...
      />

Sie benötigen den Konstruktor public MyView(Context context, AttributeSet attrs), andernfalls erhalten Sie eine, Exceptionwenn Android versucht, Ihre aufzublasen View.

Wenn Sie Ihr Viewfrom hinzufügen xmlund das android:styleAttribut wie folgt angeben :

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

Der 2. Konstruktor wird ebenfalls aufgerufen und der Stil wird standardmäßig verwendet, MyCustomStylebevor explizite XML-Attribute angewendet werden.

Der dritte Konstruktor wird normalerweise verwendet, wenn alle Ansichten in Ihrer Anwendung denselben Stil haben sollen.

Ovidiu Latcu
quelle
3
Wann soll dann der erste Konstruktor verwendet werden?
Android Killer
@OvidiuLatcu können Sie bitte ein Beispiel für die dritte CTOR (mit den 3 Parametern) zeigen?
Android-Entwickler
Kann ich dem Konstruktor zusätzliche Parameter hinzufügen und wie kann ich sie verwenden?
Mohammed Subhi Sheikh Quroush
24
In Bezug auf den dritten Konstruktor ist dies tatsächlich völlig falsch . XML ruft immer den Konstruktor mit zwei Argumenten auf. Die Konstruktoren mit drei Argumenten (und vier Argumenten ) werden von Unterklassen aufgerufen, wenn sie ein Attribut angeben möchten, das einen Standardstil enthält, oder einen Standardstil direkt (im Fall des Konstruktors mit vier Argumenten)
imgx64
Ich habe gerade eine Bearbeitung eingereicht, um die Antwort korrekt zu machen. Ich habe auch unten eine alternative Antwort vorgeschlagen.
mbonnin
117

Wenn Sie alle drei Konstruktoren überschreiben, CASCADE this(...)CALLS NICHT . Sie sollten stattdessen Folgendes tun:

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
}

Der Grund dafür ist, dass die übergeordnete Klasse möglicherweise Standardattribute in ihren eigenen Konstruktoren enthält, die Sie möglicherweise versehentlich überschreiben. Dies ist beispielsweise der Konstruktor für 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);
}

Wenn Sie nicht angerufen hätten super(context), hätten Sie R.attr.textViewStyleden Stil attr nicht richtig eingestellt .

Jin
quelle
12
Dies ist ein wichtiger Ratschlag bei der Erweiterung von ListView. Als (vorheriger) Fan der oben genannten Kaskadierung erinnere ich mich daran, Stunden damit verbracht zu haben, einen subtilen Fehler aufzuspüren, der verschwunden ist, als ich für jeden Konstruktor die richtige Supermethode aufgerufen habe.
Groovee60
Übrigens @Jin Ich habe den Code in dieser Antwort verwendet: stackoverflow.com/a/22780035/294884, der auf Ihrer Antwort zu basieren scheint - aber beachten Sie, dass der Verfasser die Verwendung des Inflators einschließt?
Fattie
1
Ich denke, es ist nicht notwendig, init in allen Konstruktoren aufzurufen, denn wenn Sie der Aufrufhierarchie folgen, landen Sie ohnehin im Standardkonstruktor für die Erstellung programmatischer Ansichten. Ansicht (Kontextkontext) {}
Marian Klühspies
Ich mache das gleiche, konnte aber keine Werte in der Textansicht festlegen, die in meiner benutzerdefinierten Ansicht verfügbar ist. Ich möchte den Wert aus der Aktivität
festlegen
1
Woher wusste ich das nie?
Suragch
49

MyView (Kontextkontext)

Wird verwendet, wenn Ansichten programmgesteuert instanziiert werden.

MyView (Kontextkontext, AttributeSet-Attribute)

Wird von verwendet LayoutInflater, um XML-Attribute anzuwenden. Wenn eines dieser Attribute benannt ist style, werden die Attribute im Stil nachgeschlagen, bevor nach expliziten Werten in der XML-Layoutdatei gesucht wird.

MyView (Kontextkontext, AttributeSet-Attribute, int defStyleAttr)

Angenommen, Sie möchten einen Standardstil auf alle Widgets anwenden, ohne dies stylein jeder Layoutdatei angeben zu müssen. Zum Beispiel lassen Sie alle Kontrollkästchen standardmäßig rosa werden. Sie können dies mit defStyleAttr tun, und das Framework sucht nach dem Standardstil in Ihrem Thema.

Beachten Sie, dass dies defStyleAttrvor defStyleeiniger Zeit falsch benannt wurde und es einige Diskussionen darüber gibt, ob dieser Konstruktor wirklich benötigt wird oder nicht. Siehe https://code.google.com/p/android/issues/detail?id=12683

MyView (Kontextkontext, AttributeSet-Attribute, int defStyleAttr, int defStyleRes)

Der 3. Konstruktor funktioniert gut, wenn Sie die Kontrolle über das Basisthema der Anwendungen haben. Das funktioniert bei Google, weil sie ihre Widgets neben den Standardthemen versenden. Angenommen, Sie schreiben eine Widget-Bibliothek und möchten, dass ein Standardstil festgelegt wird, ohne dass Ihre Benutzer ihr Thema anpassen müssen. Sie können dies jetzt tun, defStyleResindem Sie es in den beiden ersten Konstruktoren auf den Standardwert setzen:

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

Alles in allem

Wenn Sie Ihre eigenen Ansichten implementieren, sollten nur die beiden ersten Konstruktoren benötigt werden und können vom Framework aufgerufen werden.

Wenn Sie möchten, dass Ihre Ansichten erweiterbar sind, implementieren Sie möglicherweise den 4. Konstruktor, damit Kinder Ihrer Klasse das globale Styling verwenden können.

Ich sehe keinen wirklichen Anwendungsfall für den 3. Konstruktor. Möglicherweise eine Verknüpfung, wenn Sie keinen Standardstil für Ihr Widget angeben und dennoch möchten, dass Ihre Benutzer dies tun können. Sollte nicht so viel passieren.

mbonnin
quelle
7

Kotlin scheint viel von diesem Schmerz wegzunehmen:

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

@JvmOverloads generiert alle erforderlichen Konstruktoren (siehe Dokumentation dieser Annotation ), von denen jeder vermutlich super () aufruft. Ersetzen Sie dann einfach Ihre Initialisierungsmethode durch einen Kotlin init {} -Block. Boilerplate-Code weg!

Jules
quelle
1

Der dritte Konstruktor ist viel komplizierter. Lassen Sie mich ein Beispiel geben.

Support-v7- SwitchCompactPaket unterstützt thumbTintund trackTintAttribute seit 24 Version, während 23 Version sie nicht unterstützt. Jetzt möchten Sie sie in 23 Version unterstützen und wie werden Sie dies erreichen?

Wir gehen davon aus Gewohnheit verwenden Ansicht SupportedSwitchCompacterstreckt 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();
}

Es ist ein traditioneller Codestil. Beachten Sie, dass wir hier 0 an den dritten Parameter übergeben . Wenn Sie den Code ausführen, werden Sie getThumbDrawable()immer null zurückgeben, wie seltsam es ist, weil die Methode die Methode getThumbDrawable()ihrer Superklasse ist SwitchCompact.

Wenn Sie R.attr.switchStylezum dritten Parameter übergehen , läuft alles gut. Warum also?

Der dritte Parameter ist ein einfaches Attribut. Das Attribut verweist auf eine Stilressource. Im obigen Fall findet das System das switchStyleAttribut im aktuellen Thema. Glücklicherweise findet das System es.

In sehen frameworks/base/core/res/res/values/themes.xmlSie:

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

Wenn Sie drei Konstruktoren wie den jetzt diskutierten einschließen müssen, können Sie dies auch tun.

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
quelle
2
@Jin Das ist in vielen Fällen eine gute Idee, aber in vielen Fällen auch sicher (z. B. RelativeLayout, FrameLayout, RecyclerView usw.). Ich würde also sagen, dass dies wahrscheinlich von Fall zu Fall erforderlich ist und die Basisklasse überprüft werden sollte, bevor die Entscheidung getroffen wird, ob eine Kaskade durchgeführt werden soll oder nicht. Wenn der 2-Parameter-Konstruktor in der Basisklasse dies nur aufruft (context, attrs, 0), ist dies im Wesentlichen auch in der benutzerdefinierten Ansichtsklasse sicher.
Ejw
@IanWong wird natürlich aufgerufen, da die erste und die zweite Methode die dritte aufrufen.
CoolMind