苦苦找寻的2个版本,经过测试好用。再次感谢原作者!
1.第一个版本
http://blog.csdn.net/leehong2005/article/details/12567757
前段时间项目中用到了下拉刷新功能,之前在网上也找到过类似的demo,但这些demo的质量参差不齐,用户体验也不好,接口设计也不行。最张没办法,终于忍不了了,自己就写了一个下拉刷新的框架,这个框架是一个通用的框架,效果和设计感觉都还不错,现在分享给各位看官。
1. 关于下拉刷新
2. 实现原理
3. 具体实现
1、IPullToRefresh<T extends View>
- public interface IPullToRefresh<T extends View> {
- public void setPullRefreshEnabled(boolean pullRefreshEnabled);
- public void setPullLoadEnabled(boolean pullLoadEnabled);
- public void setScrollLoadEnabled(boolean scrollLoadEnabled);
- public boolean isPullRefreshEnabled();
- public boolean isPullLoadEnabled();
- public boolean isScrollLoadEnabled();
- public void setOnRefreshListener(OnRefreshListener<T> refreshListener);
- public void onPullDownRefreshComplete();
- public void onPullUpRefreshComplete();
- public T getRefreshableView();
- public LoadingLayout getHeaderLoadingLayout();
- public LoadingLayout getFooterLoadingLayout();
- public void setLastUpdatedLabel(CharSequence label);
- }
2、PullToRefreshBase<T extends View>
- 处理onInterceptTouchEvent()和onTouchEvent()中的事件:当内容的View(比如ListView)正如处于最顶部,此时再向下拉,我们必须截断事件,然后move事件就会把后续的事件传递到onTouchEvent()方法中,然后再在这个方法中,我们根据move的距离再进行scroll整个View。
- 负责创建Header、Footer和Content View:在构造方法中调用方法去创建这三个部分的View,派生类可以重写这些方法,以提供不同式样的Header和Footer,它会调用createHeaderLoadingLayout和createFooterLoadingLayout方法来创建Header和Footer创建Content View的方法是一个抽象方法,必须让派生类来实现,返回一个非null的View,然后容器再把这个View添加到自己里面。
- 设置各种状态:这里面有很多状态,如下拉、上拉、刷新、加载中、释放等,它会根据用户拉动的距离来更改状态,状态的改变,它也会把Header和Footer的状态改变,然后Header和Footer会根据状态去显示相应的界面式样。
- 对于ListView,ScrollView,WebView这三种情况,他们是否滑动到最顶部或是最底部的实现是不一样的,所以,在PullToRefreshBase类中需要调用两个抽象方法来判断当前的位置是否在顶部或底部,而其派生类必须要实现这两个方法。比如对于ListView,它滑动到最顶部的条件就是第一个child完全可见并且first postion是0。这两个抽象方法是:
- /**
- * 判断刷新的View是否滑动到顶部
- *
- * @return true表示已经滑动到顶部,否则false
- */
- protected abstract boolean isReadyForPullDown();
- /**
- * 判断刷新的View是否滑动到底
- *
- * @return true表示已经滑动到底部,否则false
- */
- protected abstract boolean isReadyForPullUp();
- 创建可下拉刷新的View(也就是content view)的抽象方法是
- /**
- * 创建可以刷新的View
- *
- * @param context context
- * @param attrs 属性
- * @return View
- */
- protected abstract T createRefreshableView(Context context, AttributeSet attrs);
- getContentSize
这个方法返回当前这个刷新Layout的大小,通常返回的是布局的高度,为了以后可以扩展为水平拉动,所以方法名字没有取成getLayoutHeight()之类的,这个返回值,将会作为松手后是否可以刷新的临界值,如果下拉的偏移值大于这个值,就认为可以刷新,否则不刷新,这个方法必须由派生类来实现。
- setState
这个方法用来设置当前刷新Layout的状态,PullToRefreshBase类会调用这个方法,当进入下拉,松手等动作时,都会调用这个方法,派生类里面只需要根据这些状态实现不同的界面显示,如下拉状态时,就显示出箭头,刷新状态时,就显示loading的图标。可能的状态值有: RESET, PULL_TO_REFRESH, RELEASE_TO_REFRESH, REFRESHING, NO_MORE_DATA
4. 如何使用
- @Override
- public void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mPullListView = new PullToRefreshListView(this);
- setContentView(mPullListView);
- // 上拉加载不可用
- mPullListView.setPullLoadEnabled(false);
- // 滚动到底自动加载可用
- mPullListView.setScrollLoadEnabled(true);
- mCurIndex = mLoadDataCount;
- mListItems = new LinkedList<String>();
- mListItems.addAll(Arrays.asList(mStrings).subList(0, mCurIndex));
- mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mListItems);
- // 得到实际的ListView
- mListView = mPullListView.getRefreshableView();
- // 绑定数据
- mListView.setAdapter(mAdapter);
- // 设置下拉刷新的listener
- mPullListView.setOnRefreshListener(new OnRefreshListener<ListView>() {
- @Override
- public void onPullDownToRefresh(PullToRefreshBase<ListView> refreshView) {
- mIsStart = true;
- new GetDataTask().execute();
- }
- @Override
- public void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {
- mIsStart = false;
- new GetDataTask().execute();
- }
- });
- setLastUpdateTime();
- // 自动刷新
- mPullListView.doPullRefreshing(true, 500);
- }
5. 运行效果
6. 源码下载
7. Bug修复
- PullToRefreshListView#setScrollLoadEnabled方法,修正后的代码如下:
- @Override
- public void setScrollLoadEnabled(boolean scrollLoadEnabled) {
- if (isScrollLoadEnabled() == scrollLoadEnabled) {
- return;
- }
- super.setScrollLoadEnabled(scrollLoadEnabled);
- if (scrollLoadEnabled) {
- // 设置Footer
- if (null == mLoadMoreFooterLayout) {
- mLoadMoreFooterLayout = new FooterLoadingLayout(getContext());
- mListView.addFooterView(mLoadMoreFooterLayout, null, false);
- }
- mLoadMoreFooterLayout.show(true);
- } else {
- if (null != mLoadMoreFooterLayout) {
- mLoadMoreFooterLayout.show(false);
- }
- }
- }
- LoadingLayout#show方法,修正后的代码如下:
- /**
- * 显示或隐藏这个布局
- *
- * @param show flag
- */
- public void show(boolean show) {
- // If is showing, do nothing.
- if (show == (View.VISIBLE == getVisibility())) {
- return;
- }
- ViewGroup.LayoutParams params = mContainer.getLayoutParams();
- if (null != params) {
- if (show) {
- params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
- } else {
- params.height = 0;
- }
- requestLayout();
- setVisibility(show ? View.VISIBLE : View.INVISIBLE);
- }
- }
- 图片旋转兼容2.x系统
- @Override
- public void onPull(float scale) {
- if (null == mRotationHelper) {
- mRotationHelper = new ImageViewRotationHelper(mArrowImageView);
- }
- float angle = scale * 180f; // SUPPRESS CHECKSTYLE
- mRotationHelper.setRotation(angle);
- }
- /**
- * The image view rotation helper
- *
- * @author lihong06
- * @since 2014-5-2
- */
- static class ImageViewRotationHelper {
- /** The imageview */
- private final ImageView mImageView;
- /** The matrix */
- private Matrix mMatrix;
- /** Pivot X */
- private float mRotationPivotX;
- /** Pivot Y */
- private float mRotationPivotY;
- /**
- * The constructor method.
- *
- * @param imageView the image view
- */
- public ImageViewRotationHelper(ImageView imageView) {
- mImageView = imageView;
- }
- /**
- * Sets the degrees that the view is rotated around the pivot point. Increasing values
- * result in clockwise rotation.
- *
- * @param rotation The degrees of rotation.
- *
- * @see #getRotation()
- * @see #getPivotX()
- * @see #getPivotY()
- * @see #setRotationX(float)
- * @see #setRotationY(float)
- *
- * @attr ref android.R.styleable#View_rotation
- */
- public void setRotation(float rotation) {
- if (APIUtils.hasHoneycomb()) {
- mImageView.setRotation(rotation);
- } else {
- if (null == mMatrix) {
- mMatrix = new Matrix();
- // 计算旋转的中心点
- Drawable imageDrawable = mImageView.getDrawable();
- if (null != imageDrawable) {
- mRotationPivotX = Math.round(imageDrawable.getIntrinsicWidth() / 2f);
- mRotationPivotY = Math.round(imageDrawable.getIntrinsicHeight() / 2f);
- }
- }
- mMatrix.setRotate(rotation, mRotationPivotX, mRotationPivotY);
- mImageView.setImageMatrix(mMatrix);
- }
- }
- }
- PullToRefreshBase构造方法兼容2.x
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
效果图如下:
下拉刷新的原理就不讲了,可以去看 Android通用版下拉刷新上拉加载控件,实现自动加载的思路就是:
在ListView后面增加一个FooterView,时刻监听ListView的滑动状态,当FooterView被滑到可见时,执行自动加载操作。
但是这里需要注意的是手动滑到底和自由滚到底时的区别:
1、使劲滑的时候自由滚动在底部时会显示自动加载并执行加载回调
2、当用手慢慢滑动到底时,如果不松手,不会自动加载。
所以,基于这两个考虑,自然而然就需要覆写View的两个方法:onScrollChanged和onTouchEvent。
接下来就可以看代码了:
- package com.jingchen.autoload;
- import android.content.Context;
- import android.graphics.drawable.AnimationDrawable;
- import android.util.AttributeSet;
- import android.view.LayoutInflater;
- import android.view.MotionEvent;
- import android.view.View;
- import android.widget.ImageView;
- import android.widget.ListView;
- import android.widget.TextView;
- /**
- * 如果不需要下拉刷新直接在canPullDown中返回false,这里的自动加载和下拉刷新没有冲突,通过增加在尾部的footerview实现自动加载,
- * 所以在使用中不要再动footerview了
- *
- * @author chenjing
- *
- */
- public class PullableListView extends ListView implements Pullable
- {
- public static final int INIT = 0;
- public static final int LOADING = 1;
- private OnLoadListener mOnLoadListener;
- private ImageView mLoadingView;
- private TextView mStateTextView;
- private int state = INIT;
- private boolean canLoad = true;
- private AnimationDrawable mLoadAnim;
- public PullableListView(Context context)
- {
- super(context);
- init(context);
- }
- public PullableListView(Context context, AttributeSet attrs)
- {
- super(context, attrs);
- init(context);
- }
- public PullableListView(Context context, AttributeSet attrs, int defStyle)
- {
- super(context, attrs, defStyle);
- init(context);
- }
- private void init(Context context)
- {
- View view = LayoutInflater.from(context).inflate(R.layout.load_more,
- null);
- mLoadingView = (ImageView) view.findViewById(R.id.loading_icon);
- mLoadingView.setBackgroundResource(R.anim.loading_anim);
- mLoadAnim = (AnimationDrawable) mLoadingView.getBackground();
- mStateTextView = (TextView) view.findViewById(R.id.loadstate_tv);
- addFooterView(view, null, false);
- }
- @Override
- public boolean onTouchEvent(MotionEvent ev)
- {
- switch (ev.getActionMasked())
- {
- case MotionEvent.ACTION_DOWN:
- // 按下的时候禁止自动加载
- canLoad = false;
- break;
- case MotionEvent.ACTION_UP:
- // 松开手判断是否自动加载
- canLoad = true;
- checkLoad();
- break;
- }
- return super.onTouchEvent(ev);
- }
- @Override
- protected void onScrollChanged(int l, int t, int oldl, int oldt)
- {
- super.onScrollChanged(l, t, oldl, oldt);
- // 在滚动中判断是否满足自动加载条件
- checkLoad();
- }
- /**
- * 判断是否满足自动加载条件
- */
- private void checkLoad()
- {
- if (reachBottom() && mOnLoadListener != null && state != LOADING
- && canLoad)
- {
- mOnLoadListener.onLoad(this);
- changeState(LOADING);
- }
- }
- private void changeState(int state)
- {
- this.state = state;
- switch (state)
- {
- case INIT:
- mLoadAnim.stop();
- mLoadingView.setVisibility(View.INVISIBLE);
- mStateTextView.setText(R.string.more);
- break;
- case LOADING:
- mLoadingView.setVisibility(View.VISIBLE);
- mLoadAnim.start();
- mStateTextView.setText(R.string.loading);
- break;
- }
- }
- /**
- * 完成加载
- */
- public void finishLoading()
- {
- changeState(INIT);
- }
- @Override
- public boolean canPullDown()
- {
- if (getCount() == 0)
- {
- // 没有item的时候也可以下拉刷新
- return true;
- } else if (getFirstVisiblePosition() == 0
- && getChildAt(0).getTop() >= 0)
- {
- // 滑到ListView的顶部了
- return true;
- } else
- return false;
- }
- public void setOnLoadListener(OnLoadListener listener)
- {
- this.mOnLoadListener = listener;
- }
- /**
- * @return footerview可见时返回true,否则返回false
- */
- public boolean reachBottom()
- {
- if (getCount() == 0)
- {
- // 没有item的时候也可以上拉加载
- return true;
- } else if (getLastVisiblePosition() == (getCount() - 1))
- {
- // 滑到底部了
- if (getChildAt(getLastVisiblePosition() - getFirstVisiblePosition()) != null
- && getChildAt(
- getLastVisiblePosition()
- - getFirstVisiblePosition()).getTop() < getMeasuredHeight())
- return true;
- }
- return false;
- }
- public interface OnLoadListener
- {
- void onLoad(PullableListView pullableListView);
- }
- }
Pullable接口是实现下拉刷新的,不用管。代码中判断滑动到底部是根据FooterView的上边缘距离ListView底部的距离。这个功能实现起来没什么难度,其他代码就不贴上来了,下面提供源码下载:
源码下载:https://github.com/jingchenUSTC/AutoLoad