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

(转载注明: 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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值