endian11
2020-09-29 65f303d74eef06b3ceccac11c9fca1fe569506a9
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
package safeluck.drive.evaluation.customview;
 
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
 
import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
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 safeluck.drive.evaluation.R;
 
 
/**
 * 自定义的布局,用来管理三个子控件,其中一个是下拉头,一个是包含内容的pullableView(可以是实现Pullable接口的的任何View),
 * 还有一个上拉头,更多详解见博客http://blog.csdn.net/zhongkejingwang/article/details/38868463
 * 
 * @author 陈靖
 */
public class PullToRefreshLayout extends RelativeLayout {
    public static final String TAG = "PullToRefreshLayout";
    /**
     * last y
     */
    private int mLastMotionY;
    /**
     * last x
     */
    private int mLastMotionX;
    // 初始状态
    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:
            // 刷新成功
            SimpleDateFormat format = new SimpleDateFormat(
                    "yyyy-MM-dd HH:mm:ss");
            refreshStateImageView.setVisibility(View.VISIBLE);
            refreshStateTextView.setText("刷新成功   "+format
                    .format(new Date()));
            //            refreshStateTextView.setText(R.string.refresh_succeed+format
            //                    .format(new Date()));
            refreshStateImageView
            .setBackgroundResource(R.drawable.allview_refresh_succeed);
            break;
        case FAIL:
        default:
            // 刷新失败
            refreshStateImageView.setVisibility(View.VISIBLE);
            refreshStateTextView.setText(R.string.refresh_fail);
            refreshStateImageView
            .setBackgroundResource(R.drawable.allview_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.allview_load_succeed);
            break;
        case FAIL:
        default:
            // 加载失败
            loadStateImageView.setVisibility(View.VISIBLE);
            loadStateTextView.setText(R.string.load_fail);
            loadStateImageView
            .setBackgroundResource(R.drawable.allview_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;
    }
 
    @Override
    public boolean onInterceptTouchEvent(MotionEvent e) {
        int y = (int) e.getRawY();
        int x = (int) e.getRawX();
        switch (e.getAction()) {
        case MotionEvent.ACTION_DOWN:
            // 首先拦截down事件,记录y坐标
            mLastMotionY = y;
            mLastMotionX = x;
            break;
        case MotionEvent.ACTION_MOVE:
            // deltaY > 0 是向下运动< 0是向上运动
            int deltaY = y - mLastMotionY;
            int deltaX = x - mLastMotionX;
            if (Math.abs(deltaX * 3) > Math.abs(deltaY)) {
                return false;
            }
        case MotionEvent.ACTION_UP:
        case MotionEvent.ACTION_CANCEL:
            break;
        }
        return false;
    }
 
    /*
     * (非 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 (((PullInterface) 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 (((PullInterface) 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);
    }
 
}