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, 0, 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, 0, getMeasuredWidth(), moveDeltaY);
  Paint paint = new Paint();
  paint.setAntiAlias(true);
  // 阴影的高度为26
  LinearGradient linearGradient = new LinearGradient(0, moveDeltaY, 0, 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 l, int t, int r, 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, null, 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, View view, int position, long id)
   {
    Toast.makeText(MainActivity.this, " 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, 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, View view, int position, long id)
   {
    Toast.makeText(MainActivity.this, " Click on " + parent.getAdapter().getItemId(position), 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, long id)
   {
    Toast.makeText(MainActivity.this, " Click on group " + groupPosition + " item " + childPosition, Toast.LENGTH_SHORT).show();
    return true;
   }
  });
  ((ExpandableListView) 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;
   }
  });
  ((ExpandableListView) alv).setOnGroupClickListener(new OnGroupClickListener()
  { 

   @Override
   public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, 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, int childPosition, boolean isLastChild, 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(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下拉刷新控件有所帮助。

(0)

相关推荐

  • Android下拉刷新上拉加载控件(适用于所有View)

    前面写过一篇关于下拉刷新控件的文章下拉刷新控件终结者:PullToRefreshLayout,后来看到好多人还有上拉加载更多的需求,于是就在前面下拉刷新控件的基础上进行了改进,加了上拉加载的功能.不仅如此,我已经把它改成了对所有View都通用!可以随心所欲使用这两个功能~~ 我做了一个大集合的demo,实现了ListView.GridView.ExpandableListView.ScrollView.WebView.ImageView.TextView的下拉刷新和上拉加载.后面会提供demo的

  • Android下拉刷新ListView——RTPullListView(demo)

    下拉刷新在越来越多的App中使用,已经形成一种默认的用户习惯,遇到列表显示的内容时,用户已经开始习惯性的拉拉.在交互习惯上已经形成定性.之前在我的文章<IOS学习笔记34-EGOTableViewPullRefresh实现下拉刷新>中介绍过如何在IOS上实现下拉刷新的功能.今天主要介绍下在Android上实现下拉刷新的Demo,下拉控件参考自Github上开源项目PullToRefresh,并做简单修改.最终效果如下:                         工程结构如下: 使用过程中

  • Android自定义实现顶部粘性下拉刷新效果

    本文实例为大家分享了Android实现顶部粘性下拉刷新效果的具体代码,供大家参考,具体内容如下 学习:视频地址 activity_view_mv代码 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://sche

  • Android中使用RecyclerView实现下拉刷新和上拉加载

    推荐阅读:使用RecyclerView添加Header和Footer的方法                       RecyclerView的使用之HelloWorld RecyclerView 是Android L版本中新添加的一个用来取代ListView的SDK,它的灵活性与可替代性比listview更好.本文给大家介绍如何为RecyclerView添加下拉刷新和上拉加载,过去在ListView当中添加下拉刷新和上拉加载是非常方便的利用addHeaderView和addFooterVie

  • Android RecyclerView实现下拉刷新和上拉加载

    RecyclerView已经出来很久了,许许多多的项目都开始从ListView转战RecyclerView,那么,上拉加载和下拉刷新是一件很有必要的事情. 在ListView上,我们可以通过自己添加addHeadView和addFootView去添加头布局和底部局实现自定义的上拉和下拉,或者使用一些第三方库来简单的集成,例如Android-pulltorefresh或者android-Ultra-Pull-to-Refresh,后者的自定义更强,但需要自己实现上拉加载. 而在下面我们将用两种方式

  • Android实现上拉加载更多以及下拉刷新功能(ListView)

    首先为大家介绍Andorid5.0原生下拉刷新简单实现. 先上效果图: 相对于上一个19.1.0版本中的横条效果好看了很多.使用起来也很简单. <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/container" and

  • android下拉刷新ListView的介绍和实现代码

    大致上,我们发现,下拉刷新的列表和一般列表的区别是,当滚动条在顶端的时候,再往下拉动就会把整个列表拉下来,显示出松开刷新的提示.由此可以看出,在构建这个下拉刷新的组件的时候,只用继承ListView,然后重写onTouchEvent就能实现.还有就是要能在xml布局文件中引用,还需要一个参数为Context,AttributeSet的构造函数. 表面上的功能大概就这些了.另一方面,刷新的行为似乎还没有定义,在刷新前做什么,刷新时要做什么,刷新完成后要做什么,这些行为写入一个接口中,然后让组件去实

  • Android官方下拉刷新控件SwipeRefreshLayout使用详解

    可能开发安卓的人大多数都用过很多下拉刷新的开源组件,但是今天用了官方v4支持包的SwipeRefreshLayout觉得效果也蛮不错的,特拿出来分享. 简介: SwipeRefreshLayout组件只接受一个子组件:即需要刷新的那个组件.它使用一个侦听机制来通知拥有该组件的监听器有刷新事件发生,换句话说我们的Activity必须实现通知的接口.该Activity负责处理事件刷新和刷新相应的视图.一旦监听者接收到该事件,就决定了刷新过程中应处理的地方.如果要展示一个"刷新动画",它必须

  • Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能(附源码)

    最近项目中需要用到ListView下拉刷新的功能,一开始想图省事,在网上直接找一个现成的,可是尝试了网上多个版本的下拉刷新之后发现效果都不怎么理想.有些是因为功能不完整或有Bug,有些是因为使用起来太复杂,十全十美的还真没找到.因此我也是放弃了在网上找现成代码的想法,自己花功夫编写了一种非常简单的下拉刷新实现方案,现在拿出来和大家分享一下.相信在阅读完本篇文章之后,大家都可以在自己的项目中一分钟引入下拉刷新功能. 首先讲一下实现原理.这里我们将采取的方案是使用组合View的方式,先自定义一个布局

  • android开发教程之实现listview下拉刷新和上拉刷新效果

    复制代码 代码如下: public class PullToLoadListView extends ListView implements OnScrollListener { private static final String TAG = PullToLoadListView.class.getSimpleName(); private static final int STATE_NON = 0; private static final int STATE_PULL_TO_REFRE

随机推荐