ListView Android ที่มีเลย์เอาต์ที่แตกต่างกันสำหรับแต่ละแถว


348

ฉันพยายามกำหนดวิธีที่ดีที่สุดในการมี ListView เดียวที่มีเค้าโครงที่แตกต่างกันสำหรับแต่ละแถว ฉันรู้วิธีสร้างอะแด็ปเตอร์แถวที่กำหนดเอง + อะเรย์ที่กำหนดเองเพื่อสนับสนุนแถวที่กำหนดเองสำหรับมุมมองรายการทั้งหมด แต่ฉันจะใช้สไตล์แถวที่แตกต่างกันใน ListView ได้อย่างไร


1
ปรับปรุง: การสาธิตสำหรับรูปแบบแถวหลายแถวโดยใช้ RecyclerView ของ android code2concept.blogspot.in/2015/10//
nitesh

คำตอบ:


413

เมื่อคุณรู้ว่าคุณมีเลย์เอาต์กี่ประเภทคุณสามารถใช้วิธีการเหล่านี้ได้

getViewTypeCount() - วิธีการนี้จะคืนค่าข้อมูลจำนวนแถวที่คุณมีในรายการของคุณ

getItemViewType(int position) - ส่งคืนข้อมูลประเภทโครงร่างที่คุณควรใช้ตามตำแหน่ง

getItemViewTypeแล้วคุณจะขยายรูปแบบเฉพาะในกรณีที่มันโมฆะและกำหนดประเภทการใช้

ดูบทช่วยสอนนี้สำหรับข้อมูลเพิ่มเติม

เพื่อให้บรรลุการเพิ่มประสิทธิภาพบางอย่างในโครงสร้างที่คุณได้อธิบายไว้ในความคิดเห็นฉันอยากจะแนะนำ:

  • ViewHolderมุมมองการจัดเก็บในวัตถุที่เรียกว่า มันจะเพิ่มความเร็วเพราะคุณจะไม่ต้องโทรfindViewById()ทุกครั้งในgetViewวิธีการ ดูList14 ในการสาธิต
  • สร้างเลย์เอาต์ทั่วไปหนึ่งรายการที่จะสอดคล้องกับการรวมกันของคุณสมบัติและซ่อนองค์ประกอบบางอย่างหากตำแหน่งปัจจุบันไม่มี

ฉันหวังว่าจะช่วยคุณ หากคุณสามารถให้ XML กับโครงสร้างข้อมูลของคุณและข้อมูลที่คุณต้องการแมปลงในแถวฉันจะสามารถให้คำแนะนำที่แม่นยำยิ่งขึ้นได้ ตามพิกเซล


ขอบคุณบล็อกดีมาก แต่ฉันเพิ่มช่องทำเครื่องหมาย ฉันมีปัญหาในการที่จะตรวจสอบรายการแรกและเลื่อนรายการ รายการที่ไม่ระบุชื่อแปลก ๆ ที่ได้รับการตรวจสอบ คุณช่วยหาทางออกให้ได้ไหม ขอบคุณ
Lalit Poptani

2
ขออภัยที่ขุดขึ้นมาใหม่ แต่คุณอยากแนะนำให้มีไฟล์เลย์เอาต์ขนาดใหญ่เดี่ยวและควบคุมการมองเห็นของส่วนต่าง ๆ แทนที่จะมีไฟล์เลย์เอาต์แยกต่างหากซึ่งทำให้พองตามลำดับโดยใช้ getItemViewType?
Makibo

2
คุณก็ทำได้เช่นกัน แม้ว่าฉันจะยังคงชอบวิธีการเปิดเผยที่นี่ มันทำให้ชัดเจนในสิ่งที่คุณต้องการบรรลุ
Cristian

แต่ในกลยุทธ์หลายเลย์เอาต์เราไม่สามารถใช้ตัวยึดมุมมองของผู้ใช้ได้อย่างถูกต้องเนื่องจาก setTag สามารถมีตัวยึดมุมมองเดียวเท่านั้นและเมื่อใดก็ตามที่สวิตช์แถวเลย์เอาต์อีกครั้งเราต้องเรียกใช้ findViewById () ซึ่งทำให้ listview มีประสิทธิภาพต่ำมาก ฉันมีประสบการณ์ส่วนตัวเกี่ยวกับคำแนะนำของคุณเกี่ยวกับอะไร
pyus13

@ pyus13 คุณสามารถประกาศมุมมองได้มากเท่าที่คุณต้องการในผู้ถือมุมมองเดียวและไม่จำเป็นต้องใช้ทุกมุมมองที่ประกาศในมุมมองผู้ถือ หากต้องการรหัสตัวอย่างโปรดแจ้งให้เราทราบฉันจะโพสต์
Ahmad Ali Nasir

62

ฉันรู้วิธีสร้างอะแดปเตอร์แถวที่กำหนดเอง + อะแดปเตอร์อาเรย์ที่กำหนดเองเพื่อสนับสนุนแถวที่กำหนดเองสำหรับมุมมองรายการทั้งหมด แต่ listview หนึ่งสามารถสนับสนุนสไตล์แถวที่แตกต่างกันได้อย่างไร?

คุณรู้พื้นฐานแล้ว คุณเพียงแค่ต้องให้อแด็ปเตอร์ที่กำหนดเองของคุณเพื่อส่งคืนโครงร่าง / มุมมองที่แตกต่างกันตามข้อมูลแถว / เคอร์เซอร์ที่มีให้

A ListViewสามารถรองรับสไตล์แถวได้หลายรูปแบบเนื่องจากมาจากAdapterView :

AdapterView เป็นมุมมองที่ลูกถูกกำหนดโดยอะแดปเตอร์

หากคุณดูอะแดปเตอร์คุณจะเห็นวิธีการที่บัญชีใช้มุมมองเฉพาะแถว:

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

สองวิธีหลังให้ตำแหน่งเพื่อให้คุณสามารถใช้เพื่อกำหนดประเภทของมุมมองที่คุณควรใช้สำหรับแถวนั้น


แน่นอนว่าโดยทั่วไปคุณจะไม่ใช้ AdapterView และ Adapter โดยตรง แต่ควรใช้หรือสืบทอดมาจากคลาสย่อยของพวกเขา คลาสย่อยของอแด็ปเตอร์อาจเพิ่มฟังก์ชันการทำงานเพิ่มเติมที่เปลี่ยนวิธีรับเค้าโครงที่กำหนดเองสำหรับแถวที่ต่างกัน เนื่องจากมุมมองที่ใช้สำหรับแถวที่กำหนดถูกขับเคลื่อนโดยอะแด็ปเตอร์เคล็ดลับคือการรับอะแดปเตอร์เพื่อส่งกลับมุมมองที่ต้องการสำหรับแถวที่กำหนด วิธีการทำเช่นนี้แตกต่างกันไปขึ้นอยู่กับอะแดปเตอร์เฉพาะ

ตัวอย่างเช่นการใช้ArrayAdapter ,

  • แทนที่getView()เพื่อขยายเติมและส่งคืนมุมมองที่ต้องการสำหรับตำแหน่งที่กำหนด getView()วิธีการรวมถึงมุมมองที่มีโอกาสนำมาใช้ใหม่ผ่านconvertViewพารามิเตอร์

แต่การที่จะใช้ตราสารอนุพันธ์ของCursorAdapter ,

  • แทนที่newView()เพื่อขยายเติมและกลับสู่มุมมองที่ต้องการสำหรับสถานะเคอร์เซอร์ปัจจุบัน (เช่น "แถว" ปัจจุบัน) [คุณยังต้องแทนที่bindViewเพื่อให้วิดเจ็ตสามารถใช้มุมมองใหม่]

อย่างไรก็ตามเพื่อให้การใช้SimpleCursorAdapter ,

  • กำหนดSimpleCursorAdapter.ViewBinderด้วยsetViewValue()วิธีการขยายเติมและส่งกลับมุมมองที่ต้องการสำหรับแถวที่กำหนด (สถานะเคอร์เซอร์ปัจจุบัน) และข้อมูล "คอลัมน์" วิธีนี้สามารถกำหนดมุมมอง "พิเศษ" และเลื่อนไปสู่พฤติกรรมมาตรฐานของ SimpleCursorAdapter สำหรับการผูก "ปกติ"

ค้นหาตัวอย่าง / บทช่วยสอนเฉพาะเกี่ยวกับประเภทของอะแดปเตอร์ที่คุณใช้


มีความคิดเห็นเกี่ยวกับอะแดปเตอร์ประเภทใดบ้างที่ดีที่สุดสำหรับการปรับใช้อะแดปเตอร์อย่างยืดหยุ่น ฉันกำลังเพิ่มคำถามอื่นบนกระดานสำหรับสิ่งนี้
Androider

1
@Androider - "ดีที่สุดสำหรับความยืดหยุ่น" นั้นเปิดกว้างมาก - ไม่มีที่สิ้นสุดทั้งหมดเป็นคลาสทั้งหมดที่จะตอบสนองทุกความต้องการ มันเป็นลำดับชั้นที่สมบูรณ์ - มันลงมาว่ามีฟังก์ชันการทำงานในคลาสย่อยที่มีประโยชน์ต่อวัตถุประสงค์ของคุณหรือไม่ ถ้าเป็นเช่นนั้นเริ่มต้นด้วยคลาสย่อยนั้น BaseAdapterถ้าไม่ย้ายขึ้นไป การได้รับจาก BaseAdapter จะเป็น "ยืดหยุ่น" มากที่สุด แต่จะแย่ที่สุดในการใช้ซ้ำและการกำหนดรหัสเนื่องจากมันไม่ได้ใช้ประโยชน์จากความรู้และวุฒิภาวะที่ใส่ลงในอะแดปเตอร์อื่น BaseAdapterมีสำหรับบริบทที่ไม่เป็นมาตรฐานหรือที่อะแดปเตอร์อื่นไม่พอดี
Bert F

3
+1 สำหรับความแตกต่างระหว่างดีและCursorAdapter SimpleCursorAdapter
Giulio Piancastelli

1
โปรดทราบด้วยว่าหากคุณแทนที่ArrayAdapterมันไม่สำคัญว่าเค้าโครงใดที่คุณให้กับตัวสร้างตราบใดที่getView()ขยายใหญ่ขึ้นและส่งคืนประเภทเค้าโครงที่ถูกต้อง
woojoo666

1
มันควรจะตั้งข้อสังเกตว่าgetViewTypeCount()จะถูกเรียกเพียงครั้งเดียวทุกครั้งที่คุณโทรหาไม่ได้สำหรับทุกคนListView.setAdapter() Adapter.notifyDataSetChanged()
hidro

43

ดูรหัสด้านล่าง

ก่อนอื่นเราสร้างเลย์เอาต์ที่กำหนดเอง ในกรณีนี้สี่ประเภท

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>

จากนั้นเราจะสร้างรายการมุมมอง ในกรณีของเราด้วยสตริงและประเภท

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

    }

หลังจากนั้นเราสร้างเจ้าของมุมมอง ขอแนะนำอย่างยิ่งเพราะ Android OS เก็บการอ้างอิงเลย์เอาต์เพื่อนำรายการของคุณกลับมาใช้ใหม่เมื่อมันหายไปและปรากฏบนหน้าจอ หากคุณไม่ได้ใช้วิธีการนี้ทุกครั้งที่รายการของคุณปรากฏบนหน้าจอ Android OS จะสร้างขึ้นมาใหม่และทำให้แอปของคุณรั่วไหลหน่วยความจำ

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

    }

ในที่สุดเราสร้างอะแดปเตอร์ที่กำหนดเองของเราแทนที่ getViewTypeCount () และ getItemViewType (ตำแหน่ง 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;
        }

    }

และกิจกรรมของเราก็เป็นอย่างนี้:

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

    }
}

ตอนนี้สร้าง listview ภายใน mainactivity.xml เช่นนี้

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

<include layout = "@ layout / content_main" /> ซึ่งมาจาก
ไหน

ฉันต้องการเฉพาะข้อ (convertView == null) ไม่ต้องการ 'ผู้ดู'
Jomme

14

ในอะแดปเตอร์อาร์เรย์แบบกำหนดเองของคุณคุณแทนที่เมธอด getView () ตามที่คุณคุ้นเคย สิ่งที่คุณต้องทำคือใช้คำสั่ง switch หรือคำสั่ง if เพื่อส่งคืนมุมมองที่กำหนดเองโดยขึ้นอยู่กับอาร์กิวเมนต์ตำแหน่งที่ส่งผ่านไปยังเมธอด getView Android มีความฉลาดในการที่จะให้ตัวแปลงประเภทที่เหมาะสมสำหรับตำแหน่ง / แถวของคุณ คุณไม่จำเป็นต้องตรวจสอบว่าเป็นประเภทที่ถูกต้อง คุณสามารถช่วย Android ในเรื่องนี้ได้โดยการเอาชนะเมธอด getItemViewType () และ getViewTypeCount () อย่างเหมาะสม


4

ถ้าเราต้องการแสดงมุมมองประเภทต่าง ๆ ใน list-view มันก็ดีที่จะใช้ getViewTypeCount () และ getItemViewType () ในอะแดปเตอร์แทนที่จะสลับมุมมอง VIEW.GONE และ VIEW.VISIBLE อาจเป็นงานที่มีราคาแพงภายใน getView () ส่งผลกระทบต่อการเลื่อนรายการ

โปรดตรวจสอบสิ่งนี้เพื่อใช้งาน getViewTypeCount () และ getItemViewType () ในอะแดปเตอร์

ลิงก์: the-use-of-getviewtypecount


1

ListView มีไว้สำหรับกรณีการใช้งานง่ายเช่นมุมมองคงที่เดียวกันสำหรับรายการแถวทั้งหมด
เนื่องจากคุณต้องสร้าง ViewHolders และใช้ประโยชน์อย่างมีนัยสำคัญgetItemViewType()และแสดง xml layout เลย์เอาต์ของรายการต่าง ๆ แบบไดนามิกคุณควรลองทำเช่นนั้นโดยใช้RecyclerViewซึ่งมีอยู่ใน Android API 22 ซึ่งให้การสนับสนุนและโครงสร้างที่ดีกว่าสำหรับมุมมองหลายประเภท

ลองดูบทช่วยสอนนี้เกี่ยวกับวิธีใช้ RecyclerView เพื่อทำสิ่งที่คุณต้องการ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.