Создать собственный вид, раздув макет?

107

Я пытаюсь создать настраиваемый вид, который заменит определенный макет, который я использую в нескольких местах, но я изо всех сил пытаюсь это сделать.

В принципе, я хочу заменить это:

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

Этим:

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

Итак, мне не нужен настраиваемый макет, я хочу настраиваемый вид (у этого представления не должно быть дочерних элементов).

Единственное, что может измениться от одного экземпляра MyQuantityBox к другому, - это заголовок. Я бы очень хотел иметь возможность указать это в XML (как я это делаю в последней строке XML)

Как я могу это сделать? Должен ли я поместить RelativeLayout в XML-файл в / res / layout и раздуть его в моем классе MyBoxQuantity? Если да, как мне это сделать?

Спасибо!

nbarraille
источник
См. «Составные элементы управления» в Android и эту ссылку: stackoverflow.com/questions/1476371/…
greg7gkb

Ответы:

27

Да, ты можешь это сделать. RelativeLayout, LinearLayout и т. Д. Являются представлениями, поэтому настраиваемый макет является настраиваемым представлением. Просто подумайте, потому что, если вы хотите создать собственный макет, вы можете это сделать.

Что вы хотите сделать, так это создать составной элемент управления. Вы создадите подкласс RelativeLayout, добавите все наши компоненты в код (TextView и т. Д.), А в своем конструкторе вы сможете читать атрибуты, переданные из XML. Затем вы можете передать этот атрибут в свой заголовок TextView.

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

пухлый
источник
130

Немного устарел, но я подумал поделиться тем, как я это сделаю, основываясь на ответе chubbsondubs: Я использую FrameLayout(см. Документацию ), поскольку он используется для хранения одного представления и раздувает в нем представление из xml.

Код следующий:

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);
    }
}
Лиса
источник
13
Поскольку View класс имеет статический Inflate () метод не существует потребность в LayoutInflater.from ()
окраинное
1
Разве это не просто решение Йоханнеса отсюда: stackoverflow.com/questions/17836695/… Тем не менее, это раздувает другой макет внутри. Так что, я думаю, это не лучшее решение.
Тобиас Райх
3
это так, но решение Йоханнеса из 7.24.13, а разум - из 7.1.13 ... Кроме того, в моем решении используется FrameLayout, который должен содержать только одно представление (как написано в документе, на который есть ссылка в решении). Так что на самом деле он предназначен для использования в качестве заполнителя для представления. Я не знаю ни одного решения, которое бы не использовало заполнитель для завышенного представления.
Fox
Я не понимаю. Этот метод (inflate) возвращает представление, которое игнорируется. Похоже, вам нужно добавить его в текущее представление.
Джеффри Блаттман,
1
@ Джеффри Блаттман, пожалуйста, ознакомьтесь с методом View.inflate , мы используем этот (указав корень как thisтретий параметр)
V1raNi
36

Вот простая демонстрация для создания customview (составного представления) путем надувания из 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

Мы должны использовать mergeздесь, а не ConstraintLayoutпотому, что

Если мы используем ConstraintLayoutздесь, иерархия макета будет ConstraintLayout-> ConstraintLayout-> ImageView+ TextView=> у нас есть 1 избыточный ConstraintLayout=> не очень хорошо для производительности

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

Использование 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>

Результат

введите описание изображения здесь

Демо Github

Фан Ван Линь
источник
5
Это должен быть принятый или получивший наибольшее количество голосов ответ в этой ветке, поскольку в нем упоминается ненужная иерархия макетов.
Фарид
15

Используйте LayoutInflater, как показано ниже.

    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;
    }
Цунадзе
источник
Я пробовал то же самое. он работает нормально. но, когда я нажимаю btn1, он вызывает веб-службы, и после получения ответа от сервера я хочу обновить некоторый текст в определенной позиции text2 .. Любая помощь, пожалуйста?
harikrishnan
7

На практике я обнаружил, что вам нужно быть немного осторожным, особенно если вы постоянно используете немного xml. Предположим, например, что у вас есть таблица, в которой вы хотите создать строку таблицы для каждой записи в списке. Вы настроили некоторый xml:

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

Затем вы хотите создать его один раз для каждой строки с некоторым кодом. Предполагается, что вы определили родительский TableLayout myTable для присоединения строк.

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

Для ясного простого примера обработки rowButton.setOnClickListener (this) см. Onclicklistener для программно созданной кнопки .

Нил Таунсенд
источник
Привет, Нил, я пробовал то же самое. он работает нормально. но когда я нажимаю кнопку rowButton, он вызывает веб-службы, и после получения ответа от сервера я хочу обновить текст в определенной позиции rowText..any help pls?
harikrishnan
см. также мой вопрос. stackoverflow.com/questions/48719970/…
harikrishnan