View的学习笔记(三)_自己造轮子_一个带header刷新头和footer加载脚的

实现效果

刷新前
下拉刷新
上拉加载
使用方法
可以指定控件大小,默认的RecyclerView会填充指定的大小
自定义属性就三条

		<!--可以指定列表控件为ListView,赋值1-->
		<attr name="view_type" format="integer"/>
		<!--指定自己的header/footer布局-->
        <attr name="header_layout" format="reference"/>
        <attr name="footer_layout" format="reference"/>
        <!--可以自行控制需要显示header/还是footer-->
        <attr name="header_visible" format="boolean"/>
        <attr name="footer_visible" format="boolean"/>

资料文件
项目目录
项目结构分三部分,自己引用即可
项目地址:github连接点这里

项目结构

造轮子的经验总结

因为控件结构简单,子View数量也比较小,因此初始化/测量/摆放都很简单
难点在于滑动事件的处理

设计思路

在学习笔记里写到

触摸事件的处理,首先判断需要拦截的情况,在onInterceptTouchEvent(MotionEvent ev)中对应情况下返回true
然后在onTounch()中处理具体的滑动和手指抬起事件

但是自己去写的时候不知道如何下手,经常写的时候信心满满,测试的时候心态就崩了.
后来自己整理了下思路
1.响应式设计.不用考虑所有情况,只对符合我要求的情况,进行处理
2.面向过程式设计.假设要触发一个事件,我们从down手指按下事件分发开始,到move滑动处理,最后up手指弹起处理,一步一步考虑
对于触摸事件比较复杂,而需要的效果比较简单,可以考虑响应式思路,比如本项目,如果只需要处理下拉的弹出,其他都不管,那么只需要监听view滑动到底端事件分发处理,滑动弹出footer即可
但是如果我们需要考虑的情况多了,就需要从用户的角度,来考虑,用户的上拉或者下拉操作目的是什么
下面开始记录我的设计过程,这只是最终的思路,实际上,在这个思路之前,我已经更换了两种思路,都不太理想

事件分发处理

因为我们的列表zview自身是可以滑动的,所以如果不对事件分发进行处理,界面效果就是一个单纯的滑动View,header/fpooter不会弹出
因此我们需要处理事件分发,什么样的情况下,需要把屏幕滑动事件分配给ViewGroup整体滑动
header的弹出/取消
header的作用是提示用户刷新.何时header该弹出,何时header该消失呢
弹出
当列表View滑动到顶端的时候,如果用户还在向上滑动,我们就认为是想刷新,此时弹出header,
取消
当header已经弹出,用户向下拉的时候,是想要取消刷新,我们就取消header
另外,当后台刷新逻辑处理完以后,也需要我们取消header

footer的原理类似
列表View滑动到顶端/底端的监听如何开始呢
我是下面这样设计的

				int distance = (int) (lastY - ev.getRawY());
                if (Math.abs(distance) > mSlop) {
                    if (contentView instanceof RecyclerView) {
                        LinearLayoutManager manager = (LinearLayoutManager) ((RecyclerView) contentView).getLayoutManager();
                        if (manager != null) {
//                       判断滑动到顶端,开始下拉
                            if (headerVisible&&manager.findFirstCompletelyVisibleItemPosition() == 0 && distance < 0) {
                                return true;
                            }
//                     判断滑动到底端,开始上拉
                            if (footerVisible&&(manager.findLastCompletelyVisibleItemPosition() + 1) == Objects.requireNonNull(((RecyclerView) contentView).getAdapter()).getItemCount() && distance > 0) {
                                return true;
                            }
//                       只要当前显示了header/footer,就拦截事件
                            if (headerRefreshCompleted&&headerVisible){
                                return true;
                            }
                            if (footerRefreshCompleted&&footerVisible){
                                return true;
                            }
                        }
                    }
				}

首先判断是否在顶端,根据完全露出的item是否是第一条决定,但是view加载的时候默认显示的就是第一条.所以,我们需要排除默认显示的情况,默认显示的时候,如果滑动方向向下,那自然就是view自己的滑动,所以加上方向的限制.就能把滑动到顶端/底端跟正常显示到顶端/底端的事件区分开
然后,footerRefreshCompleted || headerRefreshCompleted是什么意思呢
想象一下,如果header正常显示了,用户希望取消header这个时候,开始下拉(distance>0),这个时候也需要处理,因此我们定义了一个标志值,当header显示的时候,headerRefreshCompleted为true/footer显示的时候footerRefreshCompleted 为true.只要这两个标志有一个为真,就继续分发事件
这样事件分发就搞定了

然后就是难点,滑动事件处理
我们按照滑动距离来分类,我们用Scroller类来辅助滑动
scroller类的实现如下

 @Override
 ...
 //初始化
 mScroller = new Scroller(context);
        autoScrollRange = 0.6;
        ...
        //实现自动滑动的方法(格式可以是固定的)
    public void computeScroll() {
        super.computeScroll();
        if (mScroller.computeScrollOffset()) {
            this.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            invalidate();
        }
    }
    //使用,实现滑动返回原位
     mScroller.startScroll(getScrollX(), getScrollY(), 0, -getScrollY());
      invalidate();

在onTouchEvent()中首先支持滑动

 public boolean onTouchEvent(MotionEvent event) {
        float distance = lastY - event.getRawY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_MOVE:
                lastY = event.getRawY();
//                默认可滑动,在move中处理视图内滑动事件,在up中处理视图外滑动事件
                scrollBy(0, (int) distance);
...
}

然后还是在case MotionEvent.ACTION_MOVE中,当视图在我们的viewGroup范围内滑动时
如果滑动方向向下,那么分两种情况考虑,一个是用户想要下拉显示header,另一种是想要取消footer
我们开始添加效果,如果滑动的距离超过dheader/footer的高度的一定范围,那么久调用Scroller类,来辅助滑动,显示/隐藏完整的header/footer
如果滑动方向向上,那么也是分两种情况,一个是用户想要上拉显示footer,另一种是想要取消header

//                在视图内滑动处理
                if (getScrollY() >= -headerHeight && getScrollY() <= footerHeight) {
//                向上滑动,a想要上拉显示footer,b想要上拉取消header
                    if (distance > 0) {
//                        a要上拉显示footer,超过角标的autoScrollRange就自动下拉显示
                        if (!footerRefreshCompleted && getScrollY() >= footerHeight * autoScrollRange) {
                            Log.i(TAG, "onTouchEvent: 自动上拉");
                            mScroller.startScroll(getScrollX(), getScrollY(), 0, footerHeight - getScrollY());
                            footerRefreshCompleted = true;
                            if (mListener != null) {
                                mListener.footerRefreshStart(footer, contentView);
                            } else {
                                Log.e(TAG, "onTouchEvent: mListener=null");
                            }
                        }
//                      b想要上拉取消header,超过角标的autoScrollRange就自动下拉显示
                        if (headerRefreshCompleted && getScrollY() < 0) {
                            Log.i(TAG, "onTouchEvent: 取消下拉");
                            mScroller.startScroll(getScrollX(), getScrollY(), 0, -getScrollY(), 1000);
                            headerRefreshCompleted = false;
                            if (mListener != null) {
                                mListener.headerRefreshCancel();
                            } else {
                                Log.e(TAG, "onTouchEvent: mListener=null");
                            }
                        }
                    }
                    //                    向下滑动,a想要下拉显示header,b想要下拉取消footer
                    if (distance < 0) {

//                  a判定下拉显示header,超过角标的autoScrollRange就自动下拉显示
                        if (!headerRefreshCompleted && getScrollY() <= -headerHeight * autoScrollRange) {
                            Log.i(TAG, "onTouchEvent: 自动下拉");
                            mScroller.startScroll(getScrollX(), getScrollY(), 0, -headerHeight - getScrollY());
                            headerRefreshCompleted = true;
                            headerRefreshStart();
                        }

//                  b判定想要上拉,取消上拉footer,超过角标的autoScrollRange就自动取消下拉footer
                        if (footerRefreshCompleted && getScrollY() > 0) {
                            Log.i(TAG, "onTouchEvent: 取消上拉");
                            mScroller.startScroll(getScrollX(), getScrollY(), 0, -footerHeight, 1000);
                            footerRefreshCompleted = false;
                            if (mListener != null) {
                                mListener.footerRefreshCancel();
                            } else {
                                Log.e(TAG, "onTouchEvent: mListener=null");
                            }
                        }
                    }
                }
                invalidate();

记得在处理完的最后,添加invalidate(),Scroller类才生效
这样处理完了以后,已经可以自己弹出/隐藏header/footer了,但是还有两点不足的地方需要改进
1当滑动的距离超过自动显示/隐藏范围时,自动显示/隐藏,那么当没有超过的时候,显示的就是不完整的header/footer怎么办
2当滑动的范围超过了我们定义的GroupView的范围时,会在header/footer的外围露出大片的空白
解决办法,在手指抬起事件中,如果滑动的范围超过了我们定义的GroupView的范围,那么久默认显示边界为header顶端或者footer底端,调用Scroller滚动即可;滑动的距离没有超过自动显示/隐藏范围时,我们直接调用Scroller类隐藏即可

case MotionEvent.ACTION_UP:
//                如果移动范围超过视图顶端范围,那么在手指抬起时,返回到视图最顶端
                if (getScrollY() < -headerHeight) {
                    mScroller.startScroll(getScrollX(), getScrollY(), 0, -headerHeight - getScrollY(), 500);
                }
//                如果移动范围超过视图底端范围,那么在手指抬起时,返回到视图最底端
                if (getScrollY() > footerHeight) {
                    mScroller.startScroll(getScrollX(), getScrollY(), 0, footerHeight - getScrollY());
                }
//                如果在视图范围内,手指抬起时,没有触发自动显示header/footer,就自动隐藏
                if (getScrollY() >= -headerHeight && getScrollY() <= footerHeight) {
//                    自动隐藏header
                    if (!headerRefreshCompleted && getScrollY() > -headerHeight * autoScrollRange) {
                        mScroller.startScroll(getScrollX(), getScrollY(), 0, -getScrollY());
                    }
//                    自动隐藏footer
                    if (!footerRefreshCompleted && getScrollY() < footerHeight * autoScrollRange) {
                        mScroller.startScroll(getScrollX(), getScrollY(), 0, -getScrollY());
                    }
                }
                invalidate();
                break;

##添加逻辑处理完后,取消header/footer的方法

 public void onHeaderRefreshCompleted() {
        headerRefreshCompleted = false;
        mScroller.startScroll(getScrollX(), getScrollY(), 0, -getScrollY());
        invalidate();
    }

    public void onFooterRefreshCompleted() {
        footerRefreshCompleted = false;
        mScroller.startScroll(getScrollX(), getScrollY(), 0, -getScrollY());
        invalidate();
    }

猜你喜欢

转载自blog.csdn.net/RungBy/article/details/83446038
今日推荐