Android下拉刷新上拉加载控件,对所有View通用! – Android移动开发技术文章_手机开发 – 红黑联盟

前面写过一篇关于下拉刷新控件的博客下拉刷新控件终结者:PullToRefreshLayout,后来看到好多人还有上拉加载更多的需求,于是就在前面下拉刷新控件的基础上进行了改进,加了上拉加载的功能。不仅如此,我已经把它改成了对所有View都通用!可以随心所欲使用这两个功能~~

我做了一个大集合的demo,实现了ListView、GridView、ExpandableListView、ScrollView、WebView、ImageView、TextView的下拉刷新和上拉加载。后面会提供demo的下载地址。

依照惯例,下面将会是一大波效果图:

demo首页也是可下拉的ListView,在底下可以加入table:

\

ListView:

\

GridView:

\

ExpandableListView:

\

ScrollView:

\

WebView:

\

ImageView:

\

TextView:

\

很不错吧?最后的ImageView和TextView是最简单的,直接在下面的接口方法里返回true。

增加上拉加载很简单,和管理下拉头一样,再多管理一个上拉头,也不费事;至于把它改成通用的就需要统一一下View的行为了,为此,我定义了这样一个接口:

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.jingchen.pulltorefresh.pullableview;
public interface Pullable
{
    /**
     * 判断是否可以下拉,如果不需要下拉功能可以直接return false
     *
     * @return true如果可以下拉否则返回false
     */
    boolean canPullDown();
    /**
     * 判断是否可以上拉,如果不需要上拉功能可以直接return false
     *
     * @return true如果可以上拉否则返回false
     */
    boolean canPullUp();
}

从接口名就可以看出它是一个提供判断是否可拉的方法的接口。这个接口的两个方法,canPullDown()是判断何时可以下拉的方法,canPullUp()则是判断何时可以上拉,我在demo中的判断是滑到顶部的时候可以下拉,滑到底部的时候可以上拉。所有需要上拉和下拉的View都需要实现这个接口。后面会给出一些View的实现。先来看看改进后的自定义的布局PullToRefreshLayout,增加了一个上拉头,下拉头和上拉头之间的View是实现了Pullable接口的pullableView。相比前面的版本,这里有改动的需要注意的地方如下:

 

1、增加了上拉头,相应的也增加了控制变量。

2、拉动时消除content_view事件防止误触发不再使用反射,直接设置 event.setAction(MotionEvent.ACTION_CANCEL)。

3、消除了拉动过程中的多点触碰导致的剧变。

4、不再设置content_view的onTouListener,让使用者可以更加自由的设置监听器。

这个PullToRefreshLayout只负责管理三个控件,如果一个View需要有上拉下拉功能则只需实现接口就行了。下面看PullToRefreshLayout的代码,注释写了好多:

 

?
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
package com.jingchen.pulltorefresh;
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.ViewGroup;
import android.view.animation.AnimationUtils;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.RelativeLayout;
import android.widget.TextView;
import com.jingchen.pulltorefresh.pullableview.Pullable;
/**
 * 自定义的布局,用来管理三个子控件,其中一个是下拉头,一个是包含内容的pullableView(可以是实现Pullable接口的的任何View),
 * 还有一个上拉头
 *
 * @author 陈靖
 */
public class PullToRefreshLayout extends RelativeLayout
{
    public static final String TAG = PullToRefreshLayout;
    // 初始状态
    public static final int INIT = 0;
    // 释放刷新
    public static final int RELEASE_TO_REFRESH = 1;
    // 正在刷新
    public static final int REFRESHING = 2;
    // 释放加载
    public static final int RELEASE_TO_LOAD = 3;
    // 正在加载
    public static final int LOADING = 4;
    // 操作完毕
    public static final int DONE = 5;
    // 当前状态
    private int state = INIT;
    // 刷新回调接口
    private OnRefreshListener mListener;
    // 刷新成功
    public static final int SUCCEED = 0;
    // 刷新失败
    public static final int FAIL = 1;
    // 按下Y坐标,上一个事件点Y坐标
    private float downY, lastY;
    // 下拉的距离。注意:pullDownY和pullUpY不可能同时不为0
    public float pullDownY = 0;
    // 上拉的距离
    private float pullUpY = 0;
    // 释放刷新的距离
    private float refreshDist = 200;
    // 释放加载的距离
    private float loadmoreDist = 200;
    private MyTimer timer;
    // 回滚速度
    public float MOVE_SPEED = 8;
    // 第一次执行布局
    private boolean isLayout = false;
    // 在刷新过程中滑动操作
    private boolean isTouch = false;
    // 手指滑动距离与下拉头的滑动距离比,中间会随正切函数变化
    private float radio = 2;
    // 下拉箭头的转180°动画
    private RotateAnimation rotateAnimation;
    // 均匀旋转动画
    private RotateAnimation refreshingAnimation;
    // 下拉头
    private View refreshView;
    // 下拉的箭头
    private View pullView;
    // 正在刷新的图标
    private View refreshingView;
    // 刷新结果图标
    private View refreshStateImageView;
    // 刷新结果:成功或失败
    private TextView refreshStateTextView;
    // 上拉头
    private View loadmoreView;
    // 上拉的箭头
    private View pullUpView;
    // 正在加载的图标
    private View loadingView;
    // 加载结果图标
    private View loadStateImageView;
    // 加载结果:成功或失败
    private TextView loadStateTextView;
    // 实现了Pullable接口的View
    private View pullableView;
    // 过滤多点触碰
    private int mEvents;
    // 这两个变量用来控制pull的方向,如果不加控制,当情况满足可上拉又可下拉时没法下拉
    private boolean canPullDown = true;
    private boolean canPullUp = true;
    /**
     * 执行自动回滚的handler
     */
    Handler updateHandler = new Handler()
    {
        @Override
        public void handleMessage(Message msg)
        {
            // 回弹速度随下拉距离moveDeltaY增大而增大
            MOVE_SPEED = (float) (8 + 5 * Math.tan(Math.PI / 2
                    / getMeasuredHeight() * (pullDownY + Math.abs(pullUpY))));
            if (!isTouch)
            {
                // 正在刷新,且没有往上推的话则悬停,显示正在刷新...
                if (state == REFRESHING && pullDownY <= refreshDist)
                {
                    pullDownY = refreshDist;
                    timer.cancel();
                } else if (state == LOADING && -pullUpY <= loadmoreDist)
                {
                    pullUpY = -loadmoreDist;
                    timer.cancel();
                }
            }
            if (pullDownY > 0)
                pullDownY -= MOVE_SPEED;
            else if (pullUpY < 0)
                pullUpY += MOVE_SPEED;
            if (pullDownY < 0)
            {
                // 已完成回弹
                pullDownY = 0;
                pullView.clearAnimation();
                // 隐藏下拉头时有可能还在刷新,只有当前状态不是正在刷新时才改变状态
                if (state != REFRESHING && state != LOADING)
                    changeState(INIT);
                timer.cancel();
            }
            if (pullUpY > 0)
            {
                // 已完成回弹
                pullUpY = 0;
                pullUpView.clearAnimation();
                // 隐藏下拉头时有可能还在刷新,只有当前状态不是正在刷新时才改变状态
                if (state != REFRESHING && state != LOADING)
                    changeState(INIT);
                timer.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 MyTimer(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 hide()
    {
        timer.schedule(5);
    }
    /**
     * 完成刷新操作,显示刷新结果。注意:刷新完成后一定要调用这个方法
     */
    /**
     * @param refreshResult
     *            PullToRefreshLayout.SUCCEED代表成功,PullToRefreshLayout.FAIL代表失败
     */
    public void refreshFinish(int refreshResult)
    {
        refreshingView.clearAnimation();
        refreshingView.setVisibility(View.GONE);
        switch (refreshResult)
        {
        case SUCCEED:
            // 刷新成功
            refreshStateImageView.setVisibility(View.VISIBLE);
            refreshStateTextView.setText(R.string.refresh_succeed);
            refreshStateImageView
                    .setBackgroundResource(R.drawable.refresh_succeed);
            break;
        case FAIL:
        default:
            // 刷新失败
            refreshStateImageView.setVisibility(View.VISIBLE);
            refreshStateTextView.setText(R.string.refresh_fail);
            refreshStateImageView
                    .setBackgroundResource(R.drawable.refresh_failed);
            break;
        }
        // 刷新结果停留1秒
        new Handler()
        {
            @Override
            public void handleMessage(Message msg)
            {
                changeState(DONE);
                hide();
            }
        }.sendEmptyMessageDelayed(0, 1000);
    }
    /**
     * 加载完毕,显示加载结果。注意:加载完成后一定要调用这个方法
     *
     * @param refreshResult
     *            PullToRefreshLayout.SUCCEED代表成功,PullToRefreshLayout.FAIL代表失败
     */
    public void loadmoreFinish(int refreshResult)
    {
        loadingView.clearAnimation();
        loadingView.setVisibility(View.GONE);
        switch (refreshResult)
        {
        case SUCCEED:
            // 加载成功
            loadStateImageView.setVisibility(View.VISIBLE);
            loadStateTextView.setText(R.string.load_succeed);
            loadStateImageView.setBackgroundResource(R.drawable.load_succeed);
            break;
        case FAIL:
        default:
            // 加载失败
            loadStateImageView.setVisibility(View.VISIBLE);
            loadStateTextView.setText(R.string.load_fail);
            loadStateImageView.setBackgroundResource(R.drawable.load_failed);
            break;
        }
        // 刷新结果停留1秒
        new Handler()
        {
            @Override
            public void handleMessage(Message msg)
            {
                changeState(DONE);
                hide();
            }
        }.sendEmptyMessageDelayed(0, 1000);
    }
    private void changeState(int to)
    {
        state = to;
        switch (state)
        {
        case INIT:
            // 下拉布局初始状态
            refreshStateImageView.setVisibility(View.GONE);
            refreshStateTextView.setText(R.string.pull_to_refresh);
            pullView.clearAnimation();
            pullView.setVisibility(View.VISIBLE);
            // 上拉布局初始状态
            loadStateImageView.setVisibility(View.GONE);
            loadStateTextView.setText(R.string.pullup_to_load);
            pullUpView.clearAnimation();
            pullUpView.setVisibility(View.VISIBLE);
            break;
        case RELEASE_TO_REFRESH:
            // 释放刷新状态
            refreshStateTextView.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);
            refreshStateTextView.setText(R.string.refreshing);
            break;
        case RELEASE_TO_LOAD:
            // 释放加载状态
            loadStateTextView.setText(R.string.release_to_load);
            pullUpView.startAnimation(rotateAnimation);
            break;
        case LOADING:
            // 正在加载状态
            pullUpView.clearAnimation();
            loadingView.setVisibility(View.VISIBLE);
            pullUpView.setVisibility(View.INVISIBLE);
            loadingView.startAnimation(refreshingAnimation);
            loadStateTextView.setText(R.string.loading);
            break;
        case DONE:
            // 刷新或加载完毕,啥都不做
            break;
        }
    }
    /**
     * 不限制上拉或下拉
     */
    private void releasePull()
    {
        canPullDown = true;
        canPullUp = true;
    }
    /*
     * (非 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;
            timer.cancel();
            mEvents = 0;
            releasePull();
            break;
        case MotionEvent.ACTION_POINTER_DOWN:
        case MotionEvent.ACTION_POINTER_UP:
            // 过滤多点触碰
            mEvents = -1;
            break;
        case MotionEvent.ACTION_MOVE:
            if (mEvents == 0)
            {
                if (((Pullable) pullableView).canPullDown() && canPullDown
                        && state != LOADING)
                {
                    // 可以下拉,正在加载时不能下拉
                    // 对实际滑动距离做缩小,造成用力拉的感觉
                    pullDownY = pullDownY + (ev.getY() - lastY) / radio;
                    if (pullDownY < 0)
                    {
                        pullDownY = 0;
                        canPullDown = false;
                        canPullUp = true;
                    }
                    if (pullDownY > getMeasuredHeight())
                        pullDownY = getMeasuredHeight();
                    if (state == REFRESHING)
                    {
                        // 正在刷新的时候触摸移动
                        isTouch = true;
                    }
                } else if (((Pullable) pullableView).canPullUp() && canPullUp
                        && state != REFRESHING)
                {
                    // 可以上拉,正在刷新时不能上拉
                    pullUpY = pullUpY + (ev.getY() - lastY) / radio;
                    if (pullUpY > 0)
                    {
                        pullUpY = 0;
                        canPullDown = true;
                        canPullUp = false;
                    }
                    if (pullUpY < -getMeasuredHeight())
                        pullUpY = -getMeasuredHeight();
                    if (state == LOADING)
                    {
                        // 正在加载的时候触摸移动
                        isTouch = true;
                    }
                } else
                    releasePull();
            } else
                mEvents = 0;
            lastY = ev.getY();
            // 根据下拉距离改变比例
            radio = (float) (2 + 2 * Math.tan(Math.PI / 2 / getMeasuredHeight()
                    * (pullDownY + Math.abs(pullUpY))));
            requestLayout();
            if (pullDownY <= refreshDist && state == RELEASE_TO_REFRESH)
            {
                // 如果下拉距离没达到刷新的距离且当前状态是释放刷新,改变状态为下拉刷新
                changeState(INIT);
            }
            if (pullDownY >= refreshDist && state == INIT)
            {
                // 如果下拉距离达到刷新的距离且当前状态是初始状态刷新,改变状态为释放刷新
                changeState(RELEASE_TO_REFRESH);
            }
            // 下面是判断上拉加载的,同上,注意pullUpY是负值
            if (-pullUpY <= loadmoreDist && state == RELEASE_TO_LOAD)
            {
                changeState(INIT);
            }
            if (-pullUpY >= loadmoreDist && state == INIT)
            {
                changeState(RELEASE_TO_LOAD);
            }
            // 因为刷新和加载操作不能同时进行,所以pullDownY和pullUpY不会同时不为0,因此这里用(pullDownY +
            // Math.abs(pullUpY))就可以不对当前状态作区分了
            if ((pullDownY + Math.abs(pullUpY)) > 8)
            {
                // 防止下拉过程中误触发长按事件和点击事件
                ev.setAction(MotionEvent.ACTION_CANCEL);
            }
            break;
        case MotionEvent.ACTION_UP:
            if (pullDownY > refreshDist || -pullUpY > loadmoreDist)
                // 正在刷新时往下拉(正在加载时往上拉),释放后下拉头(上拉头)不隐藏
                isTouch = false;
            if (state == RELEASE_TO_REFRESH)
            {
                changeState(REFRESHING);
                // 刷新操作
                if (mListener != null)
                    mListener.onRefresh(this);
            } else if (state == RELEASE_TO_LOAD)
            {
                changeState(LOADING);
                // 加载操作
                if (mListener != null)
                    mListener.onLoadMore(this);
            }
            hide();
        default:
            break;
        }
        // 事件分发交给父类
        super.dispatchTouchEvent(ev);
        return true;
    }
    private void initView()
    {
        // 初始化下拉布局
        pullView = refreshView.findViewById(R.id.pull_icon);
        refreshStateTextView = (TextView) refreshView
                .findViewById(R.id.state_tv);
        refreshingView = refreshView.findViewById(R.id.refreshing_icon);
        refreshStateImageView = refreshView.findViewById(R.id.state_iv);
        // 初始化上拉布局
        pullUpView = loadmoreView.findViewById(R.id.pullup_icon);
        loadStateTextView = (TextView) loadmoreView
                .findViewById(R.id.loadstate_tv);
        loadingView = loadmoreView.findViewById(R.id.loading_icon);
        loadStateImageView = loadmoreView.findViewById(R.id.loadstate_iv);
    }
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b)
    {
        if (!isLayout)
        {
            // 这里是第一次进来的时候做一些初始化
            refreshView = getChildAt(0);
            pullableView = getChildAt(1);
            loadmoreView = getChildAt(2);
            isLayout = true;
            initView();
            refreshDist = ((ViewGroup) refreshView).getChildAt(0)
                    .getMeasuredHeight();
            loadmoreDist = ((ViewGroup) loadmoreView).getChildAt(0)
                    .getMeasuredHeight();
        }
        // 改变子控件的布局,这里直接用(pullDownY + pullUpY)作为偏移量,这样就可以不对当前状态作区分
        refreshView.layout(0,
                (int) (pullDownY + pullUpY) - refreshView.getMeasuredHeight(),
                refreshView.getMeasuredWidth(), (int) (pullDownY + pullUpY));
        pullableView.layout(0, (int) (pullDownY + pullUpY),
                pullableView.getMeasuredWidth(), (int) (pullDownY + pullUpY)
                        + pullableView.getMeasuredHeight());
        loadmoreView.layout(0,
                (int) (pullDownY + pullUpY) + pullableView.getMeasuredHeight(),
                loadmoreView.getMeasuredWidth(),
                (int) (pullDownY + pullUpY) + pullableView.getMeasuredHeight()
                        + loadmoreView.getMeasuredHeight());
    }
    class MyTimer
    {
        private Handler handler;
        private Timer timer;
        private MyTask mTask;
        public MyTimer(Handler handler)
        {
            this.handler = handler;
            timer = new Timer();
        }
        public void schedule(long period)
        {
            if (mTask != null)
            {
                mTask.cancel();
                mTask = null;
            }
            mTask = new MyTask(handler);
            timer.schedule(mTask, 0, period);
        }
        public void cancel()
        {
            if (mTask != null)
            {
                mTask.cancel();
                mTask = null;
            }
        }
        class MyTask extends TimerTask
        {
            private Handler handler;
            public MyTask(Handler handler)
            {
                this.handler = handler;
            }
            @Override
            public void run()
            {
                handler.obtainMessage().sendToTarget();
            }
        }
    }
    /**
     * 刷新加载回调接口
     *
     * @author chenjing
     *
     */
    public interface OnRefreshListener
    {
        /**
         * 刷新操作
         */
        void onRefresh(PullToRefreshLayout pullToRefreshLayout);
        /**
         * 加载操作
         */
        void onLoadMore(PullToRefreshLayout pullToRefreshLayout);
    }
}

上面就是整个布局的代码,并不是很难。

 

下面看各个View对Pullable接口的实现,ListView和GridView还有ExpandableListView的判断方法是一样的:

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
package com.jingchen.pulltorefresh.pullableview;
import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.widget.ListView;
public class PullableListView extends ListView implements Pullable
{
    public PullableListView(Context context)
    {
        super(context);
    }
    public PullableListView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    public PullableListView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }
    @Override
    public boolean canPullDown()
    {
        if (getCount() == 0)
        {
            // 没有item的时候也可以下拉刷新
            return true;
        } else if (getFirstVisiblePosition() == 0
                && getChildAt(0).getTop() >= 0)
        {
            // 滑到ListView的顶部了
            return true;
        } else
            return false;
    }
    @Override
    public boolean canPullUp()
    {
        if (getCount() == 0)
        {
            // 没有item的时候也可以上拉加载
            return true;
        } else if (getLastVisiblePosition() == (getCount() - 1))
        {
            // 滑到底部了
            if (getChildAt(getLastVisiblePosition() - getFirstVisiblePosition()) != null
                    && getChildAt(
                            getLastVisiblePosition()
                                    - getFirstVisiblePosition()).getBottom() <= getMeasuredHeight())
                return true;
        }
        return false;
    }
}

 

 

PullableExpandableListView:

 

?
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
package com.jingchen.pulltorefresh.pullableview;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ExpandableListView;
public class PullableExpandableListView extends ExpandableListView implements
        Pullable
{
    public PullableExpandableListView(Context context)
    {
        super(context);
    }
    public PullableExpandableListView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    public PullableExpandableListView(Context context, AttributeSet attrs,
            int defStyle)
    {
        super(context, attrs, defStyle);
    }
    @Override
    public boolean canPullDown()
    {
        if (getCount() == 0)
        {
            // 没有item的时候也可以下拉刷新
            return true;
        } else if (getFirstVisiblePosition() == 0
                && getChildAt(0).getTop() >= 0)
        {
            // 滑到顶部了
            return true;
        } else
            return false;
    }
    @Override
    public boolean canPullUp()
    {
        if (getCount() == 0)
        {
            // 没有item的时候也可以上拉加载
            return true;
        } else if (getLastVisiblePosition() == (getCount() - 1))
        {
            // 滑到底部了
            if (getChildAt(getLastVisiblePosition() - getFirstVisiblePosition()) != null
                    && getChildAt(
                            getLastVisiblePosition()
                                    - getFirstVisiblePosition()).getBottom() <= getMeasuredHeight())
                return true;
        }
        return false;
    }
}

 

 

PullableGridView:

 

?
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
package com.jingchen.pulltorefresh.pullableview;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.GridView;
public class PullableGridView extends GridView implements Pullable
{
    public PullableGridView(Context context)
    {
        super(context);
    }
    public PullableGridView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    public PullableGridView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }
    @Override
    public boolean canPullDown()
    {
        if (getCount() == 0)
        {
            // 没有item的时候也可以下拉刷新
            return true;
        } else if (getFirstVisiblePosition() == 0
                && getChildAt(0).getTop() >= 0)
        {
            // 滑到顶部了
            return true;
        } else
            return false;
    }
    @Override
    public boolean canPullUp()
    {
        if (getCount() == 0)
        {
            // 没有item的时候也可以上拉加载
            return true;
        } else if (getLastVisiblePosition() == (getCount() - 1))
        {
            // 滑到底部了
            if (getChildAt(getLastVisiblePosition() - getFirstVisiblePosition()) != null
                    && getChildAt(
                            getLastVisiblePosition()
                                    - getFirstVisiblePosition()).getBottom() <= getMeasuredHeight())
                return true;
        }
        return false;
    }
}

 

PullableScrollView:

 

 

?
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
package com.jingchen.pulltorefresh.pullableview;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.ScrollView;
public class PullableScrollView extends ScrollView implements Pullable
{
    public PullableScrollView(Context context)
    {
        super(context);
    }
    public PullableScrollView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    public PullableScrollView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }
    @Override
    public boolean canPullDown()
    {
        if (getScrollY() == 0)
            return true;
        else
            return false;
    }
    @Override
    public boolean canPullUp()
    {
        if (getScrollY() >= (getChildAt(0).getHeight() - getMeasuredHeight()))
            return true;
        else
            return false;
    }
}

 

PullableWebView:

 

 

?
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
package com.jingchen.pulltorefresh.pullableview;
import android.content.Context;
import android.util.AttributeSet;
import android.webkit.WebView;
public class PullableWebView extends WebView implements Pullable
{
    public PullableWebView(Context context)
    {
        super(context);
    }
    public PullableWebView(Context context, AttributeSet attrs)
    {
        super(context, attrs);
    }
    public PullableWebView(Context context, AttributeSet attrs, int defStyle)
    {
        super(context, attrs, defStyle);
    }
    @Override
    public boolean canPullDown()
    {
        if (getScrollY() == 0)
            return true;
        else
            return false;
    }
    @Override
    public boolean canPullUp()
    {
        if (getScrollY() >= getContentHeight() * getScale()
                - getMeasuredHeight())
            return true;
        else
            return false;
    }
}

 

ImageView和TextView就不贴了,我直接在方法里返回了true。

 

OK了,整个demo的代码有点多,就不贴了。

源码下载

来源URL:http://www.2cto.com/kf/201408/329317.html