Criar uma visualização personalizada inflando um layout?

107

Estou tentando criar um modo de exibição personalizado que substituiria um determinado layout que uso em vários lugares, mas estou lutando para fazer isso.

Basicamente, desejo substituir este:

<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 este:

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

Então, eu não quero um layout customizado, eu quero uma Visualização customizada (não deveria ser possível para esta visualização ter filho).

A única coisa que pode mudar de uma instância de MyQuantityBox para outra é o título. Eu gostaria muito de poder especificar isso no XML (como faço na última linha do XML)

Como posso fazer isso? Devo colocar o RelativeLayout em um arquivo XML em / res / layout e aumentá-lo na minha classe MyBoxQuantity? Se sim, como faço isso?

Obrigado!

Nbarraille
fonte
Consulte "Compound Controls" no Android e este link: stackoverflow.com/questions/1476371/…
greg7gkb

Respostas:

27

Sim, você pode fazer isso. RelativeLayout, LinearLayout, etc são visualizações, portanto, um layout personalizado é uma visualização personalizada. Apenas algo a se considerar porque se você quisesse criar um layout personalizado, você poderia.

O que você deseja fazer é criar um Controle Composto. Você criará uma subclasse de RelativeLayout, adicionará todos os nossos componentes no código (TextView, etc) e em seu construtor poderá ler os atributos passados ​​do XML. Você pode então passar esse atributo para o TextView do título.

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

chubbsondubs
fonte
130

Um pouco velho, mas pensei em compartilhar como faria, com base na resposta do chubbsondubs: Eu uso FrameLayout(veja a Documentação ), já que é usado para conter uma única visão, e aumente nela a visão do xml.

Código a seguir:

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);
    }
}
Raposa
fonte
13
Uma vez que a classe View tem o método estático inflate (), não há necessidade de LayoutInflater.from ()
afastado de
1
Essa não é apenas a solução de Johannes daqui: stackoverflow.com/questions/17836695/… Ainda assim, isso aumenta outro layout interno. Portanto, não é realmente a melhor solução, eu acho.
Tobias Reich
3
é, mas a solução de Johannes é de 7.24.13 e a mente era de 7.1.13 ... Além disso, minha solução usa FrameLayout, que supostamente contém apenas uma visualização (conforme escrito no documento referenciado na solução). Então, na verdade, ele deve ser usado como um espaço reservado para uma Visualização. Não conheço nenhuma solução que não envolva o uso de um espaço reservado para a visão inflada.
Fox de
Eu não entendo. Esse método (inflate) retorna uma visualização, que é ignorada. Parece que você precisa adicioná-lo à visualização atual.
Jeffrey Blattman
1
@Jeffrey Blattman, verifique o método View.inflate , usamos este (especificando a raiz como this, 3º parâmetro)
V1raNi
36

Aqui está uma demonstração simples para criar customview (compositeview) inflando de xml

attrs.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()
        }
    }
}

custom_layout.xml

Nós deve usar mergeaqui em vez de ConstraintLayoutporque

Se usarmos ConstraintLayoutaqui, a hierarquia de layout será ConstraintLayout-> ConstraintLayout-> ImageView+ TextView=> temos 1 redundante ConstraintLayout=> não muito bom para o desempenho

<?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>

Usando activity_main.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

insira a descrição da imagem aqui

Github demo

Phan Van Linh
fonte
4
Essa deve ser a resposta aceita ou mais votada neste tópico, pois menciona hierarquia de layout desnecessária.
Farid
15

Use o LayoutInflater conforme mostrado abaixo.

    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;
    }
Tsunaze
fonte
Eu tentei mesmo. está funcionando bem. mas, quando clico em btn1, ele chama os serviços da web e, após receber a resposta do servidor, quero atualizar algum texto na posição específica text2..any help pls?
harikrishnan de
7

Na prática, descobri que você precisa ter um pouco de cuidado, especialmente se estiver usando um pouco de xml repetidamente. Suponha, por exemplo, que você tenha uma tabela e deseja criar uma linha de tabela para cada entrada em uma lista. Você configurou algum xml:

Em 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>

Então você deseja criá-lo uma vez por linha com algum código. Ele assume que você definiu um TableLayout pai myTable ao qual anexar as linhas.

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 obter um exemplo simples e claro sobre como lidar com rowButton.setOnClickListener (this), consulte Onclicklistener para um botão criado programaticamente .

Neil Townsend
fonte
Oi Neil, eu tentei mesmo. está funcionando bem. mas, quando clico em rowButton, ele chama os serviços da web e, após receber a resposta do servidor, quero atualizar algum texto na posição específica rowText..any help pls?
Harikrishnan de
veja minha pergunta também. stackoverflow.com/questions/48719970/…
harikrishnan