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

       说到下拉刷新控件,网上版本有很多,很多软件也都有下拉刷新功能。有一个叫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下拉刷新控件有所帮助。

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索Android下拉刷新
pulltorefreshlayout、pullrefreshlayout、pulltorefresh layout、pullrefresh 下拉样式、pulltorefresh,以便于您获取更多的相关知识。

时间: 2024-08-03 17:48:19

Android PullToRefreshLayout下拉刷新控件的终结者_Android的相关文章

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

说到下拉刷新控件,网上版本有很多,很多软件也都有下拉刷新功能.有一个叫XListView的,我看别人用过,没看过是咋实现的,看这名字估计是继承自ListView修改的,不过效果看起来挺丑的,也没什么扩展性,太单调了.看了QQ2014的列表下拉刷新,发现挺好看的,我喜欢,贴一下图看一下qq的下拉刷新效果: 不错吧?嗯,是的.一看就知道实现方式不一样.咱们今天就来实现一个下拉刷新控件.由于有时候不仅仅是ListView需要下拉刷新,ExpandableListView和GridView也有这个需求,

Android SwipeRefreshLayout 下拉刷新控件介绍

下面App基本都有下拉刷新的功能,以前基本都使用XListView或者自己写一个下拉刷新,近期Google提供了一个官方的下拉刷新控件 SwipeRefreshLayout,我感觉还不错啊,见惯了传统的下拉刷新,这个反而给人耳目一新的感觉(貌似知乎的APP已经使用这种下拉刷新了). Google也在官方网站给出了V4的兼容包: 再来看看布局文件里的代码(我这里放的是一个ListView 当然也可以放其他控件 只要你高兴就好)  <android.support.v4.widget.SwipeRe

Android自定义下拉刷新控件RefreshableView_Android

这是在了解下拉刷新功能原理下的产物,下拉刷新可以说是国产APP里面必有的功能,连Google都为此出了SwipeRefreshLayout,一种MD风格的下拉刷新. 不过,MD风格在国内似乎很是艰难,不单单是国内系统主流仍是4.4的原因,也有用户习惯的问题,扯的有点多了,在看了许多博客之后,我突然想写一个能仿照 SwipeRefreshLayout 的兼容所有控件的下拉刷新,不单单只是 ListView,希望它也可以包容普通的View和ScrollView,经过两天的奋斗,终于搞定了,因为我的目

Android自定义下拉刷新控件RefreshableView

这是在了解下拉刷新功能原理下的产物,下拉刷新可以说是国产APP里面必有的功能,连Google都为此出了SwipeRefreshLayout,一种MD风格的下拉刷新. 不过,MD风格在国内似乎很是艰难,不单单是国内系统主流仍是4.4的原因,也有用户习惯的问题,扯的有点多了,在看了许多博客之后,我突然想写一个能仿照 SwipeRefreshLayout 的兼容所有控件的下拉刷新,不单单只是 ListView,希望它也可以包容普通的View和ScrollView,经过两天的奋斗,终于搞定了,因为我的目

Android自定义控件实战——下拉刷新控件终结者:PullToRefreshLayout

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

Android下拉刷新控件PullToRefresh实例解析_Android

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

Android开发之无痕过渡下拉刷新控件的实现思路详解_Android

相信大家已经对下拉刷新熟悉得不能再熟悉了,市面上的下拉刷新琳琅满目,然而有很多在我看来略有缺陷,接下来我将说明一下存在的缺陷问题,然后提供一种思路来解决这一缺陷,废话不多说!往下看嘞! 1.市面一些下拉刷新控件普遍缺陷演示 以直播吧APP为例: 第1种情况: 滑动控件在初始的0位置时,手势往下滑动然后再往上滑动,可以看到滑动到初始位置时滑动控件不能滑动. 原因: 下拉刷新控件响应了触摸事件,后续的一系列事件都由它来处理,当滑动控件到顶端的时候,滑动事件都被下拉刷新控件消费掉了,传递不到它的子控件

Android下拉刷新控件SwipeRefreshLayout源码解析_Android

SwipeRefreshLayout是Android官方的下拉刷新控件,使用简单,界面美观,不熟悉的朋友可以随便搜索了解一下,这里就不废话了,直接进入正题.  首先给张流程图吧,标出了几个主要方法的作用,可以结合着看一下哈.   这种下拉刷新控件的原理不难,基本就是监听手指的运动,获取手指的坐标,通过计算判断出是哪种操作,然后就是回调相应的接口了.SwipeRefreshLayout是继承自ViewGroup的,根据Android的事件分发机制,触摸事件应该是先传递到ViewGroup,根据on

Android开发之无痕过渡下拉刷新控件的实现思路详解

相信大家已经对下拉刷新熟悉得不能再熟悉了,市面上的下拉刷新琳琅满目,然而有很多在我看来略有缺陷,接下来我将说明一下存在的缺陷问题,然后提供一种思路来解决这一缺陷,废话不多说!往下看嘞! 1.市面一些下拉刷新控件普遍缺陷演示 以直播吧APP为例: 第1种情况: 滑动控件在初始的0位置时,手势往下滑动然后再往上滑动,可以看到滑动到初始位置时滑动控件不能滑动. 原因: 下拉刷新控件响应了触摸事件,后续的一系列事件都由它来处理,当滑动控件到顶端的时候,滑动事件都被下拉刷新控件消费掉了,传递不到它的子控件