Android Code : ทำ Pull to refresh หรือดึงเพื่อโหลด ให้ ListView
บทความนี้ จะบันทึกเรื่องของ การทำ Pull to refresh สำหรับ ListView ครับ เป็นการทบทวนไปในตัว
แนะนำให้อ่านอ่านบทความเรื่อง CustomViewGroup และ CustomViewGroup on ListView ก่อน
โดยผมจะทำต่อจากโปรเจค ใน 2 บทความข้างล่าง
Android Code : ListView และ Custom Groupview บน Android เบื้องต้น
Pull to refresh คืออะไร
มันก็ UX รูปแบบนึง ที่พอผู้ใช้ดึงหน้าจอขณะที่มีข้อมูลล่าสุดแล้วลงมา ก็จะโหลดข้อมูลใหม่มาแสดง ซึ่งก็มีแอปดังๆ ใช้กันให้เห็นทั่วไป
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 ดูว่าข้อมูลออกมารึปล่าว
สรุป 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. ระยะของไอเท็มแถวแรกที่เลยขอบบนของจอไป
เราสามารถดึงค่า 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. }
ลองทดสอบรันดู
Pull to refresh Library
ไม่ได้มีเพียงแต่ของดั้งเดิมบน support v4 เท่านั้นนะ มีให้ลองเล่นอยู่เยอะพอสมควร ยกตัวอย่างเช่น
PullToMakeSoup
อ่านวิธีการใช้ และรายละเอียดได้ที่
https://android-arsenal.com/details/1/3588
พอเราเข้าใจวิธีการของ Pull to refresh แล้วก็ ListView เราก็สามารถนำมาประยุกต์ใช้ได้แล้ว เช่น นำไปใช้กับพวกเรียกผ่าน service
(: