Erstellen Sie eine benutzerdefinierte Ansicht, indem Sie ein Layout aufblasen?

106

Ich versuche, eine benutzerdefinierte Ansicht zu erstellen, die ein bestimmtes Layout ersetzt, das ich an mehreren Stellen verwende, aber ich habe Schwierigkeiten, dies zu tun.

Grundsätzlich möchte ich dies ersetzen:

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

Dadurch:

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

Ich möchte also kein benutzerdefiniertes Layout, sondern eine benutzerdefinierte Ansicht (es sollte nicht möglich sein, dass diese Ansicht ein untergeordnetes Element hat).

Das einzige, was sich von einer Instanz einer MyQuantityBox zu einer anderen ändern kann, ist der Titel. Ich würde dies sehr gerne im XML angeben können (wie in der letzten XML-Zeile).

Wie kann ich das machen? Sollte ich das RelativeLayout in einer XML-Datei in / res / layout ablegen und in meiner MyBoxQuantity-Klasse aufblasen? Wenn ja, wie mache ich das?

Vielen Dank!

nbarraille
quelle
Siehe "Compound Controls" in Android und diesen Link: stackoverflow.com/questions/1476371/…
greg7gkb

Antworten:

27

Ja, das kannst du machen. RelativeLayout, LinearLayout usw. sind Ansichten, sodass ein benutzerdefiniertes Layout eine benutzerdefinierte Ansicht ist. Nur etwas zu beachten, denn wenn Sie ein benutzerdefiniertes Layout erstellen möchten, können Sie dies tun.

Sie möchten ein zusammengesetztes Steuerelement erstellen. Sie erstellen eine Unterklasse von RelativeLayout, fügen alle unsere Komponenten in Code (TextView usw.) ein und können in Ihrem Konstruktor die aus dem XML übergebenen Attribute lesen. Sie können dieses Attribut dann an Ihren Titel TextView übergeben.

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

Chubbsondubs
quelle
130

Ein bisschen alt, aber ich dachte, ich würde teilen, wie ich es machen würde, basierend auf der Antwort von chubbsondubs: Ich verwende FrameLayout(siehe Dokumentation ), da es verwendet wird, um eine einzelne Ansicht zu enthalten, und fülle die Ansicht aus der XML-Datei hinein.

Code folgt:

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);
    }
}
Fuchs
quelle
13
Da die View-Klasse über die statische inflate () -Methode verfügt, ist LayoutInflater.from () nicht erforderlich
außerhalb des
1
Ist dies nicht nur die Lösung von Johannes von hier aus: stackoverflow.com/questions/17836695/… Trotzdem bläst dies ein anderes Layout auf. Es ist also nicht wirklich die beste Lösung, die ich vermuten würde.
Tobias Reich
3
es ist, aber Johannes Lösung ist von 7.24.13 und Geist war von 7.1.13 ... Außerdem verwendet meine Lösung FrameLayout, das nur eine Ansicht enthalten soll (wie in dem Dokument geschrieben, auf das in der Lösung verwiesen wird). Eigentlich soll es als Platzhalter für eine Ansicht verwendet werden. Ich kenne keine Lösung, bei der kein Platzhalter für die aufgeblasene Ansicht verwendet wird.
Fox
Ich verstehe es nicht Diese Methode (aufblasen) gibt eine Ansicht zurück, die ignoriert wird. Scheint, als müssten Sie es der aktuellen Ansicht hinzufügen.
Jeffrey Blattman
1
@ Jeffrey Blattman Schauen Sie sich bitte die View.inflate- Methode an. Wir verwenden diese (Angabe der Wurzel als this3. Parameter)
V1raNi
35

Hier ist eine einfache Demo zum Erstellen einer benutzerdefinierten Ansicht (Compoundview) durch Aufblasen aus 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

Wir sollten verwenden mergehier statt , ConstraintLayoutweil

Wenn wir ConstraintLayouthier verwenden, ist die Layouthierarchie ConstraintLayout-> ConstraintLayout-> ImageView+ TextView=> wir haben 1 redundante ConstraintLayout=> nicht sehr gut für die Leistung

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

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

Ergebnis

Geben Sie hier die Bildbeschreibung ein

Github-Demo

Phan Van Linh
quelle
4
Dies sollte entweder die akzeptierte oder die am häufigsten gewählte Antwort in diesem Thread sein, da unnötige Layouthierarchien erwähnt werden.
Farid
15

Verwenden Sie den LayoutInflater wie unten gezeigt.

    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
quelle
Ich habe es gleich versucht. es funktioniert gut. aber wenn ich auf btn1 klicke, ruft es Webdienste auf und nachdem ich eine Antwort vom Server erhalten habe, möchte ich etwas Text an einer bestimmten Position aktualisieren. text2..any help pls?
Harikrishnan
7

In der Praxis habe ich festgestellt, dass Sie etwas vorsichtig sein müssen, insbesondere wenn Sie wiederholt ein bisschen XML verwenden. Angenommen, Sie haben eine Tabelle, für die Sie für jeden Eintrag in einer Liste eine Tabellenzeile erstellen möchten. Sie haben einige XML-Dateien eingerichtet:

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

Dann möchten Sie es einmal pro Zeile mit etwas Code erstellen. Es wird davon ausgegangen, dass Sie ein übergeordnetes TableLayout myTable definiert haben, an das die Zeilen angehängt werden sollen.

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

Ein klares, einfaches Beispiel für die Behandlung von rowButton.setOnClickListener (this) finden Sie unter Onclicklistener für eine programmgesteuert erstellte Schaltfläche .

Neil Townsend
quelle
Hallo Neil, ich habe es gleich versucht. es funktioniert gut. Aber wenn ich auf rowButton klicke, werden Webdienste aufgerufen, und nach Erhalt der Antwort vom Server möchte ich Text an einer bestimmten Position aktualisieren. rowText..any help pls?
Harikrishnan
siehe auch meine frage stackoverflow.com/questions/48719970/…
harikrishnan