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>
时间: 2024-10-12 15:04:30

Android PullToRefreshView自定义下拉刷新控件的相关文章

自定义下拉刷新控件

一.功能效果 1.在很多app中,在信息展示页面,当我们向下拖拽时,页面会加载最新的数据,并有一个短暂的提示控件出现,有些会有加载进度条,有些会记录加载日期.条目,有些还带有加载动画.其基本实现原理都相仿,本文中将探讨其实现原理,并封装出一个简单的下拉刷新控件 2.自定义刷新工具简单的示例 二.系统提供的下拉刷新工具 1.iOS6.0以后系统提供了自己的下拉刷新的控件:UIRefreshControl .例如,refreshControl,作为UITableViewController中的一个属

Android——谷歌官方下拉刷新控件SwipeRefreshLayout(转)

转自:http://blog.csdn.net/zouzhigang96/article/details/50476402 版权声明:本文为博主原创文章,未经博主允许不得转载. 前言: 如今谷歌推出了更官方的下拉刷新控件, 这无疑是对安卓开发人员来说是个好消息,很方便的使用这个SwipeRefreshLayout控件实现下拉刷新功能.Android4.0以下的版本需要用到 Android-support-v4.jar包才能用到 android-support-v4.jar 包下载地址:http:

Android SwipeRefreshLayout 官方下拉刷新控件介绍

转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/24521483 以下App基本都有下拉刷新的功能,曾经基本都使用XListView或者自己写一个下拉刷新,最近Google提供了一个官方的下拉刷新控件SwipeRefreshLayout,我感觉还不错啊,见惯了传统的下拉刷新,这个反而给人耳目一新的感觉(貌似知乎的APP已经使用这样的下拉刷新了). Google也在官方站点给出了V4的兼容包: 顺便看一眼API呗: 和XlistV

自定义下拉刷新控件-CBStoreHouseRefreshControl

本文转载至 http://www.cocoachina.com/ios/20141110/10177.html iOS开发自定义刷新CBStoreHouseRefres 介绍 这是一款在Storehouse启发下创作出来的控件,下拉刷新的时候可以完全定制自己想要的效果.来看效果图: 通过plist文件你可以使用任何想要的形状,下面这张是作者所在公司的logo: 安装 CBStoreHouseRefreshControl依赖于CocoaPods,通过在你的Podfile中添加下面这行命令来安装:

Android SwipeRefreshLayout 官方下拉刷新控件介绍—Handler原理—Adapter总结

參考博客:http://blog.csdn.net/lmj623565791/article/details/24521483 自己敲了下代码,这个方式刷新确实给人耳目一新的感觉.资源:含有两个SwipeRefreshLayout实战的Demo,http://download.csdn.net/detail/itjavawfc/8847339 用起来很好用:看看文档几个重要的方法,简单拿来用没有不论什么问题. watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZX

Android下拉刷新控件SwipeRefreshLayout源码浅析

SwipeRefreshLayout是Android官方的下拉刷新控件,使用简单,界面美观,不熟悉的朋友可以随便搜索了解一下,这里就不废话了,直接进入正题. 这种下拉刷新控件的原理不难,基本就是监听手指的运动,获取手指的坐标,通过计算判断出是哪种操作,然后就是回调相应的接口了.SwipeRefreshLayout是继承自ViewGroup的,根据Android的事件分发机制,触摸事件应该是先传递到ViewGroup,根据onInterceptTouchEvent的返回值决定是否拦截事件的,那么就

[Android]下拉刷新控件RefreshableView的实现

需求:自定义一个ViewGroup,实现可以下拉刷新的功能.下拉一定距离后(下拉时显示的界面可以自定义任何复杂的界面)释放手指可以回调刷新的功能,用户处理完刷新的内容后,可以调用方法onCompleteRefresh()通知刷新完毕,然后回归正常状态.效果如下:     源代码:RefreshableView(https://github.com/wangjiegulu/RefreshableView) 分析: 我们的目的是不管什么控件,只要在xml中外面包一层标签,那这个标签下面的所有子标签所

Android 解决下拉刷新控件和ScrollVIew的滑动冲突问题。

最近项目要实现ScrollView中嵌套广告轮播图+RecyleView卡片布局,并且RecyleView按照header和内容的排列样式,因为RecyleView的可扩展性很强,所以我毫无疑问的选择了它,而且让RecyleView实现了可拖拽的效果, 最后我再加上了下拉刷新的效果(这里我用的下拉刷新控件是三方的SmartRefreshLayout).记得刚开始实现这个效果的时候还是十分的得心印手.可是当我测试的时候,发现RecyleView的子item的拖拽效果并不流畅,起初我以 为是由于Re

Android下拉刷新控件--PullToRefresh的简单使用

Android中很多时候都会用到上下拉刷新,这是一个很常用的功能,Android的v4包中也为我们提供了一种原生的下拉刷新控件--SwipeRefreshLayout,可以用它实现一个简洁的刷新效果,但今天我们的主角并不是它,而是一个很火的第三方的上下拉刷新控件--PullToRefresh.PullToRefresh包括PullToRefreshScrollView.PullToRefreshListView.PullToRefreshGridView等等很多为我们提供的控件,我们可以在xml