Android - Ecrire un composant (composé) personnalisé


132

L'application Android que je développe actuellement a une activité principale qui est devenue assez importante. C'est principalement parce qu'il contient un TabWidgetavec 3 onglets. Chaque onglet contient plusieurs composants. L'activité doit contrôler tous ces composants à la fois. Donc, je pense que vous pouvez imaginer que cette activité a comme 20 champs (un champ pour presque chaque composant). En outre, il contient beaucoup de logique (écouteurs de clic, logique pour remplir les listes, etc.).

Ce que je fais normalement dans les frameworks basés sur des composants, c'est de tout diviser en composants personnalisés. Chaque composant personnalisé aurait alors une responsabilité claire. Il contiendrait son propre ensemble de composants et toute autre logique liée à ce composant.

J'ai essayé de comprendre comment cela pouvait être fait, et j'ai trouvé quelque chose dans la documentation Android ce qu'ils aiment appeler un «contrôle composé». (Voir http://developer.android.com/guide/topics/ui/custom-components.html#compound et faites défiler jusqu'à la section "Contrôles composés") Je voudrais créer un tel composant basé sur un fichier XML définissant le voir la structure.

Dans la documentation, il est dit:

Notez que tout comme avec une activité, vous pouvez utiliser soit l'approche déclarative (basée sur XML) pour créer les composants contenus, soit les imbriquer par programme à partir de votre code.

Eh bien, c'est une bonne nouvelle! L'approche basée sur XML est exactement ce que je veux! Mais il ne dit pas comment le faire, sauf que c'est "comme avec une activité" ... Mais ce que je fais dans une activité, c'est appeler setContentView(...)à gonfler les vues depuis XML. Cette méthode n'est pas disponible si vous par exemple sous-classez LinearLayout.

J'ai donc essayé de gonfler le XML manuellement comme ceci:

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

Cela fonctionne, à l'exception du fait que le XML que je charge a été LinearLayoutdéclaré comme élément racine. Il en résulte que le gonflé LinearLayoutest un enfant MyCompoundComponentdont lui-même est déjà un LinearLayout!! Nous avons donc maintenant un LinearLayout redondant entre MyCompoundComponentles vues dont il a réellement besoin.

Quelqu'un peut-il me fournir une meilleure façon d'aborder cela, en évitant d'avoir une LinearLayoutinstance redondante ?


14
J'adore les questions dont j'apprends quelque chose. Merci.
Jeremy Logan

5
J'ai récemment écrit une entrée de blog à ce sujet: blog.jteam.nl/2009/10/08/exploring-the-world-of-android-part-3
Tom van Zummeren

Réponses:


101

Utilisez la balise de fusion comme racine XML

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

Consultez cet article.


10
Merci beaucoup! C'est exactement ce que je recherchais. C'est incroyable de voir comment une question aussi longue peut avoir une réponse aussi courte. Excellent!
Tom van Zummeren

Qu'en est-il de l'intégration de cette disposition de fusion dans le paysage?
Kostadin

2
@Timmmm hahahaha J'ai posé cette question bien avant que l'éditeur visuel n'existe :)
Tom van Zummeren

0

Je pense que la façon dont vous êtes censé le faire est d'utiliser le nom de votre classe comme élément racine XML:

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

Et puis faites dériver votre classe de la disposition que vous souhaitez utiliser. Notez que si vous utilisez cette méthode, vous ne le gonfleur de mise en page ici.

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

Et puis, vous pouvez utiliser votre vue dans les mises en page XML comme d'habitude. Si vous souhaitez créer la vue par programmation, vous devez la gonfler vous-même:

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

Malheureusement, cela ne vous permet pas de le faire v = new MyView(context)car il ne semble pas y avoir de moyen de contourner le problème des dispositions imbriquées, ce n'est donc pas vraiment une solution complète. Vous pouvez ajouter une méthode comme celle-ci MyViewpour la rendre un peu plus agréable:

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

Avertissement: Je parle peut-être de conneries complètes.


Merci! Je pense que cette réponse est également correcte :) Mais il y a trois ans, quand j'ai posé cette question, "fusionner" a aussi fait l'affaire!
Tom van Zummeren

8
Et puis quelqu'un arrive et essaie d'utiliser votre vue personnalisée dans une mise en page quelque part avec juste <com.example.views.MyView />et vos appels setDataet onFinishInflatecommencent à lancer des NPE, et vous ne savez pas pourquoi.
Christopher Perry

L'astuce ici est d'utiliser votre vue personnalisée, puis dans le constructeur, de gonfler une mise en page qui utilise une balise de fusion comme racine. Vous pouvez maintenant l'utiliser en XML, ou simplement en le renouvelant. Toutes les bases sont couvertes, ce qui est exactement ce que la question / réponse acceptée fait ensemble. Cependant, vous ne pouvez plus vous référer directement à la mise en page. Il est désormais «détenu» par le contrôle personnalisé et ne doit être utilisé que par celui-ci dans le constructeur. Mais si vous utilisez cette approche, pourquoi auriez-vous besoin de l'utiliser ailleurs de toute façon? Vous ne le feriez pas.
Mark A. Donohoe
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.