2009-09-25 17 views
129

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 TabWidget con 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 (haga clic en oyentes, lógica para llenar listas, etc.).Android - Escribir un componente personalizado (compuesto)

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

Traté de descubrir cómo se puede hacer esto, y encontré algo en la documentación de Android lo que les gusta llamar un "control compuesto". (Consulte http://developer.android.com/guide/topics/ui/custom-components.html#compound y vaya a la sección "Controles compuestos") Me gustaría crear dicho componente en base a un archivo XML que defina la estructura de vista.

En la documentación que dice:

Tenga en cuenta que al igual que con una actividad, puede utilizar ya sea el enfoque declarativo (basado en XML) para crear el componentes contenidos, o puede anidar ellos programáticamente desde tu 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 al setContentView(...) para inflar las vistas desde XML. Ese método no está disponible si, por ejemplo, la subclase LinearLayout.

así que traté de inflar el XML manualmente así:

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 estoy de carga tiene LinearLayout declarada como elemento raíz. Esto da como resultado que el LinearLayout inflado es un hijo de MyCompoundComponent que ya es un LinearLayout !! Entonces ahora tenemos LinearLayout redundante entre MyCompoundComponent y las vistas que realmente necesita.

¿Alguien me puede dar una mejor manera de abordar esto, evitando tener una instancia LinearLayout redundante?

+13

Me encantan las preguntas de las que aprendo algo. Gracias. –

+4

Hace poco escribió una entrada de blog acerca de esto: http://blog.jteam.nl/2009/10/08/exploring-the-world-of-android-part-3/ –

Respuesta

98

Uso fusionar etiqueta como su raíz XML

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

Check this article.

+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! –

+0

¿Qué hay de icluding este diseño de fusión en el paisaje? – Kostadin

+0

Pero esto significa que no puede usar el editor visual ... – Timmmm

0

Creo que la forma en que se supone que debemos hacer, es utilizar el nombre de clase como el elemento raíz XML:

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

Y luego haz que tu clase se derive del diseño que quieras usar. Tenga en cuenta que si está utilizando este método, no utilice utilice el inflador de disposición 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 entonces usted puede utilizar su punto de vista en los diseños de XML como normal.Si desea hacer la vista mediante programación que tiene para inflar usted mismo:

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

Desafortunadamente esto no te deja hacer v = new MyView(context) porque no parece ser una forma de evitar el problema de los diseños anidados, por lo que este ISN realmente una solución completa Se podría añadir un método como este para MyView para que sea un poco más agradable:

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

responsabilidad: puedo estar hablando bollocks completos.

+0

Gracias! Creo que esta respuesta también es correcta :) Pero hace tres años, cuando hice esta pregunta, "fusionar" también funcionó. –

+8

Y luego viene alguien e intenta usar su vista personalizada en un diseño en algún lugar con solo '' '' '' y sus llamadas 'setData' y' onFinishInflate' comienzan a lanzar NPE, y no tienes idea por qué. –

+0

El truco aquí es usar su vista personalizada, luego en el constructor, inflar un diseño que usa 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 la pregunta/respuesta aceptada juntas. Sin embargo, lo que no puede hacer es referirse directamente al diseño. Ahora es 'propiedad' del control personalizado y solo debe usarse en el constructor. Pero si usa este enfoque, ¿por qué debería usarlo en cualquier otro lugar? No lo harías – MarqueIV

Cuestiones relacionadas