自定義ListView實現下拉重新整理和上拉載入
阿新 • • 發佈:2019-01-04
實現ListView的下拉重新整理和上拉載入,需要先新增headerView和footerView,通過在拖動的過程中,控制頭尾佈局的paddingTop實現。先把paddingTop設為負值,來隱藏header,在下拉的過程中,不斷改變headerView的paddingTop,實現下拉過程中headerView慢慢顯示的效果。
下拉重新整理,是先將listview的頭部隱藏,然後用手勢拖動螢幕重新整理。
那麼,listview的頭部如何隱藏呢?這裡用的是header.setPadding(0,-headerViewHeight,0,0)來隱藏頭部。
當MotionEvent.ACTION_DOWN(手勢按下)觸發時,需要一個downY記錄手勢按下時y的偏移值,
然後隨著手勢的滑動即 MotionEvent.ACTION_MOVE(手勢滑動)觸發時,又需要一個moveY記錄移動的偏移值,
然後dy=moveY- downY得到的就是手指滑動的距離。然後用 paddingTop = (int) (dy - mHeadViewHeight)得到頭部的頂部到螢幕的頂部距離。如果想paddingTop >= 0則說明頭部完全顯示,如果paddingTop <0 則頭部沒有完全顯示。
下拉重新整理的幾種狀態:
/**
* 下拉重新整理
*/
public static final int STATE_PULL_TO_REFRESH = 1;
/**
* 釋放重新整理
*/
public static final int STATE_RELASE_TO_REFRESH = 2;
/**
* 正在重新整理
*/
public static final int STATE_REFRESHING = 3;
自定義包含下拉重新整理和上拉載入功能的的ListView:
package com.example.a01_pulltorefreshview.widget;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.example.a01_pulltorefreshview.R;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* 自定義包含下拉重新整理和上拉載入功能的的ListView
* Created by xiaoyehai on 2016/11/24.
*/
public class PullToRefreshListView extends ListView implements AbsListView.OnScrollListener {
/**
* 下拉重新整理
*/
public static final int STATE_PULL_TO_REFRESH = 1;
/**
* 釋放重新整理
*/
public static final int STATE_RELASE_TO_REFRESH = 2;
/**
* 正在重新整理
*/
public static final int STATE_REFRESHING = 3;
/**
* 當前狀態
*/
private int mCurrentState = STATE_PULL_TO_REFRESH;
/**
* 下拉重新整理頭佈局
*/
private View mHeadView;
/**
* 下拉重新整理狀態
*/
private TextView tvState;
/**
* 下拉重新整理時間
*/
private TextView tvTime;
/**
* 下拉重新整理旋轉箭頭圖片
*/
private ImageView ivArrow;
/**
* 下拉重新整理ProgressBar
*/
private ProgressBar pbProgress;
/**
* 下拉重新整理按下時Y抽座標
*/
private float startY;
//private int startY = -1;
/**
* 下拉重新整理移動時Y抽座標
*/
private float endY;
/**
* 下拉重新整理頭佈局的高度
*/
private int mHeadViewHeight;
/**
* 下拉重新整理箭頭旋轉動畫
*/
private RotateAnimation animUp;
/**
* 下拉重新整理箭頭旋轉動畫
*/
private RotateAnimation animDown;
/**
* 下拉重新整理過程中和頂部的padding
*/
private int paddingTop;
/**
* 上拉載入底部佈局
*/
private View mFooterView;
/**
* 上拉載入底部佈局高度
*/
private int mFooterViewHeight;
/**
* 是否正在載入更多
*/
private boolean isLoadMore;
/**
* 下拉重新整理和上拉載入事件監聽
*/
private onRefreshListener onRefreshListener;
public PullToRefreshListView(Context context) {
this(context, null);
}
public PullToRefreshListView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
/**
* 初始化頭佈局,腳佈局
*/
private void init() {
initHeadView();
initFoodView();
initAnimation();
}
/**
* 初始化頭佈局
*/
private void initHeadView() {
mHeadView = View.inflate(getContext(), R.layout.pull_to_refresh_head, null);
this.addHeaderView(mHeadView);
ivArrow = (ImageView) mHeadView.findViewById(R.id.iv_arrow);
tvState = (TextView) mHeadView.findViewById(R.id.tv_state);
tvTime = (TextView) mHeadView.findViewById(R.id.tv_time);
pbProgress = (ProgressBar) mHeadView.findViewById(R.id.pd_loading);
//按照設定的規則測量寬高
mHeadView.measure(0, 0);
//控制元件高度
mHeadViewHeight = mHeadView.getMeasuredHeight();
//隱藏頭佈局
mHeadView.setPadding(0, -mHeadViewHeight, 0, 0);
}
/**
* 初始化底部,載入更多
*/
private void initFoodView() {
mFooterView = View.inflate(getContext(), R.layout.pull_to_refresh_footer, null);
this.addFooterView(mFooterView);
//測量控制元件
mFooterView.measure(0, 0);
//控制元件的高度
mFooterViewHeight = mFooterView.getMeasuredHeight();
//隱藏控制元件
mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);
//設定監聽,載入更多
this.setOnScrollListener(this);
}
/**
* 初始化頭佈局箭頭動畫
*/
private void initAnimation() {
animUp = new RotateAnimation(0, -180,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animUp.setDuration(200);
animUp.setFillAfter(true);
animDown = new RotateAnimation(-180, 0,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animDown.setDuration(200);
animDown.setFillAfter(true);
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
//記錄起點按下的Y座標
startY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
//添加了廣告輪播頭佈局:
//當用戶按住廣告圖片進行下拉時ACTION_DOWN會被viewpager消費掉,
//導致startY沒有被賦值,此處需要重新獲取一下
// if (startY == -1) {
// startY = (int) ev.getY();
// }
//如果此時正在重新整理,跳出迴圈,不讓再次重新整理
if (mCurrentState == STATE_REFRESHING) {
break;
//return super.onTouchEvent(ev); //或者這樣寫,執行父類的處理
}
endY = ev.getY();
//手指滑動偏移量
float dy = endY - startY;
//當前顯示第一個條目的位置
int firstVisiblePosition = getFirstVisiblePosition();
//往下拉,並且顯示的是第一個可見item時第一個的時候,才能劃出控制元件
if (dy > 0 && firstVisiblePosition == 0) {
//計算當前控制元件的padding
paddingTop = (int) (dy - mHeadViewHeight);
mHeadView.setPadding(0, paddingTop, 0, 0);
//當控制元件拉至完全顯示,改為鬆開重新整理
if (paddingTop >= 0 && mCurrentState != STATE_RELASE_TO_REFRESH) {
mCurrentState = STATE_RELASE_TO_REFRESH; //鬆開重新整理狀態
//重新整理頭佈局
refreshState();
} else if (paddingTop < 0 && mCurrentState != STATE_PULL_TO_REFRESH) {
//不完全顯示,下拉重新整理
mCurrentState = STATE_PULL_TO_REFRESH;//下拉重新整理狀態
//重新整理頭佈局
refreshState();
}
return true; //當前事件被消費,攔截TouchMove,不讓listview處理該次move事件,會造成listview無法滑動
}
break;
case MotionEvent.ACTION_UP:
// startY = -1;
if (mCurrentState == STATE_PULL_TO_REFRESH) { //下拉的時候沒完全顯示就鬆開或者回去的時候,就恢復
mHeadView.setPadding(0, -mHeadViewHeight, 0, 0);
} else if (mCurrentState == STATE_RELASE_TO_REFRESH) { //鬆開重新整理狀態的時候擡起手要變成正在重新整理
mCurrentState = STATE_REFRESHING;
refreshState();
//正在重新整理時完整展示頭佈局
mHeadView.setPadding(0, 0, 0, 0);
//介面回撥,通知載入資料
if (onRefreshListener != null) {
onRefreshListener.onRefresh();
}
}
break;
}
return super.onTouchEvent(ev);
}
/**
* 根據當前狀態重新整理控制元件
*/
private void refreshState() {
switch (mCurrentState) {
case STATE_PULL_TO_REFRESH:
// TODO: 2016/11/24 下拉重新整理狀態
tvState.setText("下拉重新整理");
pbProgress.setVisibility(INVISIBLE);
ivArrow.setVisibility(VISIBLE);
ivArrow.startAnimation(animDown);
break;
case STATE_RELASE_TO_REFRESH:
// TODO: 2016/11/24 鬆開重新整理狀態
tvState.setText("釋放重新整理");
pbProgress.setVisibility(INVISIBLE);
ivArrow.setVisibility(VISIBLE);
ivArrow.startAnimation(animUp);
break;
case STATE_REFRESHING:
// TODO: 2016/11/24 正在重新整理狀態
tvState.setText("正在重新整理...");
pbProgress.setVisibility(VISIBLE);
ivArrow.setVisibility(INVISIBLE);
ivArrow.clearAnimation(); //清除箭頭動畫,否則無法隱藏
break;
}
}
/**
* 重新整理結束,收起控制元件
*
* @param success:是否下拉重新整理成功
*/
public void onRefreshComplete(boolean success) {
if (isLoadMore) { //載入更多
//載入更多完成後,收起控制元件
mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);
isLoadMore = false;
} else { //下拉重新整理
mHeadView.setPadding(0, -mHeadViewHeight, 0, 0);
mCurrentState = STATE_PULL_TO_REFRESH;
tvState.setText("下拉重新整理");
pbProgress.setVisibility(INVISIBLE);
ivArrow.setVisibility(VISIBLE);
//設定重新整理時間,只有重新整理成功之後才更新時間
if (success) {
tvTime.setText("最後重新整理時間:" + getCurrentTime());
}
}
}
/**
* 設定下拉重新整理裡面的時間
*/
private String getCurrentTime() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String time = sdf.format(new Date());
return time;
}
/**
* SCROLL_STATE_IDLE:當螢幕停止滾動時
* SCROLL_STATE_TOUCH_SCROLL:當螢幕以觸屏方式滾動並且手指還在螢幕
* SCROLL_STATE_FLING:當用戶之前滑動螢幕並擡起手指,螢幕以慣性滾動
*
* @param view
* @param scrollState
*/
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO: 2016/11/19 滑動狀態發生變化的回撥
if (scrollState == SCROLL_STATE_IDLE) { //空閒狀態
int lastVisiblePosition = getLastVisiblePosition();
//顯示最後一個item並且沒有載入更多
if (lastVisiblePosition == getCount() - 1 && !isLoadMore) {
isLoadMore = true;
mFooterView.setPadding(0, 0, 0, 0); //顯示
//setSelection(getCount() - 1); //顯示在最後一個item上
setSelection(getCount()); //顯示在最後一個item上
//通知主介面載入下一頁資料
if (onRefreshListener != null) {
onRefreshListener.onLoadMore();
}
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// TODO: 2016/11/19 滑動過程的回撥
}
/**
* 下拉重新整理的回撥介面
*/
public interface onRefreshListener {
//通知重新整理(正在重新整理)
void onRefresh();
//通知載入更多
void onLoadMore();
}
/**
* 暴露介面,設定監聽
*
* @param onRefreshListener
*/
public void setOnRefreshListener(PullToRefreshListView.onRefreshListener onRefreshListener) {
this.onRefreshListener = onRefreshListener;
}
}
頭佈局:
<?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:orientation="horizontal">
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:paddingTop="10dp">
<ImageView
android:id="@+id/iv_arrow"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="@mipmap/common_listview_headview_red_arrow" />
<ProgressBar
android:id="@+id/pd_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:indeterminateDrawable="@drawable/custom_progress"
android:visibility="invisible" />
</FrameLayout>
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tv_state"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="下拉重新整理"
android:textColor="#f00"
android:textSize="18sp" />
<TextView
android:id="@+id/tv_time"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:text="最後重新整理時間:2015-10-21 09:00:30"
android:textColor="#a000"
android:textSize="16sp" />
</LinearLayout>
</LinearLayout>
custom_progress:
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
android:fromDegrees="0"
android:pivotX="50%"
android:pivotY="50%"
android:toDegrees="360">
<shape
android:innerRadius="18dp"
android:shape="ring"
android:thickness="5dp"
android:useLevel="false">
<gradient
android:centerColor="#9f00"
android:endColor="#f00"
android:startColor="#fff"
android:type="sweep" />
</shape>
</rotate>
底部佈局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:gravity="center"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/pd_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:indeterminateDrawable="@drawable/custom_progress" />
<TextView
android:id="@+id/tv_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="載入中..."
android:textColor="#f00"
android:layout_marginLeft="10dp"
android:textSize="18sp" />
</LinearLayout>
用法:
<?xml version="1.0" encoding="utf-8"?>
<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"
tools:context="com.example.a01_pulltorefreshview.MainActivity">
<com.example.a01_pulltorefreshview.widget.PullToRefreshListView
android:id="@+id/listview"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
package com.example.a01_pulltorefreshview;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Window;
import com.example.a01_pulltorefreshview.widget.PullToRefreshListView;
import java.util.ArrayList;
import java.util.List;
/**
* 自定義下拉重新整理和上拉載入的ListView
*/
public class MainActivity extends AppCompatActivity {
private PullToRefreshListView mListView;
private List<String> list;
private ListViewAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.activity_main);
mListView = (PullToRefreshListView) findViewById(R.id.listview);
list = new ArrayList<>();
for (int i = 0; i < 30; i++) {
list.add("這是一條ListView資料:" + i);
}
//新增頭部,要在setAdapter之前新增
adapter = new ListViewAdapter(this, list);
mListView.setAdapter(adapter);
mListView.setOnRefreshListener(new PullToRefreshListView.onRefreshListener() {
@Override
public void onRefresh() {
//TODO: 2016/11/24 下拉重新整理的時候回撥該方法,載入資料
loadData();
}
@Override
public void onLoadMore() {
// TODO: 2016/11/24 上拉載入下一頁資料的回撥
loadMoreData();
}
});
}
private void loadMoreData() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//下拉重新整理的資料
list.add("上拉載入的資料");
runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.notifyDataSetChanged();
//載入結束,收起控制元件
mListView.onRefreshComplete(true);
}
});
}
}).start();
}
private void loadData() {
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//下拉重新整理的資料
list.add(0, "下拉重新整理的資料");
runOnUiThread(new Runnable() {
@Override
public void run() {
adapter.notifyDataSetChanged();
//載入結束,收起控制元件
mListView.onRefreshComplete(true);
}
});
}
}).start();
}
}