Android: escritura de un componente personalizado (compuesto)

132

La aplicación de Android que estoy desarrollando actualmente tiene una actividad principal que ha crecido bastante. Esto se debe principalmente a que contiene un TabWidgetcon 3 pestañas. Cada pestaña tiene bastantes componentes. La actividad tiene que controlar todos esos componentes a la vez. Así que creo que puedes imaginar que esta Actividad tiene como 20 campos (un campo para casi todos los componentes). También contiene mucha lógica (escuchas de clics, lógica para llenar listas, etc.).

Lo que normalmente hago en marcos basados ​​en componentes es dividir todo en componentes personalizados. Cada componente personalizado tendría una clara responsabilidad. Contendría su propio conjunto de componentes y toda otra lógica relacionada con ese componente.

Traté de averiguar cómo se puede hacer esto, y encontré algo en la documentación de Android que les gusta llamar un "Control Compuesto". (Consulte http://developer.android.com/guide/topics/ui/custom-components.html#compound y desplácese a la sección "Controles compuestos") Me gustaría crear dicho componente basado en un archivo XML que defina Ver estructura.

En la documentación dice:

Tenga en cuenta que al igual que con una actividad, puede usar el enfoque declarativo (basado en XML) para crear los componentes contenidos, o puede anidarlos mediante programación desde su código.

Bueno, eso es una buena noticia! ¡El enfoque basado en XML es exactamente lo que quiero! Pero no dice cómo hacerlo, excepto que es "como con una Actividad" ... Pero lo que hago en una Actividad es llamar setContentView(...)a inflar las vistas desde XML. Ese método no está disponible si, por ejemplo, la subclase LinearLayout.

Así que intenté inflar el XML manualmente de esta manera:

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

Esto funciona, excepto por el hecho de que el XML que estoy cargando se ha LinearLayoutdeclarado como el elemento raíz. ¡Esto da como resultado que el LinearLayoutniño sea un niño del MyCompoundComponentque ya es un hijo LinearLayout! Así que ahora tenemos un LinearLayout redundante en el medio MyCompoundComponenty las vistas que realmente necesita.

¿Puede alguien proporcionarme una mejor manera de abordar esto, evitando tener una LinearLayoutinstancia redundante ?

Tom van Zummeren
fuente
14
Me encantan las preguntas de las que aprendo algo. Gracias.
Jeremy Logan
55
Recientemente escribí una entrada de blog sobre esto: blog.jteam.nl/2009/10/08/exploring-the-world-of-android-part-3
Tom van Zummeren

Respuestas:

101

Use la etiqueta merge como su raíz XML

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

Mira este artículo.

bhatt4982
fuente
10
¡Muchas gracias! Eso es exactamente lo que estaba buscando. Es sorprendente cómo una pregunta tan larga puede tener una respuesta tan corta. ¡Excelente!
Tom van Zummeren
¿Qué hay de incluir este diseño de fusión en el paisaje?
Kostadin
2
@Timmmm jajajaja Hice esta pregunta mucho antes de que el editor visual existiera :)
Tom van Zummeren
0

Creo que la forma en que se supone que debes hacerlo es usar el nombre de tu clase como elemento raíz XML:

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

Y luego haga que su clase se derive del diseño que desee usar. Tenga en cuenta que si está utilizando este método , no utiliza el inflador de diseño aquí.

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

Y luego puede usar su vista en diseños XML de manera normal. Si desea hacer la vista mediante programación, debe inflarla usted mismo:

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

Desafortunadamente, esto no le permite hacerlo v = new MyView(context)porque no parece haber una solución al problema de los diseños anidados, por lo que esta no es realmente una solución completa. Puede agregar un método como este MyViewpara hacerlo un poco más agradable:

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

Descargo de responsabilidad: puedo estar hablando de bollocks completos.

Timmmm
fuente
¡Gracias! Creo que esta respuesta también es correcta :) Pero hace tres años, cuando hice esta pregunta, "fusionar" ¡también funcionó!
Tom van Zummeren
8
Y entonces alguien viene y trata de usar la vista personalizada en algún lugar en un diseño con sólo <com.example.views.MyView />y su setDatay onFinishInflatellamadas de empezar a tirar NPE, y no tiene idea de por qué.
Christopher Perry
El truco aquí es usar su vista personalizada, luego, en el constructor, inflar un diseño que use una etiqueta de fusión como raíz. Ahora puede usarlo en XML, o simplemente actualizándolo. Todas las bases están cubiertas, que es exactamente lo que hacen juntas la pregunta / respuesta aceptada. Sin embargo, lo que no puede hacer es consultar el diseño directamente. Ahora es 'propiedad' del control personalizado y solo debe ser usado por eso en el constructor. Pero si está usando este enfoque, ¿por qué necesitaría usarlo en otro lugar? No lo harías
Mark A. Donohoe