-
-
Notifications
You must be signed in to change notification settings - Fork 5k
Expand file tree
/
Copy pathSmartRefreshLayout.java
More file actions
3757 lines (3514 loc) · 174 KB
/
SmartRefreshLayout.java
File metadata and controls
3757 lines (3514 loc) · 174 KB
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
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
package com.scwang.smartrefresh.layout;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.os.Build;
import android.os.Handler;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.NestedScrollingChildHelper;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.NestedScrollingParentHelper;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.widget.AbsListView;
import android.widget.Scroller;
import android.widget.TextView;
import android.widget.Toast;
import com.scwang.smartrefresh.layout.api.DefaultRefreshFooterCreator;
import com.scwang.smartrefresh.layout.api.DefaultRefreshHeaderCreator;
import com.scwang.smartrefresh.layout.api.DefaultRefreshInitializer;
import com.scwang.smartrefresh.layout.api.RefreshContent;
import com.scwang.smartrefresh.layout.api.RefreshFooter;
import com.scwang.smartrefresh.layout.api.RefreshHeader;
import com.scwang.smartrefresh.layout.api.RefreshInternal;
import com.scwang.smartrefresh.layout.api.RefreshKernel;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.api.ScrollBoundaryDecider;
import com.scwang.smartrefresh.layout.constant.DimensionStatus;
import com.scwang.smartrefresh.layout.constant.RefreshState;
import com.scwang.smartrefresh.layout.constant.SpinnerStyle;
import com.scwang.smartrefresh.layout.footer.BallPulseFooter;
import com.scwang.smartrefresh.layout.header.BezierRadarHeader;
import com.scwang.smartrefresh.layout.impl.RefreshContentWrapper;
import com.scwang.smartrefresh.layout.impl.RefreshFooterWrapper;
import com.scwang.smartrefresh.layout.impl.RefreshHeaderWrapper;
import com.scwang.smartrefresh.layout.listener.OnLoadMoreListener;
import com.scwang.smartrefresh.layout.listener.OnMultiPurposeListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshLoadMoreListener;
import com.scwang.smartrefresh.layout.listener.OnStateChangedListener;
import com.scwang.smartrefresh.layout.util.SmartUtil;
import static android.view.MotionEvent.obtain;
import static android.view.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.EXACTLY;
import static android.view.View.MeasureSpec.getSize;
import static android.view.View.MeasureSpec.makeMeasureSpec;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static com.scwang.smartrefresh.layout.util.SmartUtil.dp2px;
import static com.scwang.smartrefresh.layout.util.SmartUtil.fling;
import static com.scwang.smartrefresh.layout.util.SmartUtil.isContentView;
import static java.lang.System.currentTimeMillis;
/**
* 智能刷新布局
* Intelligent RefreshLayout
* Created by scwang on 2017/5/26.
*/
@SuppressLint("RestrictedApi")
@SuppressWarnings({"unused"})
public class SmartRefreshLayout extends ViewGroup implements RefreshLayout, NestedScrollingParent/*, NestedScrollingChild*/ {
//<editor-fold desc="属性变量 property and variable">
//<editor-fold desc="滑动属性">
protected int mTouchSlop;
protected int mSpinner;//当前的 Spinner
protected int mLastSpinner;//最后的,的Spinner
protected int mTouchSpinner;//触摸时候,的Spinner
protected int mFloorDuration = 300;//二楼展开时长
protected int mReboundDuration = 300;//回弹动画时长
protected int mScreenHeightPixels;//屏幕高度
protected float mTouchX;
protected float mTouchY;
protected float mLastTouchX;//用于实现Header的左右拖动效果
protected float mLastTouchY;//用于实现多点触摸
protected float mDragRate = .5f;
protected char mDragDirection = 'n';//拖动的方向 none-n horizontal-h vertical-v
protected boolean mIsBeingDragged;//是否正在拖动
protected boolean mSuperDispatchTouchEvent;//父类是否处理触摸事件
protected boolean mEnableDisallowIntercept;//是否允许拦截事件
protected int mFixedHeaderViewId = View.NO_ID;//固定在头部的视图Id
protected int mFixedFooterViewId = View.NO_ID;//固定在底部的视图Id
protected int mHeaderTranslationViewId = View.NO_ID;//下拉Header偏移的视图Id
protected int mFooterTranslationViewId = View.NO_ID;//下拉Footer偏移的视图Id
protected int mMinimumVelocity;
protected int mMaximumVelocity;
protected int mCurrentVelocity;
protected Scroller mScroller;
protected VelocityTracker mVelocityTracker;
protected Interpolator mReboundInterpolator;
//</editor-fold>
//<editor-fold desc="功能属性">
protected int[] mPrimaryColors;
protected boolean mEnableRefresh = true;
protected boolean mEnableLoadMore = false;
protected boolean mEnableClipHeaderWhenFixedBehind = true;//当 Header FixedBehind 时候是否剪裁遮挡 Header
protected boolean mEnableClipFooterWhenFixedBehind = true;//当 Footer FixedBehind 时候是否剪裁遮挡 Footer
protected boolean mEnableHeaderTranslationContent = true;//是否启用内容视图拖动效果
protected boolean mEnableFooterTranslationContent = true;//是否启用内容视图拖动效果
protected boolean mEnableFooterFollowWhenNoMoreData = false;//是否在全部加载结束之后Footer跟随内容 1.0.4-6
protected boolean mEnablePreviewInEditMode = true;//是否在编辑模式下开启预览功能
protected boolean mEnableOverScrollBounce = true;//是否启用越界回弹
protected boolean mEnableOverScrollDrag = false;//是否启用越界拖动(仿苹果效果)1.0.4-6
protected boolean mEnableAutoLoadMore = true;//是否在列表滚动到底部时自动加载更多
protected boolean mEnablePureScrollMode = false;//是否开启纯滚动模式
protected boolean mEnableScrollContentWhenLoaded = true;//是否在加载更多完成之后滚动内容显示新数据
protected boolean mEnableScrollContentWhenRefreshed = true;//是否在刷新完成之后滚动内容显示新数据
protected boolean mEnableLoadMoreWhenContentNotFull = true;//在内容不满一页的时候,是否可以上拉加载更多
protected boolean mEnableNestedScrolling = true;//是否启用潜逃滚动功能
protected boolean mDisableContentWhenRefresh = false;//是否开启在刷新时候禁止操作内容视图
protected boolean mDisableContentWhenLoading = false;//是否开启在刷新时候禁止操作内容视图
protected boolean mFooterNoMoreData = false;//数据是否全部加载完成,如果完成就不能在触发加载事件
protected boolean mFooterNoMoreDataEffective = false;//是否 NoMoreData 生效(有的 Footer 可能不支持)
protected boolean mManualLoadMore = false;//是否手动设置过LoadMore,用于智能开启
// protected boolean mManualNestedScrolling = false;//是否手动设置过 NestedScrolling,用于智能开启
protected boolean mManualHeaderTranslationContent = false;//是否手动设置过内容视图拖动效果
protected boolean mManualFooterTranslationContent = false;//是否手动设置过内容视图拖动效果
//</editor-fold>
//<editor-fold desc="监听属性">
protected OnRefreshListener mRefreshListener;
protected OnLoadMoreListener mLoadMoreListener;
protected OnMultiPurposeListener mOnMultiPurposeListener;
protected ScrollBoundaryDecider mScrollBoundaryDecider;
//</editor-fold>
//<editor-fold desc="嵌套滚动">
protected int mTotalUnconsumed;
protected boolean mNestedInProgress;
protected int[] mParentOffsetInWindow = new int[2];
protected NestedScrollingChildHelper mNestedChild = new NestedScrollingChildHelper(this);
protected NestedScrollingParentHelper mNestedParent = new NestedScrollingParentHelper(this);
//</editor-fold>
//<editor-fold desc="内部视图">
protected int mHeaderHeight; //头部高度 和 头部高度状态
protected DimensionStatus mHeaderHeightStatus = DimensionStatus.DefaultUnNotify;
protected int mFooterHeight; //底部高度 和 底部高度状态
protected DimensionStatus mFooterHeightStatus = DimensionStatus.DefaultUnNotify;
protected int mHeaderInsetStart; // Header 起始位置偏移
protected int mFooterInsetStart; // Footer 起始位置偏移
protected float mHeaderMaxDragRate = 2.5f; //最大拖动比率(最大高度/Header高度)
protected float mFooterMaxDragRate = 2.5f; //最大拖动比率(最大高度/Footer高度)
protected float mHeaderTriggerRate = 1.0f; //触发刷新距离 与 HeaderHeight 的比率
protected float mFooterTriggerRate = 1.0f; //触发加载距离 与 FooterHeight 的比率
protected RefreshInternal mRefreshHeader; //下拉头部视图
protected RefreshInternal mRefreshFooter; //上拉底部视图
protected RefreshContent mRefreshContent; //显示内容视图
//</editor-fold>
protected Paint mPaint;
protected Handler mHandler;
protected RefreshKernel mKernel = new RefreshKernelImpl();
/**
* 【主要状态】
* 面对 SmartRefresh 外部的滚动状态
*/
protected RefreshState mState = RefreshState.None; //主状态
/**
* 【附加状态】
* 用于主状态 mState 为 Refreshing 或 Loading 时的滚动状态
* 1.mState=Refreshing|Loading 时 mViceState 有可能与 mState 不同
* 2.mState=None,开启越界拖动 时 mViceState 有可能与 mState 不同
* 3.其他状态时与主状态相等 mViceState=mState
* 4.SmartRefresh 外部无法察觉 mViceState
*/
protected RefreshState mViceState = RefreshState.None; //副状态(主状态刷新时候的滚动状态)
protected long mLastOpenTime = 0; //上一次 刷新或者加载 时间
protected int mHeaderBackgroundColor = 0; //为Header绘制纯色背景
protected int mFooterBackgroundColor = 0;
protected boolean mHeaderNeedTouchEventWhenRefreshing; //为游戏Header提供独立事件
protected boolean mFooterNeedTouchEventWhenLoading;
protected boolean mAttachedToWindow; //是否添加到Window
protected boolean mFooterLocked = false;//Footer 正在loading 的时候是否锁住 列表不能向上滚动
protected static DefaultRefreshFooterCreator sFooterCreator = null;
protected static DefaultRefreshHeaderCreator sHeaderCreator = null;
protected static DefaultRefreshInitializer sRefreshInitializer = null;
protected static MarginLayoutParams sDefaultMarginLP = new MarginLayoutParams(-1,-1);
//</editor-fold>
//<editor-fold desc="构造方法 construction methods">
public SmartRefreshLayout(Context context) {
this(context, null);
}
public SmartRefreshLayout(Context context, AttributeSet attrs) {
super(context, attrs);
ViewConfiguration configuration = ViewConfiguration.get(context);
mHandler = new Handler();
mScroller = new Scroller(context);
mVelocityTracker = VelocityTracker.obtain();
mScreenHeightPixels = context.getResources().getDisplayMetrics().heightPixels;
mReboundInterpolator = new SmartUtil(SmartUtil.INTERPOLATOR_VISCOUS_FLUID);
mTouchSlop = configuration.getScaledTouchSlop();
mMinimumVelocity = configuration.getScaledMinimumFlingVelocity();
mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
mFooterHeight = SmartUtil.dp2px(60);
mHeaderHeight = SmartUtil.dp2px(100);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.SmartRefreshLayout);
if (!ta.hasValue(R.styleable.SmartRefreshLayout_android_clipToPadding)) {
super.setClipToPadding(false);
}
if (!ta.hasValue(R.styleable.SmartRefreshLayout_android_clipChildren)) {
super.setClipChildren(false);
}
if (sRefreshInitializer != null) {
sRefreshInitializer.initialize(context, this);//调用全局初始化
}
mDragRate = ta.getFloat(R.styleable.SmartRefreshLayout_srlDragRate, mDragRate);
mHeaderMaxDragRate = ta.getFloat(R.styleable.SmartRefreshLayout_srlHeaderMaxDragRate, mHeaderMaxDragRate);
mFooterMaxDragRate = ta.getFloat(R.styleable.SmartRefreshLayout_srlFooterMaxDragRate, mFooterMaxDragRate);
mHeaderTriggerRate = ta.getFloat(R.styleable.SmartRefreshLayout_srlHeaderTriggerRate, mHeaderTriggerRate);
mFooterTriggerRate = ta.getFloat(R.styleable.SmartRefreshLayout_srlFooterTriggerRate, mFooterTriggerRate);
mEnableRefresh = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableRefresh, mEnableRefresh);
mReboundDuration = ta.getInt(R.styleable.SmartRefreshLayout_srlReboundDuration, mReboundDuration);
mEnableLoadMore = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableLoadMore, mEnableLoadMore);
mHeaderHeight = ta.getDimensionPixelOffset(R.styleable.SmartRefreshLayout_srlHeaderHeight, mHeaderHeight);
mFooterHeight = ta.getDimensionPixelOffset(R.styleable.SmartRefreshLayout_srlFooterHeight, mFooterHeight);
mHeaderInsetStart = ta.getDimensionPixelOffset(R.styleable.SmartRefreshLayout_srlHeaderInsetStart, mHeaderInsetStart);
mFooterInsetStart = ta.getDimensionPixelOffset(R.styleable.SmartRefreshLayout_srlFooterInsetStart, mFooterInsetStart);
mDisableContentWhenRefresh = ta.getBoolean(R.styleable.SmartRefreshLayout_srlDisableContentWhenRefresh, mDisableContentWhenRefresh);
mDisableContentWhenLoading = ta.getBoolean(R.styleable.SmartRefreshLayout_srlDisableContentWhenLoading, mDisableContentWhenLoading);
mEnableHeaderTranslationContent = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableHeaderTranslationContent, mEnableHeaderTranslationContent);
mEnableFooterTranslationContent = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableFooterTranslationContent, mEnableFooterTranslationContent);
mEnablePreviewInEditMode = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnablePreviewInEditMode, mEnablePreviewInEditMode);
mEnableAutoLoadMore = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableAutoLoadMore, mEnableAutoLoadMore);
mEnableOverScrollBounce = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableOverScrollBounce, mEnableOverScrollBounce);
mEnablePureScrollMode = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnablePureScrollMode, mEnablePureScrollMode);
mEnableScrollContentWhenLoaded = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableScrollContentWhenLoaded, mEnableScrollContentWhenLoaded);
mEnableScrollContentWhenRefreshed = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableScrollContentWhenRefreshed, mEnableScrollContentWhenRefreshed);
mEnableLoadMoreWhenContentNotFull = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableLoadMoreWhenContentNotFull, mEnableLoadMoreWhenContentNotFull);
mEnableFooterFollowWhenNoMoreData = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableFooterFollowWhenLoadFinished, mEnableFooterFollowWhenNoMoreData);
mEnableFooterFollowWhenNoMoreData = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableFooterFollowWhenNoMoreData, mEnableFooterFollowWhenNoMoreData);
mEnableClipHeaderWhenFixedBehind = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableClipHeaderWhenFixedBehind, mEnableClipHeaderWhenFixedBehind);
mEnableClipFooterWhenFixedBehind = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableClipFooterWhenFixedBehind, mEnableClipFooterWhenFixedBehind);
mEnableOverScrollDrag = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableOverScrollDrag, mEnableOverScrollDrag);
mFixedHeaderViewId = ta.getResourceId(R.styleable.SmartRefreshLayout_srlFixedHeaderViewId, mFixedHeaderViewId);
mFixedFooterViewId = ta.getResourceId(R.styleable.SmartRefreshLayout_srlFixedFooterViewId, mFixedFooterViewId);
mHeaderTranslationViewId = ta.getResourceId(R.styleable.SmartRefreshLayout_srlHeaderTranslationViewId, mHeaderTranslationViewId);
mFooterTranslationViewId = ta.getResourceId(R.styleable.SmartRefreshLayout_srlFooterTranslationViewId, mFooterTranslationViewId);
mEnableNestedScrolling = ta.getBoolean(R.styleable.SmartRefreshLayout_srlEnableNestedScrolling, mEnableNestedScrolling);
mNestedChild.setNestedScrollingEnabled(mEnableNestedScrolling);
mManualLoadMore = mManualLoadMore || ta.hasValue(R.styleable.SmartRefreshLayout_srlEnableLoadMore);
mManualHeaderTranslationContent = mManualHeaderTranslationContent || ta.hasValue(R.styleable.SmartRefreshLayout_srlEnableHeaderTranslationContent);
mManualFooterTranslationContent = mManualFooterTranslationContent || ta.hasValue(R.styleable.SmartRefreshLayout_srlEnableFooterTranslationContent);
// mManualNestedScrolling = mManualNestedScrolling || ta.hasValue(R.styleable.SmartRefreshLayout_srlEnableNestedScrolling);
mHeaderHeightStatus = ta.hasValue(R.styleable.SmartRefreshLayout_srlHeaderHeight) ? DimensionStatus.XmlLayoutUnNotify : mHeaderHeightStatus;
mFooterHeightStatus = ta.hasValue(R.styleable.SmartRefreshLayout_srlFooterHeight) ? DimensionStatus.XmlLayoutUnNotify : mFooterHeightStatus;
int accentColor = ta.getColor(R.styleable.SmartRefreshLayout_srlAccentColor, 0);
int primaryColor = ta.getColor(R.styleable.SmartRefreshLayout_srlPrimaryColor, 0);
if (primaryColor != 0) {
if (accentColor != 0) {
mPrimaryColors = new int[]{primaryColor, accentColor};
} else {
mPrimaryColors = new int[]{primaryColor};
}
} else if (accentColor != 0) {
mPrimaryColors = new int[]{0, accentColor};
}
// if (mEnablePureScrollMode && !ta.hasValue(R.styleable.SmartRefreshLayout_srlEnableOverScrollDrag)) {
// /*
// * 前期【纯滚动模式】使用虚拟 Header 来实现,而后期添加的【越界拖动】功能,一样可以实现【纯滚动模式】
// * 所以取消 【纯滚动模式】 虚拟 Header 的实现,直接打开 【越界拖动】即可
// * 而不去掉【纯滚动模式】的原因是,纯滚动模式的定义和【越界拖动】不一致,
// * 【纯滚动模式】会阻止 Header 和 Footer 的出现,即没有Header和Footer,只有滚动
// * 【越界拖动】可以与 Header 和 Footer 共存,如 上面 Header,下面 越界,或者 上面越界,下面 Footer
// */
// mEnableOverScrollDrag = true;
// }
if (mEnablePureScrollMode && !mManualLoadMore && !mEnableLoadMore) {
mEnableLoadMore = true;
}
ta.recycle();
}
//</editor-fold>
//<editor-fold desc="生命周期 life cycle">
/**
* 重写 onFinishInflate 来完成 smart 的特定功能
* 1.智能寻找 Xml 中定义的 Content、Header、Footer
*/
@Override
public void onFinishInflate() {
super.onFinishInflate();
final int count = super.getChildCount();
if (count > 3) {
throw new RuntimeException("最多只支持3个子View,Most only support three sub view");
}
int contentLevel = 0;
int indexContent = -1;
for (int i = 0; i < count; i++) {
View view = super.getChildAt(i);
if (isContentView(view) && (contentLevel < 2 || i == 1)) {
indexContent = i;
contentLevel = 2;
} else if (!(view instanceof RefreshInternal) && contentLevel < 1) {
indexContent = i;
contentLevel = i > 0 ? 1 : 0;
}
}
int indexHeader = -1;
int indexFooter = -1;
if (indexContent >= 0) {
mRefreshContent = new RefreshContentWrapper(super.getChildAt(indexContent));
if (indexContent == 1) {
indexHeader = 0;
if (count == 3) {
indexFooter = 2;
}
} else if (count == 2) {
indexFooter = 1;
}
}
for (int i = 0; i < count; i++) {
View view = super.getChildAt(i);
if (i == indexHeader || (i != indexFooter && indexHeader == -1 && mRefreshHeader == null && view instanceof RefreshHeader)) {
mRefreshHeader = (view instanceof RefreshHeader) ? (RefreshHeader) view : new RefreshHeaderWrapper(view);
} else if (i == indexFooter || (indexFooter == -1 && view instanceof RefreshFooter)) {
mEnableLoadMore = (mEnableLoadMore || !mManualLoadMore);
mRefreshFooter = (view instanceof RefreshFooter) ? (RefreshFooter) view : new RefreshFooterWrapper(view);
// } else if (mRefreshContent == null) {
// mRefreshContent = new RefreshContentWrapper(view);
}
}
}
/**
* 重写 onAttachedToWindow 来完成 smart 的特定功能
* 1.添加默认或者全局设置的 Header 和 Footer (缺省情况下才会)
* 2.做 Content 为空时的 TextView 提示
* 3.智能开启 嵌套滚动 NestedScrollingEnabled
* 4.初始化 主题颜色 和 调整 Header Footer Content 的显示顺序
*/
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mAttachedToWindow = true;
final View thisView = this;
if (!thisView.isInEditMode()) {
if (mRefreshHeader == null) {
if (sHeaderCreator != null) {
setRefreshHeader(sHeaderCreator.createRefreshHeader(thisView.getContext(), this));
} else {
setRefreshHeader(new BezierRadarHeader(thisView.getContext()));
}
}
if (mRefreshFooter == null) {
if (sFooterCreator != null) {
setRefreshFooter(sFooterCreator.createRefreshFooter(thisView.getContext(), this));
} else {
boolean old = mEnableLoadMore;
setRefreshFooter(new BallPulseFooter(thisView.getContext()));
mEnableLoadMore = old;
}
} else {
mEnableLoadMore = mEnableLoadMore || !mManualLoadMore;
}
if (mRefreshContent == null) {
for (int i = 0, len = getChildCount(); i < len; i++) {
View view = getChildAt(i);
if ((mRefreshHeader == null || view != mRefreshHeader.getView())&&
(mRefreshFooter == null || view != mRefreshFooter.getView())) {
mRefreshContent = new RefreshContentWrapper(view);
}
}
}
if (mRefreshContent == null) {
final int padding = SmartUtil.dp2px(20);
final TextView errorView = new TextView(thisView.getContext());
errorView.setTextColor(0xffff6600);
errorView.setGravity(Gravity.CENTER);
errorView.setTextSize(20);
errorView.setText(R.string.srl_content_empty);
super.addView(errorView, 0, new LayoutParams(MATCH_PARENT, MATCH_PARENT));
mRefreshContent = new RefreshContentWrapper(errorView);
mRefreshContent.getView().setPadding(padding, padding, padding, padding);
}
View fixedHeaderView = thisView.findViewById(mFixedHeaderViewId);
View fixedFooterView = thisView.findViewById(mFixedFooterViewId);
mRefreshContent.setScrollBoundaryDecider(mScrollBoundaryDecider);
mRefreshContent.setEnableLoadMoreWhenContentNotFull(mEnableLoadMoreWhenContentNotFull);
mRefreshContent.setUpComponent(mKernel, fixedHeaderView, fixedFooterView);
if (mSpinner != 0) {
notifyStateChanged(RefreshState.None);
mRefreshContent.moveSpinner(mSpinner = 0, mHeaderTranslationViewId, mFooterTranslationViewId);
}
// if (!mManualNestedScrolling && !isNestedScrollingEnabled()) {
// post(new Runnable() {
// @Override
// public void run() {
// final View thisView = SmartRefreshLayout.this;
// for (ViewParent parent = thisView.getParent() ; parent != null ; ) {
// if (parent instanceof NestedScrollingParent) {
// View target = SmartRefreshLayout.this;
// //noinspection RedundantCast
// if (((NestedScrollingParent)parent).onStartNestedScroll(target,target,ViewCompat.SCROLL_AXIS_VERTICAL)) {
// setNestedScrollingEnabled(true);
// mManualNestedScrolling = false;
// break;
// }
// }
// if (parent instanceof View) {
// View thisParent = (View) parent;
// parent = thisParent.getParent();
// } else {
// break;
// }
// }
// }
// });
// }
}
if (mPrimaryColors != null) {
if (mRefreshHeader != null) {
mRefreshHeader.setPrimaryColors(mPrimaryColors);
}
if (mRefreshFooter != null) {
mRefreshFooter.setPrimaryColors(mPrimaryColors);
}
}
//重新排序
if (mRefreshContent != null) {
super.bringChildToFront(mRefreshContent.getView());
}
if (mRefreshHeader != null && mRefreshHeader.getSpinnerStyle().front) {
super.bringChildToFront(mRefreshHeader.getView());
}
if (mRefreshFooter != null && mRefreshFooter.getSpinnerStyle().front) {
super.bringChildToFront(mRefreshFooter.getView());
}
}
/**
* 测量 Header Footer Content
* 1.测量代码看起来很复杂,时因为 Header Footer 有四种拉伸变换样式 {@link SpinnerStyle},每一种样式有自己的测量方法
* 2.提供预览测量,可以在编辑 XML 的时候直接预览 (isInEditMode)
* 3.恢复水平触摸位置缓存 mLastTouchX 到屏幕中央
* @param widthMeasureSpec 水平测量参数
* @param heightMeasureSpec 竖直测量参数
*/
@Override
protected void onMeasure(final int widthMeasureSpec,final int heightMeasureSpec) {
int minimumHeight = 0;
final View thisView = this;
final boolean needPreview = thisView.isInEditMode() && mEnablePreviewInEditMode;
for (int i = 0, len = super.getChildCount(); i < len; i++) {
View child = super.getChildAt(i);
if (child.getVisibility() == GONE || child.getTag(R.string.srl_component_falsify) == child) {
continue;
}
if (mRefreshHeader != null && mRefreshHeader.getView() == child) {
final View headerView = mRefreshHeader.getView();
final ViewGroup.LayoutParams lp = headerView.getLayoutParams();
final MarginLayoutParams mlp = lp instanceof MarginLayoutParams ? (MarginLayoutParams)lp : sDefaultMarginLP;
final int widthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec, mlp.leftMargin + mlp.rightMargin, lp.width);
int height = mHeaderHeight;
if (mHeaderHeightStatus.ordinal < DimensionStatus.XmlLayoutUnNotify.ordinal) {
if (lp.height > 0) {
height = lp.height + mlp.bottomMargin + mlp.topMargin;
if (mHeaderHeightStatus.canReplaceWith(DimensionStatus.XmlExactUnNotify)) {
mHeaderHeight = lp.height + mlp.bottomMargin + mlp.topMargin;
mHeaderHeightStatus = DimensionStatus.XmlExactUnNotify;
}
} else if (lp.height == WRAP_CONTENT && (mRefreshHeader.getSpinnerStyle() != SpinnerStyle.MatchLayout || !mHeaderHeightStatus.notified)) {
final int maxHeight = Math.max(getSize(heightMeasureSpec) - mlp.bottomMargin - mlp.topMargin, 0);
headerView.measure(widthSpec, makeMeasureSpec(maxHeight, AT_MOST));
final int measuredHeight = headerView.getMeasuredHeight();
if (measuredHeight > 0) {
height = -1;
if (measuredHeight != (maxHeight) && mHeaderHeightStatus.canReplaceWith(DimensionStatus.XmlWrapUnNotify)) {
mHeaderHeight = measuredHeight + mlp.bottomMargin + mlp.topMargin;
mHeaderHeightStatus = DimensionStatus.XmlWrapUnNotify;
}
}
}
}
if (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.MatchLayout) {
height = getSize(heightMeasureSpec);
} else if (mRefreshHeader.getSpinnerStyle().scale && !needPreview) {
height = Math.max(0, isEnableRefreshOrLoadMore(mEnableRefresh) ? mSpinner : 0);
}
if (height != -1) {
headerView.measure(widthSpec, makeMeasureSpec(Math.max(height - mlp.bottomMargin - mlp.topMargin, 0), EXACTLY));
}
if (!mHeaderHeightStatus.notified) {
mHeaderHeightStatus = mHeaderHeightStatus.notified();
mRefreshHeader.onInitialized(mKernel, mHeaderHeight, (int) (mHeaderMaxDragRate * mHeaderHeight));
}
if (needPreview && isEnableRefreshOrLoadMore(mEnableRefresh)) {
minimumHeight += headerView.getMeasuredHeight();
}
}
if (mRefreshFooter != null && mRefreshFooter.getView() == child) {
final View footerView = mRefreshFooter.getView();
final ViewGroup.LayoutParams lp = footerView.getLayoutParams();
final MarginLayoutParams mlp = lp instanceof MarginLayoutParams ? (MarginLayoutParams)lp : sDefaultMarginLP;
final int widthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec, mlp.leftMargin + mlp.rightMargin, lp.width);
int height = mFooterHeight;
if (mFooterHeightStatus.ordinal < DimensionStatus.XmlLayoutUnNotify.ordinal) {
if (lp.height > 0) {
height = lp.height + mlp.topMargin + mlp.bottomMargin;
if (mFooterHeightStatus.canReplaceWith(DimensionStatus.XmlExactUnNotify)) {
mFooterHeight = lp.height + mlp.topMargin + mlp.bottomMargin;
mFooterHeightStatus = DimensionStatus.XmlExactUnNotify;
}
} else if (lp.height == WRAP_CONTENT && (mRefreshFooter.getSpinnerStyle() != SpinnerStyle.MatchLayout || !mFooterHeightStatus.notified)) {
int maxHeight = Math.max(getSize(heightMeasureSpec) - mlp.bottomMargin - mlp.topMargin, 0);
footerView.measure(widthSpec, makeMeasureSpec(maxHeight, AT_MOST));
int measuredHeight = footerView.getMeasuredHeight();
if (measuredHeight > 0) {
height = -1;
if (measuredHeight != (maxHeight) && mFooterHeightStatus.canReplaceWith(DimensionStatus.XmlWrapUnNotify)) {
mFooterHeight = measuredHeight + mlp.topMargin + mlp.bottomMargin;
mFooterHeightStatus = DimensionStatus.XmlWrapUnNotify;
}
}
}
}
if (mRefreshFooter.getSpinnerStyle() == SpinnerStyle.MatchLayout) {
height = getSize(heightMeasureSpec);
} else if (mRefreshFooter.getSpinnerStyle().scale && !needPreview) {
height = Math.max(0, isEnableRefreshOrLoadMore(mEnableLoadMore) ? -mSpinner : 0);
}
if (height != -1) {
footerView.measure(widthSpec, makeMeasureSpec(Math.max(height - mlp.bottomMargin - mlp.topMargin, 0), EXACTLY));
}
if (!mFooterHeightStatus.notified) {
mFooterHeightStatus = mFooterHeightStatus.notified();
mRefreshFooter.onInitialized(mKernel, mFooterHeight, (int) (mFooterMaxDragRate * mFooterHeight));
}
if (needPreview && isEnableRefreshOrLoadMore(mEnableLoadMore)) {
minimumHeight += footerView.getMeasuredHeight();
}
}
if (mRefreshContent != null && mRefreshContent.getView() == child) {
final View contentView = mRefreshContent.getView();
final ViewGroup.LayoutParams lp = contentView.getLayoutParams();
final MarginLayoutParams mlp = lp instanceof MarginLayoutParams ? (MarginLayoutParams)lp : sDefaultMarginLP;
final boolean showHeader = (mRefreshHeader != null && isEnableRefreshOrLoadMore(mEnableRefresh) && isEnableTranslationContent(mEnableHeaderTranslationContent, mRefreshHeader));
final boolean showFooter = (mRefreshFooter != null && isEnableRefreshOrLoadMore(mEnableLoadMore) && isEnableTranslationContent(mEnableFooterTranslationContent, mRefreshFooter));
final int widthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec,
thisView.getPaddingLeft() + thisView.getPaddingRight() + mlp.leftMargin + mlp.rightMargin, lp.width);
final int heightSpec = ViewGroup.getChildMeasureSpec(heightMeasureSpec,
thisView.getPaddingTop() + thisView.getPaddingBottom() + mlp.topMargin + mlp.bottomMargin +
((needPreview && showHeader) ? mHeaderHeight : 0) +
((needPreview && showFooter) ? mFooterHeight : 0), lp.height);
contentView.measure(widthSpec, heightSpec);
minimumHeight += contentView.getMeasuredHeight();
}
}
super.setMeasuredDimension(
View.resolveSize(super.getSuggestedMinimumWidth(), widthMeasureSpec),
View.resolveSize(minimumHeight, heightMeasureSpec));
mLastTouchX = thisView.getMeasuredWidth() / 2f;
}
/**
* 布局 Header Footer Content
* 1.布局代码看起来相对简单,时因为测量的时候,已经做了复杂的计算,布局的时候,直接按照测量结果,布局就可以了
* @param changed 是否改变
* @param l 左
* @param t 上
* @param r 右
* @param b 下
*/
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
final View thisView = this;
final int paddingLeft = thisView.getPaddingLeft();
final int paddingTop = thisView.getPaddingTop();
final int paddingBottom = thisView.getPaddingBottom();
for (int i = 0, len = super.getChildCount(); i < len; i++) {
View child = super.getChildAt(i);
if (child.getVisibility() == GONE || child.getTag(R.string.srl_component_falsify) == child) {
continue;
}
if (mRefreshContent != null && mRefreshContent.getView() == child) {
boolean isPreviewMode = thisView.isInEditMode() && mEnablePreviewInEditMode && isEnableRefreshOrLoadMore(mEnableRefresh) && mRefreshHeader != null;
final View contentView = mRefreshContent.getView();
final ViewGroup.LayoutParams lp = contentView.getLayoutParams();
final MarginLayoutParams mlp = lp instanceof MarginLayoutParams ? (MarginLayoutParams)lp : sDefaultMarginLP;
int left = paddingLeft + mlp.leftMargin;
int top = paddingTop + mlp.topMargin;
int right = left + contentView.getMeasuredWidth();
int bottom = top + contentView.getMeasuredHeight();
if (isPreviewMode && (isEnableTranslationContent(mEnableHeaderTranslationContent, mRefreshHeader))) {
top = top + mHeaderHeight;
bottom = bottom + mHeaderHeight;
}
contentView.layout(left, top, right, bottom);
}
if (mRefreshHeader != null && mRefreshHeader.getView() == child) {
boolean isPreviewMode = thisView.isInEditMode() && mEnablePreviewInEditMode && isEnableRefreshOrLoadMore(mEnableRefresh);
final View headerView = mRefreshHeader.getView();
final ViewGroup.LayoutParams lp = headerView.getLayoutParams();
final MarginLayoutParams mlp = lp instanceof MarginLayoutParams ? (MarginLayoutParams)lp : sDefaultMarginLP;
int left = mlp.leftMargin;
int top = mlp.topMargin + mHeaderInsetStart;
int right = left + headerView.getMeasuredWidth();
int bottom = top + headerView.getMeasuredHeight();
if (!isPreviewMode) {
if (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.Translate) {
top = top - mHeaderHeight;
bottom = bottom - mHeaderHeight;
/*
* SpinnerStyle.Scale headerView.getMeasuredHeight() 已经重复处理
**/
// } else if (mRefreshHeader.getSpinnerStyle().scale && mSpinner > 0) {
// bottom = top + Math.max(Math.max(0, isEnableRefreshOrLoadMore(mEnableRefresh) ? mSpinner : 0) - lp.bottomMargin - lp.topMargin, 0);
}
}
headerView.layout(left, top, right, bottom);
}
if (mRefreshFooter != null && mRefreshFooter.getView() == child) {
final boolean isPreviewMode = thisView.isInEditMode() && mEnablePreviewInEditMode && isEnableRefreshOrLoadMore(mEnableLoadMore);
final View footerView = mRefreshFooter.getView();
final ViewGroup.LayoutParams lp = footerView.getLayoutParams();
final MarginLayoutParams mlp = lp instanceof MarginLayoutParams ? (MarginLayoutParams)lp : sDefaultMarginLP;
final SpinnerStyle style = mRefreshFooter.getSpinnerStyle();
int left = mlp.leftMargin;
int top = mlp.topMargin + thisView.getMeasuredHeight() - mFooterInsetStart;
if (mFooterNoMoreData && mFooterNoMoreDataEffective && mEnableFooterFollowWhenNoMoreData && mRefreshContent != null
&& mRefreshFooter.getSpinnerStyle() == SpinnerStyle.Translate
&& isEnableRefreshOrLoadMore(mEnableLoadMore)) {
final View contentView = mRefreshContent.getView();
final ViewGroup.LayoutParams clp = contentView.getLayoutParams();
final int topMargin = clp instanceof MarginLayoutParams ? ((MarginLayoutParams)clp).topMargin : 0;
top = paddingTop + paddingTop + topMargin + contentView.getMeasuredHeight();
}
if (style == SpinnerStyle.MatchLayout) {
top = mlp.topMargin - mFooterInsetStart;
} else if (isPreviewMode
|| style == SpinnerStyle.FixedFront
|| style == SpinnerStyle.FixedBehind) {
top = top - mFooterHeight;
} else if (style.scale && mSpinner < 0) {
top = top - Math.max(isEnableRefreshOrLoadMore(mEnableLoadMore) ? -mSpinner : 0, 0);
}
int right = left + footerView.getMeasuredWidth();
int bottom = top + footerView.getMeasuredHeight();
footerView.layout(left, top, right, bottom);
}
}
}
/**
* 重写 onDetachedFromWindow 来完成 smart 的特定功能
* 1.恢复原始状态
* 2.清除动画数据 (防止内存泄露)
*/
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
mAttachedToWindow = false;
mKernel.moveSpinner(0, true);
notifyStateChanged(RefreshState.None);
if (mHandler != null) {
mHandler.removeCallbacksAndMessages(null);
}
mManualLoadMore = true;
// mManualNestedScrolling = true;
animationRunnable = null;
if (reboundAnimator != null) {
Animator animator = reboundAnimator;
animator.removeAllListeners();
reboundAnimator.removeAllUpdateListeners();
reboundAnimator.cancel();
reboundAnimator = null;
}
/*
* https://github.com/scwang90/SmartRefreshLayout/issues/716
* 在一些特殊情况下,当触发上拉加载更多后,
* 如果 onDetachedFromWindow 在 finishLoadMore 的 Runnable 执行之前被调用,
* 将会导致 mFooterLocked 一直为 true,再也无法上滑列表,
* 建议在 onDetachedFromWindow 方法中重置 mFooterLocked = false
*/
mFooterLocked = false;
}
/**
* 重写 drawChild 来完成 smart 的特定功能
* 1.为 Header 和 Footer 绘制背景 (设置了背景才绘制)
* 2.为 Header 和 Footer 在 FixedBehind 样式时,做剪裁功能 (mEnableClipHeaderWhenFixedBehind=true 才做)
* @param canvas 绘制发布
* @param child 需要绘制的子View
* @param drawingTime 绘制耗时
*/
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
final View thisView = this;
final View contentView = mRefreshContent != null ? mRefreshContent.getView() : null;
if (mRefreshHeader != null && mRefreshHeader.getView() == child) {
if (!isEnableRefreshOrLoadMore(mEnableRefresh) || (!mEnablePreviewInEditMode && thisView.isInEditMode())) {
return true;
}
if (contentView != null) {
int bottom = Math.max(contentView.getTop() + contentView.getPaddingTop() + mSpinner, child.getTop());
if (mHeaderBackgroundColor != 0 && mPaint != null) {
mPaint.setColor(mHeaderBackgroundColor);
if (mRefreshHeader.getSpinnerStyle().scale) {
bottom = child.getBottom();
} else if (mRefreshHeader.getSpinnerStyle() == SpinnerStyle.Translate) {
bottom = child.getBottom() + mSpinner;
}
canvas.drawRect(0, child.getTop(), thisView.getWidth(), bottom, mPaint);
}
if (mEnableClipHeaderWhenFixedBehind && mRefreshHeader.getSpinnerStyle() == SpinnerStyle.FixedBehind) {
canvas.save();
canvas.clipRect(child.getLeft(), child.getTop(), child.getRight(), bottom);
boolean ret = super.drawChild(canvas, child, drawingTime);
canvas.restore();
return ret;
}
}
}
if (mRefreshFooter != null && mRefreshFooter.getView() == child) {
if (!isEnableRefreshOrLoadMore(mEnableLoadMore) || (!mEnablePreviewInEditMode && thisView.isInEditMode())) {
return true;
}
if (contentView != null) {
int top = Math.min(contentView.getBottom() - contentView.getPaddingBottom() + mSpinner, child.getBottom());
if (mFooterBackgroundColor != 0 && mPaint != null) {
mPaint.setColor(mFooterBackgroundColor);
if (mRefreshFooter.getSpinnerStyle().scale) {
top = child.getTop();
} else if (mRefreshFooter.getSpinnerStyle() == SpinnerStyle.Translate) {
top = child.getTop() + mSpinner;
}
canvas.drawRect(0, top, thisView.getWidth(), child.getBottom(), mPaint);
}
if (mEnableClipFooterWhenFixedBehind && mRefreshFooter.getSpinnerStyle() == SpinnerStyle.FixedBehind) {
canvas.save();
canvas.clipRect(child.getLeft(), top, child.getRight(), child.getBottom());
boolean ret = super.drawChild(canvas, child, drawingTime);
canvas.restore();
return ret;
}
}
}
return super.drawChild(canvas, child, drawingTime);
}
//<editor-fold desc="惯性计算">
protected boolean mVerticalPermit = false; //竖直通信证(用于特殊事件的权限判定)
/**
* 重写 computeScroll 来完成 smart 的特定功能
* 1.越界回弹
* 2.边界碰撞
*/
@Override
public void computeScroll() {
int lastCurY = mScroller.getCurrY();
if (mScroller.computeScrollOffset()) {
int finalY = mScroller.getFinalY();
if ((finalY < 0 && (mEnableRefresh || mEnableOverScrollDrag) && mRefreshContent.canRefresh())
|| (finalY > 0 && (mEnableLoadMore || mEnableOverScrollDrag) && mRefreshContent.canLoadMore())) {
if(mVerticalPermit) {
float velocity;
if (Build.VERSION.SDK_INT >= 14) {
velocity = finalY > 0 ? -mScroller.getCurrVelocity() : mScroller.getCurrVelocity();
} else {
velocity = 1f * (mScroller.getCurrY() - finalY) / Math.max((mScroller.getDuration() - mScroller.timePassed()), 1);
}
animSpinnerBounce(velocity);
}
mScroller.forceFinished(true);
} else {
mVerticalPermit = true;//打开竖直通行证
final View thisView = this;
thisView.invalidate();
}
}
}
//</editor-fold>
//</editor-fold>
//<editor-fold desc="滑动判断 judgement of slide">
protected MotionEvent mFalsifyEvent = null;
/**
* 事件分发 (手势核心)
* 1.多点触摸
* 2.无缝衔接内容滚动
* @param e 事件
*/
@Override
public boolean dispatchTouchEvent(MotionEvent e) {
//<editor-fold desc="多点触摸计算代码">
//---------------------------------------------------------------------------
//多点触摸计算代码
//---------------------------------------------------------------------------
final int action = e.getActionMasked();
final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
final int skipIndex = pointerUp ? e.getActionIndex() : -1;
// Determine focal point
float sumX = 0, sumY = 0;
final int count = e.getPointerCount();
for (int i = 0; i < count; i++) {
if (skipIndex == i) continue;
sumX += e.getX(i);
sumY += e.getY(i);
}
final int div = pointerUp ? count - 1 : count;
final float touchX = sumX / div;
final float touchY = sumY / div;
if ((action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN)
&& mIsBeingDragged) {
mTouchY += touchY - mLastTouchY;
}
mLastTouchX = touchX;
mLastTouchY = touchY;
//---------------------------------------------------------------------------
//</editor-fold>
//---------------------------------------------------------------------------
//嵌套滚动模式辅助
//---------------------------------------------------------------------------
final View thisView = this;
if (mNestedInProgress) {//嵌套滚动时,补充竖直方向不滚动,但是水平方向滚动,需要通知 onHorizontalDrag
int totalUnconsumed = mTotalUnconsumed;
boolean ret = super.dispatchTouchEvent(e);
if (action == MotionEvent.ACTION_MOVE) {
if (totalUnconsumed == mTotalUnconsumed) {
final int offsetX = (int) mLastTouchX;
final int offsetMax = thisView.getWidth();
final float percentX = mLastTouchX / (offsetMax == 0 ? 1 : offsetMax);
if (isEnableRefreshOrLoadMore(mEnableRefresh) && mSpinner > 0 && mRefreshHeader != null && mRefreshHeader.isSupportHorizontalDrag()) {
mRefreshHeader.onHorizontalDrag(percentX, offsetX, offsetMax);
} else if (isEnableRefreshOrLoadMore(mEnableLoadMore) && mSpinner < 0 && mRefreshFooter != null && mRefreshFooter.isSupportHorizontalDrag()) {
mRefreshFooter.onHorizontalDrag(percentX, offsetX, offsetMax);
}
}
}
return ret;
} else if (!thisView.isEnabled()
|| (!mEnableRefresh && !mEnableLoadMore && !mEnableOverScrollDrag)
|| (mHeaderNeedTouchEventWhenRefreshing && ((mState.isOpening || mState.isFinishing) && mState.isHeader))
|| (mFooterNeedTouchEventWhenLoading && ((mState.isOpening || mState.isFinishing) && mState.isFooter))) {
return super.dispatchTouchEvent(e);
}
if (interceptAnimatorByAction(action) || mState.isFinishing
|| (mState == RefreshState.Loading && mDisableContentWhenLoading)
|| (mState == RefreshState.Refreshing && mDisableContentWhenRefresh)) {
return false;
}
// if (mEnableNestedScrollingOnly && mNestedChild.isNestedScrollingEnabled()) {
// return super.dispatchTouchEvent(e);
// }
//-------------------------------------------------------------------------//
//---------------------------------------------------------------------------
//传统模式滚动
//---------------------------------------------------------------------------
switch (action) {
case MotionEvent.ACTION_DOWN:
/*----------------------------------------------------*/
/* 速度追踪初始化 */
/*----------------------------------------------------*/
mCurrentVelocity = 0;
mVelocityTracker.addMovement(e);
mScroller.forceFinished(true);
/*----------------------------------------------------*/
/* 触摸事件初始化 */
/*----------------------------------------------------*/
mTouchX = touchX;
mTouchY = touchY;
mLastSpinner = 0;
mTouchSpinner = mSpinner;
mIsBeingDragged = false;
mEnableDisallowIntercept = false;
/*----------------------------------------------------*/
mSuperDispatchTouchEvent = super.dispatchTouchEvent(e);
if (mState == RefreshState.TwoLevel && mTouchY < 5 * thisView.getMeasuredHeight() / 6) {
mDragDirection = 'h';//二级刷新标记水平滚动来禁止拖动
return mSuperDispatchTouchEvent;
}
if (mRefreshContent != null) {
//为 RefreshContent 传递当前触摸事件的坐标,用于智能判断对应坐标位置View的滚动边界和相关信息
mRefreshContent.onActionDown(e);
}
return true;
case MotionEvent.ACTION_MOVE:
float dx = touchX - mTouchX;
float dy = touchY - mTouchY;
mVelocityTracker.addMovement(e);//速度追踪
if (!mIsBeingDragged && !mEnableDisallowIntercept && mDragDirection != 'h' && mRefreshContent != null) {//没有拖动之前,检测 canRefresh canLoadMore 来开启拖动
if (mDragDirection == 'v' || (Math.abs(dy) >= mTouchSlop && Math.abs(dx) < Math.abs(dy))) {//滑动允许最大角度为45度
mDragDirection = 'v';
if (dy > 0 && (mSpinner < 0 || ((mEnableOverScrollDrag || mEnableRefresh) && mRefreshContent.canRefresh()))) {
mIsBeingDragged = true;
mTouchY = touchY - mTouchSlop;//调整 mTouchSlop 偏差
} else if (dy < 0 && (mSpinner > 0 || ((mEnableOverScrollDrag || mEnableLoadMore) && ((mState==RefreshState.Loading&&mFooterLocked)||mRefreshContent.canLoadMore())))) {
mIsBeingDragged = true;
mTouchY = touchY + mTouchSlop;//调整 mTouchSlop 偏差
}
if (mIsBeingDragged) {
dy = touchY - mTouchY;//调整 mTouchSlop 偏差 重新计算 dy
if (mSuperDispatchTouchEvent) {//如果父类拦截了事件,发送一个取消事件通知
e.setAction(MotionEvent.ACTION_CANCEL);
super.dispatchTouchEvent(e);
}
mKernel.setState((mSpinner > 0 || (mSpinner == 0 && dy > 0)) ? RefreshState.PullDownToRefresh : RefreshState.PullUpToLoad);
final ViewParent parent = thisView.getParent();
if (parent instanceof ViewGroup) {
//修复问题 https://github.com/scwang90/SmartRefreshLayout/issues/580
//noinspection RedundantCast