美文网首页AndroidAndroid开发学习移动开发
带你一步一步实现listview上拉加载下拉刷新

带你一步一步实现listview上拉加载下拉刷新

作者: Lazy1 | 来源:发表于2016-07-03 14:28 被阅读1030次

    带你一步一步实现listview上拉加载下拉刷新

    思路:

    • 要是实现这个效果我们需要,有一个view能在头部和底部存在(通过查找的只listview有addheard,和addfoot的方法可以将一个view添加到头部的底部)
    • 如何进来的时候不显示头部的view呢?(我门可以设置View的初始高度为0,或者设置setPadding设置他距离上面的距离为-的高度就可以隐藏了,我们使用setPadding方法)
    • 如何隐藏底部的view?(同上)
    • 如何能让view跟着我们的手势移动呢?(第一种我们可以使用OverScroller这个类,第二种我们可以通过动态的改变头部view和底部view的高度来实现移动的的效果,我们使用后者)
    • 如何实现view的自动回弹呢?(我们可以使用OverScroller这个类的startScroll方法,然后实现computeScroll方法在这个里面去动态的改变view的高度)

    第一步我们创建HeaderView这个类当顶部的刷新的view

    
    public class HeaderView extends LinearLayout {
        /** 刷新状态 */
        private LoadState mState = LoadState.NORMAL;
    
        private View mHeader = null;
        private ImageView mArrow = null;
        private ProgressBar mProgressBar = null;
        private TextView mRefreshTips = null;
        private TextView mRefreshLastTime = null;
        private RotateAnimation mRotateUp = null;
        private RotateAnimation mRotateDown = null;
        private final static int ROTATE_DURATION = 250;
    
        /** 一分钟的毫秒值,用于判断上次的更新时间. */
        private final long ONE_MINUTE = 60 * 1000;
        /** 一小时的毫秒值,用于判断上次的更新时间. */
        private final long ONE_HOUR = 60 * ONE_MINUTE;
        /** 一天的毫秒值,用于判断上次的更新时间. */
        private final long ONE_DAY = 24 * ONE_HOUR;
        /** 一月的毫秒值,用于判断上次的更新时间. */
        private final long ONE_MONTH = 30 * ONE_DAY;
        /** 一年的毫秒值,用于判断上次的更新时间. */
        private final long ONE_YEAR = 12 * ONE_MONTH;
    
        public HeaderView(Context context) {
            this(context, null);
        }
    
        public HeaderView(Context context, AttributeSet attrs) {
            super(context, attrs);
            initHeaderView(context);
        }
    
        private void initHeaderView(Context context) {
            LinearLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
            mHeader = LayoutInflater.from(context).inflate(R.layout.g_refresh_header, null);
            addView(mHeader, lp);
            setGravity(Gravity.BOTTOM);
            mArrow = (ImageView) mHeader.findViewById(R.id.ivArrow);
            mProgressBar = (ProgressBar) mHeader.findViewById(R.id.pbWaiting);
            mRefreshTips = (TextView) mHeader.findViewById(R.id.refresh_tips);
            mRefreshLastTime = (TextView) mHeader.findViewById(R.id.refresh_last_time);
            //初始化旋转的动画需要反转箭头
            mRotateUp = new RotateAnimation(0.0f, -180.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            mRotateUp.setDuration(ROTATE_DURATION);
            mRotateUp.setFillAfter(true);
    
            mRotateDown = new RotateAnimation(-180.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            mRotateDown.setDuration(ROTATE_DURATION);
            mRotateDown.setFillAfter(true);
            Log.e("HeaderView","HeaderView初始化了");
        }
    
        public void setHeaderState(LoadState state) {
            if (mState == state) {
                return;
            }
            mArrow.clearAnimation();
            if (state == LoadState.LOADING) {
                mArrow.setVisibility(View.GONE);
                mProgressBar.setVisibility(View.VISIBLE);
            } else {
                mProgressBar.setVisibility(View.GONE);
                mArrow.setVisibility(View.VISIBLE);
            }
    
            switch (state) {
            case NORMAL:
                mArrow.startAnimation(mRotateDown);
                mRefreshTips.setText(R.string.g_pull_down_for_refresh);
                break;
    
            case WILL_RELEASE:
                //旋转当前的箭头的状态
                mArrow.startAnimation(mRotateUp);
                mRefreshTips.setText(R.string.g_release_for_refresh);
                break;
    
            case LOADING:
                mRefreshTips.setText(R.string.g_refreshing);
                break;
    
            default:
                break;
            }
    
            mState = state;
        }
    
        public LoadState getCurrentState() {
            return mState;
        }
    
        public void setHeaderHeight(int height) {
            if (height <= 0) {
                height = 0;
            }
            LayoutParams lp = (LayoutParams) mHeader.getLayoutParams();
            lp.height = height;
            mHeader.setLayoutParams(lp);
        }
    
        public int getHeaderHeight() {
            return mHeader.getHeight();
        }
    
    
    
    

    我们可以看到HeaderView这个类很简单,就是初始化一个view,然后提供了设置高度,和不同状态的判断显示的不同字体的逻辑

    我们创建FooterView这个类其实和heardview一样

            
    /**
     * 底部
     */
    public class FooterView extends LinearLayout {
        /** 加载更多. */
        private LoadState mState = LoadState.NORMAL;
    
        private View mFooter = null;
        private ImageView mArrow = null;
        private ProgressBar mProgressBar = null;
        private TextView mLoaderTips = null;
    
        private RotateAnimation mRotateUp = null;
        private RotateAnimation mRotateDown = null;
        private final static int ROTATE_DURATION = 250;
    
        public FooterView(Context context) {
            this(context, null);
        }
    
        public FooterView(Context context, AttributeSet attrs) {
            super(context, attrs);
            initFooterView(context);
        }
    
        private void initFooterView(Context context) {
            LinearLayout.LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT, 0);
            mFooter = LayoutInflater.from(context).inflate(R.layout.g_loader_footer, null);
            addView(mFooter, lp);
    
            mArrow = (ImageView) mFooter.findViewById(R.id.ivLoaderArrow);
            mProgressBar = (ProgressBar) mFooter.findViewById(R.id.pbLoaderWaiting);
            mLoaderTips = (TextView) mFooter.findViewById(R.id.loader_tips);
    
            mRotateDown = new RotateAnimation(0.0f, 180.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            mRotateDown.setDuration(ROTATE_DURATION);
            mRotateDown.setFillAfter(true);
    
            mRotateUp = new RotateAnimation(180.0f, 0.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
            mRotateUp.setDuration(ROTATE_DURATION);
            mRotateUp.setFillAfter(true);
    
            show();
        }
    
        public void setFooterState(LoadState state) {
            if (mState == state) {
                return;
            }
    
            mArrow.clearAnimation();
            if (state == LoadState.LOADING) {
                mProgressBar.setVisibility(View.VISIBLE);
                mArrow.setVisibility(View.GONE);
            } else if (state == LoadState.NODATA) {
                mProgressBar.setVisibility(View.GONE);
                mArrow.setVisibility(View.GONE);
            } else {
                mProgressBar.setVisibility(View.GONE);
                mArrow.setVisibility(View.VISIBLE);
            }
    
            switch (state) {
            case NORMAL:
                mArrow.startAnimation(mRotateUp);
                mLoaderTips.setText(R.string.g_pull_up_for_more);
                break;
    
            case WILL_RELEASE:
                mArrow.startAnimation(mRotateDown);
                mLoaderTips.setText(R.string.g_release_for_more);
                break;
    
            case LOADING:
                mLoaderTips.setText(R.string.g_loading);
                break;
    
            case NODATA:
                mLoaderTips.setText(R.string.g_nodata);
                break;
    
            default:
                break;
            }
            mState = state;
        }
    
        public LoadState getCurrentState() {
            return mState;
        }
    
        public void setFooterHeight(int height) {
            if (height <= 0) {
                height = 0;
            }
    
            LayoutParams lp = (LayoutParams) mFooter.getLayoutParams();
            lp.height = height;
            mFooter.setLayoutParams(lp);
        }
    
        public int getFooterHeight() {
            return mFooter.getHeight();
        }
    
    }
    
    
    

    逻辑也是一样,设置高度,提供不同状态的判断,显示不同的内容

    接下来上我们的主要类XrefershListview

    
    
    /**
     * 作者:liujingyuan on 2015/12/21 13:14
     * 邮箱:906514731@qq.com
     * 自定义的下拉刷新上拉加载的Listview
     */
    public class XrefershListview extends ListView implements AbsListView.OnScrollListener {
        private final static String TAG = "[XrefershListview]";
        private final static float OFFSET_Y = 0.7f;
        private OverScroller mScroller;
        private final static int SCROLL_HEADER = 0;
        private final static int SCROLL_FOOTER = 1;
        //刷新的监听
        public XrefershListviewListener mListViewListener;
        /** 滑动项. */
        private int iScrollWhich = SCROLL_HEADER;
        //是否显示footview
        public boolean isShowLoadeFooterView=false;
        HeaderView mHeaderView;
        FooterView mFooterView;
        int   iHeaderHeight;
        int iFooterHeight;
        float EndRawy;
        float mStartY;
        float dy;
        private float mLastY;
    
        public XrefershListview(Context context) {
            super(context);
            initHeardView(context);
        }
    
        public XrefershListview(Context context, AttributeSet attrs) {
            super(context, attrs);
            initHeardView(context);
        }
    
        public XrefershListview(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initHeardView(context);
        }
        //初始化头部的view
        private void initHeardView(Context context) {
            //初始化scmScroller
            mScroller = new OverScroller(context,new DecelerateInterpolator());
            mHeaderView = new HeaderView(context);
            mFooterView = new FooterView(context);
            //初始化hearview的高度
            mHeaderView.setHeaderHeight(200);
            //初始化footview
            initFooterView(context);
            final RelativeLayout header_content = (RelativeLayout) mHeaderView.findViewById(R.id.header_content);
            addHeaderView(mHeaderView);
            this.setOnScrollListener(this);
            //监听到view加载完毕获取view的高度
            mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    iHeaderHeight =mHeaderView.getMeasuredHeight();
                    Log.d(TAG, "iHeaderHeight = " + iHeaderHeight);
                    //刚进来的时候我们隐藏heardview
                    mHeaderView.setPadding(0,-iHeaderHeight,0,0);
                    getViewTreeObserver().removeGlobalOnLayoutListener(this);
                }
            });
        }
    
        @Override
        public boolean onTouchEvent(MotionEvent ev) {
            switch (ev.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    //按下的时候我们需要记住起始点的Y轴的坐标
                    mLastY = ev.getY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    //移动的时候获取当前的y轴的坐标
                    mStartY = ev.getY();
                    //计算滑动的距离
                    dy=mStartY-mLastY;
                    //再次获取一次,因为我们是增量增加宽度的,每次获取的是相对于上一点的位移
                    mLastY = ev.getY();
                    if (getFirstVisiblePosition()==0&& (mHeaderView.getMeasuredHeight() > 0 || dy > 0)){
                        //通过改变heardview的高度
                        upDataHeardView(dy*0.7f);
                    }else if (getLastVisiblePosition() == getCount() - 1 && (mFooterView.getFooterHeight() > 0 || dy < 0)){
                        //通过改变footview的高度
                        updateFooterState(-dy);
                    }
                    Log.e(TAG,"dy============"+dy);
                   break;
                case MotionEvent.ACTION_UP:
                    EndRawy = ev.getRawY();
                    //如果当前的可见的第一个条目是0,并且当前的hreadview的高度不为0,滑动的距离大于0就证明是在下拉刷新
                    if (getFirstVisiblePosition()==0&& (mHeaderView.getMeasuredHeight() > 0 || dy > 0)){
                        //判断当前的状态不是正在刷新的状态就设置为刷新的状态
                        if (mHeaderView.getCurrentState() != LoadState.LOADING) {
                            if (mHeaderView.getHeaderHeight() > iHeaderHeight) {
                                mHeaderView.setHeaderState(LoadState.LOADING);
                                //调用是界面的刷新,延时1秒
                                mListViewListener.onRefresh();
                                new Handler().postDelayed(new Runnable() {
                                    @Override
                                    public void run() {
                                        resetHeaderHeight();
                                    }
                                },1000);
                            }
                        }
                     //如果当前已经显示到最后一个条目了就是上拉加载,并且当前footview的高度大于0,移动的距离也大于0
                    }else if(getLastVisiblePosition() == getCount() - 1 && (mFooterView.getFooterHeight() > 0 || dy < 0)){
                        //判断当前的状态不是正在刷新的状态就设置为刷新的状态
                        if (mFooterView.getCurrentState() != LoadState.LOADING) {
                            if (mFooterView.getFooterHeight() > iFooterHeight) {
                                mFooterView.setFooterState(LoadState.LOADING);
                                new Handler().postDelayed(new Runnable() {
                                    @Override
                                    public void run() {
                                        //调用是界面的刷新,延时1秒
                                        mListViewListener.onLoadMore();
                                        resetFooter();
                                    }
                                },3000);
                            }
                        }
                    }
                    break;
                default:
                    break;
            }
            return super.onTouchEvent(ev);
        }
        /**
         * 更新底部footview的状态
         *
         * @param delta
         */
        private void updateFooterState(float delta) {
            if (null == mFooterView) {
                return;
            }
            //设置footview的高度移动的时候,这个高度是增加每次移动的点相当于上一个点的位置的距离
            mFooterView.setFooterHeight((int) (delta + mFooterView.getFooterHeight()));
            //当前的状态不是在刷新的状态!
            if (mFooterView.getCurrentState() != LoadState.LOADING) {
                if (mFooterView.getFooterHeight() > iFooterHeight) {
                    //设置当前的显示的内容为松开加载更多
                    mFooterView.setFooterState(LoadState.WILL_RELEASE);
                } else {
                    mFooterView.setFooterState(LoadState.NORMAL);
                }
            }
        }
        /**
         * 初始化底部footview
         */
        private void initFooterView(Context context) {
            mFooterView = new FooterView(context);
            mFooterView.setFooterHeight(200);
            //隐藏footview
            mFooterView.setPadding(0,0,0,-200);
            addFooterView(mFooterView);
            mFooterView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
                @Override
                public void onGlobalLayout() {
                    //获取测量的footview的高度
                    iFooterHeight = mFooterView.getMeasuredHeight();
                    getViewTreeObserver().removeGlobalOnLayoutListener(this);
                }
            });
        }
        /**
         * 重置footview
         */
        private void resetFooter() {
            //重置刷新状态
            mFooterView.setFooterState(LoadState.NORMAL);
            if (null == mFooterView) {
                return;
            }
            int height = mFooterView.getMeasuredHeight();
            if (height == 0) {
                return;
            }
            int finalHeight = 0;
            if (height > iFooterHeight && mFooterView.getCurrentState() != LoadState.NORMAL) {
                finalHeight = iFooterHeight;
            } else if (mFooterView.getCurrentState() == LoadState.LOADING) {
                return;
            }
            iScrollWhich = SCROLL_FOOTER;
            mScroller.startScroll(0, height, 0, finalHeight - height, 300);
            invalidate();
        }
        /**
         * 重置heardview的状态
         */
        private void resetHeaderHeight() {
            //重置刷新的状态
            mHeaderView.setHeaderState(LoadState.NORMAL);
            //获取测量得到的heardview的高度
            int height = mHeaderView.getMeasuredHeight();
            //如果获取当前的View的高度等于0代表是没有移动就不做处理
            if (height == 0) // not visible.
                return;
            int finalHeight = 0;
            //如果超过HeaderView高度,则回滚到HeaderView高度即可
            if (height > iHeaderHeight && mHeaderView.getCurrentState() != LoadState.NORMAL) {
                finalHeight = iHeaderHeight;
            }
            //标记当前是heardview在移动
            iScrollWhich = SCROLL_HEADER;
            //inalHeight - height, finalHeight代表heardview抬起的时候高度-heardview初始化的高度=heardview在y轴上移动的距离,280毫秒内移动完毕
            //int startX,开始的位置
            // int mLastY,y位置开始的位置
            // int dx, x滑动的距离
            // int dy, y滑动的距离
            // int duration执行完毕需要的时间
            mScroller.startScroll(0, height, 0, finalHeight - height, 300);
            //手动调用刷新移动
            invalidate();
        }
        /**
         * 设置头部的view的高度,来达到下移的效果
         */
        private void upDataHeardView(float dy) {
            //如果当前的状态不是正在加载中,就改变状态
            if (mHeaderView.getCurrentState() != LoadState.LOADING) {
                //如果当前的heardview的高度大于原始的高度就代表用户下拉刷新了要该表状态,变成下拉刷新的状态
                if (mHeaderView.getHeaderHeight() > iHeaderHeight) {
                    mHeaderView.setHeaderState(LoadState.WILL_RELEASE);
                } else {
                    mHeaderView.setHeaderState(LoadState.NORMAL);
                }
            }
            //移动距离等于move的距离加上heardview的高度
            mHeaderView.setHeaderHeight((int) (dy + mHeaderView.getHeaderHeight()));
        }
    
        /**
         * 设置刷新的监听
         */
        public void setXrefershListviewListener(XrefershListviewListener l) {
            mListViewListener = l;
        }
        //每次移动都会调用computeScroll
        @Override
        public void computeScroll() {
            //判断是否滑动结束
            if (mScroller.computeScrollOffset()) {
                //代表是下拉刷新
                if (iScrollWhich == SCROLL_HEADER) {
                    //获取到当前滚动的位置,来动态的设置mHeaderView的高度
                    mHeaderView.setHeaderHeight(mScroller.getCurrY());
                    //代表是上拉加载改变footview
                } else if (iScrollWhich == SCROLL_FOOTER) {
                    //不断去改变footview的高度
                    mFooterView.setFooterHeight(mScroller.getCurrY());
                }
                invalidate();
            }
        }
        //监听listview的滑动监听
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
        }
        //监听listview的滑动监听
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        }
    }
    
    

    上面的注释也非常的清楚,大体思路:

    • 首先我们直接初始化HeaderView和FooterView然后设置她们的默认高度为200dp
    • addHeaderView然后我们添加头部的view
    • 设置addOnGlobalLayoutListener的监听mHeaderView.getMeasuredHeight();在回调的方法中获取测量的heardview的高度
    • mHeaderView.setPadding(0,-iHeaderHeight,0,0);这步我们就隐藏了heardview,设置他距离上面的距离为-iHeaderHeight
    • 然后我们重写onTouchEvent来实现具体的移动的逻辑,首先我们在ACTION_DOWN里面获取当前的坐标,然后在ACTION_MOVE中我们去计算滑动的距离,详情看代码的注释
    • 通过 upDataHeardView(dy*0.7f);这个方法我们实现头部view下拉的效果
     /**
         * 设置头部的view的高度,来达到下移的效果
         */
        private void upDataHeardView(float dy) {
            //如果当前的状态不是正在加载中,就改变状态
            if (mHeaderView.getCurrentState() != LoadState.LOADING) {
                //如果当前的heardview的高度大于原始的高度就代表用户下拉刷新了要该表状态,变成下拉刷新的状态
                if (mHeaderView.getHeaderHeight() > iHeaderHeight) {
                    mHeaderView.setHeaderState(LoadState.WILL_RELEASE);
                } else {
                    mHeaderView.setHeaderState(LoadState.NORMAL);
                }
            }
            //移动距离等于move的距离加上heardview的高度
            mHeaderView.setHeaderHeight((int) (dy + mHeaderView.getHeaderHeight()));
        }
    
    
    • 然后我们在看ACTION_UP,抬起的时候我们去判断当前的是否是下拉刷新,如果是就直接调用onRefresh是刷新界面的数据在resetHeaderHeight我们重置头部view的状态
     /**
         * 重置heardview的状态
         */
        private void resetHeaderHeight() {
            //重置刷新的状态
            mHeaderView.setHeaderState(LoadState.NORMAL);
            //获取测量得到的heardview的高度
            int height = mHeaderView.getMeasuredHeight();
            //如果获取当前的View的高度等于0代表是没有移动就不做处理
            if (height == 0) // not visible.
                return;
            int finalHeight = 0;
            //如果超过HeaderView高度,则回滚到HeaderView高度即可
            if (height > iHeaderHeight && mHeaderView.getCurrentState() != LoadState.NORMAL) {
                finalHeight = iHeaderHeight;
            }
            //标记当前是heardview在移动
            iScrollWhich = SCROLL_HEADER;
            //inalHeight - height, finalHeight代表heardview抬起的时候高度-heardview初始化的高度=heardview在y轴上移动的距离,280毫秒内移动完毕
            //int startX,开始的位置
            // int mLastY,y位置开始的位置
            // int dx, x滑动的距离
            // int dy, y滑动的距离
            // int duration执行完毕需要的时间
            mScroller.startScroll(0, height, 0, finalHeight - height, 300);
            //手动调用刷新移动
            invalidate();
        }
    
    
    • 我们需要重写computeScroll这个方法来实现阻尼的效果
         @Override
        public void computeScroll() {
            //判断是否滑动结束
            if (mScroller.computeScrollOffset()) {
                //代表是下拉刷新
                if (iScrollWhich == SCROLL_HEADER) {
                    //获取到当前滚动的位置,来动态的设置mHeaderView的高度
                    mHeaderView.setHeaderHeight(mScroller.getCurrY());
                    //代表是上拉加载改变footview
                } else if (iScrollWhich == SCROLL_FOOTER) {
                    //不断去改变footview的高度
                    mFooterView.setFooterHeight(mScroller.getCurrY());
                }
                invalidate();
            }
        }
    
    

    底部的刷新逻辑和头部的都一样,这样我们就实现了上拉刷新下拉加载的效果了!
    代码地址点击这

    相关文章

      网友评论

        本文标题:带你一步一步实现listview上拉加载下拉刷新

        本文链接:https://www.haomeiwen.com/subject/qfqcjttx.html