¿Crear una vista personalizada inflando un diseño?

12 minutos de lectura

Estoy tratando de crear una vista personalizada que reemplace un determinado diseño que uso en varios lugares, pero me cuesta hacerlo.

Básicamente, quiero reemplazar esto:

<RelativeLayout
 android:id="@+id/dolphinLine"
 android:layout_width="fill_parent"
 android:layout_height="wrap_content"
    android:layout_centerInParent="true"
 android:background="@drawable/background_box_light_blue"
 android:padding="10dip"
 android:layout_margin="10dip">
  <TextView
   android:id="@+id/dolphinTitle"
   android:layout_width="200dip"
   android:layout_height="100dip"
   android:layout_alignParentLeft="true"
   android:layout_marginLeft="10dip"
   android:text="@string/my_title"
   android:textSize="30dip"
   android:textStyle="bold"
   android:textColor="#2E4C71"
   android:gravity="center"/>
  <Button
   android:id="@+id/dolphinMinusButton"
   android:layout_width="100dip"
   android:layout_height="100dip"
   android:layout_toRightOf="@+id/dolphinTitle"
   android:layout_marginLeft="30dip"
   android:text="@string/minus_button"
   android:textSize="70dip"
   android:textStyle="bold"
   android:gravity="center"
   android:layout_marginTop="1dip"
   android:background="@drawable/button_blue_square_selector"
   android:textColor="#FFFFFF"
   android:onClick="onClick"/>
  <TextView
   android:id="@+id/dolphinValue"
   android:layout_width="100dip"
   android:layout_height="100dip"
   android:layout_marginLeft="15dip"
   android:background="@android:drawable/editbox_background"
   android:layout_toRightOf="@+id/dolphinMinusButton"
   android:text="0"
   android:textColor="#2E4C71"
   android:textSize="50dip"
   android:gravity="center"
   android:textStyle="bold"
   android:inputType="none"/>
  <Button
   android:id="@+id/dolphinPlusButton"
   android:layout_width="100dip"
   android:layout_height="100dip"
   android:layout_toRightOf="@+id/dolphinValue"
   android:layout_marginLeft="15dip"
   android:text="@string/plus_button"
   android:textSize="70dip"
   android:textStyle="bold"
   android:gravity="center"
   android:layout_marginTop="1dip"
   android:background="@drawable/button_blue_square_selector"
   android:textColor="#FFFFFF"
   android:onClick="onClick"/>
</RelativeLayout>

Por esto:

<view class="com.example.MyQuantityBox"
    android:id="@+id/dolphinBox"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:myCustomAttribute="@string/my_title"/>

Por lo tanto, no quiero un diseño personalizado, quiero una vista personalizada (no debería ser posible que esta vista tenga un hijo).

Lo único que podría cambiar de una instancia de MyQuantityBox a otra es el título. Me gustaría mucho poder especificar esto en el XML (como lo hago en la última línea XML)

¿Cómo puedo hacer esto? ¿Debo poner RelativeLayout en un archivo XML en /res/layout e inflarlo en mi clase MyBoxQuantity? Si es así, ¿cómo lo hago?

¡Gracias!

  • Consulte “Controles compuestos” en Android y este enlace: stackoverflow.com/questions/1476371/…

    – greg7gkb

    9 de agosto de 2012 a las 20:26

avatar de usuario
zorro

Un poco viejo, pero pensé en compartir cómo lo haría, según la respuesta de chubbsondubs: uso FrameLayout (ver Documentación), ya que se usa para contener una sola vista e inflar en ella la vista del xml.

Código siguiente:

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

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public MyView(Context context) {
        super(context);
        initView();
    }

    private void initView() {
        inflate(getContext(), R.layout.my_view_layout, this);
    }
}

  • Dado que la clase View tiene un método inflado () estático, no hay necesidad de LayoutInflater.from ()

    – periférico

    22 de julio de 2013 a las 14:22

  • ¿No es esta solo la solución de Johannes de aquí: stackoverflow.com/questions/17836695/… Aún así, esto infla otro diseño interno. Así que no es realmente la mejor solución, supongo.

    – Tobías Reich

    18/10/2014 a las 14:59

  • lo es, pero la solución de Johannes es de 7.24.13, y la mente era de 7.1.13… Además, mi solución usa FrameLayout, que se supone que contiene solo una Vista (como está escrito en el documento al que se hace referencia en la solución). Entonces, en realidad está destinado a usarse como marcador de posición para una Vista. No conozco ninguna solución que no involucre el uso de un marcador de posición para la Vista inflada.

    – Zorro

    18/10/2014 a las 15:43


  • no lo entiendo Ese método (inflar) devuelve una vista, que se ignora. Parece que necesita agregarlo a la vista actual.

    –Jeffrey Blattman

    16 de junio de 2020 a las 17:56

  • @Jeffrey Blattman por favor echa un vistazo Ver.inflar método, usamos este (especificando la raíz como this3er parámetro)

    – V1raNi

    30 de julio de 2020 a las 21:17


avatar de usuario
Linh

Aquí hay una demostración simple para crear una vista personalizada (vista compuesta) inflando desde xml

atributos.xml

<resources>
    
    <declare-styleable name="CustomView">
        <attr format="string" name="text"/>
        <attr format="reference" name="image"/>
    </declare-styleable>
</resources>

CustomView.kt

class CustomView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
        ConstraintLayout(context, attrs, defStyleAttr) {

    init {
        init(attrs)
    }

    private fun init(attrs: AttributeSet?) {
        View.inflate(context, R.layout.custom_layout, this)

        val ta = context.obtainStyledAttributes(attrs, R.styleable.CustomView)
        try {
            val text = ta.getString(R.styleable.CustomView_text)
            val drawableId = ta.getResourceId(R.styleable.CustomView_image, 0)
            if (drawableId != 0) {
                val drawable = AppCompatResources.getDrawable(context, drawableId)
                image_thumb.setImageDrawable(drawable)
            }
            text_title.text = text
        } finally {
            ta.recycle()
        }
    }
}

diseño_personalizado.xml

Nosotros deberían utilizar merge aquí en lugar de ConstraintLayout porque

si usamos ConstraintLayout aquí, la jerarquía de diseño será ConstraintLayout->ConstraintLayout -> ImageView + TextView => tenemos 1 redundante ConstraintLayout => no muy bueno para el rendimiento

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:parentTag="android.support.constraint.ConstraintLayout">

    <ImageView
        android:id="@+id/image_thumb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        tools:ignore="ContentDescription"
        tools:src="@mipmap/ic_launcher" />

    <TextView
        android:id="@+id/text_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        app:layout_constraintEnd_toEndOf="@id/image_thumb"
        app:layout_constraintStart_toStartOf="@id/image_thumb"
        app:layout_constraintTop_toBottomOf="@id/image_thumb"
        tools:text="Text" />

</merge>

Utilizando
actividad_principal.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <your_package.CustomView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#f00"
        app:image="@drawable/ic_android"
        app:text="Android" />

    <your_package.CustomView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#0f0"
        app:image="@drawable/ic_adb"
        app:text="ADB" />

</LinearLayout>

Resultado

ingrese la descripción de la imagen aquí

demostración de Github

  • Esta debería ser la respuesta aceptada o la más votada en este hilo, ya que menciona una jerarquía de diseño innecesaria.

    – Farid

    27 de noviembre de 2019 a las 11:44

  • ¿Hay alguna manera de hacer referencia de alguna manera en la vista personalizada, todos los atributos disponibles en el texto y todos ellos de la imagen y de alguna manera conectarlos a la vista de texto y la vista de imagen sin hacerlo manualmente?

    – uberfrialdad

    22 oct 2020 a las 10:41

  • Después de una búsqueda de 2 horas de algún ejemplo práctico, encontré el tuyo. Es por eso que debe desplazarse hacia abajo más allá de la respuesta aceptada. Eso fue muy útil, ¡gracias!

    – Nickolay Yakovliev

    3 abr 2021 a las 13:54

  • Gracias por la mención de la tools:parentTagincluso después de 9 años de desarrollo de Android no lo sabía ❤️

    – Billda

    8 de mayo de 2021 a las 8:52

  • Gran respuesta, gracias. Para aquellos que prefieren usar el enlace de vista: CustomViewBinding.bind(View.inflate(context, R.layout.custom_layout, this))

    – giorgos.nl

    22 de junio de 2021 a las 12:59


Sí, usted puede hacer esto. RelativeLayout, LinearLayout, etc. son vistas, por lo que un diseño personalizado es una vista personalizada. Solo algo a considerar porque si quisiera crear un diseño personalizado, podría hacerlo.

Lo que quieres hacer es crear un control compuesto. Creará una subclase de RelativeLayout, agregará todos nuestros componentes en el código (TextView, etc.) y en su constructor podrá leer los atributos pasados ​​desde el XML. Luego puede pasar ese atributo a su título TextView.

http://developer.android.com/guide/topics/ui/custom-components.html

avatar de usuario
tsunaze

Use el LayoutInflater como se muestra a continuación.

public View myView() {
    View v; // Creating an instance for View Object
    LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    v = inflater.inflate(R.layout.myview, null);

    TextView text1 = v.findViewById(R.id.dolphinTitle);
    Button btn1 = v.findViewById(R.id.dolphinMinusButton);
    TextView text2 = v.findViewById(R.id.dolphinValue);
    Button btn2 = v.findViewById(R.id.dolphinPlusButton);

    return v;
}

avatar de usuario
Neil Townsend

En la práctica, descubrí que debe tener un poco de cuidado, especialmente si está usando un poco de xml repetidamente. Suponga, por ejemplo, que tiene una tabla en la que desea crear una fila de tabla para cada entrada en una lista. Ha configurado algunos xml:

En my_table_row.xml:

<?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent" android:id="@+id/myTableRow">
    <ImageButton android:src="@android:drawable/ic_menu_delete" android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/rowButton"/>
    <TextView android:layout_height="wrap_content" android:layout_width="wrap_content" android:textAppearance="?android:attr/textAppearanceMedium" android:text="TextView" android:id="@+id/rowText"></TextView>
</TableRow>

Luego, desea crearlo una vez por fila con algún código. Se supone que ha definido un TableLayout myTable principal para adjuntar las filas.

for (int i=0; i<numRows; i++) {
    /*
    * 1. Make the row and attach it to myTable. For some reason this doesn't seem
    * to return the TableRow as you might expect from the xml, so you need to
    * receive the View it returns and then find the TableRow and other items, as
    * per step 2.
    */
    LayoutInflater inflater = (LayoutInflater)getBaseContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View v =  inflater.inflate(R.layout.my_table_row, myTable, true);

    // 2. Get all the things that we need to refer to to alter in any way.
    TableRow    tr        = (TableRow)    v.findViewById(R.id.profileTableRow);
    ImageButton rowButton = (ImageButton) v.findViewById(R.id.rowButton);
    TextView    rowText   = (TextView)    v.findViewById(R.id.rowText);
                            
    // 3. Configure them out as you need to
    rowText.setText("Text for this row");
    rowButton.setId(i); // So that when it is clicked we know which one has been clicked!
    rowButton.setOnClickListener(this); // See note below ...           

    /*
    * To ensure that when finding views by id on the next time round this
    * loop (or later) gie lots of spurious, unique, ids.
    */
    rowText.setId(1000+i);
    tr.setId(3000+i);
}

Para ver un ejemplo sencillo y claro sobre el manejo de rowButton.setOnClickListener(this), consulte Onclicklistener para un botón creado mediante programación.

  • Hola Neil, he intentado lo mismo. funciona bien. pero, cuando hago clic en el botón de fila, llamará a los servicios web y después de recibir la respuesta del servidor, quiero actualizar algún texto en una posición particular de texto de fila… ¿alguna ayuda, por favor?

    – harikrishnan

    10 de febrero de 2018 a las 10:00

  • ver mi pregunta también. stackoverflow.com/questions/48719970/…

    – harikrishnan

    10 de febrero de 2018 a las 10:58

Hay múltiples respuestas que apuntan de la misma manera en diferentes enfoques, creo que el siguiente es el enfoque más simple sin usar bibliotecas de terceros, incluso puede usarlo usando Java.

en Kotlin;

Crear values/attr.xml

<resources>
    <declare-styleable name="DetailsView">
        <attr format="string" name="text"/>
        <attr format="string" name="value"/>
    </declare-styleable>
</resources>

Crear layout/details_view.xml archivo para su vista

<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/text_label"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="0.5"
            tools:text="Label" />

        <TextView
            android:id="@+id/text_value"
            android:layout_width="0dip"
            android:layout_height="wrap_content"
            android:layout_weight="0.5"
            tools:text="Value" />

    </LinearLayout>

</merge>

Crear el widget de vista personalizado DetailsView.kt

import android.content.Context
import android.content.res.TypedArray
import android.util.AttributeSet
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import com.payable.taponphone.R

class DetailsView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {

    private val attributes: TypedArray = context.obtainStyledAttributes(attrs, R.styleable.DetailsView)
    private val view: View = View.inflate(context, R.layout.details_view, this)

    init {
        view.findViewById<TextView>(R.id.text_label).text = attributes.getString(R.styleable.DetailsView_text)
        view.findViewById<TextView>(R.id.text_value).text = attributes.getString(R.styleable.DetailsView_value)
    }
}

Eso es todo, ahora puede llamar al widget en cualquier lugar de su aplicación como se muestra a continuación

<com.yourapp.widget.DetailsView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:text="Welcome"
    app:value="Feb" />

  • Hola Neil, he intentado lo mismo. funciona bien. pero, cuando hago clic en el botón de fila, llamará a los servicios web y después de recibir la respuesta del servidor, quiero actualizar algún texto en una posición particular de texto de fila… ¿alguna ayuda, por favor?

    – harikrishnan

    10 de febrero de 2018 a las 10:00

  • ver mi pregunta también. stackoverflow.com/questions/48719970/…

    – harikrishnan

    10 de febrero de 2018 a las 10:58

avatar de usuario
WerWet

Una vista personalizada simple usando Kotlin

Reemplace FrameLayout con cualquier vista que desee extender

/**
 * Simple Custom view
 */
class [email protected]
constructor(context: Context,
            attrs: AttributeSet? = null,
            defStyleAttr: Int = 0)
    : FrameLayout(context, attrs, defStyleAttr) {

    init {
        // Init View
        val rootView = (getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater)
                .inflate(R.layout.custom_layout, this, true)
        val titleView= rootView.findViewById(id.txtTitle)

        // Load Values from XML
        val attrsArray = getContext().obtainStyledAttributes(attrs, R.styleable.CutsomView, defStyleAttr, 0)
        val titleString = attrsArray.getString(R.styleable.cutomAttrsText)
        attrsArray.recycle()
    }
}

  • ¿Qué es R.styleable.CutsomView y R.styleable.cutomAttrsText?

    – Andrew Bekhtold

    5 de mayo de 2021 a las 10:12

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad