仿照知乎Android APP五——为ListView中同时实现上拉加载和下拉刷新,点击
上拉加载之前跟大家分享过,就是通过监听listview的滚动事件,确定最后一个可视条目为listview的最后一个条目,这时候用户继续下拉,就去加载更多,然后需要注意的是需要本次加载完成之后才能去加载更多,所以需要有一个flag去标志是否完成加载。用户执行下拉刷新或者是上拉加载的操作是不同时的,就是下拉刷新的时候不能上拉加载,反之亦然。这样下拉就可以完成这个功能。
下拉刷新,首先,我们下拉得到的数据需要添加在原有数据的前面。然后下拉刷新的时候我们需要注意的地方一个是header的显示,他的提示文字随着用户的滑动不停的改变。然后当用户滑动到一定程度的时候,释放可以刷新数据。
所以,我们首先需要定义上拉刷新的四种状态,没有动作,开始下拉,释放可以刷新,正在刷新。然后,我们首次需要隐藏掉header,就利用到header的padding Top,不断改变该属性可以实现header随用户手滑动而改变。大概的效果如下图
下面是详细的代码实现:自定义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(); } }
在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;
结果图: