首页 > 代码库 > 自定义ListView实现下拉刷新,下拉加载的功能

自定义ListView实现下拉刷新,下拉加载的功能

package com.loaderman.myrefreshlistviewdemo;


import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
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 java.text.SimpleDateFormat;
import java.util.Date;

/**
 * 
 * 实现步骤:
 * 1、给ListView添加头布局
 * 2、默认让ListView的头布局隐藏起来
 * 负的paddingTop的值
 * 如何获取头布局的高度
 * 3、慢慢的将头布局拖出来
 * 获取在ListView中的滑动偏移量--onTouchEvent
 * 关于起点坐标的获取dispatchTouchEvent
 * 4、给RefreshListView定义了三种状态
 * refreshUi:根据当前的状态刷新控件的显示
 * 在状态发生改变的时候来调用此方法即可
 * 5、增加了动画效果
 * clearAnimation的使用
 * 6、处理up的事件
 * STATE_PULL_TO_REFRESH的时候up
 * 隐藏头布局
 * STATE_RELEASE_TO_REFRESH的时候up
 * 显示头布局
 * 更新状态--STATE_REFRESHING
 * 通知观察者去加载数据
 * 7、观察者设计模式的使用
 * 找出被观察者
 * 定义观察者接口,接口中的方法就是观察者感兴趣的事件
 * 在被观察中存储观察者的引用
 * 在事件发生的时候,通知观察者
 * 为什么要用接口而不使用抽象类--单继承,多实现
 * 8、由TabDetailPager来通知RefreshListView数据加载完成
 * setOnRefreshComplete
 * 更新状态,隐藏头布局
 * 9、设置时间的显示
 * 存在sp中
 * 10、自定义ProgressBar的效果
 * 上拉加载:
 * 1、添加脚布局,默认隐藏
 * 2、增加了滚动监听,
 * idle,显示最后一个条目的时候,显示脚布局
 * 3、通知观察者加载下一页的数据
 * 4、加载下一页数据的逻辑
 * 将下一页数据的集合添加到上一页数据的集合红,不能new Adapter
 * 5、TabDetailPager通知ListView下一页数据加载完成
 * 重置isLoadingMore
 * 隐藏脚布局
 */

public class RefreshListView extends ListView {

    public static final int STATE_PULL_TO_REFRESH    = 0;
    public static final int STATE_RELEASE_TO_REFRESH = 1;
    public static final int STATE_REFRESHING         = 2;

    private int mCurrentState = STATE_PULL_TO_REFRESH;//定义ListView当前的状态

    private float           startY;
    private int             headerViewHeight;
    private View            headerView;
    private ImageView       ivArrow;
    private ProgressBar     pb;
    private TextView        tvTips;
    private TextView        tvDate;
    private RotateAnimation downAnimation;
    private RotateAnimation upAnimation;
    private View            footerView;
    private int             footerViewHeight;

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

    public RefreshListView(Context context, AttributeSet attrs) {
        this(context, attrs, -1);
    }

    public RefreshListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initHeaderView();
        initAnimation();
        initFooterView();
    }


    private void initAnimation() {
        upAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        upAnimation.setFillAfter(true);
        upAnimation.setDuration(200);
        downAnimation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
        downAnimation.setFillAfter(true);
        downAnimation.setDuration(200);

    }

    private void initHeaderView() {
        //头布局越早添加,位于越上边
        headerView = View.inflate(getContext(), R.layout.layout_refresh_header, null);
        ivArrow = (ImageView) headerView.findViewById(R.id.ivArrow);
        pb = (ProgressBar) headerView.findViewById(R.id.pb);
        tvTips = (TextView) headerView.findViewById(R.id.tvTips);
        tvDate = (TextView) headerView.findViewById(R.id.tvDate);

        String lastUpdateTime = PrefUtils.getString(getContext(), "lastUpdateTime", "");
        tvDate.setText(lastUpdateTime);
        //设置一个控件的高度或者宽度的信息得找LayoutParams
        //如果设置一个负的padding的值,只能在代码中设置才会其效果
        //measure-layout-draw
        //千万不要在Activity的onCreate方法中获取一个控件的宽度或者高度或者位置信息
        //监听视图树
        /*headerView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
            @Override
            public void onGlobalLayout() {

            }
        });*/
        //手动测量
        headerView.measure(0, 0);//将测量的工作交给系统来完成,我们不参与任何的限制的意见
        //获取测量之后的宽度或者高度信息
        headerViewHeight = headerView.getMeasuredHeight();
        headerView.setPadding(0, -headerViewHeight, 0, 0);
        this.addHeaderView(headerView);
    }

    private boolean isLoadingMore = false;

    private void initFooterView() {
        footerView = View.inflate(getContext(), R.layout.layout_refresh_footer, null);
        footerView.measure(0, 0);
        footerViewHeight = footerView.getMeasuredHeight();
        footerView.setPadding(0, -footerViewHeight, 0, 0);
        this.addFooterView(footerView);

        //给ListView增加一个监听
        this.setOnScrollListener(new OnScrollListener() {
            @Override
            public void onScrollStateChanged(AbsListView view, int scrollState) {
                int lastVisiblePosition = getLastVisiblePosition();
                if (scrollState == SCROLL_STATE_IDLE && lastVisiblePosition == getCount() - 1 && !isLoadingMore) {
                    //System.out.println("到底了...");
                    Log.i("RefreshListView", "到底了...");
                    isLoadingMore = true;
                    //将脚布局显示出来
                    footerView.setPadding(0, 0, 0, 0);
                    //自动滑到脚布局的位置,让脚布局可以一下子就能够看得见
                    setSelection(getCount() - 1);

                    notifyLoadMore();//通知观察者去加载下一页的数据

                }
            }

            @Override
            public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

            }
        });
    }

    //一旦事件到达了一个控件上,一定,最先,会调用dispatchTouchEvent
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        if (action == MotionEvent.ACTION_DOWN) {
            startY = ev.getY();//在这个父控件得到事件的时候,就把起点坐标初始化,这样就不会受制于子控件是否消费了事件,起点坐标就会很精确了
        }
        return super.dispatchTouchEvent(ev);
    }

    //onTouchEvent的来源:
    //1、自身拦截   2、子控件回传
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        int action = ev.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                startY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:

                if (mCurrentState == STATE_REFRESHING) {
                    break;
                }

                float moveY = ev.getY();
                float dy = moveY - startY;
                //什么情况下需要把头布局拖出来
                int firstVisiblePosition = getFirstVisiblePosition();
                //1、下拉  2、显示的第0个条目是下拉刷新头布局
                if (dy > 0 && firstVisiblePosition == 0) {
                    int paddingTop = (int) (dy - headerViewHeight);
                    headerView.setPadding(0, paddingTop, 0, 0);

                    int oldState = mCurrentState;
                    if (paddingTop < 0) {
                        //头布局有一部分没有显示出来
                        mCurrentState = STATE_PULL_TO_REFRESH;
                    } else {
                        mCurrentState = STATE_RELEASE_TO_REFRESH;
                    }

                    //在状态发生改变的时候才需要刷新UI
                    if (oldState != mCurrentState) {
                        refreshUi();
                    }


                    return true;//代表消费了事件
                }
                break;
            case MotionEvent.ACTION_UP:
                if (mCurrentState == STATE_PULL_TO_REFRESH) {
                    //将头布局隐藏起来
                    headerView.setPadding(0, -headerViewHeight, 0, 0);
                } else if (mCurrentState == STATE_RELEASE_TO_REFRESH) {
                    //改变当前的状态,刷新控件
                    mCurrentState = STATE_REFRESHING;
                    refreshUi();
                    //将头布局完全显示出来
                    headerView.setPadding(0, 0, 0, 0);
                    //去重写加载网络上的数据
                    //tabDetailPager.getDataFromServer();
                    notifyRefresh();
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

    public void setOnRefreshComplete(boolean success) {
        //1、更新当前的状态
        mCurrentState = STATE_PULL_TO_REFRESH;
        pb.setVisibility(View.INVISIBLE);
        ivArrow.setVisibility(View.VISIBLE);
        tvTips.setText("下拉刷新");
        //2、隐藏头布局
        headerView.setPadding(0, -headerViewHeight, 0, 0);

        if (success) {
            //更新tvDate的显示
            setCurrentDate();
        }

    }

    private void setCurrentDate() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        String currentDate = sdf.format(new Date());
        tvDate.setText(currentDate);

        PrefUtils.setString(getContext(), "lastUpdateTime", currentDate);
    }

    public void setOnLoadMoreComplete() {
        isLoadingMore = false;
        //隐藏脚布局
        footerView.setPadding(0, -footerViewHeight, 0, 0);

    }


    //定义观察者接口
    public interface OnRefreshListener {
        public void onRefresh();

        public void onl oadMore();
    }

    //保存观察者的实例对象
    private OnRefreshListener listener;

    public void setOnRefreshListener(OnRefreshListener listener) {
        this.listener = listener;
    }

    //通知观察者
    private void notifyRefresh() {
        if (listener != null) {
            listener.onRefresh();
        }
    }

    private void notifyLoadMore() {
        if (listener != null) {
            listener.onLoadMore();
        }
    }

    /*private TabDetailPager tabDetailPager;

    public void setTabDetailPager(TabDetailPager tabDetailPager) {
        this.tabDetailPager = tabDetailPager;
    }*/

    private void refreshUi() {
        switch (mCurrentState) {
            case STATE_PULL_TO_REFRESH:
                pb.setVisibility(View.INVISIBLE);//INVISIBLE会占位,GONE不会占位
                ivArrow.setVisibility(View.VISIBLE);
                tvTips.setText("下拉刷新");
                ivArrow.startAnimation(downAnimation);
                break;
            case STATE_RELEASE_TO_REFRESH:
                pb.setVisibility(View.INVISIBLE);
                ivArrow.setVisibility(View.VISIBLE);
                ivArrow.startAnimation(upAnimation);
                tvTips.setText("松开刷新");
                break;
            case STATE_REFRESHING:
                pb.setVisibility(View.VISIBLE);
                ivArrow.clearAnimation();//要控制一个控件的可见度的时候,需要先移除之前设置过的动画
                ivArrow.setVisibility(View.INVISIBLE);
                tvTips.setText("正在刷新");
                break;
        }
    }
}

 layout_refresh_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:gravity="center"
    android:orientation="horizontal"
    >

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

    <TextView
        android:id="@+id/tvTips"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="正在加载"
        android:textColor="#F00"
        android:textSize="16sp"/>

</LinearLayout>

 layout_refresh_header.xml

<?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:layout_height="match_parent"
    >

    <FrameLayout
        android:layout_margin="5dp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">

        <ImageView
            android:id="@+id/ivArrow"
            android:layout_gravity="center"
            android:layout_width="wrap_content"
            android:src="http://www.mamicode.com/@drawable/common_listview_headview_red_arrow"
            android:layout_height="wrap_content"/>

        <ProgressBar
            android:id="@+id/pb"
            android:visibility="invisible"
            android:indeterminateDrawable="@drawable/shape_progress"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>

    </FrameLayout>

    <LinearLayout
        android:layout_margin="5dp"
        android:gravity="center"
        android:layout_width="match_parent"
        android:orientation="vertical"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="wrap_content"
            android:text="下拉刷新"
            android:id="@+id/tvTips"
            android:textColor="#F00"
            android:textSize="16sp"
            android:layout_height="wrap_content"/>

        <TextView
            android:layout_width="wrap_content"
            android:text="2016-12-17"
            android:id="@+id/tvDate"
            android:textColor="#ccc"
            android:textSize="12sp"
            android:layout_height="wrap_content"/>

    </LinearLayout>


</LinearLayout>

 shape_progress.xml

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
        android:toDegrees="720"
        android:pivotY="50%"
        android:pivotX="50%"
    >
    <shape
           android:innerRadius="15dp"
           android:shape="ring"
           android:thickness="3dp"
           android:useLevel="false"
        >
        <!--<solid android:color="@android:"-->
        <gradient
            android:startColor="#f00"
            android:centerColor="#af00"
            android:endColor="#fff"
            />
    </shape>
</rotate>

 

package com.loaderman.myrefreshlistviewdemo;

import android.content.Context;
import android.content.SharedPreferences;

/**
 * 关于SharedPreference的工具类
 */

public class PrefUtils {
    public static String getString(Context context,String key,String defValue) {
        SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);
        String retString = sp.getString(key, defValue);
        return retString;
    }
    public static void setString(Context context,String key ,String value) {
        SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);
        SharedPreferences.Editor edit = sp.edit();
        edit.putString(key, value);
        edit.commit();
    }
}

 代码使用:

package com.loaderman.myrefreshlistviewdemo;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.Random;

public class MainActivity extends AppCompatActivity implements RefreshListView.OnRefreshListener {

    private RefreshListView lvListNews;
    private ArrayList mList;
    private MyListAdapter myListAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mList = new ArrayList<>();
        for (int i = 0; i < 30; i++) {
            mList.add("我是天才" + i + "号");
        }
        lvListNews = (RefreshListView) findViewById(R.id.lvListNews);
        lvListNews.setOnRefreshListener(this);
        lvListNews.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

            }
        });
        myListAdapter = new MyListAdapter();
        lvListNews.setAdapter(myListAdapter);
    }
    //下拉刷新
    @Override
    public void onRefresh() {
        final Random random = new Random();
        mList.add(0, "我是天才" + random.nextInt(100) + "号");
        Toast.makeText(MainActivity.this, "刷新了一条数据", Toast.LENGTH_SHORT).show();
        //刷新完成
        lvListNews.setOnRefreshComplete(true);
        myListAdapter.notifyDataSetChanged();
    }
    //上拉加载
    @Override
    public void onl oadMore() {
        // 添加数据
        for (int i = 30; i < 35; i++) {
            mList.add("我是天才" + i+ "号");
            // 这里要放在里面刷新,放在外面会导致刷新的进度条卡住
            myListAdapter.notifyDataSetChanged();
        }
         //加载完成
        lvListNews.setOnLoadMoreComplete();
        Toast.makeText(MainActivity.this, "加载了" + 5 + "条数据", Toast.LENGTH_SHORT).show();
    }
    class MyListAdapter extends BaseAdapter {


        @Override
        public int getCount() {
            return mList.size();
        }

        @Override
        public Object getItem(int position) {
            return mList.get(position);
        }

        @Override
        public long getItemId(int position) {
            return position;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) {
            ViewHolder holder = null;
            if(convertView == null){
                convertView = View.inflate(MainActivity.this, R.layout.item_news_tab_detail, null);
                holder = new ViewHolder();
                holder.tvContent = (TextView) convertView.findViewById(R.id.tvContent);
                convertView.setTag(holder);
            } else {
                holder = (ViewHolder) convertView.getTag();
            }
            holder.tvContent.setText(mList.get(position)+"");
            return convertView;
        }
    }

    static class ViewHolder {
        TextView tvContent;
    }

}

 activity_main.xml

<?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:id="@+id/activity_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.loaderman.myrefreshlistviewdemo.MainActivity">
    <com.loaderman.myrefreshlistviewdemo.RefreshListView
        android:id="@+id/lvListNews"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
       />
</RelativeLayout>

 效果图:

技术分享

自定义ListView实现下拉刷新,下拉加载的功能