Utworzyć widok niestandardowy, rozszerzając układ?


107

Próbuję utworzyć niestandardowy widok, który zastąpiłby pewien układ, którego używam w wielu miejscach, ale z trudem to robię.

Zasadniczo chcę to zastąpić:

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

Przez to:

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

Więc nie chcę niestandardowego układu, chcę niestandardowego widoku (nie powinno być możliwe, aby ten widok miał dziecko).

Jedyną rzeczą, która może się zmienić z jednej instancji MyQuantityBox na inną, jest tytuł. Bardzo chciałbym móc to określić w XML (tak jak w ostatniej linii XML)

W jaki sposób mogę to zrobić? Czy powinienem umieścić RelativeLayout w pliku XML w / res / layout i zawyżać go w mojej klasie MyBoxQuantity? Jeśli tak, jak mam to zrobić?

Dzięki!


Zobacz „Sterowanie złożone” w systemie Android i to łącze: stackoverflow.com/questions/1476371/ ...
greg7gkb

Odpowiedzi:


27

Tak, możesz to zrobić. RelativeLayout, LinearLayout itp. Są widokami, więc układ niestandardowy jest widokiem niestandardowym. Po prostu coś do rozważenia, ponieważ jeśli chcesz stworzyć niestandardowy układ, możesz.

To, co chcesz zrobić, to utworzyć kontrolę złożoną. Utworzysz podklasę RelativeLayout, dodasz wszystkie nasze komponenty w kodzie (TextView itp.), Aw konstruktorze możesz odczytać atrybuty przekazane z XML. Następnie możesz przekazać ten atrybut do swojego tytułu TextView.

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


130

Trochę stary, ale pomyślałem, że podzielę się tym, jak to zrobię, na podstawie odpowiedzi chubbsondubs: używam FrameLayout(patrz Dokumentacja ), ponieważ jest używany do przechowywania pojedynczego widoku i nadmuchuję do niego widok z xml.

Kod następujący:

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
Ponieważ klasa View ma statyczną metodę inflate (), nie ma potrzeby stosowania LayoutInflater.from ()
odstające

1
Czy to nie jest tylko rozwiązanie Johannesa z tego miejsca: stackoverflow.com/questions/17836695/… To jednak nadyma inny układ w środku. Więc tak naprawdę nie jest to najlepsze rozwiązanie, jak sądzę.
Tobias Reich

3
jest, ale rozwiązanie Johannesa pochodzi z 7.24.13, a umysł z 7.1.13 ... Ponadto moje rozwiązanie wykorzystuje FrameLayout, który powinien zawierać tylko jeden widok (jak napisano w dokumencie, do którego odwołuje się rozwiązanie). Tak więc w rzeczywistości ma być używany jako symbol zastępczy dla widoku. Nie znam żadnego rozwiązania, które nie obejmuje użycia symbolu zastępczego dla zawyżonego widoku.
Fox

Nie rozumiem. Ta metoda (inflate) zwraca widok, który jest ignorowany. Wygląda na to, że musisz dodać go do bieżącego widoku.
Jeffrey Blattman

1
@Jeffrey Blattman, sprawdź metodę View.inflate , używamy tej (określając root jako this, trzeci parametr)
V1raNi

36

Oto proste demo do tworzenia widoku niestandardowego (widoku złożonego) przez zawyżanie z 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

My powinniśmy używać mergetutaj zamiast ConstraintLayoutponieważ

Jeśli użyjemy ConstraintLayouttutaj, hierarchia układu będzie ConstraintLayout-> ConstraintLayout-> ImageView+ TextView=> mamy 1 nadmiarowy ConstraintLayout=> niezbyt dobry pod względem wydajności

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

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

Wynik

wprowadź opis obrazu tutaj

Demo Github


4
Powinna to być odpowiedź zaakceptowana lub najczęściej głosowana w tym wątku, ponieważ wspomina o niepotrzebnej hierarchii układu.
Farid

15

Użyj LayoutInflater, jak pokazano poniżej.

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

Próbowałem tego samego. działa dobrze. ale kiedy kliknę btn1, będzie wywoływać usługi internetowe i po otrzymaniu odpowiedzi z serwera, chcę zaktualizować jakiś tekst w określonej pozycji text2..any pls pomocy?
harikrishnan

7

W praktyce odkryłem, że musisz być trochę ostrożny, zwłaszcza jeśli wielokrotnie używasz fragmentu XML. Załóżmy na przykład, że masz tabelę, w której chcesz utworzyć wiersz tabeli dla każdego wpisu na liście. Skonfigurowałeś XML:

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

Następnie chcesz utworzyć go raz w wierszu za pomocą kodu. Zakłada się, że zdefiniowałeś element nadrzędny TableLayout myTable, do którego chcesz dołączyć Rows.

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

Aby zapoznać się z jasnym, prostym przykładem obsługi rowButton.setOnClickListener (this), zobacz Onclicklistener dla programowo utworzonego przycisku .


Cześć Neil, próbowałem tego samego. działa dobrze. ale kiedy kliknę rowButton, będzie wywoływać usługi internetowe i po otrzymaniu odpowiedzi z serwera, chcę zaktualizować jakiś tekst w określonej pozycji rowText..any pls pomocy?
harikrishnan

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.