GVKun编程网logo

Android 使用SwipeRefreshLayout控件仿抖音做的视频下拉刷新效果(android下拉刷新框架)

20

在这里,我们将给大家分享关于Android使用SwipeRefreshLayout控件仿抖音做的视频下拉刷新效果的知识,让您更了解android下拉刷新框架的本质,同时也会涉及到如何更有效地Andro

在这里,我们将给大家分享关于Android 使用SwipeRefreshLayout控件仿抖音做的视频下拉刷新效果的知识,让您更了解android下拉刷新框架的本质,同时也会涉及到如何更有效地Android PullToRefreshLayout下拉刷新控件的终结者、Android RefreshLayout实现下拉刷新布局、Android SwipeRefreshLayout、android SwipeRefreshLayout 与 ListView 结合实现下拉刷新,加载功能的内容。

本文目录一览:

Android 使用SwipeRefreshLayout控件仿抖音做的视频下拉刷新效果(android下拉刷新框架)

Android 使用SwipeRefreshLayout控件仿抖音做的视频下拉刷新效果(android下拉刷新框架)

SwipeRefreshLayout(这个控件),我先跟大家介绍一下这个控件:

一、SwipeRefreshLayout简单介绍

•先看以下官方文档,已有了很详细的描述了。

官方文档说明

•这里我再大概解释一下:

•在竖直滑动时想要刷新页面可以用SwipeRefreshLayout来实现。它通过设置OnRefreshListener来监听界面的滑动从而实现刷新。也可以通过一些方法来设置SwipeRefreshLayout是否可以刷新。如:setRefreshing(true),展开刷新动画。
setRefreshing(false),取消刷新动画。setEnable(true)下拉刷新将不可用。

•使用这个布局要想达到刷新的目的,需要在这个布局里包裹可以滑动的子控件,如ListView等,并且只能有一个子控件。

•介绍总结:使用SwipeRefreshLayout可以实现下拉刷新,前提是布局里需要包裹一个可以滑动的子控件,然后在代码里设置OnRefreshListener设置监听,最后在监听里设置刷新时的数据获取就可以了。由于是新出来的东西,所以要想使用,先把support library的版本升级到19.1或更新。

二、SwipeRefreshLayout主要方法介绍

翻看官方的文档,可以看到方法有很多,这里只介绍五个经常用到的方法。

•isRefreshing()
•判断当前的状态是否是刷新状态。

•setColorSchemeResources(int... colorResIds)

•设置下拉进度条的颜色主题,参数为可变参数,并且是资源id,可以设置多种不同的颜色,每转一圈就显示一种颜色。

•setonRefreshListener(SwipeRefreshLayout.OnRefreshListener listener)

•设置监听,需要重写onRefresh()方法,顶部下拉时会调用这个方法,在里面实现请求数据的逻辑,设置下拉进度条消失等等。

•setProgressBackgroundColorSchemeResource(int colorRes)

•设置下拉进度条的背景颜色,默认白色。

•setRefreshing(boolean refreshing)

•设置刷新状态,true表示正在刷新,false表示取消刷新。

那么话就不多说了,直接开始了:

效果图:

activity_listview布局文件

<android.support.v4.widget.SwipeRefreshLayout 
       android:id="@+id/sr1" 
       android:layout_width="match_parent" 
      android:layout_height="match_parent"> 
       <ListView 
         android:id="@+id/lv" 
         android:layout_width="match_parent" 
         android:layout_height="wrap_content"/> 
    </android.support.v4.widget.SwipeRefreshLayout> 

Activity代码(ListViewActivity)

public class ListViewActivity extends AppCompatActivity implements SwipeRefreshLayout.OnRefreshListener { 
  private SwipeRefreshLayout swipeRefreshLayout; 
  private ListView listView; 
  private List<String> list; 
  private ArrayAdapter adapter; 
  @Override 
  protected void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.activity_list_view); 
    swipeRefreshLayout = (SwipeRefreshLayout) findViewById(R.id.sr1); 
    swipeRefreshLayout.setonRefreshListener(this); 
    list = new ArrayList<>(); 
    for(int i = 0;i<10;i++){
      list.add("这是第"+i+"个数据");
    }
    listView = (ListView) findViewById(R.id.lv); 
    adapter = new ArrayAdapter(this,android.R.layout.simple_list_item_1,android.R.id.text1,list); 
    listView.setAdapter(adapter); 
  } 
  @Override 
  public void onRefresh() { 
    new Handler().postDelayed(new Runnable() { 
      @Override 
      public void run() { 
        swipeRefreshLayout.setRefreshing(false); 
        adapter.clear(); 
        list.add("这是第11个数据"); 
        adapter.notifyDataSetChanged(); 
      } 
    },1000); 
  } 
} 

总结

以上所述是小编给大家介绍的Android 使用SwipeRefreshLayout控件仿抖音做的视频下拉刷新效果,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对编程小技巧网站的支持!

您可能感兴趣的文章:

  • Android SwipeRefreshLayout仿抖音app静态刷新

Android PullToRefreshLayout下拉刷新控件的终结者

Android PullToRefreshLayout下拉刷新控件的终结者

       说到下拉刷新控件,网上版本有很多,很多软件也都有下拉刷新功能。有一个叫XListView的,我看别人用过,没看过是咋实现的,看这名字估计是继承自ListView修改的,不过效果看起来挺丑的,也没什么扩展性,太单调了。看了QQ2014的列表下拉刷新,发现挺好看的,我喜欢,贴一下图看一下qq的下拉刷新效果:

                                          


    不错吧?嗯,是的。一看就知道实现方式不一样。咱们今天就来实现一个下拉刷新控件。由于有时候不仅仅是ListView需要下拉刷新,ExpandableListView和GridView也有这个需求,由于ListView,GridView都是AbsListView的子类,ExpandableListView是ListView的子类所以也是AbsListView的子类。所以我的思路是自定义一个对所有AbsListView的子类通用的下拉管理布局,叫PullToRefreshLayout,如果需要GridView,只需要在布局文件里将ListView换成GridView就行了,ExpandableListView也一样,不需要再继承什么GridView啊ListView啊乱七八糟的。


看上图,主要工作就是定义黑色大布局,红色部分是不下拉的时候的可见部分,可以是任意的AbsListView的子类(GridView,ListView,ExpandableListView等等)。其实我已经写好了,先看一下效果图:

正常拉法:

               

           

强迫症拉法:

                 


上面是ListView的,下面是GridView的

                  


再来看一下ExpandableListView的下拉刷新效果:

                              


可以看到,点击事件和长按事件都能正常触发而不会误触发,在使用ExpandableListView的时候需要注意禁止展开时自动滚动,否则会出现bug。后面会提供demo源码下载,可以根据自己的需求去修改。

下面讲解PullToRefreshLayout的实现,在贴完整的源码之前先理解整个类的大概思路:

public class PullToRefreshLayout extends RelativeLayout implements OnTouchListener 
{ 
  
 // 下拉的距离 
 public float moveDeltaY = 0; 
 // 是否可以下拉 
 private boolean canPull = true; 
  
  
 private void hideHead() 
 { 
  // 在这里开始异步隐藏下拉头,在松手的时候或这刷新完毕的时候隐藏 
 } 
 
  
 public void refreshFinish(int refreshResult) 
 { 
  // 完成刷新操作,显示刷新结果 
 } 
 
 private void changeState(int to) 
 { 
  // 改变当前所处的状态,有四个状态:下拉刷新、释放刷新、正在刷新、刷新完成 
 } 
 
 /* 
  * (非 Javadoc)由父控件决定是否分发事件,防止事件冲突 
  * 
  * @see android.view.ViewGroup#dispatchTouchEvent(android.view.MotionEvent) 
  */ 
 @Override 
 public boolean dispatchTouchEvent(MotionEvent ev) 
 { 
  switch (ev.getActionMasked()) 
  { 
  case MotionEvent.ACTION_DOWN: 
   /*手指按下的时候,无法判断是否将要下拉,所以这时候break让父类把down事件分发给子View 
   记录按下的坐标*/ 
   break; 
  case MotionEvent.ACTION_MOVE: 
   /*如果往上滑动且moveDetaY==0则说明不在下拉,break继续将move事件分发给子View 
   如果往下拉,则计算下拉的距离moveDeltaY,根据moveDeltaY重新Layout子控件。但是 
   由于down事件传到了子View,如果不清除子View的事件,会导致子View误触发长按事件和点击事件。所以在这里清除子View的事件回调。 
   下拉超过一定的距离时,改变当前状态*/ 
   break; 
  case MotionEvent.ACTION_UP: 
   //根据当前状态执行刷新操作或者hideHead 
  default: 
   break; 
  } 
  // 事件分发交给父类 
  return super.dispatchTouchEvent(ev); 
 } 
 
  
 
 /* 
  * (非 Javadoc)绘制阴影效果,颜色值可以修改 
  * 
  * @see android.view.ViewGroup#dispatchDraw(android.graphics.Canvas) 
  */ 
 @Override 
 protected void dispatchDraw(Canvas canvas) 
 { 
  //在这里用一个渐变绘制分界线阴影 
 } 
 
  
 
 @Override 
 protected void onLayout(boolean changed,int l,int t,int r,int b) 
 { 
  //这个方法就是重新Layout子View了,根据moveDeltaY来定位子View的位置 
 } 
 
  
 @Override 
 public boolean onTouch(View v,MotionEvent event) 
 { 
  //这个是OnTouchListener的方法,只判断AbsListView的状态来决定是否canPull,除此之外不做其他处理 
 } 
} 

可以看到,这里复写了ViewGroup的dispatchTouchEvent,这样就可以掌控事件的分发,如果不了解这个方法可以看一下这篇Android事件分发、View事件Listener全解析。之所以要控制事件分发是因为我们不可能知道手指down在AbsListView上之后将往上滑还是往下拉,所以down事件会分发给AbsListView的,但是在move的时候就需要看情况了,因为我们不想在下拉的同时AbsListView也在滑动,所以在下拉的时候不分发move事件,但这样问题又来了,前面AbsListView已经接收了down事件,如果这时候不分发move事件给它,它会触发长按事件或者点击事件,所以在这里还需要清除AbsListView消息列表中的callback。
onLayout用于重新布置下拉头和AbsListView的位置的,这个不难理解。

理解了大概思路之后,看一下PullToRefreshLayout完整的源码吧~

package com.jingchen.pulltorefresh; 
 
import java.lang.reflect.Field; 
import java.util.Timer; 
import java.util.TimerTask; 
 
import android.content.Context; 
import android.graphics.Canvas; 
import android.graphics.LinearGradient; 
import android.graphics.Paint; 
import android.graphics.Paint.Style; 
import android.graphics.RectF; 
import android.graphics.Shader.TileMode; 
import android.os.Handler; 
import android.os.Message; 
import android.util.AttributeSet; 
import android.util.Log; 
import android.view.MotionEvent; 
import android.view.View; 
import android.view.View.OnTouchListener; 
import android.view.ViewGroup; 
import android.view.animation.AnimationUtils; 
import android.view.animation.LinearInterpolator; 
import android.view.animation.RotateAnimation; 
import android.widget.AbsListView; 
import android.widget.RelativeLayout; 
import android.widget.TextView; 
 
/** 
 * 整个下拉刷新就这一个布局,用来管理两个子控件,其中一个是下拉头,另一个是包含内容的contentView(可以是AbsListView的任何子类) 
 * 
 * @author 陈靖 
 */ 
public class PullToRefreshLayout extends RelativeLayout implements OnTouchListener 
{ 
 public static final String TAG = "PullToRefreshLayout"; 
 // 下拉刷新 
 public static final int PULL_TO_REFRESH = 0; 
 // 释放刷新 
 public static final int RELEASE_TO_REFRESH = 1; 
 // 正在刷新 
 public static final int REFRESHING = 2; 
 // 刷新完毕 
 public static final int DONE = 3; 
 // 当前状态 
 private int state = PULL_TO_REFRESH; 
 // 刷新回调接口 
 private OnRefreshListener mListener; 
 // 刷新成功 
 public static final int REFRESH_SUCCEED = 0; 
 // 刷新失败 
 public static final int REFRESH_FAIL = 1; 
 // 下拉头 
 private View headView; 
 // 内容 
 private View contentView; 
 // 按下Y坐标,上一个事件点Y坐标 
 private float downY,lastY; 
 // 下拉的距离 
 public float moveDeltaY = 0; 
 // 释放刷新的距离 
 private float refreshdist = 200; 
 private Timer timer; 
 private MyTimerTask mTask; 
 // 回滚速度 
 public float MOVE_SPEED = 8; 
 // 第一次执行布局 
 private boolean isLayout = false; 
 // 是否可以下拉 
 private boolean canPull = true; 
 // 在刷新过程中滑动操作 
 private boolean isTouchInRefreshing = false; 
 // 手指滑动距离与下拉头的滑动距离比,中间会随正切函数变化 
 private float radio = 2; 
 // 下拉箭头的转180°动画 
 private RotateAnimation rotateAnimation; 
 // 均匀旋转动画 
 private RotateAnimation refreshingAnimation; 
 // 下拉的箭头 
 private View pullView; 
 // 正在刷新的图标 
 private View refreshingView; 
 // 刷新结果图标 
 private View stateImageView; 
 // 刷新结果:成功或失败 
 private TextView stateTextView; 
 /** 
  * 执行自动回滚的handler 
  */ 
 Handler updateHandler = new Handler() 
 { 
 
  @Override 
  public void handleMessage(Message msg) 
  { 
   // 回弹速度随下拉距离moveDeltaY增大而增大 
   MOVE_SPEED = (float) (8 + 5 * Math.tan(Math.PI / 2 / getMeasuredHeight() * moveDeltaY)); 
   if (state == REFRESHING && moveDeltaY <= refreshdist && !isTouchInRefreshing) 
   { 
    // 正在刷新,且没有往上推的话则悬停,显示"正在刷新..." 
    moveDeltaY = refreshdist; 
    mTask.cancel(); 
   } 
   if (canPull) 
    moveDeltaY -= MOVE_SPEED; 
   if (moveDeltaY <= 0) 
   { 
    // 已完成回弹 
    moveDeltaY = 0; 
    pullView.clearanimation(); 
    // 隐藏下拉头时有可能还在刷新,只有当前状态不是正在刷新时才改变状态 
    if (state != REFRESHING) 
     changeState(PULL_TO_REFRESH); 
    mTask.cancel(); 
   } 
   // 刷新布局,会自动调用onLayout 
   requestLayout(); 
  } 
 
 }; 
 
 public void setonRefreshListener(OnRefreshListener listener) 
 { 
  mListener = listener; 
 } 
 
 public PullToRefreshLayout(Context context) 
 { 
  super(context); 
  initView(context); 
 } 
 
 public PullToRefreshLayout(Context context,AttributeSet attrs) 
 { 
  super(context,attrs); 
  initView(context); 
 } 
 
 public PullToRefreshLayout(Context context,AttributeSet attrs,int defStyle) 
 { 
  super(context,attrs,defStyle); 
  initView(context); 
 } 
 
 private void initView(Context context) 
 { 
  timer = new Timer(); 
  mTask = new MyTimerTask(updateHandler); 
  rotateAnimation = (RotateAnimation) AnimationUtils.loadAnimation(context,R.anim.reverse_anim); 
  refreshingAnimation = (RotateAnimation) AnimationUtils.loadAnimation(context,R.anim.rotating); 
  // 添加匀速转动动画 
  LinearInterpolator lir = new LinearInterpolator(); 
  rotateAnimation.setInterpolator(lir); 
  refreshingAnimation.setInterpolator(lir); 
 } 
 
 private void hideHead() 
 { 
  if (mTask != null) 
  { 
   mTask.cancel(); 
   mTask = null; 
  } 
  mTask = new MyTimerTask(updateHandler); 
  timer.schedule(mTask,5); 
 } 
 
 /** 
  * 完成刷新操作,显示刷新结果 
  */ 
 public void refreshFinish(int refreshResult) 
 { 
  refreshingView.clearanimation(); 
  refreshingView.setVisibility(View.GONE); 
  switch (refreshResult) 
  { 
  case REFRESH_SUCCEED: 
   // 刷新成功 
   stateImageView.setVisibility(View.VISIBLE); 
   stateTextView.setText(R.string.refresh_succeed); 
   stateImageView.setBackgroundResource(R.drawable.refresh_succeed); 
   break; 
  case REFRESH_FAIL: 
   // 刷新失败 
   stateImageView.setVisibility(View.VISIBLE); 
   stateTextView.setText(R.string.refresh_fail); 
   stateImageView.setBackgroundResource(R.drawable.refresh_Failed); 
   break; 
  default: 
   break; 
  } 
  // 刷新结果停留1秒 
  new Handler() 
  { 
   @Override 
   public void handleMessage(Message msg) 
   { 
    state = PULL_TO_REFRESH; 
    hideHead(); 
   } 
  }.sendEmptyMessageDelayed(0,1000); 
 } 
 
 private void changeState(int to) 
 { 
  state = to; 
  switch (state) 
  { 
  case PULL_TO_REFRESH: 
   // 下拉刷新 
   stateImageView.setVisibility(View.GONE); 
   stateTextView.setText(R.string.pull_to_refresh); 
   pullView.clearanimation(); 
   pullView.setVisibility(View.VISIBLE); 
   break; 
  case RELEASE_TO_REFRESH: 
   // 释放刷新 
   stateTextView.setText(R.string.release_to_refresh); 
   pullView.startAnimation(rotateAnimation); 
   break; 
  case REFRESHING: 
   // 正在刷新 
   pullView.clearanimation(); 
   refreshingView.setVisibility(View.VISIBLE); 
   pullView.setVisibility(View.INVISIBLE); 
   refreshingView.startAnimation(refreshingAnimation); 
   stateTextView.setText(R.string.refreshing); 
   break; 
  default: 
   break; 
  } 
 } 
 
 /* 
  * (非 Javadoc)由父控件决定是否分发事件,防止事件冲突 
  * 
  * @see android.view.ViewGroup#dispatchTouchEvent(android.view.MotionEvent) 
  */ 
 @Override 
 public boolean dispatchTouchEvent(MotionEvent ev) 
 { 
  switch (ev.getActionMasked()) 
  { 
  case MotionEvent.ACTION_DOWN: 
   downY = ev.getY(); 
   lastY = downY; 
   if (mTask != null) 
   { 
    mTask.cancel(); 
   } 
   /* 
    * 触碰的地方位于下拉头布局,由于我们没有对下拉头做事件响应,这时候它会给咱返回一个false导致接下来的事件不再分发进来。 
    * 所以我们不能交给父类分发,直接返回true 
    */ 
   if (ev.getY() < moveDeltaY) 
    return true; 
   break; 
  case MotionEvent.ACTION_MOVE: 
   // canPull这个值在底下onTouch中会根据ListView是否滑到顶部来改变,意思是是否可下拉 
   if (canPull) 
   { 
    // 对实际滑动距离做缩小,造成用力拉的感觉 
    moveDeltaY = moveDeltaY + (ev.getY() - lastY) / radio; 
    if (moveDeltaY < 0) 
     moveDeltaY = 0; 
    if (moveDeltaY > getMeasuredHeight()) 
     moveDeltaY = getMeasuredHeight(); 
    if (state == REFRESHING) 
    { 
     // 正在刷新的时候触摸移动 
     isTouchInRefreshing = true; 
    } 
   } 
   lastY = ev.getY(); 
   // 根据下拉距离改变比例 
   radio = (float) (2 + 2 * Math.tan(Math.PI / 2 / getMeasuredHeight() * moveDeltaY)); 
   requestLayout(); 
   if (moveDeltaY <= refreshdist && state == RELEASE_TO_REFRESH) 
   { 
    // 如果下拉距离没达到刷新的距离且当前状态是释放刷新,改变状态为下拉刷新 
    changeState(PULL_TO_REFRESH); 
   } 
   if (moveDeltaY >= refreshdist && state == PULL_TO_REFRESH) 
   { 
    changeState(RELEASE_TO_REFRESH); 
   } 
   if (moveDeltaY > 8) 
   { 
    // 防止下拉过程中误触发长按事件和点击事件 
    clearContentViewEvents(); 
   } 
   if (moveDeltaY > 0) 
   { 
    // 正在下拉,不让子控件捕获事件 
    return true; 
   } 
   break; 
  case MotionEvent.ACTION_UP: 
   if (moveDeltaY > refreshdist) 
    // 正在刷新时往下拉释放后下拉头不隐藏 
    isTouchInRefreshing = false; 
   if (state == RELEASE_TO_REFRESH) 
   { 
    changeState(REFRESHING); 
    // 刷新操作 
    if (mListener != null) 
     mListener.onRefresh(); 
   } else 
   { 
 
   } 
   hideHead(); 
  default: 
   break; 
  } 
  // 事件分发交给父类 
  return super.dispatchTouchEvent(ev); 
 } 
 
 /** 
  * 通过反射修改字段去掉长按事件和点击事件 
  */ 
 private void clearContentViewEvents() 
 { 
  try 
  { 
   Field[] fields = AbsListView.class.getDeclaredFields(); 
   for (int i = 0; i < fields.length; i++) 
    if (fields[i].getName().equals("mPendingCheckForLongPress")) 
    { 
     // mPendingCheckForLongPress是AbsListView中的字段,通过反射获取并从消息列表删除,去掉长按事件 
     fields[i].setAccessible(true); 
     contentView.getHandler().removeCallbacks((Runnable) fields[i].get(contentView)); 
    } else if (fields[i].getName().equals("mTouchMode")) 
    { 
     // TOUCH_MODE_REST = -1, 这个可以去除点击事件 
     fields[i].setAccessible(true); 
     fields[i].set(contentView,-1); 
    } 
   // 去掉焦点 
   ((AbsListView) contentView).getSelector().setState(new int[] 
   { 0 }); 
  } catch (Exception e) 
  { 
   Log.d(TAG,"error : " + e.toString()); 
  } 
 } 
 
 /* 
  * (非 Javadoc)绘制阴影效果,颜色值可以修改 
  * 
  * @see android.view.ViewGroup#dispatchDraw(android.graphics.Canvas) 
  */ 
 @Override 
 protected void dispatchDraw(Canvas canvas) 
 { 
  super.dispatchDraw(canvas); 
  if (moveDeltaY == 0) 
   return; 
  RectF rectF = new RectF(0,getMeasuredWidth(),moveDeltaY); 
  Paint paint = new Paint(); 
  paint.setAntiAlias(true); 
  // 阴影的高度为26 
  LinearGradient linearGradient = new LinearGradient(0,moveDeltaY,moveDeltaY - 26,0x66000000,0x00000000,TileMode.CLAMP); 
  paint.setShader(linearGradient); 
  paint.setStyle(Style.FILL); 
  // 在moveDeltaY处往上变淡 
  canvas.drawRect(rectF,paint); 
 } 
 
 private void initView() 
 { 
  pullView = headView.findViewById(R.id.pull_icon); 
  stateTextView = (TextView) headView.findViewById(R.id.state_tv); 
  refreshingView = headView.findViewById(R.id.refreshing_icon); 
  stateImageView = headView.findViewById(R.id.state_iv); 
 } 
 
 @Override 
 protected void onLayout(boolean changed,int b) 
 { 
  if (!isLayout) 
  { 
   // 这里是第一次进来的时候做一些初始化 
   headView = getChildAt(0); 
   contentView = getChildAt(1); 
   // 给AbsListView设置OnTouchListener 
   contentView.setonTouchListener(this); 
   isLayout = true; 
   initView(); 
   refreshdist = ((ViewGroup) headView).getChildAt(0).getMeasuredHeight(); 
  } 
  if (canPull) 
  { 
   // 改变子控件的布局 
   headView.layout(0,(int) moveDeltaY - headView.getMeasuredHeight(),headView.getMeasuredWidth(),(int) moveDeltaY); 
   contentView.layout(0,(int) moveDeltaY,contentView.getMeasuredWidth(),(int) moveDeltaY + contentView.getMeasuredHeight()); 
  }else super.onLayout(changed,l,t,r,b); 
 } 
 
 class MyTimerTask extends TimerTask 
 { 
  Handler handler; 
 
  public MyTimerTask(Handler handler) 
  { 
   this.handler = handler; 
  } 
 
  @Override 
  public void run() 
  { 
   handler.sendMessage(handler.obtainMessage()); 
  } 
 
 } 
 
 @Override 
 public boolean onTouch(View v,MotionEvent event) 
 { 
  // 第一个item可见且滑动到顶部 
  AbsListView alv = null; 
  try 
  { 
   alv = (AbsListView) v; 
  } catch (Exception e) 
  { 
   Log.d(TAG,e.getMessage()); 
   return false; 
  } 
  if (alv.getCount() == 0) 
  { 
   // 没有item的时候也可以下拉刷新 
   canPull = true; 
  } else if (alv.getFirstVisiblePosition() == 0 && alv.getChildAt(0).getTop() >= 0) 
  { 
   // 滑到AbsListView的顶部了 
   canPull = true; 
  } else 
   canPull = false; 
  return false; 
 } 
} 

代码中的注释已经写的很清楚了。
既然PullToRefreshLayout已经写好了,接下来就来使用这个Layout实现下拉刷新~

首先得写个OnRefreshListener接口来回调刷新操作:

public interface OnRefreshListener { 
 void onRefresh(); 
} 

就一个刷新操作的方法,待会儿让Activity实现这个接口就可以在Activity中执行刷新操作了。

看一下MainActivity的布局:

<com.jingchen.pulltorefresh.PullToRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" 
 android:id="@+id/refresh_view" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" > 
 
 <include layout="@layout/refresh_head" /> 
  
 <!-- 支持AbsListView的所有子类 --> 
 <ListView 
  android:id="@+id/content_view" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  android:background="@color/white" 
  android:divider="@color/gray" 
  android:dividerHeight="1dp" > 
 </ListView> 
 
</com.jingchen.pulltorefresh.PullToRefreshLayout> 

PullToRefreshLayout只能包含两个子控件:refresh_head和content_view。
看一下refresh_head的布局:

<?xml version="1.0" encoding="utf-8"?> 
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
 android:id="@+id/head_view" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" 
 android:background="@color/light_blue" > 
 
 <RelativeLayout 
  android:layout_width="match_parent" 
  android:layout_height="wrap_content" 
  android:layout_alignParentBottom="true" 
  android:paddingBottom="20dp" 
  android:paddingTop="20dp" > 
 
  <RelativeLayout 
   android:layout_width="match_parent" 
   android:layout_height="wrap_content" 
   android:layout_centerInParent="true" > 
 
   <ImageView 
    android:id="@+id/pull_icon" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:layout_centerVertical="true" 
    android:layout_marginLeft="60dp" 
    android:background="@drawable/pull_icon_big" /> 
 
   <ImageView 
    android:id="@+id/refreshing_icon" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:layout_centerVertical="true" 
    android:layout_marginLeft="60dp" 
    android:background="@drawable/refreshing" 
    android:visibility="gone" /> 
 
   <TextView 
    android:id="@+id/state_tv" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:layout_centerInParent="true" 
    android:text="@string/pull_to_refresh" 
    android:textColor="@color/white" 
    android:textSize="16sp" /> 
 
   <ImageView 
    android:id="@+id/state_iv" 
    android:layout_width="wrap_content" 
    android:layout_height="wrap_content" 
    android:layout_centerVertical="true" 
    android:layout_marginRight="8dp" 
    android:layout_toLeftOf="@id/state_tv" 
    android:visibility="gone" /> 
  </RelativeLayout> 
 </RelativeLayout> 
 
</RelativeLayout> 

可以根据需要修改refresh_head的布局然后在PullToRefreshLayout中处理,但是相关View的id要和PullToRefreshLayout中用到的保持同步!

接下来是MainActivity的代码:

package com.jingchen.pulltorefresh; 
 
import java.util.ArrayList; 
import java.util.List; 
 
import android.app.Activity; 
import android.content.Context; 
import android.os.Bundle; 
import android.os.Handler; 
import android.os.Message; 
import android.support.v4.view.PagerAdapter; 
import android.support.v4.view.ViewPager; 
import android.support.v4.view.ViewPager.OnPagechangelistener; 
import android.view.LayoutInflater; 
import android.view.View; 
import android.view.View.OnClickListener; 
import android.view.ViewGroup; 
import android.view.animation.AnimationUtils; 
import android.view.animation.LinearInterpolator; 
import android.view.animation.RotateAnimation; 
import android.widget.AbsListView; 
import android.widget.AdapterView; 
import android.widget.AdapterView.OnItemClickListener; 
import android.widget.AdapterView.OnItemLongClickListener; 
import android.widget.Baseexpandablelistadapter; 
import android.widget.ExpandableListView; 
import android.widget.ExpandableListView.OnChildClickListener; 
import android.widget.ExpandableListView.OnGroupClickListener; 
import android.widget.ListView; 
import android.widget.TextView; 
import android.widget.Toast; 
 
/** 
 * 除了下拉刷新,在contenview为ListView的情况下我给ListView增加了FooterView,实现点击加载更多 
 * 
 * @author 陈靖 
 * 
 */ 
public class MainActivity extends Activity implements OnRefreshListener,OnClickListener 
{ 
 private AbsListView alv; 
 private PullToRefreshLayout refreshLayout; 
 private View loading; 
 private RotateAnimation loadingAnimation; 
 private TextView loadTextView; 
 private MyAdapter adapter; 
 private boolean isLoading = false; 
 
 @Override 
 protected void onCreate(Bundle savedInstanceState) 
 { 
  super.onCreate(savedInstanceState); 
  setContentView(R.layout.activity_main); 
  init(); 
 } 
 
 private void init() 
 { 
  alv = (AbsListView) findViewById(R.id.content_view); 
  refreshLayout = (PullToRefreshLayout) findViewById(R.id.refresh_view); 
  refreshLayout.setonRefreshListener(this); 
  initListView(); 
 
  loadingAnimation = (RotateAnimation) AnimationUtils.loadAnimation(this,R.anim.rotating); 
  // 添加匀速转动动画 
  LinearInterpolator lir = new LinearInterpolator(); 
  loadingAnimation.setInterpolator(lir); 
 } 
 
 /** 
  * ListView初始化方法 
  */ 
 private void initListView() 
 { 
  List<String> items = new ArrayList<String>(); 
  for (int i = 0; i < 30; i++) 
  { 
   items.add("这里是item " + i); 
  } 
  // 添加head 
  View headView = getLayoutInflater().inflate(R.layout.listview_head,null); 
  ((ListView) alv).addHeaderView(headView,null,false); 
  // 添加footer 
  View footerView = getLayoutInflater().inflate(R.layout.load_more,null); 
  loading = footerView.findViewById(R.id.loading_icon); 
  loadTextView = (TextView) footerView.findViewById(R.id.loadmore_tv); 
  ((ListView) alv).addFooterView(footerView,false); 
  footerView.setonClickListener(this); 
  adapter = new MyAdapter(this,items); 
  alv.setAdapter(adapter); 
  alv.setonItemLongClickListener(new OnItemLongClickListener() 
  { 
 
   @Override 
   public boolean onItemLongClick(AdapterView<?> parent,View view,int position,long id) 
   { 
    Toast.makeText(MainActivity.this,"LongClick on " + parent.getAdapter().getItemId(position),Toast.LENGTH_SHORT).show(); 
    return true; 
   } 
  }); 
  alv.setonItemClickListener(new OnItemClickListener() 
  { 
 
   @Override 
   public void onItemClick(AdapterView<?> parent," Click on " + parent.getAdapter().getItemId(position),Toast.LENGTH_SHORT).show(); 
   } 
  }); 
 } 
 
 /** 
  * GridView初始化方法 
  */ 
 private void initGridView() 
 { 
  List<String> items = new ArrayList<String>(); 
  for (int i = 0; i < 30; i++) 
  { 
   items.add("这里是item " + i); 
  } 
  adapter = new MyAdapter(this,Toast.LENGTH_SHORT).show(); 
   } 
  }); 
 } 
 
 /** 
  * ExpandableListView初始化方法 
  */ 
 private void initExpandableListView() 
 { 
  ((ExpandableListView) alv).setAdapter(new expandablelistadapter(this)); 
  ((ExpandableListView) alv).setonChildClickListener(new OnChildClickListener() 
  { 
 
   @Override 
   public boolean onChildClick(ExpandableListView parent,View v,int groupPosition,int childPosition," Click on group " + groupPosition + " item " + childPosition,Toast.LENGTH_SHORT).show(); 
    return true; 
   } 
  }); 
  ((ExpandableListView) alv).setonItemLongClickListener(new OnItemLongClickListener() 
  { 
 
   @Override 
   public boolean onItemLongClick(AdapterView<?> parent,Toast.LENGTH_SHORT).show(); 
    return true; 
   } 
  }); 
  ((ExpandableListView) alv).setonGroupClickListener(new OnGroupClickListener() 
  { 
 
   @Override 
   public boolean onGroupClick(ExpandableListView parent,long id) 
   { 
    if (parent.isGroupExpanded(groupPosition)) 
    { 
     // 如果展开则关闭 
     parent.collapseGroup(groupPosition); 
    } else 
    { 
     // 如果关闭则打开,注意这里是手动打开不要默认滚动否则会有bug 
     parent.expandGroup(groupPosition); 
    } 
    return true; 
   } 
  }); 
 } 
 
 @Override 
 public void onRefresh() 
 { 
  // 下拉刷新操作 
  new Handler() 
  { 
   @Override 
   public void handleMessage(Message msg) 
   { 
    refreshLayout.refreshFinish(PullToRefreshLayout.REFRESH_SUCCEED); 
   } 
  }.sendEmptyMessageDelayed(0,5000); 
 } 
 
 @Override 
 public void onClick(View v) 
 { 
  switch (v.getId()) 
  { 
  case R.id.loadmore_layout: 
   if (!isLoading) 
   { 
    loading.setVisibility(View.VISIBLE); 
    loading.startAnimation(loadingAnimation); 
    loadTextView.setText(R.string.loading); 
    isLoading = true; 
   } 
   break; 
  default: 
   break; 
  } 
 
 } 
 
 class expandablelistadapter extends Baseexpandablelistadapter 
 { 
  private String[] groupsstrings;// = new String[] { "这里是group 0",// "这里是group 1","这里是group 2" }; 
  private String[][] groupItems; 
  private Context context; 
 
  public expandablelistadapter(Context context) 
  { 
   this.context = context; 
   groupsstrings = new String[8]; 
   for (int i = 0; i < groupsstrings.length; i++) 
   { 
    groupsstrings[i] = new String("这里是group " + i); 
   } 
   groupItems = new String[8][8]; 
   for (int i = 0; i < groupItems.length; i++) 
    for (int j = 0; j < groupItems[i].length; j++) 
    { 
     groupItems[i][j] = new String("这里是group " + i + "里的item " + j); 
    } 
  } 
 
  @Override 
  public int getGroupCount() 
  { 
   return groupsstrings.length; 
  } 
 
  @Override 
  public int getChildrenCount(int groupPosition) 
  { 
   return groupItems[groupPosition].length; 
  } 
 
  @Override 
  public Object getGroup(int groupPosition) 
  { 
   return groupsstrings[groupPosition]; 
  } 
 
  @Override 
  public Object getChild(int groupPosition,int childPosition) 
  { 
   return groupItems[groupPosition][childPosition]; 
  } 
 
  @Override 
  public long getGroupId(int groupPosition) 
  { 
   return groupPosition; 
  } 
 
  @Override 
  public long getChildId(int groupPosition,int childPosition) 
  { 
   return childPosition; 
  } 
 
  @Override 
  public boolean hasstableIds() 
  { 
   return true; 
  } 
 
  @Override 
  public View getGroupView(int groupPosition,boolean isExpanded,View convertView,ViewGroup parent) 
  { 
   View view = LayoutInflater.from(context).inflate(R.layout.list_item_layout,null); 
   TextView tv = (TextView) view.findViewById(R.id.name_tv); 
   tv.setText(groupsstrings[groupPosition]); 
   return view; 
  } 
 
  @Override 
  public View getChildView(int groupPosition,boolean isLastChild,null); 
   TextView tv = (TextView) view.findViewById(R.id.name_tv); 
   tv.setText(groupItems[groupPosition][childPosition]); 
   return view; 
  } 
 
  @Override 
  public boolean isChildSelectable(int groupPosition,int childPosition) 
  { 
   return true; 
  } 
 
 } 
 
} 

在MainActivity中判断contentView是ListView的话给ListView添加了FooterView实现点击加载更多的功能。这只是一个演示PullToRefreshLayout使用的demo,可以参照一下修改。我已经在里面写了ListView,GridView和ExpandableListView的初始化方法,根据自己使用的是哪个来调用吧。那么这是ListView的下拉刷新和加载更多。如果我要GridView也有下拉刷新功能呢?那就把MainActivity的布局换成这样:

<com.jingchen.pulltorefresh.PullToRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" 
 android:id="@+id/refresh_view" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" > 
 
 <include layout="@layout/refresh_head" /> 
 <!-- 支持AbsListView的所有子类 --> 
 <GridView 
  android:id="@+id/content_view" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  android:background="@color/white" 
  android:columnWidth="90dp" 
  android:gravity="center" 
  android:horizontalSpacing="10dp" 
  android:numColumns="auto_fit" 
  android:stretchMode="columnWidth" 
  android:verticalSpacing="15dp" > 
 </GridView> 
 
</com.jingchen.pulltorefresh.PullToRefreshLayout> 

如果是ExpandableListView则把布局改成这样:

<com.jingchen.pulltorefresh.PullToRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" 
 android:id="@+id/refresh_view" 
 android:layout_width="match_parent" 
 android:layout_height="match_parent" > 
 
 <include layout="@layout/refresh_head" /> 
 <!-- 支持AbsListView的所有子类 --> 
 <ExpandableListView 
  android:id="@+id/content_view" 
  android:layout_width="match_parent" 
  android:layout_height="match_parent" 
  android:background="@color/white" > 
 </ExpandableListView> 
 
</com.jingchen.pulltorefresh.PullToRefreshLayout> 

怎么样?很简单吧?简单易用,不用再去继承修改了。

本文已经被整理到《Android下拉刷新上拉加载效果》,欢迎大家学习研究。

希望本文所述对大家学习Android下拉刷新控件有所帮助。

Android RefreshLayout实现下拉刷新布局

Android RefreshLayout实现下拉刷新布局

项目中需要下拉刷新的功能,但是这个View不是ListView这类的控件,需要ViewGroup实现这个功能,一开始网上大略找了一下,没发现特别合适的,代码也是没怎么看懂,所以决定还是自己写一个。

  于是翻出XlistView的源码看是一点一点看,再大致理解了XLisview源码,终于决定自己动手啦

  为了省事,headView还是用了XListView的HeadView,省了很多事:)

  下拉刷新,下拉刷新,肯定是先实现下拉功能,最开始我是打算通过 extends ScrollView 来实现,因为有现成的滚动效果嘛,可是实际因为两个原因放弃了:

1、ScrollView下只能有一个子控件View,虽然在    Scroll下添加一个ViewGroup,然后讲headView动态添加进前面的ViewGroup,但是我还是比较习惯studio的可视化预览,总觉得不直观!

2、  ScrollView内嵌ListView时会发生    冲突,还需要去重写ListView。于是放弃换个思路!

 关于上面的原因1:动态添加headView进ScrollView的中GroupView中,可以在重写ScrollView的onViewAdded()方法,将初始化时解析的headView添加进子GroupView

@Override 
public void onViewAdded(View child) { 
  super.onViewAdded(child); 
  //因为headView要在最上面,最先想到的就是Vertical的LinearLayout 
  LinearLayout linearLayout = (LinearLayout) getChildAt(0); 
  linearLayout.addView(view,0); 
} 

  换个思路,通过extends LinearLayout来实现吧!
先做准备工作,我们需要一个HeaderView以及要获取到HeaderView的高度,还有初始时Layout的高度

private void initView(Context context) { 
   mHeaderView = new SRefreshHeader(context); 
   mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content); 
   setorientation(VERTICAL); 
   addView(mHeaderView,0); 
   getHeaderViewHeight(); 
   getViewHeight(); 
 } 

mHeaderView = new SRefreshHeader(context);
通过构造方法实例化HeaderView

mHeaderViewContent = (RelativeLayout)

mHeaderView.findViewById(R.id.slistview_header_content);

 这是解析headerView内容区域iew,等会儿要获取这个view的高度,你肯定会问为啥不用上面的mHeaderView来获取高度,点进构造方法里可以看到如下代码

// 初始情况,设置下拉刷新view高度为0 
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,0); 
mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout,null); 
w(mContainer,lp); 

如果直接获取mHeaderView的高度 那肯定是0
getHeaderViewHeight();
getViewHeight();

分别是获取HeaderView的高度和Layout的初始高度

/** 
  * 获取headView高度 
  */ 
  private void getHeaderViewHeight() { 
    ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); 
    vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 
      @Override 
      public void onGlobalLayout() { 
        mHeaderViewHeight = mHeaderViewContent.getHeight(); 
        mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
      } 
    }); 
  } 
 
  /** 
  * 获取SRefreshLayout当前实例的高度 
  */ 
  private void getViewHeight() { 
    ViewTreeObserver thisView = getViewTreeObserver(); 
    thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 
      @Override 
      public void onGlobalLayout() { 
        SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight(); 
        SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
      } 
    }); 
  } 

准备工作完成了,接下来就是要成下拉操作了
到这里,肯定一下就想到了onTouchEvent()方法,是的!现在就开始在这里施工

实现下拉一共 会经历三个过程
ACTION_UP→ACTION_MOVE→ACTION_UP
在ACTION_UP事件中,也就是手指按下的时候,我们需要做的只是记录按下时候的坐标

switch (ev.getAction()) { 
      case MotionEvent.ACTION_DOWN: 
        //记录起始高度 
        mLastY = ev.getRawY();//记录按下时的Y坐标 
        break; 

然后就是ACTION_MOVE事件了,这里是最重要的,因为下拉时HeadView和Layout的高度变化都在这里进行

case MotionEvent.ACTION_MOVE: 
       if (!isRefreashing) 
         isRefreashing = true; 
       final float deltaY = ev.getRawY() - mLastY; 
       mLastY = ev.getRawY(); 
       updateHeaderViewHeight(deltaY / 1.8f);//按一定比例缩小移动距离 
       updateHeight(); 
       break; 

里面的updateHeaderViewHeight和updateHeight分别是改变HeaderView的高度和Layout的高度

 private void updateHeight() { 
    ViewGroup.LayoutParams lp = getLayoutParams(); 
    //更新当前layout实例高度为headerView高度加上最初的layout高度 
    //如果不更新layout 会造成内容高度压缩 无法保持比例 
    lp.height = (mHeight + mHeaderView.getVisiableHeight()); 
    setLayoutParams(lp); 
  } 
 
  private void updateHeaderViewHeight(float space) { 
//    if (space < 0) 
//      space = 0; 
//    int factHeight = (int) (space - mHeaderViewHeight); 
    if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) { 
      //如果不处于刷新中同时如果高度 
      if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_norMAL) { 
        mHeaderView.setState(SRefreshHeader.STATE_norMAL); 
      } 
      if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) { 
        mHeaderView.setState(SRefreshHeader.STATE_READY); 
      } 
    } 
    mHeaderView.setVisiableHeight((int) space 
        + mHeaderView.getVisiableHeight()); 
  } 

更新Header高度时,通过下拉的距离来判断是否到达刷新的距离,上面代码中我设定的是到达mHeaderView初始高度的两倍,就进入“释放刷新”的状态,如果没有达到则保持“下拉刷新”的状态
HeaderView中的状态一共设定了3个分别是

public final static int STATE_norMAL = 0;//下拉刷新 
 public final static int STATE_READY = 1;//释放刷新 
 public final static int STATE_REFRESHING = 2;//刷新中 

更新高度的方法headerView和layout都是相同的,就是原高度加上移动的距离重新赋给headerView或者layout

mHeaderView.setVisiableHeight((int) space 
               + mHeaderView.getVisiableHeight()); 

最后就是ACTION_UP事件了就是手指离开屏幕的时候,在这里我们需要根据headerView目前状态来决定headerView的最终状态!

case MotionEvent.ACTION_UP: 
        //松开时 
        //避免点击事件触发 
        if (!isRefreashing) 
          break; 
        //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态 
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { 
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); 
        } 
        //根据状态重置SrefreshLayout当前实例和headView高度 
        resetHeadView(mHeaderView.getStatus()); 
        reset(mHeaderView.getStatus()); 
        mLastY = -1;//重置坐标 
        break; 

resetHeadView和reset分别是重置headerView高度和layout高度的方法

private void reset(int status) { 
    ViewGroup.LayoutParams lp = getLayoutParams(); 
    switch (status) { 
      case SRefreshHeader.STATE_REFRESHING: 
        lp.height = mHeight + mHeaderViewHeight; 
        break; 
      case SRefreshHeader.STATE_norMAL: 
        lp.height = mHeight; 
        break; 
    } 
    setLayoutParams(lp); 
  } 
 
  private void resetHeadView(int status) { 
    switch (status) { 
      case SRefreshHeader.STATE_REFRESHING: 
        mHeaderView.setVisiableHeight(mHeaderViewHeight); 
        break; 
      case SRefreshHeader.STATE_norMAL: 
        mHeaderView.setVisiableHeight(0); 
        break; 
    } 
  } 

实现方式也是一样的。根据状态来判断,如果是处于刷新中,那headerView应该正常显示,并且高度是初始的高度,如果处于norMAL,也就是"下拉刷新"状态,那么说未触发刷新,重置时,headerView应该被隐藏,也就是高度重置为0

到这里下拉刷新操作也基本完成了,还需要加一个回调接口进行通知

interface OnRefreshListener { 
    void onRefresh(); 
  } 

case MotionEvent.ACTION_UP: 
        //松开时 
        //避免点击事件触发 
        if (!isRefreashing) 
          break; 
        //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态 
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { 
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); 
          if (mOnRefreshListener != null) 
            mOnRefreshListener.onRefresh(); 
        } 
        //根据状态重置SrefreshLayout当前实例和headView高度 
        resetHeadView(mHeaderView.getStatus()); 
        reset(mHeaderView.getStatus()); 
        mLastY = -1;//重置坐标 
        break; 

好,到这里就基本完成了,试试效果吧。咦,发现一个问题,嵌套ListView的时候为什么这个Layout不能执行下拉刷新!仔细想想应该是事件分发的问题,还需要处理一下事件的拦截!
关于事件拦截的处理,阅读了鸿洋大神写的viewgroup事件分发的博客和Android-Ultra-Pull-To-Refresh的部分源码,从中找到了解决办法:

@Override 
  public boolean onInterceptTouchEvent(MotionEvent ev) { 
    AbsListView absListView = null; 
    for (int n = 0; n < getChildCount(); n++) { 
      if (getChildAt(n) instanceof AbsListView) { 
        absListView = (ListView) getChildAt(n); 
        Logs.v("查找到listView"); 
      } 
    } 
    if (absListView == null) 
      return super.onInterceptTouchEvent(ev); 
    switch (ev.getAction()) { 
      case MotionEvent.ACTION_DOWN: 
        mStartY = ev.getRawY(); 
        break; 
      case MotionEvent.ACTION_MOVE: 
        float space = ev.getRawY() - mStartY; 
        Logs.v("space:" + space); 
        if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) { 
          Logs.v("拦截成功"); 
          return true; 
        } else { 
          Logs.v("不拦截"); 
          return false; 
        } 
    } 
    return super.onInterceptTouchEvent(ev); 
  } 

其中

if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0)
space即移动的距离 canScrollVertically()是判断ListView能否在垂直方向上滚动,参数为负数时代表向上,为正数时代码向下滚动,最后一个就是ListView第一个可见的item的postion

加上上面的事件拦截处理,一个可以满足开头提到的需求的Viewgroup也就完成了!

下面贴上Layout的源码和HeaderView(直接使用的XlistView的HeaderView)的源码

public class SRefreshLayout extends LinearLayout { 
  private SRefreshHeader mHeaderView; 
  private RelativeLayout mHeaderViewContent; 
  private boolean isRefreashing; 
  private float mLastY = -1;//按下的起始高度 
  private int mHeaderViewHeight;//headerView内容高度 
  private int mHeight;//布局高度 
  private float mStartY; 
 
  interface OnRefreshListener { 
    void onRefresh(); 
  } 
 
  public OnRefreshListener mOnRefreshListener; 
 
  public SRefreshLayout(Context context) { 
    super(context); 
    initView(context); 
  } 
 
  public SRefreshLayout(Context context,AttributeSet attrs) { 
    super(context,attrs); 
    initView(context); 
  } 
 
  public SRefreshLayout(Context context,AttributeSet attrs,int defStyleAttr) { 
    super(context,attrs,defStyleAttr); 
    initView(context); 
  } 
 
  private void initView(Context context) { 
    mHeaderView = new SRefreshHeader(context); 
    mHeaderViewContent = (RelativeLayout) mHeaderView.findViewById(R.id.slistview_header_content); 
    setorientation(VERTICAL); 
    addView(mHeaderView,0); 
    getHeaderViewHeight(); 
    getViewHeight(); 
  } 
 
  /** 
   * 获取headView高度 
   */ 
  private void getHeaderViewHeight() { 
    ViewTreeObserver vto2 = mHeaderViewContent.getViewTreeObserver(); 
    vto2.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 
      @Override 
      public void onGlobalLayout() { 
        mHeaderViewHeight = mHeaderViewContent.getHeight(); 
        mHeaderViewContent.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
      } 
    }); 
  } 
 
  /** 
   * 获取SRefreshLayout当前实例的高度 
   */ 
  private void getViewHeight() { 
    ViewTreeObserver thisView = getViewTreeObserver(); 
    thisView.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { 
      @Override 
      public void onGlobalLayout() { 
        SRefreshLayout.this.mHeight = SRefreshLayout.this.getHeight(); 
        SRefreshLayout.this.getViewTreeObserver().removeGlobalOnLayoutListener(this); 
      } 
    }); 
  } 
 
  @Override 
  public boolean onInterceptTouchEvent(MotionEvent ev) { 
    AbsListView absListView = null; 
    for (int n = 0; n < getChildCount(); n++) { 
      if (getChildAt(n) instanceof AbsListView) { 
        absListView = (ListView) getChildAt(n); 
        Logs.v("查找到listView"); 
      } 
    } 
    if (absListView == null) 
      return super.onInterceptTouchEvent(ev); 
    switch (ev.getAction()) { 
      case MotionEvent.ACTION_DOWN: 
        mStartY = ev.getRawY(); 
        break; 
      case MotionEvent.ACTION_MOVE: 
        float space = ev.getRawY() - mStartY; 
        Logs.v("space:" + space); 
        if (space > 0 && !absListView.canScrollVertically(-1) && absListView.getFirstVisiblePosition() == 0) { 
          Logs.v("拦截成功"); 
          return true; 
        } else { 
          Logs.v("不拦截"); 
          return false; 
        } 
    } 
    return super.onInterceptTouchEvent(ev); 
  } 
 
  @Override 
  public boolean onTouchEvent(MotionEvent ev) { 
    if (mLastY == -1) 
      mLastY = ev.getRawY(); 
    switch (ev.getAction()) { 
      case MotionEvent.ACTION_DOWN: 
        //记录起始高度 
        mLastY = ev.getRawY();//记录按下时的Y坐标 
        break; 
      //手指离开屏幕时 
      case MotionEvent.ACTION_UP: 
        //松开时 
        //避免点击事件触发 
        if (!isRefreashing) 
          break; 
        //如果headView状态处于READY状态 则说明松开时应该进入REFRESHING状态 
        if (mHeaderView.getStatus() == SRefreshHeader.STATE_READY) { 
          mHeaderView.setState(SRefreshHeader.STATE_REFRESHING); 
          if (mOnRefreshListener != null) 
            mOnRefreshListener.onRefresh(); 
        } 
        //根据状态重置SrefreshLayout当前实例和headView高度 
        resetHeadView(mHeaderView.getStatus()); 
        reset(mHeaderView.getStatus()); 
        mLastY = -1;//重置坐标 
        break; 
      case MotionEvent.ACTION_MOVE: 
        if (!isRefreashing) 
          isRefreashing = true; 
        final float deltaY = ev.getRawY() - mLastY; 
        mLastY = ev.getRawY(); 
        updateHeaderViewHeight(deltaY / 1.8f);//按一定比例缩小移动距离 
        updateHeight(); 
        break; 
    } 
    return super.onTouchEvent(ev); 
  } 
 
 
  private void reset(int status) { 
    ViewGroup.LayoutParams lp = getLayoutParams(); 
    switch (status) { 
      case SRefreshHeader.STATE_REFRESHING: 
        lp.height = mHeight + mHeaderViewHeight; 
        break; 
      case SRefreshHeader.STATE_norMAL: 
        lp.height = mHeight; 
        break; 
    } 
    setLayoutParams(lp); 
  } 
 
  private void resetHeadView(int status) { 
    switch (status) { 
      case SRefreshHeader.STATE_REFRESHING: 
        mHeaderView.setVisiableHeight(mHeaderViewHeight); 
        break; 
      case SRefreshHeader.STATE_norMAL: 
        mHeaderView.setVisiableHeight(0); 
        break; 
    } 
  } 
 
  private void updateHeight() { 
    ViewGroup.LayoutParams lp = getLayoutParams(); 
    //更新当前layout实例高度为headerView高度加上最初的layout高度 
    //如果不更新layout 会造成内容高度压缩 无法保持比例 
    lp.height = (mHeight + mHeaderView.getVisiableHeight()); 
    setLayoutParams(lp); 
  } 
 
  private void updateHeaderViewHeight(float space) { 
//    if (space < 0) 
//      space = 0; 
//    int factHeight = (int) (space - mHeaderViewHeight); 
    if (mHeaderView.getStatus() != SRefreshHeader.STATE_REFRESHING) { 
      //如果不处于刷新中同时如果高度 
      if (mHeaderView.getVisiableHeight() < mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_norMAL) { 
        mHeaderView.setState(SRefreshHeader.STATE_norMAL); 
      } 
      if (mHeaderView.getVisiableHeight() > mHeaderViewHeight * 2 && mHeaderView.getStatus() != SRefreshHeader.STATE_READY) { 
        mHeaderView.setState(SRefreshHeader.STATE_READY); 
      } 
    } 
    mHeaderView.setVisiableHeight((int) space 
        + mHeaderView.getVisiableHeight()); 
  } 
 
 
  public void stopRefresh() { 
    if (mHeaderView.getStatus() == SRefreshHeader.STATE_REFRESHING) { 
      mHeaderView.setState(SRefreshHeader.STATE_norMAL); 
      resetHeadView(SRefreshHeader.STATE_norMAL); 
      reset(SRefreshHeader.STATE_norMAL); 
    } 
  } 
 
  public void setonRefreshListener(OnRefreshListener onRefreshListener) { 
    this.mOnRefreshListener = onRefreshListener; 
  } 
} 

public class SRefreshHeader extends LinearLayout { 
  private LinearLayout mContainer; 
  private int mState = STATE_norMAL; 
 
  private Animation mRotateUpAnim; 
  private Animation mRotateDownAnim; 
 
  private final int ROTATE_ANIM_DURATION = 500; 
 
  public final static int STATE_norMAL = 0;//下拉刷新 
  public final static int STATE_READY = 1;//释放刷新 
  public final static int STATE_REFRESHING = 2;//刷新中 
  private ImageView mHeadArrowImage; 
  private TextView mHeadLastRefreashTimeTxt; 
  private TextView mHeadHintTxt; 
  private TextView mHeadLastRefreashTxt; 
  private ProgressBar mRefreshingProgress; 
 
  public SRefreshHeader(Context context) { 
    super(context); 
    initView(context); 
  } 
 
  /** 
   * @param context 
   * @param attrs 
   */ 
  public SRefreshHeader(Context context,attrs); 
    initView(context); 
  } 
 
  private void initView(Context context) { 
    // 初始情况,设置下拉刷新view高度为0 
    LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,0); 
    mContainer = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.listview_head_view_layout,null); 
    addView(mContainer,lp); 
    setGravity(Gravity.BottOM); 
 
    mHeadArrowImage = (ImageView) findViewById(R.id.slistview_header_arrow); 
    mHeadLastRefreashTimeTxt = (TextView) findViewById(R.id.slistview_header_time); 
    mHeadHintTxt = (TextView) findViewById(R.id.slistview_header_hint_text); 
    mHeadLastRefreashTxt = (TextView) findViewById(R.id.slistview_header_last_refreash_txt); 
    mRefreshingProgress = (ProgressBar) findViewById(R.id.slistview_header_progressbar); 
 
    mRotateUpAnim = new RotateAnimation(0.0f,-180.0f,Animation.RELATIVE_TO_SELF,0.5f,0.5f); 
    mRotateUpAnim.setDuration(ROTATE_ANIM_DURATION); 
    mRotateUpAnim.setFillAfter(true); 
    mRotateDownAnim = new RotateAnimation(-180.0f,0.0f,0.5f); 
    mRotateDownAnim.setDuration(ROTATE_ANIM_DURATION); 
    mRotateDownAnim.setFillAfter(true); 
  } 
 
  public void setState(int state) { 
    if (state == mState) return; 
 
    if (state == STATE_REFRESHING) {  // 显示进度 
      mHeadArrowImage.clearanimation(); 
      mHeadArrowImage.setVisibility(View.INVISIBLE); 
      mRefreshingProgress.setVisibility(View.VISIBLE); 
    } else {  // 显示箭头图片 
      mHeadArrowImage.setVisibility(View.VISIBLE); 
      mRefreshingProgress.setVisibility(View.INVISIBLE); 
    } 
    switch (state) { 
      case STATE_norMAL: 
        if (mState == STATE_READY) { 
          mHeadArrowImage.startAnimation(mRotateDownAnim); 
        } 
        if (mState == STATE_REFRESHING) { 
          mHeadArrowImage.clearanimation(); 
        } 
        mHeadHintTxt.setText("下拉刷新"); 
        break; 
      case STATE_READY: 
        if (mState != STATE_READY) { 
          mHeadArrowImage.clearanimation(); 
          mHeadArrowImage.startAnimation(mRotateUpAnim); 
          mHeadHintTxt.setText("松开刷新"); 
        } 
        break; 
      case STATE_REFRESHING: 
        mHeadHintTxt.setText("正在刷新"); 
        break; 
      default: 
    } 
 
    mState = state; 
  } 
 
  public void setVisiableHeight(int height) { 
    if (height < 0) 
      height = 0; 
    LayoutParams lp = (LayoutParams) mContainer 
        .getLayoutParams(); 
    lp.height = height; 
    mContainer.setLayoutParams(lp); 
  } 
 
 
  public int getStatus() { 
    return mState; 
  } 
 
  public int getVisiableHeight() { 
    return mContainer.getHeight(); 
  } 
} 

最后是布局文件

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
  android:layout_width="match_parent" 
  android:layout_height="wrap_content" 
  android:gravity="bottom"> 
 
  <RelativeLayout 
    android:id="@+id/slistview_header_content" 
    android:layout_width="match_parent" 
    android:layout_height="60dp"> 
 
    <LinearLayout 
      android:id="@+id/slistview_header_text" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:layout_centerInParent="true" 
      android:gravity="center" 
      android:orientation="vertical"> 
 
      <TextView 
        android:id="@+id/slistview_header_hint_text" 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:text="下拉刷新" /> 
 
      <LinearLayout 
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content" 
        android:layout_marginTop="3dp"> 
 
        <TextView 
          android:id="@+id/slistview_header_last_refreash_txt" 
          android:layout_width="wrap_content" 
          android:layout_height="wrap_content" 
          android:text="上次刷新时间" 
          android:textSize="12sp" /> 
 
        <TextView 
          android:id="@+id/slistview_header_time" 
          android:layout_width="wrap_content" 
          android:layout_height="wrap_content" 
          android:textSize="12sp" /> 
      </LinearLayout> 
    </LinearLayout> 
 
 
    <ProgressBar 
      android:id="@+id/slistview_header_progressbar" 
      android:layout_width="30dp" 
      android:layout_height="30dp" 
      android:layout_centerVertical="true" 
      android:layout_toLeftOf="@id/slistview_header_text" 
      android:visibility="invisible" /> 
 
    <ImageView 
      android:id="@+id/slistview_header_arrow" 
      android:layout_width="wrap_content" 
      android:layout_height="wrap_content" 
      android:layout_alignLeft="@id/slistview_header_progressbar" 
      android:layout_centerVertical="true" 
      android:layout_toLeftOf="@id/slistview_header_text" 
      android:src="@drawable/mmtlistview_arrow" /> 
 
  </RelativeLayout> 
 
</LinearLayout> 

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持编程小技巧。

Android SwipeRefreshLayout

Android SwipeRefreshLayout

SwipeRefreshLayout字面意思就是下拉刷新的布局,继承自ViewGroup,在support v4兼容包下,但必须把你的support library的版本升级到19.1。 提到下拉刷新大家一定对ActionBarPullToRefresh比较熟悉,而如今google推出了更官方的下拉刷新组件,这无疑是对开发者来说比较好的消息。利用这个组件可以很方便的实现Google Now的刷新效果,见下图:

主要方法

  • setOnRefreshListener(OnRefreshListener): 为布局添加一个Listener

  • setRefreshing(boolean): 显示或隐藏刷新进度条

  • isRefreshing(): 检查是否处于刷新状态

  • setColorScheme(): 设置进度条的颜色主题,最多能设置四种

xml布局文件

布局文件很简单,只需要在最外层加上SwipeRefreshLayout,然后他的child是可滚动的view即可,如ScrollView或者ListView。如:

<android.support.v4.widget.SwipeRefreshLayout     
    xmlns:android="http://schemas.android.com/apk/res/android"     
    android:id="@+id/swipe_container"     
    android:layout_width="match_parent"     
    android:layout_height="match_parent">    
       
    <ScrollView         
        android:layout_width="match_parent"         
        android:layout_height="match_parent">   
                
    <TextView             
        android:text="@string/hello_world"             
        android:layout_width="match_parent"             
        android:layout_height="wrap_content"             
        android:layout_marginTop="16dp"             
        android:gravity="center"/>     
    </ScrollView>   
    
</android.support.v4.widget.SwipeRefreshLayout>

Activity代码

 protected void onCreate(Bundle savedInstanceState) {     
    super.onCreate(savedInstanceState);     
    setContentView(R.layout.activity_main);       
    swipeLayout = (SwipeRefreshLayout) findViewById(R.id.swipe_container);     
    swipeLayout.setOnRefreshListener(this);     
    swipeLayout.setColorScheme(android.R.color.holo_blue_bright,
                         android.R.color.holo_green_light,
                         android.R.color.holo_orange_light,
                         android.R.color.holo_red_light); 
}   
 public void onRefresh() {     
        new Handler().postDelayed(new Runnable() {         
            @Override public void run() {             
                swipeLayout.setRefreshing(false);         
        }    
    }, 5000); 
}

上面的代码很简单,只需要给SwipeRefreshLayout添加一个listener,值得说明的是setColorScheme方法是设置刷新进度条的颜色,最多只能设置4种循环显示,默认第一个是随用户手势加载的颜色进度条。

源码

写了的小demo在github上,地址在:SwipeRefreshLayoutDemo

总结

google在不断完善自己的sdk,推出越来越多的组件,其目的是让开发更简单,设计上更统一,这可能是google未来的方向,不管怎样,这对开发者来说无疑是非常好的消息。


android SwipeRefreshLayout 与 ListView 结合实现下拉刷新,加载功能

android SwipeRefreshLayout 与 ListView 结合实现下拉刷新,加载功能

package com.example.listviewdemo;

import java.util.ArrayList;
import java.util.List;


import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.app.Activity;
import android.support.v4.widget.SwipeRefreshLayout;
import android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.ListView;

public class MainActivity extends Activity implements OnRefreshListener,OnScrollListener{
	
	private SwipeRefreshLayout swipeRefreshLayout;
	private ListView listView;
	private List<String> list = new ArrayList<String>();
	private ArrayAdapter<String> adapter ;
	private View footer;
	private boolean isfinsh = true;//是否加载完成
	private Handler handler = new Handler(){
		public void handleMessage(android.os.Message msg) {
			switch (msg.what) {
			case 1:
				list.add("bbbbbbbbb1");
				adapter.notifyDataSetChanged();
				if(listView.getFooterViewsCount()>0){
					listView.removeFooterView(footer);
				}
				isfinsh = true;
				break;

			default:
				break;
			}
		};
	};
	

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		swipeRefreshLayout = (SwipeRefreshLayout)findViewById(R.id.rf);
		swipeRefreshLayout.setColorScheme(android.R.color.holo_blue_bright, android.R.color.holo_green_light,  
				android.R.color.holo_orange_light, android.R.color.holo_red_light);
		swipeRefreshLayout.setOnRefreshListener(this);
		listView = (ListView)findViewById(R.id.ls);
		footer = LayoutInflater.from(this).inflate(R.layout.footer, nullfalse);
		for(int i = 0 ;i<20;i++){
			list.add("aaaaaaaaa"+i);
		}
		adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, list);
		listView.addFooterView(footer);
		listView.setAdapter(adapter);
		listView.removeFooterView(footer);
		listView.setOnScrollListener(this);
	}

	@Override
	public void onRefresh() {
		if(isfinsh){
			isfinsh = false;
			new ReTask().execute();
		}else{
			swipeRefreshLayout.setRefreshing(false);
		}
		
	}

	@Override
	public void onScroll(AbsListView view, int firstVisibleItem,
			int visibleItemCount, int totalItemCount) {
		int countIndex = view.getLastVisiblePosition();
		Log.d("countIndex"""+countIndex);
		Log.d("totalItemCount"""+totalItemCount);
		if((countIndex+1) == totalItemCount){
			if(totalItemCount > 0){
				if(isfinsh){
					isfinsh = false;
					listView.addFooterView(footer);
					new GetTask().execute();
				}else{
					
				}
			}
		}
	}

	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {
		
	}
	
	private class ReTask extends AsyncTask<Void , IntegerBoolean>{

		@Override
		protected Boolean doInBackground(Void... arg0) {
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return true;
		}
		
		@Override
		protected void onPostExecute(Boolean result) {
			super.onPostExecute(result);
			if(result){
				list.clear();
				for(int i = 0 ;i<20;i++){
					list.add("aaaaaaaaa"+i);
				}
				adapter.notifyDataSetChanged();
				swipeRefreshLayout.setRefreshing(false);
				isfinsh = true;
				}
		}
	}
	
	private class GetTask extends AsyncTask<Void , IntegerBoolean>{

		@Override
		protected Boolean doInBackground(Void... arg0) {
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return true;
		}
		
		@Override
		protected void onPostExecute(Boolean result) {
			super.onPostExecute(result);
			if(result)
				handler.sendEmptyMessage(1);
		}
	}

}

activity_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" >

    <android.support.v4.widget.SwipeRefreshLayout
        android:id="@+id/rf"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        >
        <ListView 
            android:id="@+id/ls"
            android:layout_width="match_parent"
        	android:layout_height="wrap_content"
        	android:scrollbars="none"
            ></ListView>
    </android.support.v4.widget.SwipeRefreshLayout>

</RelativeLayout>

footer.xml

<?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"
    android:gravity="center_horizontal"
    android:padding="8dp"
    >

    <ProgressBar android:id="@+id/c81_forthBar"
        android:layout_width="24dp"
    	android:layout_height="24dp"
         /> 
    <TextView android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:text="数据正在加载..."
    />
</LinearLayout>

注意在进行 setAdapter 时需 addFooterView,添加完再 removeFooterView,否则会报错

我们今天的关于Android 使用SwipeRefreshLayout控件仿抖音做的视频下拉刷新效果android下拉刷新框架的分享就到这里,谢谢您的阅读,如果想了解更多关于Android PullToRefreshLayout下拉刷新控件的终结者、Android RefreshLayout实现下拉刷新布局、Android SwipeRefreshLayout、android SwipeRefreshLayout 与 ListView 结合实现下拉刷新,加载功能的相关信息,可以在本站进行搜索。

本文标签: