Commit 6f0384a21a74eb2cf9d678ce6a4b540581f231fa
0 parents
init
Showing
38 changed files
with
3119 additions
and
0 deletions
.gitignore
0 → 100644
README.md
0 → 100644
1 | +++ a/README.md | |
1 | +# react-native-rk-pull-to-refresh(ios/android) | |
2 | +[中文说明](https://github.com/hzl123456/react-native-rk-pull-to-refresh/blob/master/README-ZH.md) <br><br> | |
3 | +A pull to refresh component for react-native, same api on both android and ios,also you can design you owner pull style for this component.you can use it for most of the component in react-native such as view,scrollview,listview and flatlist. | |
4 | +## Preview | |
5 | +![ios](https://github.com/hzl123456/react-native-rk-pull-to-refresh/blob/master/image/ios.gif) <br><br> | |
6 | +![android](https://github.com/hzl123456/react-native-rk-pull-to-refresh/blob/master/image/android.gif) | |
7 | +## Installation | |
8 | +npm install react-native-rk-pull-to-refresh --save <br> | |
9 | +## How to use | |
10 | +it contains PullView,PullScrollView,PullListView and PullFlatList.if you want to use PullFlatList,you should use this component whith React Native 0.43 and newer.then you must add this to FlatList(node_modules/react-native/Libraries/Lists/FlatList.js) | |
11 | +``` | |
12 | +... | |
13 | +getScrollMetrics = () => { | |
14 | + return this._listRef.getScrollMetrics() | |
15 | +} | |
16 | +... | |
17 | +``` | |
18 | +and add this to VirtualizedList(node_modules/react-native/Libraries/Lists/VirtualizedList.js) | |
19 | +``` | |
20 | +... | |
21 | + getScrollMetrics = () => { | |
22 | + return this._scrollMetrics | |
23 | + } | |
24 | + ... | |
25 | +``` | |
26 | +### Use it for Listview with default style | |
27 | +``` | |
28 | +import React, {PureComponent} from 'react'; | |
29 | +import {ListView, View, Text, Dimensions} from 'react-native'; | |
30 | +import {PullListView} from 'react-native-rk-pull-to-refresh' | |
31 | + | |
32 | +const width = Dimensions.get('window').width | |
33 | + | |
34 | +export default class PullListViewDemo extends PureComponent { | |
35 | + | |
36 | + constructor(props) { | |
37 | + super(props); | |
38 | + this.dataSource = | |
39 | + new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}).cloneWithRows(this.getDataSource()) | |
40 | + } | |
41 | + | |
42 | + getDataSource = () => { | |
43 | + let array = new Array(); | |
44 | + for (let i = 0; i < 50; i++) { | |
45 | + array.push(`ListViewItem:${i + 1}`); | |
46 | + } | |
47 | + return array; | |
48 | + } | |
49 | + | |
50 | + render() { | |
51 | + return ( | |
52 | + <PullListView | |
53 | + ref={(c)=>this.pull=c} | |
54 | + isContentScroll={true} | |
55 | + style={{flex: 1, width: width}} | |
56 | + onPushing={this.props.onPushing} | |
57 | + onPullRelease={this._onPullRelease} | |
58 | + dataSource={this.dataSource} | |
59 | + renderRow={this._renderRow}/> | |
60 | + ) | |
61 | + } | |
62 | + | |
63 | + _onPullRelease = () => { | |
64 | + setTimeout(() => { | |
65 | + this.pull && this.pull.resolveHandler() | |
66 | + }, 2000) | |
67 | + } | |
68 | + | |
69 | + _renderRow = (rowData) => { | |
70 | + return ( | |
71 | + <View style={{flex: 1, height: 50, justifyContent: 'center', alignItems: 'center'}}> | |
72 | + <Text>{rowData}</Text> | |
73 | + </View>); | |
74 | + } | |
75 | + | |
76 | + componentDidMount() { | |
77 | + this.pull && this.pull.beginRefresh() | |
78 | + } | |
79 | +} | |
80 | + | |
81 | +``` | |
82 | +### Use it for View with you owner style | |
83 | +``` | |
84 | +import React, {PureComponent} from 'react'; | |
85 | +import {View, Text, Dimensions,StyleSheet,ActivityIndicator} from 'react-native'; | |
86 | +import {PullView} from 'react-native-rk-pull-to-refresh' | |
87 | + | |
88 | +const width = Dimensions.get('window').width | |
89 | +const topIndicatorHeight = 50 | |
90 | + | |
91 | +export default class PullViewDemo extends PureComponent { | |
92 | + | |
93 | + render() { | |
94 | + return ( | |
95 | + <PullView | |
96 | + ref={(c) => this.pull = c} | |
97 | + style={{flex: 1, width: width}} | |
98 | + topIndicatorRender={this.topIndicatorRender} | |
99 | + topIndicatorHeight={topIndicatorHeight} | |
100 | + onPullStateChangeHeight={this.onPullStateChangeHeight} | |
101 | + onPushing={this.props.onPushing} | |
102 | + onPullRelease={this._onPullRelease}> | |
103 | + | |
104 | + <Text style={{flex: 1, width: width, paddingTop: 200, textAlign: 'center'}}>这是内容</Text> | |
105 | + | |
106 | + </PullView> | |
107 | + ) | |
108 | + } | |
109 | + | |
110 | + onPullStateChangeHeight = (pulling, pullok, pullrelease, moveHeight) => { | |
111 | + if (pulling) { | |
112 | + this.txtPulling && this.txtPulling.setNativeProps({style: styles.show}); | |
113 | + this.txtPullok && this.txtPullok.setNativeProps({style: styles.hide}); | |
114 | + this.txtPullrelease && this.txtPullrelease.setNativeProps({style: styles.hide}); | |
115 | + } else if (pullok) { | |
116 | + this.txtPulling && this.txtPulling.setNativeProps({style: styles.hide}); | |
117 | + this.txtPullok && this.txtPullok.setNativeProps({style: styles.show}); | |
118 | + this.txtPullrelease && this.txtPullrelease.setNativeProps({style: styles.hide}); | |
119 | + } else if (pullrelease) { | |
120 | + this.txtPulling && this.txtPulling.setNativeProps({style: styles.hide}); | |
121 | + this.txtPullok && this.txtPullok.setNativeProps({style: styles.hide}); | |
122 | + this.txtPullrelease && this.txtPullrelease.setNativeProps({style: styles.show}); | |
123 | + } | |
124 | + } | |
125 | + | |
126 | + | |
127 | + topIndicatorRender = () => { | |
128 | + return ( | |
129 | + <View style={{flexDirection: 'row', justifyContent: 'center', alignItems: 'center', height: topIndicatorHeight}}> | |
130 | + <ActivityIndicator size="small" color="gray" style={{marginRight: 5}}/> | |
131 | + <Text ref={(c) => {this.txtPulling = c;}} style={styles.hide}>pulling...</Text> | |
132 | + <Text ref={(c) => {this.txtPullok = c;}} style={styles.hide}>pullok...</Text> | |
133 | + <Text ref={(c) => {this.txtPullrelease = c;}} style={styles.hide}>pullrelease...</Text> | |
134 | + </View> | |
135 | + ); | |
136 | + } | |
137 | + | |
138 | + | |
139 | + _onPullRelease = () => { | |
140 | + setTimeout(() => { | |
141 | + this.pull && this.pull.resolveHandler() | |
142 | + }, 2000) | |
143 | + } | |
144 | + | |
145 | + componentDidMount() { | |
146 | + this.pull && this.pull.beginRefresh() | |
147 | + } | |
148 | +} | |
149 | + | |
150 | +const styles = StyleSheet.create({ | |
151 | + hide: { | |
152 | + position: 'absolute', | |
153 | + left: 10000, | |
154 | + backgroundColor: 'transparent' | |
155 | + }, | |
156 | + show: { | |
157 | + position: 'relative', | |
158 | + left: 0, | |
159 | + backgroundColor: 'transparent' | |
160 | + } | |
161 | +}); | |
162 | +``` | |
163 | +## Full Demo | |
164 | +clone or download PullToRefreshDemo | |
165 | +## Props | |
166 | +Porp|Type|Optional|Default|Description | |
167 | +---- | ---- | ------- | ------- | ------------ | |
168 | +refreshable | bool | yes | true |can pull to refresh or not | |
169 | +isContentScroll | bool | yes |false|content scroll when pulling | |
170 | +onPullRelease | func |yes | | when refreshing, this function will be called | |
171 | +topIndicatorRender |func |yes | |top pulling render for this component,when the value is undefined,this component use default top pulling render | |
172 | +topIndicatorHeight |number |yes | |top pulling render header,when topIndicatorRender is not undefined,you must set the correct topIndicatorHeight | |
173 | +onPullStateChangeHeight |func|yes| |when pulling, this function will be called | |
174 | +onPushing|func|yes| |when pulling, this function will be called | |
175 | +## Method | |
176 | +beginRefresh():force begin pull down refresh <br> | |
177 | +resolveHandler():end pull down refresh | |
178 | + | |
179 | + | |
180 | + | |
181 | + | |
182 | + | |
183 | + | |
184 | + | |
185 | + | |
186 | + | ... | ... |
android/.gradle/7.4.2/checksums/checksums.lock
0 → 100644
No preview for this file type
android/.gradle/7.4.2/fileChanges/last-build.bin
0 → 100644
No preview for this file type
android/.gradle/7.4.2/fileHashes/fileHashes.lock
0 → 100644
No preview for this file type
android/.gradle/7.4.2/gc.properties
0 → 100644
1 | +++ a/android/.gradle/7.4.2/gc.properties | ... | ... |
android/.gradle/vcs-1/gc.properties
0 → 100644
1 | +++ a/android/.gradle/vcs-1/gc.properties | ... | ... |
android/build.gradle
0 → 100644
1 | +++ a/android/build.gradle | |
1 | +apply plugin: 'com.android.library' | |
2 | + | |
3 | +android { | |
4 | + compileSdkVersion 33 | |
5 | + buildToolsVersion "33.0.0" | |
6 | + | |
7 | + defaultConfig { | |
8 | + minSdkVersion 24 | |
9 | + targetSdkVersion 33 | |
10 | + versionCode 1 | |
11 | + versionName "1.0" | |
12 | + } | |
13 | + buildTypes { | |
14 | + release { | |
15 | + minifyEnabled false | |
16 | + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' | |
17 | + } | |
18 | + } | |
19 | +} | |
20 | + | |
21 | +dependencies { | |
22 | + api fileTree(include: ['*.jar'], dir: 'libs') | |
23 | + api 'com.facebook.react:react-native:+' | |
24 | +} | ... | ... |
android/proguard-rules.pro
0 → 100644
1 | +++ a/android/proguard-rules.pro | |
1 | +# Add project specific ProGuard rules here. | |
2 | +# By default, the flags in this file are appended to flags specified | |
3 | +# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt | |
4 | +# You can edit the include path and order by changing the proguardFiles | |
5 | +# directive in build.gradle. | |
6 | +# | |
7 | +# For more details, see | |
8 | +# http://developer.android.com/guide/developing/tools/proguard.html | |
9 | + | |
10 | +# Add any project specific keep options here: | |
11 | + | |
12 | +# If your project uses WebView with JS, uncomment the following | |
13 | +# and specify the fully qualified class name to the JavaScript interface | |
14 | +# class: | |
15 | +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { | |
16 | +# public *; | |
17 | +#} | |
18 | + | |
19 | +# Disabling obfuscation is useful if you collect stack traces from production crashes | |
20 | +# (unless you are using a system that supports de-obfuscate the stack traces). | |
21 | +-dontobfuscate | |
22 | + | |
23 | +# React Native | |
24 | + | |
25 | +# Keep our interfaces so they can be used by other ProGuard rules. | |
26 | +# See http://sourceforge.net/p/proguard/bugs/466/ | |
27 | +-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip | |
28 | +-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters | |
29 | +-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip | |
30 | + | |
31 | +# Do not strip any method/class that is annotated with @DoNotStrip | |
32 | +-keep @com.facebook.proguard.annotations.DoNotStrip class * | |
33 | +-keep @com.facebook.common.internal.DoNotStrip class * | |
34 | +-keepclassmembers class * { | |
35 | + @com.facebook.proguard.annotations.DoNotStrip *; | |
36 | + @com.facebook.common.internal.DoNotStrip *; | |
37 | +} | |
38 | + | |
39 | +-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { | |
40 | + void set*(***); | |
41 | + *** get*(); | |
42 | +} | |
43 | + | |
44 | +-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } | |
45 | +-keep class * extends com.facebook.react.bridge.NativeModule { *; } | |
46 | +-keepclassmembers,includedescriptorclasses class * { native <methods>; } | |
47 | +-keepclassmembers class * { @com.facebook.react.uimanager.UIProp <fields>; } | |
48 | +-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; } | |
49 | +-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; } | |
50 | + | |
51 | +-dontwarn com.facebook.react.** | |
52 | + | |
53 | +# TextLayoutBuilder uses a non-public Android constructor within StaticLayout. | |
54 | +# See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details. | |
55 | +-dontwarn android.text.StaticLayout | |
56 | + | |
57 | +# okhttp | |
58 | + | |
59 | +-keepattributes Signature | |
60 | +-keepattributes *Annotation* | |
61 | +-keep class okhttp3.** { *; } | |
62 | +-keep interface okhttp3.** { *; } | |
63 | +-dontwarn okhttp3.** | |
64 | + | |
65 | +# okio | |
66 | + | |
67 | +-keep class sun.misc.Unsafe { *; } | |
68 | +-dontwarn java.nio.file.* | |
69 | +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement | |
70 | +-dontwarn okio.** | ... | ... |
android/src/main/AndroidManifest.xml
0 → 100644
android/src/main/java/com/hzl/pulltorefresh/RefreshReactPackage.java
0 → 100644
1 | +++ a/android/src/main/java/com/hzl/pulltorefresh/RefreshReactPackage.java | |
1 | +package com.hzl.pulltorefresh; | |
2 | + | |
3 | +import com.facebook.react.ReactPackage; | |
4 | +import com.facebook.react.bridge.JavaScriptModule; | |
5 | +import com.facebook.react.bridge.NativeModule; | |
6 | +import com.facebook.react.bridge.ReactApplicationContext; | |
7 | +import com.facebook.react.uimanager.ViewManager; | |
8 | +import com.hzl.pulltorefresh.refresh.view.RefreshHeadViewManager; | |
9 | +import com.hzl.pulltorefresh.refresh.view.RefreshViewManager; | |
10 | + | |
11 | +import java.util.ArrayList; | |
12 | +import java.util.Collections; | |
13 | +import java.util.List; | |
14 | + | |
15 | +/** | |
16 | + * 作者:请叫我百米冲刺 on 2018/1/2 上午11:49 | |
17 | + * 邮箱:mail@hezhilin.cc | |
18 | + */ | |
19 | + | |
20 | +public class RefreshReactPackage implements ReactPackage { | |
21 | + | |
22 | + @Override | |
23 | + public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { | |
24 | + return Collections.emptyList(); | |
25 | + } | |
26 | + | |
27 | + public List<Class<? extends JavaScriptModule>> createJSModules() { | |
28 | + return Collections.emptyList(); | |
29 | + } | |
30 | + | |
31 | + @Override | |
32 | + public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { | |
33 | + List<ViewManager> managers = new ArrayList<>(); | |
34 | + managers.add(new RefreshViewManager()); | |
35 | + managers.add(new RefreshHeadViewManager()); | |
36 | + return managers; | |
37 | + } | |
38 | +} | ... | ... |
android/src/main/java/com/hzl/pulltorefresh/refresh/PtrDefaultHandler.java
0 → 100755
1 | +++ a/android/src/main/java/com/hzl/pulltorefresh/refresh/PtrDefaultHandler.java | |
1 | +package com.hzl.pulltorefresh.refresh; | |
2 | + | |
3 | +import android.view.View; | |
4 | +import android.widget.AbsListView; | |
5 | + | |
6 | +public abstract class PtrDefaultHandler implements PtrHandler { | |
7 | + | |
8 | + public static boolean canChildScrollUp(View view) { | |
9 | + if (android.os.Build.VERSION.SDK_INT < 14) { | |
10 | + if (view instanceof AbsListView) { | |
11 | + final AbsListView absListView = (AbsListView) view; | |
12 | + return absListView.getChildCount() > 0 | |
13 | + && (absListView.getFirstVisiblePosition() > 0 || absListView.getChildAt(0) | |
14 | + .getTop() < absListView.getPaddingTop()); | |
15 | + } else { | |
16 | + return view.getScrollY() > 0; | |
17 | + } | |
18 | + } else { | |
19 | + return view.canScrollVertically(-1); | |
20 | + } | |
21 | + } | |
22 | + | |
23 | + /** | |
24 | + * Default implement for check can perform pull to refresh | |
25 | + * | |
26 | + * @param frame | |
27 | + * @param content | |
28 | + * @param header | |
29 | + * @return | |
30 | + */ | |
31 | + public static boolean checkContentCanBePulledDown(PtrFrameLayout frame, View content, View header) { | |
32 | + return !canChildScrollUp(content); | |
33 | + } | |
34 | + | |
35 | + @Override | |
36 | + public boolean checkCanDoRefresh(PtrFrameLayout frame, View content, View header) { | |
37 | + return checkContentCanBePulledDown(frame, content, header); | |
38 | + } | |
39 | +} | |
0 | 40 | \ No newline at end of file | ... | ... |
android/src/main/java/com/hzl/pulltorefresh/refresh/PtrFrameLayout.java
0 → 100755
1 | +++ a/android/src/main/java/com/hzl/pulltorefresh/refresh/PtrFrameLayout.java | |
1 | +package com.hzl.pulltorefresh.refresh; | |
2 | + | |
3 | +import android.content.Context; | |
4 | +import android.util.AttributeSet; | |
5 | +import android.view.Gravity; | |
6 | +import android.view.MotionEvent; | |
7 | +import android.view.View; | |
8 | +import android.view.ViewConfiguration; | |
9 | +import android.view.ViewGroup; | |
10 | +import android.widget.Scroller; | |
11 | +import android.widget.TextView; | |
12 | + | |
13 | +import com.facebook.react.uimanager.events.NativeGestureUtil; | |
14 | +import com.facebook.react.views.view.ReactViewGroup; | |
15 | +import com.hzl.pulltorefresh.refresh.indicator.PtrIndicator; | |
16 | +import com.hzl.pulltorefresh.refresh.util.PtrCLog; | |
17 | + | |
18 | + | |
19 | +/** | |
20 | + * This layout view for "Pull to Refresh(Ptr)" support all of the view, you can contain everything you want. | |
21 | + * support: pull to refresh / release to refresh / auto refresh / keep header view while refreshing / hide header view while refreshing | |
22 | + * It defines {@link PtrUIHandler}, which allows you customize the UI easily. | |
23 | + */ | |
24 | +public class PtrFrameLayout extends ReactViewGroup { | |
25 | + | |
26 | + // status enum | |
27 | + public final static byte PTR_STATUS_INIT = 1; | |
28 | + private byte mStatus = PTR_STATUS_INIT; | |
29 | + public final static byte PTR_STATUS_PREPARE = 2; | |
30 | + public final static byte PTR_STATUS_LOADING = 3; | |
31 | + public final static byte PTR_STATUS_COMPLETE = 4; | |
32 | + private static final boolean DEBUG_LAYOUT = true; | |
33 | + public static boolean DEBUG = false; | |
34 | + private static int ID = 1; | |
35 | + protected final String LOG_TAG = "ptr-frame-" + ++ID; | |
36 | + // auto refresh status | |
37 | + private final static byte FLAG_AUTO_REFRESH_AT_ONCE = 0x01; | |
38 | + private final static byte FLAG_AUTO_REFRESH_BUT_LATER = 0x01 << 1; | |
39 | + private final static byte FLAG_ENABLE_NEXT_PTR_AT_ONCE = 0x01 << 2; | |
40 | + private final static byte FLAG_PIN_CONTENT = 0x01 << 3; | |
41 | + private final static byte MASK_AUTO_REFRESH = 0x03; | |
42 | + protected View mContent; | |
43 | + // optional config for define header and content in xml file | |
44 | + private int mHeaderId = 0; | |
45 | + private int mContainerId = 0; | |
46 | + // config | |
47 | + private int mDurationToClose = 200; | |
48 | + private int mDurationToCloseHeader = 1000; | |
49 | + private boolean mKeepHeaderWhenRefresh = true; | |
50 | + private boolean mPullToRefresh = false; | |
51 | + private View mHeaderView; | |
52 | + private PtrUIHandlerHolder mPtrUIHandlerHolder = PtrUIHandlerHolder.create(); | |
53 | + private PtrHandler mPtrHandler; | |
54 | + // working parameters | |
55 | + private ScrollChecker mScrollChecker; | |
56 | + private int mPagingTouchSlop; | |
57 | + private int mHeaderHeight; | |
58 | + private boolean mDisableWhenHorizontalMove = false; | |
59 | + private int mFlag = 0x00; | |
60 | + | |
61 | + // disable when detect moving horizontally | |
62 | + private boolean mPreventForHorizontal = false; | |
63 | + | |
64 | + private MotionEvent mLastMoveEvent; | |
65 | + | |
66 | + private PtrUIHandlerHook mRefreshCompleteHook; | |
67 | + | |
68 | + private boolean isFinishInflate = false; | |
69 | + | |
70 | + private int mLoadingMinTime = 500; | |
71 | + private long mLoadingStartTime = 0; | |
72 | + private PtrIndicator mPtrIndicator; | |
73 | + private boolean mHasSendCancelEvent = false; | |
74 | + private Runnable mPerformRefreshCompleteDelay = new Runnable() { | |
75 | + @Override | |
76 | + public void run() { | |
77 | + performRefreshComplete(); | |
78 | + } | |
79 | + }; | |
80 | + | |
81 | + public PtrFrameLayout(Context context) { | |
82 | + super(context); | |
83 | + mPtrIndicator = new PtrIndicator(); | |
84 | + mScrollChecker = new ScrollChecker(); | |
85 | + final ViewConfiguration conf = ViewConfiguration.get(getContext()); | |
86 | + mPagingTouchSlop = conf.getScaledTouchSlop(); | |
87 | + } | |
88 | + | |
89 | + @Override | |
90 | + protected void onAttachedToWindow() { | |
91 | + super.onAttachedToWindow(); | |
92 | + if (!isFinishInflate) { | |
93 | + onFinishInflate(); | |
94 | + isFinishInflate = true; | |
95 | + } | |
96 | + } | |
97 | + | |
98 | + @Override | |
99 | + protected void onFinishInflate() { | |
100 | + final int childCount = getChildCount(); | |
101 | + if (childCount > 2) { | |
102 | + throw new IllegalStateException("PtrFrameLayout can only contains 2 children"); | |
103 | + } else if (childCount == 2) { | |
104 | + if (mHeaderId != 0 && mHeaderView == null) { | |
105 | + mHeaderView = findViewById(mHeaderId); | |
106 | + } | |
107 | + if (mContainerId != 0 && mContent == null) { | |
108 | + mContent = findViewById(mContainerId); | |
109 | + } | |
110 | + | |
111 | + // not specify header or content | |
112 | + if (mContent == null || mHeaderView == null) { | |
113 | + | |
114 | + View child1 = getChildAt(0); | |
115 | + View child2 = getChildAt(1); | |
116 | + if (child1 instanceof PtrUIHandler) { | |
117 | + mHeaderView = child1; | |
118 | + mContent = child2; | |
119 | + } else if (child2 instanceof PtrUIHandler) { | |
120 | + mHeaderView = child2; | |
121 | + mContent = child1; | |
122 | + } else { | |
123 | + // both are not specified | |
124 | + if (mContent == null && mHeaderView == null) { | |
125 | + mHeaderView = child1; | |
126 | + mContent = child2; | |
127 | + } | |
128 | + // only one is specified | |
129 | + else { | |
130 | + if (mHeaderView == null) { | |
131 | + mHeaderView = mContent == child1 ? child2 : child1; | |
132 | + } else { | |
133 | + mContent = mHeaderView == child1 ? child2 : child1; | |
134 | + } | |
135 | + } | |
136 | + } | |
137 | + addPtrUIHandler((PtrUIHandler) mHeaderView); | |
138 | + } | |
139 | + } else if (childCount == 1) { | |
140 | + mContent = getChildAt(0); | |
141 | + } else { | |
142 | + TextView errorView = new TextView(getContext()); | |
143 | + errorView.setClickable(true); | |
144 | + errorView.setTextColor(0xffff6600); | |
145 | + errorView.setGravity(Gravity.CENTER); | |
146 | + errorView.setTextSize(20); | |
147 | + errorView.setText("The content view in PtrFrameLayout is empty. Do you forget to specify its id in xml layout file?"); | |
148 | + mContent = errorView; | |
149 | + addView(mContent); | |
150 | + } | |
151 | + if (mHeaderView != null) { | |
152 | + mHeaderView.bringToFront(); | |
153 | + } | |
154 | + super.onFinishInflate(); | |
155 | + } | |
156 | + | |
157 | + @Override | |
158 | + protected void onDetachedFromWindow() { | |
159 | + super.onDetachedFromWindow(); | |
160 | + if (mScrollChecker != null) { | |
161 | + mScrollChecker.destroy(); | |
162 | + } | |
163 | + | |
164 | + if (mPerformRefreshCompleteDelay != null) { | |
165 | + removeCallbacks(mPerformRefreshCompleteDelay); | |
166 | + } | |
167 | + } | |
168 | + | |
169 | + @Override | |
170 | + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { | |
171 | + super.onMeasure(widthMeasureSpec, heightMeasureSpec); | |
172 | + | |
173 | + if (isDebug()) { | |
174 | + PtrCLog.d(LOG_TAG, "onMeasure frame: width: %s, height: %s, padding: %s %s %s %s", | |
175 | + getMeasuredHeight(), getMeasuredWidth(), | |
176 | + getPaddingLeft(), getPaddingRight(), getPaddingTop(), getPaddingBottom()); | |
177 | + | |
178 | + } | |
179 | + | |
180 | + if (mHeaderView != null) { | |
181 | + measureChildWithMargins(mHeaderView, widthMeasureSpec, 0, heightMeasureSpec, 0); | |
182 | + MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams(); | |
183 | + mHeaderHeight = mHeaderView.getMeasuredHeight() + lp.topMargin + lp.bottomMargin; | |
184 | + mPtrIndicator.setHeaderHeight(mHeaderHeight); | |
185 | + } | |
186 | + | |
187 | + if (mContent != null) { | |
188 | + measureContentView(mContent, widthMeasureSpec, heightMeasureSpec); | |
189 | + if (isDebug()) { | |
190 | + MarginLayoutParams lp = (MarginLayoutParams) mContent.getLayoutParams(); | |
191 | + PtrCLog.d(LOG_TAG, "onMeasure content, width: %s, height: %s, margin: %s %s %s %s", | |
192 | + getMeasuredWidth(), getMeasuredHeight(), | |
193 | + lp.leftMargin, lp.topMargin, lp.rightMargin, lp.bottomMargin); | |
194 | + PtrCLog.d(LOG_TAG, "onMeasure, currentPos: %s, lastPos: %s, top: %s", | |
195 | + mPtrIndicator.getCurrentPosY(), mPtrIndicator.getLastPosY(), mContent.getTop()); | |
196 | + } | |
197 | + } | |
198 | + } | |
199 | + | |
200 | + private void measureContentView(View child, | |
201 | + int parentWidthMeasureSpec, | |
202 | + int parentHeightMeasureSpec) { | |
203 | + final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); | |
204 | + | |
205 | + final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, | |
206 | + getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin, lp.width); | |
207 | + final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, | |
208 | + getPaddingTop() + getPaddingBottom() + lp.topMargin, lp.height); | |
209 | + | |
210 | + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); | |
211 | + } | |
212 | + | |
213 | + @Override | |
214 | + protected void onLayout(boolean flag, int i, int j, int k, int l) { | |
215 | + layoutChildren(); | |
216 | + } | |
217 | + | |
218 | + private void layoutChildren() { | |
219 | + int offset = mPtrIndicator.getCurrentPosY(); | |
220 | + int paddingLeft = getPaddingLeft(); | |
221 | + int paddingTop = getPaddingTop(); | |
222 | + | |
223 | + if (mHeaderView != null) { | |
224 | + MarginLayoutParams lp = (MarginLayoutParams) mHeaderView.getLayoutParams(); | |
225 | + final int left = paddingLeft + lp.leftMargin; | |
226 | + // enhance readability(header is layout above screen when first init) | |
227 | + final int top = -(mHeaderHeight - paddingTop - lp.topMargin - offset); | |
228 | + final int right = left + mHeaderView.getMeasuredWidth(); | |
229 | + final int bottom = top + mHeaderView.getMeasuredHeight(); | |
230 | + mHeaderView.layout(left, top, right, bottom); | |
231 | + if (isDebug()) { | |
232 | + PtrCLog.d(LOG_TAG, "onLayout header: %s %s %s %s", left, top, right, bottom); | |
233 | + } | |
234 | + } | |
235 | + if (mContent != null) { | |
236 | + if (isPinContent()) { | |
237 | + offset = 0; | |
238 | + } | |
239 | + MarginLayoutParams lp = (MarginLayoutParams) mContent.getLayoutParams(); | |
240 | + final int left = paddingLeft + lp.leftMargin; | |
241 | + final int top = paddingTop + lp.topMargin + offset; | |
242 | + final int right = left + mContent.getMeasuredWidth(); | |
243 | + final int bottom = top + mContent.getMeasuredHeight(); | |
244 | + if (isDebug()) { | |
245 | + PtrCLog.d(LOG_TAG, "onLayout content: %s %s %s %s", left, top, right, bottom); | |
246 | + } | |
247 | + mContent.layout(left, top, right, bottom); | |
248 | + } | |
249 | + } | |
250 | + | |
251 | + @SuppressWarnings({"PointlessBooleanExpression", "ConstantConditions"}) | |
252 | + private boolean isDebug() { | |
253 | + return DEBUG && DEBUG_LAYOUT; | |
254 | + } | |
255 | + | |
256 | + public boolean dispatchTouchEventSupper(MotionEvent e) { | |
257 | + return super.dispatchTouchEvent(e); | |
258 | + } | |
259 | + | |
260 | + @Override | |
261 | + public boolean dispatchTouchEvent(MotionEvent e) { | |
262 | + if (!isEnabled() || mContent == null || mHeaderView == null) { | |
263 | + return dispatchTouchEventSupper(e); | |
264 | + } | |
265 | + int action = e.getAction(); | |
266 | + switch (action) { | |
267 | + case MotionEvent.ACTION_UP: | |
268 | + | |
269 | + case MotionEvent.ACTION_CANCEL: | |
270 | + mPtrIndicator.onRelease(); | |
271 | + if (mPtrIndicator.hasLeftStartPosition()) { | |
272 | + if (DEBUG) { | |
273 | + PtrCLog.d(LOG_TAG, "call onRelease when user release"); | |
274 | + } | |
275 | + onRelease(false); | |
276 | + if (mPtrIndicator.hasMovedAfterPressedDown()) { | |
277 | + sendCancelEvent(); | |
278 | + return true; | |
279 | + } | |
280 | + return dispatchTouchEventSupper(e); | |
281 | + } else { | |
282 | + return dispatchTouchEventSupper(e); | |
283 | + } | |
284 | + | |
285 | + case MotionEvent.ACTION_DOWN: | |
286 | + mHasSendCancelEvent = false; | |
287 | + mPtrIndicator.onPressDown(e.getX(), e.getY()); | |
288 | + | |
289 | + mScrollChecker.abortIfWorking(); | |
290 | + | |
291 | + mPreventForHorizontal = false; | |
292 | + // The cancel event will be sent once the position is moved. | |
293 | + // So let the event pass to children. | |
294 | + // fix #93, #102 | |
295 | + dispatchTouchEventSupper(e); | |
296 | + return true; | |
297 | + | |
298 | + case MotionEvent.ACTION_MOVE: | |
299 | + mLastMoveEvent = e; | |
300 | + mPtrIndicator.onMove(e.getX(), e.getY()); | |
301 | + float offsetX = mPtrIndicator.getOffsetX(); | |
302 | + float offsetY = mPtrIndicator.getOffsetY(); | |
303 | + | |
304 | + //此时表示的是竖直下拉的的操作 | |
305 | + if (Math.abs(offsetX) < Math.abs(offsetY)) { | |
306 | + NativeGestureUtil.notifyNativeGestureStarted(this, e); | |
307 | + } | |
308 | + | |
309 | + if (mDisableWhenHorizontalMove && !mPreventForHorizontal && (Math.abs(offsetX) > mPagingTouchSlop && Math.abs(offsetX) > Math.abs(offsetY))) { | |
310 | + if (mPtrIndicator.isInStartPosition()) { | |
311 | + mPreventForHorizontal = true; | |
312 | + } | |
313 | + } | |
314 | + if (mPreventForHorizontal) { | |
315 | + return dispatchTouchEventSupper(e); | |
316 | + } | |
317 | + | |
318 | + boolean moveDown = offsetY > 0; | |
319 | + boolean moveUp = !moveDown; | |
320 | + boolean canMoveUp = mPtrIndicator.hasLeftStartPosition(); | |
321 | + | |
322 | + if (DEBUG) { | |
323 | + boolean canMoveDown = mPtrHandler != null && mPtrHandler.checkCanDoRefresh(this, mContent, mHeaderView); | |
324 | + PtrCLog.v(LOG_TAG, "ACTION_MOVE: offsetY:%s, currentPos: %s, moveUp: %s, canMoveUp: %s, moveDown: %s: canMoveDown: %s", offsetY, mPtrIndicator.getCurrentPosY(), moveUp, canMoveUp, moveDown, canMoveDown); | |
325 | + } | |
326 | + | |
327 | + // disable move when header not reach top | |
328 | + if (moveDown && mPtrHandler != null && !mPtrHandler.checkCanDoRefresh(this, mContent, mHeaderView)) { | |
329 | + return dispatchTouchEventSupper(e); | |
330 | + } | |
331 | + | |
332 | + if ((moveUp && canMoveUp) || moveDown) { | |
333 | + movePos(offsetY); | |
334 | + return true; | |
335 | + } | |
336 | + } | |
337 | + return dispatchTouchEventSupper(e); | |
338 | + } | |
339 | + | |
340 | + /** | |
341 | + * if deltaY > 0, move the content down | |
342 | + * | |
343 | + * @param deltaY | |
344 | + */ | |
345 | + private void movePos(float deltaY) { | |
346 | + // has reached the top | |
347 | + if ((deltaY < 0 && mPtrIndicator.isInStartPosition())) { | |
348 | + if (DEBUG) { | |
349 | + PtrCLog.e(LOG_TAG, String.format("has reached the top")); | |
350 | + } | |
351 | + return; | |
352 | + } | |
353 | + | |
354 | + int to = mPtrIndicator.getCurrentPosY() + (int) deltaY; | |
355 | + | |
356 | + // over top | |
357 | + if (mPtrIndicator.willOverTop(to)) { | |
358 | + if (DEBUG) { | |
359 | + PtrCLog.e(LOG_TAG, String.format("over top")); | |
360 | + } | |
361 | + to = PtrIndicator.POS_START; | |
362 | + } | |
363 | + | |
364 | + mPtrIndicator.setCurrentPos(to); | |
365 | + int change = to - mPtrIndicator.getLastPosY(); | |
366 | + updatePos(change); | |
367 | + } | |
368 | + | |
369 | + private void updatePos(int change) { | |
370 | + if (change == 0) { | |
371 | + return; | |
372 | + } | |
373 | + | |
374 | + boolean isUnderTouch = mPtrIndicator.isUnderTouch(); | |
375 | + | |
376 | + // once moved, cancel event will be sent to child | |
377 | + if (isUnderTouch && !mHasSendCancelEvent && mPtrIndicator.hasMovedAfterPressedDown()) { | |
378 | + mHasSendCancelEvent = true; | |
379 | + sendCancelEvent(); | |
380 | + } | |
381 | + | |
382 | + // leave initiated position or just refresh complete | |
383 | + if ((mPtrIndicator.hasJustLeftStartPosition() && mStatus == PTR_STATUS_INIT) || | |
384 | + (mPtrIndicator.goDownCrossFinishPosition() && mStatus == PTR_STATUS_COMPLETE && isEnabledNextPtrAtOnce())) { | |
385 | + | |
386 | + mStatus = PTR_STATUS_PREPARE; | |
387 | + mPtrUIHandlerHolder.onUIRefreshPrepare(this); | |
388 | + if (DEBUG) { | |
389 | + PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshPrepare, mFlag %s", mFlag); | |
390 | + } | |
391 | + } | |
392 | + | |
393 | + // back to initiated position | |
394 | + if (mPtrIndicator.hasJustBackToStartPosition()) { | |
395 | + tryToNotifyReset(); | |
396 | + | |
397 | + // recover event to children | |
398 | + if (isUnderTouch) { | |
399 | + sendDownEvent(); | |
400 | + } | |
401 | + } | |
402 | + | |
403 | + // Pull to Refresh | |
404 | + if (mStatus == PTR_STATUS_PREPARE) { | |
405 | + // reach fresh height while moving from top to bottom | |
406 | + if (isUnderTouch && !isAutoRefresh() && mPullToRefresh | |
407 | + && mPtrIndicator.crossRefreshLineFromTopToBottom()) { | |
408 | + tryToPerformRefresh(); | |
409 | + } | |
410 | + // reach header height while auto refresh | |
411 | + if (performAutoRefreshButLater() && mPtrIndicator.hasJustReachedHeaderHeightFromTopToBottom()) { | |
412 | + tryToPerformRefresh(); | |
413 | + } | |
414 | + } | |
415 | + | |
416 | + if (DEBUG) { | |
417 | + PtrCLog.v(LOG_TAG, "updatePos: change: %s, current: %s last: %s, top: %s, headerHeight: %s", | |
418 | + change, mPtrIndicator.getCurrentPosY(), mPtrIndicator.getLastPosY(), mContent.getTop(), mHeaderHeight); | |
419 | + } | |
420 | + | |
421 | + mHeaderView.offsetTopAndBottom(change); | |
422 | + if (!isPinContent()) { | |
423 | + mContent.offsetTopAndBottom(change); | |
424 | + } | |
425 | + invalidate(); | |
426 | + | |
427 | + if (mPtrUIHandlerHolder.hasHandler()) { | |
428 | + mPtrUIHandlerHolder.onUIPositionChange(this, isUnderTouch, mStatus, mPtrIndicator); | |
429 | + } | |
430 | + onPositionChange(isUnderTouch, mStatus, mPtrIndicator); | |
431 | + } | |
432 | + | |
433 | + protected void onPositionChange(boolean isInTouching, byte status, PtrIndicator mPtrIndicator) { | |
434 | + } | |
435 | + | |
436 | + @SuppressWarnings("unused") | |
437 | + public int getHeaderHeight() { | |
438 | + return mHeaderHeight; | |
439 | + } | |
440 | + | |
441 | + private void onRelease(boolean stayForLoading) { | |
442 | + | |
443 | + tryToPerformRefresh(); | |
444 | + | |
445 | + if (mStatus == PTR_STATUS_LOADING) { | |
446 | + // keep header for fresh | |
447 | + if (mKeepHeaderWhenRefresh) { | |
448 | + // scroll header back | |
449 | + if (mPtrIndicator.isOverOffsetToKeepHeaderWhileLoading() && !stayForLoading) { | |
450 | + mScrollChecker.tryToScrollTo(mPtrIndicator.getOffsetToKeepHeaderWhileLoading(), mDurationToClose); | |
451 | + } else { | |
452 | + // do nothing | |
453 | + } | |
454 | + } else { | |
455 | + tryScrollBackToTopWhileLoading(); | |
456 | + } | |
457 | + } else { | |
458 | + if (mStatus == PTR_STATUS_COMPLETE) { | |
459 | + notifyUIRefreshComplete(false); | |
460 | + } else { | |
461 | + tryScrollBackToTopAbortRefresh(); | |
462 | + } | |
463 | + } | |
464 | + } | |
465 | + | |
466 | + /** | |
467 | + * please DO REMEMBER resume the hook | |
468 | + * | |
469 | + * @param hook | |
470 | + */ | |
471 | + | |
472 | + public void setRefreshCompleteHook(PtrUIHandlerHook hook) { | |
473 | + mRefreshCompleteHook = hook; | |
474 | + hook.setResumeAction(new Runnable() { | |
475 | + @Override | |
476 | + public void run() { | |
477 | + if (DEBUG) { | |
478 | + PtrCLog.d(LOG_TAG, "mRefreshCompleteHook resume."); | |
479 | + } | |
480 | + notifyUIRefreshComplete(true); | |
481 | + } | |
482 | + }); | |
483 | + } | |
484 | + | |
485 | + /** | |
486 | + * Scroll back to to if is not under touch | |
487 | + */ | |
488 | + private void tryScrollBackToTop() { | |
489 | + if (!mPtrIndicator.isUnderTouch()) { | |
490 | + mScrollChecker.tryToScrollTo(PtrIndicator.POS_START, mDurationToCloseHeader); | |
491 | + } | |
492 | + } | |
493 | + | |
494 | + /** | |
495 | + * just make easier to understand | |
496 | + */ | |
497 | + private void tryScrollBackToTopWhileLoading() { | |
498 | + tryScrollBackToTop(); | |
499 | + } | |
500 | + | |
501 | + /** | |
502 | + * just make easier to understand | |
503 | + */ | |
504 | + private void tryScrollBackToTopAfterComplete() { | |
505 | + tryScrollBackToTop(); | |
506 | + } | |
507 | + | |
508 | + /** | |
509 | + * just make easier to understand | |
510 | + */ | |
511 | + private void tryScrollBackToTopAbortRefresh() { | |
512 | + tryScrollBackToTop(); | |
513 | + } | |
514 | + | |
515 | + private boolean tryToPerformRefresh() { | |
516 | + if (mStatus != PTR_STATUS_PREPARE) { | |
517 | + return false; | |
518 | + } | |
519 | + | |
520 | + if ((mPtrIndicator.isOverOffsetToKeepHeaderWhileLoading() && isAutoRefresh()) || mPtrIndicator.isOverOffsetToRefresh()) { | |
521 | + mStatus = PTR_STATUS_LOADING; | |
522 | + performRefresh(); | |
523 | + } | |
524 | + return false; | |
525 | + } | |
526 | + | |
527 | + private void performRefresh() { | |
528 | + mLoadingStartTime = System.currentTimeMillis(); | |
529 | + if (mPtrUIHandlerHolder.hasHandler()) { | |
530 | + mPtrUIHandlerHolder.onUIRefreshBegin(this); | |
531 | + if (DEBUG) { | |
532 | + PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshBegin"); | |
533 | + } | |
534 | + } | |
535 | + if (mPtrHandler != null) { | |
536 | + mPtrHandler.onRefreshBegin(this); | |
537 | + } | |
538 | + } | |
539 | + | |
540 | + /** | |
541 | + * If at the top and not in loading, reset | |
542 | + */ | |
543 | + private boolean tryToNotifyReset() { | |
544 | + if ((mStatus == PTR_STATUS_COMPLETE || mStatus == PTR_STATUS_PREPARE) && mPtrIndicator.isInStartPosition()) { | |
545 | + if (mPtrUIHandlerHolder.hasHandler()) { | |
546 | + mPtrUIHandlerHolder.onUIReset(this); | |
547 | + if (DEBUG) { | |
548 | + PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIReset"); | |
549 | + } | |
550 | + } | |
551 | + mStatus = PTR_STATUS_INIT; | |
552 | + clearFlag(); | |
553 | + return true; | |
554 | + } | |
555 | + return false; | |
556 | + } | |
557 | + | |
558 | + protected void onPtrScrollAbort() { | |
559 | + if (mPtrIndicator.hasLeftStartPosition() && isAutoRefresh()) { | |
560 | + if (DEBUG) { | |
561 | + PtrCLog.d(LOG_TAG, "call onRelease after scroll abort"); | |
562 | + } | |
563 | + onRelease(true); | |
564 | + } | |
565 | + } | |
566 | + | |
567 | + protected void onPtrScrollFinish() { | |
568 | + if (mPtrIndicator.hasLeftStartPosition() && isAutoRefresh()) { | |
569 | + if (DEBUG) { | |
570 | + PtrCLog.d(LOG_TAG, "call onRelease after scroll finish"); | |
571 | + } | |
572 | + onRelease(true); | |
573 | + } | |
574 | + } | |
575 | + | |
576 | + /** | |
577 | + * Detect whether is refreshing. | |
578 | + * | |
579 | + * @return | |
580 | + */ | |
581 | + public boolean isRefreshing() { | |
582 | + return mStatus == PTR_STATUS_LOADING; | |
583 | + } | |
584 | + | |
585 | + /** | |
586 | + * Call this when data is loaded. | |
587 | + * The UI will perform complete at once or after a delay, depends on the time elapsed is greater then {@link #mLoadingMinTime} or not. | |
588 | + */ | |
589 | + final public void refreshComplete() { | |
590 | + if (DEBUG) { | |
591 | + PtrCLog.i(LOG_TAG, "refreshComplete"); | |
592 | + } | |
593 | + | |
594 | + if (mRefreshCompleteHook != null) { | |
595 | + mRefreshCompleteHook.reset(); | |
596 | + } | |
597 | + | |
598 | + int delay = (int) (mLoadingMinTime - (System.currentTimeMillis() - mLoadingStartTime)); | |
599 | + if (delay <= 0) { | |
600 | + if (DEBUG) { | |
601 | + PtrCLog.d(LOG_TAG, "performRefreshComplete at once"); | |
602 | + } | |
603 | + performRefreshComplete(); | |
604 | + } else { | |
605 | + postDelayed(mPerformRefreshCompleteDelay, delay); | |
606 | + if (DEBUG) { | |
607 | + PtrCLog.d(LOG_TAG, "performRefreshComplete after delay: %s", delay); | |
608 | + } | |
609 | + } | |
610 | + } | |
611 | + | |
612 | + /** | |
613 | + * Do refresh complete work when time elapsed is greater than {@link #mLoadingMinTime} | |
614 | + */ | |
615 | + private void performRefreshComplete() { | |
616 | + mStatus = PTR_STATUS_COMPLETE; | |
617 | + | |
618 | + // if is auto refresh do nothing, wait scroller stop | |
619 | + if (mScrollChecker.mIsRunning && isAutoRefresh()) { | |
620 | + // do nothing | |
621 | + if (DEBUG) { | |
622 | + PtrCLog.d(LOG_TAG, "performRefreshComplete do nothing, scrolling: %s, auto refresh: %s", | |
623 | + mScrollChecker.mIsRunning, mFlag); | |
624 | + } | |
625 | + return; | |
626 | + } | |
627 | + | |
628 | + notifyUIRefreshComplete(false); | |
629 | + } | |
630 | + | |
631 | + /** | |
632 | + * Do real refresh work. If there is a hook, execute the hook first. | |
633 | + * | |
634 | + * @param ignoreHook | |
635 | + */ | |
636 | + private void notifyUIRefreshComplete(boolean ignoreHook) { | |
637 | + /** | |
638 | + * After hook operation is done, {@link #notifyUIRefreshComplete} will be call in resume action to ignore hook. | |
639 | + */ | |
640 | + if (mPtrIndicator.hasLeftStartPosition() && !ignoreHook && mRefreshCompleteHook != null) { | |
641 | + if (DEBUG) { | |
642 | + PtrCLog.d(LOG_TAG, "notifyUIRefreshComplete mRefreshCompleteHook run."); | |
643 | + } | |
644 | + | |
645 | + mRefreshCompleteHook.takeOver(); | |
646 | + return; | |
647 | + } | |
648 | + if (mPtrUIHandlerHolder.hasHandler()) { | |
649 | + if (DEBUG) { | |
650 | + PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshComplete"); | |
651 | + } | |
652 | + mPtrUIHandlerHolder.onUIRefreshComplete(this); | |
653 | + } | |
654 | + mPtrIndicator.onUIRefreshComplete(); | |
655 | + tryScrollBackToTopAfterComplete(); | |
656 | + tryToNotifyReset(); | |
657 | + } | |
658 | + | |
659 | + public void autoRefresh() { | |
660 | + autoRefresh(true, mDurationToCloseHeader); | |
661 | + } | |
662 | + | |
663 | + public void autoRefresh(boolean atOnce) { | |
664 | + autoRefresh(atOnce, mDurationToCloseHeader); | |
665 | + } | |
666 | + | |
667 | + private void clearFlag() { | |
668 | + // remove auto fresh flag | |
669 | + mFlag = mFlag & ~MASK_AUTO_REFRESH; | |
670 | + } | |
671 | + | |
672 | + public void autoRefresh(boolean atOnce, int duration) { | |
673 | + | |
674 | + if (mStatus != PTR_STATUS_INIT) { | |
675 | + return; | |
676 | + } | |
677 | + | |
678 | + mFlag |= atOnce ? FLAG_AUTO_REFRESH_AT_ONCE : FLAG_AUTO_REFRESH_BUT_LATER; | |
679 | + | |
680 | + mStatus = PTR_STATUS_PREPARE; | |
681 | + if (mPtrUIHandlerHolder.hasHandler()) { | |
682 | + mPtrUIHandlerHolder.onUIRefreshPrepare(this); | |
683 | + if (DEBUG) { | |
684 | + PtrCLog.i(LOG_TAG, "PtrUIHandler: onUIRefreshPrepare, mFlag %s", mFlag); | |
685 | + } | |
686 | + } | |
687 | + mScrollChecker.tryToScrollTo(mPtrIndicator.getOffsetToRefresh(), duration); | |
688 | + if (atOnce) { | |
689 | + mStatus = PTR_STATUS_LOADING; | |
690 | + performRefresh(); | |
691 | + } | |
692 | + } | |
693 | + | |
694 | + public boolean isAutoRefresh() { | |
695 | + return (mFlag & MASK_AUTO_REFRESH) > 0; | |
696 | + } | |
697 | + | |
698 | + private boolean performAutoRefreshButLater() { | |
699 | + return (mFlag & MASK_AUTO_REFRESH) == FLAG_AUTO_REFRESH_BUT_LATER; | |
700 | + } | |
701 | + | |
702 | + public boolean isEnabledNextPtrAtOnce() { | |
703 | + return (mFlag & FLAG_ENABLE_NEXT_PTR_AT_ONCE) > 0; | |
704 | + } | |
705 | + | |
706 | + /** | |
707 | + * If @param enable has been set to true. The user can perform next PTR at once. | |
708 | + * | |
709 | + * @param enable | |
710 | + */ | |
711 | + public void setEnabledNextPtrAtOnce(boolean enable) { | |
712 | + if (enable) { | |
713 | + mFlag = mFlag | FLAG_ENABLE_NEXT_PTR_AT_ONCE; | |
714 | + } else { | |
715 | + mFlag = mFlag & ~FLAG_ENABLE_NEXT_PTR_AT_ONCE; | |
716 | + } | |
717 | + } | |
718 | + | |
719 | + public boolean isPinContent() { | |
720 | + return (mFlag & FLAG_PIN_CONTENT) > 0; | |
721 | + } | |
722 | + | |
723 | + /** | |
724 | + * The content view will now move when {@param pinContent} set to true. | |
725 | + * | |
726 | + * @param pinContent | |
727 | + */ | |
728 | + public void setPinContent(boolean pinContent) { | |
729 | + if (pinContent) { | |
730 | + mFlag = mFlag | FLAG_PIN_CONTENT; | |
731 | + } else { | |
732 | + mFlag = mFlag & ~FLAG_PIN_CONTENT; | |
733 | + } | |
734 | + } | |
735 | + | |
736 | + /** | |
737 | + * It's useful when working with viewpager. | |
738 | + * | |
739 | + * @param disable | |
740 | + */ | |
741 | + public void disableWhenHorizontalMove(boolean disable) { | |
742 | + mDisableWhenHorizontalMove = disable; | |
743 | + } | |
744 | + | |
745 | + /** | |
746 | + * loading will last at least for so long | |
747 | + * | |
748 | + * @param time | |
749 | + */ | |
750 | + public void setLoadingMinTime(int time) { | |
751 | + mLoadingMinTime = time; | |
752 | + } | |
753 | + | |
754 | + /** | |
755 | + * Not necessary any longer. Once moved, cancel event will be sent to child. | |
756 | + * | |
757 | + * @param yes | |
758 | + */ | |
759 | + @Deprecated | |
760 | + public void setInterceptEventWhileWorking(boolean yes) { | |
761 | + } | |
762 | + | |
763 | + @SuppressWarnings({"unused"}) | |
764 | + public View getContentView() { | |
765 | + return mContent; | |
766 | + } | |
767 | + | |
768 | + public void setPtrHandler(PtrHandler ptrHandler) { | |
769 | + mPtrHandler = ptrHandler; | |
770 | + } | |
771 | + | |
772 | + public void addPtrUIHandler(PtrUIHandler ptrUIHandler) { | |
773 | + PtrUIHandlerHolder.addHandler(mPtrUIHandlerHolder, ptrUIHandler); | |
774 | + } | |
775 | + | |
776 | + @SuppressWarnings({"unused"}) | |
777 | + public void removePtrUIHandler(PtrUIHandler ptrUIHandler) { | |
778 | + mPtrUIHandlerHolder = PtrUIHandlerHolder.removeHandler(mPtrUIHandlerHolder, ptrUIHandler); | |
779 | + } | |
780 | + | |
781 | + public void setPtrIndicator(PtrIndicator slider) { | |
782 | + if (mPtrIndicator != null && mPtrIndicator != slider) { | |
783 | + slider.convertFrom(mPtrIndicator); | |
784 | + } | |
785 | + mPtrIndicator = slider; | |
786 | + } | |
787 | + | |
788 | + @SuppressWarnings({"unused"}) | |
789 | + public float getResistance() { | |
790 | + return mPtrIndicator.getResistance(); | |
791 | + } | |
792 | + | |
793 | + public void setResistance(float resistance) { | |
794 | + mPtrIndicator.setResistance(resistance); | |
795 | + } | |
796 | + | |
797 | + @SuppressWarnings({"unused"}) | |
798 | + public float getDurationToClose() { | |
799 | + return mDurationToClose; | |
800 | + } | |
801 | + | |
802 | + /** | |
803 | + * The duration to return back to the refresh position | |
804 | + * | |
805 | + * @param duration | |
806 | + */ | |
807 | + public void setDurationToClose(int duration) { | |
808 | + mDurationToClose = duration; | |
809 | + } | |
810 | + | |
811 | + @SuppressWarnings({"unused"}) | |
812 | + public long getDurationToCloseHeader() { | |
813 | + return mDurationToCloseHeader; | |
814 | + } | |
815 | + | |
816 | + /** | |
817 | + * The duration to close time | |
818 | + * | |
819 | + * @param duration | |
820 | + */ | |
821 | + public void setDurationToCloseHeader(int duration) { | |
822 | + mDurationToCloseHeader = duration; | |
823 | + } | |
824 | + | |
825 | + public void setRatioOfHeaderHeightToRefresh(float ratio) { | |
826 | + mPtrIndicator.setRatioOfHeaderHeightToRefresh(ratio); | |
827 | + } | |
828 | + | |
829 | + public int getOffsetToRefresh() { | |
830 | + return mPtrIndicator.getOffsetToRefresh(); | |
831 | + } | |
832 | + | |
833 | + @SuppressWarnings({"unused"}) | |
834 | + public void setOffsetToRefresh(int offset) { | |
835 | + mPtrIndicator.setOffsetToRefresh(offset); | |
836 | + } | |
837 | + | |
838 | + @SuppressWarnings({"unused"}) | |
839 | + public float getRatioOfHeaderToHeightRefresh() { | |
840 | + return mPtrIndicator.getRatioOfHeaderToHeightRefresh(); | |
841 | + } | |
842 | + | |
843 | + @SuppressWarnings({"unused"}) | |
844 | + public int getOffsetToKeepHeaderWhileLoading() { | |
845 | + return mPtrIndicator.getOffsetToKeepHeaderWhileLoading(); | |
846 | + } | |
847 | + | |
848 | + @SuppressWarnings({"unused"}) | |
849 | + public void setOffsetToKeepHeaderWhileLoading(int offset) { | |
850 | + mPtrIndicator.setOffsetToKeepHeaderWhileLoading(offset); | |
851 | + } | |
852 | + | |
853 | + @SuppressWarnings({"unused"}) | |
854 | + public boolean isKeepHeaderWhenRefresh() { | |
855 | + return mKeepHeaderWhenRefresh; | |
856 | + } | |
857 | + | |
858 | + public void setKeepHeaderWhenRefresh(boolean keepOrNot) { | |
859 | + mKeepHeaderWhenRefresh = keepOrNot; | |
860 | + } | |
861 | + | |
862 | + public boolean isPullToRefresh() { | |
863 | + return mPullToRefresh; | |
864 | + } | |
865 | + | |
866 | + public void setPullToRefresh(boolean pullToRefresh) { | |
867 | + mPullToRefresh = pullToRefresh; | |
868 | + } | |
869 | + | |
870 | + @SuppressWarnings({"unused"}) | |
871 | + public View getHeaderView() { | |
872 | + return mHeaderView; | |
873 | + } | |
874 | + | |
875 | + public void setHeaderView(View header) { | |
876 | + if (mHeaderView != null && header != null && mHeaderView != header) { | |
877 | + removeView(mHeaderView); | |
878 | + } | |
879 | + ViewGroup.LayoutParams lp = header.getLayoutParams(); | |
880 | + if (lp == null) { | |
881 | + lp = new LayoutParams(-1, -2); | |
882 | + header.setLayoutParams(lp); | |
883 | + } | |
884 | + mHeaderView = header; | |
885 | + addView(header); | |
886 | + } | |
887 | + | |
888 | + @Override | |
889 | + protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { | |
890 | + return p != null && p instanceof LayoutParams; | |
891 | + } | |
892 | + | |
893 | + @Override | |
894 | + protected ViewGroup.LayoutParams generateDefaultLayoutParams() { | |
895 | + return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); | |
896 | + } | |
897 | + | |
898 | + @Override | |
899 | + protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { | |
900 | + return new LayoutParams(p); | |
901 | + } | |
902 | + | |
903 | + @Override | |
904 | + public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { | |
905 | + return new LayoutParams(getContext(), attrs); | |
906 | + } | |
907 | + | |
908 | + private void sendCancelEvent() { | |
909 | + if (DEBUG) { | |
910 | + PtrCLog.d(LOG_TAG, "send cancel event"); | |
911 | + } | |
912 | + // The ScrollChecker will update position and lead to send cancel event when mLastMoveEvent is null. | |
913 | + // fix #104, #80, #92 | |
914 | + if (mLastMoveEvent == null) { | |
915 | + return; | |
916 | + } | |
917 | + MotionEvent last = mLastMoveEvent; | |
918 | + MotionEvent e = MotionEvent.obtain(last.getDownTime(), last.getEventTime() + ViewConfiguration.getLongPressTimeout(), MotionEvent.ACTION_CANCEL, last.getX(), last.getY(), last.getMetaState()); | |
919 | + dispatchTouchEventSupper(e); | |
920 | + } | |
921 | + | |
922 | + private void sendDownEvent() { | |
923 | + if (DEBUG) { | |
924 | + PtrCLog.d(LOG_TAG, "send down event"); | |
925 | + } | |
926 | + final MotionEvent last = mLastMoveEvent; | |
927 | + MotionEvent e = MotionEvent.obtain(last.getDownTime(), last.getEventTime(), MotionEvent.ACTION_DOWN, last.getX(), last.getY(), last.getMetaState()); | |
928 | + dispatchTouchEventSupper(e); | |
929 | + } | |
930 | + | |
931 | + public static class LayoutParams extends MarginLayoutParams { | |
932 | + | |
933 | + public LayoutParams(Context c, AttributeSet attrs) { | |
934 | + super(c, attrs); | |
935 | + } | |
936 | + | |
937 | + public LayoutParams(int width, int height) { | |
938 | + super(width, height); | |
939 | + } | |
940 | + | |
941 | + @SuppressWarnings({"unused"}) | |
942 | + public LayoutParams(MarginLayoutParams source) { | |
943 | + super(source); | |
944 | + } | |
945 | + | |
946 | + public LayoutParams(ViewGroup.LayoutParams source) { | |
947 | + super(source); | |
948 | + } | |
949 | + } | |
950 | + | |
951 | + class ScrollChecker implements Runnable { | |
952 | + | |
953 | + private int mLastFlingY; | |
954 | + private Scroller mScroller; | |
955 | + private boolean mIsRunning = false; | |
956 | + private int mStart; | |
957 | + private int mTo; | |
958 | + | |
959 | + public ScrollChecker() { | |
960 | + mScroller = new Scroller(getContext()); | |
961 | + } | |
962 | + | |
963 | + public void run() { | |
964 | + boolean finish = !mScroller.computeScrollOffset() || mScroller.isFinished(); | |
965 | + int curY = mScroller.getCurrY(); | |
966 | + int deltaY = curY - mLastFlingY; | |
967 | + if (DEBUG) { | |
968 | + if (deltaY != 0) { | |
969 | + PtrCLog.v(LOG_TAG, | |
970 | + "scroll: %s, start: %s, to: %s, currentPos: %s, current :%s, last: %s, delta: %s", | |
971 | + finish, mStart, mTo, mPtrIndicator.getCurrentPosY(), curY, mLastFlingY, deltaY); | |
972 | + } | |
973 | + } | |
974 | + if (!finish) { | |
975 | + mLastFlingY = curY; | |
976 | + movePos(deltaY); | |
977 | + post(this); | |
978 | + } else { | |
979 | + finish(); | |
980 | + } | |
981 | + } | |
982 | + | |
983 | + private void finish() { | |
984 | + if (DEBUG) { | |
985 | + PtrCLog.v(LOG_TAG, "finish, currentPos:%s", mPtrIndicator.getCurrentPosY()); | |
986 | + } | |
987 | + reset(); | |
988 | + onPtrScrollFinish(); | |
989 | + } | |
990 | + | |
991 | + private void reset() { | |
992 | + mIsRunning = false; | |
993 | + mLastFlingY = 0; | |
994 | + removeCallbacks(this); | |
995 | + } | |
996 | + | |
997 | + private void destroy() { | |
998 | + reset(); | |
999 | + if (!mScroller.isFinished()) { | |
1000 | + mScroller.forceFinished(true); | |
1001 | + } | |
1002 | + } | |
1003 | + | |
1004 | + public void abortIfWorking() { | |
1005 | + if (mIsRunning) { | |
1006 | + if (!mScroller.isFinished()) { | |
1007 | + mScroller.forceFinished(true); | |
1008 | + } | |
1009 | + onPtrScrollAbort(); | |
1010 | + reset(); | |
1011 | + } | |
1012 | + } | |
1013 | + | |
1014 | + public void tryToScrollTo(int to, int duration) { | |
1015 | + if (mPtrIndicator.isAlreadyHere(to)) { | |
1016 | + return; | |
1017 | + } | |
1018 | + mStart = mPtrIndicator.getCurrentPosY(); | |
1019 | + mTo = to; | |
1020 | + int distance = to - mStart; | |
1021 | + if (DEBUG) { | |
1022 | + PtrCLog.d(LOG_TAG, "tryToScrollTo: start: %s, distance:%s, to:%s", mStart, distance, to); | |
1023 | + } | |
1024 | + removeCallbacks(this); | |
1025 | + | |
1026 | + mLastFlingY = 0; | |
1027 | + | |
1028 | + // fix #47: Scroller should be reused, https://github.com/liaohuqiu/android-Ultra-Pull-To-Refresh/issues/47 | |
1029 | + if (!mScroller.isFinished()) { | |
1030 | + mScroller.forceFinished(true); | |
1031 | + } | |
1032 | + mScroller.startScroll(0, 0, 0, distance, duration); | |
1033 | + post(this); | |
1034 | + mIsRunning = true; | |
1035 | + } | |
1036 | + } | |
1037 | +} | ... | ... |
android/src/main/java/com/hzl/pulltorefresh/refresh/PtrHandler.java
0 → 100755
1 | +++ a/android/src/main/java/com/hzl/pulltorefresh/refresh/PtrHandler.java | |
1 | +package com.hzl.pulltorefresh.refresh; | |
2 | + | |
3 | +import android.view.View; | |
4 | + | |
5 | +public interface PtrHandler { | |
6 | + | |
7 | + /** | |
8 | + * Check can do refresh or not. For example the content is empty or the first child is in view. | |
9 | + * <p/> | |
10 | + */ | |
11 | + public boolean checkCanDoRefresh(final PtrFrameLayout frame, final View content, final View header); | |
12 | + | |
13 | + /** | |
14 | + * When refresh begin | |
15 | + * | |
16 | + * @param frame | |
17 | + */ | |
18 | + public void onRefreshBegin(final PtrFrameLayout frame); | |
19 | +} | |
0 | 20 | \ No newline at end of file | ... | ... |
android/src/main/java/com/hzl/pulltorefresh/refresh/PtrUIHandler.java
0 → 100755
1 | +++ a/android/src/main/java/com/hzl/pulltorefresh/refresh/PtrUIHandler.java | |
1 | +package com.hzl.pulltorefresh.refresh; | |
2 | + | |
3 | + | |
4 | +import com.hzl.pulltorefresh.refresh.indicator.PtrIndicator; | |
5 | + | |
6 | +/** | |
7 | + * | |
8 | + */ | |
9 | +public interface PtrUIHandler { | |
10 | + | |
11 | + /** | |
12 | + * When the content view has reached top and refresh has been completed, view will be reset. | |
13 | + * | |
14 | + * @param frame | |
15 | + */ | |
16 | + public void onUIReset(PtrFrameLayout frame); | |
17 | + | |
18 | + /** | |
19 | + * prepare for loading | |
20 | + * | |
21 | + * @param frame | |
22 | + */ | |
23 | + public void onUIRefreshPrepare(PtrFrameLayout frame); | |
24 | + | |
25 | + /** | |
26 | + * perform refreshing UI | |
27 | + */ | |
28 | + public void onUIRefreshBegin(PtrFrameLayout frame); | |
29 | + | |
30 | + /** | |
31 | + * perform UI after refresh | |
32 | + */ | |
33 | + public void onUIRefreshComplete(PtrFrameLayout frame); | |
34 | + | |
35 | + public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator); | |
36 | +} | ... | ... |
android/src/main/java/com/hzl/pulltorefresh/refresh/PtrUIHandlerHolder.java
0 → 100755
1 | +++ a/android/src/main/java/com/hzl/pulltorefresh/refresh/PtrUIHandlerHolder.java | |
1 | +package com.hzl.pulltorefresh.refresh; | |
2 | + | |
3 | + | |
4 | +import com.hzl.pulltorefresh.refresh.indicator.PtrIndicator; | |
5 | + | |
6 | +/** | |
7 | + * A single linked list to wrap PtrUIHandler | |
8 | + */ | |
9 | +class PtrUIHandlerHolder implements PtrUIHandler { | |
10 | + | |
11 | + private PtrUIHandler mHandler; | |
12 | + private PtrUIHandlerHolder mNext; | |
13 | + | |
14 | + private boolean contains(PtrUIHandler handler) { | |
15 | + return mHandler != null && mHandler == handler; | |
16 | + } | |
17 | + | |
18 | + private PtrUIHandlerHolder() { | |
19 | + | |
20 | + } | |
21 | + | |
22 | + public boolean hasHandler() { | |
23 | + return mHandler != null; | |
24 | + } | |
25 | + | |
26 | + private PtrUIHandler getHandler() { | |
27 | + return mHandler; | |
28 | + } | |
29 | + | |
30 | + public static void addHandler(PtrUIHandlerHolder head, PtrUIHandler handler) { | |
31 | + | |
32 | + if (null == handler) { | |
33 | + return; | |
34 | + } | |
35 | + if (head == null) { | |
36 | + return; | |
37 | + } | |
38 | + if (null == head.mHandler) { | |
39 | + head.mHandler = handler; | |
40 | + return; | |
41 | + } | |
42 | + | |
43 | + PtrUIHandlerHolder current = head; | |
44 | + for (; ; current = current.mNext) { | |
45 | + | |
46 | + // duplicated | |
47 | + if (current.contains(handler)) { | |
48 | + return; | |
49 | + } | |
50 | + if (current.mNext == null) { | |
51 | + break; | |
52 | + } | |
53 | + } | |
54 | + | |
55 | + PtrUIHandlerHolder newHolder = new PtrUIHandlerHolder(); | |
56 | + newHolder.mHandler = handler; | |
57 | + current.mNext = newHolder; | |
58 | + } | |
59 | + | |
60 | + public static PtrUIHandlerHolder create() { | |
61 | + return new PtrUIHandlerHolder(); | |
62 | + } | |
63 | + | |
64 | + public static PtrUIHandlerHolder removeHandler(PtrUIHandlerHolder head, PtrUIHandler handler) { | |
65 | + if (head == null || handler == null || null == head.mHandler) { | |
66 | + return head; | |
67 | + } | |
68 | + | |
69 | + PtrUIHandlerHolder current = head; | |
70 | + PtrUIHandlerHolder pre = null; | |
71 | + do { | |
72 | + | |
73 | + // delete current: link pre to next, unlink next from current; | |
74 | + // pre will no change, current move to next element; | |
75 | + if (current.contains(handler)) { | |
76 | + | |
77 | + // current is head | |
78 | + if (pre == null) { | |
79 | + | |
80 | + head = current.mNext; | |
81 | + current.mNext = null; | |
82 | + | |
83 | + current = head; | |
84 | + } else { | |
85 | + | |
86 | + pre.mNext = current.mNext; | |
87 | + current.mNext = null; | |
88 | + current = pre.mNext; | |
89 | + } | |
90 | + } else { | |
91 | + pre = current; | |
92 | + current = current.mNext; | |
93 | + } | |
94 | + | |
95 | + } while (current != null); | |
96 | + | |
97 | + if (head == null) { | |
98 | + head = new PtrUIHandlerHolder(); | |
99 | + } | |
100 | + return head; | |
101 | + } | |
102 | + | |
103 | + @Override | |
104 | + public void onUIReset(PtrFrameLayout frame) { | |
105 | + PtrUIHandlerHolder current = this; | |
106 | + do { | |
107 | + final PtrUIHandler handler = current.getHandler(); | |
108 | + if (null != handler) { | |
109 | + handler.onUIReset(frame); | |
110 | + } | |
111 | + } while ((current = current.mNext) != null); | |
112 | + } | |
113 | + | |
114 | + @Override | |
115 | + public void onUIRefreshPrepare(PtrFrameLayout frame) { | |
116 | + if (!hasHandler()) { | |
117 | + return; | |
118 | + } | |
119 | + PtrUIHandlerHolder current = this; | |
120 | + do { | |
121 | + final PtrUIHandler handler = current.getHandler(); | |
122 | + if (null != handler) { | |
123 | + handler.onUIRefreshPrepare(frame); | |
124 | + } | |
125 | + } while ((current = current.mNext) != null); | |
126 | + } | |
127 | + | |
128 | + @Override | |
129 | + public void onUIRefreshBegin(PtrFrameLayout frame) { | |
130 | + PtrUIHandlerHolder current = this; | |
131 | + do { | |
132 | + final PtrUIHandler handler = current.getHandler(); | |
133 | + if (null != handler) { | |
134 | + handler.onUIRefreshBegin(frame); | |
135 | + } | |
136 | + } while ((current = current.mNext) != null); | |
137 | + } | |
138 | + | |
139 | + @Override | |
140 | + public void onUIRefreshComplete(PtrFrameLayout frame) { | |
141 | + PtrUIHandlerHolder current = this; | |
142 | + do { | |
143 | + final PtrUIHandler handler = current.getHandler(); | |
144 | + if (null != handler) { | |
145 | + handler.onUIRefreshComplete(frame); | |
146 | + } | |
147 | + } while ((current = current.mNext) != null); | |
148 | + } | |
149 | + | |
150 | + @Override | |
151 | + public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) { | |
152 | + PtrUIHandlerHolder current = this; | |
153 | + do { | |
154 | + final PtrUIHandler handler = current.getHandler(); | |
155 | + if (null != handler) { | |
156 | + handler.onUIPositionChange(frame, isUnderTouch, status, ptrIndicator); | |
157 | + } | |
158 | + } while ((current = current.mNext) != null); | |
159 | + } | |
160 | +} | ... | ... |
android/src/main/java/com/hzl/pulltorefresh/refresh/PtrUIHandlerHook.java
0 → 100755
1 | +++ a/android/src/main/java/com/hzl/pulltorefresh/refresh/PtrUIHandlerHook.java | |
1 | +package com.hzl.pulltorefresh.refresh; | |
2 | + | |
3 | +/** | |
4 | + * Run a hook runnable, the runnable will run only once. | |
5 | + * After the runnable is done, call resume to resume. | |
6 | + * Once run, call takeover will directory call the resume action | |
7 | + */ | |
8 | +public abstract class PtrUIHandlerHook implements Runnable { | |
9 | + | |
10 | + private Runnable mResumeAction; | |
11 | + private static final byte STATUS_PREPARE = 0; | |
12 | + private static final byte STATUS_IN_HOOK = 1; | |
13 | + private static final byte STATUS_RESUMED = 2; | |
14 | + private byte mStatus = STATUS_PREPARE; | |
15 | + | |
16 | + public void takeOver() { | |
17 | + takeOver(null); | |
18 | + } | |
19 | + | |
20 | + public void takeOver(Runnable resumeAction) { | |
21 | + if (resumeAction != null) { | |
22 | + mResumeAction = resumeAction; | |
23 | + } | |
24 | + switch (mStatus) { | |
25 | + case STATUS_PREPARE: | |
26 | + mStatus = STATUS_IN_HOOK; | |
27 | + run(); | |
28 | + break; | |
29 | + case STATUS_IN_HOOK: | |
30 | + break; | |
31 | + case STATUS_RESUMED: | |
32 | + resume(); | |
33 | + break; | |
34 | + } | |
35 | + } | |
36 | + | |
37 | + public void reset() { | |
38 | + mStatus = STATUS_PREPARE; | |
39 | + } | |
40 | + | |
41 | + public void resume() { | |
42 | + if (mResumeAction != null) { | |
43 | + mResumeAction.run(); | |
44 | + } | |
45 | + mStatus = STATUS_RESUMED; | |
46 | + } | |
47 | + | |
48 | + /** | |
49 | + * Hook should always have a resume action, which is hooked by this hook. | |
50 | + * | |
51 | + * @param runnable | |
52 | + */ | |
53 | + public void setResumeAction(Runnable runnable) { | |
54 | + mResumeAction = runnable; | |
55 | + } | |
56 | +} | |
0 | 57 | \ No newline at end of file | ... | ... |
android/src/main/java/com/hzl/pulltorefresh/refresh/header/RefreshHeader.java
0 → 100644
1 | +++ a/android/src/main/java/com/hzl/pulltorefresh/refresh/header/RefreshHeader.java | |
1 | +package com.hzl.pulltorefresh.refresh.header; | |
2 | + | |
3 | +import android.content.Context; | |
4 | +import androidx.annotation.NonNull; | |
5 | + | |
6 | +import com.facebook.react.views.view.ReactViewGroup; | |
7 | +import com.hzl.pulltorefresh.refresh.PtrFrameLayout; | |
8 | +import com.hzl.pulltorefresh.refresh.PtrUIHandler; | |
9 | +import com.hzl.pulltorefresh.refresh.indicator.PtrIndicator; | |
10 | + | |
11 | +/** | |
12 | + * 作者:请叫我百米冲刺 on 2018/1/2 下午1:21 | |
13 | + * 邮箱:mail@hezhilin.cc | |
14 | + */ | |
15 | + | |
16 | +public class RefreshHeader extends ReactViewGroup implements PtrUIHandler { | |
17 | + | |
18 | + private PullStateChangeListener listener; | |
19 | + | |
20 | + public void setPullStateChangeListener(PullStateChangeListener listener) { | |
21 | + this.listener = listener; | |
22 | + } | |
23 | + | |
24 | + public RefreshHeader(@NonNull Context context) { | |
25 | + super(context); | |
26 | + } | |
27 | + | |
28 | + @Override | |
29 | + public void onUIReset(PtrFrameLayout frame) { | |
30 | + | |
31 | + } | |
32 | + | |
33 | + @Override | |
34 | + public void onUIRefreshPrepare(PtrFrameLayout frame) { | |
35 | + | |
36 | + } | |
37 | + | |
38 | + @Override | |
39 | + public void onUIRefreshBegin(PtrFrameLayout frame) { | |
40 | + | |
41 | + } | |
42 | + | |
43 | + @Override | |
44 | + public void onUIRefreshComplete(PtrFrameLayout frame) { | |
45 | + | |
46 | + } | |
47 | + | |
48 | + @Override | |
49 | + public void onUIPositionChange(PtrFrameLayout frame, boolean isUnderTouch, byte status, PtrIndicator ptrIndicator) { | |
50 | + if (listener != null) { | |
51 | + listener.onStateChange(isUnderTouch, status, ptrIndicator.getCurrentPosY()); | |
52 | + } | |
53 | + } | |
54 | + | |
55 | + | |
56 | + public interface PullStateChangeListener { | |
57 | + void onStateChange(boolean isUnderTouch, int state, int moveHeight); | |
58 | + } | |
59 | +} | ... | ... |
android/src/main/java/com/hzl/pulltorefresh/refresh/indicator/PtrIndicator.java
0 → 100755
1 | +++ a/android/src/main/java/com/hzl/pulltorefresh/refresh/indicator/PtrIndicator.java | |
1 | +package com.hzl.pulltorefresh.refresh.indicator; | |
2 | + | |
3 | +import android.graphics.PointF; | |
4 | + | |
5 | +public class PtrIndicator { | |
6 | + | |
7 | + public final static int POS_START = 0; | |
8 | + protected int mOffsetToRefresh = 0; | |
9 | + private PointF mPtLastMove = new PointF(); | |
10 | + private float mOffsetX; | |
11 | + private float mOffsetY; | |
12 | + private int mCurrentPos = 0; | |
13 | + private int mLastPos = 0; | |
14 | + private int mHeaderHeight; | |
15 | + private int mPressedPos = 0; | |
16 | + | |
17 | + private float mRatioOfHeaderHeightToRefresh = 1.2f; | |
18 | + private float mResistance = 1.7f; | |
19 | + private boolean mIsUnderTouch = false; | |
20 | + private int mOffsetToKeepHeaderWhileLoading = -1; | |
21 | + // record the refresh complete position | |
22 | + private int mRefreshCompleteY = 0; | |
23 | + | |
24 | + public boolean isUnderTouch() { | |
25 | + return mIsUnderTouch; | |
26 | + } | |
27 | + | |
28 | + public float getResistance() { | |
29 | + return mResistance; | |
30 | + } | |
31 | + | |
32 | + public void setResistance(float resistance) { | |
33 | + mResistance = resistance; | |
34 | + } | |
35 | + | |
36 | + public void onRelease() { | |
37 | + mIsUnderTouch = false; | |
38 | + } | |
39 | + | |
40 | + public void onUIRefreshComplete() { | |
41 | + mRefreshCompleteY = mCurrentPos; | |
42 | + } | |
43 | + | |
44 | + public boolean goDownCrossFinishPosition() { | |
45 | + return mCurrentPos >= mRefreshCompleteY; | |
46 | + } | |
47 | + | |
48 | + protected void processOnMove(float currentX, float currentY, float offsetX, float offsetY) { | |
49 | + setOffset(offsetX, offsetY / mResistance); | |
50 | + } | |
51 | + | |
52 | + public void setRatioOfHeaderHeightToRefresh(float ratio) { | |
53 | + mRatioOfHeaderHeightToRefresh = ratio; | |
54 | + mOffsetToRefresh = (int) (mHeaderHeight * ratio); | |
55 | + } | |
56 | + | |
57 | + public float getRatioOfHeaderToHeightRefresh() { | |
58 | + return mRatioOfHeaderHeightToRefresh; | |
59 | + } | |
60 | + | |
61 | + public int getOffsetToRefresh() { | |
62 | + return mOffsetToRefresh; | |
63 | + } | |
64 | + | |
65 | + public void setOffsetToRefresh(int offset) { | |
66 | + mRatioOfHeaderHeightToRefresh = mHeaderHeight * 1f / offset; | |
67 | + mOffsetToRefresh = offset; | |
68 | + } | |
69 | + | |
70 | + public void onPressDown(float x, float y) { | |
71 | + mIsUnderTouch = true; | |
72 | + mPressedPos = mCurrentPos; | |
73 | + mPtLastMove.set(x, y); | |
74 | + } | |
75 | + | |
76 | + public final void onMove(float x, float y) { | |
77 | + float offsetX = x - mPtLastMove.x; | |
78 | + float offsetY = (y - mPtLastMove.y); | |
79 | + processOnMove(x, y, offsetX, offsetY); | |
80 | + mPtLastMove.set(x, y); | |
81 | + } | |
82 | + | |
83 | + protected void setOffset(float x, float y) { | |
84 | + mOffsetX = x; | |
85 | + mOffsetY = y; | |
86 | + } | |
87 | + | |
88 | + public float getOffsetX() { | |
89 | + return mOffsetX; | |
90 | + } | |
91 | + | |
92 | + public float getOffsetY() { | |
93 | + return mOffsetY; | |
94 | + } | |
95 | + | |
96 | + public int getLastPosY() { | |
97 | + return mLastPos; | |
98 | + } | |
99 | + | |
100 | + public int getCurrentPosY() { | |
101 | + return mCurrentPos; | |
102 | + } | |
103 | + | |
104 | + /** | |
105 | + * Update current position before update the UI | |
106 | + */ | |
107 | + public final void setCurrentPos(int current) { | |
108 | + mLastPos = mCurrentPos; | |
109 | + mCurrentPos = current; | |
110 | + onUpdatePos(current, mLastPos); | |
111 | + } | |
112 | + | |
113 | + protected void onUpdatePos(int current, int last) { | |
114 | + | |
115 | + } | |
116 | + | |
117 | + public int getHeaderHeight() { | |
118 | + return mHeaderHeight; | |
119 | + } | |
120 | + | |
121 | + public void setHeaderHeight(int height) { | |
122 | + mHeaderHeight = height; | |
123 | + updateHeight(); | |
124 | + } | |
125 | + | |
126 | + protected void updateHeight() { | |
127 | + mOffsetToRefresh = (int) (mRatioOfHeaderHeightToRefresh * mHeaderHeight); | |
128 | + } | |
129 | + | |
130 | + public void convertFrom(PtrIndicator ptrSlider) { | |
131 | + mCurrentPos = ptrSlider.mCurrentPos; | |
132 | + mLastPos = ptrSlider.mLastPos; | |
133 | + mHeaderHeight = ptrSlider.mHeaderHeight; | |
134 | + } | |
135 | + | |
136 | + public boolean hasLeftStartPosition() { | |
137 | + return mCurrentPos > POS_START; | |
138 | + } | |
139 | + | |
140 | + public boolean hasJustLeftStartPosition() { | |
141 | + return mLastPos == POS_START && hasLeftStartPosition(); | |
142 | + } | |
143 | + | |
144 | + public boolean hasJustBackToStartPosition() { | |
145 | + return mLastPos != POS_START && isInStartPosition(); | |
146 | + } | |
147 | + | |
148 | + public boolean isOverOffsetToRefresh() { | |
149 | + return mCurrentPos >= getOffsetToRefresh(); | |
150 | + } | |
151 | + | |
152 | + public boolean hasMovedAfterPressedDown() { | |
153 | + return mCurrentPos != mPressedPos; | |
154 | + } | |
155 | + | |
156 | + public boolean isInStartPosition() { | |
157 | + return mCurrentPos == POS_START; | |
158 | + } | |
159 | + | |
160 | + public boolean crossRefreshLineFromTopToBottom() { | |
161 | + return mLastPos < getOffsetToRefresh() && mCurrentPos >= getOffsetToRefresh(); | |
162 | + } | |
163 | + | |
164 | + public boolean hasJustReachedHeaderHeightFromTopToBottom() { | |
165 | + return mLastPos < mHeaderHeight && mCurrentPos >= mHeaderHeight; | |
166 | + } | |
167 | + | |
168 | + public boolean isOverOffsetToKeepHeaderWhileLoading() { | |
169 | + return mCurrentPos > getOffsetToKeepHeaderWhileLoading(); | |
170 | + } | |
171 | + | |
172 | + public void setOffsetToKeepHeaderWhileLoading(int offset) { | |
173 | + mOffsetToKeepHeaderWhileLoading = offset; | |
174 | + } | |
175 | + | |
176 | + public int getOffsetToKeepHeaderWhileLoading() { | |
177 | + return mOffsetToKeepHeaderWhileLoading >= 0 ? mOffsetToKeepHeaderWhileLoading : mHeaderHeight; | |
178 | + } | |
179 | + | |
180 | + public boolean isAlreadyHere(int to) { | |
181 | + return mCurrentPos == to; | |
182 | + } | |
183 | + | |
184 | + public float getLastPercent() { | |
185 | + final float oldPercent = mHeaderHeight == 0 ? 0 : mLastPos * 1f / mHeaderHeight; | |
186 | + return oldPercent; | |
187 | + } | |
188 | + | |
189 | + public float getCurrentPercent() { | |
190 | + final float currentPercent = mHeaderHeight == 0 ? 0 : mCurrentPos * 1f / mHeaderHeight; | |
191 | + return currentPercent; | |
192 | + } | |
193 | + | |
194 | + public boolean willOverTop(int to) { | |
195 | + return to < POS_START; | |
196 | + } | |
197 | +} | ... | ... |
android/src/main/java/com/hzl/pulltorefresh/refresh/indicator/PtrTensionIndicator.java
0 → 100755
1 | +++ a/android/src/main/java/com/hzl/pulltorefresh/refresh/indicator/PtrTensionIndicator.java | |
1 | +package com.hzl.pulltorefresh.refresh.indicator; | |
2 | + | |
3 | +public class PtrTensionIndicator extends PtrIndicator { | |
4 | + | |
5 | + private float DRAG_RATE = 0.5f; | |
6 | + private float mDownY; | |
7 | + private float mDownPos; | |
8 | + private float mOneHeight = 0; | |
9 | + | |
10 | + private float mCurrentDragPercent; | |
11 | + | |
12 | + private int mReleasePos; | |
13 | + private float mReleasePercent = -1; | |
14 | + | |
15 | + @Override | |
16 | + public void onPressDown(float x, float y) { | |
17 | + super.onPressDown(x, y); | |
18 | + mDownY = y; | |
19 | + mDownPos = getCurrentPosY(); | |
20 | + } | |
21 | + | |
22 | + @Override | |
23 | + public void onRelease() { | |
24 | + super.onRelease(); | |
25 | + mReleasePos = getCurrentPosY(); | |
26 | + mReleasePercent = mCurrentDragPercent; | |
27 | + } | |
28 | + | |
29 | + @Override | |
30 | + public void onUIRefreshComplete() { | |
31 | + mReleasePos = getCurrentPosY(); | |
32 | + mReleasePercent = getOverDragPercent(); | |
33 | + } | |
34 | + | |
35 | + @Override | |
36 | + public void setHeaderHeight(int height) { | |
37 | + super.setHeaderHeight(height); | |
38 | + mOneHeight = height * 4f / 5; | |
39 | + } | |
40 | + | |
41 | + @Override | |
42 | + protected void processOnMove(float currentX, float currentY, float offsetX, float offsetY) { | |
43 | + | |
44 | + if (currentY < mDownY) { | |
45 | + super.processOnMove(currentX, currentY, offsetX, offsetY); | |
46 | + return; | |
47 | + } | |
48 | + | |
49 | + // distance from top | |
50 | + final float scrollTop = (currentY - mDownY) * DRAG_RATE + mDownPos; | |
51 | + final float currentDragPercent = scrollTop / mOneHeight; | |
52 | + | |
53 | + if (currentDragPercent < 0) { | |
54 | + setOffset(offsetX, 0); | |
55 | + return; | |
56 | + } | |
57 | + | |
58 | + mCurrentDragPercent = currentDragPercent; | |
59 | + | |
60 | + // 0 ~ 1 | |
61 | + float boundedDragPercent = Math.min(1f, Math.abs(currentDragPercent)); | |
62 | + float extraOS = scrollTop - mOneHeight; | |
63 | + | |
64 | + // 0 ~ 2 | |
65 | + // if extraOS lower than 0, which means scrollTop lower than onHeight, tensionSlingshotPercent will be 0. | |
66 | + float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, mOneHeight * 2) / mOneHeight); | |
67 | + | |
68 | + float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow((tensionSlingshotPercent / 4), 2)) * 2f; | |
69 | + float extraMove = (mOneHeight) * tensionPercent / 2; | |
70 | + int targetY = (int) ((mOneHeight * boundedDragPercent) + extraMove); | |
71 | + int change = targetY - getCurrentPosY(); | |
72 | + | |
73 | + setOffset(currentX, change); | |
74 | + } | |
75 | + | |
76 | + private float offsetToTarget(float scrollTop) { | |
77 | + | |
78 | + // distance from top | |
79 | + final float currentDragPercent = scrollTop / mOneHeight; | |
80 | + | |
81 | + mCurrentDragPercent = currentDragPercent; | |
82 | + | |
83 | + // 0 ~ 1 | |
84 | + float boundedDragPercent = Math.min(1f, Math.abs(currentDragPercent)); | |
85 | + float extraOS = scrollTop - mOneHeight; | |
86 | + | |
87 | + // 0 ~ 2 | |
88 | + // if extraOS lower than 0, which means scrollTop lower than mOneHeight, tensionSlingshotPercent will be 0. | |
89 | + float tensionSlingshotPercent = Math.max(0, Math.min(extraOS, mOneHeight * 2) / mOneHeight); | |
90 | + | |
91 | + float tensionPercent = (float) ((tensionSlingshotPercent / 4) - Math.pow((tensionSlingshotPercent / 4), 2)) * 2f; | |
92 | + float extraMove = (mOneHeight) * tensionPercent / 2; | |
93 | + int targetY = (int) ((mOneHeight * boundedDragPercent) + extraMove); | |
94 | + | |
95 | + return 0; | |
96 | + } | |
97 | + | |
98 | + @Override | |
99 | + public int getOffsetToKeepHeaderWhileLoading() { | |
100 | + return getOffsetToRefresh(); | |
101 | + } | |
102 | + | |
103 | + @Override | |
104 | + public int getOffsetToRefresh() { | |
105 | + return (int) mOneHeight; | |
106 | + } | |
107 | + | |
108 | + public float getOverDragPercent() { | |
109 | + if (isUnderTouch()) { | |
110 | + return mCurrentDragPercent; | |
111 | + } else { | |
112 | + if (mReleasePercent <= 0) { | |
113 | + return 1.0f * getCurrentPosY() / getOffsetToKeepHeaderWhileLoading(); | |
114 | + } | |
115 | + // after release | |
116 | + return mReleasePercent * getCurrentPosY() / mReleasePos; | |
117 | + } | |
118 | + } | |
119 | +} | ... | ... |
android/src/main/java/com/hzl/pulltorefresh/refresh/util/PtrCLog.java
0 → 100755
1 | +++ a/android/src/main/java/com/hzl/pulltorefresh/refresh/util/PtrCLog.java | |
1 | +package com.hzl.pulltorefresh.refresh.util; | |
2 | + | |
3 | +import android.util.Log; | |
4 | + | |
5 | +/** | |
6 | + * An encapsulation of {@link Log}, enable log level and print log with parameters. | |
7 | + * | |
8 | + * @author http://www.liaohuqiu.net/ | |
9 | + */ | |
10 | +public class PtrCLog { | |
11 | + | |
12 | + public static final int LEVEL_VERBOSE = 0; | |
13 | + public static final int LEVEL_DEBUG = 1; | |
14 | + public static final int LEVEL_INFO = 2; | |
15 | + public static final int LEVEL_WARNING = 3; | |
16 | + public static final int LEVEL_ERROR = 4; | |
17 | + public static final int LEVEL_FATAL = 5; | |
18 | + | |
19 | + private static int sLevel = LEVEL_VERBOSE; | |
20 | + | |
21 | + /** | |
22 | + * set log level, the level lower than this level will not be logged | |
23 | + * | |
24 | + * @param level | |
25 | + */ | |
26 | + public static void setLogLevel(int level) { | |
27 | + sLevel = level; | |
28 | + } | |
29 | + | |
30 | + /** | |
31 | + * Send a VERBOSE log message. | |
32 | + * | |
33 | + * @param tag | |
34 | + * @param msg | |
35 | + */ | |
36 | + public static void v(String tag, String msg) { | |
37 | + if (sLevel > LEVEL_VERBOSE) { | |
38 | + return; | |
39 | + } | |
40 | + Log.v(tag, msg); | |
41 | + } | |
42 | + | |
43 | + /** | |
44 | + * Send a VERBOSE log message. | |
45 | + * | |
46 | + * @param tag | |
47 | + * @param msg | |
48 | + * @param throwable | |
49 | + */ | |
50 | + public static void v(String tag, String msg, Throwable throwable) { | |
51 | + if (sLevel > LEVEL_VERBOSE) { | |
52 | + return; | |
53 | + } | |
54 | + Log.v(tag, msg, throwable); | |
55 | + } | |
56 | + | |
57 | + /** | |
58 | + * Send a VERBOSE log message. | |
59 | + * | |
60 | + * @param tag | |
61 | + * @param msg | |
62 | + * @param args | |
63 | + */ | |
64 | + public static void v(String tag, String msg, Object... args) { | |
65 | + if (sLevel > LEVEL_VERBOSE) { | |
66 | + return; | |
67 | + } | |
68 | + if (args.length > 0) { | |
69 | + msg = String.format(msg, args); | |
70 | + } | |
71 | + Log.v(tag, msg); | |
72 | + } | |
73 | + | |
74 | + /** | |
75 | + * Send a DEBUG log message | |
76 | + * | |
77 | + * @param tag | |
78 | + * @param msg | |
79 | + */ | |
80 | + public static void d(String tag, String msg) { | |
81 | + if (sLevel > LEVEL_DEBUG) { | |
82 | + return; | |
83 | + } | |
84 | + Log.d(tag, msg); | |
85 | + } | |
86 | + | |
87 | + /** | |
88 | + * Send a DEBUG log message | |
89 | + * | |
90 | + * @param tag | |
91 | + * @param msg | |
92 | + * @param args | |
93 | + */ | |
94 | + public static void d(String tag, String msg, Object... args) { | |
95 | + if (sLevel > LEVEL_DEBUG) { | |
96 | + return; | |
97 | + } | |
98 | + if (args.length > 0) { | |
99 | + msg = String.format(msg, args); | |
100 | + } | |
101 | + Log.d(tag, msg); | |
102 | + } | |
103 | + | |
104 | + /** | |
105 | + * Send a DEBUG log message | |
106 | + * | |
107 | + * @param tag | |
108 | + * @param msg | |
109 | + * @param throwable | |
110 | + */ | |
111 | + public static void d(String tag, String msg, Throwable throwable) { | |
112 | + if (sLevel > LEVEL_DEBUG) { | |
113 | + return; | |
114 | + } | |
115 | + Log.d(tag, msg, throwable); | |
116 | + } | |
117 | + | |
118 | + /** | |
119 | + * Send an INFO log message | |
120 | + * | |
121 | + * @param tag | |
122 | + * @param msg | |
123 | + */ | |
124 | + public static void i(String tag, String msg) { | |
125 | + if (sLevel > LEVEL_INFO) { | |
126 | + return; | |
127 | + } | |
128 | + Log.i(tag, msg); | |
129 | + } | |
130 | + | |
131 | + /** | |
132 | + * Send an INFO log message | |
133 | + * | |
134 | + * @param tag | |
135 | + * @param msg | |
136 | + * @param args | |
137 | + */ | |
138 | + public static void i(String tag, String msg, Object... args) { | |
139 | + if (sLevel > LEVEL_INFO) { | |
140 | + return; | |
141 | + } | |
142 | + if (args.length > 0) { | |
143 | + msg = String.format(msg, args); | |
144 | + } | |
145 | + Log.i(tag, msg); | |
146 | + } | |
147 | + | |
148 | + /** | |
149 | + * Send an INFO log message | |
150 | + * | |
151 | + * @param tag | |
152 | + * @param msg | |
153 | + */ | |
154 | + public static void i(String tag, String msg, Throwable throwable) { | |
155 | + if (sLevel > LEVEL_INFO) { | |
156 | + return; | |
157 | + } | |
158 | + Log.i(tag, msg, throwable); | |
159 | + } | |
160 | + | |
161 | + /** | |
162 | + * Send a WARNING log message | |
163 | + * | |
164 | + * @param tag | |
165 | + * @param msg | |
166 | + */ | |
167 | + public static void w(String tag, String msg) { | |
168 | + if (sLevel > LEVEL_WARNING) { | |
169 | + return; | |
170 | + } | |
171 | + Log.w(tag, msg); | |
172 | + } | |
173 | + | |
174 | + /** | |
175 | + * Send a WARNING log message | |
176 | + * | |
177 | + * @param tag | |
178 | + * @param msg | |
179 | + * @param args | |
180 | + */ | |
181 | + public static void w(String tag, String msg, Object... args) { | |
182 | + if (sLevel > LEVEL_WARNING) { | |
183 | + return; | |
184 | + } | |
185 | + if (args.length > 0) { | |
186 | + msg = String.format(msg, args); | |
187 | + } | |
188 | + Log.w(tag, msg); | |
189 | + } | |
190 | + | |
191 | + /** | |
192 | + * Send a WARNING log message | |
193 | + * | |
194 | + * @param tag | |
195 | + * @param msg | |
196 | + * @param throwable | |
197 | + */ | |
198 | + public static void w(String tag, String msg, Throwable throwable) { | |
199 | + if (sLevel > LEVEL_WARNING) { | |
200 | + return; | |
201 | + } | |
202 | + Log.w(tag, msg, throwable); | |
203 | + } | |
204 | + | |
205 | + /** | |
206 | + * Send an ERROR log message | |
207 | + * | |
208 | + * @param tag | |
209 | + * @param msg | |
210 | + */ | |
211 | + public static void e(String tag, String msg) { | |
212 | + if (sLevel > LEVEL_ERROR) { | |
213 | + return; | |
214 | + } | |
215 | + Log.e(tag, msg); | |
216 | + } | |
217 | + | |
218 | + /** | |
219 | + * Send an ERROR log message | |
220 | + * | |
221 | + * @param tag | |
222 | + * @param msg | |
223 | + * @param args | |
224 | + */ | |
225 | + public static void e(String tag, String msg, Object... args) { | |
226 | + if (sLevel > LEVEL_ERROR) { | |
227 | + return; | |
228 | + } | |
229 | + if (args.length > 0) { | |
230 | + msg = String.format(msg, args); | |
231 | + } | |
232 | + Log.e(tag, msg); | |
233 | + } | |
234 | + | |
235 | + /** | |
236 | + * Send an ERROR log message | |
237 | + * | |
238 | + * @param tag | |
239 | + * @param msg | |
240 | + * @param throwable | |
241 | + */ | |
242 | + public static void e(String tag, String msg, Throwable throwable) { | |
243 | + if (sLevel > LEVEL_ERROR) { | |
244 | + return; | |
245 | + } | |
246 | + Log.e(tag, msg, throwable); | |
247 | + } | |
248 | + | |
249 | + /** | |
250 | + * Send a FATAL ERROR log message | |
251 | + * | |
252 | + * @param tag | |
253 | + * @param msg | |
254 | + */ | |
255 | + public static void f(String tag, String msg) { | |
256 | + if (sLevel > LEVEL_FATAL) { | |
257 | + return; | |
258 | + } | |
259 | + Log.wtf(tag, msg); | |
260 | + } | |
261 | + | |
262 | + /** | |
263 | + * Send a FATAL ERROR log message | |
264 | + * | |
265 | + * @param tag | |
266 | + * @param msg | |
267 | + * @param args | |
268 | + */ | |
269 | + public static void f(String tag, String msg, Object... args) { | |
270 | + if (sLevel > LEVEL_FATAL) { | |
271 | + return; | |
272 | + } | |
273 | + if (args.length > 0) { | |
274 | + msg = String.format(msg, args); | |
275 | + } | |
276 | + Log.wtf(tag, msg); | |
277 | + } | |
278 | + | |
279 | + /** | |
280 | + * Send a FATAL ERROR log message | |
281 | + * | |
282 | + * @param tag | |
283 | + * @param msg | |
284 | + * @param throwable | |
285 | + */ | |
286 | + public static void f(String tag, String msg, Throwable throwable) { | |
287 | + if (sLevel > LEVEL_FATAL) { | |
288 | + return; | |
289 | + } | |
290 | + Log.wtf(tag, msg, throwable); | |
291 | + } | |
292 | +} | ... | ... |
android/src/main/java/com/hzl/pulltorefresh/refresh/util/PtrLocalDisplay.java
0 → 100755
1 | +++ a/android/src/main/java/com/hzl/pulltorefresh/refresh/util/PtrLocalDisplay.java | |
1 | +package com.hzl.pulltorefresh.refresh.util; | |
2 | + | |
3 | +import android.content.Context; | |
4 | +import android.util.DisplayMetrics; | |
5 | +import android.view.View; | |
6 | +import android.view.WindowManager; | |
7 | + | |
8 | +public class PtrLocalDisplay { | |
9 | + | |
10 | + public static int SCREEN_WIDTH_PIXELS; | |
11 | + public static int SCREEN_HEIGHT_PIXELS; | |
12 | + public static float SCREEN_DENSITY; | |
13 | + public static int SCREEN_WIDTH_DP; | |
14 | + public static int SCREEN_HEIGHT_DP; | |
15 | + | |
16 | + public static void init(Context context) { | |
17 | + if (context == null) { | |
18 | + return; | |
19 | + } | |
20 | + DisplayMetrics dm = new DisplayMetrics(); | |
21 | + WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); | |
22 | + wm.getDefaultDisplay().getMetrics(dm); | |
23 | + SCREEN_WIDTH_PIXELS = dm.widthPixels; | |
24 | + SCREEN_HEIGHT_PIXELS = dm.heightPixels; | |
25 | + SCREEN_DENSITY = dm.density; | |
26 | + SCREEN_WIDTH_DP = (int) (SCREEN_WIDTH_PIXELS / dm.density); | |
27 | + SCREEN_HEIGHT_DP = (int) (SCREEN_HEIGHT_PIXELS / dm.density); | |
28 | + } | |
29 | + | |
30 | + public static int dp2px(float dp) { | |
31 | + final float scale = SCREEN_DENSITY; | |
32 | + return (int) (dp * scale + 0.5f); | |
33 | + } | |
34 | + | |
35 | + public static int designedDP2px(float designedDp) { | |
36 | + if (SCREEN_WIDTH_DP != 320) { | |
37 | + designedDp = designedDp * SCREEN_WIDTH_DP / 320f; | |
38 | + } | |
39 | + return dp2px(designedDp); | |
40 | + } | |
41 | + | |
42 | + public static void setPadding(final View view, float left, float top, float right, float bottom) { | |
43 | + view.setPadding(designedDP2px(left), dp2px(top), designedDP2px(right), dp2px(bottom)); | |
44 | + } | |
45 | +} | ... | ... |
android/src/main/java/com/hzl/pulltorefresh/refresh/view/RefreshHeadViewManager.java
0 → 100644
1 | +++ a/android/src/main/java/com/hzl/pulltorefresh/refresh/view/RefreshHeadViewManager.java | |
1 | +package com.hzl.pulltorefresh.refresh.view; | |
2 | + | |
3 | +import androidx.annotation.Nullable; | |
4 | +import android.view.ViewGroup; | |
5 | +import android.widget.RelativeLayout; | |
6 | + | |
7 | +import com.facebook.react.bridge.Arguments; | |
8 | +import com.facebook.react.bridge.WritableMap; | |
9 | +import com.facebook.react.common.MapBuilder; | |
10 | +import com.facebook.react.uimanager.ThemedReactContext; | |
11 | +import com.facebook.react.uimanager.ViewGroupManager; | |
12 | +import com.facebook.react.uimanager.annotations.ReactProp; | |
13 | +import com.facebook.react.uimanager.events.RCTEventEmitter; | |
14 | +import com.hzl.pulltorefresh.refresh.header.RefreshHeader; | |
15 | + | |
16 | +import java.util.Map; | |
17 | + | |
18 | +/** | |
19 | + * 作者:请叫我百米冲刺 on 2018/1/2 下午1:20 | |
20 | + * 邮箱:mail@hezhilin.cc | |
21 | + */ | |
22 | + | |
23 | +public class RefreshHeadViewManager extends ViewGroupManager<RefreshHeader> { | |
24 | + | |
25 | + @Override | |
26 | + public String getName() { | |
27 | + return "RCTRefreshHeader"; | |
28 | + } | |
29 | + | |
30 | + public enum Events { //这里是一些点击事件 | |
31 | + | |
32 | + ON_PUSHING_STATE("onPushingState"); | |
33 | + | |
34 | + private final String mName; | |
35 | + | |
36 | + Events(final String name) { | |
37 | + mName = name; | |
38 | + } | |
39 | + | |
40 | + @Override | |
41 | + public String toString() { | |
42 | + return mName; | |
43 | + } | |
44 | + } | |
45 | + | |
46 | + @Override | |
47 | + @Nullable | |
48 | + public Map getExportedCustomDirectEventTypeConstants() { | |
49 | + MapBuilder.Builder builder = MapBuilder.builder(); | |
50 | + for (Events event : Events.values()) { | |
51 | + builder.put(event.toString(), MapBuilder.of("registrationName", event.toString())); | |
52 | + } | |
53 | + return builder.build(); | |
54 | + } | |
55 | + | |
56 | + @Override | |
57 | + protected RefreshHeader createViewInstance(ThemedReactContext reactContext) { | |
58 | + final RefreshHeader refreshHeader = new RefreshHeader(reactContext); | |
59 | + final RCTEventEmitter mEventEmitter = reactContext.getJSModule(RCTEventEmitter.class); | |
60 | + refreshHeader.setPullStateChangeListener(new RefreshHeader.PullStateChangeListener() { | |
61 | + @Override | |
62 | + public void onStateChange(boolean isUnderTouch, int state, int moveHeight) { | |
63 | + //进行下拉触摸的回调 | |
64 | + WritableMap map = Arguments.createMap(); | |
65 | + map.putInt("moveHeight", moveHeight); | |
66 | + map.putInt("state", state); | |
67 | + mEventEmitter.receiveEvent(refreshHeader.getId(), Events.ON_PUSHING_STATE.toString(), map); | |
68 | + } | |
69 | + }); | |
70 | + return refreshHeader; | |
71 | + } | |
72 | + | |
73 | + @ReactProp(name = "viewHeight") | |
74 | + public void setViewHeight(RefreshHeader root, int viewHeight) { | |
75 | + root.setLayoutParams(new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, viewHeight)); | |
76 | + } | |
77 | +} | |
78 | + | ... | ... |
android/src/main/java/com/hzl/pulltorefresh/refresh/view/RefreshViewManager.java
0 → 100644
1 | +++ a/android/src/main/java/com/hzl/pulltorefresh/refresh/view/RefreshViewManager.java | |
1 | +package com.hzl.pulltorefresh.refresh.view; | |
2 | + | |
3 | +import androidx.annotation.Nullable; | |
4 | + | |
5 | +import com.facebook.react.bridge.Arguments; | |
6 | +import com.facebook.react.bridge.ReadableArray; | |
7 | +import com.facebook.react.common.MapBuilder; | |
8 | +import com.facebook.react.uimanager.ThemedReactContext; | |
9 | +import com.facebook.react.uimanager.ViewGroupManager; | |
10 | +import com.facebook.react.uimanager.annotations.ReactProp; | |
11 | +import com.facebook.react.uimanager.events.RCTEventEmitter; | |
12 | +import com.hzl.pulltorefresh.refresh.PtrDefaultHandler; | |
13 | +import com.hzl.pulltorefresh.refresh.PtrFrameLayout; | |
14 | + | |
15 | +import java.util.Map; | |
16 | + | |
17 | +/** | |
18 | + * 作者:请叫我百米冲刺 on 2018/1/2 上午11:49 | |
19 | + * 邮箱:mail@hezhilin.cc | |
20 | + */ | |
21 | +public class RefreshViewManager extends ViewGroupManager<PtrFrameLayout> { | |
22 | + | |
23 | + private static final int START_REFRESH = 1; | |
24 | + | |
25 | + private static final int FINISH_REFRESH = 2; | |
26 | + | |
27 | + @Override | |
28 | + public String getName() { | |
29 | + return "RCTRefreshView"; | |
30 | + } | |
31 | + | |
32 | + | |
33 | + public enum Events { //这里是一些点击事件 | |
34 | + | |
35 | + ON_RELEASE("onPullRelease"); | |
36 | + | |
37 | + private final String mName; | |
38 | + | |
39 | + Events(final String name) { | |
40 | + mName = name; | |
41 | + } | |
42 | + | |
43 | + @Override | |
44 | + public String toString() { | |
45 | + return mName; | |
46 | + } | |
47 | + } | |
48 | + | |
49 | + @Override | |
50 | + @Nullable | |
51 | + public Map getExportedCustomDirectEventTypeConstants() { | |
52 | + MapBuilder.Builder builder = MapBuilder.builder(); | |
53 | + for (Events event : Events.values()) { | |
54 | + builder.put(event.toString(), MapBuilder.of("registrationName", event.toString())); | |
55 | + } | |
56 | + return builder.build(); | |
57 | + } | |
58 | + | |
59 | + @Override | |
60 | + public Map<String, Integer> getCommandsMap() { | |
61 | + return MapBuilder.of("startRefresh", START_REFRESH, "finishRefresh", FINISH_REFRESH); | |
62 | + } | |
63 | + | |
64 | + @Override | |
65 | + public void receiveCommand(final PtrFrameLayout root, int commandId, @Nullable final ReadableArray args) { | |
66 | + switch (commandId) { | |
67 | + case START_REFRESH: | |
68 | + root.post(new Runnable() { | |
69 | + @Override | |
70 | + public void run() { | |
71 | + root.autoRefresh(); | |
72 | + } | |
73 | + }); | |
74 | + break; | |
75 | + case FINISH_REFRESH: | |
76 | + root.refreshComplete(); | |
77 | + break; | |
78 | + } | |
79 | + } | |
80 | + | |
81 | + @Override | |
82 | + protected PtrFrameLayout createViewInstance(ThemedReactContext reactContext) { | |
83 | + final PtrFrameLayout ptrFrameLayout = new PtrFrameLayout(reactContext); | |
84 | + final RCTEventEmitter mEventEmitter = reactContext.getJSModule(RCTEventEmitter.class); | |
85 | + ptrFrameLayout.setRatioOfHeaderHeightToRefresh(1.0f); | |
86 | + ptrFrameLayout.setPtrHandler(new PtrDefaultHandler() { | |
87 | + @Override | |
88 | + public void onRefreshBegin(PtrFrameLayout frame) { | |
89 | + mEventEmitter.receiveEvent(ptrFrameLayout.getId(), Events.ON_RELEASE.toString(), Arguments.createMap()); | |
90 | + } | |
91 | + }); | |
92 | + return ptrFrameLayout; | |
93 | + } | |
94 | + | |
95 | + @ReactProp(name = "isContentScroll") | |
96 | + public void setContentScroll(PtrFrameLayout root, boolean isContentScroll) { | |
97 | + root.setPinContent(!isContentScroll); | |
98 | + } | |
99 | + | |
100 | + @ReactProp(name = "refreshable") | |
101 | + public void setRefreshable(PtrFrameLayout root, boolean refreshable) { | |
102 | + root.setEnabled(refreshable); | |
103 | + } | |
104 | +} | ... | ... |
index.js
0 → 100644
1 | +++ a/index.js | |
1 | +import Pullable from './local/Pullable'; | |
2 | +import PullView from './pull/PullView'; | |
3 | +import PullScrollView from './pull/PullScrollView'; | |
4 | +import PullListView from './pull/PullListView'; | |
5 | +import PullFlatList from './pull/PullFlatList'; | |
6 | + | |
7 | +export {Pullable, PullView, PullScrollView, PullListView, PullFlatList}; | |
0 | 8 | \ No newline at end of file | ... | ... |
local/PullRoot.js
0 → 100644
1 | +++ a/local/PullRoot.js | |
1 | +/** | |
2 | + * 作者:请叫我百米冲刺 on 2018/1/5 上午9:19 | |
3 | + * 邮箱:mail@hezhilin.cc | |
4 | + */ | |
5 | +'use strict'; | |
6 | +import React, {PureComponent} from "react"; | |
7 | +import PropTypes from 'prop-types'; | |
8 | +import {ActivityIndicator, Text, StyleSheet, View} from 'react-native' | |
9 | +import * as index from './info'; | |
10 | + | |
11 | +export default class PullRoot extends PureComponent { | |
12 | + | |
13 | + static propTypes = { | |
14 | + refreshable: PropTypes.bool, | |
15 | + isContentScroll: PropTypes.bool, | |
16 | + onPullRelease: PropTypes.func, //下拉刷新的回调 | |
17 | + onPushing: PropTypes.func, //此时正在下拉刷新,通知外界 | |
18 | + | |
19 | + topIndicatorRender: PropTypes.func, //下拉刷新的render | |
20 | + topIndicatorHeight: PropTypes.number, //头部的高度 | |
21 | + onPullStateChangeHeight: PropTypes.func //状态的回调 | |
22 | + } | |
23 | + | |
24 | + static defaultProps = { | |
25 | + refreshable: true, //是否需要下拉刷新 | |
26 | + isContentScroll: false //内容是否需要跟着滚动,默认为false | |
27 | + }; | |
28 | + | |
29 | + renderTopIndicator = () => { | |
30 | + if (this.props.topIndicatorRender == null) { | |
31 | + return this.defaultTopIndicatorRender(); | |
32 | + } else { | |
33 | + return this.props.topIndicatorRender(); | |
34 | + } | |
35 | + } | |
36 | + | |
37 | + defaultTopIndicatorRender = () => { | |
38 | + return ( | |
39 | + <View style={{flexDirection: 'row', justifyContent: 'center', alignItems: 'center', height: index.defaultTopIndicatorHeight}}> | |
40 | + | |
41 | + <ActivityIndicator size="small" color="gray" style={{marginRight: 5}}/> | |
42 | + | |
43 | + <Text ref={(c) => this.txtPulling = c} style={styles.hide}>{index.pulling}</Text> | |
44 | + | |
45 | + <Text ref={(c) => this.txtPullok = c} style={styles.hide}>{index.pullok}</Text> | |
46 | + | |
47 | + <Text ref={(c) => this.txtPullrelease = c} style={styles.hide}>{index.pullrelease}</Text> | |
48 | + </View> | |
49 | + ); | |
50 | + } | |
51 | + | |
52 | + | |
53 | + defaultTopSetting = () => { | |
54 | + if (this.props.topIndicatorRender == null) { //没有就自己来 | |
55 | + if (this.pullState == "pulling") { | |
56 | + this.txtPulling && this.txtPulling.setNativeProps({style: styles.show}); | |
57 | + this.txtPullok && this.txtPullok.setNativeProps({style: styles.hide}); | |
58 | + this.txtPullrelease && this.txtPullrelease.setNativeProps({style: styles.hide}); | |
59 | + } else if (this.pullState == "pullok") { | |
60 | + this.txtPulling && this.txtPulling.setNativeProps({style: styles.hide}); | |
61 | + this.txtPullok && this.txtPullok.setNativeProps({style: styles.show}); | |
62 | + this.txtPullrelease && this.txtPullrelease.setNativeProps({style: styles.hide}); | |
63 | + } else if (this.pullState == "pullrelease") { | |
64 | + this.txtPulling && this.txtPulling.setNativeProps({style: styles.hide}); | |
65 | + this.txtPullok && this.txtPullok.setNativeProps({style: styles.hide}); | |
66 | + this.txtPullrelease && this.txtPullrelease.setNativeProps({style: styles.show}); | |
67 | + } | |
68 | + } | |
69 | + } | |
70 | +} | |
71 | + | |
72 | +const styles = StyleSheet.create({ | |
73 | + hide: { | |
74 | + position: 'absolute', | |
75 | + left: 10000, | |
76 | + backgroundColor: 'transparent' | |
77 | + }, | |
78 | + show: { | |
79 | + position: 'relative', | |
80 | + left: 0, | |
81 | + backgroundColor: 'transparent' | |
82 | + } | |
83 | +}); | |
84 | + | ... | ... |
local/Pullable.android.js
0 → 100644
1 | +++ a/local/Pullable.android.js | |
1 | +/** | |
2 | + * 作者:请叫我百米冲刺 on 2018/1/2 下午12:50 | |
3 | + * 邮箱:mail@hezhilin.cc | |
4 | + */ | |
5 | +'use strict'; | |
6 | +import React from 'react'; | |
7 | +import RefreshLayout from '../view/RefreshLayout' | |
8 | +import RefreshHeader from '../view/RefreshHeader' | |
9 | +import PullRoot from './PullRoot' | |
10 | +import * as index from './info'; | |
11 | + | |
12 | +export default class Pullable extends PullRoot { | |
13 | + | |
14 | + constructor(props) { | |
15 | + super(props); | |
16 | + this.pullState = 'pulling'; //pulling,pullok,pullrelease | |
17 | + this.topIndicatorHeight = this.props.topIndicatorHeight ? this.props.topIndicatorHeight : index.defaultTopIndicatorHeight; | |
18 | + } | |
19 | + | |
20 | + render() { | |
21 | + return ( | |
22 | + <RefreshLayout | |
23 | + {...this.props} | |
24 | + style={{flex: 1}} | |
25 | + ref={(c) => this.refresh = c}> | |
26 | + | |
27 | + <RefreshHeader | |
28 | + style={{flex: 1, height: this.topIndicatorHeight}} | |
29 | + viewHeight={index.dip2px(this.topIndicatorHeight)} | |
30 | + onPushingState={(e) => this.onPushingState(e)}> | |
31 | + {this.renderTopIndicator()} | |
32 | + </RefreshHeader> | |
33 | + | |
34 | + {this.getScrollable()} | |
35 | + </RefreshLayout> | |
36 | + ) | |
37 | + } | |
38 | + | |
39 | + | |
40 | + onPushingState = (event) => { | |
41 | + let moveHeight = event.nativeEvent.moveHeight | |
42 | + let state = event.nativeEvent.state | |
43 | + //因为返回的moveHeight单位是px,所以要将this.topIndicatorHeight转化为px进行计算 | |
44 | + let topHeight = index.dip2px(this.topIndicatorHeight) | |
45 | + if (moveHeight > 0 && moveHeight < topHeight) { //此时是下拉没有到位的状态 | |
46 | + this.pullState = "pulling" | |
47 | + } else if (moveHeight >= topHeight) { //下拉刷新到位 | |
48 | + this.pullState = "pullok" | |
49 | + } else { //下拉刷新释放,此时返回的值为-1 | |
50 | + this.pullState = "pullrelease" | |
51 | + } | |
52 | + //此时处于刷新中的状态 | |
53 | + if (state == 3) { | |
54 | + this.pullState = "pullrelease" | |
55 | + } | |
56 | + //默认的设置 | |
57 | + this.defaultTopSetting() | |
58 | + //告诉外界是否要锁住 | |
59 | + this.props.onPushing && this.props.onPushing(this.pullState != "pullrelease") | |
60 | + //进行状态和下拉距离的回调 | |
61 | + this.props.onPullStateChangeHeight && this.props.onPullStateChangeHeight(this.pullState, moveHeight) | |
62 | + | |
63 | + } | |
64 | + | |
65 | + finishRefresh = () => { | |
66 | + this.refresh && this.refresh.finishRefresh() | |
67 | + } | |
68 | + | |
69 | + startRefresh = () => { | |
70 | + this.refresh && this.refresh.startRefresh() | |
71 | + } | |
72 | +} | |
73 | + | ... | ... |
local/Pullable.ios.js
0 → 100644
1 | +++ a/local/Pullable.ios.js | |
1 | +'use strict'; | |
2 | + | |
3 | +import React from 'react'; | |
4 | +import {ListView, View, PanResponder, Animated, Easing, FlatList} from 'react-native'; | |
5 | +import * as index from './info'; | |
6 | +import PullRoot from './PullRoot' | |
7 | + | |
8 | +export default class Pullable extends PullRoot { | |
9 | + | |
10 | + constructor(props) { | |
11 | + super(props); | |
12 | + this.pullState = 'pulling'; //pulling,pullok,pullrelease | |
13 | + this.topIndicatorHeight = this.props.topIndicatorHeight ? this.props.topIndicatorHeight : index.defaultTopIndicatorHeight; | |
14 | + this.defaultXY = {x: 0, y: this.topIndicatorHeight * -1}; | |
15 | + this.duration = this.props.duration ? this.props.duration : index.defaultDuration; | |
16 | + this.state = Object.assign({}, props, { | |
17 | + pullPan: new Animated.ValueXY(this.defaultXY), | |
18 | + atTop: true, | |
19 | + height: 0, | |
20 | + width: 0 | |
21 | + }); | |
22 | + this.panResponder = PanResponder.create({ | |
23 | + onStartShouldSetPanResponder: this.onShouldSetPanResponder, | |
24 | + onStartShouldSetPanResponderCapture: this.onShouldSetPanResponder, | |
25 | + onMoveShouldSetPanResponder: this.onShouldSetPanResponder, | |
26 | + onMoveShouldSetPanResponderCapture: this.onShouldSetPanResponder, | |
27 | + onPanResponderTerminationRequest: (evt, gestureState) => false, //这个很重要,这边不放权 | |
28 | + onPanResponderMove: this.onPanResponderMove, | |
29 | + onPanResponderRelease: this.onPanResponderRelease, | |
30 | + onPanResponderTerminate: this.onPanResponderRelease, | |
31 | + }); | |
32 | + } | |
33 | + | |
34 | + onShouldSetPanResponder = (e, gesture) => { | |
35 | + let y = 0 | |
36 | + if (this.scroll instanceof ListView) { //ListView下的判断 | |
37 | + y = this.scroll.scrollProperties.offset; | |
38 | + } else if (this.scroll instanceof FlatList) {//FlatList下的判断 | |
39 | + y = this.scroll._listRef._getScrollMetrics().offset | |
40 | + } | |
41 | + //根据y的值来判断是否到达顶部 | |
42 | + this.state.atTop = (y <= 0) | |
43 | + if (this.state.atTop && index.isDownGesture(gesture.dx, gesture.dy) && this.props.refreshable) { | |
44 | + this.lastY = this.state.pullPan.y._value; | |
45 | + return true; | |
46 | + } | |
47 | + return false; | |
48 | + } | |
49 | + | |
50 | + onPanResponderMove = (e, gesture) => { | |
51 | + if (index.isDownGesture(gesture.dx, gesture.dy) && this.props.refreshable) { //下拉 | |
52 | + this.state.pullPan.setValue({x: this.defaultXY.x, y: this.lastY + gesture.dy / 2}); | |
53 | + this.onPullStateChange(gesture.dy) | |
54 | + } | |
55 | + } | |
56 | + | |
57 | + onPanResponderRelease = (e, gesture) => { | |
58 | + if (this.pullState == 'pulling') { //没有下拉到位 | |
59 | + this.resetDefaultXYHandler(); //重置状态 | |
60 | + } else if (this.pullState == 'pullok') { //已经下拉到位了 | |
61 | + //传入-1,表示此时进行的是释放刷新的操作 | |
62 | + this.onPullStateChange(-1) | |
63 | + //进行下拉刷新的回调 | |
64 | + this.props.onPullRelease && this.props.onPullRelease(); | |
65 | + //重置刷新的头部到初始位置 | |
66 | + Animated.timing(this.state.pullPan, { | |
67 | + toValue: {x: 0, y: 0}, | |
68 | + easing: Easing.linear, | |
69 | + duration: this.duration | |
70 | + }).start(); | |
71 | + } | |
72 | + } | |
73 | + | |
74 | + //重置刷新的操作 | |
75 | + resetDefaultXYHandler = () => { | |
76 | + Animated.timing(this.state.pullPan, { | |
77 | + toValue: this.defaultXY, | |
78 | + easing: Easing.linear, | |
79 | + duration: this.duration | |
80 | + }).start(() => { | |
81 | + //ui要进行刷新 | |
82 | + this.onPullStateChange(-1) | |
83 | + }); | |
84 | + } | |
85 | + | |
86 | + /** 数据加载完成后调用此方法进行重置归位 | |
87 | + */ | |
88 | + finishRefresh = () => { | |
89 | + if (this.pullState == 'pullrelease') { //仅触摸松开时才触发 | |
90 | + this.resetDefaultXYHandler(); | |
91 | + } | |
92 | + } | |
93 | + | |
94 | + startRefresh = () => { | |
95 | + if (!this.props.refreshable) { //不支持下拉刷新的时候就不进行了 | |
96 | + return; | |
97 | + } | |
98 | + //进行数据的回调 | |
99 | + this.props.onPullRelease && this.props.onPullRelease(); | |
100 | + //此时进行状态的改变 | |
101 | + this.onPullStateChange(-1) | |
102 | + //动画的展示 | |
103 | + Animated.timing(this.state.pullPan, { | |
104 | + toValue: {x: 0, y: 0}, | |
105 | + easing: Easing.linear, | |
106 | + duration: this.duration | |
107 | + }).start(); | |
108 | + } | |
109 | + | |
110 | + onLayout = (e) => { | |
111 | + if (this.state.width != e.nativeEvent.layout.width || this.state.height != e.nativeEvent.layout.height) { | |
112 | + this.scrollContainer && this.scrollContainer.setNativeProps({ | |
113 | + style: { | |
114 | + width: e.nativeEvent.layout.width, | |
115 | + height: e.nativeEvent.layout.height | |
116 | + } | |
117 | + }); | |
118 | + this.state.width = e.nativeEvent.layout.width; | |
119 | + this.state.height = e.nativeEvent.layout.height; | |
120 | + } | |
121 | + } | |
122 | + | |
123 | + render() { | |
124 | + return ( | |
125 | + <View style={{flex: 1, flexGrow: 1, zIndex: -999}} {...this.panResponder.panHandlers} onLayout={this.onLayout}> | |
126 | + {this.props.isContentScroll ? | |
127 | + <View pointerEvents='box-none'> | |
128 | + <Animated.View style={[this.state.pullPan.getLayout()]}> | |
129 | + {this.renderTopIndicator()} | |
130 | + <View ref={(c) => { | |
131 | + this.scrollContainer = c; | |
132 | + }} | |
133 | + style={{width: this.state.width, height: this.state.height}}> | |
134 | + {this.getScrollable()} | |
135 | + </View> | |
136 | + </Animated.View> | |
137 | + </View> : | |
138 | + | |
139 | + <View> | |
140 | + <View ref={(c) => { | |
141 | + this.scrollContainer = c; | |
142 | + }} | |
143 | + style={{width: this.state.width, height: this.state.height}}> | |
144 | + {this.getScrollable()} | |
145 | + </View> | |
146 | + <View pointerEvents='box-none' | |
147 | + style={{position: 'absolute', left: 0, right: 0, top: 0}}> | |
148 | + <Animated.View style={[this.state.pullPan.getLayout()]}> | |
149 | + {this.renderTopIndicator()} | |
150 | + </Animated.View> | |
151 | + </View> | |
152 | + </View>} | |
153 | + </View> | |
154 | + ); | |
155 | + } | |
156 | + | |
157 | + //下拉的时候根据高度进行对应的操作 | |
158 | + onPullStateChange = (moveHeight) => { | |
159 | + //因为返回的moveHeight单位是px,所以要将this.topIndicatorHeight转化为px进行计算 | |
160 | + let topHeight = index.dip2px(this.topIndicatorHeight) | |
161 | + if (moveHeight > 0 && moveHeight < topHeight) { //此时是下拉没有到位的状态 | |
162 | + this.pullState = "pulling" | |
163 | + } else if (moveHeight >= topHeight) { //下拉刷新到位 | |
164 | + this.pullState = "pullok" | |
165 | + } else { //下拉刷新释放,此时返回的值为-1 | |
166 | + this.pullState = "pullrelease" | |
167 | + } | |
168 | + //默认的设置 | |
169 | + this.defaultTopSetting() | |
170 | + //告诉外界是否要锁住 | |
171 | + this.props.onPushing && this.props.onPushing(this.pullState != "pullrelease") | |
172 | + //进行状态和下拉距离的回调 | |
173 | + this.props.onPullStateChangeHeight && this.props.onPullStateChangeHeight(this.pullState, moveHeight) | |
174 | + } | |
175 | +} | |
0 | 176 | \ No newline at end of file | ... | ... |
local/info.js
0 → 100644
1 | +++ a/local/info.js | |
1 | +import {PixelRatio} from 'react-native'; | |
2 | + | |
3 | +export const pulling = "下拉刷新..." | |
4 | +export const pullok = "松开刷新..." | |
5 | +export const pullrelease = "玩命刷新中..." | |
6 | + | |
7 | +export const defaultDuration = 300; | |
8 | +export const defaultTopIndicatorHeight = 50; //顶部刷新指示器的高度 | |
9 | +export const isDownGesture = (x, y) => {return y > 0 && (y > Math.abs(x));}; | |
10 | +export const dip2px = (dpValue) => {return Math.round(dpValue * PixelRatio.get())} | ... | ... |
package.json
0 → 100644
1 | +++ a/package.json | |
1 | +{ | |
2 | + "name": "@cqfw/react-native-pull-to-refresh", | |
3 | + "version": "1.0.0", | |
4 | + "description": "a pull to refresh component for react-native, same api on both android and ios.", | |
5 | + "main": "index.js", | |
6 | + "scripts": { | |
7 | + "test": "echo \"Error: no test specified\" && exit 1;" | |
8 | + }, | |
9 | + "repository": { | |
10 | + "type": "git", | |
11 | + "url": "git+git@gitlab.feewee.cn:sdk/react-native-pull-to-refresh.git" | |
12 | + }, | |
13 | + "keywords": [ | |
14 | + "react-native-pull-to-refresh", | |
15 | + "react-native", | |
16 | + "ios", | |
17 | + "android", | |
18 | + "pull-to-refresh", | |
19 | + "pull", | |
20 | + "refresh" | |
21 | + ], | |
22 | + "author": "kurisu", | |
23 | + "license": "MIT", | |
24 | + "homepage": "http://gitlab.feewee.cn/sdk/react-native-pull-to-refresh.git" | |
25 | +} | |
0 | 26 | \ No newline at end of file | ... | ... |
pull/PullFlatList.js
0 → 100644
1 | +++ a/pull/PullFlatList.js | |
1 | +'use strict'; | |
2 | +import React from 'react'; | |
3 | +import {FlatList} from 'react-native'; | |
4 | +import Pullable from '../local/Pullable'; | |
5 | + | |
6 | +export default class PullFlatList extends Pullable { | |
7 | + | |
8 | + getScrollable = () => { | |
9 | + return ( | |
10 | + <FlatList | |
11 | + ref={(c) => this.scroll = c} | |
12 | + {...this.props}/> | |
13 | + ); | |
14 | + } | |
15 | +} | ... | ... |
pull/PullListView.js
0 → 100644
1 | +++ a/pull/PullListView.js | |
1 | +'use strict'; | |
2 | + | |
3 | +import React from 'react'; | |
4 | +import {ListView} from 'react-native'; | |
5 | +import Pullable from '../local/Pullable'; | |
6 | + | |
7 | +export default class PullListView extends Pullable { | |
8 | + | |
9 | + getScrollable = () => { | |
10 | + return ( | |
11 | + <ListView | |
12 | + ref={(c) => this.scroll = c} | |
13 | + {...this.props}/> | |
14 | + ); | |
15 | + } | |
16 | +} | ... | ... |
pull/PullScrollView.js
0 → 100644
1 | +++ a/pull/PullScrollView.js | |
1 | +'use strict'; | |
2 | +import React from 'react'; | |
3 | +import {ScrollView, ListView,View} from 'react-native'; | |
4 | +import Pullable from '../local/Pullable'; | |
5 | + | |
6 | +//ScrollView 暂时没有找到比较好的方法去判断时候滚动到顶部, | |
7 | +//所以这里用ListView配合ScrollView进行使用 | |
8 | +export default class PullScrollView extends Pullable { | |
9 | + | |
10 | + getScrollable=()=> { | |
11 | + return ( | |
12 | + <ListView | |
13 | + ref={(c) => {this.scroll = c;}} | |
14 | + renderRow={this.renderRow} | |
15 | + dataSource={new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2}).cloneWithRows([])} | |
16 | + enableEmptySections={true} | |
17 | + renderHeader={this._renderHeader}/> | |
18 | + ); | |
19 | + } | |
20 | + | |
21 | + renderRow = (rowData, sectionID, rowID, highlightRow) => { | |
22 | + return <View/> | |
23 | + } | |
24 | + | |
25 | + _renderHeader = () => { | |
26 | + return ( | |
27 | + <ScrollView | |
28 | + scrollEnabled={false}> | |
29 | + {this.props.children} | |
30 | + </ScrollView> | |
31 | + ) | |
32 | + } | |
33 | + | |
34 | +} | |
35 | + | |
36 | + | |
37 | + | ... | ... |
pull/PullView.js
0 → 100644
1 | +++ a/pull/PullView.js | |
1 | +'use strict'; | |
2 | +import React from 'react'; | |
3 | +import {View} from 'react-native'; | |
4 | +import Pullable from '../local/Pullable'; | |
5 | + | |
6 | +export default class PullView extends Pullable { | |
7 | + | |
8 | + getScrollable = () => { | |
9 | + return ( | |
10 | + <View ref={(c) => {this.scroll = c;}} | |
11 | + {...this.props}> | |
12 | + {this.props.children} | |
13 | + </View> | |
14 | + ); | |
15 | + } | |
16 | +} | ... | ... |
view/RefreshHeader.android.js
0 → 100644
1 | +++ a/view/RefreshHeader.android.js | |
1 | +/** | |
2 | + * 作者:请叫我百米冲刺 on 2018/1/2 下午1:36 | |
3 | + * 邮箱:mail@hezhilin.cc | |
4 | + */ | |
5 | +'use strict'; | |
6 | +import React, {Component} from 'react'; | |
7 | +import PropTypes from 'prop-types'; | |
8 | +import {requireNativeComponent, View} from 'react-native'; | |
9 | + | |
10 | +const RefreshHeader = requireNativeComponent('RCTRefreshHeader', AndroidRefreshHeader) | |
11 | + | |
12 | +export default class AndroidRefreshHeader extends Component { | |
13 | + | |
14 | + static propTypes = { | |
15 | + onPushingState: PropTypes.func, //下拉状态的回调 | |
16 | + viewHeight: PropTypes.number, //这里得宁外指定控件的高度,否则不起效果 | |
17 | + ...View.propTypes | |
18 | + } | |
19 | + | |
20 | + render() { | |
21 | + return ( | |
22 | + <RefreshHeader {...this.props} viewHeight={this.props.viewHeight}/> | |
23 | + ) | |
24 | + } | |
25 | +} | ... | ... |
view/RefreshHeader.ios.js
0 → 100644
1 | +++ a/view/RefreshHeader.ios.js | |
1 | +/** | |
2 | + * 作者:请叫我百米冲刺 on 2018/1/2 下午1:36 | |
3 | + * 邮箱:mail@hezhilin.cc | |
4 | + */ | |
5 | +'use strict'; | |
6 | +import React, {Component} from "react"; | |
7 | +import {View} from "react-native"; | |
8 | +export default class RefreshHeader extends Component { | |
9 | + render() { | |
10 | + return (<View/>) | |
11 | + } | |
12 | +} | ... | ... |
view/RefreshLayout.android.js
0 → 100644
1 | +++ a/view/RefreshLayout.android.js | |
1 | +/** | |
2 | + * 作者:请叫我百米冲刺 on 2018/1/2 下午12:57 | |
3 | + * 邮箱:mail@hezhilin.cc | |
4 | + */ | |
5 | +'use strict'; | |
6 | +import React, {Component} from 'react'; | |
7 | +import PropTypes from 'prop-types'; | |
8 | +import {requireNativeComponent, View, UIManager, findNodeHandle} from 'react-native'; | |
9 | + | |
10 | +const RefreshLayout = requireNativeComponent('RCTRefreshView', AndroidRefreshLayout) | |
11 | + | |
12 | +export default class AndroidRefreshLayout extends Component { | |
13 | + | |
14 | + static propTypes = { | |
15 | + refreshable: PropTypes.bool, | |
16 | + isContentScroll: PropTypes.bool, | |
17 | + ...View.propTypes | |
18 | + } | |
19 | + | |
20 | + render() { | |
21 | + return ( | |
22 | + <RefreshLayout | |
23 | + ref={(c) => this.refresh = c} {...this.props}> | |
24 | + {this.props.children} | |
25 | + </RefreshLayout> | |
26 | + ) | |
27 | + } | |
28 | + | |
29 | + getRefreshLayoutHandle = () => { | |
30 | + return findNodeHandle(this.refresh); | |
31 | + } | |
32 | + | |
33 | + finishRefresh = () => { | |
34 | + UIManager.dispatchViewManagerCommand( | |
35 | + this.getRefreshLayoutHandle(), | |
36 | + UIManager.RCTRefreshView.Commands.finishRefresh, | |
37 | + null | |
38 | + ); | |
39 | + } | |
40 | + | |
41 | + startRefresh = () => { | |
42 | + UIManager.dispatchViewManagerCommand( | |
43 | + this.getRefreshLayoutHandle(), | |
44 | + UIManager.RCTRefreshView.Commands.startRefresh, | |
45 | + null | |
46 | + ); | |
47 | + } | |
48 | +} | ... | ... |
view/RefreshLayout.ios.js
0 → 100644
1 | +++ a/view/RefreshLayout.ios.js | |
1 | +/** | |
2 | + * 作者:请叫我百米冲刺 on 2018/1/2 下午1:00 | |
3 | + * 邮箱:mail@hezhilin.cc | |
4 | + */ | |
5 | +'use strict'; | |
6 | +import React, {Component} from "react"; | |
7 | +import {View} from "react-native"; | |
8 | +export default class extends Component { | |
9 | + render() { | |
10 | + return (<View/>) | |
11 | + } | |
12 | +} | ... | ... |