【Android】通用系列 —— 下拉刷新之继承ListView的下拉刷新

【关键词】

通用系列 下拉刷新 ``

【问题】
  • 打造一个通用的下拉刷新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获取数据会出错;
  1. 可以通过 parent.getAdapter().getItem(position)来获取对应项;
  2. 可以通过 listView.getItemAtPosition(position);来获取对应项;

参考资料:

  1. ListView addHeaderView causes position to increase by one?
  2. 当ListView有Header时,onItemClick里的position不正确;
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值