
这篇文章将带大家了解listview下拉刷新和上拉加载更多的实现过程,先看效果(注:图片中listview中的阴影可以加上属性android:fadingEdge="none"去掉):
1
2
3
4
5
接下来再看一下工程文件;
首先,实现这种效果需要重写ListView控件,工程中的RefreshListView即继承了ListView,并实现了OnScrollListener接口,头部和脚部和主界面其实是连在一起的,只是在RefreshListView中没让它显示出来,只有当特定条件比如下拉上拉时才会显示,这些动作执行完毕后又会重新隐藏,先来看一下RefreshListView代码:
/** * @author baiyuliang */ public class RefreshListView extends ListView implements OnScrollListener { private int downY; // 按下时y轴的偏移量 private View headerView; // 头布局 private int headerViewHeight; // 头布局的高度 private int firstVisibleItemPosition; // 滚动时界面显示在顶部的item的position private DisplayMode currentState = DisplayMode.Pull_Down; // 头布局当前的状态, 缺省值为下拉状态 private Animation upAnim,downAnim,loadAnim; // 向上旋转的动画,向下旋转的动画,刷新数据时load动画 private ImageView ivArrow; // 头布局的箭头 private TextView tvState; // 头布局刷新状态 private ImageView loading_img_header,loading_img_footer; // 头布局和脚布局的进度条 private TextView tvLastUpdateTime; // 头布局的最后刷新时间 private OnRefreshListener mOnRefreshListener; private boolean isScroll2Bottom = false; // 是否滚动到底部 private View footerView; // 脚布局 private int footerViewHeight; // 脚布局的高度 private boolean isLoadMoving = false; // 是否正在加载更多中 public RefreshListView(Context context, AttributeSet attrs) { super(context, attrs); initHeader(); initFooter(); this.setOnScrollListener(this); } /** * 初始化脚布局 */ private void initFooter() { footerView = LayoutInflater.from(getContext()).inflate(R.layout.activity_listview_refresh_footer, null); loading_img_footer=(ImageView) footerView.findViewById(R.id.pb_listview_footer_progress); measureView(footerView); // 测量一下脚布局的高度 footerViewHeight = footerView.getMeasuredHeight(); footerView.setPadding(0, -footerViewHeight, 0, 0); // 隐藏脚布局 this.addFooterView(footerView); } /** * 初始化头布局 */ private void initHeader() { //头部 headerView = LayoutInflater.from(getContext()).inflate(R.layout.activity_listview_refresh_header, null); ivArrow = (ImageView) headerView.findViewById(R.id.iv_listview_header_down_arrow); loading_img_header = (ImageView) headerView.findViewById(R.id.pb_listview_header_progress); tvState = (TextView) headerView.findViewById(R.id.tv_listview_header_state); tvLastUpdateTime = (TextView) headerView.findViewById(R.id.tv_listview_header_last_update_time); ivArrow.setMinimumWidth(50); tvLastUpdateTime.setText("上次刷新时间: " + getLastUpdateTime()); measureView(headerView); headerViewHeight = headerView.getMeasuredHeight(); Log.i("RefreshListView", "头布局的高度: " + headerViewHeight); headerView.setPadding(0, -headerViewHeight, 0, 0); // 隐藏头布局 this.addHeaderView(headerView); initAnimation(); } /** * 获得最后刷新的时间 * @return */ private String getLastUpdateTime() { SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return sdf.format(new Date()); } /** * 初始下拉刷新时箭头动画 */ private void initAnimation() { //向上 upAnim = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); upAnim.setDuration(500); upAnim.setFillAfter(true); //向下 downAnim = new RotateAnimation(-180, -360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); downAnim.setDuration(500); downAnim.setFillAfter(true); //刷新数据时load动画 loadAnim=new RotateAnimation(0, 359, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f); loadAnim.setDuration(2000); loadAnim.setInterpolator(new LinearInterpolator()); loadAnim.setFillAfter(true); loadAnim.setRepeatCount(-1); } /** * 测量给定的View的宽和高, 测量之后, 可以得到view的宽和高 * @param child */ private void measureView(View child) { ViewGroup.LayoutParams lp = child.getLayoutParams(); if (lp == null) { lp = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0, lp.width); int lpHeight = lp.height; int childHeightSpec; if (lpHeight > 0) { childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); } else { childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } child.measure(childWidthSpec, childHeightSpec); } @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: downY = (int) ev.getY(); break; case MotionEvent.ACTION_MOVE: if(currentState == DisplayMode.Refreshing) { // 当前的状态是正在刷新中, 不执行下拉操作 break; } int moveY = (int) ev.getY(); // 移动中的y轴的偏移量 int diffY = moveY - downY; int paddingTop = -headerViewHeight + (diffY / 2); if(firstVisibleItemPosition == 0&& paddingTop > -headerViewHeight) { /** * paddingTop > 0 完全显示 * currentState == DisplayMode.Pull_Down 当是在下拉状态时 */ if(paddingTop > 0&& currentState == DisplayMode.Pull_Down) { // 完全显示, 进入到刷新状态 Log.i("RefreshListView", "松开刷新"); currentState = DisplayMode.Release_Refresh; // 把当前的状态改为松开刷新 refreshHeaderViewState(); } else if(paddingTop < 0&& currentState == DisplayMode.Release_Refresh) { // 没有完全显示, 进入到下拉状态 Log.i("RefreshListView", "下拉刷新"); currentState = DisplayMode.Pull_Down; refreshHeaderViewState(); } headerView.setPadding(0, paddingTop, 0, 0); return true; } break; case MotionEvent.ACTION_UP: downY = -1; if(currentState == DisplayMode.Pull_Down) { // 松开时, 当前显示的状态为下拉状态, 执行隐藏headerView的操作 headerView.setPadding(0, -headerViewHeight, 0, 0); } else if(currentState == DisplayMode.Release_Refresh) { // 松开时, 当前显示的状态为松开刷新状态, 执行刷新的操作 headerView.setPadding(0, 0, 0, 0); currentState = DisplayMode.Refreshing; refreshHeaderViewState(); if(mOnRefreshListener != null) { mOnRefreshListener.onRefresh(); } } break; default: break; } return super.onTouchEvent(ev); } /** * 当刷新任务执行完毕时, 回调此方法, 去刷新界面 */ public void onRefreshFinish() { if(isLoadMoving) { // 隐藏脚布局 loading_img_footer.clearAnimation(); isLoadMoving = false; isScroll2Bottom = false; footerView.setPadding(0, -footerViewHeight, 0, 0); } else { // 隐藏头布局 headerView.setPadding(0, -headerViewHeight, 0, 0); loading_img_header.clearAnimation(); loading_img_header.setVisibility(View.GONE); ivArrow.setVisibility(View.VISIBLE); tvState.setText("下拉刷新"); tvLastUpdateTime.setText("上次刷新时间: " + getLastUpdateTime()); currentState = DisplayMode.Pull_Down; } } /** * 刷新头布局的状态 */ private void refreshHeaderViewState() { if(currentState == DisplayMode.Pull_Down) { // 当前进入下拉状态 ivArrow.startAnimation(downAnim); tvState.setText("下拉刷新"); } else if(currentState == DisplayMode.Release_Refresh) { //当前进入松开刷新状态 ivArrow.startAnimation(upAnim); tvState.setText("释放立即刷新"); } else if(currentState == DisplayMode.Refreshing) { //当前进入正在刷新中 ivArrow.clearAnimation(); ivArrow.setVisibility(View.GONE); loading_img_header.setVisibility(View.VISIBLE); loading_img_header.startAnimation(loadAnim); tvState.setText("正在获取新内容"); } } /** * 刷新头布局的状态 * 当ListView滚动状态改变时回调 * SCROLL_STATE_IDLE // 当ListView滚动停止时 * SCROLL_STATE_TOUCH_SCROLL // 当ListView触摸滚动时 * SCROLL_STATE_FLING // 快速的滚动(手指快速的触摸移动) */ @Override public void onScrollStateChanged(AbsListView view, int scrollState) { if(scrollState == OnScrollListener.SCROLL_STATE_IDLE || scrollState == OnScrollListener.SCROLL_STATE_FLING) { if(isScroll2Bottom && !isLoadMoving) { // 滚动到底部 // 加载更多 loading_img_footer.startAnimation(loadAnim); footerView.setPadding(0, 0, 0, 0); this.setSelection(this.getCount()); // 滚动到ListView的底部 isLoadMoving = true; if(mOnRefreshListener != null) { mOnRefreshListener.onLoadMoring(); } } } } /** * 当ListView滚动时触发 * firstVisibleItem 屏幕上显示的第一个Item的position * visibleItemCount 当前屏幕显示的总个数 * totalItemCount ListView的总条数 */ @Override public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) { firstVisibleItemPosition = firstVisibleItem; Log.i("RefreshListView", "onScroll: " + firstVisibleItem + ", " + visibleItemCount + ", " + totalItemCount); if((firstVisibleItem + visibleItemCount) >= totalItemCount&& totalItemCount > 0) { Log.i("RefreshListView", "加载更多"); isScroll2Bottom = true; } else { isScroll2Bottom = false; } } /** * @author andong * 下拉头部的几种显示状态 */ public enum DisplayMode { Pull_Down, // 下拉刷新的状态 Release_Refresh, // 松开刷新的状态 Refreshing // 正在刷新中的状态 } /** * 设置刷新的监听事件 * @param listener */ public void setOnRefreshListener(OnRefreshListener listener) { this.mOnRefreshListener = listener; } }
代码看上去比较复杂,大家可以慢慢研究,主要就是设置监听了几个动作,触摸屏幕,向下滑动,离开屏幕,下上滑动等等,根据不同动作执行相应操作,比如下拉时,头部布局便会显示出来,而当头部拉伸超过一定尺度后,箭头会执行一个动画,又指向下变为指向上,头部布局中的Textview空间内容也会发生相应变化,当松开手指时,又会出现一个动画如上图标记额地方,其实是一张图片,设置了旋转动画而已,而上拉加载更多是同样的道理,而且在动作执行完毕时,仍会有一个监听,主要是销毁动画和隐藏头部或脚部布局。
图中listview的效果是需要写一个自定义的适配器,这个我想大家都明白,也都会想到的吧,就不用我多说了,看代码:
/** * @author baiyuliang */ public class MyAdapter extends BaseAdapter { private Activity activity; private ArrayList<HashMap<String, String>> listDate; private static LayoutInflater inflater=null; public MyAdapter(Activity a, ArrayList<HashMap<String, String>> d) { activity = a; listDate=d; inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE); } public int getCount() { return listDate.size(); } public Object getItem(int position) { return position; } public long getItemId(int position) { return position; } public View getView(int position, View convertView, ViewGroup parent) { View vi=convertView; if(convertView==null) vi = inflater.inflate(R.layout.list_item, null); TextView username = (TextView) vi.findViewById(R.id.username); // 用户头像 TextView creatdate=(TextView) vi.findViewById(R.id.creatdate);//时间 TextView mood=(TextView) vi.findViewById(R.id.mood);//心情内容 HashMap<String, String> map = new HashMap<String, String>(); map = listDate.get(position); //设置ListView的相关控件 username.setText(map.get("username")); creatdate.setText(map.get("creatdate")); mood.setText(map.get("mood")); return vi; } }
接下来我们看一下,主界面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" tools:context=".MainActivity" > <TextView android:id="@+id/title" android:layout_width="fill_parent" android:layout_height="45dip" android:text="下拉刷新上拉加载" android:textColor="#FFFFFF" android:textSize="20sp" android:gravity="center" android:background="@drawable/tab_footer_bg" /> <com.baiyuliang.pullrefresh.RefreshListView android:id="@+id/refresh_listview" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_below="@+id/title"/> </RelativeLayout>
很简单,就是一个标题栏和我们重写的ListView,listview中每个条目的布局list_item布局如下:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#FFFFFF" android:orientation="vertical" > <RelativeLayout android:layout_width="fill_parent" android:layout_height="50dp" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginTop="10dp" > <RelativeLayout android:id="@+id/headimg" android:layout_width="40dp" android:layout_height="40dp" android:layout_centerVertical="true" > <ImageView android:id="@+id/head_img" android:layout_width="40dp" android:layout_height="40dp" android:layout_centerVertical="true" android:src="@drawable/head_img"/> </RelativeLayout> <RelativeLayout android:id="@+id/name_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="10dp" android:layout_toRightOf="@+id/headimg" > <TextView android:id="@+id/username" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="baiyuliang" android:textColor="#00008B" android:textSize="20sp" /> <TextView android:id="@+id/creatdate" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/username" android:layout_below="@+id/username" android:layout_marginLeft="2dp" android:text="今天12:00" android:textSize="12sp" /> </RelativeLayout> </RelativeLayout> <TextView android:id="@+id/mood" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginTop="3dp" android:text="今天心情不错,出来冒个泡!" android:textColor="#000000" android:textSize="15sp" /> <ImageView android:id="@+id/mood_img" android:layout_width="fill_parent" android:layout_height="wrap_content" android:visibility="gone" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginTop="5dp" /> <RelativeLayout android:id="@+id/phonetype" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="5dp" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:layout_marginTop="10dp" > <ImageView android:id="@+id/phone_type" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:layout_centerVertical="true" android:layout_marginRight="2dp" android:src="@drawable/phone_type" /> <TextView android:id="@+id/phone_type_hanzi" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_toRightOf="@+id/phone_type" android:text="Android" /> <ImageView android:id="@+id/zan_img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="3dp" android:layout_toLeftOf="@+id/zan_hanzi" android:src="@drawable/qz_detail_icon_praise" /> <TextView android:id="@+id/zan_hanzi" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerVertical="true" android:layout_marginLeft="2dp" android:layout_marginRight="10dp" android:layout_toLeftOf="@+id/pinglun_img" android:text="赞" android:textSize="12sp" /> <ImageView android:id="@+id/pinglun_img" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginRight="3dp" android:layout_toLeftOf="@+id/pinglun_hanzi" android:src="@drawable/qz_detail_icon_comment" /> <TextView android:id="@+id/pinglun_hanzi" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentRight="true" android:layout_centerVertical="true" android:text="评论" android:textSize="12sp" /> </RelativeLayout> </LinearLayout>
再看MainActivity:
/** * @author baiyuliang */ public class MainActivity extends Activity { private MyAdapter mAdapter; private ArrayList<HashMap<String, String>> listItem;//生成动态数组,加入数据 private RefreshListView mRefreshListView; private HashMap<String, String> map; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); initView(); initData(); } public void initView() { mRefreshListView = (RefreshListView)findViewById(R.id.refresh_listview); listItem = new ArrayList<HashMap<String, String>>(); } private void initData() { for (int i = 0; i < 10; i++) { map=new HashMap<String, String>(); map.put("username", "baiyuliang"+(i+1)); map.put("mood", "今天心情不错哈!!!"); map.put("creatdate", new SimpleDateFormat("HH:mm:ss").format(new Date())); listItem.add(map); } mAdapter = new MyAdapter(this, listItem); mRefreshListView.setAdapter(mAdapter); mRefreshListView.setOnRefreshListener(new OnRefreshListener() { @Override public void onRefresh() { // 异步查询数据 new AsyncTask<Void, Void, Void>(){ @Override protected Void doInBackground(Void... params) { SystemClock.sleep(1000); map=new HashMap<String, String>(); map.put("username", "new"); map.put("mood", "我是被下拉刷新出来的"); map.put("creatdate", new SimpleDateFormat("HH:mm:ss").format(new Date())); listItem.add(0,map); return null; } protected void onPostExecute(Void result) { mAdapter.notifyDataSetChanged(); // 隐藏头布局 mRefreshListView.onRefreshFinish(); } }.execute(new Void[]{}); } @Override public void onLoadMoring() { new AsyncTask<Void, Void, Void>() { @Override protected Void doInBackground(Void... params) { SystemClock.sleep(2000); for (int i = 0; i < 10; i++) { map=new HashMap<String, String>(); map.put("username", "baiyuliang"); map.put("mood", "上拉刷新出来的数据"+(i+1)); map.put("creatdate", new SimpleDateFormat("HH:mm:ss").format(new Date())); listItem.add(map); } return null; } @Override protected void onPostExecute(Void result) { super.onPostExecute(result); mAdapter.notifyDataSetChanged(); mRefreshListView.onRefreshFinish(); } }.execute(new Void[]{}); } }); } }
这个跟普通的listView适配一样,我想大家都知道该怎么做,主要就是我们重写的RefreshListView中包含了两个监听,下拉刷新和上拉加载需要在这里实现,对listview的更新我们需用到notifyDataSetChanged()方法,这个也不需要我多说,我想大家都应该知道,最后我们看一下,头部布局:
<?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="horizontal" > <FrameLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_vertical|center_horizontal" android:padding="5dp"> <ImageView android:id="@+id/iv_listview_header_down_arrow" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:src="@drawable/common_listview_headview_red_arrow" /> <ImageView android:id="@+id/pb_listview_header_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:src="@drawable/refresh_loading" android:visibility="gone" /> </FrameLayout> <LinearLayout android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical|center_horizontal" android:gravity="center_horizontal" android:orientation="vertical" android:padding="5dp"> <TextView android:id="@+id/tv_listview_header_state" android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="下拉刷新" android:textColor="@android:color/darker_gray" android:textSize="12sp" /> <TextView android:id="@+id/tv_listview_header_last_update_time" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="5dip" android:text="最后刷新时间: 1990-09-09 09:00:00" android:textColor="@android:color/darker_gray" android:textSize="12sp" /> </LinearLayout> </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:background="#FFFFFF" android:orientation="vertical" > <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:gravity="center_vertical" android:orientation="horizontal" android:padding="10dp" > <ImageView android:id="@+id/pb_listview_footer_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center_horizontal" android:src="@drawable/refresh_loading" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dip" android:text="加载更多..." android:textColor="@android:color/darker_gray" android:textSize="12sp" /> </LinearLayout> </LinearLayout>
其实就这么多,是不是很简单,主要就是我们重写的ListView控件,如果看不明白,大家可以在下面下载我的源码研究: