Ai-je besoin des trois constructeurs pour une vue personnalisée Android?

143

Lors de la création d'une vue personnalisée, j'ai remarqué que de nombreuses personnes semblent le faire comme ceci:

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

Ma première question est: qu'en est-il du constructeur MyView(Context context, AttributeSet attrs, int defStyle)? Je ne sais pas où il est utilisé, mais je le vois dans la super classe. En ai-je besoin et où est-il utilisé?

Il y a une autre partie à cette question .

Micah Hainline
la source

Réponses:

145

Si vous allez ajouter votre coutume à Viewpartir de xmlaussi:

 <com.mypack.MyView
      ...
      />

vous aurez besoin du constructeur public MyView(Context context, AttributeSet attrs), sinon vous obtiendrez un Exceptionquand Android essaiera de gonfler votre View.

Si vous ajoutez votre Viewfrom xmlet spécifiez également l' android:styleattribut comme:

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

le 2ème constructeur sera également appelé et le style par défaut sera MyCustomStyleavant d'appliquer des attributs XML explicites.

Le troisième constructeur est généralement utilisé lorsque vous souhaitez que toutes les vues de votre application aient le même style.

Ovidiu Latcu
la source
3
quand utiliser le premier constructeur alors?
Android Killer
@OvidiuLatcu pouvez-vous s'il vous plaît montrer un exemple du troisième CTOR (avec les 3 paramètres)?
développeur android
puis-je ajouter des paramètres supplémentaires au constructeur et comment puis-je les utiliser?
Mohammed Subhi Sheikh Quroush
24
En ce qui concerne le troisième constructeur, c'est en fait complètement faux . XML appelle toujours le constructeur à deux arguments. Les constructeurs à trois arguments (et à quatre arguments ) sont appelés par des sous - classes s'ils veulent spécifier un attribut contenant un style par défaut, ou un style par défaut directement (dans le cas du constructeur à quatre arguments)
imgx64
Je viens de soumettre une modification pour que la réponse soit correcte. J'ai également proposé une réponse alternative ci-dessous.
mbonnin le
118

Si vous remplacez les trois constructeurs, veuillez NE PAS CASCADEZ LES this(...)APPELS. Vous devriez plutôt faire ceci:

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
}

La raison en est que la classe parente peut inclure des attributs par défaut dans ses propres constructeurs que vous risquez de remplacer accidentellement. Par exemple, voici le constructeur pour 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);
}

Si vous n'avez pas appelé super(context), vous n'auriez pas correctement défini R.attr.textViewStylecomme attr de style.

Jin
la source
12
C'est un conseil essentiel lors de l'extension de ListView. En tant que (précédent) fan de cette cascade ci-dessus, je me souviens avoir passé des heures à rechercher un bug subtil qui a disparu lorsque j'ai appelé la super méthode correcte pour chaque constructeur.
Groovee60
BTW @Jin J'ai utilisé le code dans cette réponse: stackoverflow.com/a/22780035/294884 qui semble être basé sur votre réponse - mais notez que l'auteur inclut l'utilisation du gonfleur?
Fattie
1
Je pense qu'il n'est pas nécessaire d'appeler init dans tous les constructeurs, car lorsque vous suivez la hiérarchie des appels, vous vous retrouverez de toute façon dans le constructeur par défaut pour la création de vues programmatiques. View (Contexte contextuel) {}
Marian Klühspies
Je fais la même chose mais je n'ai pas réussi à définir les valeurs dans textview qui est disponible dans ma vue personnalisée Je veux définir la valeur de l'activité
Erum
1
Comment n'ai-je jamais su cela?
Suragch
49

MyView (contexte contextuel)

Utilisé lors de l'instanciation de Views par programme.

MyView (contexte de contexte, attrs AttributeSet)

Utilisé par le LayoutInflaterpour appliquer les attributs xml. Si l'un de ces attributs est nommé style, les attributs seront recherchés dans le style avant de rechercher des valeurs explicites dans le fichier xml de mise en page.

MyView (Contexte contextuel, AttributeSet attrs, int defStyleAttr)

Supposons que vous souhaitiez appliquer un style par défaut à tous les widgets sans avoir à le spécifier styledans chaque fichier de mise en page. Pour un exemple, rendez toutes les cases à cocher roses par défaut. Vous pouvez le faire avec defStyleAttr et le framework recherchera le style par défaut dans votre thème.

Notez qu'il a defStyleAttrété mal nommé il y a defStylequelque temps et il y a une discussion pour savoir si ce constructeur est vraiment nécessaire ou non. Voir https://code.google.com/p/android/issues/detail?id=12683

MyView (Contexte contextuel, AttributeSet attrs, int defStyleAttr, int defStyleRes)

Le 3ème constructeur fonctionne bien si vous contrôlez le thème de base des applications. Cela fonctionne pour Google car ils livrent leurs widgets avec les thèmes par défaut. Mais supposons que vous écriviez une bibliothèque de widgets et que vous souhaitiez qu'un style par défaut soit défini sans que vos utilisateurs aient besoin de modifier leur thème. Vous pouvez maintenant le faire defStyleResen lui attribuant la valeur par défaut dans les 2 premiers constructeurs:

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

En tout

Si vous implémentez vos propres vues, seuls les 2 premiers constructeurs devraient être nécessaires et peuvent être appelés par le framework.

Si vous voulez que vos vues soient extensibles, vous pouvez implémenter le 4ème constructeur pour les enfants de votre classe afin de pouvoir utiliser le style global.

Je ne vois pas de cas d'utilisation réel pour le 3ème constructeur. Peut-être un raccourci si vous ne fournissez pas de style par défaut pour votre widget, mais que vous souhaitez toujours que vos utilisateurs puissent le faire. Ça ne devrait pas arriver tant que ça.

mbonnin
la source
7

Kotlin semble enlever une grande partie de cette douleur:

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

@JvmOverloads générera tous les constructeurs requis (voir la documentation de cette annotation ), chacun appelant vraisemblablement super (). Ensuite, remplacez simplement votre méthode d'initialisation par un bloc Kotlin init {}. Le code de la chaudière est parti!

Jules
la source
1

Le troisième constructeur est beaucoup plus compliqué, laissez-moi vous donner un exemple.

Support-v7 SwitchCompactprend en charge thumbTintet trackTintattribut depuis la version 24 alors que la version 23 ne les prend pas en charge.Maintenant, vous voulez les prendre en charge dans la version 23 et comment ferez-vous pour y parvenir?

Nous supposons que vous utilisez des SupportedSwitchCompactextensions de vue personnalisées 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();
}

C'est un style de code traditionnel. Notez que nous passons 0 au troisième paramètre ici . Lorsque vous exécutez le code, vous trouverez getThumbDrawable()toujours return null à quel point il est étrange car la méthode getThumbDrawable()est la méthode de sa super classe SwitchCompact.

Si vous passez R.attr.switchStyleau troisième paramètre, tout se passe bien, alors pourquoi?

Le troisième paramètre est un attribut simple. L'attribut pointe vers une ressource de style. Dans le cas ci-dessus, le système trouvera l' switchStyleattribut dans le thème courant, heureusement que le système le trouve.

Dans frameworks/base/core/res/res/values/themes.xml, vous verrez:

<style name="Theme">
    <item name="switchStyle">@style/Widget.CompoundButton.Switch</item>
</style>
Plus confortable
la source
-2

Si vous devez inclure trois constructeurs comme celui en cours de discussion, vous pouvez également le faire.

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
la source
2
@Jin C'est une bonne idée dans de nombreux cas, mais c'est aussi sûr dans de nombreux cas (par exemple: RelativeLayout, FrameLayout, RecyclerView, etc.). Donc, je dirais que c'est probablement une exigence au cas par cas et que la classe de base doit être vérifiée avant de prendre la décision de cascade ou non. Essentiellement, si le constructeur à 2 paramètres dans la classe de base appelle simplement this (context, attrs, 0), alors il est également possible de le faire dans la classe de vue personnalisée.
ejw
@IanWong, bien sûr, il sera appelé, car la première et la deuxième méthodes appellent la troisième.
CoolMind