标签:android
《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>
效果图:
版权声明:本文为博主原创文章,未经博主允许不得转载。
Android ListView下拉/上拉刷新:设计原理与实现
标签:android
原文地址:http://blog.csdn.net/zhangphil/article/details/47036177