ListView下拉刷新

       不知为何,一直对ListView的下拉刷新情有独钟。或许是在项目开发过程中,使用的频率太多了吧。ListView下拉刷新,和自动加载更多,对于大多数人来说,是一个头疼的问题。很多时候,我们都选择使用框架去实现下拉刷新,加载更多,无奈,很多框架很难修改成自己所需要的效果。如果这时候,自己有一个自己的下拉刷新框架,该有多好!我也曾使用过很多的下拉刷新框架,经过自己对这些框架的使用经历,我发现该如何做一个下拉刷新框架才是最好的。这里,我将介绍如何去简单的定义出一个流畅的下拉刷新ListView。

          先预览demo的效果


         在介绍ListView编写之前,有一些知识是需要先了解的。

          1.我们将使用LinearLayout里面嵌套ListView组合来实现。因此需要熟悉事件分发机制。这里,我们通过重写LinearLayout的   onInterceptTouchEvent 方法,来进行事件的拦截。如果当前LinearLayout需要事件,来进行下拉刷新,则onInterceptTouchEvent返回true;如果当前不需要下拉刷新,则返回false,事件交给ListView处理,此时ListView就可以滑动了。

          2.在LinearLayout中,调用scrollTo()方法,对ListView进行滑动。

          3.弹性滑动,ListView下拉刷新后,需要弹性回复,使用Scroller来完成该功能。

          4.我将使用mScrollY来进行下拉刷新的判断依据。mScollY初始值为0,随着调用scrollTo()下拉,将会逐渐变小。

         所以,要完成ListView的下拉刷新,需要掌握android事件分发机制,View的scrollTo()方法,Scroller弹性滑动。     


        我将通过代码的方式,使用注释来说明:

package com.mjc.pulltorefreshdemo.refresh;

import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.AbsListView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.Scroller;

import static android.view.GestureDetector.*;

/**
 * Created by mjc on 2015/12/26.
 */
public class PullToRefreshViewListView extends LinearLayout implements AbsListView.OnScrollListener {

    private static final String TAG = "PullToRefreshViewGroup";
    //暂时未用到
    private IFooterView mFooterView;
    //自定义的一个布局,包含了刷新时,下拉时动画等方法。
    private IHeaderView mHeaderView;
    //HeaderView的高度
    private int mHeaderViewHeight;
    private ListView mListView;
    //当前listview是否允许下拉
    private boolean isPullDownEnable = false;
    //按下时的坐标
    private float downY = 0;
    //阻尼系数
    private float rate = 0.4f;
    //弹性滑动需要使用Scroller
    private Scroller mScroller;
    //向下滑动,mScrollY变小;滑动临界值
    private int mMaxScrollHeight;

    //表示是否正在刷新
    private boolean isRefreshing = false;
    //记录当前的一个mScrollY的值,做判断
    private int mScrollTop;
    private int mTouchSlop;

    public PullToRefreshViewListView(Context context) {
        super(context);
        init(context);
    }

    public PullToRefreshViewListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    private void init(Context context) {
        this.setOrientation(VERTICAL);
        mListView = new ListView(context);
        LayoutParams listParams = new LayoutParams(-1, -1);

        mListView.setLayoutParams(listParams);
        mListView.setOnScrollListener(this);
        addView(mListView);
        //添加所有的子View
        mHeaderView = new CustomHeaderView(context);
        LayoutParams params = new LayoutParams(-1, -2);
        mHeaderView.setLayoutParams(params);
        addView(mHeaderView, 0);

        //getViewTreeObserver()来获取我们的headerView的高度
        getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {
                //获取各个视图的高度
                mHeaderViewHeight = mHeaderView.getHeight();
                mHeaderView.getViewTreeObserver()
                        .removeGlobalOnLayoutListener(this);
                //隐藏headerView
                HideHeaderView(-mHeaderViewHeight);
                //初始化我们需要的当前滑动距离和滑动临界点
                mScrollTop = 0;
                mMaxScrollHeight = mHeaderViewHeight * 2;
            }
        });

        mScroller = new Scroller(context);

        mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
    }

    /**
     * 我们需要隐藏HeaderView
     *
     * @param margin
     */
    private void HideHeaderView(int margin) {
        LayoutParams params = (LayoutParams) mHeaderView.getLayoutParams();
        int topMargin = params.topMargin;
        int newMargin = topMargin + margin;
        params.topMargin = newMargin;
        mHeaderView.requestLayout();
    }

    public View getRefreshView() {
        return mListView;
    }

    /**
     * 用来确定是否允许事件传递给ListView
     *
     * @param ev
     * @return
     */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        //getScrollY()<0,表示当前正在下拉刷新的过程中,事件需要拦截,由LinearLayout处理
        boolean isHandled = false;

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //ACTION_DOWN触发时,无法判断是否拦截事件
                downY = ev.getY();
                isHandled = false;
                break;
            case MotionEvent.ACTION_MOVE:
                float dY = ev.getY() - downY;
                //isPullDownEnable在ListView的onScrollChange方法中,监听,达到了ListView的最顶端,允许拉下
                if (isPullDownEnable && dY >= mTouchSlop) {
                    isHandled = true;
                }
                if(dY<mTouchSlop){
                    isHandled =false;
                }
        }
        return isHandled;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downY = event.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float moveY = event.getY();
                float delta = (moveY - downY);
                //获取需要滑动到的位置,mScrollTop为滑动前的位置,其值等于getScrollY()。
                delta = mScrollTop - rate * delta;
                /**
                 * della为滑动的目标位置。可以取值范围为, -mHeaderViewHeight - mMaxScrollHeight到0
                 * 这里,0到-mHeaderViewHeight表示下拉刷新状态
                 *   -mHeaderViewHeight到 -mHeaderViewHeight - mMaxScrollHeight,表示松开刷新状态
                 *   因为mScrollY向正方向滑动时,是负值,逐渐变小的过程
                 *   表示为:0 ----下拉刷新状态----  (-mHeaderViewHeight)  ----松开刷新--- (-mHeaderViewHeight - mMaxScrollHeight)
                 */
                //如果目标位置大于0,则让它等于0
                if (delta >= 0) {
                    delta = 0;
                }

                if (delta <= -mHeaderViewHeight - mMaxScrollHeight) {
                    delta = -mHeaderViewHeight - mMaxScrollHeight;
                }
                //滑动到对应位置
                scrollTo(0, (int) delta);
                //根据位置更新状态
                resetRefreshState((int) delta);
                return true;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                //记录下次要布局的位置
                mScrollTop = getScrollY();
                //根据位置处理结果
                doReslut(mScrollTop);

                break;

        }
        return true;
    }


    /**
     * 处理松开后的结果
     *
     * @param mScrollTop -mHeaderViewHeight - mMaxScrollHeight  -松开刷新->-mHeaderViewHeight-下拉刷新->0
     */
    private void doReslut(int mScrollTop) {

        if (mScrollTop < 0 && mScrollTop > -mHeaderViewHeight) {
            //当前为下拉刷新状态,弹性回复到初始状态
            //todo 弹性回复到正常状态
            if (!isRefreshing)
                mHeaderView.onNormal();
            smoothScrollTo(0);

        } else if (mScrollTop >= -mHeaderViewHeight - mMaxScrollHeight && mScrollTop <= -mHeaderViewHeight) {
            //todo 弹性滑动到-mHeaderViewHeight位置,然后开始刷新
            smoothScrollTo(-mHeaderViewHeight);
            //正在刷新
            mHeaderView.onRefreshing();
            if (isRefreshing) {
                //如果当前正在刷新,无作为
            } else {
                //如果当前没有刷新,刷新
                isRefreshing = true;
                if (mRefresh != null)
                    mRefresh.onPullDownRefresh();
            }
        }
    }


    /**
     * @param delta 值为  -mHeaderViewHeight - mMaxScrollHeight  -松开刷新->-mHeaderViewHeight-下拉刷新->0
     */
    private void resetRefreshState(float delta) {
        if (isRefreshing) {
            return;
        }
        if (delta > -mHeaderViewHeight && delta <= 0) {
            //下拉刷新状态
            mHeaderView.onPullToRefresh((int) -delta);
            Log.v(TAG, "下拉刷新");
        } else if (delta >= -mHeaderViewHeight - mMaxScrollHeight && delta <= -mHeaderViewHeight) {
            //松开刷新
            mHeaderView.onReleaseToRefresh((int) -delta);
            Log.v(TAG, "松开刷新");
        }

    }


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

    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        //只有当第一个显示,并且getTop==0,才可以下拉
        if (mListView.getChildAt(0) == null) {
            isPullDownEnable = firstVisibleItem == 0;
        } else {
            //如果当前第一个可见的为0并且第一个item的gettop>=0
            isPullDownEnable = firstVisibleItem == 0 && mListView.getChildAt(0).getTop() >= 0;
        }

    }

    private IRefresh mRefresh;

    public void setOnRefreshListener(IRefresh mRefresh) {
        this.mRefresh = mRefresh;
    }

    public void onPullDownRefreshComplete() {
        isRefreshing = false;
        //todo 弹性从 -mHeaderViewHeight回复到0
        smoothScrollTo(0);
    }

    /**
     * 使用Scroller弹性滑动到指定位置
     *
     * @param scrollValue
     */
    private void smoothScrollTo(int scrollValue) {
        mScroller.startScroll(0, mScrollTop, 0, scrollValue - mScrollTop, 500);
        //主动要求重绘视图
        invalidate();
    }

    /**
     * Scroller需要配合重写这个方法才能实现弹性滑动
     */
    @Override
    public void computeScroll() {
        if (mScroller.computeScrollOffset()) {
            Log.e(TAG, "" + mScroller.getCurrY());
            scrollTo(0, mScroller.getCurrY());
            mScrollTop = mScroller.getCurrY();
            postInvalidate();
        }

    }
}


        

         整个下拉刷新的流程,其实很简单。当ListView滑动到最顶端,通过判断是否向下滑动,来判断是否拦截事件,如果拦截事件,当前Linearlayout处理滑动;处理滑动,通过监听手指滑动的距离,使用scrollTo()来滑动Linearlayout的内容;并使用Scroller来进行弹性回复。在headerView中,我们可以自定义各种各样的动画,大家可以进行尝试。


         最后,我会将代码打包,,上传到csdn。有什么错误,希望大家指正。

      源码下载地址:点击打开链接












                
  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值