安卓自定义ListView实现上拉加载、下拉刷新

自定义ListView实现上拉加载、下来刷新,暂时未加入侧滑功能,可以设置是否需要加载、刷新以及每次加载的item个数,可以根据设置的加载item个数判断第一次获取数据后是否加载(如果少于设置的个数则不许加载)

xml头部布局
<?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:gravity="center">

    <RelativeLayout
        android:id="@+id/my_list_view_header_content"
        android:layout_width="match_parent"
        android:layout_height="60dp">

        <LinearLayout
            android:id="@+id/my_list_view_header_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical">

            <TextView
                android:id="@+id/my_list_view_header_hint_textview"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下拉刷新" />

            <LinearLayout
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="3dp">

                <TextView
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="上次更新时间:"
                    android:textSize="12sp" />

                <TextView
                    android:id="@+id/my_list_view_header_time"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textSize="12sp" />
            </LinearLayout>
        </LinearLayout>

        <ImageView
            android:id="@+id/my_list_view_header_arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignLeft="@id/my_list_view_header_text"
            android:layout_centerVertical="true"
            android:layout_marginLeft="-35dp"
            android:src="@drawable/my_listview_arrow" />

        <ProgressBar
            android:id="@+id/my_list_view_header_progressbar"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_alignLeft="@id/my_list_view_header_text"
            android:layout_centerVertical="true"
            android:layout_marginLeft="-40dp"
            android:visibility="gone" />
    </RelativeLayout>

</LinearLayout>

 
xml底部布局
<?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="75dp"
    android:clickable="false"
    android:orientation="vertical">

    <LinearLayout
        android:id="@+id/my_listview_footer_load_layout"
        android:layout_width="match_parent"
        android:layout_height="75dp"
        android:gravity="center"
        android:orientation="horizontal"
        android:paddingBottom="10dp"
        android:paddingTop="10dp">

        <ProgressBar
            android:id="@+id/my_listview_footer_load_progress"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_marginRight="10dp" />

        <TextView
            android:id="@+id/my_listview_footer_load_text"
            android:layout_width="wrap_content"
            android:layout_height="20dp"
            android:text="正在加载"
            android:textSize="16sp" />
    </LinearLayout>

</LinearLayout>

 
java代码
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.jwwl.carrierapp.R;

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

/**
 * 自定义ListView用于上拉加载、下拉刷新、左滑菜单
 */
public class MyListView extends ListView implements OnScrollListener {
    /**
     * 滑动开始时,手指按下Y的坐标
     */
    private int downY;
    /**
     * 每次加载的Item个数,根据此个数判断第一次获取数据后是否需要加载,默认为0个
     */
    private int sizeItem = 0;

    /**
     * 用于保证每次滑动是一个完整的滑动事件,按下的downY坐标值每次事件中只被记录一次(按下->滑动->松开,为一个完整事件),默认为false
     * true处于一个事件中
     * false未开始事件
     */
    private boolean isRecored = false;
    /**
     * 外部调用设置,是否可以下拉刷新,默认为false
     * true可以下拉刷新
     * false不可以下拉刷新
     */
    private boolean isRefreshable = false;
    /**
     * 外部调用设置,是否可以上拉加载数据,默认为false
     * true 可以上拉加载
     * false 不可以上拉加载
     */
    private boolean isLoadable = false;
    /**
     * 是否处于可下拉刷新状态,当Item为0时为true,默认为false
     * true可以下拉刷新状态
     * false不可以下拉刷新状态
     */
    private boolean isRefresh = false;
    /**
     * 是否处于可上拉加载状态,当前屏幕最后一个Item下标为总的Item下标时为true,默认为false
     * true可以上拉加载状态
     * false不可以上拉加载状态
     */
    private boolean isLoad = false;
    /**
     * 用于判断是否是从下拉要刷新状态滑动到下拉要返回状态,默认为false
     * true 是
     * false 不是
     */
    private boolean isBack = false;
    /**
     * 是否处于上拉加载中,默认为false
     * true上拉加载中
     * false不在上拉加载中
     */
    private boolean isLoading = false;
    /**
     * 下拉刷新头部布局,各个控件
     */
    private View headerView; // 头部布局
    private TextView headerTipsTv; //下来刷新提示
    private TextView headerTimeTv; //下来刷新时间
    private ImageView headerArrowIv; //下来刷新箭头
    private ProgressBar headerProgressBar; //下来刷新进度条
    /**
     * 底部布局
     */
    private View footerView;
    /**
     * 加载监听接口
     */
    private OnLoaderListener onLoaderListener;
    /**
     * 刷新监听接口
     */
    private OnRefreshListener refreshListener;
    /**
     * 下拉刷新头部布局的高度
     */
    private int headerContentHeight;

    /**
     * 下来刷新箭头动画
     */
    private RotateAnimation rotateAnimation; //正向旋转,
    private RotateAnimation reverseRotateAnimation; //反向旋转,


    /**
     * 下拉刷新状态值:下拉状态,松开不刷新
     */
    private final static int PULL_TO_BACK = 0;
    /**
     * 下拉刷新状态值:下拉状态,松开会刷新
     */
    private final static int PULL_TO_REFRESHING = 1;
    /**
     * 下拉刷新状态值:刷新中
     */
    private final static int REFRESHING = 2;
    /**
     * 上拉刷新状态值:完成
     */
    private final static int DONE = 3;
    /**
     * 上拉刷新状态,默认为完成状态
     */
    private int state = DONE;

    /**
     * 滑动的偏移比例,当滑动距离除以此比例大于等于布局高度时,则达到可以刷新加载状态
     */
    private final static int RATIO = 3;


    //构造方法
    public MyListView(Context context) {
        super(context);
        initView(context);
    }

    public MyListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView(context);
    }

    public MyListView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView(context);
    }

    /**
     * 初始化View
     */
    private void initView(Context context) {
        LayoutInflater inflater = LayoutInflater.from(context);
        setCacheColorHint(context.getResources().getColor(R.color.app_white)); //ListView滑动时背景

		/*
         * 下拉刷新:
		 */
        //获取布局
        headerView = inflater.inflate(R.layout.my_listview_header, null); //获取头部控件
        headerView.setClickable(false); //设置布局不可点击
        headerTipsTv = (TextView) headerView.findViewById(R.id.my_list_view_header_hint_textview); //下来刷新标题
        headerTimeTv = (TextView) headerView.findViewById(R.id.my_list_view_header_time); //下拉刷新时间
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        headerTimeTv.setText(sdf.format(new Date()));
        headerArrowIv = (ImageView) headerView.findViewById(R.id.my_list_view_header_arrow); //下拉刷新箭头
        headerProgressBar = (ProgressBar) headerView.findViewById(R.id.my_list_view_header_progressbar); //下拉刷新进度条
        //获取下拉刷新控件高度
        headerView.measure(0, 0);
        headerContentHeight = headerView.getMeasuredHeight();
        //设置内边距,正好距离顶部为一个负的整个布局的高度,正好把头部隐藏
        headerView.setPadding(0, -headerContentHeight, 0, 0);
        //重绘一下
        headerView.invalidate();
        //将下拉刷新的布局加入ListView的顶部
        addHeaderView(headerView, null, false);
        //设置正向旋转动画事件
        rotateAnimation = new RotateAnimation(0, -180,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        rotateAnimation.setInterpolator(new LinearInterpolator());
        rotateAnimation.setDuration(200);
        rotateAnimation.setFillAfter(true);
        //设置反向旋转动画事件
        reverseRotateAnimation = new RotateAnimation(-180, 0,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f,
                RotateAnimation.RELATIVE_TO_SELF, 0.5f);
        reverseRotateAnimation.setInterpolator(new LinearInterpolator());
        reverseRotateAnimation.setDuration(200);
        reverseRotateAnimation.setFillAfter(true);

		/*
         * 上拉加载
		 */
        //获取布局
        footerView = inflater.inflate(R.layout.my_listview_footer, null); //获取底部加载控件
        footerView.findViewById(R.id.my_listview_footer_load_layout).setVisibility(GONE);
        footerView.setClickable(false);//设置布局不可点击
        // 将上拉加载的布局加入ListView的底部
        addFooterView(footerView);

        /*
        滑动监听事件
         */
        setOnScrollListener(this);
    }

    /**
     * 设置是否可以下拉刷新
     *
     * @param enable true-可以下拉刷新,false-不可以下拉刷新
     */
    public void setRefreshEnable(boolean enable) {
        isRefreshable = enable;
    }

    /**
     * 设置是否可以上拉加载
     *
     * @param enable true-可以上拉加载,false-不可以上拉加载
     */
    public void setLoadEnable(boolean enable) {
        isLoadable = enable;
    }

    /**
     * 设置每次加载的Item个数
     */
    public void setSizeItem(int sizeItem) {
        this.sizeItem = sizeItem;
    }


    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: //按下

                downY = (int) ev.getY(); //记录按下时的y坐标

                /*
                * 如果可以刷新或加载,并且未开始一个事件,则开始一个滑动事件
			    */
                if (isRefreshable && !isRecored && (isRefresh || isLoad)) {
                    isRecored = true;
                }
                break;

            case MotionEvent.ACTION_MOVE: //滑动
                int moveY = (int) ev.getY();
                /*
                 * 如果可以刷新或加载,并且未开始一个事件,则开始一个滑动事件
			     */
                if (isRefreshable && !isRecored && (isRefresh || isLoad)) {
                    isRecored = true;
                    downY = moveY;
                }

                /**
                 * 根据是否处于滑动事件和刷新加载状态及是否处于可刷新判断刷新
                 */
                if (isRecored && state != REFRESHING && isRefresh) {
                    /*
                    如果在完成状态滑动
                    当下滑时,状态设置为下拉状态,并更新header的信息
                     */
                    if (state == DONE) {
                        //1、如果滑动值为正,则是往下滑,状态设置为下拉要返回状态
                        if (moveY - downY > 0) {
                            state = PULL_TO_BACK;
                            changeViewByState();//更新hander信息
                        }
                    }

                    /*
                    如果在下拉要返回状态滑动
                     */
                    if (state == PULL_TO_BACK) {
                        setSelection(0);
                        //1、如果滑动值为负,则是上滑,当滑动超出原按下位置时设为完成状态
                        if ((moveY - downY) <= 0) {
                            state = DONE;
                            changeViewByState();//更新hander信息
                        }
                        //2、如果滑动值为正,则是下滑,当滑动达到可以刷新时设为下拉可刷新状态
                        if ((moveY - downY) > 0 && (moveY - downY) / RATIO >= headerContentHeight) {
                            state = PULL_TO_REFRESHING;
                            changeViewByState();//更新hander信息
                        }
                        //3、设置header的padding值
                        headerView.setPadding(0, (moveY - downY) / RATIO - headerContentHeight, 0, 0);
                    }

                    /*
                    如果在下拉可刷新状态滑动
                     */
                    if (state == PULL_TO_REFRESHING) {
                        setSelection(0);
                        //1、如果滑动值为正,则是下滑,当滑动达回到下拉要返回状态时,设置为下拉要返回状态
                        if ((moveY - downY) > 0 && (moveY - downY) / RATIO < headerContentHeight) {
                            isBack = true;
                            state = PULL_TO_BACK;
                            changeViewByState();//更新hander信息
                        }
                        //2、设置header的padding值
                        headerView.setPadding(0, (moveY - downY) / RATIO - headerContentHeight, 0, 0);
                    }
                }

                /**
                 * 根据是否处于滑动事件和刷新加载状态及是否处于可刷新判断加载
                 */
                if (isRecored && !isLoading && isLoad) {

                    //1、如果滑动值为负,则是往上滑,状态设置上拉加载状态
                    if (moveY - downY < 0 && onLoaderListener != null) {
                        isLoading = true;
                        Log.i("TAG", "数据加载load");
                        footerView.findViewById(R.id.my_listview_footer_load_layout).setVisibility(VISIBLE);
                        onLoaderListener.onLoad();
                    }
                }
                break;

            case MotionEvent.ACTION_UP: //松开
                isBack = false;
                isRecored = false; //按下、滑动、松开整个事件结束

                /**
                 *  根据松开时的刷新状态判断是否刷新
                 *  只有不处于刷新中才判断
                 */
                if (state != REFRESHING) {
                    //下拉到要返回状态松开
                    if (state == PULL_TO_BACK) {
                        state = DONE; //状态设为完成
                        changeViewByState();//更新hander信息
                    }

                    //下拉到要刷新状态松开
                    if (state == PULL_TO_REFRESHING) {
                        //回调刷新方法
                        if (refreshListener != null) {
                            state = REFRESHING; //状态设为刷新
                            changeViewByState();//更新hander信息
                            refreshListener.onRefresh();
                        } else {
                            state = DONE; //状态设为完成
                            changeViewByState();//更新hander信息
                        }
                    }
                }
                break;
        }

        return super.onTouchEvent(ev);
    }


    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
    }


    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        /*
         * 滑动时会一直回调该方法,直到停止滑动。单击时回调一次该方法。
         * firstVisibleItem表示当前屏幕显示的第一个listItem在整个listView里的位置(下标从0开始);
         * visibleItemCount表示当前屏幕可见的listItem(部分显示的listItem也算)总数;
         * totalItemCount表示listView里listItem总数。
         * <p/>
         * 当滑动到列表首个Item时,判断是否可以下拉刷新
         * 当滑动到列表结尾之前,判断是否加载数据
         */

        //根据当前首个item下标是否是0并且是否可以下拉刷新,判断是否能够下拉刷新
        if (isRefreshable && firstVisibleItem == 0) {
            isRefresh = true;
        } else {
            isRefresh = false;
        }

        //当前屏幕首个item的下标与当前屏幕item个数之和同item总个数比较,判断是否加载数据
        if (isLoadable && totalItemCount == (firstVisibleItem + visibleItemCount) && sizeItem <= totalItemCount && totalItemCount != 0) {
            isLoad = true;
        } else {
            isLoad = false;
        }
    }


    /**
     * 当刷新状态改变时候,调用该方法,根据当前状态更新header界面信息
     */
    private void changeViewByState() {
        switch (state) {
            case PULL_TO_BACK: //松开不刷新
                headerProgressBar.setVisibility(View.GONE); //隐藏进度条
                headerTipsTv.setVisibility(View.VISIBLE); //显示刷新提示
                headerTipsTv.setText("下拉刷新"); //设置刷新提示内容
                headerTimeTv.setVisibility(View.VISIBLE); //显示上次刷新时间
                headerArrowIv.setVisibility(View.VISIBLE); //显示刷新箭头
                headerArrowIv.clearAnimation(); //清除刷新箭头动画
                //根据是否是从下拉要刷新状态滑动到下拉要返回状态,来判断是否调用反向旋转的箭头动画
                if (isBack) {
                    isBack = false;
                    headerArrowIv.startAnimation(reverseRotateAnimation);//设置刷新动画为反向旋转动画
                }
                break;

            case PULL_TO_REFRESHING: //松开要刷新
                headerProgressBar.setVisibility(View.GONE);//隐藏进度条
                headerTipsTv.setVisibility(View.VISIBLE);//显示刷新提示
                headerTipsTv.setText("松开刷新");//设置刷新提示内容
                headerTimeTv.setVisibility(View.VISIBLE);//显示上次刷新时间
                headerArrowIv.setVisibility(View.VISIBLE);//显示刷新箭头
                headerArrowIv.clearAnimation(); //清除刷新箭头动画
                headerArrowIv.startAnimation(rotateAnimation);//设置刷新动画为正向旋转动画
                break;

            case REFRESHING: //刷新中
                headerView.setPadding(0, 0, 0, 0); //设置刷新头布局padding
                headerProgressBar.setVisibility(View.VISIBLE);//显示进度条
                headerTimeTv.setVisibility(View.VISIBLE);//显示刷新提示
                headerTipsTv.setText("正在刷新");//设置刷新提示内容
                headerArrowIv.clearAnimation();//清除刷新箭头动画
                headerArrowIv.setVisibility(View.GONE);//隐藏刷新箭头
                break;

            case DONE: //刷新完成
                headerView.setPadding(0, -1 * headerContentHeight, 0, 0);//设置刷新头部布局padding
                headerProgressBar.setVisibility(View.GONE);//隐藏进度条
                headerTimeTv.setVisibility(View.VISIBLE);//显示刷新提示
                headerTipsTv.setText("下拉刷新");//设置刷新提示内容
                headerArrowIv.clearAnimation();//清除刷新箭头动画
                headerArrowIv.setVisibility(View.VISIBLE);//显示刷新箭头
                break;
        }
    }

    //刷新监听方法,用于刷新接口实现
    public void setRefreshListener(OnRefreshListener refreshListener) {
        this.refreshListener = refreshListener;
    }

    //刷新回调接口,用于回调刷新方法
    public interface OnRefreshListener {
        void onRefresh();
    }

    //刷新完成调用方法
    public void refreshComplete() {
        //刷新状态设为完成状态
        state = DONE;
        //设置刷新时间
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        headerTimeTv.setText(sdf.format(new Date()));
        //更新header信息
        changeViewByState();
    }

    //加载监听方法,用于加载接口的实现
    public void setLoaderListener(OnLoaderListener onLoaderListener) {
        this.onLoaderListener = onLoaderListener;
    }

    // 加载回调接口,用于回调加载方法
    public interface OnLoaderListener {
        void onLoad();
    }

    //加载完毕调用方法
    public void loadComplete() {
        isLoading = false;
        footerView.findViewById(R.id.my_listview_footer_load_layout).setVisibility(GONE);
    }
}


 
 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值