Android ListView dengan tata letak yang berbeda untuk setiap baris

348

Saya mencoba menentukan cara terbaik untuk memiliki satu ListView yang berisi tata letak yang berbeda untuk setiap baris. Saya tahu cara membuat baris kustom + adaptor array kustom untuk mendukung baris khusus untuk seluruh tampilan daftar, tetapi bagaimana saya bisa menerapkan banyak gaya baris yang berbeda di ListView?

w.donahue
sumber
1
pembaruan: Demo untuk tata letak beberapa baris menggunakan android RecyclerView code2concept.blogspot.in/2015/10/…
nitesh

Jawaban:

413

Karena Anda tahu berapa banyak jenis tata letak yang Anda miliki - Anda dapat menggunakan metode-metode itu.

getViewTypeCount() - metode ini mengembalikan informasi berapa banyak jenis baris yang Anda miliki dalam daftar Anda

getItemViewType(int position) - mengembalikan informasi jenis tata letak yang harus Anda gunakan berdasarkan posisi

Kemudian Anda mengembang tata letak hanya jika itu nol dan tentukan jenis menggunakan getItemViewType.

Lihatlah tutorial ini untuk informasi lebih lanjut.

Untuk mencapai beberapa optimasi dalam struktur yang telah Anda jelaskan dalam komentar saya sarankan:

  • Menyimpan tampilan dalam objek yang disebut ViewHolder. Ini akan meningkatkan kecepatan karena Anda tidak perlu menelepon findViewById()setiap kali dalam getViewmetode. Lihat List14 dalam demo API .
  • Buat satu tata letak generik yang akan menyesuaikan semua kombinasi properti dan menyembunyikan beberapa elemen jika posisi saat ini tidak memilikinya.

Saya harap itu akan membantu Anda. Jika Anda dapat memberikan beberapa rintisan XML dengan struktur data dan informasi bagaimana tepatnya Anda ingin memetakannya menjadi baris, saya akan dapat memberi Anda saran yang lebih tepat. Berdasarkan piksel.

Cristian
sumber
Terima kasih blognya sangat bagus, tetapi saya menambahkan kotak centang. Saya punya masalah karena akan memeriksa item pertama dan gulir Daftar. Anonim item aneh di mana diperiksa. Bisakah Anda memberikan solusi untuk itu? Terima kasih
Lalit Poptani
2
maaf telah menggali ini lagi, tetapi Anda benar-benar akan merekomendasikan memiliki file tata letak tunggal yang besar dan mengontrol visibilitas bagian-bagian itu, alih-alih memiliki file tata letak yang terpisah, yang masing-masing digembungkan menggunakan getItemViewType?
Makibo
2
Anda bisa melakukannya juga. Meskipun saya masih lebih suka cara terkena di sini. Itu membuat lebih jelas apa yang ingin Anda capai.
Cristian
Tetapi dalam beberapa strategi tata letak, kita tidak dapat melihat dudukan pengguna dengan benar karena setTag hanya dapat berisi satu dudukan tampilan dan setiap kali tata letak baris beralih lagi kita perlu memanggil findViewById (). Yang membuat tampilan daftar kinerja sangat rendah. Saya pribadi mengalaminya apa saran Anda?
pyus13
@ pyus13 Anda dapat mendeklarasikan sebanyak mungkin tampilan dalam satu tempat tampilan dan tidak perlu menggunakan setiap tampilan yang dinyatakan dalam tempat tampilan. Jika kode sampel diperlukan, tolong beri tahu saya, saya akan mempostingnya.
Ahmad Ali Nasir
62

Saya tahu cara membuat baris khusus + adaptor array kustom untuk mendukung baris khusus untuk seluruh tampilan daftar. Tetapi bagaimana satu tampilan daftar dapat mendukung banyak gaya baris yang berbeda?

Anda sudah tahu dasar-dasarnya. Anda hanya perlu mendapatkan adaptor khusus untuk mengembalikan tata letak / tampilan berbeda berdasarkan informasi baris / kursor yang disediakan.

A ListViewdapat mendukung beberapa gaya baris karena berasal dari AdapterView :

AdaptorView adalah tampilan yang anak-anaknya ditentukan oleh Adaptor.

Jika Anda melihat Adaptor , Anda akan melihat metode yang memperhitungkan penggunaan tampilan spesifik baris:

abstract int getViewTypeCount()
// Returns the number of types of Views that will be created ...

abstract int getItemViewType(int position)
// Get the type of View that will be created ...

abstract View getView(int position, View convertView, ViewGroup parent)
// Get a View that displays the data ...

Dua metode terakhir menyediakan posisi sehingga Anda dapat menggunakannya untuk menentukan jenis tampilan yang harus Anda gunakan untuk baris itu .


Tentu saja, Anda biasanya tidak menggunakan AdapterView dan Adaptor secara langsung, melainkan menggunakan atau berasal dari salah satu subclass mereka. Subkelas Adaptor dapat menambahkan fungsionalitas tambahan yang mengubah cara mendapatkan tata letak khusus untuk baris yang berbeda. Karena tampilan yang digunakan untuk baris tertentu digerakkan oleh adaptor, triknya adalah membuat adaptor mengembalikan tampilan yang diinginkan untuk baris tertentu. Cara melakukannya berbeda tergantung pada adaptor spesifik.

Misalnya, untuk menggunakan ArrayAdapter ,

  • ganti getView()untuk mengembang, mengisi, dan mengembalikan tampilan yang diinginkan untuk posisi yang diberikan. The getView()Metode meliputi kesempatan reuse pandangan melalui convertViewparameter.

Tetapi untuk menggunakan turunan dari CursorAdapter ,

  • ganti newView()untuk mengembang, mengisi, dan mengembalikan tampilan yang diinginkan untuk keadaan kursor saat ini (yaitu "baris" saat ini) [Anda juga perlu mengganti bindViewagar widget dapat menggunakan kembali tampilan]

Namun, untuk menggunakan SimpleCursorAdapter ,

  • mendefinisikan a SimpleCursorAdapter.ViewBinderdengan setViewValue()metode untuk mengembang, mengisi, dan mengembalikan tampilan yang diinginkan untuk baris yang diberikan (keadaan kursor saat ini) dan data "kolom". Metode ini hanya dapat mendefinisikan tampilan "khusus" dan tunduk pada perilaku standar SimpleCursorAdapter untuk binding "normal".

Cari contoh / tutorial khusus untuk jenis adaptor yang akhirnya Anda gunakan.

Bert F
sumber
Adakah pemikiran tentang tipe adaptor mana yang terbaik untuk implementasi adaptor yang fleksibel? Saya menambahkan pertanyaan lain di papan tulis untuk ini.
Androider
1
@Androider - "terbaik untuk fleksibel" sangat terbuka - tidak ada akhir, semua-semua kelas yang akan memenuhi setiap kebutuhan; itu adalah hierarki yang kaya - turun ke apakah ada fungsionalitas dalam subkelas yang berguna untuk tujuan Anda. Jika demikian, mulailah dengan subclass itu; jika tidak, naik ke BaseAdapter. Berasal dari BaseAdapter akan menjadi yang paling "fleksibel", tetapi akan menjadi yang terburuk dalam penggunaan ulang dan kematangan kode karena tidak memanfaatkan pengetahuan dan kematangan yang sudah dimasukkan ke dalam adaptor lain. BaseAdapterada untuk konteks non-standar di mana adaptor lain tidak cocok.
Bert F
3
+1 untuk perbedaan yang baik antara CursorAdapterdan SimpleCursorAdapter.
Giulio Piancastelli
1
juga perhatikan bahwa jika Anda menimpa ArrayAdapter, tidak peduli tata letak apa yang Anda berikan pada konstruktor, asalkan getView()mengembang dan mengembalikan jenis tata letak yang tepat
woojoo666
1
Perlu dicatat bahwa getViewTypeCount()hanya dipicu sekali setiap kali Anda menelepon ListView.setAdapter(), bukan untuk setiap Adapter.notifyDataSetChanged().
hidro
43

Lihatlah kode di bawah ini.

Pertama, kami membuat tata letak khusus. Dalam hal ini, empat tipe.

even.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ff500000"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/white"
        android:layout_width="match_parent"
        android:layout_gravity="center"
        android:textSize="24sp"
        android:layout_height="wrap_content" />

 </LinearLayout>

odd.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ff001f50"
    android:gravity="right"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/white"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:textSize="28sp"
        android:layout_height="wrap_content"  />

 </LinearLayout>

white.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ffffffff"
    android:gravity="right"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/black"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:textSize="28sp"
        android:layout_height="wrap_content"   />

 </LinearLayout>

black.xml

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:background="#ff000000"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/text"
        android:textColor="@android:color/white"
        android:layout_width="wrap_content"
        android:layout_gravity="center"
        android:textSize="33sp"
        android:layout_height="wrap_content"   />

 </LinearLayout>

Lalu, kami membuat item listview. Dalam kasus kami, dengan string dan tipe.

public class ListViewItem {
        private String text;
        private int type;

        public ListViewItem(String text, int type) {
            this.text = text;
            this.type = type;
        }

        public String getText() {
            return text;
        }

        public void setText(String text) {
            this.text = text;
        }

        public int getType() {
            return type;
        }

        public void setType(int type) {
            this.type = type;
        }

    }

Setelah itu, kami membuat view view. Ini sangat disarankan karena OS Android menyimpan referensi tata letak untuk menggunakan kembali item Anda saat item itu hilang dan muncul kembali di layar. Jika Anda tidak menggunakan pendekatan ini, setiap kali item Anda muncul di layar, OS Android akan membuat yang baru dan menyebabkan aplikasi Anda bocor memori.

public class ViewHolder {
        TextView text;

        public ViewHolder(TextView text) {
            this.text = text;
        }

        public TextView getText() {
            return text;
        }

        public void setText(TextView text) {
            this.text = text;
        }

    }

Terakhir, kami membuat adaptor khusus kami menggantikan getViewTypeCount () dan getItemViewType (posisi int).

public class CustomAdapter extends ArrayAdapter {

        public static final int TYPE_ODD = 0;
        public static final int TYPE_EVEN = 1;
        public static final int TYPE_WHITE = 2;
        public static final int TYPE_BLACK = 3;

        private ListViewItem[] objects;

        @Override
        public int getViewTypeCount() {
            return 4;
        }

        @Override
        public int getItemViewType(int position) {
            return objects[position].getType();
        }

        public CustomAdapter(Context context, int resource, ListViewItem[] objects) {
            super(context, resource, objects);
            this.objects = objects;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {

            ViewHolder viewHolder = null;
            ListViewItem listViewItem = objects[position];
            int listViewItemType = getItemViewType(position);


            if (convertView == null) {

                if (listViewItemType == TYPE_EVEN) {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_even, null);
                } else if (listViewItemType == TYPE_ODD) {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_odd, null);
                } else if (listViewItemType == TYPE_WHITE) {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_white, null);
                } else {
                    convertView = LayoutInflater.from(getContext()).inflate(R.layout.type_black, null);
                }

                TextView textView = (TextView) convertView.findViewById(R.id.text);
                viewHolder = new ViewHolder(textView);

                convertView.setTag(viewHolder);

            } else {
                viewHolder = (ViewHolder) convertView.getTag();
            }

            viewHolder.getText().setText(listViewItem.getText());

            return convertView;
        }

    }

Dan aktivitas kami adalah seperti ini:

private ListView listView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main); // here, you can create a single layout with a listview

        listView = (ListView) findViewById(R.id.listview);

        final ListViewItem[] items = new ListViewItem[40];

        for (int i = 0; i < items.length; i++) {
            if (i == 4) {
                items[i] = new ListViewItem("White " + i, CustomAdapter.TYPE_WHITE);
            } else if (i == 9) {
                items[i] = new ListViewItem("Black " + i, CustomAdapter.TYPE_BLACK);
            } else if (i % 2 == 0) {
                items[i] = new ListViewItem("EVEN " + i, CustomAdapter.TYPE_EVEN);
            } else {
                items[i] = new ListViewItem("ODD " + i, CustomAdapter.TYPE_ODD);
            }
        }

        CustomAdapter customAdapter = new CustomAdapter(this, R.id.text, items);
        listView.setAdapter(customAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView adapterView, View view, int i, long l) {
                Toast.makeText(getBaseContext(), items[i].getText(), Toast.LENGTH_SHORT).show();
            }
        });

    }
}

sekarang buat listview di dalam mainactivity.xml seperti ini

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout 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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    tools:context="com.example.shivnandan.gygy.MainActivity">

    <android.support.design.widget.AppBarLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay" />

    </android.support.design.widget.AppBarLayout>

    <include layout="@layout/content_main" />

    <ListView
        android:layout_width="match_parent"

        android:layout_height="match_parent"

        android:id="@+id/listView"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true"


        android:layout_marginTop="100dp" />

</android.support.design.widget.CoordinatorLayout>
pisau
sumber
<include layout = "@ layout / content_main" /> dari mana
asalnya
Saya hanya membutuhkan klausa (convertView == null). Tidak perlu 'penonton'
Jomme
14

Di adaptor larik kustom Anda, Anda mengganti metode getView (), seperti yang mungkin Anda kenal. Maka yang harus Anda lakukan adalah menggunakan pernyataan switch atau pernyataan if untuk mengembalikan tampilan kustom tertentu tergantung pada argumen posisi yang diteruskan ke metode getView. Android pintar karena hanya akan memberi Anda convertView dari jenis yang sesuai untuk posisi / baris Anda; Anda tidak perlu memeriksa apakah jenisnya benar. Anda dapat membantu Android dalam hal ini dengan mengganti metode getItemViewType () dan getViewTypeCount () dengan tepat.

Jems
sumber
4

Jika kita perlu menunjukkan berbagai jenis tampilan dalam tampilan daftar maka bagus untuk menggunakan getViewTypeCount () dan getItemViewType () dalam adaptor alih-alih beralih tampilan VIEW.GONE dan VIEW.VISIBLE bisa menjadi tugas yang sangat mahal di dalam getView () yang akan mempengaruhi daftar gulir.

Silakan periksa ini untuk penggunaan getViewTypeCount () dan getItemViewType () di Adapter.

Tautan: the use-of-getviewtypecount

kyogs
sumber
1

ListView dimaksudkan untuk kasus penggunaan sederhana seperti tampilan statis yang sama untuk semua item baris.
Karena Anda harus membuat ViewHolders dan memanfaatkan secara signifikan getItemViewType(), dan secara dinamis menampilkan tata letak item baris yang berbeda xml, Anda harus mencoba melakukan itu menggunakan RecyclerView , yang tersedia di Android API 22. Ia menawarkan dukungan dan struktur yang lebih baik untuk berbagai jenis tampilan.

Lihatlah tutorial ini tentang cara menggunakan RecyclerView untuk melakukan apa yang Anda cari.

Phileo99
sumber