自定义ListView实现下拉刷新和上拉加载

实现ListView的下拉刷新和上拉加载,需要先添加headerView和footerView,通过在拖动的过程中,控制头尾布局的paddingTop实现。先把paddingTop设为负值,来隐藏header,在下拉的过程中,不断改变headerView的paddingTop,实现下拉过程中headerView慢慢显示的效果。

下拉刷新,是先将listview的头部隐藏,然后用手势拖动屏幕刷新。

那么,listview的头部如何隐藏呢?这里用的是header.setPadding(0,-headerViewHeight,0,0)来隐藏头部。

当MotionEvent.ACTION_DOWN(手势按下)触发时,需要一个downY记录手势按下时y的偏移值,
然后随着手势的滑动即 MotionEvent.ACTION_MOVE(手势滑动)触发时,又需要一个moveY记录移动的偏移值,
然后dy=moveY- downY得到的就是手指滑动的距离。然后用 paddingTop = (int) (dy - mHeadViewHeight)得到头部的顶部到屏幕的顶部距离。如果想paddingTop >= 0则说明头部完全显示,如果paddingTop <0 则头部没有完全显示。

下拉刷新的几种状态:

 /**
     * 下拉刷新
     */
    public static final int STATE_PULL_TO_REFRESH = 1;

    /**
     * 释放刷新
     */
    public static final int STATE_RELASE_TO_REFRESH = 2;

    /**
     * 正在刷新
     */
    public static final int STATE_REFRESHING = 3;

自定义包含下拉刷新和上拉加载功能的的ListView:

package com.example.a01_pulltorefreshview.widget;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.a01_pulltorefreshview.R;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 自定义包含下拉刷新和上拉加载功能的的ListView
 * Created by xiaoyehai on 2016/11/24.
 */
public class PullToRefreshListView extends ListView implements AbsListView.OnScrollListener {

    /**
     * 下拉刷新
     */
    public static final int STATE_PULL_TO_REFRESH = 1;

    /**
     * 释放刷新
     */
    public static final int STATE_RELASE_TO_REFRESH = 2;

    /**
     * 正在刷新
     */
    public static final int STATE_REFRESHING = 3;

    /**
     * 当前状态
     */
    private int mCurrentState = STATE_PULL_TO_REFRESH;

    /**
     * 下拉刷新头布局
     */
    private View mHeadView;

    /**
     * 下拉刷新状态
     */
    private TextView tvState;

    /**
     * 下拉刷新时间
     */
    private TextView tvTime;

    /**
     * 下拉刷新旋转箭头图片
     */
    private ImageView ivArrow;

    /**
     * 下拉刷新ProgressBar
     */
    private ProgressBar pbProgress;

    /**
     * 下拉刷新按下时Y抽坐标
     */
    private float startY;
    //private int startY = -1;

    /**
     * 下拉刷新移动时Y抽坐标
     */
    private float endY;

    /**
     * 下拉刷新头布局的高度
     */
    private int mHeadViewHeight;

    /**
     * 下拉刷新箭头旋转动画
     */
    private RotateAnimation animUp;

    /**
     * 下拉刷新箭头旋转动画
     */
    private RotateAnimation animDown;

    /**
     * 下拉刷新过程中和顶部的padding
     */
    private int paddingTop;

    /**
     * 上拉加载底部布局
     */
    private View mFooterView;

    /**
     * 上拉加载底部布局高度
     */
    private int mFooterViewHeight;

    /**
     * 是否正在加载更多
     */
    private boolean isLoadMore;

    /**
     * 下拉刷新和上拉加载事件监听
     */
    private onRefreshListener onRefreshListener;

    public PullToRefreshListView(Context context) {
        this(context, null);
    }

    public PullToRefreshListView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

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

    /**
     * 初始化头布局,脚布局
     */
    private void init() {

        initHeadView();

        initFoodView();

        initAnimation();

    }

    /**
     * 初始化头布局
     */
    private void initHeadView() {
        mHeadView = View.inflate(getContext(), R.layout.pull_to_refresh_head, null);
        this.addHeaderView(mHeadView);
        ivArrow = (ImageView) mHeadView.findViewById(R.id.iv_arrow);
        tvState = (TextView) mHeadView.findViewById(R.id.tv_state);
        tvTime = (TextView) mHeadView.findViewById(R.id.tv_time);
        pbProgress = (ProgressBar) mHeadView.findViewById(R.id.pd_loading);

        //按照设置的规则测量宽高
        mHeadView.measure(0, 0);
        //控件高度
        mHeadViewHeight = mHeadView.getMeasuredHeight();

        //隐藏头布局
        mHeadView.setPadding(0, -mHeadViewHeight, 0, 0);
    }

    /**
     * 初始化底部,加载更多
     */
    private void initFoodView() {
        mFooterView = View.inflate(getContext(), R.layout.pull_to_refresh_footer, null);
        this.addFooterView(mFooterView);

        //测量控件
        mFooterView.measure(0, 0);
        //控件的高度
        mFooterViewHeight = mFooterView.getMeasuredHeight();
        //隐藏控件
        mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);

        //设置监听,加载更多
        this.setOnScrollListener(this);

    }

    /**
     * 初始化头布局箭头动画
     */
    private void initAnimation() {
        animUp = new RotateAnimation(0, -180,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animUp.setDuration(200);
        animUp.setFillAfter(true);

        animDown = new RotateAnimation(-180, 0,
                Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        animDown.setDuration(200);
        animDown.setFillAfter(true);
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                //记录起点按下的Y坐标
                startY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:

                //添加了广告轮播头布局:
                //当用户按住广告图片进行下拉时ACTION_DOWN会被viewpager消费掉,
                //导致startY没有被赋值,此处需要重新获取一下
                //                if (startY == -1) {
                //                    startY = (int) ev.getY();
                //                }

                //如果此时正在刷新,跳出循环,不让再次刷新
                if (mCurrentState == STATE_REFRESHING) {
                    break;
                    //return super.onTouchEvent(ev); //或者这样写,执行父类的处理
                }

                endY = ev.getY();

                //手指滑动偏移量
                float dy = endY - startY;

                //当前显示第一个条目的位置
                int firstVisiblePosition = getFirstVisiblePosition();
                //往下拉,并且显示的是第一个可见item时第一个的时候,才能划出控件
                if (dy > 0 && firstVisiblePosition == 0) {
                    //计算当前控件的padding
                    paddingTop = (int) (dy - mHeadViewHeight);
                    mHeadView.setPadding(0, paddingTop, 0, 0);

                    //当控件拉至完全显示,改为松开刷新
                    if (paddingTop >= 0 && mCurrentState != STATE_RELASE_TO_REFRESH) {
                        mCurrentState = STATE_RELASE_TO_REFRESH;  //松开刷新状态

                        //刷新头布局
                        refreshState();
                    } else if (paddingTop < 0 && mCurrentState != STATE_PULL_TO_REFRESH) {
                        //不完全显示,下拉刷新
                        mCurrentState = STATE_PULL_TO_REFRESH;//下拉刷新状态

                        //刷新头布局
                        refreshState();
                    }
                    return true; //当前事件被消费,拦截TouchMove,不让listview处理该次move事件,会造成listview无法滑动
                }
                break;
            case MotionEvent.ACTION_UP:
                //                startY = -1;
                if (mCurrentState == STATE_PULL_TO_REFRESH) { //下拉的时候没完全显示就松开或者回去的时候,就恢复
                    mHeadView.setPadding(0, -mHeadViewHeight, 0, 0);
                } else if (mCurrentState == STATE_RELASE_TO_REFRESH) { //松开刷新状态的时候抬起手要变成正在刷新
                    mCurrentState = STATE_REFRESHING;
                    refreshState();

                    //正在刷新时完整展示头布局
                    mHeadView.setPadding(0, 0, 0, 0);

                    //接口回调,通知加载数据
                    if (onRefreshListener != null) {
                        onRefreshListener.onRefresh();
                    }
                }
                break;

        }
        return super.onTouchEvent(ev);
    }

    /**
     * 根据当前状态刷新控件
     */
    private void refreshState() {
        switch (mCurrentState) {
            case STATE_PULL_TO_REFRESH:
                // TODO: 2016/11/24 下拉刷新状态
                tvState.setText("下拉刷新");
                pbProgress.setVisibility(INVISIBLE);
                ivArrow.setVisibility(VISIBLE);
                ivArrow.startAnimation(animDown);

                break;
            case STATE_RELASE_TO_REFRESH:
                // TODO: 2016/11/24 松开刷新状态
                tvState.setText("释放刷新");
                pbProgress.setVisibility(INVISIBLE);
                ivArrow.setVisibility(VISIBLE);
                ivArrow.startAnimation(animUp);
                break;
            case STATE_REFRESHING:
                // TODO: 2016/11/24 正在刷新状态
                tvState.setText("正在刷新...");
                pbProgress.setVisibility(VISIBLE);
                ivArrow.setVisibility(INVISIBLE);

                ivArrow.clearAnimation();   //清除箭头动画,否则无法隐藏
                break;
        }
    }

    /**
     * 刷新结束,收起控件
     *
     * @param success:是否下拉刷新成功
     */
    public void onRefreshComplete(boolean success) {
        if (isLoadMore) { //加载更多
            //加载更多完成后,收起控件
            mFooterView.setPadding(0, -mFooterViewHeight, 0, 0);
            isLoadMore = false;
        } else { //下拉刷新
            mHeadView.setPadding(0, -mHeadViewHeight, 0, 0);
            mCurrentState = STATE_PULL_TO_REFRESH;
            tvState.setText("下拉刷新");
            pbProgress.setVisibility(INVISIBLE);
            ivArrow.setVisibility(VISIBLE);

            //设置刷新时间,只有刷新成功之后才更新时间
            if (success) {
                tvTime.setText("最后刷新时间:" + getCurrentTime());
            }
        }
    }

    /**
     * 设置下拉刷新里面的时间
     */
    private String getCurrentTime() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String time = sdf.format(new Date());
        return time;
    }

    /**
     * SCROLL_STATE_IDLE:当屏幕停止滚动时
     * SCROLL_STATE_TOUCH_SCROLL:当屏幕以触屏方式滚动并且手指还在屏幕
     * SCROLL_STATE_FLING:当用户之前滑动屏幕并抬起手指,屏幕以惯性滚动
     *
     * @param view
     * @param scrollState
     */
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        // TODO: 2016/11/19 滑动状态发生变化的回调
        if (scrollState == SCROLL_STATE_IDLE) { //空闲状态
            int lastVisiblePosition = getLastVisiblePosition();
            //显示最后一个item并且没有加载更多
            if (lastVisiblePosition == getCount() - 1 && !isLoadMore) {
                isLoadMore = true;
                mFooterView.setPadding(0, 0, 0, 0); //显示

                //setSelection(getCount() - 1); //显示在最后一个item上
                setSelection(getCount()); //显示在最后一个item上

                //通知主界面加载下一页数据
                if (onRefreshListener != null) {
                    onRefreshListener.onLoadMore();
                }
            }
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        // TODO: 2016/11/19 滑动过程的回调
    }

    /**
     * 下拉刷新的回调接口
     */
    public interface onRefreshListener {
        //通知刷新(正在刷新)
        void onRefresh();

        //通知加载更多
        void onLoadMore();
    }

    /**
     * 暴露接口,设置监听
     *
     * @param onRefreshListener
     */
    public void setOnRefreshListener(PullToRefreshListView.onRefreshListener onRefreshListener) {
        this.onRefreshListener = onRefreshListener;
    }
}

头布局:

<?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="wrap_content"
    android:orientation="horizontal">

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingTop="10dp">

        <ImageView
            android:id="@+id/iv_arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:src="@mipmap/common_listview_headview_red_arrow" />

        <ProgressBar
            android:id="@+id/pd_loading"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:indeterminateDrawable="@drawable/custom_progress"
            android:visibility="invisible" />
    </FrameLayout>

    <LinearLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1"
        android:gravity="center"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tv_state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下拉刷新"
            android:textColor="#f00"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="3dp"
            android:text="最后刷新时间:2015-10-21 09:00:30"
            android:textColor="#a000"
            android:textSize="16sp" />
    </LinearLayout>

</LinearLayout>

custom_progress:

<?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
        android:innerRadius="18dp"
        android:shape="ring"
        android:thickness="5dp"
        android:useLevel="false">

        <gradient
            android:centerColor="#9f00"
            android:endColor="#f00"
            android:startColor="#fff"
            android:type="sweep" />
    </shape>
</rotate>

底部布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:gravity="center"
    android:layout_height="match_parent">

    <ProgressBar
        android:id="@+id/pd_loading"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:indeterminateDrawable="@drawable/custom_progress" />

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="加载中..."
        android:textColor="#f00"
        android:layout_marginLeft="10dp"
        android:textSize="18sp" />

</LinearLayout>

用法:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.a01_pulltorefreshview.MainActivity">

    <com.example.a01_pulltorefreshview.widget.PullToRefreshListView
        android:id="@+id/listview"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</RelativeLayout>
package com.example.a01_pulltorefreshview;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.Window;

import com.example.a01_pulltorefreshview.widget.PullToRefreshListView;

import java.util.ArrayList;
import java.util.List;

/**
 * 自定义下拉刷新和上拉加载的ListView
 */
public class MainActivity extends AppCompatActivity {

    private PullToRefreshListView mListView;

    private List<String> list;

    private ListViewAdapter adapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
        setContentView(R.layout.activity_main);

        mListView = (PullToRefreshListView) findViewById(R.id.listview);
        list = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            list.add("这是一条ListView数据:" + i);
        }

        //添加头部,要在setAdapter之前添加
        adapter = new ListViewAdapter(this, list);
        mListView.setAdapter(adapter);

        mListView.setOnRefreshListener(new PullToRefreshListView.onRefreshListener() {
            @Override
            public void onRefresh() {
                //TODO: 2016/11/24 下拉刷新的时候回调该方法,加载数据
                loadData();
            }

            @Override
            public void onLoadMore() {
                // TODO: 2016/11/24 上拉加载下一页数据的回调
                loadMoreData();
            }
        });
    }

    private void loadMoreData() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //下拉刷新的数据
                list.add("上拉加载的数据");
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        adapter.notifyDataSetChanged();
                        //加载结束,收起控件
                        mListView.onRefreshComplete(true);
                    }
                });
            }
        }).start();
    }

    private void loadData() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //下拉刷新的数据
                list.add(0, "下拉刷新的数据");

                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        adapter.notifyDataSetChanged();
                        //加载结束,收起控件
                        mListView.onRefreshComplete(true);
                    }
                });
            }
        }).start();
    }
}
阅读更多
个人分类: 自定义控件
上一篇Java设计模式(2):单例模式
下一篇Fragment切换
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭