ListView实现下拉刷新(第一次打开listView的界面进行自动刷新)和上拉加载更多

本文参考http://blog.csdn.net/allen315410/article/details/39965327?utm_source=tuicool&utm_medium=referral

前言:距离上次写博客已经是3~4个月之前了,这中间有写了一篇论文,完成公司分配的任务。。。。但这并不是“三天打鱼,4个月晒网”的理由,所以在以后会时刻提醒自己,坚持来总结自己的学习心得,好了废话不多说。今天我要来讲一讲Android中ListView实现下拉刷新,上拉加载更多的功能。

实现ListView的以上功能,首先需要知道基本的思路:1、常见app下拉刷新总是伴随有动画的加载,本文中的动画主要是箭头图片的旋转(随着下拉的位置不同,相应的旋转不同的角度);2、布局的隐藏和显示(主要通过设置布局的setPadding来实现的),即当下拉/上拉到一定位置显示布局;3、下拉刷新主要是重写ListView中的onTouchEvent()来时刻监听手指触摸和滑动事件;4、上拉加载更多主要是重写AbslistView.OnScrollListener接口中的onScrollStateChanged来监听列表的滚动状态,从而做出相应的判断。


一、头布局

1.头布局xml文件(header_layout.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="wrap_content">
    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp">
        <ImageView
            android:id="@+id/iv_listView_header_arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/arrow"
            android:minWidth="30dp"
            android:layout_gravity="center"/>
        <ProgressBar
            android:id="@+id/pb_listView_header"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:indeterminateDrawable="@drawable/common_progressbar"
            android:visibility="gone"
            android:layout_gravity="center"/>
    </FrameLayout>
    <LinearLayout
        android:gravity="center_horizontal"
        android:layout_gravity="center_vertical"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
        <TextView
            android:id="@+id/tv_listView_header_state"
            android:text="下拉刷新"
            android:textSize="18sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"/>
        <TextView
            android:id="@+id/tv_listView_header_last_update_time"
            android:text="最后刷新时间:2016-7-18 17:00:00"
            android:textSize="14sp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dp"/>
    </LinearLayout>
</LinearLayout>


2、ps:在设置进度条时,给进度条添加旋转背景效果:
即android:indeterminateDrawable="@drawable/common_progressbar"
在drawable下新建名为common_progressbar.xml的文件
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android">
    android:fromDegrees="0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toDegrees="360" >

    <shape
        android:innerRadiusRatio="3"
        android:shape="ring"
        android:useLevel="false" >
        <gradient
            android:centerColor="#FF6666"
            android:endColor="#FF0000"
            android:startColor="#FFFFFF"
            android:type="sweep" />
    </shape>
</rotate>
二、脚布局
footer_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:layout_width="match_parent"
              android:layout_height="wrap_content">
<LinearLayout
    android:layout_margin="10dp"
    android:layout_gravity="center_horizontal"
    android:gravity="center_vertical"
    android:orientation="horizontal"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:indeterminateDrawable="@drawable/common_progressbar"/>
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dp"
        android:text="加载更多..."
        android:textSize="18sp"/>
</LinearLayout>
</LinearLayout>
三、自定义ListView代码
1.MyListView
package com.mylistviewtest;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
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 com.orhanobut.logger.Logger;

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

/**
 * Created by huiyi on 2016/7/18.
 */
public class MyListView extends ListView implements AbsListView.OnScrollListener {
    private static final String TAG="MyListView";
    private Context mContext;
    private LayoutInflater mLayoutInflater;
    private Date mDate;
    private SimpleDateFormat mSimpleDateFormat;

    /**
     * 定义头布局对象和高度
     */
    private int firstVisibleItemPosition;//屏幕显示子啊第一个的item的索引
    private int downY;//按下屏幕时y轴的偏移量
    private int headerViewHeight;//头布局的高度(通过measureHeight测量获得)
    public View headerView;//头布局的对象

    /**
     * 下拉刷新相应的状态
     */
    private final int DOWN_PULL_REFRESH=0;//下拉刷新状态
    private final int RELEASE_REFRESH=1;//松开刷新
    private final int REFRESHING=2;//正在刷新状态
    private int currentState_header=REFRESHING;//头布局的状态,默认为下拉刷新状态

    /**
     * 下拉刷新时的动画变量
     */
    private Animation upAnimation;//向上旋转的动画
    private Animation downAnimation;//向下旋转的动画

    //下拉刷新时显示的子控件
    private ImageView ivArrow;//头布局的显示箭头
    private ProgressBar mProgressBar;//头布局的进度条
    private TextView tvState;//头布局的状态
    private TextView tvLastUpdateTime;//头布局的最后更新时间

    /**
     * 下拉刷新监听接口
     */
    private OnRefreshListener mOnRefreshListener;

    /**
     * 脚布局对象和动作判定
     */
    private boolean isScrollToBottom;//是否滑动到底部
    private View footerView;//脚布局的对象
    private int footerViewHeight;//脚布局的高度
    private boolean isLoadingMore=false;//是否正在加载更多中


    /**
     * 构造函数初始化
     * @param context
     * @param attrs
     */
    public MyListView(Context context, AttributeSet attrs) {
        super(context, attrs);
        mContext=context;
        mLayoutInflater=LayoutInflater.from(mContext);
        initHeaderView();
        initFooterView();
        this.setOnScrollListener(this);
    }

    /**
     * 初始化头布局
     */
    private void initHeaderView(){
        headerView= mLayoutInflater.inflate(R.layout.header_layout,null);
        //箭头
        ivArrow= (ImageView) headerView.findViewById(R.id.iv_listView_header_arrow);
        //进度条
        mProgressBar= (ProgressBar) headerView.findViewById(R.id.pb_listView_header);
        //文本状态
        tvState= (TextView) headerView.findViewById(R.id.tv_listView_header_state);
        //最后更新时间
        tvLastUpdateTime= (TextView) headerView.findViewById(R.id.tv_listView_header_last_update_time);

        //设置最后刷新时间
        tvLastUpdateTime.setText("最后刷新时间:" + getLastUpdateTime());
        //系统测量出headerView的高度
        //指定measure的参数都为0时,系统便不会按这个规格去设置,而是根据实际来测量
        headerView.measure(0,0);
        //头布局的高度为负值(在屏幕top的上方)
        headerViewHeight=headerView.getMeasuredHeight();
        //设置headerView的内边距(移到屏幕上方,隐藏掉)
        headerView.setPadding(0, -headerViewHeight, 0, 0);
        //设置成此效果是一样的headerView.setPadding(0, 0, 0, -headerViewHeight);(距离在内部为正,外面看不见的为负)
        //将headerView添加到listView中,布局以xml为准
        this.addHeaderView(headerView);
        //初始化动画
        initAnimation();
    }


    /**
     * 最后刷新时间
     */
    private String getLastUpdateTime(){
        mDate=new Date();
        mSimpleDateFormat=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        return mSimpleDateFormat.format(mDate);
    }


    /**
     * 初始化动画
     */
    private void initAnimation(){
        //松手动画
        upAnimation=new RotateAnimation(0f, -180f,Animation.RELATIVE_TO_SELF,
                0.5f,Animation.RELATIVE_TO_SELF,0.5f);
        //持续500ms
        upAnimation.setDuration(500);
        //动画结束后,保留状态
        upAnimation.setFillAfter(true);

        //下拉动画
        downAnimation=new RotateAnimation(-180f, -360f,Animation.RELATIVE_TO_SELF,
                0.5f,Animation.RELATIVE_TO_SELF,0.5f);
        //downAnimation.setInterpolator(new LinearInterpolator());
        downAnimation.setDuration(500);
        downAnimation.setFillAfter(true);
    }

    /**
     * 触摸监听事件(处理下拉刷新事件)
     */
    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN://按下
                //记录按下时的坐标,与移动的坐标进行比较
                downY= (int) ev.getY();
                break;
            case MotionEvent.ACTION_MOVE://移动
                int moveY= (int) ev.getY();
                //间距=移动的点-按下的点
                int diff=(moveY-downY)/2;
                //paddingTop=-头布局的高度+间距
                int paddingTop=-headerViewHeight+diff;
                //只有当firstVisibleItem===0时,才进行相应的刷新准备
                //同时要想刷新,必须首先下拉一下,即diff>0
                if(firstVisibleItemPosition==0&&paddingTop>-headerViewHeight){
                    //下拉之后还要判断是下拉刷新还是松开刷新(默认执行次判断)
                    if (paddingTop>0&¤tState_header==DOWN_PULL_REFRESH){//当前状态为正在往下拉
                        Logger.d("即将要松开刷新");
                        //改变状态,以便进行松开刷新
                        currentState_header=RELEASE_REFRESH;
                        refreshHeaderView();//进行相应的刷新动画操作,currentState_header为最新改变的值
                    }else if (paddingTop<0&¤tState_header==RELEASE_REFRESH){//当前状态为松手了,当没有成功
                        Logger.d("还要下拉刷新");
                        currentState_header=DOWN_PULL_REFRESH;
                        refreshHeaderView();
                    }
                    //将头布局显示出来(随着paddingTop变化)
                    headerView.setPadding(0,paddingTop,0,0);
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP://松手
                //松手时判断当前的状态
                if (currentState_header==RELEASE_REFRESH){
                    Logger.d("正在刷新数据");
                    //把头布局设置为完全显示,要进行刷新数据操作和切换视图
                    //进入到正在刷新中状态
                    currentState_header=REFRESHING;
                    //切换视图
                    refreshHeaderView();
                    //调用刷新回调方法(刷新完成后,可以添加数据)
                }else if (currentState_header==DOWN_PULL_REFRESH){//回到初始的状态
                    //隐藏头布局
                    headerView.setPadding(0,-headerViewHeight,0,0);
                }
                break;
            default:
                break;
        }
        //如果diff<0(paddingTop<-headerViewHeight),则不做任何操作
        return super.onTouchEvent(ev);
    }

    /**
     * 根据currentState_header刷新头布局的状态
     */
    public void refreshHeaderView(){
        switch (currentState_header){
            case DOWN_PULL_REFRESH://默认要下拉的状态,开启箭头动画

                ivArrow.startAnimation(downAnimation);
                tvState.setText("下拉刷新");
                break;
            case RELEASE_REFRESH://松开刷新箭头动画

                ivArrow.startAnimation(upAnimation);
                tvState.setText("松开刷新");
                break;
            case REFRESHING://正在刷新中
                //切换视图
                headerView.setPadding(0,0,0,0);
                ivArrow.clearAnimation();
                ivArrow.setVisibility(GONE);
                mProgressBar.setVisibility(VISIBLE);
                tvState.setText("正在刷新中...");
                if(mOnRefreshListener!=null){
                    mOnRefreshListener.onDownPullRefresh();
                }
                break;
        }
    }

    /**
     * 隐藏头布局
     */
    public void hideHeaderView(){
        headerView.setPadding(0,-headerViewHeight,0,0);
        ivArrow.setVisibility(VISIBLE);
        mProgressBar.setVisibility(GONE);
        tvState.setText("下拉刷新");
        tvLastUpdateTime.setText("最后刷新时间:" + getLastUpdateTime());
        currentState_header=DOWN_PULL_REFRESH;
    }
    /**
     * 初始化脚布局
     */
    private void initFooterView(){
        footerView=mLayoutInflater.inflate(R.layout.foot_layout,null);
        //测量footerView的高度
        footerView.measure(0, 0);
        footerViewHeight=footerView.getMeasuredHeight();
        //初始化隐藏脚布局
        footerView.setPadding(0, -footerViewHeight, 0, 0);
        //设置成此效果是一样的footerView.setPadding(0,0,0,-footerViewHeight);
        //添加到myListView上
        this.addFooterView(footerView);
    }

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



    /**
     * 设置刷新监听事件
     * @param onRefreshListener
     */
    public void setOnRefreshListener(OnRefreshListener onRefreshListener){
        this.mOnRefreshListener=onRefreshListener;
    }

    /**
     *处理上拉加载更多事件(在ListView状态改变时调用)
     * @param view
     * @param scrollState(空闲SCROLL_STATE_IDLE 、滑动SCROLL_STATE_TOUCH_SCROLL和惯性滑动SCROLL_STATE_FLING)
     */
    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (scrollState==SCROLL_STATE_IDLE||scrollState==SCROLL_STATE_FLING){//当滑动停止或惯性滑动时
            //判断是否已经到达了底部
            if (isScrollToBottom&&!isLoadingMore){//若在底部,并且之前没有加载,则加载更多
                //即将加载更多(在每次加载完成之后才重置标识位为false)
                isLoadingMore=true;
                Logger.d("加载更多数据");
                //将脚布局显示出来
                footerView.setPadding(0,0,0,0);
                //获得listView的数量,显示新加载的那一项(原有的最后一个为getCount-1)
                this.setSelection(this.getCount());
                if (mOnRefreshListener!=null){
                    mOnRefreshListener.onLoadingMore();
                }
            }

        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        //时刻监视可见视图中的第一个item
        firstVisibleItemPosition=firstVisibleItem;
        if (firstVisibleItem+visibleItemCount==totalItemCount){//到达了底部
            isScrollToBottom=true;
        }else {
            isScrollToBottom=false;
        }
    }
}
ps : MyListView中实现的回调接口类(OnRefreshListener.java)
package com.mylistviewtest;

/**
 * Created by huiyi on 2016/7/19.
 */
public interface OnRefreshListener {
    void onDownPullRefresh();
    void onLoadingMore();
}

2、ListView的adpter(继承自BaseAdapter)
package com.mylistviewtest;

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import com.bumptech.glide.Glide;

import java.util.List;

/**
 * Created by huiyi on 2016/7/18.
 */
public class ViewHolderAdapter extends BaseAdapter {
    private Context mContext;
    private List<String> mStringList;
    private List<Integer> mIntegerList;
    private LayoutInflater mLayoutInflater;

    /**
     * 通过构造函数传参数进来
     */
    public ViewHolderAdapter(Context context,List<String> stringList,List<Integer> integerList){
        this.mContext=context;
        this.mStringList=stringList;
        this.mIntegerList=integerList;
        mLayoutInflater=LayoutInflater.from(context);
    }

    /**
     * 要显示的所有item的个数
     * @return
     */
    @Override
    public int getCount() {
        return mStringList.size();
    }


    /**
     * 返回点击的textView的内容
     * @param position
     * @return
     */
    @Override
    public Object getItem(int position) {
        return mStringList.get(position);
    }


    /**
     * 返回点击的第几个item
     * @param position
     * @return
     */
    @Override
    public long getItemId(int position) {
        return position;
    }

    /**
     * 获得要显示的控件item,通过viewHolder模式可以充分的利用ListView的视图缓存机制
     * 避免了每次都要findViewById()
     * @param position
     * @param convertView
     * @param parent
     * @return 返回item的整个布局
     */
    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder=null;
        //判断是否缓存convertView为显示的子视图,通过其是否存在来判断
        //目的是获得带holder的convertView
        if (convertView==null){//初始化viewHolder
            viewHolder=new ViewHolder();
            //通过LayoutInflater获得布局文件
            convertView=mLayoutInflater.inflate(R.layout.simple_item_listview,parent,false);
            viewHolder.mImageView= (ImageView) convertView.findViewById(R.id.item_imageView);
            viewHolder.mTextView= (TextView) convertView.findViewById(R.id.item_textView);
            convertView.setTag(viewHolder);

        }else {//直接取出viewHolder来用
            viewHolder= (ViewHolder) convertView.getTag();
        }
        //设置布局中控件要显示的视图
        Glide.with(mContext).load(mIntegerList.get(position)).centerCrop().into(viewHolder.mImageView);
        viewHolder.mTextView.setText(mStringList.get(position));
        return convertView;//显示item布局视图
    }


    /**
     * 自定义viewHolder内部类
     */
    class ViewHolder {
        private ImageView mImageView;
        private TextView mTextView;
    }
}
ps: ListView中的子item布局(imageView+textView实现)
<?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">

    <ImageView
        android:src="@drawable/im1"
        android:scaleType="centerCrop"
        android:layout_weight="1"
        android:id="@+id/item_imageView"
        android:layout_width="match_parent"
        android:layout_height="300dp"/>
    <TextView
        android:gravity="center"
        android:text="Hello World!"
        android:layout_weight="1"
        android:id="@+id/item_textView"
        android:layout_width="match_parent"
        android:layout_height="300dp"/>
</LinearLayout>

3、在MainActivity.java中使用(首先在mainAty对应的xml文件中调用自定义MyListView控件)
<span style="color:#333333;">package com.mylistviewtest;

import android.os.AsyncTask;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.ImageView;

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

public class MainActivity extends AppCompatActivity implements OnRefreshListener{
    private Integer[] imageInteger={R.drawable.im1,R.drawable.im2,R.drawable.im3,
            R.drawable.im4,R.drawable.im5,R.drawable.im7,R.drawable.im8,R.drawable.im3,};
    //private ListView mListView;
    private MyListView mMyListView;
    private List<String> mStringList;
    private List<Integer> mIntegerList;
    private ViewHolderAdapter mViewHolderAdapter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mIntegerList=new ArrayList<>();
        mStringList=new ArrayList<>();
        initData();
         mMyListView= (MyListView) findViewById(R.id.myListView);
       // mListView= (ListView) findViewById(R.id.listView);         
         mViewHolderAdapter=new ViewHolderAdapter(MainActivity.this,mStringList,mIntegerList);
         mMyListView.setAdapter(mViewHolderAdapter);
            //设置回调
         mMyListView.setOnRefreshListener(this);
            </span><span style="color:#ff0000;">//第一次打开ListView界面就自动刷新
         mMyListView.refreshHeaderView();</span><span style="color:#333333;">                  
    }

    /**
     * 初始化数据
     */
    private void initData(){
        for (int i=0;i<8;i++){
            mIntegerList.add(imageInteger[i]);
            mStringList.add("items"+i);
        }
    }
<span style="white-space:pre">	</span>
    @Override
    public void onDownPullRefresh() {
        new AsyncTask<Void,Void,Void>(){
            @Override
            protected Void doInBackground(Void... params) {
                SystemClock.sleep(2000);
                for (int i=0;i<2;i++){
                    mStringList.add(0,"这是新刷新出来的数据"+i);
                    mIntegerList.add(0,imageInteger[6]);
                }
                return null;
            }
            @Override
            protected void onPostExecute(Void aVoid) {
                mViewHolderAdapter.notifyDataSetChanged();
                mMyListView.hideHeaderView();
            }
        }.execute(new Void[]{});
    }

    @Override
    public void onLoadingMore() {
        new AsyncTask<Void,Void,Void>(){
            @Override
            protected Void doInBackground(Void... params) {
                SystemClock.sleep(2000);
                for (int i=0;i<1;i++){
                    mStringList.add("这是新加载出来的数据"+i);
                    mIntegerList.add(imageInteger[5]);
                }
                return null;
            }
            @Override
            protected void onPostExecute(Void aVoid) {
                mViewHolderAdapter.notifyDataSetChanged();
                mMyListView.hideFooterView();
            }
        }.execute(new Void[]{});
    }
}
</span>

注:1、文中打印调试用了Logger:可在build.gradle中的dependencies添加
compile  'com.orhanobut:logger:1.13'
2、由于文中的图片像素较高,使用imageView的setImageResource已发生OOM(实际使用的是Bitmap来实现图片的加载),所以本文也使用了第三方加载图片库Glide,在build.gradle中的dependencies添加
compile 'com.github.bumptech.glide:glide:3.7.0'





 
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值