Commit 6f0384a21a74eb2cf9d678ce6a4b540581f231fa

Authored by 张志伟
0 parents

init

Showing 38 changed files with 3119 additions and 0 deletions
.gitignore 0 → 100644
  1 +++ a/.gitignore
  1 +/**/*.iml
  2 +/android/build
  3 +.DS_Store
0 4 \ No newline at end of file
... ...
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
  1 +++ a/android/src/main/AndroidManifest.xml
  1 +<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  2 + package="com.hzl.pulltorefresh" />
... ...
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 +}
... ...