【关键词】
通用系列
下拉刷新
``
【问题】
- 打造一个通用的下拉刷新ListView控件;
【效果图】
【分析】
【解决方案】
- 见源码;
【代码】
使用方法
[activity_pull_to_refresh.xml]
<com.lyloou.android.PullToRefreshView
android:id="@+id/scan_lv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginEnd="16dp"
android:headerDividersEnabled="false"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_weight="1"
android:choiceMode="none"
android:divider="@color/main_sep"
android:dividerHeight="1.2dp"
android:scrollbars="none" />
[activity_scan_pull_to_refresh.xml]
<?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:background="#88d5effe"
android:gravity="center"
android:orientation="horizontal" >
<ProgressBar
android:visibility="gone"
android:id="@+id/pb_scan_progress"
style="?android:attr/progressBarStyleSmall"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/tv_scan_pull_to_refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="24dp"
android:text="下拉可以刷新"
android:textSize="18sp" />
</LinearLayout>
public class PullToRefreshActivity extends AppCompatActivity {
private LouAdapter<String> mPtrLvAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_pull_to_refresh);
final PullToRefreshView ptrLv = (PullToRefreshView) findViewById(R.id.ptrlv);
ptrLv.setAdapter(mPtrLvAdapter = new LouAdapter<String>(this, ptrLv, android.R.layout.simple_list_item_1) {
@Override
protected void assign(LouHolder holder, String s) {
holder.putText(android.R.id.text1, s);
}
});
ArrayList<String> strs = new ArrayList<String>();
for (int i = 0; i < 20; i++) {
strs.add("Item:" + i);
}
mPtrLvAdapter.initList(strs);
ptrLv.setHeadView(R.layout.activity_scan_pull_to_refresh);
ptrLv.setOnChangeStatusListener(new PullToRefreshView.OnChangeStatusListener() {
@Override
public void changeStatus(int status, int currentPaddingTop) {
TextView tips = (TextView) ptrLv.getHeadView().findViewById(R.id.tv_scan_pull_to_refresh);
switch (status) {
case PullToRefreshView.OnChangeStatusListener.STATUS_PULL:
tips.setText("下拉刷新");
break;
case PullToRefreshView.OnChangeStatusListener.STATUS_RELEASE:
tips.setText("松开以刷新");
break;
case PullToRefreshView.OnChangeStatusListener.STATUS_RUNNING:
tips.setText("正在加载...");
break;
case PullToRefreshView.OnChangeStatusListener.STATUS_COMPLETE:
tips.setText("加载完成");
break;
}
}
@Override
public void loadData() {
ptrLv.postDelayed(new Runnable() {
@Override
public void run() {
mPtrLvAdapter.addItem("I'm New");
ptrLv.recover(true);
}
}, 2000);
}
});
}
}
源码
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.app.Activity;
import android.content.Context;
import android.graphics.Point;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Display;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.WindowManager;
import android.widget.AbsListView;
import android.widget.ListView;
/**
* Created by Lou on 2016/3/29.
*/
public class PullToRefreshView extends ListView {
private View mHeadView;
private int HEAD_VIEW_HEIGHT = 0;
private int mStatus;
private boolean mLoaded;
private OnChangeStatusListener mOnChangeStatusListener;
private int mFirstVisibleItem = -1;
public PullToRefreshView(Context context) {
this(context, null);
}
public PullToRefreshView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PullToRefreshView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// 用来判断是否要展示HeadView
mFirstVisibleItem = firstVisibleItem;
}
});
}
@Override
public boolean onTouchEvent(MotionEvent ev) {
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
mLastY = ev.getY();
break;
case MotionEvent.ACTION_MOVE:
move(ev);
break;
case MotionEvent.ACTION_UP:
up();
break;
}
return super.onTouchEvent(ev);
}
public View getHeadView() {
return mHeadView;
}
public void setHeadView(int layoutId) {
mHeadView = LayoutInflater.from(getContext()).inflate(layoutId, null);
addHeaderView(mHeadView);
// 获取 HeadView 的布局高度;
HEAD_VIEW_HEIGHT = getLayoutHeight(mHeadView);
//初始化的时候隐藏 HeadView;
changePullViewPaddingTop(-HEAD_VIEW_HEIGHT);
}
private void changePullViewPaddingTop(int currentPaddingTop) {
if (mLoaded) {
changeStatus(OnChangeStatusListener.STATUS_COMPLETE, currentPaddingTop);
} else {
int boundary = ScreenUtil.dp2Px(getContext(), 2);
if (currentPaddingTop > -boundary) {
if (currentPaddingTop > 0)
currentPaddingTop = 0;
changeStatus(OnChangeStatusListener.STATUS_RELEASE, currentPaddingTop);
} else {
changeStatus(OnChangeStatusListener.STATUS_PULL, currentPaddingTop);
}
}
mHeadView.setPadding(mHeadView.getPaddingLeft(), currentPaddingTop, mHeadView.getPaddingRight(), mHeadView.getPaddingBottom());
}
public void setOnChangeStatusListener(OnChangeStatusListener listener) {
mOnChangeStatusListener = listener;
}
private void changeStatus(int status, int currentPaddingTop) {
mStatus = status;
if (mOnChangeStatusListener != null) {
mOnChangeStatusListener.changeStatus(status, currentPaddingTop);
}
}
private void changePullViewPaddingTopByDelta(int delta) {
int currentPaddingTop = mHeadView.getPaddingTop();
changePullViewPaddingTop(currentPaddingTop + delta);
}
private float mLastY;
private void move(MotionEvent ev) {
// 如果没有设置HeadView,则拖拽的时候什么都不处理;
// 如果第一个 item 不可见,则拖拽的时候什么都不处理;
// 如果没有设置更改监听,则拖拽的时候什么都不处理;
// 如果正在加载,则拖拽的时候什么都不处理;
if (mHeadView == null || mFirstVisibleItem != 0 || mOnChangeStatusListener == null || mLoaded) {
return;
}
// 往下拖拽,根据变化量,开始一点点显示 mHeadView ;
float delta = (ev.getY() - mLastY);
changePullViewPaddingTopByDelta((int) (delta / 1.5));
mLastY = ev.getY();
}
// 松开手指后根据当前的状态进行具体的操作;
private void up() {
switch (mStatus) {
case OnChangeStatusListener.STATUS_PULL:
recoverPullView();
break;
case OnChangeStatusListener.STATUS_RELEASE:
if (mOnChangeStatusListener != null) {
mLoaded = true;
changeStatus(OnChangeStatusListener.STATUS_RUNNING, mHeadView.getPaddingTop());
mOnChangeStatusListener.loadData();
}
break;
}
}
// 恢复到原始状态(让PaddingTop逐渐变为 -HEAD_VIEW_HEIGHT)
private void recoverPullView() {
ValueAnimator animator = ValueAnimator.ofInt(mHeadView.getPaddingTop(), -HEAD_VIEW_HEIGHT);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator valueAnimator) {
int value = (int) valueAnimator.getAnimatedValue();
changePullViewPaddingTop(value);
}
});
animator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
super.onAnimationEnd(animation);
mLoaded = false;
}
});
animator.setDuration(400);
animator.start();
}
/**
* 外部调用,用于恢复视图;
* @param loaded 是否已经加载过数据,"是"的话改变状态并且恢复视图,"否"的话直接恢复视图;
*/
public void recover(boolean loaded){
if(loaded){
changeStatus(PullToRefreshView.OnChangeStatusListener.STATUS_COMPLETE, 0);
recoverPullView();
} else {
recoverPullView();
}
}
/**
* 外部调用,设置一开始的时候处于正在运行状态;
*/
public void initRunning(){
changePullViewPaddingTop(0);
changeStatus(OnChangeStatusListener.STATUS_RUNNING, mHeadView.getPaddingTop());
mLoaded = true;
}
// 用于回调的接口;
public interface OnChangeStatusListener {
// 状态,0:下拉可以刷新;1:松开开始刷新;2:正在刷新;3:加载完成;
int STATUS_PULL = 0;
int STATUS_RELEASE = 1;
int STATUS_RUNNING = 2;
int STATUS_COMPLETE = 3;
void changeStatus(int status, int currentPaddingTop);
void loadData();
}
// ---------------帮助方法
public static int getLayoutHeight(View view) {
Display display = ((Activity) (view.getContext())).getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
view.measure(size.x, size.y);
return view.getMeasuredHeight();
}
// ~~~~~~~~~~~~~~~~~
// --------------------帮助类
private static class ScreenUtil {
public static DisplayMetrics getMetrics(Context context) {
DisplayMetrics metrics = new DisplayMetrics();
WindowManager wm = (WindowManager) context
.getSystemService(Context.WINDOW_SERVICE);
Display display = wm.getDefaultDisplay();
display.getMetrics(metrics);
return metrics;
}
public static int getScreenWidth(Context context) {
return getMetrics(context).widthPixels;
}
public static int getScreenHeight(Context context) {
return getMetrics(context).heightPixels;
}
public static float getScreenDensity(Context context) {
return getMetrics(context).density;
}
public static float getScreenScaleDensity(Context context) {
return getMetrics(context).scaledDensity;
}
public static int dp2Px(Context context, float dp) {
float px = (int) (getScreenDensity(context) * dp);
return (int) (px + 0.5f);
}
public static float sp2Px(Context context, float sp) {
float px = getScreenScaleDensity(context);
return sp * px;
}
}
}
【参考资料】
【扩展问题】
- 添加了HeadView之后,listView获取数据会出错;
- 可以通过 parent.getAdapter().getItem(position)来获取对应项;
- 可以通过 listView.getItemAtPosition(position);来获取对应项;
参考资料: