¿Necesito los tres constructores para una vista personalizada de Android?

142

Al crear una vista personalizada, me di cuenta de que muchas personas parecen hacerlo así:

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

Mi primera pregunta es, ¿qué pasa con el constructor MyView(Context context, AttributeSet attrs, int defStyle)? No estoy seguro de dónde se usa, pero lo veo en la superclase. ¿Lo necesito y dónde se usa?

Hay otra parte de esta pregunta .

Micah Hainline
fuente

Respuestas:

144

Si va a agregar su personalizado Viewde xmltambién me gusta:

 <com.mypack.MyView
      ...
      />

necesitará el constructor public MyView(Context context, AttributeSet attrs), de lo contrario obtendrá un Exceptioncuando Android intente inflar su View.

Si agrega su Viewdesde xmly también especifica el android:styleatributo como:

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

también se llamará al segundo constructor y se aplicará el estilo por defecto MyCustomStyleantes de aplicar atributos XML explícitos.

El tercer constructor generalmente se usa cuando desea que todas las Vistas de su aplicación tengan el mismo estilo.

Ovidiu Latcu
fuente
3
¿Cuándo usar el primer constructor entonces?
Android Killer
@OvidiuLatcu, ¿puede mostrar un ejemplo del tercer CTOR (con los 3 parámetros)?
Desarrollador de Android
¿Puedo agregar parámetros adicionales al constructor y cómo puedo usarlos?
Mohammed Subhi Sheikh Quroush
24
Con respecto al tercer constructor, esto es realmente completamente incorrecto . XML siempre llama al constructor de dos argumentos. Los constructores de tres argumentos (y cuatro argumentos ) son llamados por subclases si desean especificar un atributo que contenga un estilo predeterminado, o un estilo predeterminado directamente (en el caso del constructor de cuatro argumentos)
imgx64
Acabo de enviar una edición para que la respuesta sea correcta. También he propuesto una respuesta alternativa a continuación.
mbonnin
118

Si anula los tres constructores, NO HAGA this(...)LLAMADAS EN CASCADA . En su lugar, deberías estar haciendo esto:

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 razón es que la clase principal podría incluir atributos predeterminados en sus propios constructores que podría anular accidentalmente. Por ejemplo, este es el constructor de 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 no llamó super(context), no habría establecido correctamente R.attr.textViewStylecomo el atributo de estilo.

Jin
fuente
12
Este es un consejo esencial al extender ListView. Como fanático (anterior) de la cascada anterior, recuerdo haber pasado horas rastreando un error sutil que desapareció cuando llamé al súper método correcto para cada constructor.
Groovee60
Por cierto @Jin Usé el código en esta respuesta: stackoverflow.com/a/22780035/294884, que parece estar basado en su respuesta, pero tenga en cuenta que el escritor incluye el uso del inflador.
Fattie
1
Creo que no es necesario llamar a init en todos los constructores, porque cuando sigas la jerarquía de llamadas, terminarás en el constructor predeterminado para la creación de vistas programáticas de todos modos Ver (contexto contextual) {}
Marian Klühspies
estoy haciendo lo mismo pero no pude establecer valores en la vista de texto que está disponible en mi vista personalizada, quiero establecer el valor de la actividad
Erum
1
¿Cómo nunca supe esto?
Suragch
49

MyView (contexto contextual)

Se usa al instanciar Vistas mediante programación.

MyView (contexto de contexto, atributos de AttributeSet)

Usado por LayoutInflaterpara aplicar atributos xml. Si se nombra uno de estos atributos style, los atributos se buscarán en el estilo antes de buscar valores explícitos en el archivo xml de diseño.

MyView (contexto de contexto, AttributeSet attrs, int defStyleAttr)

Suponga que desea aplicar un estilo predeterminado a todos los widgets sin tener que especificar styleen cada archivo de diseño. Por ejemplo, haga que todas las casillas de verificación sean rosas de forma predeterminada. Puede hacer esto con defStyleAttr y el marco buscará el estilo predeterminado en su tema.

Tenga en cuenta que defStyleAttrse nombró incorrectamente defStylehace algún tiempo y hay una discusión sobre si este constructor es realmente necesario o no. Ver https://code.google.com/p/android/issues/detail?id=12683

MyView (Contexto contextual, AttributeSet attrs, int defStyleAttr, int defStyleRes)

El 3er constructor funciona bien si tiene control sobre el tema base de las aplicaciones. Eso está funcionando para Google porque envían sus widgets junto con los Temas predeterminados. Pero suponga que está escribiendo una biblioteca de widgets y desea establecer un estilo predeterminado sin que sus usuarios necesiten modificar su tema. Ahora puede hacer esto mediante el defStyleResestablecimiento del valor predeterminado en los 2 primeros constructores:

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

Considerándolo todo

Si está implementando sus propias vistas, solo los 2 primeros constructores deberían ser necesarios y el marco puede invocarlos.

Si desea que sus Vistas sean extensibles, puede implementar el cuarto constructor para que los niños de su clase puedan usar un estilo global.

No veo un caso de uso real para el 3er constructor. Tal vez sea un acceso directo si no proporciona un estilo predeterminado para su widget pero aún desea que sus usuarios puedan hacerlo. No debería pasar tanto.

mbonnin
fuente
7

Kotlin parece eliminar mucho de este dolor:

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

@JvmOverloads generará todos los constructores necesarios (consulte la documentación de esa anotación ), cada uno de los cuales presumiblemente llama super (). Luego, simplemente reemplace su método de inicialización con un bloque Kotlin init {}. ¡Se acabó el código repetitivo!

jules
fuente
1

El tercer constructor es mucho más complicado. Déjenme tener un ejemplo.

El SwitchCompactpaquete Support-v7 admite thumbTinty trackTintatribuye desde la versión 24, mientras que la versión 23 no los admite. ¿Ahora desea admitirlos en la versión 23 y cómo lo hará para lograrlo?

Suponemos utilizar encargo Ver SupportedSwitchCompactextiende 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 un estilo de código tradicional. Tenga en cuenta que pasamos 0 al tercer parámetro aquí . Cuando ejecute el código, getThumbDrawable()siempre encontrará que devuelve nulo lo extraño que es porque el método getThumbDrawable()es el método de su superclase SwitchCompact.

Si pasas R.attr.switchStyleal tercer parámetro, todo va bien. Entonces, ¿por qué?

El tercer parámetro es un atributo simple. El atributo apunta a un recurso de estilo. En el caso anterior, el sistema encontrará el switchStyleatributo en el tema actual, afortunadamente el sistema lo encuentra.

En frameworks/base/core/res/res/values/themes.xml, verá:

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

Si tiene que incluir tres constructores como el que se está discutiendo ahora, también podría hacerlo.

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
fuente
2
@Jin Esa es una buena idea en muchos casos, pero también es segura en muchos casos (por ejemplo: RelativeLayout, FrameLayout, RecyclerView, etc.). Entonces, diría que este es probablemente un requisito caso por caso y la clase base debe verificarse antes de tomar la decisión de conectarse en cascada o no. Esencialmente, si el constructor de 2 parámetros en la clase base solo llama a esto (contexto, atributos, 0), entonces también es seguro hacerlo en la clase de vista personalizada.
ejw
@IanWong, por supuesto, se llamará, porque los métodos primero y segundo están llamando a terceros.
CoolMind