接着上次来讲,这次来动手写一下listview的下拉刷新功能和上拉加载更多功能。
当然google在android4.0以上的API里面的提供了一个可以下拉加载更多的控件,这个小圆圈加载控件在豆瓣,知乎日报里面都有运用到,而我在下一篇博客也会提到。
先来了解一下最基本的listview的的加载功能吧。
首先是下拉刷新功能,我先说一下基本的思路。listveiw的面提供了一个addheader()方法,我们可以重写listview,然后用addheader方法加载我们自定义的加载布局。然后就是隐藏这个header,然后复写监听方法OnScrollListener()和OnTouch()方法,最后再提供一个接口方法来让用户实现加载数据。具体的我在代码里面都注释好了。
再来说一下上拉加载,这个相比于下拉加载就简单多了,我们可以addfooter()方法添加布局,然后监听OnScrollListener就可以了,当最后一个可见的item等于总数量的item时,就可以加载数据了。具体在代码里面斗注释好了。
效果图:
先发布局文件:
<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" tools:context="com.example.listview_pulltorefresh.MainActivity" > <com.example.listview_pulltorefresh.RefreshListview android:id="@+id/listview" android:layout_width="match_parent" android:layout_height="match_parent"> </com.example.listview_pulltorefresh.RefreshListview> </RelativeLayout>
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="match_parent" android:orientation="vertical" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="10dp" android:paddingTop="10dp" > <LinearLayout android:id="@+id/layout" 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/lastrefresh_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_marginLeft="20dp" android:layout_toLeftOf="@id/layout" android:src="@drawable/pull_to_refresh_arrow" /> <!-- android:indeterminateDrawable="@drawable/loading_anim" --> <ProgressBar android:id="@+id/progress" style="?android:attr/progressBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="20dp" android:layout_toLeftOf="@id/layout" android:visibility="gone" /> </RelativeLayout> </LinearLayout>
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:orientation="vertical" > <LinearLayout android:id="@+id/load_footer" android:layout_width="match_parent" android:layout_height="wrap_content" android:gravity="center" android:orientation="horizontal" android:paddingBottom="10dp" android:paddingTop="10dp" > <ProgressBar style="?android:attr/progressBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="正在加载"/> </LinearLayout> </LinearLayout>
listitem.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="50dp"> <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:src="@drawable/ic_launcher"/> <TextView android:id="@+id/textView1" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="91dp" android:layout_toRightOf="@+id/image" android:text="数据列" /> </RelativeLayout>
自定义的listview方法:
package com.example.listview_pulltorefresh; import java.sql.Date; import java.text.SimpleDateFormat; import android.content.Context; import android.util.AttributeSet; import android.util.Log; 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; public class RefreshListview extends ListView { private View header;// 顶部布局文件 private View footer;// 底部布局 private int headerHeight;// 顶部布局文件的高度 private int firstVisibleItem;// 当前第一个可见item的位置 private boolean isRemark;// 标记当前listviews是否最顶端摁下 private int startY;// 开始的Y值 private int mscrollState;// 当前listview的滚动状态 private int state;// 当前状态 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 mtotalItemCount;//全部item的数量 private int lastVisableItem;//最后一个可见的item private boolean isLoading=false;//是否正在加载 public RefreshListview(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); // TODO Auto-generated constructor stub initView(context); } public RefreshListview(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub initView(context); } public RefreshListview(Context context) { super(context); // TODO Auto-generated constructor stub initView(context); } /** * 添加顶部布局文件 * * @param context */ private void initView(Context context) { LayoutInflater inflater = LayoutInflater.from(context); footer = inflater.inflate(R.layout.footer_loading, null, false); header = inflater.inflate(R.layout.header_layout, null, false); measureView(header); headerHeight = header.getMeasuredHeight(); Log.i("test", "headHeight:" + headerHeight); topPadding(-headerHeight); this.addHeaderView(header); //先设置底部隐藏 footer.setVisibility(View.GONE); this.addFooterView(footer); this.setOnScrollListener(new OnScrollListener() { @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // TODO Auto-generated method stub mscrollState = scrollState; //最后一个可见的item是总数量,并且当前滚动状态停止,就加载数据 if (mtotalItemCount==lastVisableItem&&scrollState==OnScrollListener.SCROLL_STATE_IDLE) { if (!isLoading) { //加载数据 isLoading=true; footer.setVisibility(VISIBLE); mListener2.onReflashMore(); } } } /** * firstvisebleitem第一个可见的位置 */ @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // TODO Auto-generated method stub lastVisableItem=firstVisibleItem+visibleItemCount; mtotalItemCount=totalItemCount; } }); this.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { // TODO Auto-generated method stub switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (firstVisibleItem == 0) { isRemark = true; startY = (int) event.getY(); } case MotionEvent.ACTION_MOVE: onMove(event); break; case MotionEvent.ACTION_UP: if (state == RELEASE) { state = REFRESHING; reflashViewByState(); mListener.onrReflash(); // 加载数据 // 在外部调用reflashcomplete } else if (state == PULL) { state = NONE; isRemark = false; reflashViewByState(); } break; default: break; } return false; } }); } /** * 通过view获取layoutparams,然后初始化lp, 要调用measure()方法来设置子view的宽高 * measure的方法参数有变化的是用MeasureSpec.makeMeasureSpec设置 * 没有变化的用getChildMeasureSpec()方法设置 * * @param view */ public void measureView(View view) { ViewGroup.LayoutParams lParams = view.getLayoutParams(); if (lParams == null) { lParams = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); // spec左右边距,padding内边距 int width = ViewGroup.getChildMeasureSpec(0, 0, lParams.width); int height; int tempHeight = lParams.height; if (tempHeight > 0) { // 填充用exactly height = MeasureSpec.makeMeasureSpec(tempHeight, MeasureSpec.EXACTLY); } else { // 意思就是<=0时,则告诉父布局子view高度填充0 height = MeasureSpec .makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } view.measure(width, height); } } /** * 设置header布局的上边距 * * @param topPadding */ public void topPadding(int topPadding) { header.setPadding(header.getPaddingLeft(), topPadding, header.getPaddingRight(), header.getPaddingBottom()); header.invalidate(); } /** * 判断移动过程中的操作 * * @param event */ private void onMove(MotionEvent event) { // TODO Auto-generated method stub if (!isRemark) { return; } int tempY = (int) event.getY();// 获取当前y int space = tempY - startY;// 显示的高度 int topPadding = space - headerHeight;// 因为是要用负值,所以减去高度 switch (state) { case NONE: if (space > 0) { state = PULL; reflashViewByState(); } break; case PULL: topPadding(topPadding); // 大于heigh+30且在滑动时,则是可以刷新 if (space > headerHeight + 30 && mscrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { state = RELEASE; reflashViewByState(); } break; case RELEASE: topPadding(topPadding); // 为释放状态时,则可以回到下拉状态 if (space < headerHeight + 30) { state = PULL; reflashViewByState(); } else if (space <= 0) { state = NONE; isRemark = false; reflashViewByState(); } break; case REFRESHING: break; default: break; } } /** * 改变下拉过程中的header布局中的控件的内容 */ public void reflashViewByState() { TextView tip = (TextView) header.findViewById(R.id.tip); ImageView arrow = (ImageView) header.findViewById(R.id.arrow); ProgressBar progressBar = (ProgressBar) header .findViewById(R.id.progress); // RotateAnimation旋转动画,旋转的角度,相对与自己,中心位置 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); progressBar.setVisibility(GONE); arrow.clearAnimation(); arrow.setAnimation(animation2); tip.setText("下拉可以刷新"); break; case RELEASE: arrow.setVisibility(View.VISIBLE); progressBar.setVisibility(GONE); arrow.clearAnimation(); arrow.setAnimation(animation); tip.setText("松开可以刷新"); break; case REFRESHING: topPadding(50); arrow.clearAnimation(); arrow.setVisibility(View.GONE); progressBar.setVisibility(VISIBLE); tip.setText("正在刷新"); break; default: break; } } /** * 获取完数据 */ public void reflashComplete() { state = NONE; isRemark = false; reflashViewByState(); TextView lastTimeReflash = (TextView) header .findViewById(R.id.lastrefresh_time); SimpleDateFormat format = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss"); Date date = new Date(System.currentTimeMillis()); String timeString = format.format(date); lastTimeReflash.setText(timeString); } /** * 底部加载完毕 */ public void reflashFooterComplete() { isLoading=false; footer.setVisibility(GONE); } /** * 刷新数据接口 * * @author nickming * */ public interface OnReflashListener { public void onrReflash(); } public OnReflashListener mListener;// 刷新数据的接口 public void setOnReflashListener(OnReflashListener listener) { mListener = listener; } /** * 加载更多接口 * @author nickming * */ public interface OnReflashMoreListener{ public void onReflashMore(); } public OnReflashMoreListener mListener2; public void setOnReflashMoreListener(OnReflashMoreListener listener) { mListener2=listener; } }
MainActivity.class
package com.example.listview_pulltorefresh; import java.util.ArrayList; import java.util.List; import java.util.UUID; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import com.example.listview_pulltorefresh.RefreshListview.OnReflashListener; import com.example.listview_pulltorefresh.RefreshListview.OnReflashMoreListener; public class MainActivity extends Activity { private RefreshListview mlistview; MyAdapter adapter; List<DataBean> mdata; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mlistview = (RefreshListview) findViewById(R.id.listview); mdata = new ArrayList<DataBean>(); for (int i = 0; i < 20; i++) { DataBean dataBean = new DataBean(); dataBean.setTextString("数据列:" + i); mdata.add(dataBean); } adapter = new MyAdapter(this, mdata); mlistview.setAdapter(adapter); mlistview.setOnReflashListener(new OnReflashListener() { @Override public void onrReflash() { // TODO Auto-generated method stub // 模拟网络延时 Handler mHandler = new Handler(); mHandler.postDelayed(new Runnable() { @Override public void run() { // TODO Auto-generated method stub // 获取最新数据 addHeaderData(); // 通知布局显示 adapter.notifyDataSetChanged(); // listview刷新 mlistview.reflashComplete(); } }, 3000); } }); mlistview.setOnReflashMoreListener(new OnReflashMoreListener() { @Override public void onReflashMore() { // TODO Auto-generated method stub Handler mHandler=new Handler(); mHandler.postDelayed(new Runnable() { @Override public void run() { // TODO Auto-generated method stub addFooterData(); adapter.notifyDataSetChanged(); mlistview.reflashFooterComplete(); } }, 2000); } }); } public void addHeaderData() { for (int i = 0; i < 10; i++) { DataBean dataBean = new DataBean(); String nameString = UUID.randomUUID().toString(); dataBean.setTextString("最新数据:" + nameString); mdata.add(0, dataBean);// 放在最前面 } } public void addFooterData() { for (int i = 0; i < 10; i++) { DataBean dataBean = new DataBean(); String nameString = UUID.randomUUID().toString(); dataBean.setTextString("最新数据:" + nameString); mdata.add( dataBean);// 放在最后面 } } }
adapter
package com.example.listview_pulltorefresh; import java.util.List; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.TextView; public class MyAdapter extends BaseAdapter { private Context mcontext; private List<DataBean> mData; LayoutInflater inflater; public MyAdapter(Context context, List<DataBean> mData) { super(); this.mcontext = context; this.mData = mData; inflater=LayoutInflater.from(context); } @Override public int getCount() { // TODO Auto-generated method stub return mData.size(); } @Override public Object getItem(int position) { // TODO Auto-generated method stub return mData.get(position); } @Override public long getItemId(int position) { // TODO Auto-generated method stub return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { // TODO Auto-generated method stub viewHolder holder=null; if (convertView == null) { convertView = inflater.inflate(R.layout.listitem, parent,false); holder = new viewHolder(); holder.textView = (TextView) convertView .findViewById(R.id.textView1); convertView.setTag(holder); }else { holder=(viewHolder) convertView.getTag(); } holder.textView.setText(mData.get(position).getTextString()); return convertView; } class viewHolder { TextView textView; } }
DataBean.class
package com.example.listview_pulltorefresh; import android.widget.TextView; public class DataBean { String textString; public DataBean() { // TODO Auto-generated constructor stub } public DataBean(String textString) { super(); this.textString = textString; } public String getTextString() { return textString; } public void setTextString(String textString) { this.textString = textString; } }
基本上没什么问题了,其实实现还是很简单的,不过就是要多多练习。