21 ธันวาคม 2555

[Android Code] Endless Scrolling ListView ทำใหม่ใช้ง่ายกว่าเดิม


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




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

ทีนี้มาดูโค๊ดกันเลยดีกว่าว่าใช้งานยังไง



Main.java
package app.akexorcist.listviewendlessloop;

import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import android.widget.Toast;

public class Main extends Activity {
    ListView lv; 
    ListViewEndlessLoop lvel;

    String[] lv_arr = { "Row 0", "Row 1", "Row 2", "Row 3", "Row 4"
            , "Row 5", "Row 6", "Row 7", "Row 8", "Row 9", "Row 10"
            , "Row 11", "Row 12", "Row 13", "Row 14", "Row 15", "Row 16"
            , "Row 17", "Row 18", "Row 19" , "Row 20", "Row 21", "Row 22"
            , "Row 23", "Row 24", "Row 25", "Row 26", "Row 27", "Row 28"
            , "Row 29", "Row 30", "Row 31", "Row 32", "Row 33", "Row 34"
            , "Row 35", "Row 36", "Row 37", "Row 38", "Row 39", "Row 40"
            , "Row 41", "Row 42", "Row 43", "Row 44", "Row 45", "Row 46"
            , "Row 47", "Row 48", "Row 49", "Row 50", "Row 51", "Row 52"
            , "Row 53", "Row 54", "Row 55", "Row 56", "Row 57", "Row 58"
            , "Row 59", "Row 60", "Row 61", "Row 62", "Row 63", "Row 64"
            , "Row 65", "Row 66", "Row 67", "Row 68", "Row 69", "Row 70"
            , "Row 71", "Row 72", "Row 73", "Row 74", "Row 75", "Row 76"
            , "Row 77", "Row 78", "Row 79", "Row 80", "Row 81", "Row 82"
            , "Row 83", "Row 84", "Row 85", "Row 86", "Row 87", "Row 88"
            , "Row 89", "Row 90", "Row 91", "Row 92", "Row 93", "Row 94"
            , "Row 95", "Row 96", "Row 97", "Row 98", "Row 99", "Row 100"
            , "Row 101", "Row 102", "Row 103", "Row 104", "Row 105"
            , "Row 106", "Row 107", "Row 108", "Row 109", "Row 110"
            , "Row 111", "Row 112", "Row 113", "Row 114", "Row 115"
            , "Row 116", "Row 117", "Row 118", "Row 119", "Row 120"
            , "Row 121", "Row 122", "Row 123", "Row 124", "Row 125"
            , "Row 126", "Row 127", "Row 128", "Row 129", "Row 130"
            , "Row 131" };
    
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        
        lv = (ListView)findViewById(R.id.listView1);
        lv.setOnItemClickListener(new OnItemClickListener() {
            public void onItemClick(AdapterView<?> arg0, View arg1
                    , int arg2, long arg3) {
                Toast.makeText(getApplicationContext(), "Select row " 
                        + String.valueOf(lvel.getSelectedRow(arg2))
                        , Toast.LENGTH_SHORT).show();
            }
            
        });
        
        lvel = new ListViewEndlessLoop(getApplicationContext(), lv
                , lv_arr, 20);
        lvel.setSelection(121);
    }
}

ผมขออิงตัวอย่างจากบทความเก่าเลยนะ ที่เก็บข้อมูลแบบ String[]
ไว้ทั้งหมด 132 ตัว จริงๆจะเป็น String[] หรือ ArrayList<String> ก็ได้
เจ้าของบล็อกเขียนให้รองรับแล้ว แต่ถ้าใครยังไม่รู้จักว่ามันคืออะไร
แนะนำให้ไปหาศึกษาเรื่อพื่นฐานการใช้งาน List View มาก่อนนะครับ
การทำงานคร่าวๆก็คือ ให้ประกาศ List View ที่จะใช้งานขึ้นมา
โดยกำหนดให้กับ List View ตามปกตินั่นแหละ แล้วกำหนด
Event Listener ให้กับ lv ได้ตามใจชอบ แต่ห้ามใช้ onScrollListener นะ
เพราะเจ้าของบล็อกต้องใช้สำหรับทำ Endless Scrolling ListView
ทีนี้ก็ประกาศคลาสของ ListViewEndlessLoop ได้เลย โดยมีรูปแบบดังนี้



จะเห็นว่าเจ้าของบล็อกทำคลาสให้กำหนดข้อมูลได้สองแบบ
จะเป็น String[] หรือ ArrayString<String> ก็ได้ เพื่อให้สะดวกสำหรับผู้ใช้
จากตัวอย่างเจ้าของบล็อกจะกำหนดให้แสดงข้อมูลทีละ 20 บรรทัด

** จากบทความเก่า เจ้าของบล็อกเตือนไว้แล้วว่า 
ห้ามกำหนดค่าดังกล่าวต่ำกว่า 20 เพราะอาจจะมีบั๊กได้ **

จากตัวอย่างเจ้าของบล็อกก็ประกาศ ListViewEndlessLoop ดังนี้

lvel = new ListViewEndlessLoop(getApplicationContext(), lv, lv_arr, 20); 

สำหรับ Context เจ้าของบล็อกให้ดึงจาก Context ปัจจุบันด้วย getApplicationContext()
หรือจะใช้อีกแบบเป็น Main.this ก็ได้ แล้วแต่ แต่เจ้าของบล็อกชอบใช้เป็น
getApplicationContext() เพราะว่า Main.this คือค่าสำหรับใน Main.java
ในกรณีที่ใช้ใน Activity อื่นๆเช่น MainMenu.java ก็ต้องเป็น MainMenu.this
แต่ getApplicationContext() จะดึง Context จาก Activity นั้นๆให้อัตโนมัติ
ก็เลยใช้เป็นประจำ จะได้ไม่ต้องมานั่งแก้ชื่อ Activity ใหม่ทุกครั้ง

ส่วน lv ก็คือ List View ที่เจ้าของบล็อกประกาศไปในบรรทัดข้างบน
ส่วน lv_arr คือข้อมูลที่จะแสดงใน List View เจ้าของบล็อกประกาศไว้แล้ว
มีทั้งหมด 132 ตัว ตั้งแต่ "row 0" ถึง "row 131" นั่นเอง

และ 20 คือ จาก 132 ตัว เจ้าของบล็อกจะให้ List View ดึงมาแค่ 20 ตัวเพื่อแสดง
เวลาที่เลื่อนลงก็จะดึงข้อมูลถัดไปมาเอง และถ้าเลื่อนขึ้นก็จะดึงข้อมูลก่อนหน้ามาเอง

** อย่าลืมนะ ห้ามกำหนดค่านี้ต่ำกว่า 20 ไม่งั้นเจอบั๊กได้ **

สำหรับคำสั่ง setSelection เอาไว้กำหนดว่าจะให้เลื่อนไปแสดงแถวที่เท่าไร
อันนี้คงไม่ต้องอธิบายมาก น่าจะเข้าใจกันได้อยู่แล้ว
ในตัวอย่างก็จะให้เลื่อนไปที่บรรทัด 121 (แถวที่ 122 เพราะแถวแรกนับ 0)
การใช้ setSelection ควรกำหนดค่าให้ไม่เกินจำนวนข้อมูลที่มี โดยเริ่มแถวแรกเป็น 0
(ข้อมูลมี 132 แถว ถ้าใส่ให้เลื่อนไปที่แถวที่ 200 มันก็ Error ตามปกติของมันแหละ)

ทีนี้คำสั่งต่อมาคือ getSelected คำสั่งนี้จะอยู่ใน onItemClickListener
เอาไว้รับค่าว่าแถวที่เลือกเป็นแถวที่เท่าไร เพราะ Endless Scrolling ListView
ไม่สามารถดึงค่าจาก arg2 โดยตรงได้ เพราะว่าดึงมาแสดงแค่ 20 แถว
ทำให้ค่าที่ได้มีแค่ 0 ถึง 19 ซึ่งจริงๆแล้วข้อมูลมีทั้งหมด 132 แถว
โดยคำสั่ง getSelected ให้ใช้ใน Listener เท่านั้น (ทำมาเพื่อ Listener โดยเฉพาะ)


ดังนั้นจึงต้องเอาค่าจากตัวแปร arg2 ไปใส่ในคำสั่ง getSelected ก่อน
เพื่อให้ได้ค่าจริงออกมา โดยจะต้องสร้างตัวแปร Int เพื่อรับค่าแถวที่ผู้ใช้เลือก



แต่ในตัวอย่างเจ้าของบล็อกไม่ได้ใช้ทำอะไรนอกจากแสดงผ่าน Toast
ก็เลยเอาคำสั่งไปใส่ใน Toast เพื่อให้แสดงออกมาเลย โดยไม่ต้องสร้างตัวแปรมารับค่า


main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#454545" >
    <ListView
        android:id="@+id/listView1"
        android:layout_width="300dp"
        android:layout_height="300dp"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:cacheColorHint="#00000000"
        android:scrollbars="none" >
    </ListView>
</RelativeLayout>


ListViewEndlessLoop.java
package app.akexorcist.listviewendlessloop;

import java.util.ArrayList;

import android.content.Context;
import android.graphics.BitmapFactory;
import android.graphics.Paint;
import android.util.Log;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.AbsListView.OnScrollListener;

public class ListViewEndlessLoop {
    private ArrayList<String> arr_all, arr_show;
    private int first_row, last_row, total_row = 20;
    private Context mContext;
    private ListView listView;
    public ListViewEndlessLoop (Context context, ListView lv
            , ArrayList<String> array, int viewRow) {
        mContext = context;
        listView = lv;
        total_row = viewRow;
        arr_all = new ArrayList<String>();
        arr_show = new ArrayList<String>();
        arr_all = array;
        first_row = arr_all.size() - 1;

        while(first_row < 0) {
            first_row += arr_all.size();
        }
        
        for(int i = 0 ; i < total_row ; i++) {
            arr_show.add(arr_all.get(i));
            first_row++;
            if(first_row >= arr_all.size()) 
                first_row = 0;
        }
        
        first_row -= total_row;
        
        while(first_row < 0) {
            first_row += arr_all.size();
        }
        
        last_row = first_row + total_row - 1;

        while(last_row >= arr_all.size()) {
            last_row -= arr_all.size();
        }
        
        initListView();
    }
    
    public ListViewEndlessLoop (Context context, ListView lv
            , String[] array, int viewRow) {
        mContext = context;
        listView = lv;
        total_row = viewRow;
        arr_all = new ArrayList<String>();
        arr_show = new ArrayList<String>();
        for(int i = 0 ; i < array.length ; i++) {
            arr_all.add(array[i]);
        }

        first_row = array.length - 1;

        while(first_row < 0) {
            first_row += array.length;
        }
        
        for(int i = 0 ; i < total_row ; i++) {
            arr_show.add(array[first_row]);
            first_row++;
            if(first_row >= array.length) 
                first_row = 0;
        }
        
        first_row -= total_row;
        
        while(first_row < 0) {
            first_row += array.length;
        }
        
        last_row = first_row + total_row - 1;

        while(last_row >= array.length) {
            last_row -= array.length;
        }

        initListView();
    }
    
    public void initListView() {
        listView.setAdapter(new ArrayAdapter<String>(mContext,
                android.R.layout.simple_list_item_1, arr_show));
        listView.setSelection((total_row / 2));
        listView.setOnScrollListener(new OnScrollListener() {
            public void onScroll(AbsListView view, int firstVisibleItem,
                    int visibleItemCount, int totalItemCount) {
                if(firstVisibleItem + visibleItemCount >= totalItemCount) {
                    arr_show = new ArrayList<String>();
                    last_row -= total_row / 2;

                    while(last_row < 0) {
                        last_row += arr_all.size();
                    }
                    
                    for(int i = 0 ; i < total_row ; i++) {
                        arr_show.add(arr_all.get(last_row));
                        last_row++;
                        if(last_row >= arr_all.size()) 
                            last_row = 0;
                    }
                    
                    first_row = last_row - total_row;
                    
                    while(first_row < 0) {
                        first_row += arr_all.size();
                    }
                    
                    last_row--;
                    while(last_row < 0) {
                        last_row += arr_all.size();
                    }

                    listView.setAdapter(new ArrayAdapter<String>
                             (mContext, android.R.layout.simple_list_item_1
                             , arr_show));
                    
                    if(total_row % 2 == 0) {
                        listView.setSelection((firstVisibleItem + 2) - 
                                (total_row / 2) );
                    } else {
                        listView.setSelection((firstVisibleItem + 1) - 
                                (total_row / 2) );
                    }
                }
                
                if(firstVisibleItem == 0) {
                    arr_show = new ArrayList<String>();
                    first_row -= total_row / 2;

                    while(first_row < 0) {
                        first_row += arr_all.size();
                    }

                    for(int i = 0 ; i < total_row ; i++) {
                        arr_show.add(arr_all.get(first_row));
                        first_row++;
                        if(first_row >= arr_all.size()) 
                            first_row = 0;
                    }

                    first_row -= total_row;
                    
                    while(first_row < 0) {
                        first_row += arr_all.size();
                    }
                    
                    last_row = first_row + total_row - 1;

                    while(last_row >= arr_all.size()) {
                        last_row -= arr_all.size();
                    }

                    listView.setAdapter(new ArrayAdapter<String>
                            (mContext,
                            android.R.layout.simple_list_item_1
                            , arr_show));
                    
                    listView.setSelection((total_row / 2) + 1);
                }
            }

            public void onScrollStateChanged(AbsListView view
                    , int scrollState) { }
        });
    }
    
    public int getSelectedRow(int position) {
        if(first_row + position >= arr_all.size())
            return (first_row + position) - arr_all.size();
        else {
            return first_row + position;
        }
    }
    
    public void setSelection(int position) {
        arr_show = new ArrayList<String>();
        last_row = position - (total_row / 2);

        while(last_row < 0) {
            last_row += arr_all.size();
        }
        
        for(int i = 0 ; i < total_row ; i++) {
            arr_show.add(arr_all.get(last_row));
            last_row++;
            if(last_row >= arr_all.size()) 
                last_row = 0;
        }
        
        first_row = last_row - total_row;
        
        while(first_row < 0) {
            first_row += arr_all.size();
        }
        
        last_row--;
        while(last_row < 0) {
            last_row += arr_all.size();
        }

        listView.setAdapter(new ArrayAdapter<String>(mContext,
                android.R.layout.simple_list_item_1, arr_show));
        
        if(total_row % 2 == 0) {
            listView.setSelection(total_row / 2);
        } else {
            listView.setSelection(total_row / 2);
        }
    }
}


AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="app.akexorcist.listviewendlessloop"
    android:versionCode="1"
    android:versionName="1.0" >
    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="8" />
    <application
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".Main"
            android:label="@string/title_activity_main" 
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

ก็เรียบร้อยแล้วแหละสำหรับ Endless Scrolling ListView
จะเห็นว่าโคตรง่ายขึ้นเลย เพราะเจ้าของบล็อกทำเป็นคลาสให้ใช้งานเลย
แต่ถ้าใครอยากดูรายละเอียดใน ListViewEndlessLoop.java
ให้ไปอ่านเพิ่มเติมในบทความเก่าแทน เพราะอธิบายไว้หมดแล้ว


สำหรับผู้ที่หลงเข้ามาอ่านผู้ใดที่ต้องการดาวน์โหลดไฟล์ตัวอย่าง
สามารถดาวน์โหลดได้จากที่นี่เลย List View Endless Loop [Google Drive]


เสร็จแล้ว~





เหล่าพันธมิตรแอนดรอยด์

Devahoy Layer Net NuuNeoI The Cheese Factory Somkiat CC Mart Routine Artit-K Arnondora Kamonway Try to be android developer Oatrice Benz Nest Studios Kotchaphan@Medium Jirawatee@Medium Travispea@Medium