ListView下拉刷新,上拉加载

最近不忙,想到自己用到的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>







评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值