ListView下拉刷新,上拉加载

来源:互联网 发布:js弹出路径选择对话框 编辑:程序博客网 时间:2024/06/02 04:40

最近不忙,想到自己用到的PullToRefreshListView,就自己写着看看,之前都是用现成的,主要还是知道原理吧,

借鉴的一篇文章ListView下拉刷新,上拉自动加载更多

这篇文章写的很清楚,我就不重复了,直接贴代码吧

activity_mainxml:

<?xml version="1.0" encoding="utf-8"?><android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:app="http://schemas.android.com/apk/res-auto"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context="com.shop.hsz88.autolistviewdemo.MainActivity">    <com.shop.hsz88.autolistviewdemo.AutoListView        android:id="@+id/atlv"        android:layout_width="match_parent"        android:layout_height="match_parent"        app:layout_constraintBottom_toBottomOf="parent"        app:layout_constraintLeft_toLeftOf="parent"        app:layout_constraintRight_toRightOf="parent"        app:layout_constraintTop_toTopOf="parent" /></android.support.constraint.ConstraintLayout>

MainActiviyt.java

package com.shop.hsz88.autolistviewdemo;import android.content.Context;import android.support.v7.app.AppCompatActivity;import android.os.Bundle;import android.view.View;import java.util.ArrayList;public class MainActivity extends AppCompatActivity {    private Context mContext;    private String tag;    private ArrayList<String> datas;    private AutoListView aulv;    private MyAdapter myAdapter;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mContext=this;        tag="MainActivity";        initView();        initData();    }    private void initView() {        aulv = (AutoListView) findViewById(R.id.atlv);    }    private void initData() {        datas=new ArrayList<String>();        //添加数据        for (int i=0;i<20;i++){            datas.add("第"+i+"条数据");        }        myAdapter = new MyAdapter(mContext, datas);        aulv.setAdapter(myAdapter);    }}

重点:自定义ListView

package com.shop.hsz88.autolistviewdemo;import android.content.Context;import android.os.Handler;import android.util.AttributeSet;import android.util.Log;import android.view.MotionEvent;import android.view.View;import android.view.ViewGroup;import android.widget.AbsListView;import android.widget.ListView;import android.widget.TextView;/** * Created by Administrator on 2017/11/16. */public class AutoListView extends ListView implements AbsListView.OnScrollListener {    private String tag = "AutoListView";    // 定义header的四种状态和当前状态    private static final int NONE = 0;    private static final int PULL = 1;    private static final int RELEASE = 2;    private static final int REFRESHING = 3;    private int state;    // 区分PULL和RELEASE的距离的大小    private static final int SPACE = 50;    //头部顶部内边距    private int headerContentInitialHeight;    //头布局实际高度,而不是显示的高度    private int headerContentHeight;    //记录手指滑动ListView的状态    private int scrollState;    private int firstVisibleItem;    private View header_view;    private View foot_view;    //加一个标记,记录显示的第一个item是否是数据源的第一个    private boolean isFist = false;    //记录手指刚按下的位置    private float startY;    public AutoListView(Context context) {        super(context);        initView(context);    }    public AutoListView(Context context, AttributeSet attrs) {        super(context, attrs);        initView(context);    }    public AutoListView(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);        initView(context);    }    //初始化头布局和尾布局    private void initView(Context context) {        //1.获取头布局        header_view = View.inflate(context, R.layout.layout_header, null);        /**         * 重点         */        //获取头部的顶部内边距        headerContentInitialHeight = header_view.getPaddingTop();        //计算header_view的大小        measureView(header_view);        //获取header的测量高度(实际高度,另一个高度是显示高度(getHeight))        headerContentHeight = header_view.getMeasuredHeight();        //打印 38        Log.i(tag, "头布局测量高度==:" + headerContentHeight);        //将头布局顶部内边距设置为负让其不显示出来        topPadding(header_view, -headerContentHeight);        //2.获取脚布局        foot_view = View.inflate(context, R.layout.layout_foot, null);        //3.将头布局和脚布局添加到ListView中去,添加到第1条数据的前面和最后一条数据的后面        this.addHeaderView(header_view);        this.addFooterView(foot_view);        //一开始为初始状态        state = NONE;        //让头布局和脚布局都不显示//        header_view.setVisibility(GONE);//        foot_view.setVisibility(GONE);        //设置滑动监听        this.setOnScrollListener(this);    }    /**     * *监听着ListView的滑动状态改变。官方的有三种状态SCROLL_STATE_TOUCH_SCROLL、SCROLL_STATE_FLING、SCROLL_STATE_IDLE:     * SCROLL_STATE_TOUCH_SCROLL:手指正拖着ListView滑动     * SCROLL_STATE_FLING:ListView正自由滑动     * SCROLL_STATE_IDLE:ListView滑动后静止     *     * @param view     * @param scrollState     */    @Override    public void onScrollStateChanged(AbsListView view, int scrollState) {        //记录手指滑动的状态        this.scrollState = scrollState;    }    /**     * @param view     * @param firstVisibleItem 表示在屏幕中第一条显示的数据在adapter中的位置     * @param visibleItemCount 则表示屏幕中最后一条数据在adapter中的数据,     * @param totalItemCount   则是在adapter中的总条数     */    @Override    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {        //记录屏幕上显示的第一条数据在数据源的位置        this.firstVisibleItem = firstVisibleItem;    }    @Override    public boolean onTouchEvent(MotionEvent ev) {        switch (ev.getAction()) {            case MotionEvent.ACTION_DOWN://手指按下                //先判断显示的第一个item是不是数据源的第一个                if (firstVisibleItem == 0) {                    isFist = true;                    //记录手指按下y方向的位置                    startY = ev.getY();                }                break;            case MotionEvent.ACTION_MOVE://手指滑动                //根据移动距离去切换头布局的状态                //每移动一像素就调用一次                whenMove(ev);                break;            //当用户保持按下操作,并从你的控件转移到外层控件时,会触发ACTION_CANCEL            case MotionEvent.ACTION_CANCEL:            case MotionEvent.ACTION_UP:                //手指抬起或者移到控件外时,让头布局隐藏,或者执行刷新操作                if (state == PULL) {                    //移动距离太小,执行隐藏                    state = NONE;                    Log.i(tag, "onTouchEvent   None");                    refreshHeaderViewByState();                } else if (state == RELEASE) {                    //执行刷新操作,刷新完后要隐藏掉                    state = REFRESHING;                    refreshHeaderViewByState();                    Log.i(tag, "onTouchEvent REFeeshing");                    //在这里写获取数据请求                }                break;        }        return super.onTouchEvent(ev);    }    /**     * 手指滑动走一遍的顺序为 12 4 32     * 获取手势状态,根据状态不同显示不同效果     *     * @param ev     */    private void whenMove(MotionEvent ev) {            //显示的数据是否是数据源的第一个,不是直接返回        if (!isFist) {            return;        }        //手指移动的y方向位置        int tmpY = (int) ev.getY();        //计算位置差        int space = (int) (tmpY - startY);        Log.i(tag,"手指移动位置差==:"+space);        //根据手指移动距离减去头布局初始高度,来设置头布局的高度        int topPadding = space - headerContentHeight;        switch (state) {            case NONE:                if (space > 0) {                    state = PULL;                    //下拉刷新                    refreshHeaderViewByState();                    Log.i(tag, "状态:1");                }                break;            case PULL:                //将头部显示出来,动态显示,参数2根据手指滑动距离而改变                topPadding(header_view, topPadding);                //当处于手指正拖着ListView滑动,且滑动的距离大于头布局距离与设置距离大小时,就可以进行刷新操作                if (scrollState == SCROLL_STATE_TOUCH_SCROLL && space > headerContentHeight + SPACE) {                    state = RELEASE;                    //头布局显得是内容,松手刷新                    refreshHeaderViewByState();                    Log.i(tag, "状态2");                }                break;            //刷新操作            case RELEASE:                //设置头布局内边距                topPadding(header_view, topPadding);                Log.i(tag, "状态4");                //拉动距离小,就不刷新                if (space > 0 && space < headerContentHeight + SPACE) {                    state = PULL;                    //下拉刷新                    refreshHeaderViewByState();                    Log.i(tag, "状态3");                } else if (space <= 0) {                    //回到初始位置                    state = NONE;                    refreshHeaderViewByState();                }                break;        }    }    /**     * 头部页面显示效果     */    // 根据当前状态,调整header    private void refreshHeaderViewByState() {        switch (state) {            //初始状态,啥都没做,            case NONE:                //调整头部的大小                topPadding(header_view, -headerContentHeight);                ((TextView) header_view.findViewById(R.id.tv_content)).setText("下拉刷新");                break;            case PULL:                //下拉状态,此时松开会还原到状态NONE,并不进行刷新                ((TextView) header_view.findViewById(R.id.tv_content)).setText("下拉刷新");                //让其显示出来                header_view.findViewById(R.id.tv_content).setVisibility(VISIBLE);                break;            case RELEASE:                //同样是下拉状态,但此刻松开会执行刷新,进入状态REFRESHING                ((TextView) header_view.findViewById(R.id.tv_content)).setText("松手刷新");                break;            case REFRESHING:                //正在执行刷新操作,                // 刷新结束后进入状态NONE。                //headerContentInitialHeight,为0                topPadding(header_view, headerContentInitialHeight);                Log.i(tag, "刷新结束==:" + headerContentInitialHeight);                //隐藏掉,不让其显示                ((TextView) header_view.findViewById(R.id.tv_content)).setText("正在刷新");                /**                 * 回到初始位置,如果不写咋只能刷新一次,延时回到刷新完成                 */                new Handler().postDelayed(new Runnable() {                    @Override                    public void run() {                        state = NONE;                        refreshHeaderViewByState();                    }                }, 3 * 1000);                break;        }    }    /**     * 用来计算header大小的。比较隐晦。因为header的初始高度就是0,貌似可以不用。     * 计算一个控件的大小     *     * @param child 需要计算的控件     */    private void measureView(View child) {        //获取工具的配置参数对象        ViewGroup.LayoutParams p = child.getLayoutParams();        if (p == null) {            //如果p为空则创建一个,宽度匹配父布局,高度包裹内容            p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);        }        /**         * 计算子布局尺寸的最主要部分:         * 为特定子控件计算出它的MeasureSpec,(测量说明)         * 这个方法为一个子布局的任一一个维度(高度/宽度)计算出正确的MeasureSpec。         */        int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);        int lpHeight = p.height;        int childHeightSpec;        if (lpHeight > 0) {            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);        } else {            childHeightSpec = MeasureSpec.makeMeasureSpec(0,                    MeasureSpec.UNSPECIFIED);        }        child.measure(childWidthSpec, childHeightSpec);    }    /**     * 调整一个控件的内边距,距离顶部的距离     *     * @param view       需要调整的控件     * @param topPadding 顶部距离     */    private void topPadding(View view, int topPadding) {        //左。上。右,下        view.setPadding(view.getPaddingLeft(), topPadding,                view.getPaddingRight(),                view.getPaddingBottom());        view.invalidate();    }}

头布局

<?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">    <TextView        android:id="@+id/tv_content"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:text="头布局" /></LinearLayout>

item布局

<?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">    <TextView        android:id="@+id/tv_text"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:text="测试数据"        android:textSize="40sp"/></LinearLayout>