Ho bisogno di tutti e tre i costruttori per una vista personalizzata Android?


142

Quando ho creato una vista personalizzata, ho notato che molte persone sembrano farlo in questo modo:

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

La mia prima domanda è: che dire del costruttore MyView(Context context, AttributeSet attrs, int defStyle)? Non sono sicuro di dove venga utilizzato, ma lo vedo nella super classe. Ne ho bisogno e dove viene utilizzato?

C'è un'altra parte di questa domanda .

Risposte:


144

Se si aggiungerà la vostra abitudine Viewda xmlpiace anche:

 <com.mypack.MyView
      ...
      />

avrai bisogno del costruttore public MyView(Context context, AttributeSet attrs), altrimenti ne otterrai uno Exceptionquando Android tenta di gonfiare il tuo View.

Se aggiungi il tuo Viewda xmle specifichi anche l' android:styleattributo come:

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

verrà inoltre chiamato il secondo costruttore e impostato automaticamente lo stile MyCustomStyleprima di applicare gli attributi XML espliciti.

Il terzo costruttore viene solitamente utilizzato quando si desidera che tutte le viste dell'applicazione abbiano lo stesso stile.


3
quando usare il primo costruttore allora?
Android Killer,

@OvidiuLatcu puoi per favore mostrare un esempio del terzo CTOR (con i 3 parametri)?
Sviluppatore Android

posso aggiungere parametri extra al costruttore e come posso usarli?
Mohammed Subhi Sheikh Quroush,

24
Per quanto riguarda il terzo costruttore, questo è in realtà completamente sbagliato . XML chiama sempre il costruttore a due argomenti. I costruttori a tre argomenti (e a quattro argomenti ) vengono chiamati da sottoclassi se desiderano specificare un attributo contenente uno stile predefinito o uno stile predefinito direttamente (nel caso del costruttore a quattro argomenti)
imgx64

Ho appena inviato una modifica per rendere corretta la risposta. Di seguito ho anche proposto una risposta alternativa.
mbonnin,

118

Se si sovrascrivono tutti e tre i costruttori, NON CHIAMARE CASCADE this(...). Dovresti invece fare questo:

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
}

Il motivo è che la classe genitore potrebbe includere attributi predefiniti nei propri costruttori che potrebbero essere sovrascritti accidentalmente. Ad esempio, questo è il costruttore per 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);
}

Se non avessi chiamato super(context), non avresti impostato correttamente R.attr.textViewStylelo stile attr.


12
Questo è un consiglio essenziale quando si estende ListView. Come un fan (precedente) di questo sopra-a cascata, ricordo di aver passato ore a rintracciare un insetto sottile che è andato via quando ho chiamato il metodo super corretto per ciascun costruttore.
Groovee60,

BTW @Jin Ho usato il codice in questa risposta: stackoverflow.com/a/22780035/294884 che sembra essere basato sulla tua risposta - ma noti che lo scrittore include l'uso del Inflator?
Fattie,

1
Penso che non sia necessario chiamare init in tutti i costruttori, perché quando segui la gerarchia delle chiamate,
finirai

sto facendo lo stesso, ma non sono riuscito a impostare i valori nella visualizzazione di testo che è disponibile nella mia visualizzazione personalizzata voglio impostare il valore dall'attività
Erum

1
Come non l'ho mai saputo?
Suragch,

49

MyView (contesto di contesto)

Utilizzato durante l'installazione di Viste a livello di codice.

MyView (Contesto di contesto, AttributeSet attrs)

Utilizzato da LayoutInflaterper applicare gli attributi xml. Se uno di questi attributi è denominato style, gli attributi verranno cercati nello stile prima di cercare valori espliciti nel file xml di layout.

MyView (Contesto di contesto, AttributeSet attrs, int defStyleAttr)

Supponiamo di voler applicare uno stile predefinito a tutti i widget senza dover specificare stylein ciascun file di layout. Ad esempio, rendere tutte le caselle di controllo rosa per impostazione predefinita. Puoi farlo con defStyleAttr e il framework cercherà lo stile predefinito nel tuo tema.

Si noti che è defStyleAttrstato erroneamente chiamato defStylequalche tempo fa e si discute se questo costruttore sia davvero necessario o meno. Vedi https://code.google.com/p/android/issues/detail?id=12683

MyView (Contesto di contesto, AttributeSet attrs, int defStyleAttr, int defStyleRes)

Il terzo costruttore funziona bene se si ha il controllo sul tema di base delle applicazioni. Questo funziona per Google perché spediscono i loro widget insieme ai temi predefiniti. Supponiamo che tu stia scrivendo una libreria di widget e desideri impostare uno stile predefinito senza che i tuoi utenti debbano modificare il loro tema. Ora puoi farlo usando defStyleResimpostandolo sul valore predefinito nei primi 2 costruttori:

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

Tutto sommato

Se stai implementando le tue viste, dovrebbero essere necessari solo i primi 2 costruttori e possono essere chiamati dal framework.

Se desideri che le tue visualizzazioni siano estensibili, potresti implementare il 4o costruttore per consentire ai bambini della tua classe di utilizzare lo stile globale.

Non vedo un caso d'uso reale per il terzo costruttore. Forse un collegamento se non fornisci uno stile predefinito per il tuo widget ma desideri comunque che gli utenti siano in grado di farlo. Non dovrebbe succedere così tanto.


7

Kotlin sembra portare via molto di questo dolore:

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

@JvmOverloads genererà tutti i costruttori richiesti (vedere la documentazione di quell'annotazione ), ciascuno dei quali presumibilmente chiama super (). Quindi, sostituisci semplicemente il tuo metodo di inizializzazione con un blocco init {} di Kotlin. Il codice della caldaia è andato!


1

Il terzo costruttore è molto più complicato. Fammi un esempio.

Il SwitchCompactpacchetto Support-v7 supporta thumbTinte trackTintattribuisce dalla versione 24 mentre la versione 23 non li supporta. Ora vuoi supportarli nella versione 23 e come farai per raggiungere questo obiettivo?

Partiamo dal presupposto di utilizzare SupportedSwitchCompactestensioni di visualizzazione personalizzate 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();
}

È uno stile di codice tradizionale. Nota passiamo 0 al terzo parametro qui . Quando esegui il codice, troverai getThumbDrawable()sempre null quanto sia strano perché il metodo getThumbDrawable()è il metodo della sua super classe SwitchCompact.

Se passi R.attr.switchStyleal terzo parametro, tutto va bene, quindi perché?

Il terzo parametro è un attributo semplice. L'attributo punta a una risorsa di stile. Nel caso precedente, il sistema troverà switchStylefortunatamente l'attributo nel tema corrente.

In frameworks/base/core/res/res/values/themes.xml, vedrai:

<style name="Theme">
    <item name="switchStyle">@style/Widget.CompoundButton.Switch</item>
</style>

-2

Se ora devi includere tre costruttori come quello in discussione, puoi farlo anche tu.

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 Questa è una buona idea in molti casi, ma è sicura anche in molti casi (ad esempio: RelativeLayout, FrameLayout, RecyclerView, ecc.). Quindi, direi che questo è probabilmente un requisito caso per caso e la classe base dovrebbe essere verificata prima di prendere la decisione di cascata o no. In sostanza, se il costruttore a 2 parametri nella classe base lo sta semplicemente chiamando (contesto, attrs, 0), allora è sicuro farlo anche nella classe vista personalizzata.
ejw

@IanWong, ovviamente, verrà chiamato, perché il primo e il secondo metodo chiamano terzo.
CoolMind
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.