web analytics

Android Code : ทำ Pull to refresh หรือดึงเพื่อโหลด ให้ ListView

cover-1

 

บทความนี้ จะบันทึกเรื่องของ การทำ Pull to refresh สำหรับ ListView ครับ เป็นการทบทวนไปในตัว

 

แนะนำให้อ่านอ่านบทความเรื่อง CustomViewGroup และ CustomViewGroup on ListView ก่อน
โดยผมจะทำต่อจากโปรเจค ใน 2 บทความข้างล่าง

Android Code : Custom Viewgroup บน Android เบื้องต้น

Android Code : ListView และ Custom Groupview บน Android เบื้องต้น

 

 

 

Pull to refresh คืออะไร

มันก็ UX รูปแบบนึง ที่พอผู้ใช้ดึงหน้าจอขณะที่มีข้อมูลล่าสุดแล้วลงมา ก็จะโหลดข้อมูลใหม่มาแสดง ซึ่งก็มีแอปดังๆ ใช้กันให้เห็นทั่วไป

p1

 

SwipeRefreshLayout

การทำ Pull to refresh ไม่ได้ยากเย็นอะไร ทาง Android เตรียมไว้ให้แล้ว คือ SwipeRefreshLayout ที่อยู่ใน support v4
โดย เจ้า SwipeRefreshLayout สามารถทำ Pull to refresh ได้โดยแค่ ใส่ tag ครอบ listview หรืออะไรที่มัน scroll ได้ มันก็จะกลายเป็น Pull to refresh ในทันที

activity_main.xml (ตัวอย่างไฟล์ layout)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/swipe_refresh_layout"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <ListView
            android:id="@+id/listView"
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        </ListView>
    </android.support.v4.widget.SwipeRefreshLayout>
</LinearLayout>

 

ต่อมาก็มาเขียนใน Java

ประกาศตัวแปร SwipeRefreshLayout

SwipeRefreshLayout swipeRefreshLayout;

 

กำหนดว่า เมื่อผู้ใช้ pull to refresh แล้ว ให้ทำอะไร

        swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                // do something.
            }
        });

 

พอเราอยากให้ loading refresh หายไปก็เรียกคำสั่งข้างล่างนี้

swipeRefreshLayout.setRefreshing(false);

 

เดี๋ยวเราจะมาลองทำ การสมมุติโหลดข้อมูลใหม่แล้วมาใส่ใน listView โดยสมมุติว่าโหลด 3 วินาที
ตัวอย่างการ delay ให้ทำงานบางอย่าง หลังจากผ่านไป ทุกๆ 3 วินาที

                Handle handle = new Handler();
                Runable runable = new Runnable() {

                    @Override
                    public void run() {
                        // do some thing after 3s.
                    }
                };
                handle.postDelayed(runable, 3000); // delay 3s.

 

ดังนั้นใน method run เราต้อง implement ส่วนที่ทำการโหลดข้อมูล แต่ทว่า runable จะทำงานไปเรื่อยๆ เราต้องปิดการทำงานของมันหลังโหลดเสร็จด้วย

                handle = new Handler();
                runable = new Runnable() {

                    @Override
                    public void run() {
                        swipeRefreshLayout.setRefreshing(false);
                        testAddDataToListView();
                        handle.removeCallbacks(runable); // stop runable.
                    }
                };
                handle.postDelayed(runable, 3000); // delay 3 s.

 

ในส่วนของการโหลดข้อมูล จะสมมุติโดยการสร้างของมา 10 ชิ้น และชื่อเป็นการสุ่มเลขขึ้นมา เสร็จแล้วแล้วใส่เข้าไปใน Adapter จากนั้นแค่เรียกคำสั่ง adapter.notifySetChanged() เจ้า listView ก็จะอัพเดทข้อมูล

    private void testAddDataToListView() {
        adapter.addDataToTop(sampleRandomData()); // add data to top
        adapter.notifyDataSetChanged();
    }
    
    private ArrayList<Integer> sampleRandomData() {
        ArrayList<Integer> randomData = new ArrayList<>();
        for (int i=0; i < 10; i++) { // random 10 items.
            Random rand = new Random();
            randomData.add(rand.nextInt(10000));
        }
        return randomData;
    }

 

ที่คลาสของ adapter เราก็มากำหนดในส่วนของการเพิ่มข้อมูลด้วย

คำสั่งนี้คือ การเพิ่มข้อมูลทั้งหมดใน dataNew ไปที่ data โดยเพิ่มไปที่จุดแรกสุด (Top)

data.addAll(0,dataNew);

 

CustomAdapter.java

public class CustomAdapter extends BaseAdapter {

    ArrayList<Integer> data;

    public void setData( ArrayList<Integer> data){
        this.data = data;
    }

    public void addDataToTop(ArrayList<Integer> dataNew){
        data.addAll(0,dataNew); // add data to top
    }

    @Override
    public int getCount() {
        return data.size();
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

    @Override
    public long getItemId(int position) {
        return 0;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        MyCustomViewGroup row = null;
        if(convertView == null) {
            row = new MyCustomViewGroup(parent.getContext());
        }else{
            row = (MyCustomViewGroup) convertView;
        }

        row.setName("Random : "+data.get(position));
        row.setDescription("Random : "+data.get(position));
        return row;
    }
}

 

ลองทดสอบการ Pull to refresh ดูว่าข้อมูลออกมารึปล่าว

p2

 

สรุป MainActivity.java

public class MainActivity extends AppCompatActivity {

    ListView listView;
    CustomAdapter adapter;

    // Pull to refresh.
    SwipeRefreshLayout swipeRefreshLayout;

    // for test delay.
    Handler handle;
    Runnable runable;

    // data in listView.
    ArrayList<Integer> data = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initInstance();
    }

    private void initInstance() {

        listView = (ListView) findViewById(R.id.listView);
        adapter = new CustomAdapter();
        adapter.setData(data);
        listView.setAdapter(adapter);

        swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_refresh_layout);
        swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
            @Override
            public void onRefresh() {
                handle = new Handler();
                runable = new Runnable() {

                    @Override
                    public void run() {
                        swipeRefreshLayout.setRefreshing(false);
                        testAddDataToListView();
                        handle.removeCallbacks(runable); // stop runable.
                    }
                };
                handle.postDelayed(runable, 3000); // delay 3 s.
            }
        });
    }

    private void testAddDataToListView() {
        adapter.addDataToTop(sampleRandomData()); // add data to top
        adapter.notifyDataSetChanged();
    }

    private ArrayList<Integer> sampleRandomData() {
        ArrayList<Integer> randomData = new ArrayList<>();
        for (int i=0; i < 10; i++) { // random 10 items.
            Random rand = new Random();
            randomData.add(rand.nextInt(10000));
        }
        return randomData;
    }


}

 

ให้ Refresh แล้วตำแหน่งใน listview อยู่ที่เดิม

บางทีเราก็อยากให้ refresh แล้วตำแหน่งยังอยู่ที่เดิม แต่ข้อมูลโหลดมาแล้วอยู่ด้านบน ต้องให้ผู้ใช้เลื่อนเอง ก็สามารถทำได้

ก่อนที่เราจะ refresh จะต้องเก็บค่าของ 2 อย่างคือ
1. ตำแหน่งของไอเท็มที่แสดงเป็นอันแรกในขณะนั้น
2. ระยะของไอเท็มแถวแรกที่เลยขอบบนของจอไป

1

 

เราสามารถดึงค่า 2 อย่างนี้ จาก listView ได้เลย

int firstVisiblePosition = listView.getFirstVisiblePosition();
int heightForTop = 0;
View view = listView.getChildAt(0);
if(view != null){
      heightForTop = view.getTop();
}

 

และหลังจากเรา โหลดข้อมูลเสร็จแล้ว อัพเดท listView แล้ว เราก็ให้มัน scroll มาที่ตำแหน่งเดิม
โดยพารามิเตอร์เป็นตำแหน่งที่จะให้ scroll ไป ซึ่งเราก็ต้องนำมาบวกกับขนาดที่เราใส่ไปเพิ่มด้านบนด้วย ผมสมมุติว่าเราโหลดมาทีละ 10 อัน ก็เลย +10 ส่วนพารามิเตอร์ที่สองก็เป็นระยะที่เลยขอบบนไปนั่นเอง

listView.setSelectionFromTop(firstVisiblePosition + 10 , heightForTop);

 

สรุปแล้วใน onRefresh ก็จะเขียนได้ประมาณนี้

            @Override
            public void onRefresh() {
                handle = new Handler();
                runable = new Runnable() {

                    @Override
                    public void run() {
                        swipeRefreshLayout.setRefreshing(false);

                        // init before refresh.
                        int firstVisiblePosition = listView.getFirstVisiblePosition();
                        int heightForTop = 0;
                        View view = listView.getChildAt(0);
                        if(view != null){
                            heightForTop = view.getTop();
                        }

                        testAddDataToListView();

                        // set position on listview to before refresh.
                        listView.setSelectionFromTop(firstVisiblePosition + 10 , heightForTop);

                        handle.removeCallbacks(runable); // stop runable.
                    }
                };
                handle.postDelayed(runable, 3000); // delay 3 s.
            }

 

ลองทดสอบรันดู

p3

 

 

Pull to refresh Library

ไม่ได้มีเพียงแต่ของดั้งเดิมบน support v4 เท่านั้นนะ มีให้ลองเล่นอยู่เยอะพอสมควร ยกตัวอย่างเช่น

 

PullToMakeSoup

อ่านวิธีการใช้ และรายละเอียดได้ที่
https://android-arsenal.com/details/1/3588

p4

 

พอเราเข้าใจวิธีการของ Pull to refresh แล้วก็ ListView เราก็สามารถนำมาประยุกต์ใช้ได้แล้ว เช่น นำไปใช้กับพวกเรียกผ่าน service

(: