Android实现简单的下拉刷新pulltorefresh

网上下拉刷新的DEMO很多,但是总有各种不满意的地方,有些会下拉卡住,有些回弹不流畅,有些性能太低会各种卡顿,有些emptyView无法下拉......

自己写的才是最合适自己的,代码很简单,也很容易修改,稍微阅读下代码就能改出自己需要的各种效果。

首先,重写ListView,自定义Touch事件,为了使emptyView也可下拉,emptyView也加上Touch事件。 如果要实现GridView,把这里的ListView改成GridView即可。

PullableListView :

public class PullableListView extends ListView {
  private boolean inited;
  private float density;
  private int mDownY, mMoveY;
  private int mPullY;
  private boolean isPull;
  private PullListener mPullListener;
  private VelocityTracker mVelocityTracker;

  public interface PullListener {

    public boolean onPullDownStart();

    public void onPullDown(int moveY);

    public void onPullDownDrop();
  }

  public PullableListView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
  }

  public PullableListView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  }

  public PullableListView(Context context) {
    super(context);
    init();
  }

  private void init() {
    if (!inited) {
      density = getResources().getDisplayMetrics().density;
    }
  }

  public void setPullListener(PullListener mPullListener) {
    this.mPullListener = mPullListener;
  }

  public boolean isPulling() {
    return isPull;
  }

  @Override
  public void setEmptyView(View emptyView) {
    super.setEmptyView(emptyView);
    // 重写emptyView的Touch事件,使显示emptyView时也可以下拉刷新
    emptyView.setOnTouchListener(new OnTouchListener() {
      @Override
      public boolean onTouch(View v, MotionEvent ev) {
        if (mVelocityTracker == null) {
          mVelocityTracker = VelocityTracker.obtain();
        }
        mVelocityTracker.addMovement(ev);
        switch (ev.getAction()) {
          case MotionEvent.ACTION_DOWN:
            mDownY = (int) ev.getY();
            break;
          case MotionEvent.ACTION_MOVE:
            mMoveY = (int) ev.getY();
            if (!isPull) {
              mVelocityTracker.computeCurrentVelocity(1000, 8000f);
              if (mVelocityTracker.getYVelocity() > 500 // 下拉速度大于500
                  && Math.abs(mMoveY - mDownY) > 20 * density) { // 下拉距离超过20dp
                mPullY = mMoveY;
                if (mPullListener.onPullDownStart()) {
                  isPull = true;
                }
              }
            } else {
              // 阻尼下拉(随着下拉距离增加,阻力增加)
              mPullListener.onPullDown(mMoveY - mPullY + v.getScrollY());
              // 等阻力下拉(阻力恒定,不随下拉距离增加而增加)
              // mPullListener.onPullDown(mMoveY - mPullY);
              if (mMoveY < mPullY) {
                isPull = false;
              }
              return true;
            }
            break;
          case MotionEvent.ACTION_UP:
            if (mVelocityTracker != null) {
              mVelocityTracker.clear();
              mVelocityTracker.recycle();
              mVelocityTracker = null;
            }
            if (isPull) {
              mPullY = 0;
              isPull = false;
              mPullListener.onPullDownDrop();
              return true;
            }
            break;
        }
        return true;
      }
    });
  }

  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (isPull) {
      // 正在下拉时,阻住Touch事件向下传递,同时会向各个ChildView发送ACTION_CANLE事件,
      // 使之前捕捉到了ACTION_DOWN事件的ChildView回复到正常状态
      return true;
    }

    return super.onInterceptTouchEvent(ev);
  }

  @Override
  public boolean onTouchEvent(MotionEvent ev) {
    if (mVelocityTracker == null) {
      mVelocityTracker = VelocityTracker.obtain();
    }
    mVelocityTracker.addMovement(ev);
    switch (ev.getAction()) {
      case MotionEvent.ACTION_DOWN:
        mDownY = (int) ev.getY();
        break;
      case MotionEvent.ACTION_MOVE:
        mMoveY = (int) ev.getY();
        if (!isPull) {
          if (getFirstVisiblePosition() == 0) {
            View view = getChildAt(0);
            mVelocityTracker.computeCurrentVelocity(1000, 8000f);
            if (mVelocityTracker.getYVelocity() > 500// 下拉速度大于500
                && (view == null || view.getTop() == getPaddingTop()) // 已拉动到顶部
                && Math.abs(mMoveY - mDownY) > 15 * density) { // 下拉距离超过20dp
              mPullY = mMoveY;
              if (mPullListener.onPullDownStart()) {
                // 根据返回值确认是否进入下拉状态
                isPull = true;
              }
            }
          }
        } else {
          // 阻尼下拉(随着下拉距离增加,阻力增加)
          mPullListener.onPullDown(mMoveY - mPullY);
          // 等阻力下拉(阻力恒定,不随下拉距离增加而增加)
          // mPullListener.onPullDown(mMoveY - mPullY - getScrollY());
          if (mMoveY < mPullY) {
            isPull = false;
          }
          return true;
        }
        break;
      case MotionEvent.ACTION_UP:
        if (mVelocityTracker != null) {
          mVelocityTracker.clear();
          mVelocityTracker.recycle();
          mVelocityTracker = null;
        }
        if (isPull) {
          mPullY = 0;
          isPull = false;
          mPullListener.onPullDownDrop();
          return true;
        }
        break;
      case MotionEvent.ACTION_CANCEL:
        break;
    }
    return super.onTouchEvent(ev);
  }
}

然后是外层的LinearyLayer,监听PullableListView的下拉回调,实现下拉效果。同时提供ListView(GridView)的外部接口,如 setEmptyView(View view),setAdapter(ListAdapter adapter)...等等,这里只提供部分我需要使用的,可以根据自身需求去提供外部接口。 
代码中R.drawable.pulltorefresh 和 R.drawable.loading 分别是下拉箭头 和 刷新滚动条 的图片,这里不提供了,自己随意找两张图片贴上就行了。

PullToRefreshView:

public class PullToRefreshView extends LinearLayout {

  protected static final String TAG = "PullToRefreshView";

  /**
   * 下拉阻力系数
   */
  private static final float SCALL_PULL_DOWW = 2.0f;

  private View mView;

  private PullableListView mListView;

  private TextView mPullTv;

  private ImageView mProgressBar;

  private View mPullV;

  private View mEmptyView;

  private boolean isInited;

  private boolean canRefresh;

  private boolean isRefreshing;

  private boolean isPullable = true;

  private int mOrMargin;

  private ObjectAnimator mArrowRotateAnimator;

  private Animation mProAnimation;

  private PullToRefreshListener mPullToRefreshListener;

  public PullToRefreshView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    initView(context);
  }

  public PullToRefreshView(Context context, AttributeSet attrs) {
    super(context, attrs);
    initView(context);
  }

  public PullToRefreshView(Context context) {
    super(context);
    initView(context);
  }

  public interface PullToRefreshListener {
    /**
     * do data refresh here
     */
    public void onRefreshStart();

    /**
     * do view update here
     */
    public void onRefreshFinished();
  }

  private void initView(Context context) {
    if (!isInited) {
      isInited = true;
      mView = LayoutInflater.from(context).inflate(R.layout.view_pulltorefresh, null);
      mProgressBar = (ImageView) mView.findViewById(R.id.iv_pulltorefresh_arrow);
      mProgressBar.setImageResource(R.drawable.pulltorefresh);
      mPullTv = (TextView) mView.findViewById(R.id.tv_pulltorefresh);
      mPullV = mView.findViewById(R.id.ly_pulltorefresh_pull);
      mListView = (PullableListView) mView.findViewById(R.id.gv_smarturc_urcs);
      mListView.setPullListener(mPullListener);
      LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
          LayoutParams.MATCH_PARENT);
      addView(mView, lp);
      LayoutParams lParams = (LayoutParams) mPullV.getLayoutParams();
      mOrMargin = lParams.topMargin;
      mProAnimation = AnimationUtils.loadAnimation(getContext(),
          R.anim.anim_progressbar);
    }
  }

  private PullListener mPullListener = new PullListener() {

    @Override
    public boolean onPullDownStart() {
      if (isRefreshing || !isPullable) {
        return false;
      }
      mPullTv.setText("下拉刷新");
      mProgressBar.setRotation(0f);
      mProgressBar.setImageResource(R.drawable.pulltorefresh);
      if (mProgressBar.getAnimation() != null) {
        mProgressBar.clearAnimation();
      }
      return true;
    }

    @Override
    public void onPullDown(int moveY) {
      if (isRefreshing || !isPullable) {
        return;
      }
      moveY = (int) Math.max(0, moveY / SCALL_PULL_DOWW);
      mView.scrollTo(0, -moveY);
      mEmptyView.scrollTo(0, -moveY);
      if (!canRefresh
          && Math.abs(mView.getScrollY()) > Math.abs(mOrMargin)) {
        mPullTv.setText("松开刷新");
        canRefresh = true;
        if (mArrowRotateAnimator != null) {
          mArrowRotateAnimator.cancel();
        }
        float rotation = mProgressBar.getRotation();
        mArrowRotateAnimator = ObjectAnimator.ofFloat(mProgressBar, "rotation",
            rotation, 180f);
        mArrowRotateAnimator.setDuration(100).start();
      } else if (canRefresh
          && Math.abs(mView.getScrollY()) <= Math.abs(mOrMargin)) {
        mPullTv.setText("下拉刷新");
        canRefresh = false;
        if (mArrowRotateAnimator != null) {
          mArrowRotateAnimator.cancel();
        }
        float rotation = mProgressBar.getRotation();
        mArrowRotateAnimator = ObjectAnimator.ofFloat(mProgressBar, "rotation",
            rotation, 0f);
        mArrowRotateAnimator.setDuration(100).start();
      }
    }

    @Override
    public void onPullDownDrop() {
      if (canRefresh) {
        setRefreshing();
      } else {
        isRefreshing = false;
        backTo(mView.getScrollY(), 0);
      }
    }
  };

  private void backTo(final int from, final int to) {
    ObjectAnimator.ofInt(mView, "scrollY", from, to).setDuration(300)
        .start();
    ObjectAnimator.ofInt(mEmptyView, "scrollY", from, to).setDuration(300)
        .start();
  }

  /**
   * 设置为正在刷新状态
   */
  public void setRefreshing() {
    isRefreshing = true;
    mProgressBar.setImageResource(R.drawable.loading);
    mProgressBar.startAnimation(mProAnimation);
    mPullTv.setText("正在刷新");
    backTo(mView.getScrollY(), mOrMargin);
    if (mPullToRefreshListener != null) {
      mPullToRefreshListener.onRefreshStart();
    }
  }

  /**
   * 刷新完成
   */
  public void setRrefreshFinish() {
    if (isRefreshing) {
      isRefreshing = false;
      backTo(mView.getScrollY(), 0);
    }
    if (mPullToRefreshListener != null) {
      mPullToRefreshListener.onRefreshFinished();
    }
  }

  public void setPullable(boolean pullable) {
    isPullable = pullable;
  }

  public void setPullToRefreshListener(
      PullToRefreshListener mPullToRefreshListener) {
    this.mPullToRefreshListener = mPullToRefreshListener;
  }

  public void setAdapter(ListAdapter adapter) {
    mListView.setAdapter(adapter);
  }

  public void setEmptyView(View emptyView) {
    mListView.setEmptyView(emptyView);
    this.mEmptyView = emptyView;
  }

  public void setOnItemClickListener(OnItemClickListener itemClickListener) {
    mListView.setOnItemClickListener(itemClickListener);
  }

  public void setOnItemLongClickListener(OnItemLongClickListener itemLongClickListener) {
    mListView.setOnItemLongClickListener(itemLongClickListener);
  }
}

layout-view_pulltorefresh:

<?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="match_parent"
  android:background="#cccccc"
  android:orientation="vertical" >

  <LinearLayout
    android:id="@+id/ly_pulltorefresh_pull"
    android:layout_width="wrap_content"
    android:layout_height="48dp"
    android:layout_gravity="center_horizontal"
    android:layout_marginTop="-48dp" >

    <ImageView
      android:id="@+id/iv_pulltorefresh_arrow"
      android:layout_width="20dp"
      android:layout_height="match_parent"
      android:scaleType="fitCenter"
      android:src="@drawable/pulltorefresh" />

    <TextView
      android:id="@+id/tv_pulltorefresh"
      android:layout_width="wrap_content"
      android:layout_height="match_parent"
      android:layout_marginBottom="4dp"
      android:layout_marginLeft="8dp"
      android:gravity="center"
      android:textColor="@android:color/white"
      android:textSize="16sp" />
  </LinearLayout>

  <com.example.pulltorefresh.PullableListView
    android:id="@+id/gv_smarturc_urcs"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/transparent"
    android:overScrollMode="never"
    android:scrollingCache="false" >
  </com.example.pulltorefresh.PullableListView>

</LinearLayout>

anim-anim_progressbar:

<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
  android:fromDegrees="0"
  android:toDegrees="360"
  android:pivotX="50%"
  android:pivotY="50%"
  android:repeatCount="infinite"
  android:repeatMode="restart"
  android:duration="800"
  android:interpolator="@android:anim/linear_interpolator"/>

最后是DEMO ACTIVITY:

public class PullToRefreshActivity extends Activity {

  private PullToRefreshView mPullToRefreshView;
  private List<String> data = new ArrayList<String>();
  private MyAdapter mAdapter;
  private Handler mHandler;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // TODO Auto-generated method stub
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_pulltorefresh);
    mHandler = new Handler();
    mPullToRefreshView = (PullToRefreshView) findViewById(R.id.pullToRefreshView1);
    mAdapter = new MyAdapter();
    mPullToRefreshView.setAdapter(mAdapter);
    mPullToRefreshView.setEmptyView(findViewById(R.id.empty));
    mPullToRefreshView.setOnItemLongClickListener(new OnItemLongClickListener() {
      @Override
      public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
        Toast.makeText(getApplicationContext(), "Long click : " + data.get(position),
            Toast.LENGTH_SHORT).show();
        return true;
      }
    });
    mPullToRefreshView.setOnItemClickListener(new OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        Toast.makeText(getApplicationContext(), data.get(position), Toast.LENGTH_SHORT)
            .show();
      }
    });
    mPullToRefreshView.setPullToRefreshListener(new PullToRefreshListener() {
      @Override
      public void onRefreshStart() {
        // 模拟刷新数据
        mHandler.postDelayed(new Runnable() {
          @Override
          public void run() {
            data.add(String.valueOf((int) (Math.random() * 1000)));
            mPullToRefreshView.setRrefreshFinish();
          }
        }, 2000);
      }

      @Override
      public void onRefreshFinished() {
        // 更新视图
        mAdapter.notifyDataSetChanged();
      }
    });
//    mHandler.postDelayed(new Runnable() {
//      @Override
//      public void run() {
//        // TODO Auto-generated method stub
//        mPullToRefreshView.setRefreshing();
//      }
//    }, 500);
  }

  public class MyAdapter extends BaseAdapter {

    @Override
    public int getCount() {
      // TODO Auto-generated method stub
      return data.size();
    }

    @Override
    public Object getItem(int position) {
      // TODO Auto-generated method stub
      return data.get(position);
    }

    @Override
    public long getItemId(int position) {
      // TODO Auto-generated method stub
      return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      // TODO Auto-generated method stub
      if (convertView == null) {
        convertView = new TextView(PullToRefreshActivity.this);
      }
      TextView textView = (TextView) convertView;
      textView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 40f);
      textView.setPadding(30, 30, 30, 30);
      textView.setText(data.get(position));
      return convertView;
    }

  }
}

layout-activity_pulltorefresh:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/container"
  android:layout_width="match_parent"
  android:layout_height="match_parent" >

  <com.example.pulltorefresh.PullToRefreshView
    android:id="@+id/pullToRefreshView1"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_alignParentLeft="true"
    android:layout_alignParentTop="true" >
  </com.example.pulltorefresh.PullToRefreshView>

  <LinearLayout
    android:id="@+id/empty"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center_horizontal"
    android:orientation="vertical"
    android:padding="60dp" >

    <ImageView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:src="@drawable/ic_launcher" />

    <TextView
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="NO DATA" />
  </LinearLayout>

</RelativeLayout>

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

(0)

相关推荐

  • Android UI设计系列之自定义ListView仿QQ空间阻尼下拉刷新和渐变菜单栏效果(8)

    好久没有写有关UI的博客了,刚刚翻了一下之前的博客,最近一篇有关UI的博客:Android UI设计系列之自定义Dialog实现各种风格的对话框效果(7) ,实现各种风格效果的对话框,在那篇博客写完后由于公司封闭开发封网以及其它原因致使博客中断至今,中断这么久很是惭愧,后续我会尽量把该写的都补充出来.近来项目有个需求,要做个和QQ空间类似的菜单栏透明度渐变和下拉刷新带有阻尼回弹的效果.于是花点时间动手试了试,基本上达到了QQ空间的效果,截图如下: 通过观察QQ空间的运行效果,发现当往上滚动时菜单

  • Android ListView实现上拉加载更多和下拉刷新功能

    本文实例为大家介绍了Android ListView下拉刷新功能的实现方法和功能,供大家参考,具体内容如下 1.ListView优化方式 界面缓存:ViewHolder+convertView 分页加载:上拉刷新 图片缓存 快速滑动ListView禁止刷新 2.效果 3.上拉加载更多原理及实现 当我们手指滑动到listview最后位置的时候,我们触发加载数据的方法.这触发之前我们需要做一些工作,包括: 如何判断滑动到最后? 如何避免重复加载数据? 加载之后如何刷新界面? 1).界面实现AbsLi

  • 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下拉刷新上拉加载控件(适用于所有View)

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

  • Android仿微信滑动弹出编辑、删除菜单效果、增加下拉刷新功能

    如何为不同的list item呈现不同的菜单,本文实例就为大家介绍了Android仿微信或QQ滑动弹出编辑.删除菜单效果.增加下拉刷新等功能的实现,分享给大家供大家参考,具体内容如下 效果图: 1. 下载开源项目,并将其中的liberary导入到自己的项目中: 2. 使用SwipeMenuListView代替ListView,在页面中布局: <android.support.v4.widget.SwipeRefreshLayout android:id="@+id/swipeRefresh

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

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

  • Android使用RecyclerView实现自定义列表、点击事件以及下拉刷新

    Android使用RecyclerView 1. 什么是RecyclerView RecyclerView 是 Android-support-v7-21 版本中新增的一个 Widgets,官方对于它的介绍则是:RecyclerView 是 ListView 的升级版本,更加先进和灵活. 简单来说就是:RecyclerView是一种新的视图组,目标是为任何基于适配器的视图提供相似的渲染方式.它被作为ListView和GridView控件的继承者,在最新的support-V7版本中提供支持. 2.

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

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

  • Android实现支持所有View的通用的下拉刷新控件

    下拉刷新对于一个app来说是必不可少的一个功能,在早期大多数使用的是chrisbanes的PullToRefresh,或是修改自该框架的其他库.而到现在已经有了更多的选择,github上还是有很多体验不错的下拉刷新. 而下拉刷新主要有两种实现方式: 1. 在ListView中添加header和footer,监听ListView的滑动事件,动态设置header/footer的高度,但是这种方式只适用于ListView,RecyclerView. 2. 第二种方式则是继承ViewGroup或其子类,

  • Android自定义下拉刷新上拉加载

    本文实例为大家分享了Android自定义下拉刷新上拉加载的具体实现步骤,供大家参考,具体内容如下 实现的方式是SwipeRefreshLayout + RecyclerView 的VIewType 首先看效果: 总的思路: 布局文件 <android.support.v4.widget.SwipeRefreshLayout android:layout_marginTop="?attr/actionBarSize" android:id="@+id/one_refres

随机推荐