Android 自定义ListView 实现下拉刷新 上拉加载功能

时间:2022-03-11 21:19:08

效果图

Android 自定义ListView 实现下拉刷新 上拉加载功能

思考

  • Listview是viewGroup的子类,它本身提供了方法addHeaderView(View view),addFooterView(View view)去添加头布局和底布局,所以我们只要监听它的onTouchEvent方法,判断头布局 底布局的显示状态。
  • 头布局(headerView)

    1. 默认的话,得隐藏。先获取到头布局的高度int headerViewHeight = headerView.getMeasuredHeight();通过设置它的padding(0, -headerViewHeight, 0, 0); 这里要注意:获取头布局高度之前,需要等头布局完全加载上来后,才获取得到,否则为0。可以通过measure()方法主动通知系统去测量该view,也可以通过获取视图树的观察者去增加一个全局的布局监听器。
    2. 重写onTouchEvent(MotionEvent event);
      手指按下时 获取该view的Y方向的坐标downY。
      手指移动时
      获取移动的Y方向的距离deltY = (int) (event.getY() - downY);
      将移动的距离deltY+(-headerViewHeight)就是头布局的位置paddingTop。
      当然这个paddingTop需要限制,比如你上滑时,如果已经到了-headerViewHeight,就不要继续将头布局往上设置了;而且下拉时,如果不是第一个条目,就用listview本身的滑动事件,限制代码:paddingTop > -headerViewHeight && getFirstVisiblePosition() == 0
      在此条件下,继续判断:如果paddingTop>0 && currentState==下拉刷新状态,就将currentState设置为松开立即刷新状态并更新头布局内容;如果paddingTop<0 && currentState==松开立即刷新状态,就将currentState设置为下拉刷新状态并更新头布局内容。
      手指抬起时
      如果是下拉刷新状态,则隐藏头布局
      如果是松开立即刷新状态,则显示头布局,将当前状态设置为正在刷新状态并更新头布局内容。同时在这里设置暴露一个接口方法,让用户在该方法中去加载要刷新出来的数据。

    3. 类中还应该提供刷新完成重置headerView的方法,由用户在获取完数据并更新完adapter之后,去在UI线程中调用该方法。

  • 底布局(footerView)
    1. 默认隐藏。方法同头布局一样
    2. 在初始化的时候,为listview设置一个滑动监听。当滑动状态为OnScrollListener.SCROLL_STATE_IDLE (即手指松开) && 是滑动listview最后一个条目 && 不是加载更多,这时,将footerView显示出来,同时在这里设置暴露一个接口方法,让用户在该方法中去加载更多要加载出来的数据。
    3. 提供加载完成重置footerView的方法

步骤

1.headerView布局

<?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="wrap_content"
android:gravity="center"
android:orientation="horizontal">


<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp">


<ImageView
android:id="@+id/iv_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:src="@mipmap/indicator_arrow" />


<ProgressBar
android:id="@+id/pb_load"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_centerInParent="true"
android:indeterminateDuration="1000"
android:indeterminateDrawable="@drawable/indeterminate_drawable"
android:visibility="invisible" />

</RelativeLayout>

<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:orientation="vertical">


<TextView
android:id="@+id/tv_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:text="下拉刷新"
android:textColor="#a000"
android:textSize="20sp" />


<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:text="最后更新:2015-06-06"
android:textColor="@android:color/darker_gray"
android:textSize="14sp" />

</LinearLayout>


</LinearLayout>

2.footerView布局

<?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="wrap_content"
android:gravity="center"
android:orientation="horizontal">


<ProgressBar
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp"
android:indeterminateDrawable="@drawable/indeterminate_drawable"
android:indeterminateDuration="1000" />


<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:text="加载更多..."
android:textSize="16sp" />


</LinearLayout>

3.自定义listview的代码

public class RefreshListView extends ListView implements AbsListView.OnScrollListener {

private static final String TAG = "RefreshListView1";
private View headerView;//头布局
private ImageView iv_arrow;
private ProgressBar pb_load;
private TextView tv_state;
private TextView tv_time;
private View footerView;//底布局

private int headerViewHeight;//头布局的高度
private int downY;//手指按下时Y的坐标
private static final int PULL_TO_REFRESH = 0; //下拉刷新
private static final int RELEASE_REFRESH = 1; //松开以刷新
private static final int REFRESHING = 2;//正在刷新
private int currentState = PULL_TO_REFRESH;
private RotateAnimation upAnimation;
private RotateAnimation downAnimation;
private int footerViewHeight;//底布局的高度
private boolean isLoadingMore = false;//当前是否正在处于加载更多

public RefreshListView(Context context) {
super(context);
init();
}

public RefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}

public RefreshListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}

private void init() {
setOnScrollListener(this);
initHeaderView();
initRotateAnimation();
initFooterView();
}

/**
* 初始化头布局
*/

private void initHeaderView() {
headerView = View.inflate(getContext(), R.layout.layout_header, null);
iv_arrow = (ImageView) headerView.findViewById(R.id.iv_arrow);
pb_load = (ProgressBar) headerView.findViewById(R.id.pb_load);
tv_state = (TextView) headerView.findViewById(R.id.tv_state);
tv_time = (TextView) headerView.findViewById(R.id.tv_time);
// 第一种方法获取头布局的高度
// final ViewTreeObserver viewTreeObserver = headerView.getViewTreeObserver();
// viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
// @Override
// public void onGlobalLayout() {
// viewTreeObserver.removeOnGlobalLayoutListener(this);
// int headerViewHeight = headerView.getHeight();
// Log.i(TAG, "headerViewHeight:" + headerViewHeight);
// headerView.setPadding(0, -headerViewHeight, 0, 0);
// addHeaderView(headerView);
// }
// });
//第二种方法获取头布局的高度
headerView.measure(0, 0);//主动通知系统去测量该view;
headerViewHeight = headerView.getMeasuredHeight();
Log.i(TAG, "headerViewHeight:" + headerViewHeight);
headerView.setPadding(0, -headerViewHeight, 0, 0);
addHeaderView(headerView);
}

/**
* 初始化旋转动画
*/

private void initRotateAnimation() {
upAnimation = new RotateAnimation(0, -180,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
upAnimation.setDuration(300);
upAnimation.setFillAfter(true);
downAnimation = new RotateAnimation(-180, -360,
RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
downAnimation.setDuration(300);
downAnimation.setFillAfter(true);
}

private void initFooterView() {
footerView = View.inflate(getContext(), R.layout.layout_footer, null);
footerView.measure(0, 0);
footerViewHeight = footerView.getMeasuredHeight();
footerView.setPadding(0, -footerViewHeight, 0, 0);
addFooterView(footerView);
}

@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
downY = (int) event.getY();
break;
case MotionEvent.ACTION_MOVE:
if (currentState == REFRESHING) {
break;
}
int deltY = (int) (event.getY() - downY);
int paddingTop = -headerViewHeight + deltY;
if (paddingTop > -headerViewHeight && getFirstVisiblePosition() == 0) {//下拉的状态 并且 listview第一个条目可见
Log.i(TAG, "paddingTop:" + paddingTop);
headerView.setPadding(0, paddingTop, 0, 0);
if (paddingTop > 0 && currentState == PULL_TO_REFRESH) {//头布局可见 并且为 下拉刷新状态
//进入松开刷新状态
currentState = RELEASE_REFRESH;
refreshHeaderView();
} else if (paddingTop < 0 && currentState == RELEASE_REFRESH) {
currentState = PULL_TO_REFRESH;
refreshHeaderView();
}
return true;
}
break;
case MotionEvent.ACTION_UP:
if (currentState == PULL_TO_REFRESH) {
//隐藏headerView
headerView.setPadding(0, -headerViewHeight, 0, 0);
} else if (currentState == RELEASE_REFRESH) {
headerView.setPadding(0, 0, 0, 0);
currentState = REFRESHING;
refreshHeaderView();
if (listener != null) {
listener.onPullRefresh();
}
}
break;
}
return super.onTouchEvent(event);
}

/**
* 刷新headerView
*/

private void refreshHeaderView() {
switch (currentState) {
case PULL_TO_REFRESH:
tv_state.setText("下拉刷新");
iv_arrow.startAnimation(downAnimation);
break;
case RELEASE_REFRESH:
tv_state.setText("松开立即刷新");
iv_arrow.startAnimation(upAnimation);
break;
case REFRESHING:
iv_arrow.clearAnimation();//因为向上的旋转动画有可能没有执行完
iv_arrow.setVisibility(View.INVISIBLE);
pb_load.setVisibility(View.VISIBLE);
tv_state.setText("正在刷新...");
break;
}
}

/**
* 完成刷新操作,重置状态,在获取完数据并更新完adapter之后,去在UI线程中调用该方法
*/

public void completeRefresh() {
if (isLoadingMore) {
//重置footerView状态
footerView.setPadding(0, -footerViewHeight, 0, 0);
isLoadingMore = false;
} else {
//重置headerView状态
headerView.setPadding(0, -headerViewHeight, 0, 0);
currentState = PULL_TO_REFRESH;
pb_load.setVisibility(View.INVISIBLE);
iv_arrow.setVisibility(View.VISIBLE);
tv_state.setText("下拉刷新");
tv_time.setText("最后刷新:" + getCurrentTime());
}
}


/**
* 获取当前系统时间,并格式化
*/

private String getCurrentTime() {
SimpleDateFormat format = new SimpleDateFormat("yy-MM-dd HH:mm:ss");
return format.format(new Date());
}

private OnRefreshListener listener;

public void setOnRefreshListener(OnRefreshListener listener) {
this.listener = listener;
}


public interface OnRefreshListener {
void onPullRefresh();

void onLoadingMore();
}

/**
* SCROLL_STATE_IDLE:闲置状态,手指松开 值为0
* SCROLL_STATE_TOUCH_SCROLL:触摸滑动状态 值为1
* SCROLL_STATE_FLING:快速滑动状态 值为2
*/

@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
Log.i(TAG, "scrollState = " + scrollState);
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE
&& getLastVisiblePosition() == (getCount() - 1) && !isLoadingMore) {
isLoadingMore = true;

footerView.setPadding(0, 0, 0, 0);//显示出footerView
setSelection(getCount());//让listview最后一条显示出来

if (listener != null) {
listener.onLoadingMore();
}
}
}

@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

}

}

4.主函数调用的布局

<?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">


<com.example.refreshlistview.view.RefreshListView
android:id="@+id/my_refresh_listview"
android:layout_width="match_parent"
android:layout_height="match_parent">

</com.example.refreshlistview.view.RefreshListView>

</LinearLayout>

5.主函数调用的代码

public class MainActivity extends Activity {

private RefreshListView myRefreshListView;
private List<String> list = new ArrayList<String>();
private Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
adapter.notifyDataSetChanged();
myRefreshListView.completeRefresh();
}
};
private MyAdapter adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initViews();
initData();
}

private void initViews() {
setContentView(R.layout.activity_main);
myRefreshListView = (RefreshListView) this.findViewById(R.id.my_refresh_listview);
}

private void initData() {
for (int i = 0; i < 20; i++){
list.add("listview的原始数据:"+i);
}
initListView();
}

private void initListView() {
adapter = new MyAdapter();
myRefreshListView.setAdapter(adapter);
myRefreshListView.setOnRefreshListener(new RefreshListView.OnRefreshListener() {
@Override
public void onPullRefresh() {
getDataFromServer(false);
}

@Override
public void onLoadingMore() {
getDataFromServer(true);
}

});
}

/**
* 模拟向服务器请求数据
*/

private void getDataFromServer(final boolean isLoadingMore){
new Thread(){
@Override
public void run() {
SystemClock.sleep(3000);//模拟请求服务器的一个时间长度
if (!isLoadingMore) {
list.add(0, "下拉刷新的数据-1");
} else {
list.add("加载更多的数据-1");
list.add("加载更多的数据-2");
list.add("加载更多的数据-3");
}

//在UI线程更新UI
handler.sendEmptyMessage(0);
}
}.start();
}

class MyAdapter extends BaseAdapter{

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

@Override
public Object getItem(int position) {
return list.get(position);
}

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

@Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView textView = new TextView(MainActivity.this);
textView.setText(list.get(position));
textView.setTextSize(18);
textView.setPadding(20, 20 ,20, 20);
return textView;
}
}