仿照知乎Android APP五——为ListView中同时实现上拉加载和下拉刷新,点击

模仿知乎Android APP五——为ListView中同时实现上拉加载和下拉刷新,点击

上拉加载之前跟大家分享过,就是通过监听listview的滚动事件,确定最后一个可视条目为listview的最后一个条目,这时候用户继续下拉,就去加载更多,然后需要注意的是需要本次加载完成之后才能去加载更多,所以需要有一个flag去标志是否完成加载。用户执行下拉刷新或者是上拉加载的操作是不同时的,就是下拉刷新的时候不能上拉加载,反之亦然。这样下拉就可以完成这个功能。

下拉刷新,首先,我们下拉得到的数据需要添加在原有数据的前面。然后下拉刷新的时候我们需要注意的地方一个是header的显示,他的提示文字随着用户的滑动不停的改变。然后当用户滑动到一定程度的时候,释放可以刷新数据。

所以,我们首先需要定义上拉刷新的四种状态,没有动作,开始下拉,释放可以刷新,正在刷新。然后,我们首次需要隐藏掉header,就利用到header的padding Top,不断改变该属性可以实现header随用户手滑动而改变。大概的效果如下图

                                      仿照知乎Android APP五——为ListView中同时实现上拉加载和下拉刷新,点击


下面是详细的代码实现:自定义ListView,继承ListView,然后实现OnScrollListener,主要的功能:下拉刷新header的显示处理,下拉加载footer的显示处理,定义两个接口对应下拉刷新和上拉加载,用于接口回调,与Activity通行。没有什么,需要注意一点的就是下拉刷新过程中的header可以转变的,可以有NONE 到RELEASE 反之也可以,需要作出处理,每一次处理之后都要及时调用refreshHeaderViewByState这个方法去重新显示header。当用户拖动的距离大于headerHeigh+30之后就可以松开刷新了,这个是可以自定义的。

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import android.annotation.SuppressLint;
import android.content.Context;
import android.util.AttributeSet;
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.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.guanggong.operatestdemo01.R;

/**
 * @author Administrator liweijie
 * @since 5.19
 * @version 2.0
 * 是一个自定义的listview,可作用于上拉刷新下拉加载
 *
 */
@SuppressLint("ClickableViewAccessibility")
public class OperaSumListView extends ListView implements OnScrollListener
{
	// 区分是下拉刷新还是上拉加载
	public static final int LOAD = 0;
	public static final int REFRESH = 1;
	// 这是上拉加载的一些view+需要的变量
	private View footer;
	private TextView loadFull;
	private TextView noData;
	private TextView loading;
	private ProgressBar loadProgressbar;
	private int lastVisiableitem;
	private int totleItem;
	private boolean isLoading = false; // 每一次都需要先把上次加载的完成之后才可以去加载下一次
	//用于保证下拉刷新的时候不可以上拉加载,反之一样
	private boolean isRefresh = false;
	private boolean isLoad = false;
	
	private View header;
	private int heighHeader;
	private int SPACE = 30; // 控制下拉到什么程度的时候开始改变state的值为RELEASE,松开可以刷新
	private int scrollState; // 滚动状态,只有是手指摁在屏幕导致的下拉刷新事件有效,假如是由于惯性的则无销,个人设定
	private int firstVisiableItem; // 第一个可见的item,用于判断当前情况是否可以下拉刷新
	private boolean isRemark = false;// 标记是否正在下拉刷新状态
	private int startY;// 手指摁下的时候后的y值
	private ImageView arrow;
	private TextView tip;
	private TextView lastUpdate;
	private ProgressBar refreshBar;
	// 下拉刷新的四种状态,一开始没有显示出来为NONE
	// 下拉显示出header的时候提示信息有所改变,下拉可刷新
	// 下拉一定程度的时候文字变为松开可刷新
	// 最后为正在刷新
	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 state;
	// 箭头动画
	private RotateAnimation anim;
	private RotateAnimation anim1;

	public OperaSumListView(Context context)
	{
		super(context);
		initView(context);
	}

	public OperaSumListView(Context context, AttributeSet attrs)
	{
		super(context, attrs);
		initView(context);
	}

	public OperaSumListView(Context context, AttributeSet attrs, int defStyleAttr)
	{
		super(context, attrs, defStyleAttr);
		initView(context);
	}

	/**
	 * 每一次header的状态改变的时候都需要对他的布局进行相关的处理
	 */
	@SuppressLint("InflateParams")
  private void initView(Context context)
	{
		anim = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, (float) 0.5,
		    RotateAnimation.RELATIVE_TO_SELF, 0.5f);
		anim.setDuration(400);
		anim.setFillAfter(true);

		anim1 = new RotateAnimation(180, 0, RotateAnimation.RELATIVE_TO_SELF, (float) 0.5,
		    RotateAnimation.RELATIVE_TO_SELF, 0.5f);
		anim1.setDuration(400);
		anim1.setFillAfter(true);

		LayoutInflater inflater = LayoutInflater.from(context);
		footer = inflater.inflate(R.layout.opera_sumfragment_listview_footer, null);
		loadFull = (TextView) footer.findViewById(R.id.id_sum_listview_footer_lv_textview01);
		noData = (TextView) footer.findViewById(R.id.id_sum_listview_footer_lv_textview02);
		loading = (TextView) footer.findViewById(R.id.id_sum_listview_footer_lv_textview03);
		loadProgressbar = (ProgressBar) footer.findViewById(R.id.id_sum_listview_footer_lv_progress);
		this.addFooterView(footer);
		footer.setVisibility(View.GONE);

		header = inflater.inflate(R.layout.opera_sumfragment_listview_header, null);
		arrow = (ImageView) header.findViewById(R.id.id_sum_listview_header_lv_rv_imageview);
		tip = (TextView) header.findViewById(R.id.id_sum_listview_header_lv_rv_lv_textview);
		lastUpdate = (TextView) header.findViewById(R.id.id_sum_listview_header_lv_rv_lv_textview02);
		lastUpdate.setText(new SimpleDateFormat("yyyy年MM月dd日 hh时mm分ss秒", Locale.getDefault())
		    .format(new Date()));
		refreshBar = (ProgressBar) header.findViewById(R.id.id_sum_listview_header_lv_rv_progress);
		measureHeader(header);
		heighHeader = header.getMeasuredHeight();
		toPadding(-heighHeader);
		this.addHeaderView(header);
		this.setOnScrollListener(this);
	}

	private void measureHeader(View child)
	{
		ViewGroup.LayoutParams lp = child.getLayoutParams();
		if (lp == null)
		{
			lp = new android.view.ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
			    ViewGroup.LayoutParams.WRAP_CONTENT);
		}
		int widthSpec = ViewGroup.getChildMeasureSpec(0, 0, lp.width);
		int temp = lp.height;
		int heighSpec;
		if (temp > 0)
		{
			heighSpec = MeasureSpec.makeMeasureSpec(temp, MeasureSpec.EXACTLY);
		} else
		{
			heighSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
		}
		child.measure(widthSpec, heighSpec);
	}

	/**
	 * 设置header的padding top的值
	 * 
	 * @param i
	 */
	private void toPadding(int i)
	{
		header.setPadding(header.getPaddingLeft(), i, header.getPaddingRight(),
		    header.getPaddingBottom());
		header.invalidate();
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev)
	{
		switch (ev.getAction())
		{
		case MotionEvent.ACTION_DOWN: // 首次手指摁下
			if (firstVisiableItem == 0)
			{
				isRemark = true;
				startY = (int) ev.getY();
			}
			break;

		case MotionEvent.ACTION_MOVE:// 手指移动
			whenMove(ev);
			break;

		case MotionEvent.ACTION_CANCEL:
		case MotionEvent.ACTION_UP:// 手指抬起
			System.out.println("state=" + state);
			if (state == PULL || state == NONE)
			{
				state = NONE;
				refreshHeaderViewByState();
			} else if (state == RELEASE)
			{
				System.out.println("有执行到");
				state = REFRESHING;
				refreshHeaderViewByState();
				if (onRefreshListener != null && !isLoad)
				{
					isRefresh = true;
					onRefreshListener.onRefresh();
				}
			}
			isRemark = false;
			break;
		}
		return super.onTouchEvent(ev);
	}

	/**
	 * 用于控制移动时候的状态改变和切换 计算padding top的值
	 * 
	 * @param ev
	 */
	private void whenMove(MotionEvent ev)
	{
		if (!isRemark)
		{
			return;
		}
		int tmpY = (int) ev.getY();
		int space = tmpY - startY;
		int topPadding = space - heighHeader;
		switch (state)
		{
		case NONE:
			if (space > 0)
			{
				state = PULL;
				refreshHeaderViewByState();
			}
			break;
		case PULL:
			toPadding(topPadding);
			if (space > heighHeader + SPACE && scrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL)
			{
				state = RELEASE;
				refreshHeaderViewByState();
			} else if (space < 0)
			{
				state = NONE;
				refreshHeaderViewByState();
			}
			break;
		case RELEASE:
			toPadding(topPadding);
			if (space < heighHeader + SPACE && space > 0)
			{
				state = PULL;
				refreshHeaderViewByState();
			} else if (space <= 0)
			{
				state = NONE;
				refreshHeaderViewByState();
			}
			break;
		}

	}

	/**
	 * 每一次header的状态改变的时候都需要对他的布局进行相关的处理
	 */
	private void refreshHeaderViewByState()
	{
		switch (state)
		{
		case NONE:
			toPadding(-heighHeader);
			break;
		case PULL:
			tip.setText("下拉可以刷新");
			arrow.clearAnimation();
			arrow.setAnimation(anim1);
			arrow.startAnimation(anim1);
			refreshBar.setVisibility(View.GONE);
			arrow.setVisibility(View.VISIBLE);
			break;
		case RELEASE:
			tip.setText("松开可以刷新");
			arrow.clearAnimation();
			arrow.setAnimation(anim);
			arrow.startAnimation(anim);
			arrow.setVisibility(View.VISIBLE);
			refreshBar.setVisibility(View.GONE);
			break;
		case REFRESHING:
			System.out.println("123有执行力过");
			toPadding(30);
			arrow.clearAnimation();
			arrow.setVisibility(View.GONE);
			refreshBar.setVisibility(View.VISIBLE);
			tip.setText("正在刷新");
		}
	}

	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState)
	{
		this.scrollState = scrollState;
		if (lastVisiableitem == totleItem && scrollState == OnScrollListener.SCROLL_STATE_IDLE &&!isRefresh)
		{
			if (!isLoading)
			{
				isLoading = true;
				isLoad = true;
				footer.setVisibility(View.VISIBLE);
				if (onLoadListener != null)
				{
					onLoadListener.onLoad();
				}
			}
		}
	}

	@Override
	public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
	    int totalItemCount)
	{
		this.firstVisiableItem = firstVisibleItem;
		this.lastVisiableitem = firstVisibleItem + visibleItemCount;
		this.totleItem = totalItemCount;
	}
	/**
	 * 上拉加载监听
	 * 
	 * @author Administrator
	 *
	 */
	public interface OnLoadListener
	{
		void onLoad();
	}

	private OnLoadListener onLoadListener;

	public void setOnLoadListener(OnLoadListener onLoadListener)
	{
		this.onLoadListener = onLoadListener;
	}

	/**
	 * 下拉刷新监听
	 * 
	 * @author Administrator
	 *
	 */
	public interface OnRefreshListener
	{
		void onRefresh();
	}

	private OnRefreshListener onRefreshListener;

	public void setOnRefreshListener(OnRefreshListener onRefreshListener)
	{
		this.onRefreshListener = onRefreshListener;
	}
	
	/**
	 * 对于下拉加载的各种情况进行处理 1,数据还有很多,没有加载完成,下拉可以继续加载 2,数据本次加载完成,下次下拉不会再加载
	 * 3,数据已经之前巧合加载完成,不会再去加载
	 * 
	 * @param result
	 */
	public void setFooterResult(int result)
	{
		isLoad = false;
		System.out.println(result + "result");
		if (result == 10)// 还有剩余数据
		{
			isLoading = false;
			loading.setVisibility(View.VISIBLE);
			loadProgressbar.setVisibility(View.VISIBLE);
			noData.setVisibility(View.GONE);
			loadFull.setVisibility(View.GONE);
			footer.setVisibility(View.GONE);
		} else if (result < 10 && result > 0)// 已经完全没有数据可以加载,这是最后一次加载数据
		{
			isLoading = true;
			loading.setVisibility(View.GONE);
			loadProgressbar.setVisibility(View.GONE);
			noData.setVisibility(View.GONE);
			loadFull.setVisibility(View.VISIBLE);
			footer.setVisibility(View.VISIBLE);
		} else
		{
			isLoading = true;// 已经完全没有数据可以加载
			loading.setVisibility(View.GONE);
			loadProgressbar.setVisibility(View.GONE);
			noData.setVisibility(View.VISIBLE);
			loadFull.setVisibility(View.GONE);
			footer.setVisibility(View.VISIBLE);
		}
	}
	
	/**
	 * 刷新完成之后需要做一些后续工作
	 */
	public void refreshCompleted(boolean flag)
	{
		isRefresh = false;
		state = NONE;
		isRemark = false;
		if(flag)
		{
		lastUpdate.setText(new SimpleDateFormat("yyyy年MM月dd日 hh时mm分ss秒", Locale.getDefault())
		    .format(new Date()));
		}
		refreshHeaderViewByState();
	}
	
}

由于这一篇blog是接着之前的,需要在OperaSumFragment之中去实现MyListView的接口,包括之前的一些处理,如下,代码

import java.util.List;

import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;

import com.guanggong.operatestdemo01.R;
import com.guanggong.operatestdemo01.Constant.Constant;
import com.guanggong.operatestdemo01.Tools.MyToast;
import com.guanggong.operatestdemo01.activity.OperaDetailNewsActivity;
import com.guanggong.operatestdemo01.adapter.OperaSumAdapter;
import com.guanggong.operatestdemo01.domain.NewsInfo;
import com.guanggong.operatestdemo01.thread.ThreadOfLoadXMLInfo;
import com.guanggong.operatestdemo01.view.OperaSumListView;
import com.guanggong.operatestdemo01.view.OperaSumListView.OnLoadListener;
import com.guanggong.operatestdemo01.view.OperaSumListView.OnRefreshListener;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.display.RoundedBitmapDisplayer;

/**
 * @author Administrator liweijie
 * @since 5.12
 * @version 1.0 设置OperaMultiFragment的第一个tab的内容Fragment 粤剧近况,他会显示最近的粤剧新闻
 *
 */
public class OperaSumFragment extends Fragment implements OnItemClickListener,OnRefreshListener,OnLoadListener
{
	private View mOperaSumFragmentLayoutView;
	private OperaSumListView mListView;
	private OperaSumAdapter mAdapter;
	private DisplayImageOptions options;
	private String path = "http://10.21.64.112:8080/web/News.xml";
	@SuppressLint("HandlerLeak")
	private Handler mHandler = new Handler()
	{
		@SuppressWarnings("unchecked")
		public void handleMessage(android.os.Message msg)
		{
			if (msg.what == Constant.OK)//首次进入的时候自动去加载,成功之后就绑定适配器
			{
				mAdapter = new OperaSumAdapter(getActivity(), (List<NewsInfo>) msg.obj, options);
				mListView.setAdapter(mAdapter);
			}
			if(msg.what == Constant.REFRESH_OK)//下拉刷新获取数据成功之后
			{
				mAdapter.addDatasToFirst((List<NewsInfo>) msg.obj);
				mListView.refreshCompleted(true);
			}
			if(msg.what == Constant.LOAD_OK)<span style="font-family: Arial, Helvetica, sans-serif;">//上拉加载更多获取数据成功的时候</span>
			{
				mAdapter.addDatasToLadst((List<NewsInfo>) msg.obj);
				mListView.setFooterResult(((List<NewsInfo>) msg.obj).size());
			}
			if (msg.what == Constant.ERROR)
			{
				MyToast.showToast(getActivity(), "获取失败,请检查网络");
				mListView.refreshCompleted(false);
			}
		};
	};

	@SuppressLint("InflateParams")
	@SuppressWarnings("deprecation")
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
	{
		mOperaSumFragmentLayoutView = inflater.inflate(R.layout.layout_of_operasum_fragment, null);
		mListView = (OperaSumListView) mOperaSumFragmentLayoutView.findViewById(R.id.id_sum_lv_listview);
		options = new DisplayImageOptions.Builder().showStubImage(R.drawable.ic_launcher)
		    .showImageForEmptyUri(R.drawable.ic_empty).showImageOnFail(R.drawable.ic_error)
		    .displayer(new RoundedBitmapDisplayer(20)).build();
		new ThreadOfLoadXMLInfo(mHandler, path, Constant.OPERASUM_FRAGMENT).start();
		mListView.setOnItemClickListener(this);
		mListView.setOnRefreshListener(this);
		mListView.setOnLoadListener(this);
		return mOperaSumFragmentLayoutView;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * android.widget.AdapterView.OnItemClickListener#onItemClick(android.widget
	 * .AdapterView, android.view.View, int, long) 参数说明,第一个adapter
	 * 第二个,显示一个item的view,这里是包括三个textview+一个imageview 第三个在适配器额位置 第四个,他在listview的第几行
	 */
	@Override
	public void onItemClick(AdapterView<?> parent, View view, int position, long id)
	{
		String pathOfDetailed = ((NewsInfo) parent.getAdapter().getItem(position)).getPathOfDetailed();
		if(!pathOfDetailed.equals(null) && pathOfDetailed.trim().length()>0)
		{
		Intent intent = new Intent(getActivity(), OperaDetailNewsActivity.class);
		 intent.putExtra(Constant.NewsDetailPath, pathOfDetailed);
		System.out.println(pathOfDetailed+"详细新闻的路径");
		startActivity(intent);
		}
	}

	@Override
  public void onLoad()
  {
		System.out.println("有执行加载");
		path = "http://10.21.64.112:8080/web/load.xml";
		new ThreadOfLoadXMLInfo(mHandler, path, Constant.OPERASUM_FRAGEMTN_LOAD).start();
  }

	@Override
  public void onRefresh()
  {
		System.out.println("有执行刷新");
		path = "http://10.21.64.112:8080/web/fresh.xml";
		new ThreadOfLoadXMLInfo(mHandler, path, Constant.OPERASUM_FRAGEMTN_FRFRESH).start();
  }
}


我这里对上拉刷新和下拉加载得到数据之后在Adapter做出的处理时不一样的,下拉刷新成功之后,把原来的数据全部请掉,然后只显示下拉刷新得到的数据,当是上拉加载得到的结果的时候,就把下拉得到的结果放在原有的结果之前。

在ThreadOfLoadXMLInfo类中,多了如下的代码,由于下拉刷新或者是上拉加载,数据都是和之前的一开始进入OperaSumFragment去加载的一样,就可以使用同样的API去实现,不用写过多的方法。

	//在sum,头条界面下拉刷新时候
			case Constant.OPERASUM_FRAGEMTN_FRFRESH:
				try
        {
	        if (mNewsDatas.addAll(LoadInfoControl.getOperaSumData(loadPath)))
	        {
	        	sendMessage(mNewsDatas,Constant.REFRESH_OK);
	        } else
	        {
	        	uIHandler.sendEmptyMessage(Constant.ERROR);
	        }
        } catch (Exception e)
        {
	        e.printStackTrace();
        }
				break;
				//上拉加载
			case Constant.OPERASUM_FRAGEMTN_LOAD:
				try
        {
	        if (mNewsDatas.addAll(LoadInfoControl.getOperaSumData(loadPath)))
	        {
	        	sendMessage(mNewsDatas,Constant.LOAD_OK);
	        } else
	        {
	        	uIHandler.sendEmptyMessage(Constant.ERROR);
	        }
        } catch (Exception e)
        {
	        e.printStackTrace();
        }
				break;
				

结果图:

仿照知乎Android APP五——为ListView中同时实现上拉加载和下拉刷新,点击仿照知乎Android APP五——为ListView中同时实现上拉加载和下拉刷新,点击