一步步自定义下拉刷新上拉加载——自定义刷新组件进阶

一、必备基础
二、入门
三、进阶
四、优化

上一篇,自定义简单的自定义下拉刷新组件

上一篇我们已经基本实现了下拉刷新的效果,本篇介绍,嵌套ListView,RecyclerView以及ScrollView时的滑动冲突解决方式。

嵌套ListView

我们先看看嵌套了ListView的效果:

这里写图片描述

在嵌套ListView时,我们的父布局要在什么时候拦截呢?
当ListView滑到顶部时,父布局才能下拉。
当ListView滑到底部时,父布局才能上拉。
那如何判断ListView到达顶部以及底部呢?

第一个可见Item的位置为 0 即 firstVisibleItem=0,并且 firstVisibleitemView.getTop() == 0,ListView的第一个Item的高度为0

  if (firstVisibleItem == 0) {
                    View firstVisibleitemView = listView.getChildAt(0);
                    if (firstVisibleitemView != null && firstVisibleitemView.getTop() == 0) {
                        Log.d(TAG, "onScroll: 滑动到顶部  ");
                    }

同理,我们判断到达底部的代码为

 if ((firstVisibleItem + visibleItemCount) == totalItemCount) { //第一个可见Item的位置和总的可见数相加
                    View lastVisibleItemView = listView.getChildAt(listView.getChildCount() - 1);
                    if (lastVisibleItemView != null && lastVisibleItemView.getBottom() == listView.getHeight()) {
                        Log.d(TAG, "onScroll: 滑动到底部 ->"+lastVisibleItemView.getBottom());
                    }
                    }

代码

  listView.setOnScrollListener(new AbsListView.OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {

            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
                if (firstVisibleItem == 0) {
                    View firstVisibleitemView = listView.getChildAt(0);
                    if (firstVisibleitemView != null && firstVisibleitemView.getTop() == 0) {
                        Log.d(TAG, "onScroll: 滑动到顶部  ");
                    }
                } else if ((firstVisibleItem + visibleItemCount) == totalItemCount) { //第一个可见Item的位置和总的可见数相加
                    View lastVisibleItemView = listView.getChildAt(listView.getChildCount() - 1);
                    if (lastVisibleItemView != null && lastVisibleItemView.getBottom() == listView.getHeight()) {
                        Log.d(TAG, "onScroll: 滑动到底部 ->"+lastVisibleItemView.getBottom());
                    }
                }
            }
        });

上面的代码是我们在Activity中的使用方法。我们把这个思路换到我们的自定义View中,如下,当滑动到ListView的顶部和底部时,我们在ACTION_MOVE进行拦截。

  @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercept = false;
        int y = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastMoveY = y;
                intercept = false;
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaY = mLastMoveY - y;
                //下拉
                if (deltaY < 0) {
                    //获取最顶部的子View
                    View child = getChildAt(0);
                    if (child instanceof AbsListView) {
                        AbsListView listView = (AbsListView) child;
                        if (listView.getFirstVisiblePosition() == 0 && listView.getChildAt(0).getTop() == 0) {
                            intercept = true;
                        }
                    }
                } else { //上拉
                    //获取最底部的View
                    View child = getChildAt(lastChildIndex);
                    if (child instanceof AbsListView) {
                        AbsListView listView = (AbsListView) child;
                        Log.d(TAG, "onInterceptTouchEvent: " + listView.getCount());

                        if (listView.getLastVisiblePosition() == listView.getCount() - 1 && listView.getChildAt(listView.getChildCount() - 1).getBottom() == listView.getMeasuredHeight()) {
                            intercept = true;
                        }
                    }
                }

                break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                break;
        }
        return intercept;
    }

嵌套RecyclerView

//下拉
if (child instanceof RecyclerView) {
                        RecyclerView recyclerView = (RecyclerView) child;
                        if (!recyclerView.canScrollVertically(-1)) {//不能再向上滑
                            intercept = true;
                        }

//上拉        
if (child instanceof RecyclerView) {
                        RecyclerView recyclerView = (RecyclerView) child;
                        if (!recyclerView.canScrollVertically(1)) {//不能再向下滑
                            intercept = true;
                        }   

利用了View的一个方法。public boolean canScrollVertically (int direction)
这个方法是判断View在竖直方向是否还能 向上,向下 滑动。

根据上面的例子,应该可以看出。 -1 表示 向上, 1 表示向下。
当RecycleView不能再向上滑时,表示已经到达顶部,
当RecycleView不能再向下滑时,表示已经到达底部。

嵌套ScrollView

判断顶部

当我们的ScrollView.getScrollerY < = 0的时候就能判定ScrollView到达了顶部。

 if (child instanceof ScrollView) {
                        ScrollView scrollView = (ScrollView) child;
                        if (scrollView.getScrollY() <= 0) {
                            intercept = true;
                        }
                    }

判断底部

当我们的ScrollView.getScrollerY+getHeight > = ScrollView.getChildAt(0).getHeight ,即大于总的ScrollView的长度时,判定ScrollView到达底部。

 if (child instanceof ScrollView) {
                        ScrollView scrollView = (ScrollView) child;
                        View scrollChild = scrollView.getChildAt(0);
                        if (scrollView.getScrollY() + getHeight() >= scrollChild.getHeight()) {
                            intercept = true;
                        }
                    }

全部代码,主要变更的是onInterceptTouchEvent

public class SimpleRefreshLayout extends ViewGroup {
    private View mHeader;
    private View mFooter;
    private TextView pullText;
    private onRefreshListener mRefreshListener;
    private int mLastMoveY;
    private int effectiveScrollY = 100;
    private Scroller mLayoutScroller;
    private boolean isPullDown = false;
    private int mLayoutContentHeight;
    private int lastChildIndex;
    private String TAG = "SimpleRefreshLayout";


    public SimpleRefreshLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        mHeader = LayoutInflater.from(context).inflate(R.layout.item_header_layout, null);
        pullText = mHeader.findViewById(R.id.srl_tv_pull_down);
        mFooter = LayoutInflater.from(context).inflate(R.layout.item_footer_layout, null);

        mLayoutScroller = new Scroller(context);
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        lastChildIndex = getChildCount() - 1;

        RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        mHeader.setLayoutParams(params);
        mFooter.setLayoutParams(params);
        addView(mHeader);
        addView(mFooter);
    }


    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //测量子类
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }


    //布局
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        mLayoutContentHeight = 0;
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child == mHeader) {
                child.layout(0, 0 - child.getMeasuredHeight(), child.getMeasuredWidth(), 0);

            } else if (child == mFooter) {
                child.layout(0, mLayoutContentHeight, child.getMeasuredWidth(), child.getMeasuredHeight() + mLayoutContentHeight);

            } else {//内容
                child.layout(0, mLayoutContentHeight, child.getMeasuredWidth(), mLayoutContentHeight + child.getMeasuredHeight());
                mLayoutContentHeight += child.getMeasuredHeight();

            }
        }
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean intercept = false;
        int y = (int) ev.getY();
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastMoveY = y;
                intercept = false;
                break;
            case MotionEvent.ACTION_MOVE:
                int deltaY = mLastMoveY - y;
                //下拉
                if (deltaY < 0) {
                    //获取最顶部的子View
                    View child = getChildAt(0);
                    if (child instanceof AbsListView) {
                        AbsListView listView = (AbsListView) child;
                        if (listView.getFirstVisiblePosition() == 0 && listView.getChildAt(0).getTop() == 0) {
                            intercept = true;
                        }
                    } else if (child instanceof RecyclerView) {
                        RecyclerView recyclerView = (RecyclerView) child;
                        if (!recyclerView.canScrollVertically(-1)) {//不能再向上滑
                            intercept = true;
                        }
                    } else if (child instanceof ScrollView) {
                        ScrollView scrollView = (ScrollView) child;
                        if (scrollView.getScrollY() <= 0) {
                            intercept = true;
                        }
                    }
                } else { //上拉
                    //获取最底部的View
                    View child = getChildAt(lastChildIndex);
                    if (child instanceof AbsListView) {
                        AbsListView listView = (AbsListView) child;
                        Log.d(TAG, "onInterceptTouchEvent: " + listView.getCount());

                        if (listView.getLastVisiblePosition() == listView.getCount() - 1 && listView.getChildAt(listView.getChildCount() - 1).getBottom() == listView.getMeasuredHeight()) {
                            intercept = true;
                        }
                    } else if (child instanceof RecyclerView) {
                        RecyclerView recyclerView = (RecyclerView) child;
                        if (!recyclerView.canScrollVertically(1)) {//不能再向下滑
                            intercept = true;
                        }
                    } else if (child instanceof ScrollView) {
                        ScrollView scrollView = (ScrollView) child;
                        View scrollChild = scrollView.getChildAt(0);
                        if (scrollView.getScrollY() + getHeight() >= scrollChild.getHeight()) {
                            intercept = true;
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                intercept = false;
                break;
        }
        return intercept;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int y = (int) event.getY();
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mLastMoveY = y;
                break;
            case MotionEvent.ACTION_MOVE:
                int dy = mLastMoveY - y;
                if (dy < 0) {//下拉
                    isPullDown = true;
                    if (Math.abs(getScrollY()) <= mHeader.getMeasuredHeight() / 2) {
                        scrollBy(0, dy);
                        if (Math.abs(getScrollY()) >= effectiveScrollY) {
                            pullText.setText("松开刷新");
                        }
                    }
                } else {//上滑
                    if (Math.abs(getScrollY()) + Math.abs(dy) < mFooter.getMeasuredHeight() / 2) {
                        scrollBy(0, dy);
                        isPullDown = false;
                    }
                }

                break;
            case MotionEvent.ACTION_UP:

                if (isPullDown) {
                    if (Math.abs(getScrollY()) >= effectiveScrollY) {
                        if (mRefreshListener != null) {
                            mRefreshListener.onRefresh();
                        }
                        mLayoutScroller.startScroll(0, getScrollY(), 0, -getScrollY() - effectiveScrollY);
                        invalidate();
                    } else {
                        mLayoutScroller.startScroll(0, getScrollY(), 0, -getScrollY());
                        invalidate();
                    }
                } else {
                    if (Math.abs(getScrollY()) >= effectiveScrollY) {
                        if (mRefreshListener != null) {
                            mRefreshListener.onBottomRefresh();
                        }
                        mLayoutScroller.startScroll(0, getScrollY(), 0, -getScrollY() + effectiveScrollY);
                        invalidate();
                    } else {
                        mLayoutScroller.startScroll(0, getScrollY(), 0, -getScrollY());
                        invalidate();
                    }
                }
                break;
        }
        mLastMoveY = y;
        return true;
    }

    @Override
    public void computeScroll() {
        super.computeScroll();
        if (mLayoutScroller.computeScrollOffset()) {
            scrollTo(0, mLayoutScroller.getCurrY());
        }
        invalidate();
    }

    public void stopRefresh() {
        mLayoutScroller.startScroll(0, getScrollY(), 0, -getScrollY());
        invalidate();
    }

    public interface onRefreshListener {
        void onRefresh();

        void onBottomRefresh();
    }

    public void setRefreshListener(onRefreshListener listener) {
        mRefreshListener = listener;
    }
}

下一篇我们将对上述代码进行优化,更改上拉和下拉的动态效果。并参考SwipeRefreshLayout对组件进行修改。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值