Preciso dos três construtores para uma visualização personalizada do Android?

142

Ao criar uma exibição personalizada, notei que muitas pessoas parecem fazer assim:

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

Minha primeira pergunta é: e o construtor MyView(Context context, AttributeSet attrs, int defStyle)? Não tenho certeza de onde é usado, mas vejo isso na super classe. Preciso e onde é usado?

outra parte nessa pergunta .

Micah Hainline
fonte

Respostas:

144

Se você vai adicionar seu costume Viewde xmltambém gosto:

 <com.mypack.MyView
      ...
      />

você precisará do construtor public MyView(Context context, AttributeSet attrs), caso contrário, receberá um Exceptionquando o Android tentar aumentar o seu View.

Se você adicionar seu Viewfrom xmle também especificar o android:styleatributo como:

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

o segundo construtor também será chamado e usará o estilo como padrão MyCustomStyleantes de aplicar atributos XML explícitos.

O terceiro construtor geralmente é usado quando você deseja que todas as Views no seu aplicativo tenham o mesmo estilo.

Ovidiu Latcu
fonte
3
quando usar o primeiro construtor, então?
Android Killer
@OvidiuLatcu, você pode mostrar um exemplo do terceiro CTOR (com os 3 parâmetros)?
desenvolvedor android
posso adicionar parâmetros extras ao construtor e como posso usá-los?
Mohammed Subhi Sheikh Quroush
24
Em relação ao terceiro construtor, isso é realmente completamente errado . XML sempre chama o construtor de dois argumentos. Os três argumentos (e quatro argumento ) construtores são chamados por subclasses se quiser especificar um atributo que contém um estilo padrão, ou um estilo padrão diretamente (no caso do construtor de quatro argumento)
imgx64
Acabei de enviar uma edição para corrigir a resposta. Também propus uma resposta alternativa abaixo.
Mbonnin 1/09/16
118

Se você substituir todos os três construtores, por favor, não faça chamadas this(...). Você deveria fazer o seguinte:

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
}

O motivo é que a classe pai pode incluir atributos padrão em seus próprios construtores que você pode substituir acidentalmente. Por exemplo, este é o construtor para 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 você não ligou super(context), não teria definido corretamente R.attr.textViewStylecomo o atributo attr.

Jin
fonte
12
Este é um conselho essencial ao estender o ListView. Como fã (anterior) da cascata acima, lembro-me de passar horas rastreando um bug sutil que desapareceu quando chamei o super método correto para cada construtor.
Groovee60
BTW @Jin Usei o código nesta resposta: stackoverflow.com/a/22780035/294884, que parece basear-se na sua resposta - mas observe que o escritor inclui o uso do Inflator?
Fattie
1
Eu acho que é não neccessary para chamar o init em todos os construtores, porque quando você seguir a hierarquia de chamadas, você vai acabar no construtor padrão para programático criação da vista de qualquer maneira View (contexto Contexto) {}
Marian Klühspies
im fazendo mesmo, mas não conseguiu valores definidos em textview que está disponível na minha opinião costume eu quero valor ajustado de atividade
Erum
1
Como eu nunca soube disso?
Suragch 4/11
49

MyView (contexto de contexto)

Usado ao instanciar vistas de maneira programática.

MyView (contexto de contexto, AttributeSet attrs)

Usado pelo LayoutInflaterpara aplicar atributos xml. Se um desses atributos for nomeado style, os atributos serão procurados no estilo antes de procurar valores explícitos no arquivo xml de layout.

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

Suponha que você queira aplicar um estilo padrão a todos os widgets sem precisar especificar styleem cada arquivo de layout. Por exemplo, torne todas as caixas de seleção rosa por padrão. Você pode fazer isso com o defStyleAttr e a estrutura pesquisará o estilo padrão no seu tema.

Observe que defStyleAttrfoi nomeado incorretamente defStylehá algum tempo e há alguma discussão sobre se esse construtor é realmente necessário ou não. Consulte https://code.google.com/p/android/issues/detail?id=12683

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

O terceiro construtor funciona bem se você tiver controle sobre o tema base dos aplicativos. Isso funciona para o google porque eles enviam seus widgets ao lado dos temas padrão. Mas suponha que você esteja escrevendo uma biblioteca de widgets e deseje que um estilo padrão seja definido sem que os usuários precisem ajustar o tema. Agora você pode fazer isso usando defStyleReso valor padrão nos 2 primeiros construtores:

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

Contudo

Se você estiver implementando suas próprias visualizações, apenas os 2 primeiros construtores devem ser necessários e podem ser chamados pela estrutura.

Se você deseja que seu Views seja extensível, implemente o quarto construtor para que os filhos da sua classe possam usar o estilo global.

Não vejo um caso de uso real para o terceiro construtor. Talvez um atalho se você não fornecer um estilo padrão para o seu widget, mas ainda assim desejar que seus usuários possam fazê-lo. Não deveria acontecer tanto assim.

mbonnin
fonte
7

Kotlin parece aliviar muita dor:

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

O @JvmOverloads irá gerar todos os construtores necessários (consulte a documentação da anotação ), cada um dos quais presumivelmente chama super (). Em seguida, substitua seu método de inicialização por um bloco Kotlin init {}. Código do Boilerplate desaparecido!

jules
fonte
1

O terceiro construtor é muito mais complicado. Deixe-me dar um exemplo.

O SwitchCompactpacote Support-v7 suporta thumbTinte trackTintatribui desde a versão 24, enquanto a versão 23 não os suporta. Agora você deseja apoiá-los na versão 23 e como fará para conseguir isso?

Assumimos usar personalizado Ver SupportedSwitchCompactestende 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();
}

É um estilo de código tradicional. Observe que passamos 0 para o terceiro parâmetro aqui . Ao executar o código, você getThumbDrawable()sempre retornará nulo o quão estranho é, porque o método getThumbDrawable()é o método de sua superclasse SwitchCompact.

Se você passar R.attr.switchStylepara o terceiro parâmetro, tudo vai bem.

O terceiro parâmetro é um atributo simples. O atributo aponta para um recurso de estilo. No caso acima, o sistema encontrará o switchStyleatributo no tema atual, felizmente, o sistema o encontrará.

Em frameworks/base/core/res/res/values/themes.xml, você verá:

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

Se você precisar incluir três construtores como o que está sendo discutido agora, poderá fazer isso também.

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
fonte
2
@ Jin Essa é uma boa idéia em muitos casos, mas também é segura em muitos casos (por exemplo: RelativeLayout, FrameLayout, RecyclerView, etc.). Portanto, eu diria que esse é provavelmente um requisito caso a caso e a classe base deve ser verificada antes de tomar a decisão de cascatear ou não. Essencialmente, se o construtor 2-param na classe base está apenas chamando isso (context, attrs, 0), então é seguro fazê-lo também na classe de exibição personalizada.
EJW
@IanWong, é claro, será chamado, porque o primeiro e o segundo métodos estão chamando o terceiro.
CoolMind 04/08