Android ListView下拉/上拉刷新:设计原理与实现



《Android ListView下拉/上拉刷新:设计原理与实现》

Android上ListView的第三方开源的下拉刷新框架很多,应用场景很多很普遍,几乎成为现在APP的通用设计典范,甚至谷歌官方都索性在Android SDK层面支持下拉刷新,我之前写了一篇文章《Android SwipeRefreshLayout:谷歌官方SDK包中的下拉刷新》专门介绍过(链接地址:http://blog.csdn.net/zhangphil/article/details/46965377 
)。

每一种ListView下拉刷新的开源框架,基本功能相同,设计原理大同小异,下拉刷新的功能实现,其中一个设计实现的的方案核心要点大多集中在ListView的OnScrollListener()等事件的重写上。但是,常见的一些下拉刷新开源框架中,有些缺乏上拉刷新的功能。上拉刷新的功能在一些应用场景中也是需要的,比如,当用户的设备屏幕由于数据需要从网络中加载,但一次网络请求根本不可能把全部数据都加载完,因此在初始化阶段只喂全部数据中的一部分数据。当用户在一个ListView中翻到最底时候,“加载更多”,注意!此处出现另外一种设计方案,比如在ListView的footer
view中设计一个按钮,假设按钮就叫做“加载更多”,当用户翻到ListView最后见底时候,点击该按钮后才“加载更多”再次发起数据请求加载更多数据,然后刷新ListView,这种设计方案也比较常见。本文则介绍一个可以自动感知ListView下拉到底、然后可自动加载更多的支持下拉/上拉刷新的ListView。

A:设计原理之综述:

因为我们要同时设计与实现下拉和上拉刷新,显然,我们不能仅仅只做下拉刷新的功能,同时还要做上拉刷新的功能。下拉刷新和上拉刷新的手势方向不同(相反),所以我们首先要做的事情就是把这两种情况(用户的意图究竟是下拉见顶刷新还是上拉见底刷新?)区分出来。

为达到这一目的,我们在ListView中监测onTouch()事件,然后使用GestureDetector判断用户手指在屏幕上的移动方向是向上还是向下,进而明确用户的意图到底是打算下拉见顶(顶,ListView的第一个item,编号为0)刷新抑或上拉见底(底,ListView的最后、最尾部的一个元素)刷新。

当我们知道用户的意图之后(下拉见顶刷新,或,上拉见底刷新 )。然后计算和分析:当前ListView在屏幕可见区域内的第一个元素(firstVisibleItem)、ListView在可见区域内的元素数量(visibleItemCount),ListView全部元素的(totalItemCount)这三者的数量关系。简单的说,当firstVisibleItem==0且用户是下拉时候,代码就认为用户的意图是下拉刷新;当firstVisibleItem + visibleItemCount == totalItemCount
时候,代码就认为用户的意图是是上拉刷新。

其中,firstVisibleItem , visibleItemCount , totalItemCount 的值可从ListView的OnScrollListener中获得更新。

B:设计原理之实现:

(第1步)给ListView setOnScrollListener,重写该ListView中OnScrollListener的onScroll方法,目的是实时更新firstVisibleItem , visibleItemCount , totalItemCount值。

(第2步):每一个Android ListView继承自Android View,Android View有一个:

public void  setOnTouchListener (View.OnTouchListener l);

我们传给这个方法一个View.OnTouchListener,然后重写View.OnTouchListener里面的:

public abstract boolean  onTouch (View v, MotionEvent event);

在这个onTouch()中我们用GestureDetector做监测,用GestureDetector判断用户是在上拉还是下拉。

更具体一些,在构造GestureDetector时候需要传进来一个SimpleOnGestureListener,其实就是重载SimpleOnGestureListener的onFling,在onFling中判断velocityY值大于0还是小于0,大于0我们就认为用户是在下拉,小于0我们就认为用户是在上拉。然后根据这两种情况,分别触发不同的onTop和onBottom事件。

代码:

测试主程序(MainActivity.java):

package zhangphil.listview;

import java.util.ArrayList;

import android.app.Activity;
import android.os.Bundle;
import android.widget.ArrayAdapter;

public class MainActivity extends Activity {

	private	PhilListView listView=null;
	private int DATA = 0;

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

		setContentView(R.layout.activity_main);

		listView = (PhilListView) findViewById(R.id.philListView);

		final ArrayList<String> items = new ArrayList<String>();

		// 使用最简单的Android系统自带的android.R.layout.simple_list_item_1装载数据。
		final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
				android.R.layout.simple_list_item_1, items);
		listView.setAdapter(adapter);

		listView.setOnPullToRefreshListener(new PhilListView.OnPullToRefreshListener() {

			@Override
			public void onBottom() {
				listView.onRefresh(true);

				items.add("上拉到尾部追加:" + DATA++);
				adapter.notifyDataSetChanged();

				listView.onRefresh(false);
			}

			@Override
			public void onTop() {
				listView.onRefresh(true);

				items.add(0, "下拉到顶部添加:" + DATA++);
				adapter.notifyDataSetChanged();

				listView.onRefresh(false);
			}
		});
	}
}

代码设计实现的下拉/上拉刷新列表:

package zhangphil.listview;

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

public class PhilListView extends ListView {

	private Context context;

	private int firstVisibleItem = 0;
	private int visibleItemCount = 0;
	private int totalItemCount = 0;

	// 一个简单的圆球形进度滚动球,向用户表明正在加载
	private ProgressDialog progressDialog = null;

	private OnPullToRefreshListener mOnPullToRefreshListener = null;

	public PhilListView(Context context) {
		super(context);
		this.context = context;
	}

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

	// 此处对外开放的回调接口,让用户可以使用上拉见底刷新或者下拉见顶刷新。
	public interface OnPullToRefreshListener {

		// 当用户的手指在屏幕上往上拉见到ListView的底部最后一个元素时候回调。
		public void onBottom();

		// 当用户的手指在屏幕上往下拉见到ListView的顶部第一个元素时候回调。
		public void onTop();
	}

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

		this.setOnScrollListener(new ListView.OnScrollListener() {

			// 把最新值赋给firstVisibleItem , visibleItemCount , totalItemCount.
			@Override
			public void onScroll(AbsListView view, int arg0, int arg1, int arg2) {
				firstVisibleItem = arg0;
				visibleItemCount = arg1;
				totalItemCount = arg2;
			}

			@Override
			public void onScrollStateChanged(AbsListView view, int scrollState) {

			}
		});

		// mGestureDetector用于监测用户在手机屏幕上的上滑和下滑事件。
		// 之所以用GestureDetector而不完全依赖ListView.OnScrollListener,主要是因为当ListView在0个元素,或者当数据元素不多不足以多屏幕滚动显示时候(换句话说,正常情况假设一屏可以显示15个,但ListView只有3个元素,那么ListView下方就会剩余空出很多空白空间,在此空间上的事件不触发ListView.OnScrollListener)。
		final GestureDetector mGestureDetector = new GestureDetector(context,
				new GestureDetector.SimpleOnGestureListener() {

					@Override
					public boolean onFling(MotionEvent e1, MotionEvent e2,
							float velocityX, float velocityY) {

						// velocityY是矢量,velocityY表明手指在竖直方向(y坐标轴)移动的矢量距离。
						// 当velocityY >0时,表明用户的手指在屏幕上往下移动。
						// 即e2事件发生点在e1事件发生点的下方。
						// 我们可以据此认为用户在下拉:用户下拉希望看到位于顶部的数据。
						// 然后在这个代码块中,判断ListView中的第一个条目firstVisibleItem是否等于0 ,
						// 等于0则意味着此时的ListView已经滑到顶部了。
						// 然后开始回调mOnPullToRefreshListener.onTop()触发onTop()事件。
						if (velocityY > 0) {
							if (firstVisibleItem == 0) {
								mOnPullToRefreshListener.onTop();
							}
						}

						// 与上面的道理相同,velocityY < 0,此时的e1在e2的下方。
						// 表明用户的手指在屏幕上往上移动,希望看到底部的数据。
						// firstVisibleItem表明屏幕当前可见视野上第一个item的值,
						// visibleItemCount是可见视野中的数目。
						// totalItemCount是ListView全部的item数目
						// 如果 firstVisibleItem + visibleItemCount ==
						// totalItemCount,则说明此时的ListView已经见底。
						if (velocityY < 0) {
							int cnt = firstVisibleItem + visibleItemCount;
							if (cnt == totalItemCount) {
								mOnPullToRefreshListener.onBottom();
							}
						}

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

		this.setOnTouchListener(new View.OnTouchListener() {

			// 用mGestureDetector监测Touch事件。
			@Override
			public boolean onTouch(View v, MotionEvent event) {
				return mGestureDetector.onTouchEvent(event);
			}
		});
	}

	/**
	 * @param refreshing
	 *
	 * 控制在加载过程中的动画显示
	 * ture:显示.
	 * false:关闭
	 */
	public void onRefresh(boolean refreshing) {
		if (refreshing)
			showProgress();
		else
			closeProgress();
	}

	// 显示ProgressDialog,表明正在加载...
	private void showProgress() {
		progressDialog = ProgressDialog.show(context, "PhilListView",
				"加载中,请稍候...", true, true);
	}

	// 关闭加载显示
	private void closeProgress() {
		if (progressDialog != null)
			progressDialog.dismiss();
	}
}

MainActivity.java需要的activity_main.xml :

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <zhangphil.listview.PhilListView
        android:id="@+id/philListView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </zhangphil.listview.PhilListView>

</LinearLayout>

效果图:

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-02 10:57:53

Android ListView下拉/上拉刷新:设计原理与实现的相关文章

十分钟搭建主流框架_下拉/上拉刷新数据(OC)

本节主题(网络部分-下拉/上拉刷新) 源码地址在文章末尾 达成效果 下拉刷新数据 上拉加载更多数据 前言 经过十分钟搭建主流框架_简单的网络部分(OC)的介绍,相信你已经实现了基本的联网获取数据,但只是粗糙的获取了固定的数据,下面就让我们来实现下拉刷新和上拉加载更多吧.同样,我们先来做准备工作吧. 准备工作 Github寻找优秀的第三方刷新框架 1.前人种树,后人乘凉.有优秀的第三方框架可以使用,当然是极好的,可以大大提高我们的开发效率,如有特殊需求只能自己手写就除外了 Refresh 2.查阅

Android listview下拉刷新 上拉加载

转载自:http://blog.csdn.net/bboyfeiyu Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能 http://blog.csdn.net/guolin_blog/article/details/9255575 打造通用的Android下拉刷新组件(适用于ListView.GridView等各类View)  http://blog.csdn.net/bboyfeiyu/article/details/39718861 Android打造(ListView.Gr

最新Android ListView 下拉刷新 上滑加载

开发项目过程中基本都会用到listView的下拉刷新和上滑加载更多,之前大家最常用的应该是pull to refresh或它的变种版吧,google官方在最新的android.support.v4包中增加了一个新类SwipeRefreshLayout,地址 这个类的作用就是提供官方的下拉刷新,并且效果相当不错,而上拉加载更多则用我们自定义的listview,也是相当简单. 下拉刷新 简单的介绍下: 首先它是一个viewgroup,但是它只允许有一个子控件,子控件能是任何view,使用的时候,所在

Android ListView 下拉刷新 点击加载更多

最近项目中用到了ListView的下拉刷新的功能,总结了一下前辈们的代码,单独抽取出来写了一个demo作为示例. 效果图 下拉刷新: 加载更多: CustomListView.java [java] view plaincopy package com.example.uitest.view; import java.util.Date; import com.example.uitest.R; import android.content.Context; import android.uti

列表下拉/上拉刷新: (一)EGORefreshTableHeaderView使用、定义EGORefreshTableFooterView

现在似乎只要是个列表,都要有下拉刷新这一项,否则就跟不上潮流了,呵呵.下拉刷新应该很多人都采用了EGORefreshTableHeaderView,具体的UI效果当然会根据自己产品的设计,再进行修改.应用中如果要展示大量数据列表,肯定不会一次都加载进来的,常规的方法都是从服务器翻页请求,每次请求n条,用户选择加载更多的时候再请求n条.根据这个需求,我们可以仿照EGORefreshTableHeaderView再实现一个footerView加在列表下面,支持上拉列表松开加载下一页数据. 效果如下

Android 自定义仿IOS上拉菜单实现

最近在做一个歪果仁给我外包的项目,主页需要做一个类似于IOS那种上拉菜单的功能,于是一时间试了各种方法,什么Spinner.Drawlayout,SlidingMenu等等等等,都搞不了,后面实在被逼无奈自己写了一个上拉菜单控件,居然还能凑合着用! 姑且可以叫他MyPullUpMenu! 有时间我会封装一下发到GitHub. 效果图如下: 实现的功能有仨: 1.上拉位置未超过一定距离时,松开自动往下滚动. 2.上拉位置超过一定距离时,松开自动网上滚动直至菜单全展开. 3.菜单滚动到顶部并停止滚动

Android ListView实现不同item的方法和原理分析

ListView实现不同item的方法和原理分析 一问题抛出Listview是android里面的重要组件,用来显示一个竖向列表,这个没有什么问题:但是有个时候列表里面的item不是一样的,如下图,列表里面应该有3种类型的item  1. 头像在左边的气泡Item ,比如”今天下午我就不出来了,...”2. 头像在右边的气泡Item,比如”那就等着我发你好吧”3. 单张图片显示圆角图片item几种Item的风格是完全不同的,那么怎么实现呢? 二实现方法实现的方法我这里可以列举出两种1. 每个It

android ListView下拉刷新之功能实现

首先要知道刷新有三个状态, 1是下拉中 2是松开刷新 3是正在刷新 还有一个非常重要的是回调接口,这个接口是正在刷新的时候外界需要做的事. 然后外界再把状态重置. 回调接口需要三个属性, private OnRefLisner listener; public void setOnRefLisner(OnRefLisner listener){        this.listener = listener;    } //回调接口    public interface OnRefLisner

手机下拉/上拉刷新(基于jq或者zepto)

       $(window).scroll(function () {                 if ($(document).scrollTop() >= $(document).height() - $(window).height()) { //通过修改大于小于号实现 上拉下拉                     loadMemberList();// 你要执行的方法:                 }             });         })