继承SwipeRefreshLayout实现下拉刷新和上拉加载

通过继承系统提供的SwipeRefreshLayout ,实现下拉刷新和上拉加载:

  1. 允许设置加载时的最小子条目数
  2. 重置状态时的两种效果
  3. 允许ListView子条目左右滑动

使用时:

  1. 在xml文件中,使用SwipeRefreshLayout 包裹listView;
  2. 在activity或者fragment中,获取SwipeRefreshLayout 对象,设置刷新和加载更多监听,实现刷新和加载方法即可
  3. 设置允许上拉加载的最小子条目数,大于最小条目数则运行加载,否则不允许加载更多(默认10条)
  4. 设置是否允许子条目左右滑动 (默认不可以)
  5. 在重置状态时,通过控制添加的脚布局的setPadding(left,top,right,bottom);值来控制显示效果(默认改变top值)
    改变top值,则会逐渐覆盖脚布局
    改变bottom值,则会整体下移消失

注意:

如果在viewPager中嵌套MyRefreshLayout,MyRefreshLayout中嵌套ListView,ListView中的子条目需要左右滑动时, 没有在自定义的子View中实现从viewPager中获取左右滑动时的事件,暂时通过计算listView中子条目的高度方法来解决,找到解决方案再来替代 TODO

所以在内部对子条目的行为进行了判断,如果是可以左右滑动,则计算子条目的总高度,在listView范围内自己处理事件,否则交给系统处理

public class MyRefreshLayout extends SwipeRefreshLayout {

    /**
     * listview实例
     */
    private ListView mListView;

    /**
     * 上拉监听器, 到了最底部的上拉加载操作
     */
    private OnLoadListener mOnLoadListener;

    /**
     * ListView的加载中footer
     */
    private View mListViewFooter;

    /**
     * 按下时的y坐标
     */
    private int mYDown;
    /**
     * 抬起时的y坐标, 与mYDown一起用于滑动到底部时判断是上拉还是下拉
     */
    private int mMoveY;
    private ProgressBar loading_pb;
    private TextView loading_txt;
    //    能够上拉加载时,当前页最小条目数
    private int loadCount = 10;

    //    没有加载
    private static final int NOLOAD = 2;
    //    准备加载
    private static final int ISPRELOAD = 3;
    //    正在加载中
    private static final int ISLOADING = 3;
    //    当前加载状态
    private int currentState = 2;
    private int padding;
    private int measuredHeight;
    private int lastVisiblePosition;
    private int i;
    private float downX;
    private float downY;
    private int totalHeight;
    private ListView listView;
    private int headHeight;
    private boolean isDrag;


    /**
     * @param context
     */
    public MyRefreshLayout (Context context) {
        this(context, null);
    }

    @SuppressLint("InflateParams")
    public MyRefreshLayout (Context context, AttributeSet attrs) {
        super(context, attrs);
        addFootView(context);
    }

    private void addFootView(Context context) {
        mListViewFooter = LayoutInflater.from(context).inflate(
                R.layout.onloading, null, false);
        mListViewFooter.measure(0, 0);
        measuredHeight = mListViewFooter.getMeasuredHeight();
        loading_pb = (ProgressBar) mListViewFooter.findViewById(R.id.loading_pb);
        loading_txt = (TextView) mListViewFooter.findViewById(R.id.loading_txt);
        resetState();
    }

    @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        // 初始化ListView对象
        if (mListView == null) {
            getListView();
            mListView.addFooterView(mListViewFooter);
        }
    }

    /**
     * 获取ListView对象
     */
    private void getListView() {
        int childs = getChildCount();
        if (childs > 1) {
            View childView = getChildAt(1);
            if (childView instanceof ListView) {
                mListView = (ListView) childView;
            }
        }
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        final int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_MOVE:
                lastVisiblePosition = mListView.getLastVisiblePosition();
                i = mListView.getAdapter().getCount() - mListView.getFooterViewsCount() - 1;
                // 如果添加的脚布局可见,则通过改变脚布局的padding值,实现上拉效果
                if (lastVisiblePosition >= i) {
                    // 获取滑动到最底部时的按下坐标
                    if (mYDown == 0)
                        mYDown = (int) event.getY();
                    // 移动时坐标
                    mMoveY = (int) event.getY();
                    int disY = mMoveY - mYDown;
                    if (disY < 0) {
                        padding = padding - disY;
                        mListViewFooter.setPadding(0, 0, 0, padding);
                        if (!canLoad()) {   //判断是否满足需要加载条件
                            mListViewFooter.setVisibility(GONE);    //不满足加载更多的条件,则隐藏脚布局,只进行上拉操作
                        } else if (currentState == NOLOAD)  // 能加载则进行状态切换
                            currentState = ISPRELOAD; //改变加载状态为准备加载
                        else if (currentState == ISLOADING)
                            isMove = true;// 设置正在加载时,向上拖动的标记
                    }
                }
                break;

            case MotionEvent.ACTION_UP:
                // 松手时,恢复状态
                isMove = false;
                mYDown = 0;
                // 抬起
                if (lastVisiblePosition > i) {
                //当上拉时脚布局完全显示,则松手时开始执行加载操作
                    releaseAndLoad();
                } else {
                //重置状态
                    resetState();
                }
                break;
        }
        return super.dispatchTouchEvent(event);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
    // 在viewPager中嵌套MyRefreshLayout,RefreshLayout中嵌套ListView,ListView中的子条目需要左右滑动时, 子View无法从viewPager抢夺焦点,暂时通过计算listView中子条目的高度方法打一补丁,找到解决方案再来替代 TODO
        if (isDrag) {   //如果子条目可以拖动,则计算listView中子条目的总高度
            if (totalHeight == 0) {
                for (int i = 0; i < getChildCount(); i++) {
                    View childAt = getChildAt(i);
                    if (childAt instanceof ListView)
                        listView = (ListView) childAt;
                }
                if (listView.getAdapter() != null && listView.getChildAt(0) != null)
                    for (int i = 0; i < listView.getChildCount(); i++) {
                        View listItem = listView.getChildAt(i);
                        if (listItem != null) {
                            listItem.measure(0, 0);
                            // 统计所有子项的总高度
                            totalHeight += listItem.getMeasuredHeight();
                            if (i < listView.getHeaderViewsCount())
                                headHeight += totalHeight;
                        }
                    }
            }
            float y = ev.getY();
            if (y > totalHeight || y < headHeight) { //如果触摸范围不在listView的子条目范围内则允许父控件打断事件
                getParent().requestDisallowInterceptTouchEvent(false);
            } else   //如果触摸范围在listView的子条目范围内则将事件交给子条目处理
                getParent().requestDisallowInterceptTouchEvent(true);
        }
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                downX = ev.getX();
                downY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                float moveX = ev.getX();
                float moveY = ev.getY();
                float disX = moveX - downX;
                float dixY = moveY - downY;
                if (Math.abs(disX) > Math.abs(dixY)) { //如果是左右滑动,则将事件向下传递
                    return false;
                }
                downX = moveX;
                downY = moveY;
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    /**
     * 释放并加载
     */
    private void releaseAndLoad() {
        mListViewFooter.setPadding(0, 0, 0, 0); // 将脚布局位置置于底部
        if (currentState == ISPRELOAD) { // 如果状态为准备加载,则开始加载
            //  是否需要上拉加载
            mOnLoadListener.onLoad();
            currentState = ISLOADING; //改变加载状态
        } else
            resetState();
    }

    /**
     * 加载完成
     *
     * @param isHasData 是否加载到数据
     */
    public void setLoading(boolean isHasData) {
        if (isHasData) {  //如果加载到数据,则重置状态
            resetState();
        } else { //没有加载到数据,则显示没有更多数据,并做动画隐藏脚布局
            loading_txt.setText("没有更多数据");
            loading_pb.setVisibility(GONE);
            doAnimator();
        }
    }

    /**
     * 是否正在滑动
     */
    boolean isMove;

    /**
     * 缓慢隐藏脚布局
     */
    private void doAnimator() {
        if (isMove)
            return;
        ValueAnimator animator = ValueAnimator.ofInt(0, -measuredHeight);
        animator.setDuration(500);
        animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                //可以通过改变top或者bottom的值,来实现隐藏脚布局时的动画效果:覆盖隐藏获取下移消失
                mListViewFooter.setPadding(0, value, 0, 0);
            }
        });
        animator.addListener(new Animator.AnimatorListener() {
            @Override
            public void onAnimationStart(Animator animation) {

            }

            @Override
            public void onAnimationEnd(Animator animation) {
                resetState();
            }

            @Override
            public void onAnimationCancel(Animator animation) {
            }

            @Override
            public void onAnimationRepeat(Animator animation) {
            }
        });
        animator.start();
    }

    /**
     * 重置显示状态
     */
    private void resetState() {
        mListViewFooter.setVisibility(VISIBLE);
        loading_pb.setVisibility(VISIBLE);
        loading_txt.setText("努力加载中..");
        currentState = NOLOAD;
        padding = -measuredHeight;
        mListViewFooter.setPadding(0, 0, 0, padding);
    }

    /**
     * 根据现有条目数决定是否需要加载更多
     *
     * @return
     */
    private boolean canLoad() {
        if (mListView != null && mOnLoadListener != null) {
            int count = mListView.getAdapter().getCount();
            if (count - mListView.getHeaderViewsCount() - mListView.getFooterViewsCount() >= loadCount) {
                return true;
            }
        }
        return false;
    }

    /**
     * 子条目是否可以左右拖动
     *
     * @param isDrag
     */
    public void setIsDrag(boolean isDrag) {
        this.isDrag = isDrag;
    }

    /**
     * 加载更多接口
     */
    public interface OnLoadListener {
        void onLoad();
    }

    /**
     * 加载更多回调
     *
     * @param onLoadListener
     */
    public void setOnLoadListener(OnLoadListener onLoadListener) {
        mOnLoadListener = onLoadListener;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值