Android: scrittura di un componente (composto) personalizzato


132

L'app per Android che sto sviluppando ha un'attività principale che è cresciuta abbastanza grande. Questo principalmente perché contiene una TabWidgetcon 3 schede. Ogni scheda ha alcuni componenti. L'attività deve controllare tutti quei componenti contemporaneamente. Quindi penso che tu possa immaginare che questa attività abbia come 20 campi (un campo per quasi tutti i componenti). Inoltre contiene molta logica (fare clic su listener, logica per compilare elenchi, ecc.).

Quello che faccio normalmente nei framework basati su componenti è quello di suddividere tutto in componenti personalizzati. Ogni componente personalizzato avrebbe quindi una chiara responsabilità. Conterrebbe il proprio set di componenti e tutte le altre logiche correlate a quel componente.

Ho provato a capire come si può fare, e ho trovato qualcosa nella documentazione di Android quello che a loro piace chiamare un "controllo composto". (Vedi http://developer.android.com/guide/topics/ui/custom-components.html#compound e scorri fino alla sezione "Comandi composti") Vorrei creare un componente del genere basato su un file XML che definisce il visualizza la struttura.

Nella documentazione si dice:

Si noti che proprio come con un'attività, è possibile utilizzare l'approccio dichiarativo (basato su XML) per creare i componenti contenuti oppure è possibile nidificarli a livello di codice dal proprio codice.

Bene, questa è una buona notizia! L'approccio basato su XML è esattamente quello che voglio! Ma non dice come farlo, tranne per il fatto che è "come con un'attività" ... Ma quello che faccio in un'attività è chiamare setContentView(...)per gonfiare le viste da XML. Tale metodo non è disponibile se, ad esempio, la sottoclasse LinearLayout.

Quindi ho provato a gonfiare manualmente l'XML in questo modo:

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

Funziona, tranne per il fatto che l'XML che sto caricando ha LinearLayoutdichiarato l'elemento root. Ciò si traduce in LinearLayoutun bambino gonfiato di MyCompoundComponentcui già è un LinearLayout!! Quindi ora abbiamo un LinearLayout ridondante in mezzo MyCompoundComponente le viste di cui ha effettivamente bisogno.

Qualcuno può fornirmi un modo migliore per affrontare questo problema, evitando di avere LinearLayoutun'istanza ridondante ?


14
Adoro le domande dalle quali imparo qualcosa. Grazie.
Jeremy Logan,

Risposte:


101

Usa il tag merge come root XML

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

Controlla questo articolo.


10
Grazie mille! Questo è esattamente quello che stavo cercando. Incredibile come una domanda così lunga possa avere una risposta così breve. Eccellente!
Tom van Zummeren,

Che dire di includere questo layout di unione in orizzontale?
Kostadin,

2
@Timmmm hahahaha Ho posto questa domanda molto prima che esistesse l'editor visuale :)
Tom van Zummeren,

0

Penso che il modo in cui dovresti farlo, è usare il nome della tua classe come elemento radice XML:

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

E quindi fai in modo che la tua classe derivi da qualsiasi layout tu voglia usare. Si noti che se si utilizza questo metodo non si utilizza il gonfiatore di layout qui.

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 poi puoi usare la tua vista nei layout XML normalmente. Se vuoi creare la vista a livello di codice devi gonfiarla tu stesso:

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

Sfortunatamente questo non ti consente di farlo v = new MyView(context)perché non sembra esserci un modo per aggirare il problema dei layout nidificati, quindi questa non è davvero una soluzione completa. Puoi aggiungere un metodo come questo MyViewper renderlo un po 'più bello:

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

Disclaimer: potrei parlare di bollock completi.


Grazie! Penso che anche questa risposta sia corretta :) Ma tre anni fa, quando ho posto questa domanda, "unire" ha fatto anche il trucco!
Tom van Zummeren,

8
E poi qualcuno arriva e cerca di usare la tua vista personalizzata in un layout da qualche parte con just <com.example.views.MyView />e your setDatae le onFinishInflatechiamate iniziano a lanciare NPE, e non hai idea del perché.
Christopher Perry

Il trucco qui è utilizzare la vista personalizzata, quindi nel costruttore, gonfiare un layout che utilizza un tag di unione come radice. Ora puoi usarlo in XML o semplicemente rinnovandolo. Sono coperte tutte le basi, che è esattamente ciò che fanno insieme la domanda / risposta accettata. Ciò che non puoi fare, tuttavia, è più fare riferimento direttamente al layout. Ora è "posseduto" dal controllo personalizzato e dovrebbe essere utilizzato solo da quello nel costruttore. Ma se stai usando questo approccio, perché dovresti usarlo in qualunque altro posto? Non lo faresti.
Mark A. Donohoe,
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.