自定义View刷新头部,已适配AbsListView、RecyclerView

瀑布流

简单介绍

WarpLayout

用于捕捉手势操作并决定显示内容。具有以下职能:

  • 计算HeaderView、ContentView、FooterView的显示位置和置放
  • 手势捕捉判断
  • 是否可显示头部、脚部视图的通用判断。具体判断交由Mover进行

AttachView

添加头部和脚步View的抽象View。具有以下职能:

  • 根据展示高度显示不同的内容
  • 根据展示高度判断 WarpLayout 是否可刷新操作

AttachViewMover

根据 ContentView 类型不同采取不同的判断逻辑。

代码

WarpLayout

 public boolean onInterceptTouchEvent(MotionEvent event) {

        boolean headerMovable = false;
        boolean footerMovable = false;

        switch (event.getAction()) {

            // 所以在此记录按下位置,判断是否消费点击时间
            case MotionEvent.ACTION_DOWN:
                mDownX = event.getX();
                mDownY = event.getY();
                mLastY = mDownY;
                mIsVerticalMove = false;
//              mScroller.forceFinished(true);
                break;

            // 拦截满足条件的滑动事件,用来显示头部和脚部
            case MotionEvent.ACTION_MOVE:
                float currY = event.getY();
                float absDx = Math.abs(event.getX() - mDownX);
                float absDy = Math.abs(currY - mDownY);

                // 最小滑动距离、手指滑动在 X、Y方向上的比例筛选
                if (absDx >= mTouchSlop || absDy >= mTouchSlop) {
                    if (absDy / absDx > SCROLL_JUDGE_VERTICAL_HORIZONTAL) {
                        mIsVerticalMove = true;
                    }
                }
                if (mIsVerticalMove) {
                    headerMovable = isMovable4HeaderView(currY > mLastY);// ViewGroup下滑
                    footerMovable = isMovable4FooterView(currY < mLastY);// ViewGroup上滑
                }
                break;
        }
        Log.d("RefreshWarpLayout", "headerMovable:" + headerMovable + ";footerMovable:" + footerMovable);
        return headerMovable || footerMovable;
    }



 public boolean onTouchEvent(MotionEvent event) {

        switch (event.getAction()) {

            // onInterceptTouchEvent 中 ACTION_DOWN 事件一定返回false
            // MotionEvent.ACTION_DOWN 此事件可能在此处捕获
            case MotionEvent.ACTION_DOWN:
                // The incident is unlikely to happen
                break;

            // 消费 move 事件
            case MotionEvent.ACTION_MOVE:

                // 如果滑动没有到指定的距离,积累滑动事件
                float fingerScrollY = event.getY() - mLastY;
                if (Math.abs(fingerScrollY) < SCROLL_REFRESH_UI_VIEW_MINI_DISTANCE) {
                    return true;
                }

                // 申请想要的偏移量:offsetY4Ask,手指滑动距离 * 阻尼
                int offsetY4Ask = (int) (fingerScrollY * (1 - damping));
                mLastY = event.getY();

                // 计算允许的偏移量
                int offsetY4End = planMoveDistance(offsetY4Ask);

                // 截取反向操作,下滑 --> 上滑:导致不合理出现 FooterView。将这种情况标记为非法操作
                int scrolledDistanceAgo = getScrollY();
                int scrolledNumAfter = scrolledDistanceAgo + offsetY4End;
                mIsIllegalMove = scrolledDistanceAgo * scrolledNumAfter < 0;
//              Log.d("RefreshWarpLayout", "mIsIllegalMove:" + mIsIllegalMove + ";ago【" + scrolledDistanceAgo + "】,after【" + scrolledNumAfter + "】");

                // 恢复接近初始样子,但是留一点空间方便下次判断
                if (mIsIllegalMove) {
                    // -1、1为 保留态,方便下次滑动,如:下拉 - 上滑回归 - 下拉 ... 等情况
                    // 因为此前忽略了 mTouchSlop 单位一下的移动量,所以避过了会产生BUG的临界值
                    if (Math.abs(scrolledDistanceAgo) == 1) {
                        offsetY4End = 0;
                    }else {
                        offsetY4End = (scrolledDistanceAgo < 0 ? -1 : 1) - scrolledDistanceAgo;
                    }
                }

                // 移动操作
                scrollBy(0, offsetY4End);
                callMoved();
                break;

            // 取消事件,以后的手势操作不会传递到本 View
            case MotionEvent.ACTION_CANCEL:
                // 松手操作
            case MotionEvent.ACTION_UP:
                mIsIllegalMove = false;
                autoSelectActionAfterUp();
                break;
        }

        return true;
    }

    private int planMoveDistance(int offsetY4Ask){
        int offsetY4End = -offsetY4Ask;// 方向相反
        // ViewGroup向下滚动,返回值 < 0,只有HeaderView全部显示了才不能向下滚动
        // 最高点是:-HeaderView.height
        if (offsetY4Ask > 0) {
            int distanceLeft = Math.abs(-mHeaderView.getMeasuredHeight() - getScrollY());
            if (offsetY4Ask > distanceLeft) {
                offsetY4End = -distanceLeft;
            }
        }
        // ViewGroup向上滚动,返回值 > 0,只有FooterView全部显示了才不能向上滚动
        // 最低点是:FooterView.height
        else if (offsetY4Ask < 0) {
            int distanceLeft =  Math.abs(mFooterView.getMeasuredHeight() - getScrollY());
            if (Math.abs(offsetY4Ask) > distanceLeft) {
                offsetY4End = distanceLeft;
            }
        }
        return offsetY4End;
    }

AttachView

继承AbsAttachView实现添加的View

public class DefaultFooterView extends AbsAttachView {

    private TextView tvShow;
    private float canLoadPointPercent = 0.75f;

    public DefaultFooterView(Context context) {
        super(context);
    }

    @Override
    public int getLayoutId() {
        return R.layout.v_refresh_default_head;
    }

    @Override
    public void bindView(View layoutView) {
        tvShow = (TextView) layoutView.findViewById(R.id.tv_textView);
        tvShow.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.TOP);
    }

    @Override
    public boolean canLoad(int offsetY) {
        return Math.abs(offsetY) >= getMeasuredHeight() * canLoadPointPercent;
    }

    @Override
    public void onMove(int offsetY) {
        if (canLoad(offsetY)) {
            onReady();
        }else {
            onNormal();
        }
    }

    private void onNormal() {
        tvShow.setText("上拉加载更多");
    }

    private void onReady() {
        tvShow.setText("松手加载更多");
    }

    @Override
    protected void onLoading() {
        tvShow.setText("正在加载更多...");
    }

    @Override
    protected void onComplete() {
        tvShow.setText("加载更多完成");
    }
}

AttachViewMover

用于适配不同的 ContentView 判断是否到顶或到底

 private static class AbsListViewMover extends IAttachViewMover {

        private AbsListView view = null;

        AbsListViewMover(View contentView) {
            view = (AbsListView) contentView;
        }

        @Override
        public boolean canMovable4HeaderView() {
            return view != null
                    // 第一个Item是否可见
                    && view.getFirstVisiblePosition() == 0
                    // 没有数据也显示刷新头部
                    // ListView滑动到顶部没有,由于之前的判断,此时第一个ChildView展示的一定是第一个Item的内容
                    && (view.getChildAt(0) == null || view.getChildAt(0).getTop() >= 0);
        }

        @Override
        public boolean canMovable4FooterView() {
            return view != null
                    // AbsListView适配器不能为空
                    && view.getAdapter() != null
                    // 最后一个Item是否可见
                    && view.getLastVisiblePosition() == view.getAdapter().getCount() - 1
                    // ListView滑动到底部没有,由于之前的判断,此时最后一个ChildView展示的一定是最后一个Item的内容。
                    // ChildView.bottom 参考的是 AbsListView,而不是本View
                    && view.getHeight() == view.getChildAt(view.getChildCount() - 1).getBottom();

        }
    }

使用实例

XML

 <org.hjf.view.refreshlayout.RefreshWarpLayout
        android:id="@+id/refresh_layout"
        android:layout_width="match_parent"
        android:layout_height="@dimen/y600">

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="match_parent"/>
    </org.hjf.view.refreshlayout.RefreshWarpLayout>

JAVA

refreshWarpLayout.setLoadingListener(new RefreshWarpLayout.RefreshLoadingListener() {
            @Override
            public void onRefresh() {
                TaskMgr.execTaskOnBGThread(Task.RUNNABLE_PREFERENCE_HIGH, new Task() {
                    @Override
                    protected void doTask() {
                        try {
                            TimeUnit.MILLISECONDS.sleep(1500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                // 通知动作完成
                                refreshWarpLayout.loadComplete();
                            }
                        });
                    }
                });
            }

            @Override
            public void onLoadMore() {
                TaskMgr.execTaskOnBGThread(Task.RUNNABLE_PREFERENCE_HIGH, new Task() {
                    @Override
                    protected void doTask() {
                        try {
                            TimeUnit.MILLISECONDS.sleep(1500);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        runOnUiThread(new Runnable() {
                            @Override
                            public void run() {
                                refreshWarpLayout.loadComplete();
                            }
                        });
                    }
                });
            }
        });

具体例子在Demo中,还会更具需求和适配更多的View,还有更多东西哦

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值