Android不用OnScrollListener采用GestureDetector结合OnTouchListener实现ListView下拉/上拉刷新



Android不用OnScrollListener采用GestureDetector结合OnTouchListener实现ListView下拉/上拉刷新

通常Android的ListView的下拉/上拉刷新实现,使用OnScrollListener比较简单,比如如果要实现下拉见顶刷新,思路是在OnScrollListener判断当前ListView的滚动状态,如果滚动停止,则将此时ListView可见区域内的第一个item的firstVisibleItem值取出来,最简单的情况(当然这种情况不完善,只是拿来说明原理和思路)就是判断firstVisibleItem是否等于0,如果等于0,则认为下拉见顶,触发写好的下拉加载代码块,上拉刷新原理相类似。
但是这种依靠OnScrollListener处理下拉/上拉事件有两个无法解决的问题:
(1)当前的ListView如果是一个空的ListView。
(2)当前的ListView非空,但其子item数量较小,以至于未能铺满整个ListView。
(3)手指的方向:是下拉还是上拉?
上述三种情况如果使用OnScrollListener则无能为力或者非常棘手解决该问题。所以在我写的附录文章3引入了OnTouchListener监听事件,然后用GestureDetector检测用户手指的方向,但仍然没有完全解决问题(1)(2),因为如果当前的ListView为空,为空就没有item,没有item就没有滚动事件,或者没有铺满超越整个屏幕,即ListView不须滚动则也就无法触发OnScrollListener进行后续的下拉/上拉处理逻辑代码块。
 本文则完全不用Android ListView的OnScrollListener,仅仅依靠GestureDetector和OnTouchListener实现ListView下拉/上拉刷新。这么做的好处就是不管当前ListView的子item是否为空或者是否完全铺满或者超越整个ListView,都能正常运作。下面就是我写的支持下拉/上拉刷新事件的ListView。使用时候,和流行下拉刷新ListView一样,只需setOnPullToRefreshListener(),然后分别在onTop()和onBottom里面进行下拉见顶业务逻辑或者上拉见底逻辑即可,例如测试代码:

package zhangphil.listview;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import zhangphil.listview.ZhangPhilPullToRefreshListView.OnPullToRefreshListener;

public class MainActivity extends Activity {

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		setContentView(R.layout.activity_main);

		String[] data = new String[20];
		for (int i = 0; i < data.length; i++) {
			data[i] = i + "";
		}

		ZhangPhilPullToRefreshListView listView = (ZhangPhilPullToRefreshListView) findViewById(R.id.listView);

		ArrayAdapter adapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1, data);
		listView.setAdapter(adapter);
		
		listView.setOnPullToRefreshListener(new OnPullToRefreshListener(){

			@Override
			public void onTop() {
				Toast.makeText(getApplicationContext(), "Top", Toast.LENGTH_SHORT).show();
			}

			@Override
			public void onBottom() {
				Toast.makeText(getApplicationContext(), "Bottom", Toast.LENGTH_SHORT).show();
			}});
	}
}


核心的ZhangPhilPullToRefreshListView:

package zhangphil.listview;

import android.content.Context;
import android.util.AttributeSet;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ListView;

public class ZhangPhilPullToRefreshListView extends ListView {

	private Context context;
	private ListView listView;
	private OnPullToRefreshListener mOnPullToRefreshListener = null;

	public void setOnPullToRefreshListener(OnPullToRefreshListener l) {
		mOnPullToRefreshListener = l;

		final GestureDetector mGestureDetector = new GestureDetector(context,
				new GestureDetector.SimpleOnGestureListener() {
					@Override
					public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {

						float y1 = e1.getY();
						float y2 = e2.getY();

						int fp = listView.getFirstVisiblePosition();
						int lp = listView.getLastVisiblePosition();

						// 下拉
						boolean flag1 = (y2 - y1) > 0;

						// 如果当前ListView没有任何数据是一个空的ListView,但用户仍然下拉,那么直接触发下拉刷新事件
						if (flag1 && listView.getCount() == 0) {
							mOnPullToRefreshListener.onTop();
							return super.onFling(e1, e2, velocityX, velocityY);
						}

						if (flag1 && (fp == 0)) {
							if (isTop()) {
								mOnPullToRefreshListener.onTop();
							}
						}

						// 上拉
						boolean flag2 = (y2 - y1) < 0;

						// 如果当前ListView没有任何数据是一个空的ListView,但用户仍然上拉,那么直接触发上拉刷新事件
						if (flag2 && listView.getCount() == 0) {
							mOnPullToRefreshListener.onBottom();
							return super.onFling(e1, e2, velocityX, velocityY);
						}

						if (flag2 && (lp == (listView.getCount() - 1))) {
							if (isBottom()) {
								mOnPullToRefreshListener.onBottom();
							}
						}

						return super.onFling(e1, e2, velocityX, velocityY);
					}
				});

		this.setOnTouchListener(new View.OnTouchListener() {

			@Override
			public boolean onTouch(View v, MotionEvent event) {
				return mGestureDetector.onTouchEvent(event);
			}
		});
	}

	public interface OnPullToRefreshListener {
		public void onTop();

		public void onBottom();
	};

	private boolean isTop() {
		int cnt = this.getCount();
		if (cnt > 0) {
			View view = this.getChildAt(0);
			if (view.getTop() == 0) {
				return true;
			}
		}

		return false;
	}

	// 判断是否底部的最后一个元素是否完全显示在屏幕上有一定的技巧
	// 最后一个元素getBottom()的值与ListView的getBottom()比较只有三种情况:大于,等于,小于。
	// 只有当最后一个元素滚到到ListView的底部可见视野以外时候,view.getBottom()才大于ListView的getBottom()
	// 剩余的情况均为等于或者小于。等于则说明刚好贴合在底部,小于则说明当前ListView的item数量少没有完全铺满屏幕
	private boolean isBottom() {
		int cnt = this.getCount();
		if (cnt > 0) {
			int fp = this.getFirstVisiblePosition();
			int lp = this.getLastVisiblePosition();
			View v = this.getChildAt(lp - fp);
			if (v.getBottom() <= this.getBottom()) {
				return true;
			}
		}

		return false;
	}

	public ZhangPhilPullToRefreshListView(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.context = context;
		listView = this;
	}
}



在判断ListView第一个item(即position=0)是否完全显现在屏幕可见视野范围内比较容易,但比较麻烦的是在于判断ListView最后一个item是否完全显现在屏幕的可见视野内。其要点是:ListView最后一个item之view的getBottom()值,与ListView的getBottom()值之间的数量关系,只有三种情况:
A,view的bottom值等于ListView的bottom值。那么此时刚好两者完全贴合在一起。

B,view的bottom值大于ListView的bottom值。这种情况说明当前ListView的子item数量少,未能完全充满整个屏幕的可见视野,及当前屏幕可见视野内有空白。
C,view的bottom值大于ListView的bottom值。这种情况说明当前的ListView有很多item,至于一屏已经容纳不下所有item,最后一个item已经滚到可见屏幕的下方,其view的bottom逻辑y坐标值已经超出ListView的边界。
明白了ABC三种情况代表的不同意义,就只需要处理A、B这两种情况进行上拉刷新代码逻辑即可。


附录我写的相关文章:
1,《Android AbsListView坐标体系解析》链接地址:http://blog.csdn.net/zhangphil/article/details/50360011
2、《Android判断ListView滚动到最顶部第0条item完全完整可见及最底部最后一条item完全完整可见》链接地址:http://blog.csdn.net/zhangphil/article/details/50329601 
3、《Android ListView下拉/上拉刷新:设计原理与实现》链接地址:http://blog.csdn.net/zhangphil/article/details/47036177

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现上拉刷新下拉加载的 ListView,可以使用第三方库,如 Google 推荐的 SwipeRefreshLayout,或者自己实现。 下面是自己实现的一种方法: 1. 在布局文件中加入 ListView 和 ProgressBar: ``` <RelativeLayout android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" /> <ProgressBar android:id="@+id/progressBar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:visibility="gone" /> </RelativeLayout> ``` 2. 在 Activity 或 Fragment 中初始化 ListView 和适配器: ``` listView = findViewById(R.id.listView); listView.setAdapter(adapter); ``` 3. 给 ListView 设置滚动监听器,当滚动到底部时,执行加载更多的操作: ``` listView.setOnScrollListener(new AbsListView.OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { if (view.getLastVisiblePosition() == view.getCount() - 1) { // 滚动到底部,执行加载更多操作 loadMoreData(); } } } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { } }); ``` 4. 在加载更多的操作中,显示 ProgressBar,加载数据后更新适配器,隐藏 ProgressBar: ``` private void loadMoreData() { progressBar.setVisibility(View.VISIBLE); // 加载数据 // 更新适配器 progressBar.setVisibility(View.GONE); } ``` 5. 实现下拉刷新,可以使用 SwipeRefreshLayout,在布局文件中加入 SwipeRefreshLayout 和 ListView: ``` <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swipeRefreshLayout" android:layout_width="match_parent" android:layout_height="match_parent"> <ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="match_parent" /> </android.support.v4.widget.SwipeRefreshLayout> ``` 6. 在 Activity 或 Fragment 中初始化 SwipeRefreshLayout 和 ListView,并给 SwipeRefreshLayout 设置刷新监听器: ``` swipeRefreshLayout = findViewById(R.id.swipeRefreshLayout); swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() { @Override public void onRefresh() { // 下拉刷新,重新加载数据 refreshData(); } }); ``` 7. 在刷新数据的操作中,显示 ProgressBar,加载数据后更新适配器,隐藏 ProgressBar 和 SwipeRefreshLayout: ``` private void refreshData() { progressBar.setVisibility(View.VISIBLE); // 加载数据 // 更新适配器 progressBar.setVisibility(View.GONE); swipeRefreshLayout.setRefreshing(false); } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值