Android - Gravando um componente personalizado (composto)

132

O aplicativo para Android que estou desenvolvendo atualmente tem uma atividade principal que cresceu bastante. Isso ocorre principalmente porque ele contém um TabWidgetcom 3 guias. Cada guia possui alguns componentes. A atividade tem que controlar todos esses componentes de uma só vez. Então, eu acho que você pode imaginar que essa Atividade tenha 20 campos (um campo para quase todos os componentes). Também contém muita lógica (clique em listeners, em lógica para preencher listas, etc.).

O que normalmente faço em estruturas baseadas em componentes é dividir tudo em componentes personalizados. Cada componente personalizado teria uma responsabilidade clara. Ele conteria seu próprio conjunto de componentes e todas as outras lógicas relacionadas a esse componente.

Tentei descobrir como isso pode ser feito e encontrei algo na documentação do Android que eles chamam de "Controle composto". (Consulte http://developer.android.com/guide/topics/ui/custom-components.html#compound e vá até a seção "Compound Controls"). Gostaria de criar esse componente com base em um arquivo XML que define o ver estrutura.

Na documentação, diz:

Observe que, assim como em uma Activity, você pode usar a abordagem declarativa (baseada em XML) para criar os componentes contidos ou pode aninhá-los programaticamente a partir do seu código.

Bem, isso é uma boa notícia! A abordagem baseada em XML é exatamente o que eu quero! Mas não diz como fazê-lo, exceto que é "como uma atividade" ... Mas o que faço em uma atividade é chamar setContentView(...)para aumentar as visualizações do XML. Esse método não está disponível se você, por exemplo, subclasse LinearLayout.

Então, eu tentei inflar o XML manualmente assim:

public class MyCompoundComponent extends LinearLayout {

    public MyCompoundComponent(Context context, AttributeSet attributeSet) {
        super(context, attributeSet);
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        inflater.inflate(R.layout.my_layout, this);
    }
}

Isso funciona, exceto pelo fato de que o XML que estou carregando foi LinearLayoutdeclarado como o elemento raiz. Isso resulta no inflado LinearLayoutser um filho do MyCompoundComponentqual ele já é um LinearLayout!! Portanto, agora temos um LinearLayout redundante no meio MyCompoundComponente as visualizações de que ele realmente precisa.

Alguém pode me fornecer uma maneira melhor de abordar isso, evitando LinearLayoutinstâncias redundantes ?

Tom van Zummeren
fonte
14
Adoro perguntas das quais aprendo algo. Obrigado.
Jeremy Logan
5
Recentemente, escreveu um blog sobre isso: blog.jteam.nl/2009/10/08/exploring-the-world-of-android-part-3
Tom van Zummeren

Respostas:

101

Use a marca de mesclagem como sua raiz XML

<merge xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Your Layout -->
</merge>

Veja este artigo.

bhatt4982
fonte
10
Muito obrigado! Era exatamente isso que eu estava procurando. Incrível como uma pergunta tão longa pode ter uma resposta tão curta. Excelente!
Tom van Zummeren 25/09/09
E quanto a excluir esse layout de mesclagem na paisagem?
22412 Kostadin
2
@Timmmm hahahaha eu fiz esta pergunta muito antes do editor visual sequer existia :)
Tom van Zummeren
0

Eu acho que da maneira que você deve fazer isso, é usar o nome da sua classe como o elemento raiz XML:

<com.example.views.MyView xmlns:....
       android:orientation="vertical" etc.>
    <TextView android:id="@+id/text" ... />
</com.example.views.MyView>

E, em seguida, faça com que sua classe seja derivada do layout que você deseja usar. Observe que, se você estiver usando esse método , não usará o inflator de layout aqui.

public class MyView extends LinearLayout
{
    public ConversationListItem(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    public ConversationListItem(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }


    public void setData(String text)
    {
        mTextView.setText(text);
    }

    private TextView mTextView;

    @Override
    protected void onFinishInflate()
    {
        super.onFinishInflate();

        mTextView = (TextView)findViewById(R.id.text);
    }
}

E então você pode usar sua visualização em layouts XML normalmente. Se você deseja fazer a visualização de forma programática, precisa inflá-la você mesmo:

MyView v = (MyView)inflater.inflate(R.layout.my_view, parent, false);

Infelizmente, isso não permite que você faça isso, v = new MyView(context)porque não parece haver uma maneira de contornar o problema de layouts aninhados, portanto, essa não é realmente uma solução completa. Você pode adicionar um método como este MyViewpara torná-lo um pouco melhor:

public static MyView create(Context context)
{
    return (MyView)LayoutInflater.fromContext(context).inflate(R.layout.my_view, null, false);
}

Disclaimer: Eu posso estar falando besteira completa.

Timmmm
fonte
Obrigado! Eu acho que essa resposta também está correta :) Mas há três anos, quando fiz essa pergunta, "mesclar" também funcionou!
Tom van Zummeren
8
E então alguém aparece e tenta usar sua visualização personalizada em um layout em algum lugar com just <com.example.views.MyView />e your setDatae onFinishInflatecalls e começa a lançar NPEs, e você não tem idéia do porquê.
Christopher Perry
O truque aqui é usar sua exibição personalizada e, em seguida, no construtor, inflar um layout que usa uma tag de mesclagem como raiz. Agora você pode usá-lo em XML ou apenas atualizando-o. Todas as bases são cobertas, exatamente o que a pergunta / resposta aceita faz em conjunto. No entanto, o que você não pode fazer é se referir diretamente ao layout. Agora é 'propriedade' do controle personalizado e deve ser usado apenas por ele no construtor. Mas se você estiver usando essa abordagem, por que precisaria usá-la em qualquer outro lugar? Você não faria.
MarkDonohoe