再也不用担心下拉刷新,上拉加载啦!-自定义ListView对上拉刷新,上拉加载的详解

本文详细解析了如何自定义ListView实现下拉刷新和上拉加载功能,包括手势操作、动画效果以及关键代码实现,帮助开发者理解并掌握这一技术。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


前言:

      看过许多下拉刷新的例子,好多大牛们的代码写的很完美,让人羡慕嫉妒恨~~~,可是,对于下拉刷新时的手势操作却没有给出详细的解释,当一堆堆逻辑代码出来的时候,对于我们这些菜鸟来说,理解起来真是让人脑子都大了。为了解放大脑(懒得自己进行全面分析),一步一步详解下拉操作,妈妈再也不用担心ListView 下拉刷新是什么鬼啦!~~

先上效果图:~~

 上拉加载数据:            下拉刷新:下拉距离短不刷新数据      下拉刷新数据:

 

 

 

思路详解:

     自定义的带有下拉刷新和上拉加载的ListView开始时,跟系统的ListView一样。不过多了个header和footer只不过这两个布局以不同的方式隐藏起来了而已。(header是在手机屏幕外的上面,footer是直接隐藏起来了。因为下拉和上拉加载不同,下拉加载有手势判断,要出现动画效果,根据下拉的各种手势,来设置具体的操作。如果直接跟footer一样首先默认header的View.setVisibility(GONE),当有下拉手势时再设置View.setVisibility(View.VISIBLE)就会没有良好的动画效果。如下图所示。

PS:破电脑只有自带的Window画图工具。凑合看吧

 

先将代码拆分~ 上拉加载跟下拉刷新分开来讲,后面会有完整的代码。~~~

 

 

上拉加载数据:

     由于这个比下拉刷新简单。先理解这个。上拉加载适用于需要加载的数据量很大时,如果一下子加载完。会使ListView出现卡顿。这时候,如果利用上拉加载。先加载一部分数据。当上拉时,再加载其他的一部分数据。这样就会有很好的用户体验。

 

 

   footer布局文件

<?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:layout_centerInParent="true"
    android:orientation="vertical">
    
    <LinearLayout
        android:id="@+id/ll_footer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"

        android:orientation="vertical">
        <ProgressBar
            android:id="@+id/footer_pb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:layout_gravity="center"
            />
        <TextView
            android:id="@+id/footer_tv"
            android:text="footer正在加载。。"
            android:textSize="16sp"

            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        
    </LinearLayout>

</LinearLayout>


接下来看上拉加载逻辑:上拉加载我们利用的是AbsListView.OnScrollListener这个接口。 它有两个方法需要重写:

1.publicvoid onScroll(AbsListView view,int firstVisibleItem,int visibleItemCount,int totalItemCount) {};

2.publicvoid onScrollStateChanged(AbsListView view,int scrollState) {};

借鉴别的地方的对这两个方法的详细解释。他讲解的很详细啦:

 

 new OnScrollListener() {    
        boolean isLastRow = false;    
        
        @Override    
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {    
            //滚动时一直回调,直到停止滚动时才停止回调。单击时回调一次。    
            //firstVisibleItem:当前能看见的第一个列表项ID(从0开始)    
            //visibleItemCount:当前能看见的列表项个数(小半个也算)    
            //totalItemCount:列表项共数    
        
            //判断是否滚到最后一行    
            if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0) {    
                isLastRow = true;    
            }    
        }    
        @Override    
        public void onScrollStateChanged(AbsListView view, int scrollState) {    
            //正在滚动时回调,回调2-3次,手指没抛则回调2次。scrollState = 2的这次不回调    
            //回调顺序如下    
            //第1次:scrollState = SCROLL_STATE_TOUCH_SCROLL(1) 正在滚动    
            //第2次:scrollState = SCROLL_STATE_FLING(2) 手指做了抛的动作(手指离开屏幕前,用力滑了一下)    
            //第3次:scrollState = SCROLL_STATE_IDLE(0) 停止滚动              

            //当屏幕停止滚动时为0;当屏幕滚动且用户使用的触碰或手指还在屏幕上时为1;  
            //由于用户的操作,屏幕产生惯性滑动时为2  
        
            //当滚到最后一行且停止滚动时,执行加载    
            if (isLastRow && scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {    
                //加载元素    
                ......    
        
                isLastRow = false;    
            }    
        }    
    }  


 

 

好了,了解了OnScrollListener的这两个方法具体是干什么的,接下来看我们怎么实现~~~:

 

 

 

 上拉加载的关键就在这个接口中实现:

首先是LoadListView中的定义的变量:

 

 

  

 

private int lastVisibleItem; //最后一个可见项
    private int totalItems; //总的item
    private View footer; //底部View+头部View;
    private boolean isLoading = false;//是否正在加载
    private ILoadListener iListener;//自定义的一个加载接口。暴露给MainActivity让它实现具体加载操作。可以根据需求不同而改写。


 加载布局文件以及设置监听:

 

private void initViews(Context context) {
        //获得footer+header布局文件
        LayoutInflater inflater =LayoutInflater.from(context);
        footer = inflater.inflate(R.layout.footer,null);

        footer.findViewById(R.id.ll_footer).setVisibility(GONE);//初始化时设置footer不可见
        this.addFooterView(footer);
        this.setOnScrollListener(this);//设置滚动监听


    }


 

重写OnScrollListener的这两个方法:

@Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
               if(lastVisibleItem==totalItems&&scrollState ==SCROLL_STATE_IDLE){
            //如果不是在加载
           if(!isLoading){
                footer.findViewById(R.id.ll_footer).setVisibility(View.VISIBLE);
               iListener.onLoad();
               isLoading =true;

           }
        }


    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        this.lastVisibleItem=firstVisibleItem+visibleItemCount;
        this.totalItems = totalItemCount;
  
    }

这样就差不多了。接下来再完成接口的设置,具体操作再MainActivity中实现。

/**
     * 加载更多数据的回调接口
     */
    public interface ILoadListener {
        public void onLoad();
    }
    //上拉加载完毕
    public void loadCompleted(){
        isLoading =false;
        footer.findViewById(R.id.ll_footer).setVisibility(GONE);
    }


    public void setInterface(ILoadListener iListener){
        
        this.iListener=iListener;
    }
上拉加载就完成了80%了,具体操作时,在MainActivity中:
    private LoadListView mListView;
    private List<String> datas;
    private ArrayAdapter<String> arrayAdapter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setupViews();
        initDatas();
    }

    private void initDatas() {
        for (int i = 0; i < 16; i++) {
            datas.add("ListView的数据"+i+"");
        }
    }
    private void initNewDatas(){
        for (int i = 0; i < 3; i++) {
            datas.add("footer加载出的数据"+i+"");
        }

    }


 private void setupViews() {

        mListView = (LoadListView) findViewById(R.id.lv_main);
        //上拉加载接口
        mListView.setInterface( this);
        datas = new ArrayList<String>();
        arrayAdapter = new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_expandable_list_item_1,datas);
        mListView.setAdapter(arrayAdapter);



    }
    
实现LoadList暴露的接口中的onLoad()方法:
   //实现onLoad()方法。
    @Override
    public void onLoad() {
        //添加延时效果模拟数据加载
        Handler handler= new Handler() ;
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                initNewDatas();//得到新数据
                arrayAdapter.notifyDataSetChanged();//刷新ListView;
                mListView.loadCompleted();
            }
        }, 2000);
    }
OK~上拉加载数据就大功告成了~~
 
 
下拉刷新:
 

 首先是header布局文件:

 

<?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="vertical">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="10dip"
        android:paddingBottom="10dip"
        >
        <LinearLayout
            android:id="@+id/ll_header"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tip"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下拉可以刷新"/>
            <TextView
                android:id="@+id/tv_lastupdate_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                />




        </LinearLayout>

        <ImageView
            android:id="@+id/arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/ll_header"
            android:layout_marginRight="20dip"
            android:src="@drawable/pull_down_refresh_arrow"

            />
        <ProgressBar
            android:id="@+id/header_pb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:layout_toLeftOf="@id/ll_header"
            android:layout_marginRight="20dip"
            android:visibility="gone"/>


    </RelativeLayout>


</LinearLayout>


 

LoadList中自定义的一些变量:

private boolean isRemark = false;//判断是否在当前页的最顶端并下滑
    private int startY; //Y坐标 记录手指开始按下的坐标
    private RLoadListener rLoadListener;//自定义的一个加载接口。暴露给MainActivity让它实现具体加载操作。可以根据需求不同而改写。

    private int scrollState;//当前滚动的 状态

    private int headerHeight;//顶部布局文件的高度

    final int NONE= 0;//正常状态
    final int PULL =1;//下拉
    final int RELESE =2;//释放
    final int REFLASHING =3; //刷新

    private int state=0;//判断当前状态,默认为正常状态


    private int firstVisibleItem;//第一个可见项
private View header; //头部View;


添加头部提示到ListView中:

 LayoutInflater inflater =LayoutInflater.from(context);
    

    

        header = inflater.inflate(R.layout.header,null);
        //测量header的宽和高
        measureView(header);
        //记录下header的高
        headerHeight = header.getMeasuredHeight();
        topPadding(-headerHeight);
        this.addHeaderView(header);   
        this.setOnScrollListener(this);//设置滚动监听


 

这里添加头布局的时候需要计算header到底要移出屏幕多少的距离(移出的距离即为header的高),并且要告知父布局:

/**
     * 通知父布局,占用的宽和高
     * @param view
     */
    private void measureView(View view) {

        //得到view的布局宽高
        ViewGroup.LayoutParams vlp = view.getLayoutParams();
        //如果没有就new一个
        if (vlp==null){
            vlp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        }
        //ViewGroup.getChildMeasureSpec(int spec,int padding,int childDimension) 1.父view的详细尺寸 2.view当前尺寸的下的边距 3.child在当前尺寸下的宽(高)
        int width = ViewGroup.getChildMeasureSpec(0,0,vlp.width);
        int height;
        int tempHeight = vlp.height;
        if (tempHeight>0){
            height = MeasureSpec.makeMeasureSpec(tempHeight,MeasureSpec.EXACTLY);

        }else {
            height =MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
        }
        view.measure(width, height);

    }


    /**
     * 设置header布局的上边距
     * @param topPadding
     */
    private void topPadding(int topPadding) {
        header.setPadding(header.getPaddingLeft(), topPadding, header.getPaddingRight(), header.getPaddingBottom());
        //重绘
        header.invalidate();

    }


在OnScrollListener的两个方法中记录一些状态量便于后面对OnTouch事件的操作:

@Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        this.scrollState =scrollState;
       
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        
        this.firstVisibleItem = firstVisibleItem;
    }

 

利用OnTouchEvent对下拉手势操作进行监听:

先来说明下拉刷新会出现的情况:1.下拉距离过短,不进行数据刷新。2.下拉到一定距离刷新数据。

 

 

当进行下拉刷新的时候,手势有3种状态:1.刚按下的时候;2.手指下滑移动的时候;3.手指抬起释放的时候。这3种手指状态又对应了header不同的View状态,然后根据view的状态,再来改变header的显示。

 

就是靠下面这三个方法来实现下拉的核心操作分析在代码后面:

  @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN://如果按下
                if (firstVisibleItem==0){
                    isRemark=true;
                    startY = (int) ev.getY();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                onMove(ev);
                break;
            case MotionEvent.ACTION_UP:
                if (state==RELESE){
                    state = REFLASHING;
                    //加载最新数据
                    reflashViewByState();
                    rLoadListener.onRefresh();
                }else if (state==PULL){
                    state = NONE;
                    isRemark = false;
                    reflashViewByState();
                }
                break;


        }

        return super.onTouchEvent(ev);
    }

    /**
     * 判断移动的过程
     *
     * @param ev
     */
    private void onMove(MotionEvent ev) {
        if (!isRemark){
            return;
        }
        int tempY = (int) ev.getY();
        //记录滑动距离
        int space = tempY-startY;
        //比较滑动距离和header的高
        int topPadding = space-headerHeight;
        switch (state){
            case NONE:
                if (space>0){
                    state = PULL;
                    reflashViewByState();
                }
                break;
            case PULL:
                //重绘header
                topPadding(topPadding);
                if (space>headerHeight+30&&scrollState==SCROLL_STATE_TOUCH_SCROLL){
                    state =RELESE;
                    reflashViewByState();

                }
                break;
            case RELESE:
                topPadding(topPadding);
                if (space<headerHeight+30){
                    state =PULL;
                    reflashViewByState();
                }else if(space<=0){//如果space<0说明向上滑。所以firstVisibleItem就不是0了 所以将isRemark设置为false;即listView现在不是最顶端的位置
                    state = NONE;
                    isRemark = false;
                    reflashViewByState();
                }
                break;

        }

    }



    /**
     * 根据当前状态,改变界面显示
     */
    private void reflashViewByState() {
        TextView tip = (TextView) header.findViewById(R.id.tip);
        ImageView arrow = (ImageView) header.findViewById(R.id.arrow);
        ProgressBar header_pb = (ProgressBar) header.findViewById(R.id.header_pb);

        RotateAnimation animation = new RotateAnimation(0,180,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
        animation.setDuration(500);
        animation.setFillAfter(true);

        RotateAnimation animation2 = new RotateAnimation(180,0,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
        animation2.setDuration(500);
        animation2.setFillAfter(true);

        /**
         * 四种状态动画的改变
         */
        switch (state){
            case NONE:
                arrow.clearAnimation();
                topPadding(-headerHeight);
                break;
            case PULL:
                arrow.setVisibility(View.VISIBLE);
                header_pb.setVisibility(View.GONE);
                tip.setText("下拉可以刷新!!");
                arrow.clearAnimation();
                arrow.setAnimation(animation2);
                break;
            case RELESE:
                arrow.setVisibility(View.VISIBLE);
                header_pb.setVisibility(View.GONE);
                tip.setText("松开可以刷新!!");
                arrow.clearAnimation();
                arrow.setAnimation(animation);
                break;
            case REFLASHING:
                topPadding(50);
                arrow.setVisibility(View.GONE);
                header_pb.setVisibility(View.VISIBLE);
                tip.setText("正在刷新...");
                arrow.clearAnimation();
                break;
        }



    }

手指操作的图示:


由于图太大,放到里面看不清。。。需要高清无码大图的点击这里:点我!!点我!!点我!!

 

 

这样,下拉刷新的核心代码已经完成了,接下来就是设置接口,和刷新完所做的动作的方法:

    下拉刷新接口,暴露给MainActivity来具体实现到底刷新出什么数据:

 

 /**
     * 下拉刷新接口
     */
    public interface RLoadListener{
        public void onRefresh();

    }

    public void setReflashInterface(RLoadListener rLoadListener){
        this.rLoadListener =rLoadListener;

    }


 

刷新完要做的工作:

 

    /**
     * 获取完整数据
     *
     */
    public void reflashComplete(){
        state = NONE;
        isRemark = false;
        reflashViewByState();
        TextView lasetupdate_time = (TextView) header.findViewById(R.id.tv_lastupdate_time);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
        Date date = new Date(System.currentTimeMillis());
        String time = simpleDateFormat.format(date);
        lasetupdate_time.setText(time);



    }

duang~duang~duang ~ 接下来就看怎么用这些东西在MainActivity中。

贴上完整的代码~~。

1. 布局文件:

    footer.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="match_parent"
    android:layout_centerInParent="true"
    android:orientation="vertical">
    
    <LinearLayout
        android:id="@+id/ll_footer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"

        android:orientation="vertical">
        <ProgressBar
            android:id="@+id/footer_pb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:layout_gravity="center"
            />
        <TextView
            android:id="@+id/footer_tv"
            android:text="footer正在加载。。"
            android:textSize="16sp"

            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        
    </LinearLayout>

</LinearLayout>

header.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:orientation="vertical">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="10dip"
        android:paddingBottom="10dip"
        >
        <LinearLayout
            android:id="@+id/ll_header"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tip"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下拉可以刷新"/>
            <TextView
                android:id="@+id/tv_lastupdate_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                />




        </LinearLayout>

        <ImageView
            android:id="@+id/arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/ll_header"
            android:layout_marginRight="20dip"
            android:src="@drawable/pull_down_refresh_arrow"

            />
        <ProgressBar
            android:id="@+id/header_pb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:layout_toLeftOf="@id/ll_header"
            android:layout_marginRight="20dip"
            android:visibility="gone"/>


    </RelativeLayout>


</LinearLayout>


 


<?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="vertical">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="10dip"
        android:paddingBottom="10dip"
        >
        <LinearLayout
            android:id="@+id/ll_header"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tip"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下拉可以刷新"/>
            <TextView
                android:id="@+id/tv_lastupdate_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                />




        </LinearLayout>

        <ImageView
            android:id="@+id/arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/ll_header"
            android:layout_marginRight="20dip"
            android:src="@drawable/pull_down_refresh_arrow"

            />
        <ProgressBar
            android:id="@+id/header_pb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:layout_toLeftOf="@id/ll_header"
            android:layout_marginRight="20dip"
            android:visibility="gone"/>


    </RelativeLayout>


</LinearLayout>


 

activity_main.xml

<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" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<com.example.administrator.listviewtest.LoadListView
    android:id="@+id/lv_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"></com.example.administrator.listviewtest.LoadListView>
</RelativeLayout>


 

 

LoadListView.java

package com.example.administrator.listviewtest;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
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 java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by Administrator on 2015-10-12.
 */
public class LoadListView extends ListView implements AbsListView.OnScrollListener {
    private int lastVisibleItem; //最后一个可见项
    private int totalItems; //总的item
    private View footer,header; //底部View+头部View;
    private boolean isLoading = false;//是否正在加载
    private ILoadListener iListener;//自定义的一个加载接口。暴露给MainActivity让它实现具体加载操作。可以根据需求不同而改写。

    private boolean isRemark = false;//判断是否在当前页的最顶端并下滑
    private int startY; //Y坐标 记录手指开始按下的坐标
    private RLoadListener rLoadListener;//自定义的一个加载接口。暴露给MainActivity让它实现具体加载操作。可以根据需求不同而改写。

    private int scrollState;//当前滚动的 状态

    private int headerHeight;//顶部布局文件的高度

    final int NONE= 0;//正常状态
    final int PULL =1;//下拉
    final int RELESE =2;//释放
    final int REFLASHING =3; //刷新

    private int state=0;//判断当前状态,默认为正常状态


    private int firstVisibleItem;//第一个可见项



    public LoadListView(Context context) {
        this(context,null);

    }

    public LoadListView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.listViewStyle);

    }

    public LoadListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        initViews(context);


    }

    /**
     * 添加底部+头部提示到ListVIew
     * @param context
     */
    private void initViews(Context context) {
        //获得footer+header布局文件
        LayoutInflater inflater =LayoutInflater.from(context);
        footer = inflater.inflate(R.layout.footer,null);

        footer.findViewById(R.id.ll_footer).setVisibility(GONE);//初始化时设置footer不可见

        header = inflater.inflate(R.layout.header,null);
        //测量header的宽和高
        measureView(header);
        //记录下header的高
        headerHeight = header.getMeasuredHeight();
        topPadding(-headerHeight);

        this.addHeaderView(header);
        this.addFooterView(footer);
        this.setOnScrollListener(this);//设置滚动监听


    }


    /**
     * 通知父布局,占用的宽和高
     * @param view
     */
    private void measureView(View view) {

        //得到view的布局宽高
        ViewGroup.LayoutParams vlp = view.getLayoutParams();
        //如果没有就new一个
        if (vlp==null){
            vlp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        }
        //ViewGroup.getChildMeasureSpec(int spec,int padding,int childDimension) 1.父view的详细尺寸 2.view当前尺寸的下的边距 3.child在当前尺寸下的宽(高)
        int width = ViewGroup.getChildMeasureSpec(0,0,vlp.width);
        int height;
        int tempHeight = vlp.height;
        if (tempHeight>0){
            height = MeasureSpec.makeMeasureSpec(tempHeight,MeasureSpec.EXACTLY);

        }else {
            height =MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
        }
        view.measure(width, height);

    }


    /**
     * 设置header布局的上边距
     * @param topPadding
     */
    private void topPadding(int topPadding) {
        header.setPadding(header.getPaddingLeft(), topPadding, header.getPaddingRight(), header.getPaddingBottom());
        //重绘
        header.invalidate();

    }


    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        this.scrollState =scrollState;
        if(lastVisibleItem==totalItems&&scrollState ==SCROLL_STATE_IDLE){
            //如果不是在加载
           if(!isLoading){
                footer.findViewById(R.id.ll_footer).setVisibility(View.VISIBLE);
               iListener.onLoad();
               isLoading =true;

           }
        }


    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        this.lastVisibleItem=firstVisibleItem+visibleItemCount;
        this.totalItems = totalItemCount;
        this.firstVisibleItem = firstVisibleItem;
    }

    /**
     * 加载更多数据的回调接口
     */
    public interface ILoadListener {
        public void onLoad();
    }
    //上拉加载完毕
    public void loadCompleted(){
        isLoading =false;
        footer.findViewById(R.id.ll_footer).setVisibility(GONE);
    }


    public void setInterface(ILoadListener iListener){

        this.iListener=iListener;
    }
    /**
     * 下拉刷新接口
     */
    public interface RLoadListener{
        public void onRefresh();

    }

    public void setReflashInterface(RLoadListener rLoadListener){
        this.rLoadListener =rLoadListener;

    }



    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN://如果按下
                if (firstVisibleItem==0){
                    isRemark=true;
                    startY = (int) ev.getY();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                onMove(ev);
                break;
            case MotionEvent.ACTION_UP:
                if (state==RELESE){
                    state = REFLASHING;
                    //加载最新数据
                    reflashViewByState();
                    rLoadListener.onRefresh();
                }else if (state==PULL){
                    state = NONE;
                    isRemark = false;
                    reflashViewByState();
                }
                break;


        }

        return super.onTouchEvent(ev);
    }

    /**
     * 判断移动的过程
     *
     * @param ev
     */
    private void onMove(MotionEvent ev) {
        if (!isRemark){
            return;
        }
        int tempY = (int) ev.getY();
        //记录滑动距离
        int space = tempY-startY;
        //比较滑动距离和header的高
        int topPadding = space-headerHeight;
        switch (state){
            case NONE:
                if (space>0){
                    state = PULL;
                    reflashViewByState();
                }
                break;
            case PULL:
                //重绘header
                topPadding(topPadding);
                if (space>headerHeight+30&&scrollState==SCROLL_STATE_TOUCH_SCROLL){
                    state =RELESE;
                    reflashViewByState();

                }
                break;
            case RELESE:
                topPadding(topPadding);
                if (space<headerHeight+30){
                    state =PULL;
                    reflashViewByState();
                }else if(space<=0){//如果space<0说明向上滑。所以firstVisibleItem就不是0了 所以将isRemark设置为false;即listView现在不是最顶端的位置
                    state = NONE;
                    isRemark = false;
                    reflashViewByState();
                }
                break;

        }

    }



    /**
     * 根据当前状态,改变界面显示
     */
    private void reflashViewByState() {
        TextView tip = (TextView) header.findViewById(R.id.tip);
        ImageView arrow = (ImageView) header.findViewById(R.id.arrow);
        ProgressBar header_pb = (ProgressBar) header.findViewById(R.id.header_pb);
        //设置的下拉箭头的动画效果
        RotateAnimation animation = new RotateAnimation(0,180,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
        animation.setDuration(500);
        animation.setFillAfter(true);
        //设置的下拉箭头的动画效果
        RotateAnimation animation2 = new RotateAnimation(180,0,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
        animation2.setDuration(500);
        animation2.setFillAfter(true);

        /**
         * 四种状态动画的改变
         */
        switch (state){
            case NONE:
                arrow.clearAnimation();
                topPadding(-headerHeight);
                break;
            case PULL:
                arrow.setVisibility(View.VISIBLE);
                header_pb.setVisibility(View.GONE);
                tip.setText("下拉可以刷新!!");
                arrow.clearAnimation();
                arrow.setAnimation(animation2);
                break;
            case RELESE:
                arrow.setVisibility(View.VISIBLE);
                header_pb.setVisibility(View.GONE);
                tip.setText("松开可以刷新!!");
                arrow.clearAnimation();
                arrow.setAnimation(animation);
                break;
            case REFLASHING:
                topPadding(50);
                arrow.setVisibility(View.GONE);
                header_pb.setVisibility(View.VISIBLE);
                tip.setText("正在刷新...");
                arrow.clearAnimation();
                break;
        }



    }




    /**
     * 获取完整数据
     *
     */
    public void reflashComplete(){
        state = NONE;
        isRemark = false;
        reflashViewByState();
        //设置刷新完成的时间
        TextView lasetupdate_time = (TextView) header.findViewById(R.id.tv_lastupdate_time);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
        Date date = new Date(System.currentTimeMillis());
        String time = simpleDateFormat.format(date);
        lasetupdate_time.setText(time);



    }

}


 

 

MainActivity.java

package com.example.administrator.listviewtest;

import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ArrayAdapter;

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

import java.util.logging.LogRecord;

public class MainActivity extends AppCompatActivity implements LoadListView.ILoadListener,LoadListView.RLoadListener{
    private LoadListView mListView;
    private List<String> datas;
    private ArrayAdapter<String> arrayAdapter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setupViews();
        initDatas();
    }

    private void initDatas() {
        for (int i = 0; i < 16; i++) {
            datas.add("ListView的数据"+i+"");
        }
    }
    private void initNewDatas(){
        for (int i = 0; i < 3; i++) {
            datas.add("footer加载出的数据"+i+"");
        }

    }

    private void initREflashDatas() {

        datas.add(0,"下拉刷新加载的数据");

    }

    private void setupViews() {

        mListView = (LoadListView) findViewById(R.id.lv_main);
        //上拉加载接口
        mListView.setInterface( this);
        mListView.setReflashInterface(this);
        datas = new ArrayList<String>();
        arrayAdapter = new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_expandable_list_item_1,datas);
        mListView.setAdapter(arrayAdapter);



    }

    //实现onLoad()方法。
    @Override
    public void onLoad() {
        //添加延时效果模拟数据加载
        Handler handler= new Handler() ;
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                initNewDatas();//得到新数据
                arrayAdapter.notifyDataSetChanged();//刷新ListView;
                mListView.loadCompleted();
            }
        }, 2000);
    }
    //  实现的刷新方法
    @Override
    public void onRefresh() {
        Handler handler= new Handler() ;
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                initREflashDatas();//得到新数据
                arrayAdapter.notifyDataSetChanged();//刷新ListView;
                mListView.reflashComplete();
            }
        }, 2000);

    }


}


 

 

大功告成~~。


 

附上源码记得给好评~:源码地址。

 

 

 

Ps:Google其实早已经更新了sdk新增加的一个widget,SwipeRefreshLayout字面意思就是下拉刷新的布局,继承自ViewGroup,可以实现下拉刷新的操作~~

想要了解的同学可以点击这里:SwipeRefreshLayout

 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值