ListView是Android中一個功能強大而且很常用的控件,在很多App中都有ListView的下拉刷新數據和上拉加載更多這個功能。這里我就簡單記錄一下實現過程。
實現這個功能的方法不止一個,GitHub上有一些開源庫可以使用,但是本着學習的精神,我做的是使用自定義ListView實現這個功能。
思路:谷歌提供的ListView是不能提供下拉刷新和下拉加載的,所以我們就需要重寫ListView。在ListView的頭部和尾部加上我們的布局文件(progressbar)。
先說上拉加載更多實現原理:
首先先寫一個底部布局的xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:id="@+id/foot_load" android:padding="5dp" android:gravity="center"> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:attr/progressBarStyle" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="加載中。。。。" /> </LinearLayout> </LinearLayout>
寫一個自定義ListView繼承自ListView實現它的三個構造方法,這里必須實現三個構造方法,不實現的話就無法顯示ListView.並且在每個構造方法中都寫一個方法用於初始化相關控件。
public class MyListView extends ListView { public MyListView(Context context) { super(context); initView(context); } public MyListView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public MyListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } }
在initView(context)方法中通過LayoutInfalter.from(context).from(你的xml,null);拿到xml。返回一個View對象,ListView中有一個setfootView()方法,使用他可以設置View到ListView的底部,當然做了這兩步是完全不夠的,我們還需要對這個View做一個交互的操作,當用戶滑到底部時這個View顯示,數據加載完畢之后這個View消失。
第一步:需要對這個底部footView隱藏:
第一種方式是通過setVisibility(View.GONE)這個方法讓這個View不顯示。
第二種方式:
//測量footview的高度,通過measure()傳入兩個0參數,系統會不認這個0,自動幫我們測量出加在底部View的長度信息 footview.measure(0,0); //拿到高度 footViewHeight=footview.getMeasuredHeight(); //隱藏view,讓頭部顯示高度為實際測量高度的負數,實現布局的隱藏 footview.setPadding(0,-footViewHeight,0,0);
第二部:當滑到底部的時候顯示這個footView。這里就要實現下滑監聽接口implements AbsListView.OnScrollListener,實現接口的兩個方法。
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) { if (totaItemCounts==lasstVisible&&scrollState==SCROLL_STATE_IDLE) { if (!isLoading) { isLoading=true; // footview.findViewById(R.id.foot_load).setVisibility(View.VISIBLE); footview.setPadding(0,0,0,0); //加載數據 loadListener.onLoad();} } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//最后一個可見item是第一個可見item加上所有可見item(第一個可見item為0) this.lasstVisible=firstVisibleItem+visibleItemCount;
//所有item為listview的所有item this.totaItemCounts=totalItemCount; }
那么如何在刷新的時候通知ListView加載數據呢?這里使用一個接口回調的方法在自定義ListView寫一個接口,里面寫一個更新ListView的方法,讓主布局調用這個接口實現這個接口的方法。
public void setInterface(LoadListener loadListener){ this.loadListener=loadListener; } //接口回調 public interface LoadListener{ void onLoad(); }
@Override
public void onLoad() { //設置三秒延遲模仿延時獲取數據 new Handler().postDelayed(new Runnable() { @Override public void run() { //加載數據 for (int j=1;j<11;j++){ list.add(j); } //更新 數據 adapter.notifyDataSetChanged(); //加載完畢,就是讓View隱藏 listview.loadComplete(); } },3000); }
好了,這就實現了一個簡單的上拉加載功能。
下拉刷新和上拉加載過程差不多,但是有時候,下拉刷新有提示,下拉刷新,松開刷新,正在刷新提示,這又該如何實現吶,這里就需要實現onTouchEvent方法,設置下拉長度匹配刷新數據。同時,設置progrebar的可見狀態。
@Override
public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: yload=(int)ev.getY(); break; case MotionEvent.ACTION_MOVE: int moveY=(int)ev.getY(); int paddingY=-headViewHeight+(moveY-yload)/2; if (paddingY<0){ updateInfo.setText("下拉刷新。。。"); progressBar.setVisibility(View.GONE); } if (paddingY>0){ updateInfo.setText("松開刷新。。。"); progressBar.setVisibility(View.GONE); } headview.setPadding(0,paddingY,0,0); break; // case MotionEvent.ACTION_UP: // headview.setPadding(0,0,0,0); // updateInfo.setText("正在刷新。。。"); // progressBar.setVisibility(View.VISIBLE); // loadListener.pullLoad(); // // break; } return super.onTouchEvent(ev); }
附上完整源碼:
main.xml布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_height="match_parent"> <com.yakir.view.MyListView android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/list_view"></com.yakir.view.MyListView> </RelativeLayout>
main activity:
package com.yakir.demo; import android.os.Bundle; import android.os.Handler; import android.support.v7.app.AppCompatActivity; import android.widget.ArrayAdapter; import com.yakir.view.MyListView; import java.util.ArrayList; import java.util.List; public class MainActivity extends AppCompatActivity implements MyListView.LoadListener{ private MyListView listview; private List<Integer>list=new ArrayList<>(); private ArrayAdapter adapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); for (int i=1;i<20;i++){ list.add(i); } initView(); adapter=new ArrayAdapter<>(this,android.R.layout.simple_expandable_list_item_1,list); listview.setAdapter(adapter); } private void initView() { listview=(MyListView) findViewById(R.id.list_view); listview.setInterface(this); } @Override public void onLoad() { //設置三秒延遲模仿延時獲取數據 new Handler().postDelayed(new Runnable() { @Override public void run() { //加載數據 for (int j=1;j<11;j++){ list.add(j); } //更新 數據 adapter.notifyDataSetChanged(); //加載完畢 listview.loadComplete(); } },3000); } @Override public void pullLoad() { new Handler().postDelayed(new Runnable() { @Override public void run() { list.clear(); for (int i=1;i<20;i++){ list.add(i+1); } adapter.notifyDataSetChanged(); listview.loadComplete(); } },2000); } }
頭布局headview:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" > <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:id="@+id/head" android:orientation="horizontal" android:gravity="center" android:padding="5dp"> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:attr/progressBarStyleInverse" android:id="@+id/progressbar"/> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="正在刷新。。。。" android:id="@+id/update_info"/> </LinearLayout> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/update_time" /> </LinearLayout> </LinearLayout>
尾布局footview:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent"> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" android:id="@+id/foot_load" android:padding="5dp" android:gravity="center"> <ProgressBar android:layout_width="wrap_content" android:layout_height="wrap_content" style="?android:attr/progressBarStyle" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="加載中。。。。" /> </LinearLayout> </LinearLayout>
自定義ListView:
package com.yakir.view; import android.content.Context; import android.util.AttributeSet; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.widget.AbsListView; import android.widget.ListView; import android.widget.ProgressBar; import android.widget.TextView; import com.yakir.demo.R; import java.text.SimpleDateFormat; /** * 自定義listview實現上拉刷新下拉加載 * Created by qbqw7 on 2016/7/12. */ public class MyListView extends ListView implements AbsListView.OnScrollListener{ private View footview; private View headview; private int totaItemCounts; private int lasstVisible; private int fistVisiable; private LoadListener loadListener; private int footViewHeight; private int headViewHeight; private int yload; boolean isLoading; private TextView updateInfo; private TextView updateTime; private ProgressBar progressBar; public MyListView(Context context) { super(context); initView(context); } public MyListView(Context context, AttributeSet attrs) { super(context, attrs); initView(context); } public MyListView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initView(context); } private void initView(Context context) { //拿到頭布局xml headview=LayoutInflater.from(context).inflate(R.layout.head_layouyt,null); updateInfo=(TextView)headview.findViewById(R.id.update_info); updateTime=(TextView)headview.findViewById(R.id.update_time); progressBar=(ProgressBar)headview.findViewById(R.id.progressbar); updateTime.setText("更新於:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(System.currentTimeMillis())); //拿到尾布局xml footview=LayoutInflater.from(context).inflate(R.layout.proces_layout,null); //測量footview的高度 footview.measure(0,0); //拿到高度 footViewHeight=footview.getMeasuredHeight(); //隱藏view footview.setPadding(0,-footViewHeight,0,0); headview.measure(0,0); headViewHeight=headview.getMeasuredHeight(); headview.setPadding(0,-headViewHeight,0,0); //設置不可見 // footview.findViewById(R.id.foot_load).setVisibility(View.GONE); // headview.findViewById(R.id.head).setVisibility(View.GONE); //添加到listview底部 this.addFooterView(footview); //添加到listview頭部 this.addHeaderView(headview); //設置拉動監聽 this.setOnScrollListener(this); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: yload=(int)ev.getY(); break; case MotionEvent.ACTION_MOVE: int moveY=(int)ev.getY(); int paddingY=-headViewHeight+(moveY-yload)/2; if (paddingY<0){ updateInfo.setText("下拉刷新。。。"); progressBar.setVisibility(View.GONE); } if (paddingY>0){ updateInfo.setText("松開刷新。。。"); progressBar.setVisibility(View.GONE); } headview.setPadding(0,paddingY,0,0); break; // case MotionEvent.ACTION_UP: // headview.setPadding(0,0,0,0); // updateInfo.setText("正在刷新。。。"); // progressBar.setVisibility(View.VISIBLE); // loadListener.pullLoad(); // // break; } return super.onTouchEvent(ev); } @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (totaItemCounts==lasstVisible&&scrollState==SCROLL_STATE_IDLE) { if (!isLoading) { isLoading=true; // footview.findViewById(R.id.foot_load).setVisibility(View.VISIBLE); footview.setPadding(0,0,0,0); //加載數據 loadListener.onLoad(); } } if (fistVisiable==0){ headview.setPadding(0,0,0,0); updateInfo.setText("正在刷新。。。"); progressBar.setVisibility(View.VISIBLE); loadListener.pullLoad(); } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { this.fistVisiable=firstVisibleItem; this.lasstVisible=firstVisibleItem+visibleItemCount; this.totaItemCounts=totalItemCount; } //加載完成 public void loadComplete(){ isLoading=false; //footview.findViewById(R.id.foot_load).setVisibility(View.GONE); footview.setPadding(0,-footViewHeight,0,0); headview.setPadding(0,-headViewHeight,0,0); } public void setInterface(LoadListener loadListener){ this.loadListener=loadListener; } //接口回調 public interface LoadListener{ void onLoad(); void pullLoad(); } }