本文参考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)
2、ps:在设置进度条时,给进度条添加旋转背景效果:<?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>
即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
三、自定义ListView代码<?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>
1.MyListView
ps : MyListView中实现的回调接口类(OnRefreshListener.java)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; } } }
package com.mylistviewtest; /** * Created by huiyi on 2016/7/19. */ public interface OnRefreshListener { void onDownPullRefresh(); void onLoadingMore(); }
2、ListView的adpter(继承自BaseAdapter)
ps: ListView中的子item布局(imageView+textView实现)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; } }
<?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添加
2、由于文中的图片像素较高,使用imageView的setImageResource已发生OOM(实际使用的是Bitmap来实现图片的加载),所以本文也使用了第三方加载图片库Glide,在build.gradle中的dependencies添加compile 'com.orhanobut:logger:1.13'compile 'com.github.bumptech.glide:glide:3.7.0'