Android 中 ListView 的 下拉刷新 和 上拉加载 的 重点及学习(一)

来源:互联网 发布:微信发长视频软件 编辑:程序博客网 时间:2024/05/21 08:35

(转载注明: http://blog.csdn.net/itermeng/article/details/52289929 :)

大多App中的一个必备功能:用listView实现下拉刷新和上拉加载,其实有很多大牛都写了类似的Blog,但我还想记录一下,梳理自己的思路,而且我会想之前写的轮播图博客一样,我的重点是在如何写的思路,不愿直接贴代码,想看代码的直接看文章最下面吧 :)


这里写图片描述

如上gif动图所示,接下来我们要完成下拉刷新的实现。



一. 完成 xml 文件的编写 :


1.完成listView的头布局

这里写图片描述
结合了帧布局和线性布局

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:orientation="horizontal">    <FrameLayout        android:layout_margin="5dp"        android:layout_width="50dp"        android:layout_height="50dp" >        <ImageView            android:id="@+id/iv_arrow"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_gravity="center"            android:src="@drawable/common_listview_headview_red_arrow" />        <ProgressBar            android:id="@+id/pb"            android:layout_width="match_parent"            android:layout_height="match_parent"            android:indeterminateDrawable="@drawable/shape_progress"            android:visibility="invisible" />    </FrameLayout>    <LinearLayout        android:layout_margin="5dp"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:gravity="center_horizontal"        android:orientation="vertical" >        <TextView            android:id="@+id/tv_title"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_marginTop="5dp"            android:text="下拉刷新"            android:textColor="#F00"            android:textSize="18sp" />        <TextView            android:id="@+id/tv_desc_last_refresh"            android:layout_width="wrap_content"            android:layout_height="wrap_content"            android:layout_marginTop="5dp"            android:singleLine="true"            android:text="最后刷新时间: 2015-10-11 09:20:35"            android:textColor="#666"            android:textSize="14sp" />    </LinearLayout></LinearLayout>


2. 自定义ProgressBar

这里写图片描述

这里红色渐变的圆圈为自定义的xml

<?xml version="1.0" encoding="utf-8"?><rotate xmlns:android="http://schemas.android.com/apk/res/android"    android:fromDegrees="0"    android:pivotX="50%"    android:pivotY="50%"    android:toDegrees="-360" >    <shape        xmlns:android="http://schemas.android.com/apk/res/android"        android:innerRadiusRatio="2.5"        android:shape="ring"        android:thicknessRatio="10"        android:useLevel="false" >        <gradient            android:centerColor="#44FF0000"            android:endColor="#00000000"            android:startColor="#FF0000"            android:type="sweep" />    </shape></rotate>


二. 完成逻辑编写实现下拉刷新 :

1. 自定义RefreshListView继承 Listview

继承3个构造方法,并在init()中写initHeaderView()方法,初始化头布局

    public PullToRefreshListView(Context context) {        super(context);        init()    }    public PullToRefreshListView(Context context, AttributeSet attrs) {        super(context, attrs);        init()    }    public PullToRefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        init()    }

2. 初始化头布局并隐藏

默认让头布局隐藏,主要就是测得我们刚写的xml文件头布局的高度,用padding将它隐藏起来即可,再设置listView添加头布局

    /**     * 初始化头布局     */    private void initHeaderView() {        mHeaderView = View.inflate(getContext(), R.layout.layout_header_list, null);        mArrowView = mHeaderView.findViewById(R.id.iv_arrow);        pb = (ProgressBar) mHeaderView.findViewById(R.id.pb);        mTitleText = (TextView) mHeaderView.findViewById(R.id.tv_title);        mLastRefreshTime = (TextView) mHeaderView.findViewById(R.id.tv_desc_last_refresh);        // 提前手动测量宽高        mHeaderView.measure(0, 0);// 按照设置的规则测量        mHeaderViewHeight = mHeaderView.getMeasuredHeight();        System.out.println(" measuredHeight: " + mHeaderViewHeight);        // 设置内边距, 可以隐藏当前控件 , -自身高度        mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);        // 在设置数据适配器之前执行添加 头布局/脚布局 的方法.        addHeaderView(mHeaderView);    }

3. 滑动事件处理(重点)

万事俱备,头布局也已经隐藏掉,接下来我们想通过手指的滑动,让头布局显示出来,这时就涉及到 onTouchEvent 事件。

    public static final int PULL_TO_REFRESH = 0;// 下拉刷新状态    public static final int RELEASE_REFRESH = 1;// 释放刷新状态    public static final int REFRESHING = 2; // 刷新中状态@Override    public boolean onTouchEvent(MotionEvent ev) {        // 判断滑动距离, 给Header设置paddingTop        switch (ev.getAction()) {        case MotionEvent.ACTION_DOWN:            downY = ev.getY();            System.out.println("downY: " + downY);            break;        case MotionEvent.ACTION_MOVE:            moveY = ev.getY();            System.out.println("moveY: " + moveY);            // 如果是正在刷新中, 就执行父类的处理            if(currentState == REFRESHING){                return super.onTouchEvent(ev);            }            float offset = moveY - downY; // 移动的偏移量            // 只有 偏移量>0, 并且当前第一个可见条目索引是0, 才放大头部            if(offset > 0 && getFirstVisiblePosition() == 0){         //int paddingTop = -自身高度 + 偏移量                int paddingTop = (int) (- mHeaderViewHeight + offset);                mHeaderView.setPadding(0, paddingTop, 0, 0);                if(paddingTop >= 0 && currentState != RELEASE_REFRESH){// 头布局完全显示                    System.out.println("切换成释放刷新模式: " + paddingTop);                    // 切换成释放刷新模式                    currentState = RELEASE_REFRESH;                    updateHeader(); // 根据最新的状态值更新头布局内容                }else if(paddingTop < 0 && currentState != PULL_TO_REFRESH){ // 头布局不完全显示                    System.out.println("切换成下拉刷新模式: " + paddingTop);                    // 切换成下拉刷新模式                    currentState = PULL_TO_REFRESH;                    updateHeader(); // 根据最新的状态值更新头布局内容                }                return true; // 当前事件被我们处理并消费            }            break;        case MotionEvent.ACTION_UP:            // 根据刚刚设置状态            if(currentState == PULL_TO_REFRESH){//          - paddingTop < 0 不完全显示, 恢复                mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);            }else if(currentState == RELEASE_REFRESH){//          - paddingTop >= 0 完全显示, 执行正在刷新...                mHeaderView.setPadding(0, 0, 0, 0);                currentState = REFRESHING;                 updateHeader();            }            break;        default:            break;        }        return super.onTouchEvent(ev);    }

首先要了解三个状态:

这里写图片描述

(1)PULL_TO_REFRESH下拉刷新:拉的过程中会进入到“释放刷新”,要是拉到一半就放手未进入“释放刷新”,就不会去刷新数据。
(2)RELEASE_REFRESH释放刷新:拉到了一定距离,只要放手就会进入”刷新中”状态
(3)REFRESHING刷新中:状态为正在刷新,此时就可以去请求数据了。


其次从 DownMoveUp这三个动作来分析逻辑:
(1) Down: 即手指刚刚点下去,在这里只需要获取当前的 y坐标值(上下滑动,只在意Y值)

(2) Move: 即手指滑动的过程,在这里需要根据移动的距离 offset 当前的状态显示出头布局改变状态

1. 当前状态为“正在刷新”,即不处理(也不看第二点了,return出去)。    2.  只有 偏移量 offset>0, 并且当前第一个可见条目索引是0, 才显示头布局。        paddingTop :  需要隐藏的距离= - 自身高度 + 偏移量。        2.1 (paddingTop >= 0 且 状态不等于“释放刷新”):头布局完全显示,此时状态改为“释放刷新”        2.2 (paddingTop < 0 且 状态不等于“释放刷新”): 头布局不完全显示,此时状态改为“下拉刷新”        2.3 其余情况不管:

情况2.1:这里写图片描述

情况2.2:这里写图片描述


(3) Up: 即手指抬起,根据Move过程后改变的状态来判断是否进行刷新的逻辑状态修改

1.状态为”下拉刷新“ :即收起头布局,不需要进行刷新的逻辑。2.状态为”释放刷新“ :显示完全头布局,修改状态为”正在刷新“,进行刷新的逻辑。

(以上代码中的refreshState方法留到下下一点讲,其实就是一个UI的变化)
(监听对象mListener 采取回调,第六点讲解)



4.下拉刷新箭头旋转动画

以上完成后,接下来完善一下头布局的动画,需要在init()中初始化initAnimation()

    /**     * 初始化头布局的动画     */    private void initAnimation() {        // 向上转, 围绕着自己的中心, 逆时针旋转0 -> -180.        rotateUpAnim = new RotateAnimation(0f, -180f,                 Animation.RELATIVE_TO_SELF, 0.5f,                 Animation.RELATIVE_TO_SELF, 0.5f);        rotateUpAnim.setDuration(300);        rotateUpAnim.setFillAfter(true); // 动画停留在结束位置        // 向下转, 围绕着自己的中心, 逆时针旋转 -180 -> -360        rotateDownAnim = new RotateAnimation(-180f, -360,                Animation.RELATIVE_TO_SELF, 0.5f,                 Animation.RELATIVE_TO_SELF, 0.5f);        rotateDownAnim.setDuration(300);        rotateDownAnim.setFillAfter(true); // 动画停留在结束位置     }

5. refreshState()修改头布局中UI的变化

第五点同第四点都是有关于UI的变化,比较简单,虽然在onTouchevent中根据不同情况修改了 头布局的显示多少 ,但是头布局中的小控件都有相应的动画或改变,需要抽取一个方法更细致的展示。

    /**     * 根据当前状态刷新界面     */    private void refreshState() {        switch (mCurrentState) {        case STATE_PULL_TO_REFRESH:            tvTitle.setText("下拉刷新");            pbProgress.setVisibility(View.INVISIBLE);            ivArrow.setVisibility(View.VISIBLE);            ivArrow.startAnimation(animDown);            break;        case STATE_RELEASE_TO_REFRESH:            tvTitle.setText("松开刷新");            pbProgress.setVisibility(View.INVISIBLE);            ivArrow.setVisibility(View.VISIBLE);            ivArrow.startAnimation(animUp);            break;        case STATE_REFRESHING:            tvTitle.setText("正在刷新...");            ivArrow.clearAnimation();// 清除箭头动画,否则无法隐藏            pbProgress.setVisibility(View.VISIBLE);            ivArrow.setVisibility(View.INVISIBLE);            break;        default:            break;        }    }

6.下拉刷新监听(重点!!!!!!回调!!!!)

运用回调,使在手指抬起Up后,能够调用方法,进行刷新的逻辑。
(1)下拉刷新的回调接口

public interface OnRefreshListener {        void onRefresh();    }


(2)暴露接口,设置监听

    public void setOnRefreshListener(OnRefreshListener listener) {        mListener = listener;    }


(3)定义成员变量,接收监听对象

private OnRefreshListener mListener;


(4) 在合适的地方进行回调
(之前第三点中onTouchevent事件中进行回调)

 if (mListener != null) {                        mListener.onRefresh();                    }


(5). 前端界面设置回调(并非RefreshListView类,是使用了该listView类中写!!!)

    lvList.setOnRefreshListener(new OnRefreshListener() {            @Override            public void onRefresh() {                // 刷新数据            }        });


7. 刷新结束,收起控件

最后一步,刷新完成,恢复控件原始位置,状态恢复,标题恢复,刷新完成的条件下更新时间。

public void onRefreshComplete(boolean success) {        mHeaderView.setPadding(0, -mHeaderViewHeight, 0, 0);        mCurrentState = STATE_PULL_TO_REFRESH;        tvTitle.setText("下拉刷新");        pbProgress.setVisibility(View.INVISIBLE);        ivArrow.setVisibility(View.VISIBLE);        if (success) {// 只有刷新成功之后才更新时间            setCurrentTime();        }    }




以上就是所有的逻辑,有关于”下拉刷新“的重点,其实各个细节都会有耦合的部分,尽量抽取出一个个小模块进行讲解,其中最重要的就是 第三点.滑动事件处理第六点.下拉刷新监听回调。大致这几个步骤,讲的还是蛮细的,适合入门吧,也不知道这样分开讲好不好,总之,希望对你们有帮助 :)



后续在这里 :http://blog.csdn.net/itermeng/article/details/52297286 :)

1 0
原创粉丝点击