Android PullToRefreshView自定义下拉刷新控件

MyPullToRefreshView继承自LinearLayout,布局为vertical,该容器中包含三个子view,这三个view从上到下依次排列在LinearLayout中。

效果图如下:

                                                                         

下图中蓝色部分是充满屏幕的,HeaderView在ListView的上方,在代码中动态添加进来,使其底部Y轴坐标刚好为0,FooterView在ListView的下方,也在代码中动态添加进来,该View的TopMargin刚好为整个布局的高度。

首先看一下该控件的使用:

1.在xml中配置

<span style="font-size:14px;"><com.example.testpulltorefreshview.PullToRefreshView 
        android:id="@+id/my_pull_to_refresh_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical">
        <ListView
            android:id="@+id/listView" 
            android:layout_width="match_parent"
            android:layout_height="match_parent">
        </ListView>
</com.example.testpulltorefreshview.PullToRefreshView></span>
2.在代码中为ListView设置适配器,添加数据

<span style="font-size:14px;">ListView list=(ListView)findViewById(R.id.listView);
list.setAdapter(new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,new String[]{"sss"}));</span>
模块一:控件测绘

在代码中动态添加HeaderView时,需要首先测量HeaderView的高度h,因为我们要是其TopMargin设置为0-h,这样才能使得HeaderView的底部刚好Y轴坐标为0。同理,FooterView的topMargin应该设置为整个控件的高度getHeight。为了简化添加FooterView的处理,只需使ListView充满整个控件后再添加FooterView。

在构造函数中强制设置LinearLayout排列方法为vertical,然后在构造函数中添加HeaderView,因为此时ListView还未被添加进来。

<span style="font-size:14px;">public MyPullToRefreshView(Context context,AttributeSet attrs){
  super(context,attrs);
  mContext=context;
  this.setOrientation(LinearLayout.VERTICAL);//强制设置控件的排列方向为vertical
  init();
}</span>
构造函数中通过init函数动态添加HeaderView,注意此时在xml文件中设置的ListView还未被添加到控件中。所以此时添加的HeaderView是LinearLayout容器中的第一个控件。

<span style="font-size:14px;">public void init(){
  inflater=LayoutInflater.from(mContext);
  ...
  此时创建动画资源,后面添加
  ...
  addHeaderView();
}</span>
在addHeaderView函数中创建View,并测量其高度:

<span style="font-size:14px;">public void addHeaderView(){
  mHeaderView=inflater.inflate(R.layout.refresh_header,this,false);
  ...
  执行findViewById,初始化layout中的View实例
  ...
  measureView(mHeaderView);//测量HeaderView的高度
  mHeaderHeight=mHeaderView.getMeasuredHeight();//获得测量的HeaderView高度
  LayoutParams params=new LayoutInflater(LayoutParams.MATCH_PARENT,mHeaderHeight);//创建HeaderView的布局参数LayoutParams
  params.topMargin=-mHeaderHeight;//设置HeaderView的topMargin为-mHeaderHeight,这样HeaderView的底部Y轴坐标为0。
  addView(mHeaderView,params);
}</span>
我们需要分析measureView函数,搞清楚如何可以测量HeaderView的高度。在此之前首先需要看一下R.layout.refresh_header代码。
<span style="font-size:14px;"><?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/rv_pull_to_refresh_header"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingTop="8dp"
    android:paddingBottom="5dp">"
    
    <ImageView 
        android:id="@+id/iv_arrow"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerVertical="true"
        android:layout_marginLeft="60dp"
        android:contentDescription="@string/app_name"
        android:src="@drawable/arrow_up"/>
    
    <ProgressBar
        android:id="@+id/pull_to_refresh_progress"
        style="?android:attr/progressBarStyleInverse"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="60dp"
        android:indeterminate="true"
        android:visibility="gone"/>

    <TextView
        android:id="@+id/tv_state"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:layout_centerHorizontal="true"
        android:text="@string/pull_to_refresh" />

    <TextView
        android:id="@+id/tv_date"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignLeft="@id/tv_state"
        android:layout_below="@+id/tv_state"
        android:text="更新于。。。。" 
        android:visibility="gone"/>

</RelativeLayout></span>
在使用上可以设置HeaderView的高度为具体的值,如
<span style="font-size:14px;"><RelativeLayout
   android:layout_width=match_parent
   android:layout_height="20dp"
   ...>
...
</RelativeLayout></span>
也可以设置HeaderView的高度刚好包裹内部的View,即wrap_content.
<span style="font-size:14px;"><RelativeLayout
   android:layout_width=match_parent
   android:layout_height="wrap_content"
   ...>
...
</RelativeLayout></span>
所以在测量控件高度是要针对两种情况考虑。
<span style="font-size:14px;">public void measureView(View child){
  ViewGroup.LayoutParams params=child.getLayoutParams();
  if(params==null){
    params=new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);
  }
  //如果params的值不是具体的dp值,那么等价于MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED),如果params为具体的dp值,那么等价于MeasureSpec.makeMeasureSpec(dp值,MesureSpec.EXACTLY)
  int widthMeasureSpec=getChildMeasureSpec(0,0+0,params.width);
  int heightMeasureSpec;
  if(params.height>0){//为具体的dp值
    heightMeasureSpec=MeasureSpec.makeMeasureSpec(params.height,MeasureSpec.EXACTLY);
  }
  else{
    heightMeasureSpec=MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
  }
  child.measure(widthMeasureSpec,heightMeasureSpec);
}</span>
在添加完成HeaderView后该添加FooterView了。FooterView应该添加到整个控件的最后。Activity中执行setContentView来解析xml构建View Tree。当解析完成当前的View后会回调View的onFinishInflate,在该函数中添加FooterView,就能保证FooterView被添加到了整个控件的尾部。
<span style="font-size:14px;">@Override
protected void onFinishInflate(){
  super.onFinishInflate();
  addFooterView();
  initContentAdapterView();
}
public void addFooterView(){
  mFooterView=inflater.inflate(R.layout.refresh_footer,this,false);
  ...
  实例化view变量
  ...
  measureView(mFooterView);
  mFooterHeight=mFooterView.getMeasuredHeight();
  LayoutParams params=new LayoutParams(LayoutParams.MATCH_PARENT,mFooterHeight);
  addFooterView(mFooterView,params);
}</span>
模块二:触摸分发

上面完成了控件中View的添加和布局,下面需要实现整个控件的触摸分发模块了。

下面先简单总结Android的触摸分发机制。
public boolean dispatchTouchEvent(MotionEvent ev)    分发触摸
public boolean onInterceptTouchEvent(MotionEvent ev) 拦截触摸
public boolean onTouchEvent(MotionEvent ev)          处理触摸

ViewGroup包含以上三个函数,Activity和View只包含dispatchTouchEvent和onTouchEvent两个函数。触摸事件的分发是从Activity开始的,再到ViewGroup,ViewGroup在向下传到View中。

<span style="font-size:14px;">//Activity.java
public boolean dispatchTouchEvent(MotionEvent ev){
  if(ev.getAction()==MotionEvent.ACTION_DOWN){
    onUserInteraction();//该方法内部是空的,当每次传入触摸事件Action_down时,都会调用该方法,后续的action_move和action_up不会调用该方法。我们可以重载该方法
  }
  if(getWindow().superDispatchTouchEvent(ev)){
     return true;
  }
  return onTouchEvent(ev);
}</span>
Activity将触摸分发给了DecorView(ViewGroup),DecorView在将触摸分发该ViewGroup或View,ViewGroup将触摸分发到onInterceptTouchEvent函数中,若该函数返回true,那么后续的触摸事件就直接由ViewGroup的onTouchEvent处理,不会传到ViewGroup中的View了,若返回的是false,那么进一步将触摸传到View中。在View中再执行dispatchTouchEvent。
<span style="font-size:14px;">//View.java
public boolean dispatchTouchEvent(MotionEvent event){
  ...
  if(li!=null && li.mOnTouchListener!=null &&...&& li.mOnTouchListener.onTouch(this,event)){
    result=true;
  }
  if(!result && onTouchEvent(event)){
    result=true;
  }
  ...
}</span>
若View的onTouchListener不为空,执行onTouchListener的onTouch,否则执行View的onToucEvent函数。我们在MyPullToRefreshView中重载onInterceptTouchEvent函数。若onInterceptTouchEvent返回true,那么后续的触摸将在ViewGroup的onTouchEvent函数中处理了,不会分发给ViewGroup中的HeaderView,ListView和FooterView了。

我们现在需要先分析清楚在什么情况下需要拦截触摸,不让触摸传递子View中。

1.滑动位移过小,小于5,那么不会拦截触摸。

2.当ListView未被滑到最顶端或最底端的情况下,是不需要拦截触摸的,这时要让ListView可以自由滑动。

3.当ListView被滑动到最顶端,并且继续滑动的方向是向下的,那么就需要拦截触摸,然后在ViewGroup的onTouchEvent中使HeaderView向下移动。

4.当Listview被滑动到最底端,并且继续滑动的方向是向上的,那么就需要拦截触摸,然后在VrewGroup的onTouchEvent中使FooterView向上移动。

在这里我们要讨论一下填充控件的滑动视图ListView和ScrollView。

<span style="font-size:14px;">private AdapterView<?> mAdapterView;
private ScrollView mSrollView;</span>
在onFinshInflate函数中实例化:
<span style="font-size:14px;">@Override
protected void onFinishInflate(){
  ...
  int childCount=this.getChildCount();
  if(childCount<3){
	//IllegalArgumentException继承自RuntimeException
	throw new IllegalArgumentException("this layout must contain 3 child views,and AdapterView or"
				+ " ScrollView must in the second position! ");
    }
    //instanceof是一个二元操作符,作用是判断操作符左侧的对象是否是右侧的类的实例
	if(this.getChildAt(1) instanceof AdapterView<?>){
		mAdapterView=(AdapterView<?>) this.getChildAt(1);
	}
	else if(this.getChildAt(1) instanceof ScrollView){
		mScrollView=(ScrollView) this.getChildAt(1);
	}
	if(mAdapterView==null && mScrollView==null){
		throw new IllegalArgumentException("must contain a AdapterView or ScrollView in the layout");
	}
}</span>
在onInterceptTouchEvent中对ACTION_DOWN不拦截,我们需要拦截的是上述几种情况下的ACTION_MOVE,首先ACTION_DOWN会被传到View中处理,后续的不满足以上情况要求的部分ACTION_MOVE也会传到View中处理。一旦我们在onInterceptTouchEvent中拦截了ACTION_MOVE,那么之前处理触摸事件的view会接收到ACTION_CANCEL消息,之后所有的ACTION_MOVE全部直接被传到ViewGroup的onTouchEvent中,不会再到onInterceptTouchEvent中判读是否需要拦截了。

<span style="font-size:14px;">public boolean onInterceptTouchEvent(MotionEvent event){
  int y=(int)e.getRawY();                       //getRawX/getRawY是相对于屏幕的绝对距离,getX/getY是相对于View的相对距离
  switch(e.getAction){
  case MotionEvent.ACTION_DOWN:
     mLastActionDown=y;                         //mLastActionDown记录了上一次ACTION_DOWN的Y轴值
     break;
  case MotionEvent.ACTION_MOVE:
     int delta=y-mLastActionDown;
	 if(delta>=-5 && delta<=5){return false;}   //滑动位移过小,不消费该触摸
	 if(isRefreshViewScroll(delta)){            //在isRefreshViewSrcoll函数中判断是否符合上述情况:ListView已滑到最顶端或最底端
	   return true;
	 }
     break;
  case MotionEvent.ACTION_UP:
  case MotionEvent.ACTION_CANCEL:
     break;
  }
  return false;
}</span>
在下拉和上拉的过程中都有三个状态:

       1.下拉时HeaderView未完全显示出来,此时释放不会导致刷新;

        2.下拉时HeaderView已完全显示出来,此时释放会导致刷新;

         3.释放,正在刷新。

在滑动方向上又分下拉刷新和上拉加载两种。

private static int PULL_DOWN_STATE=0;  //两种方向

private static int PULL_UP_STATE=1;

pribate int mPullState;//当前滑动的方向

private static int PULL_TO_REFRESH=2;//滑动时状态

private static int RELEASE_TO_REFRESH=3;

private static int REFRESHING=4;

private int mHeaderState;//Headerview当前的状态

private int mFooterState;//FooterView当前的状态

<span style="font-size:14px;">boolean isRefreshViewScroll(int delta){
  if(mHeaderState==REFRESHING || mFooterState==REFRESHING){
     return false;  //正在刷新时是否可以滑动控件识实际情况而定。
  }
  if(delta>0){
     mPullState=PULL_DOWN_STATE;
  }else{
     mPullState=PULL_UP_STATE;
  }
  if(mAdapterView!=NULL){
    if(delta>0){
	   View child=mAdapterView.getChildAt(0);
	   if(child==null){
	     //listview中无数据,不拦截
		 return false;
	   }
	   if(child.getTop()==0 && mAdapterView.getFirstVisiblePosition()==0){
	     mPullState=PULL_DOWN_STATE;
		 return ture;
	   }
	   //如果设置了ListView的padding或item的top,在此处添加对其的处理
	}
	else if(delta<0){
		View lastChild=mAdapterView.getChildAt(mAdapterView.getChildCount()-1);
        if(lastChild==null){
		   return false;
		}
		if(lastChild.getBottom()<=getHeight() mAdapterView.getLastVisiblePosition()==mAdapterView.getChildCount()-1){
		   mPullState=PULL_UP_STATE;
		   return true;
		}
		//如果设置了ListView的padding或item的bottom,在此处添加对其的处理
	}
  }
  if(mScrollView!=null){
     if(delta>0 && mScrollView.getScrollY()==0){
	   mPullState=PULL_UP_STATE;
	   return true;
	 }
	 else if(delta<0 && mScrollView.getScrollY<=getHeight()-mScrollView.getChildAt(0).getHeight()){
	   mPullState=PULL_DOWN_STATE;
	   return true;
	 }
  }
  return false;
}</span>
当isRefreshViewScroll返回true,那么后续的ACTION_MOVE就直接分发到ViewGroup的onTouchEvent中处理。要注意的是此时的Math.abs(ACTION_MOVE-
mLastActionDown)是大于5的。
<span style="font-size:14px;">@Override
public boolean onTouchEvent(MotionEvent event){
  int y=event.getRawY();
  switch(event.getAction()){
  case MotionEvent.ACTION_DOWN:
      //在onInterceptTouchEvent中已经记录
      break;
  case MotionEvent.ACTION_MOVE:
      int delta=y-mLastActionDown;
	  if(mPullState==PULL_DOWN_STATE){
	     headerPrepareToRefresh(delta);
	  }
	  else if(mPullState==PULL_UP_STATE){
	     footerPrepareToRefresh(delta)	  
	  }
	  mLastActionDown=y;
      break;
  case MotionEvent.ACTION_UP:
  case MotionEvent.ACTION_CANCEL:
      .....
      break;
  }
}</span>
在headerPrepareToRefresh或footerPrepareToRefresh中改变HeaderView或FooterView的位置,以及在HeaderView或FooterView中显示提示信息。

首先来看一下如何改变HeaderView或FooterView的位置。

<span style="font-size:14px;">public int changingHeaderViewTopMargin(int delta){
    LayoutParams params=(LayoutParams)mHeaderView.getLayoutParams();
	int newTopMargin=(int) (params.topMargin+deltaY*0.6);
	params.topMargin=newTopMargin;
	mHeaderView.setLayoutParams(params);
	invalidate();
	return newTopMargin;
}</span>
很简单,直接改变HeaderView的TopMargin就可以,然后调用invalidate来进行重绘。

当HeaderView完全显示出来后要将Headerview中的箭头旋转向上,此时通过旋转动画RotateAnimation来实现。

我们在构造函数中调用init来初始化动画资源。

<span style="font-size:14px;">public void init(){
	inflater=LayoutInflater.from(mContext);
	mFlipAnimation=new RotateAnimation(0, 180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
	mFlipAnimation.setInterpolator(new LinearInterpolator());
	mFlipAnimation.setDuration(250);
	mFlipAnimation.setFillAfter(true);//这样动画播放完会停留在最后一帧
	mReverseFlipAnimation=new RotateAnimation(180,0,Animation.RELATIVE_TO_SELF,0.5f,Animation.RELATIVE_TO_SELF,0.5f);
	mReverseFlipAnimation.setInterpolator(new LinearInterpolator());
	mReverseFlipAnimation.setDuration(250);
	mReverseFlipAnimation.setFillAfter(true);
	addHeaderView();//在构造函数中addView,可以保证是第一个添加到LinearLayout中的,此时xml中设置的子View还未被添加到LinearLayout中
}

public void headerPrepareToRefresh(int delta){
	int newTopMargin=changingHeaderViewTopMargin(delta);
	//根据newTopMargin的值判断是否播放动画
	if(newTopMargin>=0 && mHeaderState!=RELEASE_TO_REFRESH){
		mHeaderText.setText("释放完成刷新");
		mHeaderImage.clearAnimation();
		mHeaderImage.startAnimation(mFlipAnimation);
		mHeaderState=RELEASE_TO_REFRESH;
	}else if(newTopMargin<0 && mHeaderState!=PULL_TO_REFRESH){
		mHeaderText.setText("下拉刷新");
		mHeaderImage.clearAnimation();
		mHeaderImage.startAnimation(mReverseFlipAnimation);
		mHeaderState=PULL_TO_REFRESH;
	}
}
public void footerPrepareToRefresh(int delta){
	int newTopMargin=changingHeaderViewTopMargin(delta);
	//根据newTopMargin的值判断是否播放动画
	if(Math.abs(newTopMargin)>=(mHeaderHeight+mFooterHeight) && mFooterState!=RELEASE_TO_REFRESH){
		mFooterText.setText("释放完成刷新");
		mFooterState=RELEASE_TO_REFRESH;
	}
	else if(Math.abs(newTopMargin)<(mHeaderHeight+mFooterHeight) && mFooterState!=PULL_TO_REFRESH){
		mFooterText.setText("上拉刷新");
		mFooterState=PULL_TO_REFRESH;
	}
}</span>
当滑动释放时,会分发ACTION_UP到ViewGroup的onTouchEvent中,此时我们就要进入更新状态了。
<span style="font-size:14px;">@Override
public boolean onTouchEvent(MotionEvent event){
  int y=event.getRawY();
  switch(event.getAction()){
  case MotionEvent.ACTION_DOWN:
      //在onInterceptTouchEvent中已经记录
      break;
  case MotionEvent.ACTION_MOVE:
      ....
      break;
  case MotionEvent.ACTION_UP:
  case MotionEvent.ACTION_CANCEL:
      //首先要通过TopMargin来查看HeaderView或FooterView是否显示完全了,否则不刷新
	  int topMargin=mHeaderView.getTopMargin();
	  if(topMargin>0 && mPullState==PULL_DOWN_STATE){
	     onHeaderRefreshing();
	  }
	  else if(topMargin<-mHeaderHeight-mFooterHeight && mPullState==PULL_UP_STATE){
	     onFooterRefreshing();
	  }else{
	     mHeaderView.setTopMargin(-mHeaderHeight);//不刷新
	  }
      break;
  }
}</span>
在onHeaderRefreshing/onFooterRefreshing中要完成显示加载进度,然后调用接口中的函数去执行一些耗时任务。

<span style="font-size:14px;">public void onHeaderRefreshing(){
	mHeaderState=REFRESHING;
	setHeaderTopMargin(0);
	mHeaderImage.setVisibility(View.GONE);
	mHeaderImage.clearAnimation();
	mHeaderProgress.setVisibility(View.VISIBLE);
	mHeaderText.setText("正在刷新...");
	if(mOnHeaderViewListener!=null){
		mOnHeaderViewListener.onHeaderRefreshing(this);
	}
}
	
public void onFooterRefreshing(){
	mFooterState=REFRESHING;
	setHeaderTopMargin(-mHeaderHeight-mFooterHeight);
	mFooterImage.setVisibility(View.GONE);
	mFooterImage.clearAnimation();
	mFooterProgress.setVisibility(View.VISIBLE);
	mFooterText.setText("正在加载中...");
	if(mOnFooterViewListener!=null){
		mOnFooterViewListener.onFooterRefreshing(this);
	}
}</span>

模块三:接口设计

我们的下拉刷新控件是观察者模式中的主题,当在下拉和上拉释放时会通知观察者。观察者会进行一些耗时处理,然后回调主题,通知其完成刷新。

1.创建接口类(该接口类可以作为控件的内部类)

<span style="font-size:14px;">interface OnHeaderViewListener{
   public void onHeaderRefreshing(MyPullToRefreshView v);
}
interface OnFooterViewListener{
   public void onFooterRefreshing(MyPullToRefreshView v);
}</span>
2.在控件内声明用于外界添加监听的函数
<span style="font-size:14px;">        //首先创建保存监听器(观察者)的变量
	private OnHeaderViewListener mOnHeaderViewListener;
	private OnFooterViewListener mOnFooterViewListener;
	//外界可通过该函数来添加观察者
	public void setOnHeaderViewListenr(OnHeaderViewListener listener){
	   mOnHeaderViewListener=listener;
	}
	public void setOnFooterViewListener(OnFooterViewListener listener){
	   mOnFooterViewListener=listener;
	}</span>
3.控件在刷新时通知外界执行一些耗时任务
<span style="font-size:14px;">if(mOnHeaderViewListener!=null){
	   mOnHeaderViewListener.onHeaderRefreshing(this);//将事件源封装在参数中,传给外界,让外界完成耗时任务后回调MyPullToRefreshView.onRefreshComplete结束刷新。
	}
	if(mOnFooterViewListener!=null){
	   mOnFooterViewListener.OnFooterViewListener(this);
	}</span>
当耗时任务完成后,需要回调MyPullToRefreshView的onHeaderRefreshComplete/onFooterRefreshComplete函数来完成刷新。
<span style="font-size:14px;">public void onHeaderRefreshComplete() { // 完成刷新
	setHeaderTopMargin(-mHeaderViewHeight);
	mHeaderImage.setVisibility(View.VISIBLE);
	mHeaderImage.setImageResource(R.drawable.ic_pulltorefresh_arrow);
	mHeaderText.setText(R.string.pull_to_refresh_pull_label);
	mHeaderProgressBar.setVisibility(View.GONE);
	mHeaderState = PULL_TO_REFRESH;
	if (mScrollView != null) {
		mScrollView.smoothScrollTo(0, 0);
	}
}
public void onFooterRefreshComplete() {
	setHeaderTopMargin(-mHeaderViewHeight);
	mFooterImage.setVisibility(View.VISIBLE);
	mFooterImage.setImageResource(R.drawable.icon_host_pull);
	mFooterText.setText(R.string.pull_to_refresh_footer_pull_label);
	mFooterProgressBar.setVisibility(View.GONE);
	mFooterState = PULL_TO_REFRESH;
}</span>
最后来看一下在Activity中的使用。
<span style="font-size:14px;">public class MainActivity extends Activity implements OnFooterViewListener,OnHeaderViewListener {
	
	private MyPullToRefreshView mPullToRefreshView;
	private ListView mListView;
	private ArrayAdapter<String> arrayAdapter;
	private String[] strs={"1111","2222","3333"};
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		init();
	}
	
	public void init(){
		mPullToRefreshView=(MyPullToRefreshView)findViewById(R.id.my_pull_to_refresh_view);
		mListView=(ListView)findViewById(R.id.listView);
		arrayAdapter=new ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,strs);
		mListView.setAdapter(arrayAdapter);
		mPullToRefreshView.setOnHeaderViewListener(this);
		mPullToRefreshView.setOnFooterViewListener(this);
	}

	@Override
	public void onFooterRefreshing(final MyPullToRefreshView v) {
		//向UI的消息队列中投递一个runnable,投递成功返回true,并不代表runnable成功执行,looper可能退出导致runnable被丢弃
		v.postDelayed(new Runnable(){
			@Override
			public void run() {
				try {
					Thread.sleep(3000);
					v.onFooterRefreshComplete();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}, 0);
	}

	@Override
	public void onHeaderRefreshing(final MyPullToRefreshView v) {
		v.postDelayed(new Runnable(){
			@Override
			public void run(){
				try {
					Thread.sleep(3000);
					v.onHeaderRefreshComplete();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		},0);
	}

}</span>


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值