Android实现简单的下拉刷新pulltorefresh

内容摘要
网上下拉刷新的DEMO很多,但是总有各种不满意的地方,有些会下拉卡住,有些回弹不流畅,有些性能太低会各种卡顿,有些emptyView无法下拉......
自己写的才是最合适自己的,代码很简单,
文章正文

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

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

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

PullableListView :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
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: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
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: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
<?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: 

1
2
3
4
5
6
7
8
9
10
<?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: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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: 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<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>

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


代码注释

作者:喵哥笔记

IDC笔记

学的不仅是技术,更是梦想!