กลไกการรีไซเคิล ListView ทำงานอย่างไร


146

ดังนั้นผมจึงมีปัญหานี้ผมมีมาก่อนและเป็นธรรมชาติที่ฉันขอความช่วยเหลือได้ที่นี่ คำตอบของ Luksprog นั้นยอดเยี่ยมเพราะฉันไม่รู้ว่า ListView และ GridView ปรับตัวเองอย่างไรด้วยการรีไซเคิล Views ดังนั้นด้วยคำแนะนำของเขาฉันสามารถเปลี่ยนวิธีที่ฉันเพิ่ม Views ให้กับ GridView ของฉัน ปัญหาคือตอนนี้ฉันมีสิ่งที่ไม่สมเหตุสมผล นี่คือของฉันgetViewจากของฉันBaseAdapter:


public View getView(int position, View convertView, ViewGroup parent) {
        if(convertView == null) {
            LayoutInflater inflater = LayoutInflater.from(parent.getContext());
            convertView = inflater.inflate(R.layout.day_view_item, parent, false);
        }
        Log.d("DayViewActivity", "Position is: "+position);
        ((TextView)convertView.findViewById(R.id.day_hour_side)).setText(array[position]);
        LinearLayout layout = (LinearLayout)convertView.findViewById(R.id.day_event_layout);

        //layout.addView(new EventFrame(parent.getContext()));

        TextView create = new TextView(DayViewActivity.this);
        LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(0, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 62, getResources().getDisplayMetrics()), 1.0f);
        params.topMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
        params.bottomMargin = (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, getResources().getDisplayMetrics());
        create.setLayoutParams(params);
        create.setBackgroundColor(Color.BLUE);
        create.setText("Test"); 
        //the following is my original LinearLayout.LayoutParams for correctly setting the TextView Height
        //new LinearLayout.LayoutParams(0, (int)TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 60, getResources().getDisplayMetrics()), 1.0f)   
        if(position == 0) {
            Log.d("DayViewActivity", "This should only be running when position is 0. The position is: "+position);
            layout.addView(create);
        }

        return convertView;
    }

}

ปัญหาคือเมื่อฉันเลื่อนสิ่งนี้เกิดขึ้นและไม่ได้อยู่ในตำแหน่ง 0 ... ดูเหมือนว่าตำแหน่งที่ 6 และตำแหน่งที่ 8 รวมทั้งทำให้ตำแหน่งที่สองอยู่ในตำแหน่งที่ 8 ตอนนี้ฉันยังคงพยายามใช้งาน ListView และ GridView อยู่ดี ไม่เข้าใจว่าทำไมสิ่งนี้เกิดขึ้น หนึ่งในสาเหตุหลักที่ฉันทำคำถามนี้คือการช่วยเหลือผู้อื่นที่อาจไม่ทราบเกี่ยวกับมุมมองการรีไซเคิลของ ListView และ GridView หรือวิธีที่บทความนี้นำมาใช้เป็นกลไก ScrapView

ป้อนคำอธิบายรูปภาพที่นี่

แก้ไขภายหลัง

การเพิ่มลิงค์ไปยัง google IO talk นั้นเป็นสิ่งที่คุณต้องเข้าใจว่า ListView ทำงานอย่างไร ลิงก์เสียชีวิตในจากความคิดเห็น ดังนั้น user3427079 ก็ดีพอที่จะอัพเดทลิงค์นั้น ที่นี่มีไว้สำหรับเข้าถึงได้ง่าย


ฮ่าฮ่าขอบคุณสำหรับลิงค์นั้น เริ่มดูเลย :)
แอนดี้

คุณไม่ได้ใช้ตัวอย่างโค้ดจากคำถามก่อนหน้านี้ทั้งหมด แนวคิดคือคุณจำเป็นต้องมีรหัสเพื่อย้อนกลับการเปลี่ยนแปลงที่คุณทำกับแถวอื่น ๆ (ในกรณีของคุณคือตำแหน่ง0)
Luksprog

ฉันเข้าใจดีว่า @Luksprog แต่นั่นอธิบายได้ไหมว่าทำไมมันถึงทำให้มุมมองอยู่ในตำแหน่งอื่น หากมีสิ่งใดฉันคาดว่าจะได้ตำแหน่งที่ 0 เท่านั้นที่จะมีจำนวนการดูซ้ำ Idk ยังคงมีปัญหาในการเข้าใจว่า ListView เชื่อมโยงสิ่งนี้อย่างไร: /
Andy

1
การรีไซเคิลหมายถึงมุมมองแถวที่เพิ่งหายไป (ตัวอย่างเช่นตำแหน่ง 0 หลังจากเลื่อนลงหนึ่งแถว) จากหน้าจออาจถูกนำมาใช้ในภายหลังเมื่อListViewต้องการให้แถวใหม่แสดง (เช่นเลื่อนขึ้น / ลงอย่างต่อเนื่อง) ปัญหาคือว่ามุมมองที่เพิ่งหายไปและที่จะใช้ในตำแหน่งที่ต้องการในอนาคตได้TextViewเพิ่มไปแล้วมันเป็นปัญหา จะแก้ปัญหาได้คุณต้องเอามันออกไปในgetViewวิธีการถ้าgetViewวิธีการที่เรียกว่าสำหรับตำแหน่งอื่น ๆ 0ที่ไม่ใช่
Luksprog

2
ลิงก์ของโพสต์บนสุดตายแล้วฉันคิดว่ามันเป็นอย่างนั้นเหรอ? youtube.com/watch?v=N6YdwzAvwOA
G_V

คำตอบ:


330

เริ่มแรกฉันยังไม่ทราบถึงการรีไซเคิล listview และกลไกการใช้งาน convertview แต่หลังจากการวิจัยตลอดทั้งวันฉันก็ค่อนข้างเข้าใจกลไกของมุมมองรายการโดยอ้างอิงจากภาพจากandroid.amberfog ป้อนคำอธิบายรูปภาพที่นี่

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

System.out.println("getview:"+position+" "+convertView);

ภายในของคุณ

public View getView(final int position, View convertView, ViewGroup parent)
{
    System.out.println("getview:"+position+" "+convertView);
    ViewHolder holder;
    View row=convertView;
    if(row==null)
    {
        LayoutInflater inflater=((Activity)context).getLayoutInflater();
        row=inflater.inflate(layoutResourceId, parent,false);
        
        holder=new PakistaniDrama();
        holder.tvDramaName=(TextView)row.findViewById(R.id.dramaName);
        holder.cbCheck=(CheckBox)row.findViewById(R.id.checkBox);
        
        row.setTag(holder);
        
    }
    else
    {
        holder=(PakistaniDrama)row.getTag();
    }
            holder.tvDramaName.setText(dramaList.get(position).getDramaName());
    holder.cbCheck.setChecked(checks.get(position));
            return row;
    }

คุณจะสังเกตเห็นใน logcat ของคุณในตอนแรก convertview เป็นโมฆะสำหรับแถวที่มองเห็นได้ทั้งหมดเนื่องจากเริ่มแรกไม่มีการดู (เช่นรายการ) ใน recycler ดังนั้น getView () ของคุณจะสร้างมุมมองใหม่สำหรับแต่ละรายการที่มองเห็นได้ ขณะที่คุณเลื่อนขึ้นและรายการที่ 1 ย้ายออกจากหน้าจอมันจะถูกส่งไปยังRecyclerด้วยสถานะปัจจุบัน (เช่น TextView 'text' หรือในกรณีของฉันถ้ามีการเลือกเช็คบ็อกซ์มันจะถูกเชื่อมโยงกับมุมมองและ เก็บไว้ในเครื่องรีไซเคิล)

ตอนนี้เมื่อคุณเลื่อนขึ้น / ลงมุมมองรายการของคุณจะไม่สร้างมุมมองใหม่มันจะใช้มุมมองที่อยู่ในเครื่องรีไซเคิลของคุณ ในLogcatของคุณคุณจะสังเกตเห็นว่า 'convertView' ไม่ใช่โมฆะเนื่องจากรายการใหม่ของคุณ 8 จะถูกดึงออกมาโดยใช้ convertview นั่นคือโดยทั่วไปจะใช้มุมมองรายการ 1 จาก recycler และขยายรายการ 8 ในตำแหน่งและคุณสามารถสังเกตได้ ในรหัสของฉัน หากคุณมีช่องทำเครื่องหมายและถ้าคุณตรวจสอบที่ตำแหน่ง 0 (สมมติว่ารายการที่ 1 มีช่องทำเครื่องหมายและคุณทำเครื่องหมายไว้) ดังนั้นเมื่อคุณเลื่อนลงมาคุณจะเห็นช่องทำเครื่องหมายรายการที่ 8 ได้ถูกตรวจสอบแล้วนี่เป็นสาเหตุที่ listview ใช้มุมมองเดียวกัน ไม่ได้สร้างใหม่สำหรับคุณเนื่องจากการเพิ่มประสิทธิภาพ

สิ่งที่สำคัญ

1 . ไม่เคยตั้งค่าlayout_heightและlayout_widthของ ListView ของคุณเพื่อwrap_contentเป็นgetView()จะบังคับอะแดปเตอร์ของคุณจะได้รับเด็กบางส่วนสำหรับการวัดความสูงของมุมมองที่จะวาดในมุมมองรายการและอาจทำให้เกิดพฤติกรรมที่ไม่คาดคิดบางอย่างเช่นการกลับ convertview แม้กระทั่งรายการไม่ scrolled.always ใช้match_parentหรือคง ความกว้างความสูง.

2 . หากคุณต้องการที่จะใช้เค้าโครงบางส่วนหรือมุมมองหลังจากดูรายการของคุณและยุทธคำถามมาในใจของคุณถ้าผมตั้งค่าlayout_heightเพื่อfill_parentมุมมองหลังจากที่มุมมองรายการจะไม่แสดงขึ้นเป็นมันไปลงหน้าจอเพื่อให้ดีกว่าที่จะใส่ ListView ของคุณภายใน layout.For ตัวอย่างเชิงเส้นเค้าโครงและการตั้งค่าความสูงและความกว้างของรูปแบบที่เป็นความต้องการของคุณและทำให้ความสูงและความกว้างแอตทริบิวต์ของ ListView ของคุณเพื่อเป็นรูปแบบของคุณ (เช่นถ้ารูปแบบของคุณความกว้าง320และความสูงเป็น280)แล้ว ListView ของคุณ ควรมีความสูงและความกว้างเท่ากัน. นี่จะบอกว่า getView () ของความสูงและความกว้างของมุมมองที่แน่นอนที่จะให้เรนเดอร์และ getView () จะไม่เรียกแถวสุ่มบางครั้งอีกครั้งและปัญหาอื่น ๆ เช่นกลับมาดูแปลงก่อนที่การเลื่อนจะไม่เกิดขึ้น ตัวเองยกเว้นว่า listview ของฉันอยู่ใน lineaLayout แต่ก็มีปัญหาเช่นการเรียกดูซ้ำและแปลงมุมมองเป็นการวาง Listview ใน LinearLayout ทำงานเหมือนเวทมนตร์สำหรับฉัน (ไม่รู้ว่าทำไม)

01-01 14:49:36.606: I/System.out(13871): getview 0 null
01-01 14:49:36.636: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.636: I/System.out(13871): getview 1 android.widget.RelativeLayout@406082c0
01-01 14:49:36.646: I/System.out(13871): getview 2 android.widget.RelativeLayout@406082c0
01-01 14:49:36.646: I/System.out(13871): getview 3 android.widget.RelativeLayout@406082c0
01-01 14:49:36.656: I/System.out(13871): getview 4 android.widget.RelativeLayout@406082c0
01-01 14:49:36.666: I/System.out(13871): getview 5 android.widget.RelativeLayout@406082c0
01-01 14:49:36.666: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.696: I/System.out(13871): getview 0 android.widget.RelativeLayout@406082c0
01-01 14:49:36.706: I/System.out(13871): getview 1 null
01-01 14:49:36.736: I/System.out(13871): getview 2 null
01-01 14:49:36.756: I/System.out(13871): getview 3 null
01-01 14:49:36.776: I/System.out(13871): getview 4 null

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


36
"ฉันรู้ว่าฉันไม่ดีในการอธิบาย" >>> คะแนนโหวต 37 จากคำตอบนี้บอกฉันว่าคุณอธิบายได้ดีมาก โปรดแบ่งปันความรู้ของคุณต่อไป! ;)
พฤษภาคม

1
คำตอบที่ดี raja ji +1 จากฉัน .. :)
Ehsan Sajjad

2
ขอบคุณสำหรับภาพ หลังจากมีปัญหามากมายกับ ListView เมื่อฉันเริ่มต้นกับ Android ฉันรู้หลักการรีไซเคิลหลังจากที่ต้องดิ้นรนมาก ถึงกระนั้นมันก็ถ่ายรูปและประโยคนี้: if you had a checkbox and if you check it at position 0(let say item1 has also a checkbox and you checked it) so when you scroll down you will see item 8 checkbox is already checked,this is why listview is re using the same view not creating a new for you due to performance optimization.เพื่อให้ฉันตระหนักถึงความผิดพลาดของฉัน โพสต์ที่ดีที่สุดเกี่ยวกับการรีไซเคิล Android ListView ฉันเจอแล้ว!
Kevin Cruijssen

1
@MuhammadBabar ใช่ฉันได้โพสต์คำถาม: stackoverflow.com/q/30122027/1318946
Pratik Butani

1
@MuhammadBabar คนอธิบายที่ยอดเยี่ยม! มันช่วยประหยัดเวลาของฉัน แค่อยากรู้ว่ามันใช้RecyclerViewกลไกเดียวกันได้หรือไม่? ในคำถามของฉันstackoverflow.com/questions/47897770/...listViewฉันรู้ว่างานเป็นที่เงียบสงบไปไม่ได้กับ ฉันควรลองด้วยrecyclerViewไหม
Shambhu

8

ระวังในรูปแบบตัวยึดหากคุณตั้งค่าตำแหน่งในวัตถุตัวยึดของคุณคุณควรตั้งค่าทุกครั้งเช่น:

@Override
public final View getView(int position, View view, ViewGroup parent) {
    Holder holder = null;
    if (view == null) {
        LayoutInflater inflater = (LayoutInflater) App.getContext()
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        view = inflater.inflate(getContainerView(), parent, false);
        holder = getHolder(position, view, parent);
        holder.setTag(tag);
        view.setTag(holder);
    } else {
        holder = (Holder) view.getTag();
    }
    holder.position = position;
    draw(holder);
    return holder.getView();
}

นี่คือตัวอย่างจากคลาสนามธรรมที่ไหน

getHolder(position, view, parent);

ดำเนินการตั้งค่าทั้งหมดสำหรับ

ImageViews, TextViews, etc..

1
ไม่ได้สังเกตว่าทุกครั้งที่ฉันทำ parent.setTag(holder);แทนวิธีที่ถูกต้องซึ่งก็คือview.setTag(holder);
ArtiomLK

2

การใช้รูปแบบตัวยึดคุณสามารถบรรลุสิ่งที่คุณต้องการ:

คุณสามารถหาคำอธิบายของรูปแบบนี้ได้ที่นี่:

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

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