欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android自定义下拉刷新控件for RecyclerView详情

程序员文章站 2022-04-08 11:36:48
1.下拉刷新肯定得用自己的 不用说 或者可以灵活运用别人的能满足各种需求 也凑合 2.而且 设计思路肯定得是包裹式的 即自定义一个view group包裹recycler view 这样解耦 这篇文...

1.下拉刷新肯定得用自己的 不用说 或者可以灵活运用别人的能满足各种需求 也凑合

2.而且 设计思路肯定得是包裹式的 即自定义一个view group包裹recycler view 这样解耦

这篇文章只是提供制作方法,在这里可以轻易的学会;但是想直接用恐怕不行,可以找别人开源的,这里的下拉刷新控件由于设计时间尚短 还很丑

效果还很初级,不过该有的都有了,所以直接用可能读者还需要改动下(比如状态恢复的时候,还有一个判断,如果拉出来一点点,直接恢复;拉出来很多,就执行刷新,还要有一个延时动画,不过这些都不是什么难事)

Android自定义下拉刷新控件for RecyclerView详情

需要解决几个问题

第一个问题

怎么实现下拉刷新,在于如何把header弄到屏幕之外

Android自定义下拉刷新控件for RecyclerView详情

绿色的是手机屏幕,如何把header弄到外面就要:

linearlayout.layoutparams params = (layoutparams) pullview.getlayoutparams();
params.topmargin = -headerheight;
pullview.setlayoutparams(params);
headerheight是在onwindowsfocuschanged中获取的控件高度,topmargin就是距离父控件的距离,负数自然就到了屏幕之外

当然这个难题可以通过scrollview来实现,不过我猜scrollview本质也是这样实现的

第二个问题

逻辑问题,什么时候事件给recyclerview,什么时候给下拉头(即recycler view的父view group ,我们要自定义的控件)

if (state == 下拉) {
    拦截
    执行下拉:修改lp
    up时,进入松开复原状态
}

if (state == 松开复原中) {
    全体阻塞
    执行复原:属性动画复原
    属性动画执行完毕,进入无状态
}

if (无状态) {
    if (rv位移为0 && 下拉手势) {
        进入下拉状态
    } else {
        不拦截
    }
}

第三个问题

逻辑转化为代码,由于你是父容器,所以你需要再onintercept中拦截事件,ontouchevent中处理事件,这两部分的事件各有耦合,所以算是个难点。不过代码注释的很清楚,可以轻松看懂

第四个问题

手松开后还有一个恢复过程,只需要弄一个value animator即可,监听topmargin的变化。

上代码,注释的一清二楚

public class pulltorefreshlayout extends linearlayout {

    private static final string tag = "xbh";

    private view pullview;
    private recyclerview rv;

    private int headerheight;
    private int headerbarheight;//有内容的部分

    private static final float pull_rate = 0.4f;

    private int recyclerviewoffset;

    public pulltorefreshlayout(context context, attributeset attrs) {
        super(context, attrs);

        //获取header view,并把它添加到recycler view之前
        pullview = layoutinflater.from(context).inflate(r.layout.pull_refresh_view, null, false);
        addview(pullview, 0);

        //设置linearlayout为垂直
        setorientation(vertical);
    }

    @override
    public void onwindowfocuschanged(boolean haswindowfocus) {
        super.onwindowfocuschanged(haswindowfocus);

        //通过this.getchildat来获取recycler view
        rv = (recyclerview) getchildat(1);

        //获取下拉view的高度(也包括上面的空白部分)
        headerheight = pullview.getheight();
        //获取下拉view不含空白部分的高度
        headerbarheight = ((viewgroup)pullview).getchildat(1).getheight();

        //初始,设置header view反向偏移一段距离,为了让他一开始处于隐藏状态
        linearlayout.layoutparams params = (layoutparams) pullview.getlayoutparams();
        params.topmargin = -headerheight;
        pullview.setlayoutparams(params);

        //通过recycler view的这个监听,实时获取recycler view的下滑位移距离
        rv.setonscrolllistener(new recyclerview.onscrolllistener() {
            @override
            public void onscrolled(recyclerview rv, int dx, int dy) {
                super.onscrolled(rv, dx, dy);
                recyclerviewoffset = rv.computeverticalscrolloffset();
            }
        });
    }

    int state = none;
    private static final int pull_down = 1;
    private static final int recover = 2;
    private static final int none = 3;

    int downy = 0;

    @override
    public boolean onintercepttouchevent(motionevent ev) {
        boolean intercepted = false;
        int y = (int) ev.gety();

        switch (ev.getaction()) {
            case motionevent.action_down:
                intercepted = false;
                downy = y;//获取按下时候的纵坐标
                break;
            case motionevent.action_move:
                if (state == pull_down) {
                    intercepted = true;//如果是正在下拉状态、恢复状态,拦截!
                }

                if (state == recover) {
                    intercepted = true;
                }

                if (state == none) {//当recycler view位移为0,且正是下拉手势,进入正在下拉状态
                    if (recyclerviewoffset == 0 && y - downy > 0) {
                        state = pull_down;
                        intercepted = true;
                    } else {
                        intercepted = false;
                    }
                }
                break;
        }
        return intercepted;
    }

    @override
    public boolean ontouchevent(motionevent ev) {
        int y = (int) ev.gety();

        switch (ev.getaction()) {
            case motionevent.action_down:
                downy = y;
                break;
            case motionevent.action_move:
                //这里获取的是鼠标下滑的距离
                int verticaloffset = y - downy;

                //鼠标下滑的距离 乘以 pull_rate,就是header view应该下滑的距离
                //因为我发现如果鼠标下滑多少,header view就下来多少,有点不太好看
                linearlayout.layoutparams params = (layoutparams) pullview.getlayoutparams();
                params.topmargin = -headerheight + (int) (verticaloffset * pull_rate);
                pullview.setlayoutparams(params);
                break;
            case motionevent.action_up:
            case motionevent.action_cancel:
                //cancel情况是手机移出屏幕,导致事件意外取消的情况,和up事件一致
                if (state == pull_down) {//事件结束,从正在下拉状态 进入 恢复状态
                    state = recover;

                    int verticaloffset2 = y - downy;

                    if (verticaloffset2 > headerbarheight) {
                        //这里就是改进的地方,不足距离,正常恢复;超过距离,延时操作
                        //还有一个改进的地方:我在恢复的时候,*了事件;其实应该是在恢复之前可以继续下拉的
                    } else {

                    }

                    //topmargin
                    //start:从你当前偏移的位置开始
                    //end:恢复到初始位置
                    final int start = -headerheight + (int) (verticaloffset2 * pull_rate);
                    final int end = -headerheight;

                    //执行一个value动画
                    valueanimator animator = valueanimator.ofint(start, end);
                    animator.setduration(500).start();
                    animator.addupdatelistener(new valueanimator.animatorupdatelistener() {
                        @override
                        public void onanimationupdate(valueanimator animation) {
                            //i:这里取得start到end动态在500ms中动态变化的值
                            int i = (int) animation.getanimatedvalue();

                            linearlayout.layoutparams params = (layoutparams) pullview.getlayoutparams();
                            params.topmargin = i;
                            pullview.setlayoutparams(params);

                            //结束情况,修改标记位成:无状态
                            if (i == end) {
                                state = none;
                            }
                        }
                    });
                }
                break;
        }
        return true;
    }
}

pull fresh view

xml version="1.0" encoding="utf-8"?>
<android.support.constraint.constraintlayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="275dp"
    android:background="@color/colorprimary"
    tools:layout_editor_absolutey="81dp">

    <imageview
        android:id="@+id/imageview"
        android:layout_width="38dp"
        android:layout_height="36dp"
        android:layout_marginstart="32dp"
        android:layout_margintop="220dp"
        android:background="@mipmap/pull_icon"
        app:layout_constraintstart_tostartof="parent"
        app:layout_constrainttop_totopof="parent" />

    <textview
        android:id="@+id/textview"
        android:layout_width="387dp"
        android:layout_height="69dp"
        android:layout_margintop="208dp"
        android:gravity="center"
        android:textcolor="#ffffff"
        app:layout_constraintstart_tostartof="parent"
        app:layout_constrainttop_totopof="parent"
        android:text="下拉刷新" />

    <progressbar
        android:id="@+id/progressbar"
        style="?android:attr/progressbarstyle"
        android:layout_width="43dp"
        android:layout_height="41dp"
        android:layout_marginend="28dp"
        android:layout_margintop="216dp"
        app:layout_constraintend_toendof="parent"
        app:layout_constrainttop_totopof="parent" />
android.support.constraint.constraintlayout>
xml version="1.0" encoding="utf-8"?>
<framelayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:app="https://schemas.android.com/apk/res-auto"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.test.mainactivity">

    <com.example.test.pulltorefreshlayout
        android:id="@+id/pt"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <android.support.v7.widget.recyclerview
            android:id="@+id/rv"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    com.example.test.pulltorefreshlayout>

framelayout>