├── .gitignore ├── README.md ├── android ├── README.md ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── byron │ │ └── refresh │ │ └── control │ │ ├── RNByronRefreshControl.java │ │ ├── RNByronRefreshControlManager.java │ │ ├── RNByronRefreshControlPackage.java │ │ ├── RNByronRefreshHeader.java │ │ ├── RNByronRefreshHeaderManager.java │ │ └── smart │ │ └── refresh │ │ ├── classics │ │ ├── ArrowDrawable.java │ │ └── ClassicsAbstract.java │ │ ├── drawable │ │ ├── PaintDrawable.java │ │ └── ProgressDrawable.java │ │ ├── footer │ │ └── ClassicsFooter.java │ │ ├── header │ │ └── ClassicsHeader.java │ │ └── layout │ │ ├── SmartRefreshLayout.java │ │ ├── api │ │ ├── RefreshComponent.java │ │ ├── RefreshContent.java │ │ ├── RefreshFooter.java │ │ ├── RefreshHeader.java │ │ ├── RefreshKernel.java │ │ └── RefreshLayout.java │ │ ├── constant │ │ ├── DimensionStatus.java │ │ ├── RefreshState.java │ │ └── SpinnerStyle.java │ │ ├── listener │ │ ├── CoordinatorLayoutListener.java │ │ ├── DefaultRefreshFooterCreator.java │ │ ├── DefaultRefreshHeaderCreator.java │ │ ├── DefaultRefreshInitializer.java │ │ ├── OnLoadMoreListener.java │ │ ├── OnMultiListener.java │ │ ├── OnRefreshListener.java │ │ ├── OnRefreshLoadMoreListener.java │ │ ├── OnStateChangedListener.java │ │ └── ScrollBoundaryDecider.java │ │ ├── simple │ │ ├── SimpleBoundaryDecider.java │ │ ├── SimpleComponent.java │ │ └── SimpleMultiListener.java │ │ ├── util │ │ ├── DesignUtil.java │ │ └── SmartUtil.java │ │ └── wrapper │ │ ├── RefreshContentWrapper.java │ │ ├── RefreshFooterWrapper.java │ │ └── RefreshHeaderWrapper.java │ └── res │ ├── layout │ ├── srl_classics_footer.xml │ └── srl_classics_header.xml │ ├── values-zh │ └── strings.xml │ └── values │ ├── attrs.xml │ ├── ids.xml │ └── strings.xml ├── assets └── arrow.png ├── example ├── .buckconfig ├── .editorconfig ├── .eslintrc.js ├── .flowconfig ├── .gitattributes ├── .gitignore ├── .prettierrc.js ├── .watchmanconfig ├── App.tsx ├── RefreshFlatList.tsx ├── __tests__ │ └── App-test.js ├── android │ ├── app │ │ ├── _BUCK │ │ ├── build.gradle │ │ ├── build_defs.bzl │ │ ├── debug.keystore │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── debug │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── ReactNativeFlipper.java │ │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── assets │ │ │ └── index.android.bundle │ │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ │ └── res │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ │ └── values │ │ │ ├── strings.xml │ │ │ └── styles.xml │ ├── build.gradle │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── app.json ├── assets │ └── arrow.png ├── babel.config.js ├── index.js ├── ios │ ├── Podfile │ ├── Podfile.lock │ ├── example.xcodeproj │ │ ├── project.pbxproj │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── example.xcscheme │ ├── example.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── example │ │ ├── AppDelegate.h │ │ ├── AppDelegate.m │ │ ├── Images.xcassets │ │ │ ├── AppIcon.appiconset │ │ │ │ └── Contents.json │ │ │ └── Contents.json │ │ ├── Info.plist │ │ ├── LaunchScreen.storyboard │ │ └── main.m │ └── exampleTests │ │ ├── Info.plist │ │ └── exampleTests.m ├── metro.config.js ├── package.json ├── tsconfig.json └── yarn.lock ├── index.d.ts ├── index.js ├── ios ├── MJRefresh │ ├── Base │ │ ├── MJRefreshAutoFooter.h │ │ ├── MJRefreshAutoFooter.m │ │ ├── MJRefreshBackFooter.h │ │ ├── MJRefreshBackFooter.m │ │ ├── MJRefreshComponent.h │ │ ├── MJRefreshComponent.m │ │ ├── MJRefreshFooter.h │ │ ├── MJRefreshFooter.m │ │ ├── MJRefreshHeader.h │ │ ├── MJRefreshHeader.m │ │ ├── MJRefreshTrailer.h │ │ └── MJRefreshTrailer.m │ ├── Custom │ │ ├── Footer │ │ │ ├── Auto │ │ │ │ ├── MJRefreshAutoGifFooter.h │ │ │ │ ├── MJRefreshAutoGifFooter.m │ │ │ │ ├── MJRefreshAutoNormalFooter.h │ │ │ │ ├── MJRefreshAutoNormalFooter.m │ │ │ │ ├── MJRefreshAutoStateFooter.h │ │ │ │ └── MJRefreshAutoStateFooter.m │ │ │ └── Back │ │ │ │ ├── MJRefreshBackGifFooter.h │ │ │ │ ├── MJRefreshBackGifFooter.m │ │ │ │ ├── MJRefreshBackNormalFooter.h │ │ │ │ ├── MJRefreshBackNormalFooter.m │ │ │ │ ├── MJRefreshBackStateFooter.h │ │ │ │ └── MJRefreshBackStateFooter.m │ │ ├── Header │ │ │ ├── MJRefreshGifHeader.h │ │ │ ├── MJRefreshGifHeader.m │ │ │ ├── MJRefreshNormalHeader.h │ │ │ ├── MJRefreshNormalHeader.m │ │ │ ├── MJRefreshStateHeader.h │ │ │ └── MJRefreshStateHeader.m │ │ └── Trailer │ │ │ ├── MJRefreshNormalTrailer.h │ │ │ ├── MJRefreshNormalTrailer.m │ │ │ ├── MJRefreshStateTrailer.h │ │ │ └── MJRefreshStateTrailer.m │ ├── Info.plist │ ├── MJRefresh.bundle │ │ ├── arrow@2x.png │ │ ├── en.lproj │ │ │ └── Localizable.strings │ │ ├── ko.lproj │ │ │ └── Localizable.strings │ │ ├── ru.lproj │ │ │ └── Localizable.strings │ │ ├── trail_arrow@2x.png │ │ ├── uk.lproj │ │ │ └── Localizable.strings │ │ ├── zh-Hans.lproj │ │ │ └── Localizable.strings │ │ └── zh-Hant.lproj │ │ │ └── Localizable.strings │ ├── MJRefresh.h │ ├── MJRefreshConfig.h │ ├── MJRefreshConfig.m │ ├── MJRefreshConst.h │ ├── MJRefreshConst.m │ ├── NSBundle+MJRefresh.h │ ├── NSBundle+MJRefresh.m │ ├── UICollectionViewLayout+MJRefresh.h │ ├── UICollectionViewLayout+MJRefresh.m │ ├── UIScrollView+MJExtension.h │ ├── UIScrollView+MJExtension.m │ ├── UIScrollView+MJRefresh.h │ ├── UIScrollView+MJRefresh.m │ ├── UIView+MJExtension.h │ ├── UIView+MJExtension.m │ └── include │ │ ├── MJRefresh.h │ │ ├── MJRefreshAutoFooter.h │ │ ├── MJRefreshAutoGifFooter.h │ │ ├── MJRefreshAutoNormalFooter.h │ │ ├── MJRefreshAutoStateFooter.h │ │ ├── MJRefreshBackFooter.h │ │ ├── MJRefreshBackGifFooter.h │ │ ├── MJRefreshBackNormalFooter.h │ │ ├── MJRefreshBackStateFooter.h │ │ ├── MJRefreshComponent.h │ │ ├── MJRefreshConfig.h │ │ ├── MJRefreshConst.h │ │ ├── MJRefreshFooter.h │ │ ├── MJRefreshGifHeader.h │ │ ├── MJRefreshHeader.h │ │ ├── MJRefreshNormalHeader.h │ │ ├── MJRefreshNormalTrailer.h │ │ ├── MJRefreshStateHeader.h │ │ ├── MJRefreshStateTrailer.h │ │ ├── MJRefreshTrailer.h │ │ ├── NSBundle+MJRefresh.h │ │ ├── UIScrollView+MJExtension.h │ │ ├── UIScrollView+MJRefresh.h │ │ └── UIView+MJExtension.h ├── RNByronCustomHeader.h ├── RNByronCustomHeader.m ├── RNByronRefreshControl.h ├── RNByronRefreshControl.m ├── RNByronRefreshControl.xcodeproj │ └── project.pbxproj ├── RNByronRefreshControl.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── RNByronRefreshHeader.h └── RNByronRefreshHeader.m ├── package.json └── react-native-refresh-control.podspec /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # node.js 6 | # 7 | node_modules/ 8 | npm-debug.log 9 | yarn-error.log 10 | 11 | # Xcode 12 | # 13 | build/ 14 | *.pbxuser 15 | !default.pbxuser 16 | *.mode1v3 17 | !default.mode1v3 18 | *.mode2v3 19 | !default.mode2v3 20 | *.perspectivev3 21 | !default.perspectivev3 22 | xcuserdata 23 | *.xccheckout 24 | *.moved-aside 25 | DerivedData 26 | *.hmap 27 | *.ipa 28 | *.xcuserstate 29 | project.xcworkspace 30 | 31 | # Android/IntelliJ 32 | # 33 | build/ 34 | .idea 35 | .gradle 36 | local.properties 37 | *.iml 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | -------------------------------------------------------------------------------- /android/README.md: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | 4 | If you want to publish the lib as a maven dependency, follow these steps before publishing a new version to npm: 5 | 6 | 1. Be sure to have the Android [SDK](https://developer.android.com/studio/index.html) and [NDK](https://developer.android.com/ndk/guides/index.html) installed 7 | 2. Be sure to have a `local.properties` file in this folder that points to the Android SDK and NDK 8 | ``` 9 | ndk.dir=/Users/{username}/Library/Android/sdk/ndk-bundle 10 | sdk.dir=/Users/{username}/Library/Android/sdk 11 | ``` 12 | 3. Delete the `maven` folder 13 | 4. Run `./gradlew installArchives` 14 | 5. Verify that latest set of generated files is in the maven folder with the correct version number 15 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | def safeExtGet(prop, fallback) { 4 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback 5 | } 6 | 7 | android { 8 | compileSdkVersion safeExtGet("compileSdkVersion", 29) 9 | buildToolsVersion safeExtGet("buildToolsVersion", '29.0.3') 10 | 11 | defaultConfig { 12 | minSdkVersion safeExtGet('minSdkVersion', 16) 13 | targetSdkVersion safeExtGet('targetSdkVersion', 29) 14 | } 15 | } 16 | 17 | dependencies { 18 | implementation 'com.facebook.react:react-native:+' 19 | implementation 'androidx.appcompat:appcompat:1.0.0' 20 | implementation 'com.google.android.material:material:1.0.0' 21 | } -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/RNByronRefreshControl.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control; 2 | 3 | import android.annotation.SuppressLint; 4 | 5 | import android.view.View; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import com.facebook.react.bridge.WritableMap; 10 | import com.facebook.react.bridge.WritableNativeMap; 11 | import com.facebook.react.uimanager.ThemedReactContext; 12 | import com.facebook.react.uimanager.events.RCTEventEmitter; 13 | import com.facebook.react.views.scroll.ReactScrollView; 14 | 15 | import byron.refresh.control.smart.refresh.layout.SmartRefreshLayout; 16 | import byron.refresh.control.smart.refresh.layout.api.RefreshHeader; 17 | import byron.refresh.control.smart.refresh.layout.api.RefreshLayout; 18 | import byron.refresh.control.smart.refresh.layout.constant.RefreshState; 19 | import byron.refresh.control.smart.refresh.layout.simple.SimpleMultiListener; 20 | import byron.refresh.control.smart.refresh.layout.util.SmartUtil; 21 | 22 | @SuppressLint("ViewConstructor") 23 | public class RNByronRefreshControl extends SmartRefreshLayout { 24 | // 刷新 25 | public static final String EVETN_NAME_CHANGE_OFFSET = "onChangeOffset"; 26 | public static final String EVETN_NAME_CHANGE_STATE = "onChangeState"; 27 | private final RCTEventEmitter eventEmitter; 28 | 29 | public RNByronRefreshControl(ThemedReactContext context) { 30 | super(context); 31 | eventEmitter = context.getJSModule(RCTEventEmitter.class); 32 | this.setEnableLoadMore(false); 33 | this.setEnableOverScrollDrag(true); 34 | this.setOnMultiListener(new SimpleMultiListener() { 35 | @Override 36 | public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) { 37 | WritableMap map = new WritableNativeMap(); 38 | if (newState == RefreshState.None) { 39 | map.putInt("state", 1); 40 | eventEmitter.receiveEvent(getTargetId(), EVETN_NAME_CHANGE_STATE, map); 41 | } else if (newState == RefreshState.ReleaseToRefresh) { 42 | map.putInt("state", 2); 43 | eventEmitter.receiveEvent(getTargetId(), EVETN_NAME_CHANGE_STATE, map); 44 | } else if (newState == RefreshState.Refreshing) { 45 | map.putInt("state", 3); 46 | eventEmitter.receiveEvent(getTargetId(), EVETN_NAME_CHANGE_STATE, map); 47 | } else if (newState == RefreshState.RefreshFinish) { 48 | map.putInt("state", 4); 49 | eventEmitter.receiveEvent(getTargetId(), EVETN_NAME_CHANGE_STATE, map); 50 | } 51 | } 52 | 53 | @Override 54 | public void onHeaderMoving(RefreshHeader header, boolean isDragging, float percent, int offset, int headerHeight, int maxDragHeight) { 55 | WritableMap map = new WritableNativeMap(); 56 | map.putDouble("offset", SmartUtil.px2dp(offset)); 57 | eventEmitter.receiveEvent(getTargetId(), EVETN_NAME_CHANGE_OFFSET, map); 58 | } 59 | }); 60 | } 61 | 62 | private int getTargetId() { 63 | return this.getId(); 64 | } 65 | 66 | public void setHeight(int height) { 67 | this.setHeaderHeight(height); 68 | } 69 | 70 | @Override 71 | public void addView(View child, int index) { 72 | if (child instanceof RNByronRefreshHeader) { 73 | this.setRefreshHeader((RefreshHeader) child); 74 | }else if (child instanceof ReactScrollView) { 75 | this.setRefreshContent(child); 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/RNByronRefreshControlManager.java: -------------------------------------------------------------------------------- 1 | // RNByronRefreshControlManager.java 2 | 3 | package byron.refresh.control; 4 | 5 | import android.util.Log; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | 10 | import com.facebook.react.common.MapBuilder; 11 | import com.facebook.react.uimanager.ViewGroupManager; 12 | import com.facebook.react.uimanager.ThemedReactContext; 13 | import com.facebook.react.uimanager.annotations.ReactProp; 14 | 15 | import java.util.Map; 16 | 17 | public class RNByronRefreshControlManager extends ViewGroupManager { 18 | 19 | @NonNull 20 | @Override 21 | public String getName() { 22 | return "RNByronRefreshControl"; 23 | } 24 | 25 | // 初始化 26 | @NonNull 27 | @Override 28 | protected RNByronRefreshControl createViewInstance(@NonNull ThemedReactContext reactContext) { 29 | return new RNByronRefreshControl(reactContext); 30 | } 31 | 32 | /** 33 | * 自定义事件 34 | */ 35 | @Nullable 36 | @Override 37 | public Map getExportedCustomDirectEventTypeConstants() { 38 | String onChangeStateEvent = RNByronRefreshControl.EVETN_NAME_CHANGE_STATE; 39 | String onChangeOffsetEvent = RNByronRefreshControl.EVETN_NAME_CHANGE_OFFSET; 40 | return MapBuilder.builder() 41 | .put(onChangeStateEvent, MapBuilder.of("registrationName", onChangeStateEvent)) 42 | .put(onChangeOffsetEvent, MapBuilder.of("registrationName", onChangeOffsetEvent)).build(); 43 | } 44 | 45 | @ReactProp(name = "refreshing") 46 | public void setRefreshing(RNByronRefreshControl view, Boolean refreshing) { 47 | if(refreshing){ 48 | view.autoRefresh(); 49 | } else { 50 | view.finishRefresh(); 51 | } 52 | } 53 | 54 | @ReactProp(name = "height") 55 | public void setHeight(RNByronRefreshControl view, int height) { 56 | view.setHeight(height); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/RNByronRefreshControlPackage.java: -------------------------------------------------------------------------------- 1 | // RNByronRefreshControlPackage.java 2 | 3 | package byron.refresh.control; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import java.util.Arrays; 8 | import java.util.Collections; 9 | import java.util.List; 10 | 11 | import com.facebook.react.ReactPackage; 12 | import com.facebook.react.bridge.NativeModule; 13 | import com.facebook.react.bridge.ReactApplicationContext; 14 | import com.facebook.react.uimanager.ViewManager; 15 | 16 | public class RNByronRefreshControlPackage implements ReactPackage { 17 | @NonNull 18 | @Override 19 | public List createNativeModules(@NonNull ReactApplicationContext reactContext) { 20 | return Collections.emptyList(); 21 | } 22 | 23 | @NonNull 24 | @Override 25 | public List createViewManagers(@NonNull ReactApplicationContext reactContext) { 26 | return Arrays.asList( 27 | new RNByronRefreshControlManager(), 28 | new RNByronRefreshHeaderManager() 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/RNByronRefreshHeader.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control; 2 | 3 | import static androidx.annotation.RestrictTo.Scope.LIBRARY; 4 | import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 5 | import static androidx.annotation.RestrictTo.Scope.SUBCLASSES; 6 | 7 | import android.content.Context; 8 | import android.view.View; 9 | 10 | import androidx.annotation.ColorInt; 11 | import androidx.annotation.NonNull; 12 | import androidx.annotation.RestrictTo; 13 | 14 | import com.facebook.react.views.view.ReactViewGroup; 15 | 16 | import byron.refresh.control.smart.refresh.layout.api.RefreshHeader; 17 | import byron.refresh.control.smart.refresh.layout.api.RefreshKernel; 18 | import byron.refresh.control.smart.refresh.layout.api.RefreshLayout; 19 | import byron.refresh.control.smart.refresh.layout.constant.RefreshState; 20 | import byron.refresh.control.smart.refresh.layout.constant.SpinnerStyle; 21 | 22 | public class RNByronRefreshHeader extends ReactViewGroup implements RefreshHeader { 23 | public RNByronRefreshHeader(Context context) { 24 | super(context); 25 | } 26 | 27 | /** 28 | * 获取实体视图 29 | * @return 实体视图 30 | */ 31 | @NonNull 32 | public View getView() { 33 | return this; 34 | } 35 | 36 | /** 37 | * 获取变换方式 {@link SpinnerStyle} 必须返回 非空 38 | * @return 变换方式 39 | */ 40 | @NonNull 41 | public SpinnerStyle getSpinnerStyle() { 42 | return SpinnerStyle.Translate; 43 | } 44 | 45 | /** 46 | * 【仅限框架内调用】设置主题颜色 47 | * @param colors 对应Xml中配置的 srlPrimaryColor srlAccentColor 48 | */ 49 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 50 | public void setPrimaryColors(@ColorInt int... colors) { 51 | 52 | } 53 | 54 | /** 55 | * 【仅限框架内调用】尺寸定义完成 (如果高度不改变(代码修改:setHeader),只调用一次, 在RefreshLayout#onMeasure中调用) 56 | * @param kernel RefreshKernel 57 | * @param height HeaderHeight or FooterHeight 58 | * @param maxDragHeight 最大拖动高度 59 | */ 60 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 61 | public void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight) { 62 | 63 | } 64 | /** 65 | * 【仅限框架内调用】手指拖动下拉(会连续多次调用,添加isDragging并取代之前的onPulling、onReleasing) 66 | * @param isDragging true 手指正在拖动 false 回弹动画 67 | * @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+maxDragHeight) / footerHeight ) 68 | * @param offset 下拉的像素偏移量 0 - offset - (footerHeight+maxDragHeight) 69 | * @param height 高度 HeaderHeight or FooterHeight (offset 可以超过 height 此时 percent 大于 1) 70 | * @param maxDragHeight 最大拖动高度 offset 可以超过 height 参数 但是不会超过 maxDragHeight 71 | */ 72 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 73 | public void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight) { 74 | 75 | } 76 | 77 | /** 78 | * 【仅限框架内调用】释放时刻(调用一次,将会触发加载) 79 | * @param refreshLayout RefreshLayout 80 | * @param height 高度 HeaderHeight or FooterHeight 81 | * @param maxDragHeight 最大拖动高度 82 | */ 83 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 84 | public void onReleased(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) { 85 | 86 | } 87 | 88 | /** 89 | * 【仅限框架内调用】开始动画 90 | * @param refreshLayout RefreshLayout 91 | * @param height HeaderHeight or FooterHeight 92 | * @param maxDragHeight 最大拖动高度 93 | */ 94 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 95 | public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight) { 96 | 97 | } 98 | 99 | /** 100 | * 【仅限框架内调用】动画结束 101 | * @param refreshLayout RefreshLayout 102 | * @param success 数据是否成功刷新或加载 103 | * @return 完成动画所需时间 如果返回 Integer.MAX_VALUE 将取消本次完成事件,继续保持原有状态 104 | */ 105 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 106 | public int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) { 107 | return 300; 108 | } 109 | 110 | /** 111 | * 【仅限框架内调用】水平方向的拖动 112 | * @param percentX 下拉时,手指水平坐标对屏幕的占比(0 - percentX - 1) 113 | * @param offsetX 下拉时,手指水平坐标对屏幕的偏移(0 - offsetX - LayoutWidth) 114 | * @param offsetMax 最大的偏移量 115 | */ 116 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 117 | public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { 118 | 119 | } 120 | 121 | /** 122 | * 是否支持水平方向的拖动(将会影响到onHorizontalDrag的调用) 123 | * @return 水平拖动需要消耗更多的时间和资源,所以如果不支持请返回false 124 | */ 125 | public boolean isSupportHorizontalDrag() { 126 | return false; 127 | } 128 | 129 | @Override 130 | public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) { 131 | 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/RNByronRefreshHeaderManager.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.facebook.react.uimanager.ThemedReactContext; 6 | import com.facebook.react.uimanager.ViewGroupManager; 7 | 8 | public class RNByronRefreshHeaderManager extends ViewGroupManager { 9 | 10 | @NonNull 11 | @Override 12 | public String getName() { 13 | return "RNByronRefreshHeader"; 14 | } 15 | 16 | @NonNull 17 | @Override 18 | protected RNByronRefreshHeader createViewInstance(@NonNull ThemedReactContext reactContext) { 19 | return new RNByronRefreshHeader(reactContext); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/classics/ArrowDrawable.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.classics; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.Path; 5 | import android.graphics.Rect; 6 | import android.graphics.drawable.Drawable; 7 | 8 | import androidx.annotation.NonNull; 9 | 10 | import byron.refresh.control.smart.refresh.drawable.PaintDrawable; 11 | 12 | /** 13 | * 箭头图像 14 | * Created by scwang on 2018/2/5. 15 | */ 16 | public class ArrowDrawable extends PaintDrawable { 17 | 18 | private int mWidth = 0; 19 | private int mHeight = 0; 20 | private final Path mPath = new Path(); 21 | 22 | @Override 23 | public void draw(@NonNull Canvas canvas) { 24 | final Drawable drawable = ArrowDrawable.this; 25 | final Rect bounds = drawable.getBounds(); 26 | final int width = bounds.width(); 27 | final int height = bounds.height(); 28 | if (mWidth != width || mHeight != height) { 29 | int lineWidth = width * 30 / 225; 30 | mPath.reset(); 31 | 32 | float vector1 = (lineWidth * 0.70710678118654752440084436210485f);//Math.sin(Math.PI/4)); 33 | float vector2 = (lineWidth / 0.70710678118654752440084436210485f);//Math.sin(Math.PI/4)); 34 | mPath.moveTo(width / 2f, height); 35 | mPath.lineTo(0, height / 2f); 36 | mPath.lineTo(vector1, height / 2f - vector1); 37 | mPath.lineTo(width / 2f - lineWidth / 2f, height - vector2 - lineWidth / 2f); 38 | mPath.lineTo(width / 2f - lineWidth / 2f, 0); 39 | mPath.lineTo(width / 2f + lineWidth / 2f, 0); 40 | mPath.lineTo(width / 2f + lineWidth / 2f, height - vector2 - lineWidth / 2f); 41 | mPath.lineTo(width - vector1, height / 2f - vector1); 42 | mPath.lineTo(width, height / 2f); 43 | mPath.close(); 44 | 45 | mWidth = width; 46 | mHeight = height; 47 | } 48 | canvas.drawPath(mPath, mPaint); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/drawable/PaintDrawable.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.drawable; 2 | 3 | import android.graphics.ColorFilter; 4 | import android.graphics.Paint; 5 | import android.graphics.PixelFormat; 6 | import android.graphics.drawable.Drawable; 7 | 8 | /** 9 | * 画笔 Drawable 10 | * Created by scwang on 2017/6/16. 11 | */ 12 | public abstract class PaintDrawable extends Drawable { 13 | 14 | protected Paint mPaint = new Paint(); 15 | 16 | protected PaintDrawable() { 17 | mPaint.setStyle(Paint.Style.FILL); 18 | mPaint.setAntiAlias(true); 19 | mPaint.setColor(0xffaaaaaa); 20 | } 21 | 22 | public void setColor(int color) { 23 | mPaint.setColor(color); 24 | } 25 | 26 | @Override 27 | public void setAlpha(int alpha) { 28 | mPaint.setAlpha(alpha); 29 | } 30 | 31 | @Override 32 | public void setColorFilter(ColorFilter cf) { 33 | mPaint.setColorFilter(cf); 34 | } 35 | 36 | @Override 37 | public int getOpacity() { 38 | return PixelFormat.TRANSLUCENT; 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/drawable/ProgressDrawable.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.drawable; 2 | 3 | import android.animation.Animator; 4 | import android.animation.ValueAnimator; 5 | import android.graphics.Canvas; 6 | import android.graphics.Path; 7 | import android.graphics.Rect; 8 | import android.graphics.drawable.Animatable; 9 | import android.graphics.drawable.Drawable; 10 | 11 | import androidx.annotation.NonNull; 12 | 13 | /** 14 | * 旋转动画 15 | * Created by scwang on 2017/6/16. 16 | */ 17 | @SuppressWarnings("WeakerAccess") 18 | public class ProgressDrawable extends PaintDrawable implements Animatable , ValueAnimator.AnimatorUpdateListener{ 19 | 20 | protected int mWidth = 0; 21 | protected int mHeight = 0; 22 | protected int mProgressDegree = 0; 23 | protected ValueAnimator mValueAnimator; 24 | protected Path mPath = new Path(); 25 | 26 | public ProgressDrawable() { 27 | mValueAnimator = ValueAnimator.ofInt(30, 3600); 28 | mValueAnimator.setDuration(10000); 29 | mValueAnimator.setInterpolator(null); 30 | mValueAnimator.setRepeatCount(ValueAnimator.INFINITE); 31 | mValueAnimator.setRepeatMode(ValueAnimator.RESTART); 32 | } 33 | 34 | @Override 35 | public void onAnimationUpdate(ValueAnimator animation) { 36 | int value = (int) animation.getAnimatedValue(); 37 | mProgressDegree = 30 * (value / 30); 38 | final Drawable drawable = ProgressDrawable.this; 39 | drawable.invalidateSelf(); 40 | } 41 | 42 | // 43 | @Override 44 | public void draw(@NonNull Canvas canvas) { 45 | final Drawable drawable = ProgressDrawable.this; 46 | final Rect bounds = drawable.getBounds(); 47 | final int width = bounds.width(); 48 | final int height = bounds.height(); 49 | final float r = Math.max(1f, width / 22f); 50 | 51 | if (mWidth != width || mHeight != height) { 52 | mPath.reset(); 53 | mPath.addCircle(width - r, height / 2f, r, Path.Direction.CW); 54 | mPath.addRect(width - 5 * r, height / 2f - r, width - r, height / 2f + r, Path.Direction.CW); 55 | mPath.addCircle(width - 5 * r, height / 2f, r, Path.Direction.CW); 56 | mWidth = width; 57 | mHeight = height; 58 | } 59 | 60 | canvas.save(); 61 | canvas.rotate(mProgressDegree, (width) / 2f, (height) / 2f); 62 | for (int i = 0; i < 12; i++) { 63 | mPaint.setAlpha((i+5) * 0x11); 64 | canvas.rotate(30, (width) / 2f, (height) / 2f); 65 | canvas.drawPath(mPath, mPaint); 66 | } 67 | canvas.restore(); 68 | } 69 | // 70 | 71 | @Override 72 | public void start() { 73 | if (!mValueAnimator.isRunning()) { 74 | mValueAnimator.addUpdateListener(this); 75 | mValueAnimator.start(); 76 | } 77 | } 78 | 79 | @Override 80 | public void stop() { 81 | if (mValueAnimator.isRunning()) { 82 | Animator animator = mValueAnimator; 83 | animator.removeAllListeners(); 84 | mValueAnimator.removeAllUpdateListeners(); 85 | mValueAnimator.cancel(); 86 | } 87 | } 88 | 89 | @Override 90 | public boolean isRunning() { 91 | return mValueAnimator.isRunning(); 92 | } 93 | 94 | } 95 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/api/RefreshComponent.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.api; 2 | 3 | import static androidx.annotation.RestrictTo.Scope.LIBRARY; 4 | import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 5 | import static androidx.annotation.RestrictTo.Scope.SUBCLASSES; 6 | 7 | import android.view.View; 8 | 9 | import androidx.annotation.ColorInt; 10 | import androidx.annotation.NonNull; 11 | import androidx.annotation.RestrictTo; 12 | 13 | import byron.refresh.control.smart.refresh.layout.constant.SpinnerStyle; 14 | import byron.refresh.control.smart.refresh.layout.listener.OnStateChangedListener; 15 | 16 | 17 | /** 18 | * 刷新内部组件 19 | * Created by scwang on 2017/5/26. 20 | */ 21 | public interface RefreshComponent extends OnStateChangedListener { 22 | /** 23 | * 获取实体视图 24 | * @return 实体视图 25 | */ 26 | @NonNull 27 | View getView(); 28 | 29 | /** 30 | * 获取变换方式 {@link SpinnerStyle} 必须返回 非空 31 | * @return 变换方式 32 | */ 33 | @NonNull 34 | SpinnerStyle getSpinnerStyle(); 35 | 36 | /** 37 | * 【仅限框架内调用】设置主题颜色 38 | * @param colors 对应Xml中配置的 srlPrimaryColor srlAccentColor 39 | */ 40 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 41 | void setPrimaryColors(@ColorInt int... colors); 42 | 43 | /** 44 | * 【仅限框架内调用】尺寸定义完成 (如果高度不改变(代码修改:setHeader),只调用一次, 在RefreshLayout#onMeasure中调用) 45 | * @param kernel RefreshKernel 46 | * @param height HeaderHeight or FooterHeight 47 | * @param maxDragHeight 最大拖动高度 48 | */ 49 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 50 | void onInitialized(@NonNull RefreshKernel kernel, int height, int maxDragHeight); 51 | /** 52 | * 【仅限框架内调用】手指拖动下拉(会连续多次调用,添加isDragging并取代之前的onPulling、onReleasing) 53 | * @param isDragging true 手指正在拖动 false 回弹动画 54 | * @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+maxDragHeight) / footerHeight ) 55 | * @param offset 下拉的像素偏移量 0 - offset - (footerHeight+maxDragHeight) 56 | * @param height 高度 HeaderHeight or FooterHeight (offset 可以超过 height 此时 percent 大于 1) 57 | * @param maxDragHeight 最大拖动高度 offset 可以超过 height 参数 但是不会超过 maxDragHeight 58 | */ 59 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 60 | void onMoving(boolean isDragging, float percent, int offset, int height, int maxDragHeight); 61 | 62 | /** 63 | * 【仅限框架内调用】释放时刻(调用一次,将会触发加载) 64 | * @param refreshLayout RefreshLayout 65 | * @param height 高度 HeaderHeight or FooterHeight 66 | * @param maxDragHeight 最大拖动高度 67 | */ 68 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 69 | void onReleased(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight); 70 | 71 | /** 72 | * 【仅限框架内调用】开始动画 73 | * @param refreshLayout RefreshLayout 74 | * @param height HeaderHeight or FooterHeight 75 | * @param maxDragHeight 最大拖动高度 76 | */ 77 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 78 | void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int maxDragHeight); 79 | 80 | /** 81 | * 【仅限框架内调用】动画结束 82 | * @param refreshLayout RefreshLayout 83 | * @param success 数据是否成功刷新或加载 84 | * @return 完成动画所需时间 如果返回 Integer.MAX_VALUE 将取消本次完成事件,继续保持原有状态 85 | */ 86 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 87 | int onFinish(@NonNull RefreshLayout refreshLayout, boolean success); 88 | 89 | /** 90 | * 【仅限框架内调用】水平方向的拖动 91 | * @param percentX 下拉时,手指水平坐标对屏幕的占比(0 - percentX - 1) 92 | * @param offsetX 下拉时,手指水平坐标对屏幕的偏移(0 - offsetX - LayoutWidth) 93 | * @param offsetMax 最大的偏移量 94 | */ 95 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 96 | void onHorizontalDrag(float percentX, int offsetX, int offsetMax); 97 | 98 | /** 99 | * 是否支持水平方向的拖动(将会影响到onHorizontalDrag的调用) 100 | * @return 水平拖动需要消耗更多的时间和资源,所以如果不支持请返回false 101 | */ 102 | boolean isSupportHorizontalDrag(); 103 | } 104 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/api/RefreshContent.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.api; 2 | 3 | import android.animation.ValueAnimator.AnimatorUpdateListener; 4 | import android.view.MotionEvent; 5 | import android.view.View; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import byron.refresh.control.smart.refresh.layout.listener.ScrollBoundaryDecider; 10 | 11 | /** 12 | * 刷新内容组件 13 | * Created by scwang on 2017/5/26. 14 | */ 15 | public interface RefreshContent { 16 | 17 | @NonNull 18 | View getView(); 19 | @NonNull 20 | View getScrollableView(); 21 | 22 | void onActionDown(MotionEvent e); 23 | 24 | void setUpComponent(RefreshKernel kernel, View fixedHeader, View fixedFooter); 25 | void setScrollBoundaryDecider(ScrollBoundaryDecider boundary); 26 | 27 | void setEnableLoadMoreWhenContentNotFull(boolean enable); 28 | 29 | void moveSpinner(int spinner, int headerTranslationViewId, int footerTranslationViewId); 30 | 31 | boolean canRefresh(); 32 | boolean canLoadMore(); 33 | 34 | AnimatorUpdateListener scrollContentWhenFinished(int spinner); 35 | } 36 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/api/RefreshFooter.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.api; 2 | 3 | 4 | import static androidx.annotation.RestrictTo.Scope.LIBRARY; 5 | import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 6 | import static androidx.annotation.RestrictTo.Scope.SUBCLASSES; 7 | 8 | import androidx.annotation.RestrictTo; 9 | 10 | /** 11 | * 刷新底部 12 | * Created by scwang on 2017/5/26. 13 | */ 14 | public interface RefreshFooter extends RefreshComponent { 15 | 16 | /** 17 | * 【仅限框架内调用】设置数据全部加载完成,将不能再次触发加载功能 18 | * @param noMoreData 是否有更多数据 19 | * @return true 支持全部加载完成的状态显示 false 不支持 20 | */ 21 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 22 | boolean setNoMoreData(boolean noMoreData); 23 | } 24 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/api/RefreshHeader.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.api; 2 | 3 | /** 4 | * 刷新头部 5 | * Created by scwang on 2017/5/26. 6 | */ 7 | public interface RefreshHeader extends RefreshComponent { 8 | 9 | } 10 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/api/RefreshKernel.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.api; 2 | 3 | import android.animation.ValueAnimator; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import byron.refresh.control.smart.refresh.layout.constant.RefreshState; 8 | 9 | /** 10 | * 刷新布局核心功能接口 11 | * 为功能复杂的 Header 或者 Footer 开放的接口 12 | * Created by scwang on 2017/5/26. 13 | */ 14 | @SuppressWarnings({"unused", "UnusedReturnValue", "SameParameterValue"}) 15 | public interface RefreshKernel { 16 | 17 | @NonNull 18 | RefreshLayout getRefreshLayout(); 19 | @NonNull 20 | RefreshContent getRefreshContent(); 21 | 22 | RefreshKernel setState(@NonNull RefreshState state); 23 | 24 | // 25 | 26 | /** 27 | * 开始执行二极刷新 28 | * @param open 是否展开 29 | * @return RefreshKernel 30 | */ 31 | RefreshKernel startTwoLevel(boolean open); 32 | 33 | /** 34 | * 结束关闭二极刷新 35 | * @return RefreshKernel 36 | */ 37 | RefreshKernel finishTwoLevel(); 38 | 39 | /** 40 | * 移动视图到指定位置 41 | * moveSpinner 的取名来自 谷歌官方 42 | * @param spinner 位置 (px) 43 | * @param isDragging true 手指正在拖动 false 回弹动画执行 44 | * @return RefreshKernel 45 | */ 46 | RefreshKernel moveSpinner(int spinner, boolean isDragging); 47 | 48 | /** 49 | * 执行动画使视图位移到指定的 位置 50 | * moveSpinner 的取名来自 谷歌官方 51 | * @param endSpinner 指定的结束位置 (px) 52 | * @return ValueAnimator 如果没有执行动画 null 53 | */ 54 | ValueAnimator animSpinner(int endSpinner); 55 | // 56 | 57 | // 58 | 59 | /** 60 | * 指定在下拉时候为 Header 或 Footer 绘制背景 61 | * @param internal Header Footer 调用时传 this 62 | * @param backgroundColor 背景颜色 63 | * @return RefreshKernel 64 | */ 65 | RefreshKernel requestDrawBackgroundFor(@NonNull RefreshComponent internal, int backgroundColor); 66 | /** 67 | * 请求事件 68 | * @param internal Header Footer 调用时传 this 69 | * @param request 请求 70 | * @return RefreshKernel 71 | */ 72 | RefreshKernel requestNeedTouchEventFor(@NonNull RefreshComponent internal, boolean request); 73 | /** 74 | * 请求设置默认内容滚动设置 75 | * @param internal Header Footer 调用时传 this 76 | * @param translation 移动 77 | * @return RefreshKernel 78 | */ 79 | RefreshKernel requestDefaultTranslationContentFor(@NonNull RefreshComponent internal, boolean translation); 80 | /** 81 | * 请求重新测量 headerHeight 或 footerHeight , 要求 height 高度为 WRAP_CONTENT 82 | * @param internal Header Footer 调用时传 this 83 | * @return RefreshKernel 84 | */ 85 | RefreshKernel requestRemeasureHeightFor(@NonNull RefreshComponent internal); 86 | /** 87 | * 设置二楼回弹时长 88 | * @param duration 二楼回弹时长 89 | * @return RefreshKernel 90 | */ 91 | RefreshKernel requestFloorDuration(int duration); 92 | /** 93 | * 设置二楼底部上划关闭所占高度的比率 94 | * @return RefreshKernel 95 | */ 96 | RefreshKernel requestFloorBottomPullUpToCloseRate(float rate); 97 | // 98 | } 99 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/constant/DimensionStatus.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.constant; 2 | 3 | /** 4 | * 尺寸值的定义状态,用于在值覆盖的时候决定优先级 5 | * 越往下优先级越高 6 | */ 7 | @SuppressWarnings("WeakerAccess") 8 | public class DimensionStatus { 9 | 10 | public static final DimensionStatus DefaultUnNotify = new DimensionStatus(0,false);//默认值,但是还没通知确认 11 | public static final DimensionStatus Default = new DimensionStatus(1,true);//默认值 12 | public static final DimensionStatus XmlWrapUnNotify = new DimensionStatus(2,false);//Xml计算,但是还没通知确认 13 | public static final DimensionStatus XmlWrap = new DimensionStatus(3,true);//Xml计算 14 | public static final DimensionStatus XmlExactUnNotify = new DimensionStatus(4,false);//Xml 的view 指定,但是还没通知确认 15 | public static final DimensionStatus XmlExact = new DimensionStatus(5,true);//Xml 的view 指定 16 | public static final DimensionStatus XmlLayoutUnNotify = new DimensionStatus(6,false);//Xml 的layout 中指定,但是还没通知确认 17 | public static final DimensionStatus XmlLayout = new DimensionStatus(7,true);//Xml 的layout 中指定 18 | public static final DimensionStatus CodeExactUnNotify = new DimensionStatus(8,false);//代码指定,但是还没通知确认 19 | public static final DimensionStatus CodeExact = new DimensionStatus(9,true);//代码指定 20 | public static final DimensionStatus DeadLockUnNotify = new DimensionStatus(10,false);//锁死,但是还没通知确认 21 | public static final DimensionStatus DeadLock = new DimensionStatus(10,true);//锁死 22 | 23 | public final int ordinal; 24 | public final boolean notified; 25 | 26 | public static final DimensionStatus[] values = new DimensionStatus[]{ 27 | DefaultUnNotify, 28 | Default, 29 | XmlWrapUnNotify, 30 | XmlWrap, 31 | XmlExactUnNotify, 32 | XmlExact, 33 | XmlLayoutUnNotify, 34 | XmlLayout, 35 | CodeExactUnNotify, 36 | CodeExact, 37 | DeadLockUnNotify, 38 | DeadLock 39 | }; 40 | 41 | private DimensionStatus(int ordinal,boolean notified) { 42 | this.ordinal = ordinal; 43 | this.notified = notified; 44 | } 45 | 46 | /** 47 | * 转换为未通知状态 48 | * @return 未通知状态 49 | */ 50 | public DimensionStatus unNotify() { 51 | if (notified) { 52 | DimensionStatus prev = values[ordinal - 1]; 53 | if (!prev.notified) { 54 | return prev; 55 | } 56 | return DefaultUnNotify; 57 | } 58 | return this; 59 | } 60 | 61 | /** 62 | * 转换为通知状态 63 | * @return 通知状态 64 | */ 65 | public DimensionStatus notified() { 66 | if (!notified) { 67 | return values[ordinal + 1]; 68 | } 69 | return this; 70 | } 71 | 72 | /** 73 | * 是否可以被新的状态替换 74 | * @param status 新转台 75 | * @return 小于等于 76 | */ 77 | public boolean canReplaceWith(DimensionStatus status) { 78 | return ordinal < status.ordinal || ((!notified || CodeExact == this) && ordinal == status.ordinal); 79 | } 80 | 81 | // /** 82 | // * 是否没有达到新的状态 83 | // * @param status 新转台 84 | // * @return 大于等于 gte 85 | // */ 86 | // public boolean gteStatusWith(DimensionStatus status) { 87 | // return ordinal() >= status.ordinal(); 88 | // } 89 | } -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/constant/RefreshState.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.constant; 2 | 3 | /** 4 | * 刷新状态 5 | */ 6 | @SuppressWarnings("unused") 7 | public enum RefreshState { 8 | None(0,false,false,false,false,false), 9 | PullDownToRefresh(1,true,false,false,false,false), PullUpToLoad(2,true,false,false,false,false), 10 | PullDownCanceled(1,false,false,false,false,false), PullUpCanceled(2,false,false,false,false,false), 11 | ReleaseToRefresh(1,true,false,false,false,true), ReleaseToLoad(2,true,false,false,false,true), 12 | ReleaseToTwoLevel(1,true,false,false,true,true), TwoLevelReleased(1,false,false,false,true,false), 13 | RefreshReleased(1,false,false,false,false,false), LoadReleased(2,false,false,false,false,false), 14 | Refreshing(1,false,true,false,false,false), Loading(2,false,true,false,false,false), TwoLevel(1, false, true,false,true,false), 15 | RefreshFinish(1,false,false,true,false,false), LoadFinish(2,false,false,true,false,false), TwoLevelFinish(1,false,false,true,true,false); 16 | 17 | public final boolean isHeader; 18 | public final boolean isFooter; 19 | public final boolean isTwoLevel;// 二级刷新 ReleaseToTwoLevel TwoLevelReleased TwoLevel 20 | public final boolean isDragging;// 正在拖动状态:PullDownToRefresh PullUpToLoad ReleaseToRefresh ReleaseToLoad ReleaseToTwoLevel 21 | public final boolean isOpening;// 正在刷新状态:Refreshing Loading TwoLevel 22 | public final boolean isFinishing;//正在完成状态:RefreshFinish LoadFinish TwoLevelFinish 23 | public final boolean isReleaseToOpening;// 释放立马打开 ReleaseToRefresh ReleaseToLoad ReleaseToTwoLevel 24 | 25 | RefreshState(int role, boolean dragging, boolean opening, boolean finishing, boolean twoLevel, boolean releaseToOpening) { 26 | this.isHeader = role == 1; 27 | this.isFooter = role == 2; 28 | this.isDragging = dragging; 29 | this.isOpening = opening; 30 | this.isFinishing = finishing; 31 | this.isTwoLevel = twoLevel; 32 | this.isReleaseToOpening = releaseToOpening; 33 | } 34 | 35 | public RefreshState toFooter() { 36 | if (isHeader && !isTwoLevel) { 37 | return values()[ordinal() + 1]; 38 | } 39 | return this; 40 | } 41 | 42 | public RefreshState toHeader() { 43 | if (isFooter && !isTwoLevel) { 44 | return values()[ordinal()-1]; 45 | } 46 | return this; 47 | } 48 | } -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/constant/SpinnerStyle.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.constant; 2 | 3 | /** 4 | * 顶部和底部的组件在拖动时候的变换方式 5 | * Created by scwang on 2017/5/26. 6 | */ 7 | @SuppressWarnings("DeprecatedIsStillUsed") 8 | public class SpinnerStyle { 9 | 10 | public static final SpinnerStyle Translate = new SpinnerStyle(0, true, false); 11 | /** 12 | * Scale 下拉过程中会动态 【测量】(header)和 【布局】(layout)降低app 性能, 13 | * 官方自带的 Header 都已经从【Scale】转向【FixedBehind】来提高性能 14 | * 自定义可以参考官方的 【飞机】【贝塞尔】【快递】等 Header 15 | * @deprecated use {@link SpinnerStyle#FixedBehind} 16 | */ 17 | @Deprecated 18 | public static final SpinnerStyle Scale = new SpinnerStyle(1, true, true); 19 | public static final SpinnerStyle FixedBehind = new SpinnerStyle(2, false, false); 20 | public static final SpinnerStyle FixedFront = new SpinnerStyle(3, true, false); 21 | public static final SpinnerStyle MatchLayout = new SpinnerStyle(4, true, false); 22 | 23 | public static final SpinnerStyle[] values = new SpinnerStyle[] { 24 | Translate, //平行移动 特点: HeaderView高度不会改变, 25 | Scale, //拉伸形变 特点:在下拉和上弹(HeaderView高度改变)时候,会自动触发OnDraw事件 26 | FixedBehind, //固定在背后 特点:HeaderView高度不会改变, 27 | FixedFront, //固定在前面 特点:HeaderView高度不会改变, 28 | MatchLayout//填满布局 特点:HeaderView高度不会改变,尺寸充满 RefreshLayout 29 | }; 30 | 31 | public final int ordinal; 32 | public final boolean front; 33 | public final boolean scale; 34 | 35 | protected SpinnerStyle(int ordinal, boolean front, boolean scale) { 36 | this.ordinal = ordinal; 37 | this.front = front; 38 | this.scale = scale; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/listener/CoordinatorLayoutListener.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.listener; 2 | 3 | public interface CoordinatorLayoutListener { 4 | void onCoordinatorUpdate(boolean enableRefresh, boolean enableLoadMore); 5 | } -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/listener/DefaultRefreshFooterCreator.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.listener; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import byron.refresh.control.smart.refresh.layout.api.RefreshFooter; 8 | import byron.refresh.control.smart.refresh.layout.api.RefreshLayout; 9 | 10 | /** 11 | * 默认Footer创建器 12 | * Created by scwang on 2018/1/26. 13 | */ 14 | public interface DefaultRefreshFooterCreator { 15 | @NonNull 16 | RefreshFooter createRefreshFooter(@NonNull Context context, @NonNull RefreshLayout layout); 17 | } 18 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/listener/DefaultRefreshHeaderCreator.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.listener; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import byron.refresh.control.smart.refresh.layout.api.RefreshHeader; 8 | import byron.refresh.control.smart.refresh.layout.api.RefreshLayout; 9 | 10 | /** 11 | * 默认Header创建器 12 | * Created by scwang on 2018/1/26. 13 | */ 14 | public interface DefaultRefreshHeaderCreator { 15 | @NonNull 16 | RefreshHeader createRefreshHeader(@NonNull Context context, @NonNull RefreshLayout layout); 17 | } 18 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/listener/DefaultRefreshInitializer.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.listener; 2 | 3 | import android.content.Context; 4 | 5 | import androidx.annotation.NonNull; 6 | 7 | import byron.refresh.control.smart.refresh.layout.api.RefreshLayout; 8 | 9 | /** 10 | * 默认全局初始化器 11 | * Created by scwang on 2018/5/29 0029. 12 | */ 13 | public interface DefaultRefreshInitializer { 14 | void initialize(@NonNull Context context, @NonNull RefreshLayout layout); 15 | } 16 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/listener/OnLoadMoreListener.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.listener; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | import byron.refresh.control.smart.refresh.layout.api.RefreshLayout; 7 | 8 | /** 9 | * 加载更多监听器 10 | * Created by scwang on 2017/5/26. 11 | */ 12 | public interface OnLoadMoreListener { 13 | void onLoadMore(@NonNull RefreshLayout refreshLayout); 14 | } 15 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/listener/OnMultiListener.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.listener; 2 | 3 | import byron.refresh.control.smart.refresh.layout.api.RefreshFooter; 4 | import byron.refresh.control.smart.refresh.layout.api.RefreshHeader; 5 | 6 | /** 7 | * 多功能监听器 8 | * Created by scwang on 2017/5/26. 9 | */ 10 | public interface OnMultiListener extends OnRefreshLoadMoreListener, OnStateChangedListener { 11 | /** 12 | * 手指拖动下拉(会连续多次调用,添加isDragging并取代之前的onPulling、onReleasing) 13 | * @param header 头部 14 | * @param isDragging true 手指正在拖动 false 回弹动画 15 | * @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+maxDragHeight) / footerHeight ) 16 | * @param offset 下拉的像素偏移量 0 - offset - (footerHeight+maxDragHeight) 17 | * @param headerHeight 高度 HeaderHeight or FooterHeight 18 | * @param maxDragHeight 最大拖动高度 19 | */ 20 | void onHeaderMoving(RefreshHeader header, boolean isDragging, float percent, int offset, int headerHeight, int maxDragHeight); 21 | 22 | void onHeaderReleased(RefreshHeader header, int headerHeight, int maxDragHeight); 23 | void onHeaderStartAnimator(RefreshHeader header, int headerHeight, int maxDragHeight); 24 | void onHeaderFinish(RefreshHeader header, boolean success); 25 | 26 | /** 27 | * 手指拖动上拉(会连续多次调用,添加isDragging并取代之前的onPulling、onReleasing) 28 | * @param footer 尾部 29 | * @param isDragging true 手指正在拖动 false 回弹动画 30 | * @param percent 下拉的百分比 值 = offset/footerHeight (0 - percent - (footerHeight+maxDragHeight) / footerHeight ) 31 | * @param offset 下拉的像素偏移量 0 - offset - (footerHeight+maxDragHeight) 32 | * @param footerHeight 高度 HeaderHeight or FooterHeight 33 | * @param maxDragHeight 最大拖动高度 34 | */ 35 | void onFooterMoving(RefreshFooter footer, boolean isDragging, float percent, int offset, int footerHeight, int maxDragHeight); 36 | 37 | void onFooterReleased(RefreshFooter footer, int footerHeight, int maxDragHeight); 38 | void onFooterStartAnimator(RefreshFooter footer, int footerHeight, int maxDragHeight); 39 | void onFooterFinish(RefreshFooter footer, boolean success); 40 | } 41 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/listener/OnRefreshListener.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.listener; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | import byron.refresh.control.smart.refresh.layout.api.RefreshLayout; 7 | 8 | /** 9 | * 刷新监听器 10 | * Created by scwang on 2017/5/26. 11 | */ 12 | public interface OnRefreshListener { 13 | void onRefresh(@NonNull RefreshLayout refreshLayout); 14 | } 15 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/listener/OnRefreshLoadMoreListener.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.listener; 2 | 3 | /** 4 | * 刷新加载组合监听器 5 | * Created by scwang on 2017/5/26. 6 | */ 7 | public interface OnRefreshLoadMoreListener extends OnRefreshListener, OnLoadMoreListener { 8 | } 9 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/listener/OnStateChangedListener.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.listener; 2 | 3 | 4 | 5 | import static androidx.annotation.RestrictTo.Scope.LIBRARY; 6 | import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP; 7 | import static androidx.annotation.RestrictTo.Scope.SUBCLASSES; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.RestrictTo; 11 | 12 | import byron.refresh.control.smart.refresh.layout.api.RefreshLayout; 13 | import byron.refresh.control.smart.refresh.layout.constant.RefreshState; 14 | 15 | 16 | /** 17 | * 刷新状态改变监听器 18 | * Created by scwang on 2017/5/26. 19 | */ 20 | public interface OnStateChangedListener { 21 | /** 22 | * 【仅限框架内调用】状态改变事件 {@link RefreshState} 23 | * @param refreshLayout RefreshLayout 24 | * @param oldState 改变之前的状态 25 | * @param newState 改变之后的状态 26 | */ 27 | @RestrictTo({LIBRARY,LIBRARY_GROUP,SUBCLASSES}) 28 | void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState); 29 | } 30 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/listener/ScrollBoundaryDecider.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.listener; 2 | 3 | import android.view.View; 4 | 5 | /** 6 | * 滚动边界 7 | * Created by scwang on 2017/7/8. 8 | */ 9 | public interface ScrollBoundaryDecider { 10 | /** 11 | * 根据内容视图状态判断是否可以开始下拉刷新 12 | * @param content 内容视图 13 | * @return true 将会触发下拉刷新 14 | */ 15 | boolean canRefresh(View content); 16 | /** 17 | * 根据内容视图状态判断是否可以开始上拉加载 18 | * @param content 内容视图 19 | * @return true 将会触发加载更多 20 | */ 21 | boolean canLoadMore(View content); 22 | } 23 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/simple/SimpleBoundaryDecider.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.simple; 2 | 3 | import android.graphics.PointF; 4 | import android.view.View; 5 | 6 | import byron.refresh.control.smart.refresh.layout.listener.ScrollBoundaryDecider; 7 | import byron.refresh.control.smart.refresh.layout.util.SmartUtil; 8 | 9 | /** 10 | * 滚动边界 11 | * Created by scwang on 2017/7/8. 12 | */ 13 | public class SimpleBoundaryDecider implements ScrollBoundaryDecider { 14 | 15 | // 16 | public PointF mActionEvent; 17 | public ScrollBoundaryDecider boundary; 18 | public boolean mEnableLoadMoreWhenContentNotFull = true; 19 | // 20 | 21 | // 22 | @Override 23 | public boolean canRefresh(View content) { 24 | if (boundary != null) { 25 | return boundary.canRefresh(content); 26 | } 27 | //mActionEvent == null 时 canRefresh 不会动态递归搜索 28 | return SmartUtil.canRefresh(content, mActionEvent); 29 | } 30 | 31 | @Override 32 | public boolean canLoadMore(View content) { 33 | if (boundary != null) { 34 | return boundary.canLoadMore(content); 35 | } 36 | //mActionEvent == null 时 canLoadMore 不会动态递归搜索 37 | return SmartUtil.canLoadMore(content, mActionEvent, mEnableLoadMoreWhenContentNotFull); 38 | } 39 | // 40 | } 41 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/simple/SimpleMultiListener.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.simple; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import byron.refresh.control.smart.refresh.layout.api.RefreshFooter; 6 | import byron.refresh.control.smart.refresh.layout.api.RefreshHeader; 7 | import byron.refresh.control.smart.refresh.layout.api.RefreshLayout; 8 | import byron.refresh.control.smart.refresh.layout.constant.RefreshState; 9 | import byron.refresh.control.smart.refresh.layout.listener.OnMultiListener; 10 | 11 | /** 12 | * 多功能监听器 13 | * Created by scwang on 2017/5/26. 14 | */ 15 | public class SimpleMultiListener implements OnMultiListener { 16 | 17 | @Override 18 | public void onHeaderMoving(RefreshHeader header, boolean isDragging, float percent, int offset, int headerHeight, int maxDragHeight) { 19 | 20 | } 21 | 22 | @Override 23 | public void onHeaderReleased(RefreshHeader header, int headerHeight, int maxDragHeight) { 24 | 25 | } 26 | 27 | @Override 28 | public void onHeaderStartAnimator(RefreshHeader header, int footerHeight, int maxDragHeight) { 29 | 30 | } 31 | 32 | @Override 33 | public void onHeaderFinish(RefreshHeader header, boolean success) { 34 | 35 | } 36 | 37 | @Override 38 | public void onFooterMoving(RefreshFooter footer, boolean isDragging, float percent, int offset, int footerHeight, int maxDragHeight) { 39 | 40 | } 41 | 42 | @Override 43 | public void onFooterReleased(RefreshFooter footer, int footerHeight, int maxDragHeight) { 44 | 45 | } 46 | 47 | @Override 48 | public void onFooterStartAnimator(RefreshFooter footer, int headerHeight, int maxDragHeight) { 49 | 50 | } 51 | 52 | @Override 53 | public void onFooterFinish(RefreshFooter footer, boolean success) { 54 | 55 | } 56 | 57 | @Override 58 | public void onRefresh(@NonNull RefreshLayout refreshLayout) { 59 | 60 | } 61 | 62 | @Override 63 | public void onLoadMore(@NonNull RefreshLayout refreshLayout) { 64 | 65 | } 66 | 67 | @Override 68 | public void onStateChanged(@NonNull RefreshLayout refreshLayout, @NonNull RefreshState oldState, @NonNull RefreshState newState) { 69 | 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/util/DesignUtil.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.util; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | 6 | import androidx.coordinatorlayout.widget.CoordinatorLayout; 7 | 8 | import com.google.android.material.appbar.AppBarLayout; 9 | 10 | import byron.refresh.control.smart.refresh.layout.api.RefreshKernel; 11 | import byron.refresh.control.smart.refresh.layout.listener.CoordinatorLayoutListener; 12 | 13 | /** 14 | * Design 兼容包缺省尝试 15 | * Created by scwang on 2018/1/29. 16 | */ 17 | public class DesignUtil { 18 | 19 | public static void checkCoordinatorLayout(View content, RefreshKernel kernel, final CoordinatorLayoutListener listener) { 20 | try {//try 不能删除,不然会出现兼容性问题 21 | if (content instanceof CoordinatorLayout) { 22 | kernel.getRefreshLayout().setEnableNestedScroll(false); 23 | ViewGroup layout = (ViewGroup) content; 24 | for (int i = layout.getChildCount() - 1; i >= 0; i--) { 25 | View view = layout.getChildAt(i); 26 | if (view instanceof AppBarLayout) { 27 | ((AppBarLayout) view).addOnOffsetChangedListener(new AppBarLayout.OnOffsetChangedListener() { 28 | @Override 29 | public void onOffsetChanged(AppBarLayout appBarLayout, int verticalOffset) { 30 | listener.onCoordinatorUpdate( 31 | verticalOffset >= 0, 32 | (appBarLayout.getTotalScrollRange() + verticalOffset) <= 0); 33 | } 34 | }); 35 | } 36 | } 37 | } 38 | } catch (Throwable e) { 39 | e.printStackTrace(); 40 | } 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/wrapper/RefreshFooterWrapper.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.wrapper; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.view.View; 5 | 6 | import byron.refresh.control.smart.refresh.layout.api.RefreshFooter; 7 | import byron.refresh.control.smart.refresh.layout.simple.SimpleComponent; 8 | 9 | /** 10 | * 刷新底部包装 11 | * Created by scwang on 2017/5/26. 12 | */ 13 | @SuppressLint("ViewConstructor") 14 | public class RefreshFooterWrapper extends SimpleComponent implements RefreshFooter { 15 | 16 | public RefreshFooterWrapper(View wrapper) { 17 | super(wrapper); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /android/src/main/java/byron/refresh/control/smart/refresh/layout/wrapper/RefreshHeaderWrapper.java: -------------------------------------------------------------------------------- 1 | package byron.refresh.control.smart.refresh.layout.wrapper; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.view.View; 5 | 6 | import byron.refresh.control.smart.refresh.layout.api.RefreshHeader; 7 | import byron.refresh.control.smart.refresh.layout.simple.SimpleComponent; 8 | 9 | /** 10 | * 刷新头部包装 11 | * Created by scwang on 2017/5/26. 12 | */ 13 | @SuppressLint("ViewConstructor") 14 | public class RefreshHeaderWrapper extends SimpleComponent implements RefreshHeader { 15 | 16 | public RefreshHeaderWrapper(View wrapper) { 17 | super(wrapper); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /android/src/main/res/layout/srl_classics_footer.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 22 | 23 | 35 | 36 | 45 | 46 | -------------------------------------------------------------------------------- /android/src/main/res/layout/srl_classics_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 22 | 23 | 35 | 36 | 43 | 51 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /android/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SmartRefreshLayout中没有找到内容视图。您是否忘记在xml布局文件中添加? 3 | %s 虚假区域\n代表运行时拖动的高度【%.1fdp】 \n而不会显示任何东西 4 | 下拉可以刷新 5 | 正在刷新… 6 | 等待底部加载完成… 7 | 释放立即刷新 8 | 刷新完成 9 | 刷新失败 10 | 上次更新 M-d HH:mm 11 | 释放进入二楼 12 | 13 | 上拉加载更多 14 | 释放立即加载 15 | 正在加载… 16 | 等待头部刷新完成… 17 | 加载完成 18 | 加载失败 19 | 没有更多数据了 20 | 21 | -------------------------------------------------------------------------------- /android/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | The content view in SmartRefreshLayout is empty. Do you forget to add it in xml layout file? 3 | %s falsify area,\n Represents the height[%.1fdp] of drag at run time,\n It does not show anything. 4 | 下拉可以刷新 5 | 正在刷新… 6 | 等待底部加载完成… 7 | 释放立即刷新 8 | 刷新完成 9 | 刷新失败 10 | 上次更新 M-d HH:mm 11 | 释放进入二楼 12 | 13 | Pull Up To Load More 14 | Release To Load More 15 | Loading… 16 | Wait For Refreshing… 17 | Load Success 18 | Load Failed 19 | No More Data 20 | 21 | -------------------------------------------------------------------------------- /assets/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/assets/arrow.png -------------------------------------------------------------------------------- /example/.buckconfig: -------------------------------------------------------------------------------- 1 | 2 | [android] 3 | target = Google Inc.:Google APIs:23 4 | 5 | [maven_repositories] 6 | central = https://repo1.maven.org/maven2 7 | -------------------------------------------------------------------------------- /example/.editorconfig: -------------------------------------------------------------------------------- 1 | # Windows files 2 | [*.bat] 3 | end_of_line = crlf 4 | -------------------------------------------------------------------------------- /example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: '@react-native-community', 4 | }; 5 | -------------------------------------------------------------------------------- /example/.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | ; We fork some components by platform 3 | .*/*[.]android.js 4 | 5 | ; Ignore "BUCK" generated dirs 6 | /\.buckd/ 7 | 8 | ; Ignore polyfills 9 | node_modules/react-native/Libraries/polyfills/.* 10 | 11 | ; Flow doesn't support platforms 12 | .*/Libraries/Utilities/LoadingView.js 13 | 14 | [untyped] 15 | .*/node_modules/@react-native-community/cli/.*/.* 16 | 17 | [include] 18 | 19 | [libs] 20 | node_modules/react-native/interface.js 21 | node_modules/react-native/flow/ 22 | 23 | [options] 24 | emoji=true 25 | 26 | exact_by_default=true 27 | 28 | format.bracket_spacing=false 29 | 30 | module.file_ext=.js 31 | module.file_ext=.json 32 | module.file_ext=.ios.js 33 | 34 | munge_underscores=true 35 | 36 | module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1' 37 | module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub' 38 | 39 | suppress_type=$FlowIssue 40 | suppress_type=$FlowFixMe 41 | suppress_type=$FlowFixMeProps 42 | suppress_type=$FlowFixMeState 43 | 44 | [lints] 45 | sketchy-null-number=warn 46 | sketchy-null-mixed=warn 47 | sketchy-number=warn 48 | untyped-type-import=warn 49 | nonstrict-import=warn 50 | deprecated-type=warn 51 | unsafe-getters-setters=warn 52 | unnecessary-invariant=warn 53 | signature-verification-failure=warn 54 | 55 | [strict] 56 | deprecated-type 57 | nonstrict-import 58 | sketchy-null 59 | unclear-type 60 | unsafe-getters-setters 61 | untyped-import 62 | untyped-type-import 63 | 64 | [version] 65 | ^0.158.0 66 | -------------------------------------------------------------------------------- /example/.gitattributes: -------------------------------------------------------------------------------- 1 | # Windows files should use crlf line endings 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | *.bat text eol=crlf 4 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | 24 | # Android/IntelliJ 25 | # 26 | build/ 27 | .idea 28 | .gradle 29 | local.properties 30 | *.iml 31 | *.hprof 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | !debug.keystore 44 | 45 | # fastlane 46 | # 47 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 48 | # screenshots whenever they are needed. 49 | # For more information about the recommended setup visit: 50 | # https://docs.fastlane.tools/best-practices/source-control/ 51 | 52 | */fastlane/report.xml 53 | */fastlane/Preview.html 54 | */fastlane/screenshots 55 | 56 | # Bundle artifact 57 | *.jsbundle 58 | 59 | # CocoaPods 60 | /ios/Pods/ 61 | -------------------------------------------------------------------------------- /example/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | bracketSpacing: false, 3 | jsxBracketSameLine: true, 4 | singleQuote: true, 5 | trailingComma: 'all', 6 | arrowParens: 'avoid', 7 | }; 8 | -------------------------------------------------------------------------------- /example/.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /example/App.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Sample React Native App 3 | * 4 | * adapted from App.js generated by the following command: 5 | * 6 | * react-native init example 7 | * 8 | * https://github.com/facebook/react-native 9 | */ 10 | 11 | import React, {useState} from 'react'; 12 | import {SafeAreaView} from 'react-native'; 13 | import {StyleSheet, Text, View} from 'react-native'; 14 | import RefreshFlatList from './RefreshFlatList'; 15 | 16 | async function waitForDisplayed(ms: number) { 17 | return new Promise(resolve => { 18 | setTimeout(() => resolve(void 0), ms); 19 | }); 20 | } 21 | 22 | const App = () => { 23 | const [list, setList] = useState(randomColors()); 24 | 25 | const onRefresh = async () => { 26 | await waitForDisplayed(2000); 27 | setList(randomColors()); 28 | }; 29 | 30 | const onEndReached = async () => { 31 | if (list.length > 50) { 32 | return; 33 | } 34 | await waitForDisplayed(2000); 35 | setList(_list => { 36 | return _list.concat(randomColors()); 37 | }); 38 | }; 39 | 40 | return ( 41 | 42 | 49 | 50 | ); 51 | }; 52 | 53 | const renderItem = ({item, index}: {item: string; index: number}) => { 54 | return ( 55 | 56 | {index} 57 | _{item} 58 | 59 | ); 60 | }; 61 | 62 | export default App; 63 | 64 | const randomColors = (size = 10) => { 65 | const colors = []; 66 | for (let i = 0; i < size; i++) { 67 | const r = Math.floor(Math.random() * 256); 68 | const g = Math.floor(Math.random() * 256); 69 | const b = Math.floor(Math.random() * 256); 70 | colors.push('#' + r.toString(16) + g.toString(16) + b.toString(16)); 71 | } 72 | return colors; 73 | }; 74 | 75 | const styles = StyleSheet.create({ 76 | container: { 77 | flex: 1, 78 | backgroundColor: '#fff', 79 | }, 80 | list: { 81 | backgroundColor: 'rgba(0, 0, 0, 0.5)', 82 | }, 83 | item: { 84 | marginHorizontal: 15, 85 | marginVertical: 5, 86 | alignItems: 'center', 87 | justifyContent: 'center', 88 | height: 80, 89 | borderRadius: 12, 90 | }, 91 | text: { 92 | fontSize: 24, 93 | color: '#fff', 94 | }, 95 | index: { 96 | fontSize: 24, 97 | color: '#000', 98 | }, 99 | control: { 100 | height: 100, 101 | marginTop: -100, 102 | alignItems: 'center', 103 | justifyContent: 'center', 104 | }, 105 | control_text: { 106 | fontSize: 18, 107 | color: 'red', 108 | }, 109 | }); 110 | -------------------------------------------------------------------------------- /example/__tests__/App-test.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import 'react-native'; 6 | import React from 'react'; 7 | import App from '../App'; 8 | 9 | // Note: test renderer must be required after react-native. 10 | import renderer from 'react-test-renderer'; 11 | 12 | it('renders correctly', () => { 13 | renderer.create(); 14 | }); 15 | -------------------------------------------------------------------------------- /example/android/app/_BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets") 12 | 13 | lib_deps = [] 14 | 15 | create_aar_targets(glob(["libs/*.aar"])) 16 | 17 | create_jar_targets(glob(["libs/*.jar"])) 18 | 19 | android_library( 20 | name = "all-libs", 21 | exported_deps = lib_deps, 22 | ) 23 | 24 | android_library( 25 | name = "app-code", 26 | srcs = glob([ 27 | "src/main/java/**/*.java", 28 | ]), 29 | deps = [ 30 | ":all-libs", 31 | ":build_config", 32 | ":res", 33 | ], 34 | ) 35 | 36 | android_build_config( 37 | name = "build_config", 38 | package = "com.example", 39 | ) 40 | 41 | android_resource( 42 | name = "res", 43 | package = "com.example", 44 | res = "src/main/res", 45 | ) 46 | 47 | android_binary( 48 | name = "app", 49 | keystore = "//android/keystores:debug", 50 | manifest = "src/main/AndroidManifest.xml", 51 | package_type = "debug", 52 | deps = [ 53 | ":app-code", 54 | ], 55 | ) 56 | -------------------------------------------------------------------------------- /example/android/app/build_defs.bzl: -------------------------------------------------------------------------------- 1 | """Helper definitions to glob .aar and .jar targets""" 2 | 3 | def create_aar_targets(aarfiles): 4 | for aarfile in aarfiles: 5 | name = "aars__" + aarfile[aarfile.rindex("/") + 1:aarfile.rindex(".aar")] 6 | lib_deps.append(":" + name) 7 | android_prebuilt_aar( 8 | name = name, 9 | aar = aarfile, 10 | ) 11 | 12 | def create_jar_targets(jarfiles): 13 | for jarfile in jarfiles: 14 | name = "jars__" + jarfile[jarfile.rindex("/") + 1:jarfile.rindex(".jar")] 15 | lib_deps.append(":" + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | -------------------------------------------------------------------------------- /example/android/app/debug.keystore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/example/android/app/debug.keystore -------------------------------------------------------------------------------- /example/android/app/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 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /example/android/app/src/debug/java/com/example/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 5 | * directory of this source tree. 6 | */ 7 | package com.example; 8 | 9 | import android.content.Context; 10 | import com.facebook.flipper.android.AndroidFlipperClient; 11 | import com.facebook.flipper.android.utils.FlipperUtils; 12 | import com.facebook.flipper.core.FlipperClient; 13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin; 14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin; 15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin; 16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping; 17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin; 18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor; 19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin; 20 | import com.facebook.flipper.plugins.react.ReactFlipperPlugin; 21 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin; 22 | import com.facebook.react.ReactInstanceManager; 23 | import com.facebook.react.bridge.ReactContext; 24 | import com.facebook.react.modules.network.NetworkingModule; 25 | import okhttp3.OkHttpClient; 26 | 27 | public class ReactNativeFlipper { 28 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 29 | if (FlipperUtils.shouldEnableFlipper(context)) { 30 | final FlipperClient client = AndroidFlipperClient.getInstance(context); 31 | 32 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults())); 33 | client.addPlugin(new ReactFlipperPlugin()); 34 | client.addPlugin(new DatabasesFlipperPlugin(context)); 35 | client.addPlugin(new SharedPreferencesFlipperPlugin(context)); 36 | client.addPlugin(CrashReporterPlugin.getInstance()); 37 | 38 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin(); 39 | NetworkingModule.setCustomClientBuilder( 40 | new NetworkingModule.CustomClientBuilder() { 41 | @Override 42 | public void apply(OkHttpClient.Builder builder) { 43 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin)); 44 | } 45 | }); 46 | client.addPlugin(networkFlipperPlugin); 47 | client.start(); 48 | 49 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized 50 | // Hence we run if after all native modules have been initialized 51 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext(); 52 | if (reactContext == null) { 53 | reactInstanceManager.addReactInstanceEventListener( 54 | new ReactInstanceManager.ReactInstanceEventListener() { 55 | @Override 56 | public void onReactContextInitialized(ReactContext reactContext) { 57 | reactInstanceManager.removeReactInstanceEventListener(this); 58 | reactContext.runOnNativeModulesQueueThread( 59 | new Runnable() { 60 | @Override 61 | public void run() { 62 | client.addPlugin(new FrescoFlipperPlugin()); 63 | } 64 | }); 65 | } 66 | }); 67 | } else { 68 | client.addPlugin(new FrescoFlipperPlugin()); 69 | } 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 16 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import com.facebook.react.ReactActivity; 4 | 5 | public class MainActivity extends ReactActivity { 6 | 7 | /** 8 | * Returns the name of the main component registered from JavaScript. This is used to schedule 9 | * rendering of the component. 10 | */ 11 | @Override 12 | protected String getMainComponentName() { 13 | return "example"; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/example/MainApplication.java: -------------------------------------------------------------------------------- 1 | package com.example; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | import com.facebook.react.PackageList; 6 | import com.facebook.react.ReactApplication; 7 | import com.facebook.react.ReactInstanceManager; 8 | import com.facebook.react.ReactNativeHost; 9 | import com.facebook.react.ReactPackage; 10 | import com.facebook.soloader.SoLoader; 11 | import java.lang.reflect.InvocationTargetException; 12 | import java.util.List; 13 | 14 | public class MainApplication extends Application implements ReactApplication { 15 | 16 | private final ReactNativeHost mReactNativeHost = 17 | new ReactNativeHost(this) { 18 | @Override 19 | public boolean getUseDeveloperSupport() { 20 | return BuildConfig.DEBUG; 21 | } 22 | 23 | @Override 24 | protected List getPackages() { 25 | @SuppressWarnings("UnnecessaryLocalVariable") 26 | List packages = new PackageList(this).getPackages(); 27 | // Packages that cannot be autolinked yet can be added manually here, for example: 28 | // packages.add(new MyReactNativePackage()); 29 | return packages; 30 | } 31 | 32 | @Override 33 | protected String getJSMainModuleName() { 34 | return "index"; 35 | } 36 | }; 37 | 38 | @Override 39 | public ReactNativeHost getReactNativeHost() { 40 | return mReactNativeHost; 41 | } 42 | 43 | @Override 44 | public void onCreate() { 45 | super.onCreate(); 46 | SoLoader.init(this, /* native exopackage */ false); 47 | initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); 48 | } 49 | 50 | /** 51 | * Loads Flipper in React Native templates. Call this in the onCreate method with something like 52 | * initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); 53 | * 54 | * @param context 55 | * @param reactInstanceManager 56 | */ 57 | private static void initializeFlipper( 58 | Context context, ReactInstanceManager reactInstanceManager) { 59 | if (BuildConfig.DEBUG) { 60 | try { 61 | /* 62 | We use reflection here to pick up the class that initializes Flipper, 63 | since Flipper library is not available in release mode 64 | */ 65 | Class aClass = Class.forName("com.example.ReactNativeFlipper"); 66 | aClass 67 | .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class) 68 | .invoke(null, context, reactInstanceManager); 69 | } catch (ClassNotFoundException e) { 70 | e.printStackTrace(); 71 | } catch (NoSuchMethodException e) { 72 | e.printStackTrace(); 73 | } catch (IllegalAccessException e) { 74 | e.printStackTrace(); 75 | } catch (InvocationTargetException e) { 76 | e.printStackTrace(); 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/example/android/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/example/android/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | example 3 | 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext { 5 | buildToolsVersion = "30.0.2" 6 | minSdkVersion = 21 7 | compileSdkVersion = 30 8 | targetSdkVersion = 30 9 | ndkVersion = "21.4.7075529" 10 | } 11 | repositories { 12 | google() 13 | mavenCentral() 14 | } 15 | dependencies { 16 | classpath("com.android.tools.build:gradle:4.2.2") 17 | // NOTE: Do not place your application dependencies here; they belong 18 | // in the individual module build.gradle files 19 | } 20 | } 21 | 22 | allprojects { 23 | repositories { 24 | mavenCentral() 25 | mavenLocal() 26 | maven { 27 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm 28 | url("$rootDir/../node_modules/react-native/android") 29 | } 30 | maven { 31 | // Android JSC is installed from npm 32 | url("$rootDir/../node_modules/jsc-android/dist") 33 | } 34 | 35 | google() 36 | maven { url 'https://www.jitpack.io' } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | # AndroidX package structure to make it clearer which packages are bundled with the 21 | # Android operating system, and which are packaged with your app's APK 22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 23 | android.useAndroidX=true 24 | # Automatically convert third-party libraries to use AndroidX 25 | android.enableJetifier=true 26 | 27 | # Version of flipper SDK to use with React Native 28 | FLIPPER_VERSION=0.99.0 29 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.9-all.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'example' 2 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 3 | include ':app' 4 | -------------------------------------------------------------------------------- /example/app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "displayName": "example" 4 | } -------------------------------------------------------------------------------- /example/assets/arrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/example/assets/arrow.png -------------------------------------------------------------------------------- /example/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: ['module:metro-react-native-babel-preset'], 3 | }; 4 | -------------------------------------------------------------------------------- /example/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @format 3 | */ 4 | 5 | import {AppRegistry} from 'react-native'; 6 | import App from './App'; 7 | import {name as appName} from './app.json'; 8 | 9 | AppRegistry.registerComponent(appName, () => App); 10 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | require_relative '../node_modules/react-native/scripts/react_native_pods' 2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules' 3 | 4 | platform :ios, '11.0' 5 | 6 | target 'example' do 7 | config = use_native_modules! 8 | 9 | use_react_native!( 10 | :path => config[:reactNativePath], 11 | # to enable hermes on iOS, change `false` to `true` and then install pods 12 | :hermes_enabled => false 13 | ) 14 | 15 | target 'exampleTests' do 16 | inherit! :complete 17 | # Pods for testing 18 | end 19 | 20 | # Enables Flipper. 21 | # 22 | # Note that if you have use_frameworks! enabled, Flipper will not work and 23 | # you should disable the next line. 24 | use_flipper!() 25 | 26 | post_install do |installer| 27 | react_native_post_install(installer) 28 | __apply_Xcode_12_5_M1_post_install_workaround(installer) 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /example/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 55 | 61 | 62 | 63 | 64 | 70 | 72 | 78 | 79 | 80 | 81 | 83 | 84 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /example/ios/example.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/example/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : UIResponder 5 | 6 | @property (nonatomic, strong) UIWindow *window; 7 | 8 | @end 9 | -------------------------------------------------------------------------------- /example/ios/example/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | 3 | #import 4 | #import 5 | #import 6 | 7 | #ifdef FB_SONARKIT_ENABLED 8 | #import 9 | #import 10 | #import 11 | #import 12 | #import 13 | #import 14 | 15 | static void InitializeFlipper(UIApplication *application) { 16 | FlipperClient *client = [FlipperClient sharedClient]; 17 | SKDescriptorMapper *layoutDescriptorMapper = [[SKDescriptorMapper alloc] initWithDefaults]; 18 | [client addPlugin:[[FlipperKitLayoutPlugin alloc] initWithRootNode:application withDescriptorMapper:layoutDescriptorMapper]]; 19 | [client addPlugin:[[FKUserDefaultsPlugin alloc] initWithSuiteName:nil]]; 20 | [client addPlugin:[FlipperKitReactPlugin new]]; 21 | [client addPlugin:[[FlipperKitNetworkPlugin alloc] initWithNetworkAdapter:[SKIOSNetworkAdapter new]]]; 22 | [client start]; 23 | } 24 | #endif 25 | 26 | @implementation AppDelegate 27 | 28 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions 29 | { 30 | #ifdef FB_SONARKIT_ENABLED 31 | InitializeFlipper(application); 32 | #endif 33 | 34 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions]; 35 | RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge 36 | moduleName:@"example" 37 | initialProperties:nil]; 38 | 39 | if (@available(iOS 13.0, *)) { 40 | rootView.backgroundColor = [UIColor systemBackgroundColor]; 41 | } else { 42 | rootView.backgroundColor = [UIColor whiteColor]; 43 | } 44 | 45 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds]; 46 | UIViewController *rootViewController = [UIViewController new]; 47 | rootViewController.view = rootView; 48 | self.window.rootViewController = rootViewController; 49 | [self.window makeKeyAndVisible]; 50 | return YES; 51 | } 52 | 53 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge 54 | { 55 | #if DEBUG 56 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil]; 57 | #else 58 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"]; 59 | #endif 60 | } 61 | 62 | @end 63 | -------------------------------------------------------------------------------- /example/ios/example/Images.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "29x29", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "29x29", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "40x40", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "40x40", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "60x60", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "60x60", 31 | "scale" : "3x" 32 | } 33 | ], 34 | "info" : { 35 | "version" : 1, 36 | "author" : "xcode" 37 | } 38 | } -------------------------------------------------------------------------------- /example/ios/example/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/example/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleDisplayName 8 | example 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleSignature 22 | ???? 23 | CFBundleVersion 24 | 1 25 | LSRequiresIPhoneOS 26 | 27 | NSAppTransportSecurity 28 | 29 | NSExceptionDomains 30 | 31 | localhost 32 | 33 | NSExceptionAllowsInsecureHTTPLoads 34 | 35 | 36 | 37 | 38 | NSLocationWhenInUseUsageDescription 39 | 40 | UILaunchStoryboardName 41 | LaunchScreen 42 | UIRequiredDeviceCapabilities 43 | 44 | armv7 45 | 46 | UISupportedInterfaceOrientations 47 | 48 | UIInterfaceOrientationPortrait 49 | UIInterfaceOrientationLandscapeLeft 50 | UIInterfaceOrientationLandscapeRight 51 | 52 | UIViewControllerBasedStatusBarAppearance 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /example/ios/example/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 24 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /example/ios/example/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char * argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/ios/exampleTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/ios/exampleTests/exampleTests.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | #import 5 | #import 6 | 7 | #define TIMEOUT_SECONDS 600 8 | #define TEXT_TO_LOOK_FOR @"Welcome to React" 9 | 10 | @interface exampleTests : XCTestCase 11 | 12 | @end 13 | 14 | @implementation exampleTests 15 | 16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test 17 | { 18 | if (test(view)) { 19 | return YES; 20 | } 21 | for (UIView *subview in [view subviews]) { 22 | if ([self findSubviewInView:subview matching:test]) { 23 | return YES; 24 | } 25 | } 26 | return NO; 27 | } 28 | 29 | - (void)testRendersWelcomeScreen 30 | { 31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController]; 32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS]; 33 | BOOL foundElement = NO; 34 | 35 | __block NSString *redboxError = nil; 36 | #ifdef DEBUG 37 | RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) { 38 | if (level >= RCTLogLevelError) { 39 | redboxError = message; 40 | } 41 | }); 42 | #endif 43 | 44 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) { 45 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 46 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]]; 47 | 48 | foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) { 49 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) { 50 | return YES; 51 | } 52 | return NO; 53 | }]; 54 | } 55 | 56 | #ifdef DEBUG 57 | RCTSetLogFunction(RCTDefaultLogFunction); 58 | #endif 59 | 60 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError); 61 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS); 62 | } 63 | 64 | 65 | @end 66 | -------------------------------------------------------------------------------- /example/metro.config.js: -------------------------------------------------------------------------------- 1 | // metro.config.js 2 | // 3 | // with multiple workarounds for this issue with symlinks: 4 | // https://github.com/facebook/metro/issues/1 5 | // 6 | // with thanks to @johnryan () 7 | // for the pointers to multiple workaround solutions here: 8 | // https://github.com/facebook/metro/issues/1#issuecomment-541642857 9 | // 10 | // see also this discussion: 11 | // https://github.com/brodybits/create-react-native-module/issues/232 12 | 13 | const path = require('path') 14 | 15 | module.exports = { 16 | // workaround for an issue with symlinks encountered starting with 17 | // metro@0.55 / React Native 0.61 18 | // (not needed with React Native 0.60 / metro@0.54) 19 | resolver: { 20 | extraNodeModules: new Proxy( 21 | {}, 22 | { get: (_, name) => path.resolve('.', 'node_modules', name) } 23 | ) 24 | }, 25 | 26 | // quick workaround for another issue with symlinks 27 | watchFolders: [path.resolve('.'), path.resolve('..')] 28 | } 29 | -------------------------------------------------------------------------------- /example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "example", 3 | "version": "0.0.1", 4 | "private": true, 5 | "scripts": { 6 | "android": "react-native run-android", 7 | "ios": "react-native run-ios", 8 | "start": "react-native start", 9 | "test": "jest", 10 | "lint": "eslint ." 11 | }, 12 | "dependencies": { 13 | "@byron-react-native/refresh-control": "link:../", 14 | "dayjs": "^1.10.7", 15 | "react": "17.0.2", 16 | "react-native": "0.66.4", 17 | "react-native-spinkit": "^1.5.1" 18 | }, 19 | "devDependencies": { 20 | "@babel/core": "^7.12.9", 21 | "@babel/runtime": "^7.12.5", 22 | "@react-native-community/eslint-config": "^2.0.0", 23 | "@types/jest": "^27.5.0", 24 | "@types/react": "^18.0.9", 25 | "@types/react-native": "^0.69.1", 26 | "@types/react-test-renderer": "^18.0.0", 27 | "babel-jest": "^26.6.3", 28 | "eslint": "7.14.0", 29 | "jest": "^26.6.3", 30 | "metro-react-native-babel-preset": "^0.66.2", 31 | "react-test-renderer": "17.0.2", 32 | "typescript": "^4.6.4" 33 | }, 34 | "jest": { 35 | "preset": "react-native" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /example/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowJs": true, 4 | "allowSyntheticDefaultImports": true, 5 | "esModuleInterop": true, 6 | "isolatedModules": true, 7 | "jsx": "react-native", 8 | "lib": ["es2017"], 9 | "moduleResolution": "node", 10 | "noEmit": true, 11 | "strict": true, 12 | "target": "esnext" 13 | }, 14 | "exclude": [ 15 | "node_modules", 16 | "babel.config.js", 17 | "metro.config.js", 18 | "jest.config.js" 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@byron-react-native/refresh-control" { 2 | import { ViewProps } from "react-native"; 3 | 4 | export class RNRefreshHeader extends React.Component {} 5 | 6 | export interface RefreshControlProps extends ViewProps { 7 | refreshing?: boolean; 8 | onRefresh?: () => Promise; 9 | onChangeState?: (state: number) => void; 10 | onChangeOffset?: (offset: number) => void; 11 | } 12 | 13 | export type RNRefreshControlProps = Omit & { 14 | height: number; 15 | }; 16 | 17 | export class RNRefreshControl extends React.Component {} 18 | 19 | export class RefreshControl extends React.Component { 20 | startRefresh: () => void; 21 | stopRefresh: () => void; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshAutoFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoFooter.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshFooter.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshAutoFooter : MJRefreshFooter 18 | /** 是否自动刷新(默认为YES) */ 19 | @property (assign, nonatomic, getter=isAutomaticallyRefresh) BOOL automaticallyRefresh; 20 | 21 | /** 当底部控件出现多少时就自动刷新(默认为1.0,也就是底部控件完全出现时,才会自动刷新) */ 22 | @property (assign, nonatomic) CGFloat appearencePercentTriggerAutoRefresh MJRefreshDeprecated("请使用triggerAutomaticallyRefreshPercent属性"); 23 | 24 | /** 当底部控件出现多少时就自动刷新(默认为1.0,也就是底部控件完全出现时,才会自动刷新) */ 25 | @property (assign, nonatomic) CGFloat triggerAutomaticallyRefreshPercent; 26 | 27 | /** 自动触发次数, 默认为 1, 仅在拖拽 ScrollView 时才生效, 28 | 29 | 如果为 -1, 则为无限触发 30 | */ 31 | @property (nonatomic) NSInteger autoTriggerTimes; 32 | @end 33 | 34 | NS_ASSUME_NONNULL_END 35 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshBackFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackFooter.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshFooter.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshBackFooter : MJRefreshFooter 18 | 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshComponent.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // MJRefreshComponent.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/3/4. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 刷新控件的基类 8 | 9 | #import 10 | #if __has_include() 11 | #import 12 | #else 13 | #import "MJRefreshConst.h" 14 | #endif 15 | 16 | NS_ASSUME_NONNULL_BEGIN 17 | 18 | /** 刷新控件的状态 */ 19 | typedef NS_ENUM(NSInteger, MJRefreshState) { 20 | /** 普通闲置状态 */ 21 | MJRefreshStateIdle = 1, 22 | /** 松开就可以进行刷新的状态 */ 23 | MJRefreshStatePulling, 24 | /** 正在刷新中的状态 */ 25 | MJRefreshStateRefreshing, 26 | /** 即将刷新的状态 */ 27 | MJRefreshStateWillRefresh, 28 | /** 所有数据加载完毕,没有更多的数据了 */ 29 | MJRefreshStateNoMoreData 30 | }; 31 | 32 | /** 进入刷新状态的回调 */ 33 | typedef void (^MJRefreshComponentRefreshingBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead"); 34 | /** 开始刷新后的回调(进入刷新状态后的回调) */ 35 | typedef void (^MJRefreshComponentBeginRefreshingCompletionBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead"); 36 | /** 结束刷新后的回调 */ 37 | typedef void (^MJRefreshComponentEndRefreshingCompletionBlock)(void) MJRefreshDeprecated("first deprecated in 3.3.0 - Use `MJRefreshComponentAction` instead"); 38 | 39 | /** 刷新用到的回调类型 */ 40 | typedef void (^MJRefreshComponentAction)(void); 41 | 42 | /** 刷新控件的基类 */ 43 | @interface MJRefreshComponent : UIView 44 | { 45 | /** 记录scrollView刚开始的inset */ 46 | UIEdgeInsets _scrollViewOriginalInset; 47 | /** 父控件 */ 48 | __weak UIScrollView *_scrollView; 49 | } 50 | 51 | #pragma mark - 刷新动画时间控制 52 | /** 快速动画时间(一般用在刷新开始的回弹动画), 默认 0.25 */ 53 | @property (nonatomic) NSTimeInterval fastAnimationDuration; 54 | /** 慢速动画时间(一般用在刷新结束后的回弹动画), 默认 0.4*/ 55 | @property (nonatomic) NSTimeInterval slowAnimationDuration; 56 | /** 关闭全部默认动画效果, 可以简单粗暴地解决 CollectionView 的回弹动画 bug */ 57 | - (instancetype)setAnimationDisabled; 58 | 59 | #pragma mark - 刷新回调 60 | /** 正在刷新的回调 */ 61 | @property (copy, nonatomic, nullable) MJRefreshComponentAction refreshingBlock; 62 | /** 设置回调对象和回调方法 */ 63 | - (void)setRefreshingTarget:(id)target refreshingAction:(SEL)action; 64 | 65 | /** 回调对象 */ 66 | @property (weak, nonatomic) id refreshingTarget; 67 | /** 回调方法 */ 68 | @property (assign, nonatomic) SEL refreshingAction; 69 | /** 触发回调(交给子类去调用) */ 70 | - (void)executeRefreshingCallback; 71 | 72 | #pragma mark - 刷新状态控制 73 | /** 进入刷新状态 */ 74 | - (void)beginRefreshing; 75 | - (void)beginRefreshingWithCompletionBlock:(void (^)(void))completionBlock; 76 | /** 开始刷新后的回调(进入刷新状态后的回调) */ 77 | @property (copy, nonatomic, nullable) MJRefreshComponentAction beginRefreshingCompletionBlock; 78 | /** 带动画的结束刷新的回调 */ 79 | @property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingAnimateCompletionBlock MJRefreshDeprecated("first deprecated in 3.3.0 - Use `endRefreshingAnimationBeginAction` instead"); 80 | @property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingAnimationBeginAction; 81 | /** 结束刷新的回调 */ 82 | @property (copy, nonatomic, nullable) MJRefreshComponentAction endRefreshingCompletionBlock; 83 | /** 结束刷新状态 */ 84 | - (void)endRefreshing; 85 | - (void)endRefreshingWithCompletionBlock:(void (^)(void))completionBlock; 86 | /** 是否正在刷新 */ 87 | @property (assign, nonatomic, readonly, getter=isRefreshing) BOOL refreshing; 88 | 89 | /** 刷新状态 一般交给子类内部实现 */ 90 | @property (assign, nonatomic) MJRefreshState state; 91 | 92 | #pragma mark - 交给子类去访问 93 | /** 记录scrollView刚开始的inset */ 94 | @property (assign, nonatomic, readonly) UIEdgeInsets scrollViewOriginalInset; 95 | /** 父控件 */ 96 | @property (weak, nonatomic, readonly) UIScrollView *scrollView; 97 | 98 | #pragma mark - 交给子类们去实现 99 | /** 初始化 */ 100 | - (void)prepare NS_REQUIRES_SUPER; 101 | /** 摆放子控件frame */ 102 | - (void)placeSubviews NS_REQUIRES_SUPER; 103 | /** 当scrollView的contentOffset发生改变的时候调用 */ 104 | - (void)scrollViewContentOffsetDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER; 105 | /** 当scrollView的contentSize发生改变的时候调用 */ 106 | - (void)scrollViewContentSizeDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER; 107 | /** 当scrollView的拖拽状态发生改变的时候调用 */ 108 | - (void)scrollViewPanStateDidChange:(nullable NSDictionary *)change NS_REQUIRES_SUPER; 109 | 110 | /** 多语言配置 language 发生变化时调用 111 | 112 | `MJRefreshConfig.defaultConfig.language` 发生改变时调用. 113 | 114 | ⚠️ 父类会调用 `placeSubviews` 方法, 请勿在 placeSubviews 中调用本方法, 造成死循环. 子类在需要重新布局时, 在配置完修改后, 最后再调用 super 方法, 否则可能导致配置修改后, 定位先于修改执行. 115 | */ 116 | - (void)i18nDidChange NS_REQUIRES_SUPER; 117 | 118 | #pragma mark - 其他 119 | /** 拉拽的百分比(交给子类重写) */ 120 | @property (assign, nonatomic) CGFloat pullingPercent; 121 | /** 根据拖拽比例自动切换透明度 */ 122 | @property (assign, nonatomic, getter=isAutoChangeAlpha) BOOL autoChangeAlpha MJRefreshDeprecated("请使用automaticallyChangeAlpha属性"); 123 | /** 根据拖拽比例自动切换透明度 */ 124 | @property (assign, nonatomic, getter=isAutomaticallyChangeAlpha) BOOL automaticallyChangeAlpha; 125 | @end 126 | 127 | @interface UILabel(MJRefresh) 128 | + (instancetype)mj_label; 129 | - (CGFloat)mj_textWidth; 130 | @end 131 | 132 | @interface MJRefreshComponent (ChainingGrammar) 133 | 134 | #pragma mark - <<< 为 Swift 扩展链式语法 >>> - 135 | /// 自动变化透明度 136 | - (instancetype)autoChangeTransparency:(BOOL)isAutoChange; 137 | /// 刷新开始后立即调用的回调 138 | - (instancetype)afterBeginningAction:(MJRefreshComponentAction)action; 139 | /// 刷新动画开始后立即调用的回调 140 | - (instancetype)endingAnimationBeginningAction:(MJRefreshComponentAction)action; 141 | /// 刷新结束后立即调用的回调 142 | - (instancetype)afterEndingAction:(MJRefreshComponentAction)action; 143 | 144 | 145 | /// 需要子类必须实现 146 | /// @param scrollView 赋值给的 ScrollView 的 Header/Footer/Trailer 147 | - (instancetype)linkTo:(UIScrollView *)scrollView; 148 | 149 | @end 150 | 151 | NS_ASSUME_NONNULL_END 152 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshFooter.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // MJRefreshFooter.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/3/5. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 上拉刷新控件 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshComponent.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshFooter : MJRefreshComponent 18 | /** 创建footer */ 19 | + (instancetype)footerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock; 20 | /** 创建footer */ 21 | + (instancetype)footerWithRefreshingTarget:(id)target refreshingAction:(SEL)action; 22 | 23 | /** 提示没有更多的数据 */ 24 | - (void)endRefreshingWithNoMoreData; 25 | - (void)noticeNoMoreData MJRefreshDeprecated("使用endRefreshingWithNoMoreData"); 26 | 27 | /** 重置没有更多的数据(消除没有更多数据的状态) */ 28 | - (void)resetNoMoreData; 29 | 30 | /** 忽略多少scrollView的contentInset的bottom */ 31 | @property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetBottom; 32 | 33 | /** 自动根据有无数据来显示和隐藏(有数据就显示,没有数据隐藏。默认是NO) */ 34 | @property (assign, nonatomic, getter=isAutomaticallyHidden) BOOL automaticallyHidden MJRefreshDeprecated("已废弃此属性,开发者请自行控制footer的显示和隐藏"); 35 | @end 36 | 37 | NS_ASSUME_NONNULL_END 38 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshFooter.m: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // MJRefreshFooter.m 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/3/5. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshFooter.h" 10 | #import "UIScrollView+MJRefresh.h" 11 | #import "UIView+MJExtension.h" 12 | 13 | @interface MJRefreshFooter() 14 | 15 | @end 16 | 17 | @implementation MJRefreshFooter 18 | #pragma mark - 构造方法 19 | + (instancetype)footerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock 20 | { 21 | MJRefreshFooter *cmp = [[self alloc] init]; 22 | cmp.refreshingBlock = refreshingBlock; 23 | return cmp; 24 | } 25 | + (instancetype)footerWithRefreshingTarget:(id)target refreshingAction:(SEL)action 26 | { 27 | MJRefreshFooter *cmp = [[self alloc] init]; 28 | [cmp setRefreshingTarget:target refreshingAction:action]; 29 | return cmp; 30 | } 31 | 32 | #pragma mark - 重写父类的方法 33 | - (void)prepare 34 | { 35 | [super prepare]; 36 | 37 | // 设置自己的高度 38 | self.mj_h = MJRefreshFooterHeight; 39 | 40 | // 默认不会自动隐藏 41 | // self.automaticallyHidden = NO; 42 | } 43 | 44 | #pragma mark . 链式语法部分 . 45 | 46 | - (instancetype)linkTo:(UIScrollView *)scrollView { 47 | scrollView.mj_footer = self; 48 | return self; 49 | } 50 | 51 | #pragma mark - 公共方法 52 | - (void)endRefreshingWithNoMoreData 53 | { 54 | MJRefreshDispatchAsyncOnMainQueue(self.state = MJRefreshStateNoMoreData;) 55 | } 56 | 57 | - (void)noticeNoMoreData 58 | { 59 | [self endRefreshingWithNoMoreData]; 60 | } 61 | 62 | - (void)resetNoMoreData 63 | { 64 | MJRefreshDispatchAsyncOnMainQueue(self.state = MJRefreshStateIdle;) 65 | } 66 | 67 | - (void)setAutomaticallyHidden:(BOOL)automaticallyHidden 68 | { 69 | _automaticallyHidden = automaticallyHidden; 70 | } 71 | @end 72 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshHeader.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // MJRefreshHeader.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/3/4. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 下拉刷新控件:负责监控用户下拉的状态 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshComponent.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshHeader : MJRefreshComponent 18 | /** 创建header */ 19 | + (instancetype)headerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock; 20 | /** 创建header */ 21 | + (instancetype)headerWithRefreshingTarget:(id)target refreshingAction:(SEL)action; 22 | 23 | /** 这个key用来存储上一次下拉刷新成功的时间 */ 24 | @property (copy, nonatomic) NSString *lastUpdatedTimeKey; 25 | /** 上一次下拉刷新成功的时间 */ 26 | @property (strong, nonatomic, readonly, nullable) NSDate *lastUpdatedTime; 27 | 28 | /** 忽略多少scrollView的contentInset的top */ 29 | @property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetTop; 30 | 31 | /** 默认是关闭状态, 如果遇到 CollectionView 的动画异常问题可以尝试打开 */ 32 | @property (nonatomic) BOOL isCollectionViewAnimationBug; 33 | @end 34 | 35 | NS_ASSUME_NONNULL_END 36 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshTrailer.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshTrailer.h 3 | // MJRefresh 4 | // 5 | // Created by kinarobin on 2020/5/3. 6 | // Copyright © 2020 小码哥. All rights reserved. 7 | // 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshComponent.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshTrailer : MJRefreshComponent 18 | 19 | /** 创建trailer*/ 20 | + (instancetype)trailerWithRefreshingBlock:(MJRefreshComponentAction)refreshingBlock; 21 | /** 创建trailer */ 22 | + (instancetype)trailerWithRefreshingTarget:(id)target refreshingAction:(SEL)action; 23 | 24 | /** 忽略多少scrollView的contentInset的right */ 25 | @property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetRight; 26 | 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Auto/MJRefreshAutoGifFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoGifFooter.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshAutoStateFooter.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshAutoGifFooter : MJRefreshAutoStateFooter 18 | @property (weak, nonatomic, readonly) UIImageView *gifView; 19 | 20 | /** 设置state状态下的动画图片images 动画持续时间duration*/ 21 | - (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state; 22 | - (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state; 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Auto/MJRefreshAutoGifFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoGifFooter.m 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshAutoGifFooter.h" 10 | #import "NSBundle+MJRefresh.h" 11 | #import "UIView+MJExtension.h" 12 | #import "UIScrollView+MJExtension.h" 13 | #import "UIScrollView+MJRefresh.h" 14 | 15 | @interface MJRefreshAutoGifFooter() 16 | { 17 | __unsafe_unretained UIImageView *_gifView; 18 | } 19 | /** 所有状态对应的动画图片 */ 20 | @property (strong, nonatomic) NSMutableDictionary *stateImages; 21 | /** 所有状态对应的动画时间 */ 22 | @property (strong, nonatomic) NSMutableDictionary *stateDurations; 23 | @end 24 | 25 | @implementation MJRefreshAutoGifFooter 26 | #pragma mark - 懒加载 27 | - (UIImageView *)gifView 28 | { 29 | if (!_gifView) { 30 | UIImageView *gifView = [[UIImageView alloc] init]; 31 | [self addSubview:_gifView = gifView]; 32 | } 33 | return _gifView; 34 | } 35 | 36 | - (NSMutableDictionary *)stateImages 37 | { 38 | if (!_stateImages) { 39 | self.stateImages = [NSMutableDictionary dictionary]; 40 | } 41 | return _stateImages; 42 | } 43 | 44 | - (NSMutableDictionary *)stateDurations 45 | { 46 | if (!_stateDurations) { 47 | self.stateDurations = [NSMutableDictionary dictionary]; 48 | } 49 | return _stateDurations; 50 | } 51 | 52 | #pragma mark - 公共方法 53 | - (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state 54 | { 55 | if (images == nil) return self; 56 | 57 | self.stateImages[@(state)] = images; 58 | self.stateDurations[@(state)] = @(duration); 59 | 60 | /* 根据图片设置控件的高度 */ 61 | UIImage *image = [images firstObject]; 62 | if (image.size.height > self.mj_h) { 63 | self.mj_h = image.size.height; 64 | } 65 | return self; 66 | } 67 | 68 | - (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state 69 | { 70 | return [self setImages:images duration:images.count * 0.1 forState:state]; 71 | } 72 | 73 | #pragma mark - 实现父类的方法 74 | - (void)prepare 75 | { 76 | [super prepare]; 77 | 78 | // 初始化间距 79 | self.labelLeftInset = 20; 80 | } 81 | 82 | - (void)placeSubviews 83 | { 84 | [super placeSubviews]; 85 | 86 | if (self.gifView.constraints.count) return; 87 | 88 | self.gifView.frame = self.bounds; 89 | if (self.isRefreshingTitleHidden) { 90 | self.gifView.contentMode = UIViewContentModeCenter; 91 | } else { 92 | self.gifView.contentMode = UIViewContentModeRight; 93 | self.gifView.mj_w = self.mj_w * 0.5 - self.labelLeftInset - self.stateLabel.mj_textWidth * 0.5; 94 | } 95 | } 96 | 97 | - (void)setState:(MJRefreshState)state 98 | { 99 | MJRefreshCheckState 100 | 101 | // 根据状态做事情 102 | if (state == MJRefreshStateRefreshing) { 103 | NSArray *images = self.stateImages[@(state)]; 104 | if (images.count == 0) return; 105 | [self.gifView stopAnimating]; 106 | 107 | self.gifView.hidden = NO; 108 | if (images.count == 1) { // 单张图片 109 | self.gifView.image = [images lastObject]; 110 | } else { // 多张图片 111 | self.gifView.animationImages = images; 112 | self.gifView.animationDuration = [self.stateDurations[@(state)] doubleValue]; 113 | [self.gifView startAnimating]; 114 | } 115 | } else if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) { 116 | [self.gifView stopAnimating]; 117 | self.gifView.hidden = YES; 118 | } 119 | } 120 | @end 121 | 122 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Auto/MJRefreshAutoNormalFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoNormalFooter.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshAutoStateFooter.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshAutoNormalFooter : MJRefreshAutoStateFooter 18 | @property (weak, nonatomic, readonly) UIActivityIndicatorView *loadingView; 19 | 20 | /** 菊花的样式 */ 21 | @property (assign, nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle MJRefreshDeprecated("first deprecated in 3.2.2 - Use `loadingView` property"); 22 | @end 23 | 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Auto/MJRefreshAutoNormalFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoNormalFooter.m 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshAutoNormalFooter.h" 10 | #import "NSBundle+MJRefresh.h" 11 | #import "UIView+MJExtension.h" 12 | #import "UIScrollView+MJExtension.h" 13 | #import "UIScrollView+MJRefresh.h" 14 | 15 | @interface MJRefreshAutoNormalFooter() 16 | @property (weak, nonatomic) UIActivityIndicatorView *loadingView; 17 | @end 18 | 19 | @implementation MJRefreshAutoNormalFooter 20 | #pragma mark - 懒加载子控件 21 | - (UIActivityIndicatorView *)loadingView 22 | { 23 | if (!_loadingView) { 24 | UIActivityIndicatorView *loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:_activityIndicatorViewStyle]; 25 | loadingView.hidesWhenStopped = YES; 26 | [self addSubview:_loadingView = loadingView]; 27 | } 28 | return _loadingView; 29 | } 30 | 31 | - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)activityIndicatorViewStyle 32 | { 33 | _activityIndicatorViewStyle = activityIndicatorViewStyle; 34 | 35 | [self.loadingView removeFromSuperview]; 36 | self.loadingView = nil; 37 | [self setNeedsLayout]; 38 | } 39 | #pragma mark - 重写父类的方法 40 | - (void)prepare 41 | { 42 | [super prepare]; 43 | 44 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 45 | if (@available(iOS 13.0, *)) { 46 | _activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium; 47 | return; 48 | } 49 | #endif 50 | 51 | _activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; 52 | } 53 | 54 | - (void)placeSubviews 55 | { 56 | [super placeSubviews]; 57 | 58 | if (self.loadingView.constraints.count) return; 59 | 60 | // 圈圈 61 | CGFloat loadingCenterX = self.mj_w * 0.5; 62 | if (!self.isRefreshingTitleHidden) { 63 | loadingCenterX -= self.stateLabel.mj_textWidth * 0.5 + self.labelLeftInset; 64 | } 65 | CGFloat loadingCenterY = self.mj_h * 0.5; 66 | self.loadingView.center = CGPointMake(loadingCenterX, loadingCenterY); 67 | } 68 | 69 | - (void)setState:(MJRefreshState)state 70 | { 71 | MJRefreshCheckState 72 | 73 | // 根据状态做事情 74 | if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) { 75 | [self.loadingView stopAnimating]; 76 | } else if (state == MJRefreshStateRefreshing) { 77 | [self.loadingView startAnimating]; 78 | } 79 | } 80 | 81 | @end 82 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoStateFooter.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/6/13. 6 | // Copyright © 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshAutoFooter.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshAutoStateFooter : MJRefreshAutoFooter 18 | /** 文字距离圈圈、箭头的距离 */ 19 | @property (assign, nonatomic) CGFloat labelLeftInset; 20 | /** 显示刷新状态的label */ 21 | @property (weak, nonatomic, readonly) UILabel *stateLabel; 22 | 23 | /** 设置state状态下的文字 */ 24 | - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state; 25 | 26 | /** 隐藏刷新状态的文字 */ 27 | @property (assign, nonatomic, getter=isRefreshingTitleHidden) BOOL refreshingTitleHidden; 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoStateFooter.m 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/6/13. 6 | // Copyright © 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshAutoStateFooter.h" 10 | #import "NSBundle+MJRefresh.h" 11 | 12 | @interface MJRefreshAutoFooter (TapTriggerFix) 13 | 14 | - (void)beginRefreshingWithoutValidation; 15 | @end 16 | 17 | 18 | @implementation MJRefreshAutoFooter (TapTriggerFix) 19 | 20 | - (void)beginRefreshingWithoutValidation { 21 | [super beginRefreshing]; 22 | } 23 | 24 | @end 25 | 26 | @interface MJRefreshAutoStateFooter() 27 | { 28 | /** 显示刷新状态的label */ 29 | __unsafe_unretained UILabel *_stateLabel; 30 | } 31 | /** 所有状态对应的文字 */ 32 | @property (strong, nonatomic) NSMutableDictionary *stateTitles; 33 | @end 34 | 35 | @implementation MJRefreshAutoStateFooter 36 | #pragma mark - 懒加载 37 | - (NSMutableDictionary *)stateTitles 38 | { 39 | if (!_stateTitles) { 40 | self.stateTitles = [NSMutableDictionary dictionary]; 41 | } 42 | return _stateTitles; 43 | } 44 | 45 | - (UILabel *)stateLabel 46 | { 47 | if (!_stateLabel) { 48 | [self addSubview:_stateLabel = [UILabel mj_label]]; 49 | } 50 | return _stateLabel; 51 | } 52 | 53 | #pragma mark - 公共方法 54 | - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state 55 | { 56 | if (title == nil) return self; 57 | self.stateTitles[@(state)] = title; 58 | self.stateLabel.text = self.stateTitles[@(self.state)]; 59 | return self; 60 | } 61 | 62 | #pragma mark - 私有方法 63 | - (void)stateLabelClick 64 | { 65 | if (self.state == MJRefreshStateIdle) { 66 | [super beginRefreshingWithoutValidation]; 67 | } 68 | } 69 | 70 | - (void)textConfiguration { 71 | // 初始化文字 72 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterIdleText] forState:MJRefreshStateIdle]; 73 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterRefreshingText] forState:MJRefreshStateRefreshing]; 74 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterNoMoreDataText] forState:MJRefreshStateNoMoreData]; 75 | } 76 | 77 | #pragma mark - 重写父类的方法 78 | - (void)prepare 79 | { 80 | [super prepare]; 81 | 82 | // 初始化间距 83 | self.labelLeftInset = MJRefreshLabelLeftInset; 84 | 85 | [self textConfiguration]; 86 | 87 | // 监听label 88 | self.stateLabel.userInteractionEnabled = YES; 89 | [self.stateLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(stateLabelClick)]]; 90 | } 91 | 92 | - (void)i18nDidChange { 93 | [self textConfiguration]; 94 | 95 | [super i18nDidChange]; 96 | } 97 | 98 | 99 | - (void)placeSubviews 100 | { 101 | [super placeSubviews]; 102 | 103 | if (self.stateLabel.constraints.count) return; 104 | 105 | // 状态标签 106 | self.stateLabel.frame = self.bounds; 107 | } 108 | 109 | - (void)setState:(MJRefreshState)state 110 | { 111 | MJRefreshCheckState 112 | 113 | if (self.isRefreshingTitleHidden && state == MJRefreshStateRefreshing) { 114 | self.stateLabel.text = nil; 115 | } else { 116 | self.stateLabel.text = self.stateTitles[@(state)]; 117 | } 118 | } 119 | @end 120 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Back/MJRefreshBackGifFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackGifFooter.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshBackStateFooter.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshBackGifFooter : MJRefreshBackStateFooter 18 | @property (weak, nonatomic, readonly) UIImageView *gifView; 19 | 20 | /** 设置state状态下的动画图片images 动画持续时间duration*/ 21 | - (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state; 22 | - (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state; 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Back/MJRefreshBackGifFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackGifFooter.m 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshBackGifFooter.h" 10 | #import "NSBundle+MJRefresh.h" 11 | #import "UIView+MJExtension.h" 12 | #import "UIScrollView+MJExtension.h" 13 | #import "UIScrollView+MJRefresh.h" 14 | 15 | @interface MJRefreshBackGifFooter() 16 | { 17 | __unsafe_unretained UIImageView *_gifView; 18 | } 19 | /** 所有状态对应的动画图片 */ 20 | @property (strong, nonatomic) NSMutableDictionary *stateImages; 21 | /** 所有状态对应的动画时间 */ 22 | @property (strong, nonatomic) NSMutableDictionary *stateDurations; 23 | @end 24 | 25 | @implementation MJRefreshBackGifFooter 26 | #pragma mark - 懒加载 27 | - (UIImageView *)gifView 28 | { 29 | if (!_gifView) { 30 | UIImageView *gifView = [[UIImageView alloc] init]; 31 | [self addSubview:_gifView = gifView]; 32 | } 33 | return _gifView; 34 | } 35 | 36 | - (NSMutableDictionary *)stateImages 37 | { 38 | if (!_stateImages) { 39 | self.stateImages = [NSMutableDictionary dictionary]; 40 | } 41 | return _stateImages; 42 | } 43 | 44 | - (NSMutableDictionary *)stateDurations 45 | { 46 | if (!_stateDurations) { 47 | self.stateDurations = [NSMutableDictionary dictionary]; 48 | } 49 | return _stateDurations; 50 | } 51 | 52 | #pragma mark - 公共方法 53 | - (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state 54 | { 55 | if (images == nil) return self; 56 | 57 | self.stateImages[@(state)] = images; 58 | self.stateDurations[@(state)] = @(duration); 59 | 60 | /* 根据图片设置控件的高度 */ 61 | UIImage *image = [images firstObject]; 62 | if (image.size.height > self.mj_h) { 63 | self.mj_h = image.size.height; 64 | } 65 | return self; 66 | } 67 | 68 | - (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state 69 | { 70 | return [self setImages:images duration:images.count * 0.1 forState:state]; 71 | } 72 | 73 | #pragma mark - 实现父类的方法 74 | - (void)prepare 75 | { 76 | [super prepare]; 77 | 78 | // 初始化间距 79 | self.labelLeftInset = 20; 80 | } 81 | 82 | - (void)setPullingPercent:(CGFloat)pullingPercent 83 | { 84 | [super setPullingPercent:pullingPercent]; 85 | NSArray *images = self.stateImages[@(MJRefreshStateIdle)]; 86 | if (self.state != MJRefreshStateIdle || images.count == 0) return; 87 | [self.gifView stopAnimating]; 88 | NSUInteger index = images.count * pullingPercent; 89 | if (index >= images.count) index = images.count - 1; 90 | self.gifView.image = images[index]; 91 | } 92 | 93 | - (void)placeSubviews 94 | { 95 | [super placeSubviews]; 96 | 97 | if (self.gifView.constraints.count) return; 98 | 99 | self.gifView.frame = self.bounds; 100 | if (self.stateLabel.hidden) { 101 | self.gifView.contentMode = UIViewContentModeCenter; 102 | } else { 103 | self.gifView.contentMode = UIViewContentModeRight; 104 | self.gifView.mj_w = self.mj_w * 0.5 - self.labelLeftInset - self.stateLabel.mj_textWidth * 0.5; 105 | } 106 | } 107 | 108 | - (void)setState:(MJRefreshState)state 109 | { 110 | MJRefreshCheckState 111 | 112 | // 根据状态做事情 113 | if (state == MJRefreshStatePulling || state == MJRefreshStateRefreshing) { 114 | NSArray *images = self.stateImages[@(state)]; 115 | if (images.count == 0) return; 116 | 117 | self.gifView.hidden = NO; 118 | [self.gifView stopAnimating]; 119 | if (images.count == 1) { // 单张图片 120 | self.gifView.image = [images lastObject]; 121 | } else { // 多张图片 122 | self.gifView.animationImages = images; 123 | self.gifView.animationDuration = [self.stateDurations[@(state)] doubleValue]; 124 | [self.gifView startAnimating]; 125 | } 126 | } else if (state == MJRefreshStateIdle) { 127 | self.gifView.hidden = NO; 128 | } else if (state == MJRefreshStateNoMoreData) { 129 | self.gifView.hidden = YES; 130 | } 131 | } 132 | @end 133 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Back/MJRefreshBackNormalFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackNormalFooter.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshBackStateFooter.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshBackNormalFooter : MJRefreshBackStateFooter 18 | @property (weak, nonatomic, readonly) UIImageView *arrowView; 19 | @property (weak, nonatomic, readonly) UIActivityIndicatorView *loadingView; 20 | 21 | /** 菊花的样式 */ 22 | @property (assign, nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle MJRefreshDeprecated("first deprecated in 3.2.2 - Use `loadingView` property"); 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Back/MJRefreshBackNormalFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackNormalFooter.m 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshBackNormalFooter.h" 10 | #import "NSBundle+MJRefresh.h" 11 | #import "UIView+MJExtension.h" 12 | 13 | @interface MJRefreshBackNormalFooter() 14 | { 15 | __unsafe_unretained UIImageView *_arrowView; 16 | } 17 | @property (weak, nonatomic) UIActivityIndicatorView *loadingView; 18 | @end 19 | 20 | @implementation MJRefreshBackNormalFooter 21 | #pragma mark - 懒加载子控件 22 | - (UIImageView *)arrowView 23 | { 24 | if (!_arrowView) { 25 | UIImageView *arrowView = [[UIImageView alloc] initWithImage:[NSBundle mj_arrowImage]]; 26 | [self addSubview:_arrowView = arrowView]; 27 | } 28 | return _arrowView; 29 | } 30 | 31 | 32 | - (UIActivityIndicatorView *)loadingView 33 | { 34 | if (!_loadingView) { 35 | UIActivityIndicatorView *loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:_activityIndicatorViewStyle]; 36 | loadingView.hidesWhenStopped = YES; 37 | [self addSubview:_loadingView = loadingView]; 38 | } 39 | return _loadingView; 40 | } 41 | 42 | - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)activityIndicatorViewStyle 43 | { 44 | _activityIndicatorViewStyle = activityIndicatorViewStyle; 45 | 46 | [self.loadingView removeFromSuperview]; 47 | self.loadingView = nil; 48 | [self setNeedsLayout]; 49 | } 50 | #pragma mark - 重写父类的方法 51 | - (void)prepare 52 | { 53 | [super prepare]; 54 | 55 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 56 | if (@available(iOS 13.0, *)) { 57 | _activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium; 58 | return; 59 | } 60 | #endif 61 | 62 | _activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; 63 | } 64 | 65 | - (void)placeSubviews 66 | { 67 | [super placeSubviews]; 68 | 69 | // 箭头的中心点 70 | CGFloat arrowCenterX = self.mj_w * 0.5; 71 | if (!self.stateLabel.hidden) { 72 | arrowCenterX -= self.labelLeftInset + self.stateLabel.mj_textWidth * 0.5; 73 | } 74 | CGFloat arrowCenterY = self.mj_h * 0.5; 75 | CGPoint arrowCenter = CGPointMake(arrowCenterX, arrowCenterY); 76 | 77 | // 箭头 78 | if (self.arrowView.constraints.count == 0) { 79 | self.arrowView.mj_size = self.arrowView.image.size; 80 | self.arrowView.center = arrowCenter; 81 | } 82 | 83 | // 圈圈 84 | if (self.loadingView.constraints.count == 0) { 85 | self.loadingView.center = arrowCenter; 86 | } 87 | 88 | self.arrowView.tintColor = self.stateLabel.textColor; 89 | } 90 | 91 | - (void)setState:(MJRefreshState)state 92 | { 93 | MJRefreshCheckState 94 | 95 | // 根据状态做事情 96 | if (state == MJRefreshStateIdle) { 97 | if (oldState == MJRefreshStateRefreshing) { 98 | self.arrowView.transform = CGAffineTransformMakeRotation(0.000001 - M_PI); 99 | [UIView animateWithDuration:self.slowAnimationDuration animations:^{ 100 | self.loadingView.alpha = 0.0; 101 | } completion:^(BOOL finished) { 102 | // 防止动画结束后,状态已经不是MJRefreshStateIdle 103 | if (self.state != MJRefreshStateIdle) return; 104 | 105 | self.loadingView.alpha = 1.0; 106 | [self.loadingView stopAnimating]; 107 | 108 | self.arrowView.hidden = NO; 109 | }]; 110 | } else { 111 | self.arrowView.hidden = NO; 112 | [self.loadingView stopAnimating]; 113 | [UIView animateWithDuration:self.fastAnimationDuration animations:^{ 114 | self.arrowView.transform = CGAffineTransformMakeRotation(0.000001 - M_PI); 115 | }]; 116 | } 117 | } else if (state == MJRefreshStatePulling) { 118 | self.arrowView.hidden = NO; 119 | [self.loadingView stopAnimating]; 120 | [UIView animateWithDuration:self.fastAnimationDuration animations:^{ 121 | self.arrowView.transform = CGAffineTransformIdentity; 122 | }]; 123 | } else if (state == MJRefreshStateRefreshing) { 124 | self.arrowView.hidden = YES; 125 | [self.loadingView startAnimating]; 126 | } else if (state == MJRefreshStateNoMoreData) { 127 | self.arrowView.hidden = YES; 128 | [self.loadingView stopAnimating]; 129 | } 130 | } 131 | 132 | @end 133 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Back/MJRefreshBackStateFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackStateFooter.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/6/13. 6 | // Copyright © 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshBackFooter.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshBackStateFooter : MJRefreshBackFooter 18 | /** 文字距离圈圈、箭头的距离 */ 19 | @property (assign, nonatomic) CGFloat labelLeftInset; 20 | /** 显示刷新状态的label */ 21 | @property (weak, nonatomic, readonly) UILabel *stateLabel; 22 | /** 设置state状态下的文字 */ 23 | - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state; 24 | 25 | /** 获取state状态下的title */ 26 | - (NSString *)titleForState:(MJRefreshState)state; 27 | @end 28 | 29 | NS_ASSUME_NONNULL_END 30 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Back/MJRefreshBackStateFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackStateFooter.m 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/6/13. 6 | // Copyright © 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshBackStateFooter.h" 10 | #import "NSBundle+MJRefresh.h" 11 | 12 | @interface MJRefreshBackStateFooter() 13 | { 14 | /** 显示刷新状态的label */ 15 | __unsafe_unretained UILabel *_stateLabel; 16 | } 17 | /** 所有状态对应的文字 */ 18 | @property (strong, nonatomic) NSMutableDictionary *stateTitles; 19 | @end 20 | 21 | @implementation MJRefreshBackStateFooter 22 | #pragma mark - 懒加载 23 | - (NSMutableDictionary *)stateTitles 24 | { 25 | if (!_stateTitles) { 26 | self.stateTitles = [NSMutableDictionary dictionary]; 27 | } 28 | return _stateTitles; 29 | } 30 | 31 | - (UILabel *)stateLabel 32 | { 33 | if (!_stateLabel) { 34 | [self addSubview:_stateLabel = [UILabel mj_label]]; 35 | } 36 | return _stateLabel; 37 | } 38 | 39 | #pragma mark - 公共方法 40 | - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state 41 | { 42 | if (title == nil) return self; 43 | self.stateTitles[@(state)] = title; 44 | self.stateLabel.text = self.stateTitles[@(self.state)]; 45 | return self; 46 | } 47 | 48 | - (NSString *)titleForState:(MJRefreshState)state { 49 | return self.stateTitles[@(state)]; 50 | } 51 | 52 | - (void)textConfiguration { 53 | // 初始化文字 54 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterIdleText] forState:MJRefreshStateIdle]; 55 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterPullingText] forState:MJRefreshStatePulling]; 56 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterRefreshingText] forState:MJRefreshStateRefreshing]; 57 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterNoMoreDataText] forState:MJRefreshStateNoMoreData]; 58 | } 59 | 60 | #pragma mark - 重写父类的方法 61 | - (void)prepare 62 | { 63 | [super prepare]; 64 | 65 | // 初始化间距 66 | self.labelLeftInset = MJRefreshLabelLeftInset; 67 | [self textConfiguration]; 68 | } 69 | 70 | - (void)i18nDidChange { 71 | [self textConfiguration]; 72 | 73 | [super i18nDidChange]; 74 | } 75 | 76 | - (void)placeSubviews 77 | { 78 | [super placeSubviews]; 79 | 80 | if (self.stateLabel.constraints.count) return; 81 | 82 | // 状态标签 83 | self.stateLabel.frame = self.bounds; 84 | } 85 | 86 | - (void)setState:(MJRefreshState)state 87 | { 88 | MJRefreshCheckState 89 | 90 | // 设置状态文字 91 | self.stateLabel.text = self.stateTitles[@(state)]; 92 | } 93 | @end 94 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Header/MJRefreshGifHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshGifHeader.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshStateHeader.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshGifHeader : MJRefreshStateHeader 18 | @property (weak, nonatomic, readonly) UIImageView *gifView; 19 | 20 | /** 设置state状态下的动画图片images 动画持续时间duration*/ 21 | - (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state; 22 | - (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state; 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Header/MJRefreshGifHeader.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshGifHeader.m 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshGifHeader.h" 10 | #import "UIView+MJExtension.h" 11 | #import "UIScrollView+MJExtension.h" 12 | 13 | @interface MJRefreshGifHeader() 14 | { 15 | __unsafe_unretained UIImageView *_gifView; 16 | } 17 | /** 所有状态对应的动画图片 */ 18 | @property (strong, nonatomic) NSMutableDictionary *stateImages; 19 | /** 所有状态对应的动画时间 */ 20 | @property (strong, nonatomic) NSMutableDictionary *stateDurations; 21 | @end 22 | 23 | @implementation MJRefreshGifHeader 24 | #pragma mark - 懒加载 25 | - (UIImageView *)gifView 26 | { 27 | if (!_gifView) { 28 | UIImageView *gifView = [[UIImageView alloc] init]; 29 | [self addSubview:_gifView = gifView]; 30 | } 31 | return _gifView; 32 | } 33 | 34 | - (NSMutableDictionary *)stateImages 35 | { 36 | if (!_stateImages) { 37 | self.stateImages = [NSMutableDictionary dictionary]; 38 | } 39 | return _stateImages; 40 | } 41 | 42 | - (NSMutableDictionary *)stateDurations 43 | { 44 | if (!_stateDurations) { 45 | self.stateDurations = [NSMutableDictionary dictionary]; 46 | } 47 | return _stateDurations; 48 | } 49 | 50 | #pragma mark - 公共方法 51 | - (instancetype)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state { 52 | if (images == nil) return self; 53 | 54 | self.stateImages[@(state)] = images; 55 | self.stateDurations[@(state)] = @(duration); 56 | 57 | /* 根据图片设置控件的高度 */ 58 | UIImage *image = [images firstObject]; 59 | if (image.size.height > self.mj_h) { 60 | self.mj_h = image.size.height; 61 | } 62 | return self; 63 | } 64 | 65 | - (instancetype)setImages:(NSArray *)images forState:(MJRefreshState)state 66 | { 67 | return [self setImages:images duration:images.count * 0.1 forState:state]; 68 | } 69 | 70 | #pragma mark - 实现父类的方法 71 | - (void)prepare 72 | { 73 | [super prepare]; 74 | 75 | // 初始化间距 76 | self.labelLeftInset = 20; 77 | } 78 | 79 | - (void)setPullingPercent:(CGFloat)pullingPercent 80 | { 81 | [super setPullingPercent:pullingPercent]; 82 | NSArray *images = self.stateImages[@(MJRefreshStateIdle)]; 83 | if (self.state != MJRefreshStateIdle || images.count == 0) return; 84 | // 停止动画 85 | [self.gifView stopAnimating]; 86 | // 设置当前需要显示的图片 87 | NSUInteger index = images.count * pullingPercent; 88 | if (index >= images.count) index = images.count - 1; 89 | self.gifView.image = images[index]; 90 | } 91 | 92 | - (void)placeSubviews 93 | { 94 | [super placeSubviews]; 95 | 96 | if (self.gifView.constraints.count) return; 97 | 98 | self.gifView.frame = self.bounds; 99 | if (self.stateLabel.hidden && self.lastUpdatedTimeLabel.hidden) { 100 | self.gifView.contentMode = UIViewContentModeCenter; 101 | } else { 102 | self.gifView.contentMode = UIViewContentModeRight; 103 | 104 | CGFloat stateWidth = self.stateLabel.mj_textWidth; 105 | CGFloat timeWidth = 0.0; 106 | if (!self.lastUpdatedTimeLabel.hidden) { 107 | timeWidth = self.lastUpdatedTimeLabel.mj_textWidth; 108 | } 109 | CGFloat textWidth = MAX(stateWidth, timeWidth); 110 | self.gifView.mj_w = self.mj_w * 0.5 - textWidth * 0.5 - self.labelLeftInset; 111 | } 112 | } 113 | 114 | - (void)setState:(MJRefreshState)state 115 | { 116 | MJRefreshCheckState 117 | 118 | // 根据状态做事情 119 | if (state == MJRefreshStatePulling || state == MJRefreshStateRefreshing) { 120 | NSArray *images = self.stateImages[@(state)]; 121 | if (images.count == 0) return; 122 | 123 | [self.gifView stopAnimating]; 124 | if (images.count == 1) { // 单张图片 125 | self.gifView.image = [images lastObject]; 126 | } else { // 多张图片 127 | self.gifView.animationImages = images; 128 | self.gifView.animationDuration = [self.stateDurations[@(state)] doubleValue]; 129 | [self.gifView startAnimating]; 130 | } 131 | } else if (state == MJRefreshStateIdle) { 132 | [self.gifView stopAnimating]; 133 | } 134 | } 135 | @end 136 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Header/MJRefreshNormalHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshNormalHeader.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshStateHeader.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshNormalHeader : MJRefreshStateHeader 18 | @property (weak, nonatomic, readonly) UIImageView *arrowView; 19 | @property (weak, nonatomic, readonly) UIActivityIndicatorView *loadingView; 20 | 21 | 22 | /** 菊花的样式 */ 23 | @property (assign, nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle MJRefreshDeprecated("first deprecated in 3.2.2 - Use `loadingView` property"); 24 | @end 25 | 26 | NS_ASSUME_NONNULL_END 27 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Header/MJRefreshNormalHeader.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshNormalHeader.m 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshNormalHeader.h" 10 | #import "NSBundle+MJRefresh.h" 11 | #import "UIScrollView+MJRefresh.h" 12 | #import "UIView+MJExtension.h" 13 | 14 | @interface MJRefreshNormalHeader() 15 | { 16 | __unsafe_unretained UIImageView *_arrowView; 17 | } 18 | @property (weak, nonatomic) UIActivityIndicatorView *loadingView; 19 | @end 20 | 21 | @implementation MJRefreshNormalHeader 22 | #pragma mark - 懒加载子控件 23 | - (UIImageView *)arrowView 24 | { 25 | if (!_arrowView) { 26 | UIImageView *arrowView = [[UIImageView alloc] initWithImage:[NSBundle mj_arrowImage]]; 27 | [self addSubview:_arrowView = arrowView]; 28 | } 29 | return _arrowView; 30 | } 31 | 32 | - (UIActivityIndicatorView *)loadingView 33 | { 34 | if (!_loadingView) { 35 | UIActivityIndicatorView *loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:_activityIndicatorViewStyle]; 36 | loadingView.hidesWhenStopped = YES; 37 | [self addSubview:_loadingView = loadingView]; 38 | } 39 | return _loadingView; 40 | } 41 | 42 | #pragma mark - 公共方法 43 | - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)activityIndicatorViewStyle 44 | { 45 | _activityIndicatorViewStyle = activityIndicatorViewStyle; 46 | 47 | [self.loadingView removeFromSuperview]; 48 | self.loadingView = nil; 49 | [self setNeedsLayout]; 50 | } 51 | 52 | #pragma mark - 重写父类的方法 53 | - (void)prepare 54 | { 55 | [super prepare]; 56 | 57 | #if __IPHONE_OS_VERSION_MAX_ALLOWED >= 130000 58 | if (@available(iOS 13.0, *)) { 59 | _activityIndicatorViewStyle = UIActivityIndicatorViewStyleMedium; 60 | return; 61 | } 62 | #endif 63 | 64 | _activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; 65 | } 66 | 67 | - (void)placeSubviews 68 | { 69 | [super placeSubviews]; 70 | 71 | // 箭头的中心点 72 | CGFloat arrowCenterX = self.mj_w * 0.5; 73 | if (!self.stateLabel.hidden) { 74 | CGFloat stateWidth = self.stateLabel.mj_textWidth; 75 | CGFloat timeWidth = 0.0; 76 | if (!self.lastUpdatedTimeLabel.hidden) { 77 | timeWidth = self.lastUpdatedTimeLabel.mj_textWidth; 78 | } 79 | CGFloat textWidth = MAX(stateWidth, timeWidth); 80 | arrowCenterX -= textWidth / 2 + self.labelLeftInset; 81 | } 82 | CGFloat arrowCenterY = self.mj_h * 0.5; 83 | CGPoint arrowCenter = CGPointMake(arrowCenterX, arrowCenterY); 84 | 85 | // 箭头 86 | if (self.arrowView.constraints.count == 0) { 87 | self.arrowView.mj_size = self.arrowView.image.size; 88 | self.arrowView.center = arrowCenter; 89 | } 90 | 91 | // 圈圈 92 | if (self.loadingView.constraints.count == 0) { 93 | self.loadingView.center = arrowCenter; 94 | } 95 | 96 | self.arrowView.tintColor = self.stateLabel.textColor; 97 | } 98 | 99 | - (void)setState:(MJRefreshState)state 100 | { 101 | MJRefreshCheckState 102 | 103 | // 根据状态做事情 104 | if (state == MJRefreshStateIdle) { 105 | if (oldState == MJRefreshStateRefreshing) { 106 | self.arrowView.transform = CGAffineTransformIdentity; 107 | 108 | [UIView animateWithDuration:self.slowAnimationDuration animations:^{ 109 | self.loadingView.alpha = 0.0; 110 | } completion:^(BOOL finished) { 111 | // 如果执行完动画发现不是idle状态,就直接返回,进入其他状态 112 | if (self.state != MJRefreshStateIdle) return; 113 | 114 | self.loadingView.alpha = 1.0; 115 | [self.loadingView stopAnimating]; 116 | self.arrowView.hidden = NO; 117 | }]; 118 | } else { 119 | [self.loadingView stopAnimating]; 120 | self.arrowView.hidden = NO; 121 | [UIView animateWithDuration:self.fastAnimationDuration animations:^{ 122 | self.arrowView.transform = CGAffineTransformIdentity; 123 | }]; 124 | } 125 | } else if (state == MJRefreshStatePulling) { 126 | [self.loadingView stopAnimating]; 127 | self.arrowView.hidden = NO; 128 | [UIView animateWithDuration:self.fastAnimationDuration animations:^{ 129 | self.arrowView.transform = CGAffineTransformMakeRotation(0.000001 - M_PI); 130 | }]; 131 | } else if (state == MJRefreshStateRefreshing) { 132 | self.loadingView.alpha = 1.0; // 防止refreshing -> idle的动画完毕动作没有被执行 133 | [self.loadingView startAnimating]; 134 | self.arrowView.hidden = YES; 135 | } 136 | } 137 | @end 138 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Header/MJRefreshStateHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshStateHeader.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshHeader.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshStateHeader : MJRefreshHeader 18 | #pragma mark - 刷新时间相关 19 | /** 利用这个block来决定显示的更新时间文字 */ 20 | @property (copy, nonatomic, nullable) NSString *(^lastUpdatedTimeText)(NSDate * _Nullable lastUpdatedTime); 21 | /** 显示上一次刷新时间的label */ 22 | @property (weak, nonatomic, readonly) UILabel *lastUpdatedTimeLabel; 23 | 24 | #pragma mark - 状态相关 25 | /** 文字距离圈圈、箭头的距离 */ 26 | @property (assign, nonatomic) CGFloat labelLeftInset; 27 | /** 显示刷新状态的label */ 28 | @property (weak, nonatomic, readonly) UILabel *stateLabel; 29 | /** 设置state状态下的文字 */ 30 | - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state; 31 | @end 32 | 33 | @interface MJRefreshStateHeader (ChainingGrammar) 34 | 35 | - (instancetype)modifyLastUpdatedTimeText:(NSString * (^)(NSDate * _Nullable lastUpdatedTime))handler; 36 | 37 | @end 38 | 39 | NS_ASSUME_NONNULL_END 40 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Trailer/MJRefreshNormalTrailer.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshNormalTrailer.h 3 | // MJRefresh 4 | // 5 | // Created by kinarobin on 2020/5/3. 6 | // Copyright © 2020 小码哥. All rights reserved. 7 | // 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshStateTrailer.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface MJRefreshNormalTrailer : MJRefreshStateTrailer 18 | 19 | @property (weak, nonatomic, readonly) UIImageView *arrowView; 20 | 21 | @end 22 | 23 | NS_ASSUME_NONNULL_END 24 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Trailer/MJRefreshNormalTrailer.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshNormalTrailer.m 3 | // MJRefresh 4 | // 5 | // Created by kinarobin on 2020/5/3. 6 | // Copyright © 2020 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshNormalTrailer.h" 10 | #import "NSBundle+MJRefresh.h" 11 | #import "UIView+MJExtension.h" 12 | 13 | @interface MJRefreshNormalTrailer() { 14 | __unsafe_unretained UIImageView *_arrowView; 15 | } 16 | @end 17 | 18 | @implementation MJRefreshNormalTrailer 19 | #pragma mark - 懒加载子控件 20 | - (UIImageView *)arrowView { 21 | if (!_arrowView) { 22 | UIImageView *arrowView = [[UIImageView alloc] initWithImage:[NSBundle mj_trailArrowImage]]; 23 | [self addSubview:_arrowView = arrowView]; 24 | } 25 | return _arrowView; 26 | } 27 | 28 | - (void)placeSubviews { 29 | [super placeSubviews]; 30 | 31 | CGSize arrowSize = self.arrowView.image.size; 32 | // 箭头的中心点 33 | CGPoint selfCenter = CGPointMake(self.mj_w * 0.5, self.mj_h * 0.5); 34 | CGPoint arrowCenter = CGPointMake(arrowSize.width * 0.5 + 5, self.mj_h * 0.5); 35 | BOOL stateHidden = self.stateLabel.isHidden; 36 | 37 | if (self.arrowView.constraints.count == 0) { 38 | self.arrowView.mj_size = self.arrowView.image.size; 39 | self.arrowView.center = stateHidden ? selfCenter : arrowCenter ; 40 | } 41 | self.arrowView.tintColor = self.stateLabel.textColor; 42 | 43 | if (stateHidden) return; 44 | 45 | BOOL noConstrainsOnStatusLabel = self.stateLabel.constraints.count == 0; 46 | CGFloat stateLabelW = ceil(self.stateLabel.font.pointSize); 47 | // 状态 48 | if (noConstrainsOnStatusLabel) { 49 | BOOL arrowHidden = self.arrowView.isHidden; 50 | CGFloat stateCenterX = (self.mj_w + arrowSize.width) * 0.5; 51 | self.stateLabel.center = arrowHidden ? selfCenter : CGPointMake(stateCenterX, self.mj_h * 0.5); 52 | self.stateLabel.mj_size = CGSizeMake(stateLabelW, self.mj_h) ; 53 | } 54 | } 55 | 56 | - (void)setState:(MJRefreshState)state { 57 | MJRefreshCheckState 58 | // 根据状态做事情 59 | if (state == MJRefreshStateIdle) { 60 | if (oldState == MJRefreshStateRefreshing) { 61 | [UIView animateWithDuration:self.fastAnimationDuration animations:^{ 62 | self.arrowView.transform = CGAffineTransformMakeRotation(M_PI); 63 | } completion:^(BOOL finished) { 64 | self.arrowView.transform = CGAffineTransformIdentity; 65 | }]; 66 | } else { 67 | [UIView animateWithDuration:self.fastAnimationDuration animations:^{ 68 | self.arrowView.transform = CGAffineTransformIdentity; 69 | }]; 70 | } 71 | } else if (state == MJRefreshStatePulling) { 72 | [UIView animateWithDuration:self.fastAnimationDuration animations:^{ 73 | self.arrowView.transform = CGAffineTransformMakeRotation(M_PI); 74 | }]; 75 | } 76 | } 77 | 78 | 79 | 80 | @end 81 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Trailer/MJRefreshStateTrailer.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshStateTrailer.h 3 | // MJRefresh 4 | // 5 | // Created by kinarobin on 2020/5/3. 6 | // Copyright © 2020 小码哥. All rights reserved. 7 | // 8 | 9 | #if __has_include() 10 | #import 11 | #else 12 | #import "MJRefreshTrailer.h" 13 | #endif 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | 18 | @interface MJRefreshStateTrailer : MJRefreshTrailer 19 | 20 | #pragma mark - 状态相关 21 | /** 显示刷新状态的label */ 22 | @property (weak, nonatomic, readonly) UILabel *stateLabel; 23 | /** 设置state状态下的文字 */ 24 | - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state; 25 | 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Trailer/MJRefreshStateTrailer.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshStateTrailer.m 3 | // MJRefresh 4 | // 5 | // Created by kinarobin on 2020/5/3. 6 | // Copyright © 2020 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshStateTrailer.h" 10 | #import "NSBundle+MJRefresh.h" 11 | #import "UIView+MJExtension.h" 12 | 13 | @interface MJRefreshStateTrailer() { 14 | /** 显示刷新状态的label */ 15 | __unsafe_unretained UILabel *_stateLabel; 16 | } 17 | /** 所有状态对应的文字 */ 18 | @property (strong, nonatomic) NSMutableDictionary *stateTitles; 19 | @end 20 | 21 | @implementation MJRefreshStateTrailer 22 | #pragma mark - 懒加载 23 | - (NSMutableDictionary *)stateTitles { 24 | if (!_stateTitles) { 25 | self.stateTitles = [NSMutableDictionary dictionary]; 26 | } 27 | return _stateTitles; 28 | } 29 | 30 | - (UILabel *)stateLabel { 31 | if (!_stateLabel) { 32 | UILabel *stateLabel = [UILabel mj_label]; 33 | stateLabel.numberOfLines = 0; 34 | [self addSubview:_stateLabel = stateLabel]; 35 | } 36 | return _stateLabel; 37 | } 38 | 39 | #pragma mark - 公共方法 40 | - (instancetype)setTitle:(NSString *)title forState:(MJRefreshState)state { 41 | if (title == nil) return self; 42 | self.stateTitles[@(state)] = title; 43 | self.stateLabel.text = self.stateTitles[@(self.state)]; 44 | return self; 45 | } 46 | 47 | - (void)textConfiguration { 48 | // 初始化文字 49 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshTrailerIdleText] forState:MJRefreshStateIdle]; 50 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshTrailerPullingText] forState:MJRefreshStatePulling]; 51 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshTrailerPullingText] forState:MJRefreshStateRefreshing]; 52 | } 53 | 54 | #pragma mark - 覆盖父类的方法 55 | - (void)prepare { 56 | [super prepare]; 57 | 58 | [self textConfiguration]; 59 | } 60 | 61 | - (void)i18nDidChange { 62 | [self textConfiguration]; 63 | 64 | [super i18nDidChange]; 65 | } 66 | 67 | - (void)setState:(MJRefreshState)state { 68 | MJRefreshCheckState 69 | // 设置状态文字 70 | self.stateLabel.text = self.stateTitles[@(state)]; 71 | } 72 | 73 | - (void)placeSubviews { 74 | [super placeSubviews]; 75 | 76 | if (self.stateLabel.hidden) return; 77 | 78 | BOOL noConstrainsOnStatusLabel = self.stateLabel.constraints.count == 0; 79 | CGFloat stateLabelW = ceil(self.stateLabel.font.pointSize); 80 | // 状态 81 | if (noConstrainsOnStatusLabel) { 82 | self.stateLabel.center = CGPointMake(self.mj_w * 0.5, self.mj_h * 0.5); 83 | self.stateLabel.mj_size = CGSizeMake(stateLabelW, self.mj_h) ; 84 | } 85 | } 86 | 87 | @end 88 | -------------------------------------------------------------------------------- /ios/MJRefresh/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefresh.bundle/arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/ios/MJRefresh/MJRefresh.bundle/arrow@2x.png -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefresh.bundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/ios/MJRefresh/MJRefresh.bundle/en.lproj/Localizable.strings -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefresh.bundle/ko.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "MJRefreshHeaderIdleText" = "아래로 당겨 새로고침"; 2 | "MJRefreshHeaderPullingText" = "놓으면 새로고침"; 3 | "MJRefreshHeaderRefreshingText" = "로딩중..."; 4 | 5 | "MJRefreshAutoFooterIdleText" = "탭 또는 위로 당겨 로드함"; 6 | "MJRefreshAutoFooterRefreshingText" = "로딩중..."; 7 | "MJRefreshAutoFooterNoMoreDataText" = "더이상 데이터 없음"; 8 | 9 | "MJRefreshBackFooterIdleText" = "위로 당겨 더 로드 가능"; 10 | "MJRefreshBackFooterPullingText" = "놓으면 더 로드됨."; 11 | "MJRefreshBackFooterRefreshingText" = "로딩중..."; 12 | "MJRefreshBackFooterNoMoreDataText" = "더이상 데이터 없음"; 13 | 14 | "MJRefreshHeaderLastTimeText" = "마지막 업데이트: "; 15 | "MJRefreshHeaderDateTodayText" = "오늘"; 16 | "MJRefreshHeaderNoneLastDateText" = "기록 없음"; 17 | -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefresh.bundle/ru.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/ios/MJRefresh/MJRefresh.bundle/ru.lproj/Localizable.strings -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefresh.bundle/trail_arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/ios/MJRefresh/MJRefresh.bundle/trail_arrow@2x.png -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefresh.bundle/uk.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/ios/MJRefresh/MJRefresh.bundle/uk.lproj/Localizable.strings -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefresh.bundle/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/472647301/react-native-refresh-control/c3a967dc16ca0bb177443b1aa81836feb71465ac/ios/MJRefresh/MJRefresh.bundle/zh-Hans.lproj/Localizable.strings -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefresh.bundle/zh-Hant.lproj/Localizable.strings: -------------------------------------------------------------------------------- 1 | "MJRefreshHeaderIdleText" = "下拉可以刷新"; 2 | "MJRefreshHeaderPullingText" = "鬆開立即刷新"; 3 | "MJRefreshHeaderRefreshingText" = "正在刷新數據中..."; 4 | 5 | "MJRefreshTrailerIdleText" = "滑動查看圖文詳情"; 6 | "MJRefreshTrailerPullingText" = "釋放查看圖文詳情"; 7 | 8 | "MJRefreshAutoFooterIdleText" = "點擊或上拉加載更多"; 9 | "MJRefreshAutoFooterRefreshingText" = "正在加載更多的數據..."; 10 | "MJRefreshAutoFooterNoMoreDataText" = "已經全部加載完畢"; 11 | 12 | "MJRefreshBackFooterIdleText" = "上拉可以加載更多"; 13 | "MJRefreshBackFooterPullingText" = "鬆開立即加載更多"; 14 | "MJRefreshBackFooterRefreshingText" = "正在加載更多的數據..."; 15 | "MJRefreshBackFooterNoMoreDataText" = "已經全部加載完畢"; 16 | 17 | "MJRefreshHeaderLastTimeText" = "最後更新:"; 18 | "MJRefreshHeaderDateTodayText" = "今天"; 19 | "MJRefreshHeaderNoneLastDateText" = "無記錄"; 20 | -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefresh.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | 3 | #import 4 | 5 | #if __has_include() 6 | FOUNDATION_EXPORT double MJRefreshVersionNumber; 7 | FOUNDATION_EXPORT const unsigned char MJRefreshVersionString[]; 8 | 9 | #import 10 | #import 11 | #import 12 | 13 | #import 14 | #import 15 | 16 | #import 17 | #import 18 | #import 19 | #import 20 | 21 | #import 22 | #import 23 | #import 24 | #import 25 | #else 26 | #import "UIScrollView+MJRefresh.h" 27 | #import "UIScrollView+MJExtension.h" 28 | #import "UIView+MJExtension.h" 29 | 30 | #import "MJRefreshNormalHeader.h" 31 | #import "MJRefreshGifHeader.h" 32 | 33 | #import "MJRefreshBackNormalFooter.h" 34 | #import "MJRefreshBackGifFooter.h" 35 | #import "MJRefreshAutoNormalFooter.h" 36 | #import "MJRefreshAutoGifFooter.h" 37 | 38 | #import "MJRefreshNormalTrailer.h" 39 | #import "MJRefreshConfig.h" 40 | #import "NSBundle+MJRefresh.h" 41 | #import "MJRefreshConst.h" 42 | #endif 43 | -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefreshConfig.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshConfig.h 3 | // 4 | // Created by Frank on 2018/11/27. 5 | // Copyright © 2018 小码哥. All rights reserved. 6 | // 7 | 8 | #import 9 | 10 | NS_ASSUME_NONNULL_BEGIN 11 | 12 | @interface MJRefreshConfig : NSObject 13 | 14 | /** 默认使用的语言版本, 默认为 nil. 将随系统的语言自动改变 */ 15 | @property (copy, nonatomic, nullable) NSString *languageCode; 16 | 17 | /** 默认使用的语言资源文件名, 默认为 nil, 即默认的 Localizable.strings. 18 | 19 | - Attention: 文件名不包含后缀.strings 20 | */ 21 | @property (copy, nonatomic, nullable) NSString *i18nFilename; 22 | /** i18n 多语言资源加载自定义 Bundle. 23 | 24 | - Attention: 默认为 nil 采用内置逻辑. 这里设置后将忽略内置逻辑的多语言模式, 采用自定义的多语言 bundle 25 | */ 26 | @property (nonatomic, nullable) NSBundle *i18nBundle; 27 | 28 | /** Singleton Config instance */ 29 | @property (class, nonatomic, readonly) MJRefreshConfig *defaultConfig; 30 | 31 | - (instancetype)init NS_UNAVAILABLE; 32 | + (instancetype)new NS_UNAVAILABLE; 33 | 34 | @end 35 | 36 | NS_ASSUME_NONNULL_END 37 | -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefreshConfig.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshConfig.m 3 | // 4 | // Created by Frank on 2018/11/27. 5 | // Copyright © 2018 小码哥. All rights reserved. 6 | // 7 | 8 | #import "MJRefreshConfig.h" 9 | #import "MJRefreshConst.h" 10 | #import "NSBundle+MJRefresh.h" 11 | 12 | @interface MJRefreshConfig (Bundle) 13 | 14 | + (void)resetLanguageResourceCache; 15 | 16 | @end 17 | 18 | @implementation MJRefreshConfig 19 | 20 | static MJRefreshConfig *mj_RefreshConfig = nil; 21 | 22 | + (instancetype)defaultConfig { 23 | static dispatch_once_t onceToken; 24 | dispatch_once(&onceToken, ^{ 25 | mj_RefreshConfig = [[self alloc] init]; 26 | }); 27 | return mj_RefreshConfig; 28 | } 29 | 30 | - (void)setLanguageCode:(NSString *)languageCode { 31 | if ([languageCode isEqualToString:_languageCode]) { 32 | return; 33 | } 34 | 35 | _languageCode = languageCode; 36 | // 重置语言资源 37 | [MJRefreshConfig resetLanguageResourceCache]; 38 | [NSNotificationCenter.defaultCenter 39 | postNotificationName:MJRefreshDidChangeLanguageNotification object:self]; 40 | } 41 | 42 | @end 43 | -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefreshConst.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | #import 3 | #import 4 | #import 5 | 6 | // 弱引用 7 | #define MJWeakSelf __weak typeof(self) weakSelf = self; 8 | 9 | // 日志输出 10 | #ifdef DEBUG 11 | #define MJRefreshLog(...) NSLog(__VA_ARGS__) 12 | #else 13 | #define MJRefreshLog(...) 14 | #endif 15 | 16 | // 过期提醒 17 | #define MJRefreshDeprecated(DESCRIPTION) __attribute__((deprecated(DESCRIPTION))) 18 | 19 | // 运行时objc_msgSend 20 | #define MJRefreshMsgSend(...) ((void (*)(void *, SEL, UIView *))objc_msgSend)(__VA_ARGS__) 21 | #define MJRefreshMsgTarget(target) (__bridge void *)(target) 22 | 23 | // RGB颜色 24 | #define MJRefreshColor(r, g, b) [UIColor colorWithRed:(r)/255.0 green:(g)/255.0 blue:(b)/255.0 alpha:1.0] 25 | 26 | // 文字颜色 27 | #define MJRefreshLabelTextColor MJRefreshColor(90, 90, 90) 28 | 29 | // 字体大小 30 | #define MJRefreshLabelFont [UIFont boldSystemFontOfSize:14] 31 | 32 | // 常量 33 | UIKIT_EXTERN const CGFloat MJRefreshLabelLeftInset; 34 | UIKIT_EXTERN const CGFloat MJRefreshHeaderHeight; 35 | UIKIT_EXTERN const CGFloat MJRefreshFooterHeight; 36 | UIKIT_EXTERN const CGFloat MJRefreshTrailWidth; 37 | UIKIT_EXTERN const CGFloat MJRefreshFastAnimationDuration; 38 | UIKIT_EXTERN const CGFloat MJRefreshSlowAnimationDuration; 39 | 40 | 41 | UIKIT_EXTERN NSString *const MJRefreshKeyPathContentOffset; 42 | UIKIT_EXTERN NSString *const MJRefreshKeyPathContentSize; 43 | UIKIT_EXTERN NSString *const MJRefreshKeyPathContentInset; 44 | UIKIT_EXTERN NSString *const MJRefreshKeyPathPanState; 45 | 46 | UIKIT_EXTERN NSString *const MJRefreshHeaderLastUpdatedTimeKey; 47 | 48 | UIKIT_EXTERN NSString *const MJRefreshHeaderIdleText; 49 | UIKIT_EXTERN NSString *const MJRefreshHeaderPullingText; 50 | UIKIT_EXTERN NSString *const MJRefreshHeaderRefreshingText; 51 | 52 | UIKIT_EXTERN NSString *const MJRefreshTrailerIdleText; 53 | UIKIT_EXTERN NSString *const MJRefreshTrailerPullingText; 54 | 55 | UIKIT_EXTERN NSString *const MJRefreshAutoFooterIdleText; 56 | UIKIT_EXTERN NSString *const MJRefreshAutoFooterRefreshingText; 57 | UIKIT_EXTERN NSString *const MJRefreshAutoFooterNoMoreDataText; 58 | 59 | UIKIT_EXTERN NSString *const MJRefreshBackFooterIdleText; 60 | UIKIT_EXTERN NSString *const MJRefreshBackFooterPullingText; 61 | UIKIT_EXTERN NSString *const MJRefreshBackFooterRefreshingText; 62 | UIKIT_EXTERN NSString *const MJRefreshBackFooterNoMoreDataText; 63 | 64 | UIKIT_EXTERN NSString *const MJRefreshHeaderLastTimeText; 65 | UIKIT_EXTERN NSString *const MJRefreshHeaderDateTodayText; 66 | UIKIT_EXTERN NSString *const MJRefreshHeaderNoneLastDateText; 67 | 68 | UIKIT_EXTERN NSString *const MJRefreshDidChangeLanguageNotification; 69 | 70 | // 状态检查 71 | #define MJRefreshCheckState \ 72 | MJRefreshState oldState = self.state; \ 73 | if (state == oldState) return; \ 74 | [super setState:state]; 75 | 76 | // 异步主线程执行,不强持有Self 77 | #define MJRefreshDispatchAsyncOnMainQueue(x) \ 78 | __weak typeof(self) weakSelf = self; \ 79 | dispatch_async(dispatch_get_main_queue(), ^{ \ 80 | typeof(weakSelf) self = weakSelf; \ 81 | {x} \ 82 | }); 83 | 84 | /// 替换方法实现 85 | /// @param _fromClass 源类 86 | /// @param _originSelector 源类的 Selector 87 | /// @param _toClass 目标类 88 | /// @param _newSelector 目标类的 Selector 89 | CG_INLINE BOOL MJRefreshExchangeImplementations( 90 | Class _fromClass, SEL _originSelector, 91 | Class _toClass, SEL _newSelector) { 92 | if (!_fromClass || !_toClass) { 93 | return NO; 94 | } 95 | 96 | Method oriMethod = class_getInstanceMethod(_fromClass, _originSelector); 97 | Method newMethod = class_getInstanceMethod(_toClass, _newSelector); 98 | if (!newMethod) { 99 | return NO; 100 | } 101 | 102 | BOOL isAddedMethod = class_addMethod(_fromClass, _originSelector, 103 | method_getImplementation(newMethod), 104 | method_getTypeEncoding(newMethod)); 105 | if (isAddedMethod) { 106 | // 如果 class_addMethod 成功了,说明之前 fromClass 里并不存在 originSelector,所以要用一个空的方法代替它,以避免 class_replaceMethod 后,后续 toClass 的这个方法被调用时可能会 crash 107 | IMP emptyIMP = imp_implementationWithBlock(^(id selfObject) {}); 108 | IMP oriMethodIMP = method_getImplementation(oriMethod) ?: emptyIMP; 109 | const char *oriMethodTypeEncoding = method_getTypeEncoding(oriMethod) ?: "v@:"; 110 | class_replaceMethod(_toClass, _newSelector, oriMethodIMP, oriMethodTypeEncoding); 111 | } else { 112 | method_exchangeImplementations(oriMethod, newMethod); 113 | } 114 | return YES; 115 | } 116 | -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefreshConst.m: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | #import 3 | 4 | const CGFloat MJRefreshLabelLeftInset = 25; 5 | const CGFloat MJRefreshHeaderHeight = 54.0; 6 | const CGFloat MJRefreshFooterHeight = 44.0; 7 | const CGFloat MJRefreshTrailWidth = 60.0; 8 | const CGFloat MJRefreshFastAnimationDuration = 0.25; 9 | const CGFloat MJRefreshSlowAnimationDuration = 0.4; 10 | 11 | 12 | NSString *const MJRefreshKeyPathContentOffset = @"contentOffset"; 13 | NSString *const MJRefreshKeyPathContentInset = @"contentInset"; 14 | NSString *const MJRefreshKeyPathContentSize = @"contentSize"; 15 | NSString *const MJRefreshKeyPathPanState = @"state"; 16 | 17 | NSString *const MJRefreshHeaderLastUpdatedTimeKey = @"MJRefreshHeaderLastUpdatedTimeKey"; 18 | 19 | NSString *const MJRefreshHeaderIdleText = @"MJRefreshHeaderIdleText"; 20 | NSString *const MJRefreshHeaderPullingText = @"MJRefreshHeaderPullingText"; 21 | NSString *const MJRefreshHeaderRefreshingText = @"MJRefreshHeaderRefreshingText"; 22 | 23 | NSString *const MJRefreshTrailerIdleText = @"MJRefreshTrailerIdleText"; 24 | NSString *const MJRefreshTrailerPullingText = @"MJRefreshTrailerPullingText"; 25 | 26 | NSString *const MJRefreshAutoFooterIdleText = @"MJRefreshAutoFooterIdleText"; 27 | NSString *const MJRefreshAutoFooterRefreshingText = @"MJRefreshAutoFooterRefreshingText"; 28 | NSString *const MJRefreshAutoFooterNoMoreDataText = @"MJRefreshAutoFooterNoMoreDataText"; 29 | 30 | NSString *const MJRefreshBackFooterIdleText = @"MJRefreshBackFooterIdleText"; 31 | NSString *const MJRefreshBackFooterPullingText = @"MJRefreshBackFooterPullingText"; 32 | NSString *const MJRefreshBackFooterRefreshingText = @"MJRefreshBackFooterRefreshingText"; 33 | NSString *const MJRefreshBackFooterNoMoreDataText = @"MJRefreshBackFooterNoMoreDataText"; 34 | 35 | NSString *const MJRefreshHeaderLastTimeText = @"MJRefreshHeaderLastTimeText"; 36 | NSString *const MJRefreshHeaderDateTodayText = @"MJRefreshHeaderDateTodayText"; 37 | NSString *const MJRefreshHeaderNoneLastDateText = @"MJRefreshHeaderNoneLastDateText"; 38 | 39 | NSString *const MJRefreshDidChangeLanguageNotification = @"MJRefreshDidChangeLanguageNotification"; 40 | -------------------------------------------------------------------------------- /ios/MJRefresh/NSBundle+MJRefresh.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+MJRefresh.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 16/6/13. 6 | // Copyright © 2016年 小码哥. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface NSBundle (MJRefresh) 14 | + (instancetype)mj_refreshBundle; 15 | + (UIImage *)mj_arrowImage; 16 | + (UIImage *)mj_trailArrowImage; 17 | + (NSString *)mj_localizedStringForKey:(NSString *)key value:(nullable NSString *)value; 18 | + (NSString *)mj_localizedStringForKey:(NSString *)key; 19 | @end 20 | 21 | NS_ASSUME_NONNULL_END 22 | -------------------------------------------------------------------------------- /ios/MJRefresh/NSBundle+MJRefresh.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+MJRefresh.m 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 16/6/13. 6 | // Copyright © 2016年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "NSBundle+MJRefresh.h" 10 | #import "MJRefreshComponent.h" 11 | #import "MJRefreshConfig.h" 12 | 13 | static NSBundle *mj_defaultI18nBundle = nil; 14 | static NSBundle *mj_systemI18nBundle = nil; 15 | 16 | @implementation NSBundle (MJRefresh) 17 | + (instancetype)mj_refreshBundle 18 | { 19 | static NSBundle *refreshBundle = nil; 20 | if (refreshBundle == nil) { 21 | #ifdef SWIFT_PACKAGE 22 | NSBundle *containnerBundle = SWIFTPM_MODULE_BUNDLE; 23 | #else 24 | NSBundle *containnerBundle = [NSBundle bundleForClass:[MJRefreshComponent class]]; 25 | #endif 26 | refreshBundle = [NSBundle bundleWithPath:[containnerBundle pathForResource:@"MJRefresh" ofType:@"bundle"]]; 27 | } 28 | return refreshBundle; 29 | } 30 | 31 | + (UIImage *)mj_arrowImage 32 | { 33 | static UIImage *arrowImage = nil; 34 | if (arrowImage == nil) { 35 | arrowImage = [[UIImage imageWithContentsOfFile:[[self mj_refreshBundle] pathForResource:@"arrow@2x" ofType:@"png"]] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 36 | } 37 | return arrowImage; 38 | } 39 | 40 | + (UIImage *)mj_trailArrowImage { 41 | static UIImage *arrowImage = nil; 42 | if (arrowImage == nil) { 43 | arrowImage = [[UIImage imageWithContentsOfFile:[[self mj_refreshBundle] pathForResource:@"trail_arrow@2x" ofType:@"png"]] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 44 | } 45 | return arrowImage; 46 | } 47 | 48 | + (NSString *)mj_localizedStringForKey:(NSString *)key 49 | { 50 | return [self mj_localizedStringForKey:key value:nil]; 51 | } 52 | 53 | + (NSString *)mj_localizedStringForKey:(NSString *)key value:(NSString *)value 54 | { 55 | NSString *table = MJRefreshConfig.defaultConfig.i18nFilename; 56 | 57 | // 如果没有缓存, 则走初始化逻辑 58 | if (mj_defaultI18nBundle == nil) { 59 | NSString *language = MJRefreshConfig.defaultConfig.languageCode; 60 | // 如果配置中没有配置语言 61 | if (!language) { 62 | language = [NSLocale preferredLanguages].firstObject; 63 | } 64 | NSBundle *bundle = MJRefreshConfig.defaultConfig.i18nBundle; 65 | // 首先优先使用公共配置中的 i18nBundle, 如果为空则使用 mainBundle 66 | bundle = bundle ? bundle : NSBundle.mainBundle; 67 | // 按语言选取语言包 68 | NSString *i18nFolderPath = [bundle pathForResource:language ofType:@"lproj"]; 69 | mj_defaultI18nBundle = [NSBundle bundleWithPath:i18nFolderPath]; 70 | // 检查语言包, 如果没有查找到, 则默认使用 mainBundle 71 | mj_defaultI18nBundle = mj_defaultI18nBundle ? mj_defaultI18nBundle : NSBundle.mainBundle; 72 | 73 | // 获取 MJRefresh 自有的语言包 74 | if (mj_systemI18nBundle == nil) { 75 | mj_systemI18nBundle = [self mj_defaultI18nBundleWithLanguage:language]; 76 | } 77 | } 78 | // 首先在 MJRefresh 内置语言文件中寻找 79 | value = [mj_systemI18nBundle localizedStringForKey:key value:value table:nil]; 80 | // 然后在 MainBundle 对应语言文件中寻找 81 | value = [mj_defaultI18nBundle localizedStringForKey:key value:value table:table]; 82 | return value; 83 | } 84 | 85 | + (NSBundle *)mj_defaultI18nBundleWithLanguage:(NSString *)language { 86 | if ([language hasPrefix:@"en"]) { 87 | language = @"en"; 88 | } else if ([language hasPrefix:@"zh"]) { 89 | if ([language rangeOfString:@"Hans"].location != NSNotFound) { 90 | language = @"zh-Hans"; // 简体中文 91 | } else { // zh-Hant\zh-HK\zh-TW 92 | language = @"zh-Hant"; // 繁體中文 93 | } 94 | } else if ([language hasPrefix:@"ko"]) { 95 | language = @"ko"; 96 | } else if ([language hasPrefix:@"ru"]) { 97 | language = @"ru"; 98 | } else if ([language hasPrefix:@"uk"]) { 99 | language = @"uk"; 100 | } else { 101 | language = @"en"; 102 | } 103 | 104 | // 从MJRefresh.bundle中查找资源 105 | return [NSBundle bundleWithPath:[[NSBundle mj_refreshBundle] pathForResource:language ofType:@"lproj"]]; 106 | } 107 | @end 108 | 109 | @implementation MJRefreshConfig (Bundle) 110 | 111 | + (void)resetLanguageResourceCache { 112 | mj_defaultI18nBundle = nil; 113 | mj_systemI18nBundle = nil; 114 | } 115 | 116 | @end 117 | -------------------------------------------------------------------------------- /ios/MJRefresh/UICollectionViewLayout+MJRefresh.h: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionViewLayout+MJRefresh.h 3 | // 4 | // 该类是用来解决 Footer 在底端加载完成后, 仍停留在原处的 bug. 5 | // 此问题出现在 iOS 14 及以下系统上. 6 | // Reference: https://github.com/CoderMJLee/MJRefresh/issues/1552 7 | // 8 | // Created by jiasong on 2021/11/15. 9 | // Copyright © 2021 小码哥. All rights reserved. 10 | // 11 | 12 | #import 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface UICollectionViewLayout (MJRefresh) 17 | 18 | @end 19 | 20 | NS_ASSUME_NONNULL_END 21 | -------------------------------------------------------------------------------- /ios/MJRefresh/UICollectionViewLayout+MJRefresh.m: -------------------------------------------------------------------------------- 1 | // 2 | // UICollectionViewLayout+MJRefresh.m 3 | // 4 | // 该类是用来解决 Footer 在底端加载完成后, 仍停留在原处的 bug. 5 | // 此问题出现在 iOS 14 及以下系统上. 6 | // Reference: https://github.com/CoderMJLee/MJRefresh/issues/1552 7 | // 8 | // Created by jiasong on 2021/11/15. 9 | // Copyright © 2021 小码哥. All rights reserved. 10 | // 11 | 12 | #import "UICollectionViewLayout+MJRefresh.h" 13 | #import "MJRefreshConst.h" 14 | #import "MJRefreshFooter.h" 15 | #import "UIScrollView+MJRefresh.h" 16 | 17 | @implementation UICollectionViewLayout (MJRefresh) 18 | 19 | + (void)load { 20 | static dispatch_once_t onceToken; 21 | dispatch_once(&onceToken, ^{ 22 | MJRefreshExchangeImplementations(self.class, @selector(finalizeCollectionViewUpdates), 23 | self.class, @selector(mj_finalizeCollectionViewUpdates)); 24 | }); 25 | } 26 | 27 | - (void)mj_finalizeCollectionViewUpdates { 28 | [self mj_finalizeCollectionViewUpdates]; 29 | 30 | __kindof MJRefreshFooter *footer = self.collectionView.mj_footer; 31 | CGSize newSize = self.collectionViewContentSize; 32 | CGSize oldSize = self.collectionView.contentSize; 33 | if (footer != nil && !CGSizeEqualToSize(newSize, oldSize)) { 34 | NSDictionary *changed = @{ 35 | NSKeyValueChangeNewKey: [NSValue valueWithCGSize:newSize], 36 | NSKeyValueChangeOldKey: [NSValue valueWithCGSize:oldSize], 37 | }; 38 | [CATransaction begin]; 39 | [CATransaction setDisableActions:YES]; 40 | [footer scrollViewContentSizeDidChange:changed]; 41 | [CATransaction commit]; 42 | } 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /ios/MJRefresh/UIScrollView+MJExtension.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // UIScrollView+Extension.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 14-5-28. 6 | // Copyright (c) 2014年 小码哥. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface UIScrollView (MJExtension) 14 | @property (readonly, nonatomic) UIEdgeInsets mj_inset; 15 | 16 | @property (assign, nonatomic) CGFloat mj_insetT; 17 | @property (assign, nonatomic) CGFloat mj_insetB; 18 | @property (assign, nonatomic) CGFloat mj_insetL; 19 | @property (assign, nonatomic) CGFloat mj_insetR; 20 | 21 | @property (assign, nonatomic) CGFloat mj_offsetX; 22 | @property (assign, nonatomic) CGFloat mj_offsetY; 23 | 24 | @property (assign, nonatomic) CGFloat mj_contentW; 25 | @property (assign, nonatomic) CGFloat mj_contentH; 26 | @end 27 | 28 | NS_ASSUME_NONNULL_END 29 | -------------------------------------------------------------------------------- /ios/MJRefresh/UIScrollView+MJExtension.m: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // UIScrollView+Extension.m 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 14-5-28. 6 | // Copyright (c) 2014年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "UIScrollView+MJExtension.h" 10 | #import 11 | 12 | #pragma clang diagnostic push 13 | #pragma clang diagnostic ignored "-Wunguarded-availability-new" 14 | 15 | @implementation UIScrollView (MJExtension) 16 | 17 | static BOOL respondsToAdjustedContentInset_; 18 | 19 | + (void)load 20 | { 21 | static dispatch_once_t onceToken; 22 | dispatch_once(&onceToken, ^{ 23 | respondsToAdjustedContentInset_ = [self instancesRespondToSelector:@selector(adjustedContentInset)]; 24 | }); 25 | } 26 | 27 | - (UIEdgeInsets)mj_inset 28 | { 29 | #ifdef __IPHONE_11_0 30 | if (respondsToAdjustedContentInset_) { 31 | return self.adjustedContentInset; 32 | } 33 | #endif 34 | return self.contentInset; 35 | } 36 | 37 | - (void)setMj_insetT:(CGFloat)mj_insetT 38 | { 39 | UIEdgeInsets inset = self.contentInset; 40 | inset.top = mj_insetT; 41 | #ifdef __IPHONE_11_0 42 | if (respondsToAdjustedContentInset_) { 43 | inset.top -= (self.adjustedContentInset.top - self.contentInset.top); 44 | } 45 | #endif 46 | self.contentInset = inset; 47 | } 48 | 49 | - (CGFloat)mj_insetT 50 | { 51 | return self.mj_inset.top; 52 | } 53 | 54 | - (void)setMj_insetB:(CGFloat)mj_insetB 55 | { 56 | UIEdgeInsets inset = self.contentInset; 57 | inset.bottom = mj_insetB; 58 | #ifdef __IPHONE_11_0 59 | if (respondsToAdjustedContentInset_) { 60 | inset.bottom -= (self.adjustedContentInset.bottom - self.contentInset.bottom); 61 | } 62 | #endif 63 | self.contentInset = inset; 64 | } 65 | 66 | - (CGFloat)mj_insetB 67 | { 68 | return self.mj_inset.bottom; 69 | } 70 | 71 | - (void)setMj_insetL:(CGFloat)mj_insetL 72 | { 73 | UIEdgeInsets inset = self.contentInset; 74 | inset.left = mj_insetL; 75 | #ifdef __IPHONE_11_0 76 | if (respondsToAdjustedContentInset_) { 77 | inset.left -= (self.adjustedContentInset.left - self.contentInset.left); 78 | } 79 | #endif 80 | self.contentInset = inset; 81 | } 82 | 83 | - (CGFloat)mj_insetL 84 | { 85 | return self.mj_inset.left; 86 | } 87 | 88 | - (void)setMj_insetR:(CGFloat)mj_insetR 89 | { 90 | UIEdgeInsets inset = self.contentInset; 91 | inset.right = mj_insetR; 92 | #ifdef __IPHONE_11_0 93 | if (respondsToAdjustedContentInset_) { 94 | inset.right -= (self.adjustedContentInset.right - self.contentInset.right); 95 | } 96 | #endif 97 | self.contentInset = inset; 98 | } 99 | 100 | - (CGFloat)mj_insetR 101 | { 102 | return self.mj_inset.right; 103 | } 104 | 105 | - (void)setMj_offsetX:(CGFloat)mj_offsetX 106 | { 107 | CGPoint offset = self.contentOffset; 108 | offset.x = mj_offsetX; 109 | self.contentOffset = offset; 110 | } 111 | 112 | - (CGFloat)mj_offsetX 113 | { 114 | return self.contentOffset.x; 115 | } 116 | 117 | - (void)setMj_offsetY:(CGFloat)mj_offsetY 118 | { 119 | CGPoint offset = self.contentOffset; 120 | offset.y = mj_offsetY; 121 | self.contentOffset = offset; 122 | } 123 | 124 | - (CGFloat)mj_offsetY 125 | { 126 | return self.contentOffset.y; 127 | } 128 | 129 | - (void)setMj_contentW:(CGFloat)mj_contentW 130 | { 131 | CGSize size = self.contentSize; 132 | size.width = mj_contentW; 133 | self.contentSize = size; 134 | } 135 | 136 | - (CGFloat)mj_contentW 137 | { 138 | return self.contentSize.width; 139 | } 140 | 141 | - (void)setMj_contentH:(CGFloat)mj_contentH 142 | { 143 | CGSize size = self.contentSize; 144 | size.height = mj_contentH; 145 | self.contentSize = size; 146 | } 147 | 148 | - (CGFloat)mj_contentH 149 | { 150 | return self.contentSize.height; 151 | } 152 | @end 153 | #pragma clang diagnostic pop 154 | -------------------------------------------------------------------------------- /ios/MJRefresh/UIScrollView+MJRefresh.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // UIScrollView+MJRefresh.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/3/4. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 给ScrollView增加下拉刷新、上拉刷新、 左滑刷新的功能 8 | 9 | #import 10 | #if __has_include() 11 | #import 12 | #else 13 | #import "MJRefreshConst.h" 14 | #endif 15 | 16 | @class MJRefreshHeader, MJRefreshFooter, MJRefreshTrailer; 17 | 18 | NS_ASSUME_NONNULL_BEGIN 19 | 20 | @interface UIScrollView (MJRefresh) 21 | /** 下拉刷新控件 */ 22 | @property (strong, nonatomic, nullable) MJRefreshHeader *mj_header; 23 | @property (strong, nonatomic, nullable) MJRefreshHeader *header MJRefreshDeprecated("使用mj_header"); 24 | /** 上拉刷新控件 */ 25 | @property (strong, nonatomic, nullable) MJRefreshFooter *mj_footer; 26 | @property (strong, nonatomic, nullable) MJRefreshFooter *footer MJRefreshDeprecated("使用mj_footer"); 27 | 28 | /** 左滑刷新控件 */ 29 | @property (strong, nonatomic, nullable) MJRefreshTrailer *mj_trailer; 30 | 31 | #pragma mark - other 32 | - (NSInteger)mj_totalDataCount; 33 | 34 | @end 35 | 36 | NS_ASSUME_NONNULL_END 37 | -------------------------------------------------------------------------------- /ios/MJRefresh/UIScrollView+MJRefresh.m: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // UIScrollView+MJRefresh.m 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 15/3/4. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "UIScrollView+MJRefresh.h" 10 | #import "MJRefreshHeader.h" 11 | #import "MJRefreshFooter.h" 12 | #import "MJRefreshTrailer.h" 13 | #import 14 | 15 | @implementation UIScrollView (MJRefresh) 16 | 17 | #pragma mark - header 18 | static const char MJRefreshHeaderKey = '\0'; 19 | - (void)setMj_header:(MJRefreshHeader *)mj_header 20 | { 21 | if (mj_header != self.mj_header) { 22 | // 删除旧的,添加新的 23 | [self.mj_header removeFromSuperview]; 24 | 25 | if (mj_header) { 26 | [self insertSubview:mj_header atIndex:0]; 27 | } 28 | // 存储新的 29 | objc_setAssociatedObject(self, &MJRefreshHeaderKey, 30 | mj_header, OBJC_ASSOCIATION_RETAIN); 31 | } 32 | } 33 | 34 | - (MJRefreshHeader *)mj_header 35 | { 36 | return objc_getAssociatedObject(self, &MJRefreshHeaderKey); 37 | } 38 | 39 | #pragma mark - footer 40 | static const char MJRefreshFooterKey = '\0'; 41 | - (void)setMj_footer:(MJRefreshFooter *)mj_footer 42 | { 43 | if (mj_footer != self.mj_footer) { 44 | // 删除旧的,添加新的 45 | [self.mj_footer removeFromSuperview]; 46 | if (mj_footer) { 47 | [self insertSubview:mj_footer atIndex:0]; 48 | } 49 | // 存储新的 50 | objc_setAssociatedObject(self, &MJRefreshFooterKey, 51 | mj_footer, OBJC_ASSOCIATION_RETAIN); 52 | } 53 | } 54 | 55 | - (MJRefreshFooter *)mj_footer 56 | { 57 | return objc_getAssociatedObject(self, &MJRefreshFooterKey); 58 | } 59 | 60 | #pragma mark - footer 61 | static const char MJRefreshTrailerKey = '\0'; 62 | - (void)setMj_trailer:(MJRefreshTrailer *)mj_trailer { 63 | if (mj_trailer != self.mj_trailer) { 64 | // 删除旧的,添加新的 65 | [self.mj_trailer removeFromSuperview]; 66 | if (mj_trailer) { 67 | [self insertSubview:mj_trailer atIndex:0]; 68 | } 69 | // 存储新的 70 | objc_setAssociatedObject(self, &MJRefreshTrailerKey, 71 | mj_trailer, OBJC_ASSOCIATION_RETAIN); 72 | } 73 | } 74 | 75 | - (MJRefreshTrailer *)mj_trailer { 76 | return objc_getAssociatedObject(self, &MJRefreshTrailerKey); 77 | } 78 | 79 | #pragma mark - 过期 80 | - (void)setFooter:(MJRefreshFooter *)footer 81 | { 82 | self.mj_footer = footer; 83 | } 84 | 85 | - (MJRefreshFooter *)footer 86 | { 87 | return self.mj_footer; 88 | } 89 | 90 | - (void)setHeader:(MJRefreshHeader *)header 91 | { 92 | self.mj_header = header; 93 | } 94 | 95 | - (MJRefreshHeader *)header 96 | { 97 | return self.mj_header; 98 | } 99 | 100 | #pragma mark - other 101 | - (NSInteger)mj_totalDataCount 102 | { 103 | NSInteger totalCount = 0; 104 | if ([self isKindOfClass:[UITableView class]]) { 105 | UITableView *tableView = (UITableView *)self; 106 | 107 | for (NSInteger section = 0; section < tableView.numberOfSections; section++) { 108 | totalCount += [tableView numberOfRowsInSection:section]; 109 | } 110 | } else if ([self isKindOfClass:[UICollectionView class]]) { 111 | UICollectionView *collectionView = (UICollectionView *)self; 112 | 113 | for (NSInteger section = 0; section < collectionView.numberOfSections; section++) { 114 | totalCount += [collectionView numberOfItemsInSection:section]; 115 | } 116 | } 117 | return totalCount; 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /ios/MJRefresh/UIView+MJExtension.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // UIView+Extension.h 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 14-5-28. 6 | // Copyright (c) 2014年 小码哥. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | NS_ASSUME_NONNULL_BEGIN 12 | 13 | @interface UIView (MJExtension) 14 | @property (assign, nonatomic) CGFloat mj_x; 15 | @property (assign, nonatomic) CGFloat mj_y; 16 | @property (assign, nonatomic) CGFloat mj_w; 17 | @property (assign, nonatomic) CGFloat mj_h; 18 | @property (assign, nonatomic) CGSize mj_size; 19 | @property (assign, nonatomic) CGPoint mj_origin; 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | -------------------------------------------------------------------------------- /ios/MJRefresh/UIView+MJExtension.m: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // UIView+Extension.m 3 | // MJRefresh 4 | // 5 | // Created by MJ Lee on 14-5-28. 6 | // Copyright (c) 2014年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "UIView+MJExtension.h" 10 | 11 | @implementation UIView (MJExtension) 12 | - (void)setMj_x:(CGFloat)mj_x 13 | { 14 | CGRect frame = self.frame; 15 | frame.origin.x = mj_x; 16 | self.frame = frame; 17 | } 18 | 19 | - (CGFloat)mj_x 20 | { 21 | return self.frame.origin.x; 22 | } 23 | 24 | - (void)setMj_y:(CGFloat)mj_y 25 | { 26 | CGRect frame = self.frame; 27 | frame.origin.y = mj_y; 28 | self.frame = frame; 29 | } 30 | 31 | - (CGFloat)mj_y 32 | { 33 | return self.frame.origin.y; 34 | } 35 | 36 | - (void)setMj_w:(CGFloat)mj_w 37 | { 38 | CGRect frame = self.frame; 39 | frame.size.width = mj_w; 40 | self.frame = frame; 41 | } 42 | 43 | - (CGFloat)mj_w 44 | { 45 | return self.frame.size.width; 46 | } 47 | 48 | - (void)setMj_h:(CGFloat)mj_h 49 | { 50 | CGRect frame = self.frame; 51 | frame.size.height = mj_h; 52 | self.frame = frame; 53 | } 54 | 55 | - (CGFloat)mj_h 56 | { 57 | return self.frame.size.height; 58 | } 59 | 60 | - (void)setMj_size:(CGSize)mj_size 61 | { 62 | CGRect frame = self.frame; 63 | frame.size = mj_size; 64 | self.frame = frame; 65 | } 66 | 67 | - (CGSize)mj_size 68 | { 69 | return self.frame.size; 70 | } 71 | 72 | - (void)setMj_origin:(CGPoint)mj_origin 73 | { 74 | CGRect frame = self.frame; 75 | frame.origin = mj_origin; 76 | self.frame = frame; 77 | } 78 | 79 | - (CGPoint)mj_origin 80 | { 81 | return self.frame.origin; 82 | } 83 | @end 84 | -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefresh.h: -------------------------------------------------------------------------------- 1 | ../MJRefresh.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshAutoFooter.h: -------------------------------------------------------------------------------- 1 | ../Base/MJRefreshAutoFooter.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshAutoGifFooter.h: -------------------------------------------------------------------------------- 1 | ../Custom/Footer/Auto/MJRefreshAutoGifFooter.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshAutoNormalFooter.h: -------------------------------------------------------------------------------- 1 | ../Custom/Footer/Auto/MJRefreshAutoNormalFooter.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshAutoStateFooter.h: -------------------------------------------------------------------------------- 1 | ../Custom/Footer/Auto/MJRefreshAutoStateFooter.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshBackFooter.h: -------------------------------------------------------------------------------- 1 | ../Base/MJRefreshBackFooter.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshBackGifFooter.h: -------------------------------------------------------------------------------- 1 | ../Custom/Footer/Back/MJRefreshBackGifFooter.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshBackNormalFooter.h: -------------------------------------------------------------------------------- 1 | ../Custom/Footer/Back/MJRefreshBackNormalFooter.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshBackStateFooter.h: -------------------------------------------------------------------------------- 1 | ../Custom/Footer/Back/MJRefreshBackStateFooter.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshComponent.h: -------------------------------------------------------------------------------- 1 | ../Base/MJRefreshComponent.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshConfig.h: -------------------------------------------------------------------------------- 1 | ../MJRefreshConfig.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshConst.h: -------------------------------------------------------------------------------- 1 | ../MJRefreshConst.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshFooter.h: -------------------------------------------------------------------------------- 1 | ../Base/MJRefreshFooter.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshGifHeader.h: -------------------------------------------------------------------------------- 1 | ../Custom/Header/MJRefreshGifHeader.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshHeader.h: -------------------------------------------------------------------------------- 1 | ../Base/MJRefreshHeader.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshNormalHeader.h: -------------------------------------------------------------------------------- 1 | ../Custom/Header/MJRefreshNormalHeader.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshNormalTrailer.h: -------------------------------------------------------------------------------- 1 | ../Custom/Trailer/MJRefreshNormalTrailer.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshStateHeader.h: -------------------------------------------------------------------------------- 1 | ../Custom/Header/MJRefreshStateHeader.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshStateTrailer.h: -------------------------------------------------------------------------------- 1 | ../Custom/Trailer/MJRefreshStateTrailer.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/MJRefreshTrailer.h: -------------------------------------------------------------------------------- 1 | ../Base/MJRefreshTrailer.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/NSBundle+MJRefresh.h: -------------------------------------------------------------------------------- 1 | ../NSBundle+MJRefresh.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/UIScrollView+MJExtension.h: -------------------------------------------------------------------------------- 1 | ../UIScrollView+MJExtension.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/UIScrollView+MJRefresh.h: -------------------------------------------------------------------------------- 1 | ../UIScrollView+MJRefresh.h -------------------------------------------------------------------------------- /ios/MJRefresh/include/UIView+MJExtension.h: -------------------------------------------------------------------------------- 1 | ../UIView+MJExtension.h -------------------------------------------------------------------------------- /ios/RNByronCustomHeader.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface RNByronCustomHeader : RCTViewManager 4 | @end -------------------------------------------------------------------------------- /ios/RNByronCustomHeader.m: -------------------------------------------------------------------------------- 1 | #import "RNByronCustomHeader.h" 2 | 3 | @implementation RNByronCustomHeader 4 | 5 | RCT_EXPORT_MODULE(RNByronCustomHeader) 6 | 7 | - (UIView *)view 8 | { 9 | return [[UIView alloc] init]; 10 | } 11 | 12 | @end -------------------------------------------------------------------------------- /ios/RNByronRefreshControl.h: -------------------------------------------------------------------------------- 1 | // RNByronRefreshControl.h 2 | 3 | #import 4 | 5 | @interface RNByronRefreshControl : RCTViewManager 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /ios/RNByronRefreshControl.m: -------------------------------------------------------------------------------- 1 | // RNByronRefreshControl.m 2 | 3 | #import "RNByronRefreshControl.h" 4 | #import "RCTRefreshableProtocol.h" 5 | #import "RNByronRefreshHeader.h" 6 | 7 | @implementation RNByronRefreshControl 8 | 9 | RCT_EXPORT_MODULE() 10 | 11 | - (UIView *)view { 12 | return [[RNByronRefreshHeader alloc] init]; 13 | } 14 | 15 | RCT_EXPORT_VIEW_PROPERTY(refreshing, BOOL) 16 | RCT_EXPORT_VIEW_PROPERTY(height, NSInteger) 17 | RCT_EXPORT_VIEW_PROPERTY(onChangeState, RCTDirectEventBlock) 18 | RCT_EXPORT_VIEW_PROPERTY(onChangeOffset, RCTDirectEventBlock) 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /ios/RNByronRefreshControl.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/RNByronRefreshControl.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/RNByronRefreshHeader.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #import 4 | #import 5 | #import "MJRefresh.h" 6 | 7 | NS_ASSUME_NONNULL_BEGIN 8 | 9 | @interface RNByronRefreshHeader : MJRefreshHeader 10 | 11 | @property (nonatomic, copy) RCTDirectEventBlock onChangeState; 12 | @property (nonatomic, copy) RCTDirectEventBlock onChangeOffset; 13 | 14 | @end 15 | 16 | NS_ASSUME_NONNULL_END 17 | -------------------------------------------------------------------------------- /ios/RNByronRefreshHeader.m: -------------------------------------------------------------------------------- 1 | #import "RNByronRefreshHeader.h" 2 | #import 3 | #import "RCTUtils.h" 4 | 5 | @interface RNByronRefreshHeader () { 6 | float offset; 7 | } 8 | @end 9 | 10 | @implementation RNByronRefreshHeader 11 | 12 | - (instancetype)init { 13 | self = [super init]; 14 | return self; 15 | } 16 | 17 | RCT_NOT_IMPLEMENTED(-(instancetype)initWithCoder : (NSCoder *)aDecoder) 18 | 19 | - (void)setRefreshing:(BOOL)refreshing { 20 | if (refreshing) { 21 | [self beginRefreshing]; 22 | } else { 23 | [self endRefreshing]; 24 | } 25 | } 26 | 27 | - (void)setHeight:(NSInteger *)height { 28 | self.mj_h = (long) height; 29 | } 30 | 31 | #pragma mark - 重写方法 32 | #pragma mark 在这里做一些初始化配置(比如添加子控件) 33 | - (void)prepare { 34 | [super prepare]; 35 | } 36 | 37 | #pragma mark 在这里设置子控件的位置和尺寸 38 | - (void)placeSubviews { 39 | [super placeSubviews]; 40 | } 41 | 42 | #pragma mark 监听scrollView的contentOffset改变 43 | - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change { 44 | [super scrollViewContentOffsetDidChange:change]; 45 | if (change && [change.allKeys containsObject:@"new"]) { 46 | CGPoint point = [[change objectForKey:@"new"] CGPointValue]; 47 | if(_onChangeOffset) { 48 | if (point.y > 0) { 49 | // 与android保持一致 避免为0之后多次通知 50 | if (offset != 0) { 51 | offset = 0; 52 | _onChangeOffset(@{@"offset": @(offset)}); 53 | } 54 | } else { 55 | // 取绝对值, 与android保持一致 56 | offset = fabs(point.y); 57 | _onChangeOffset(@{@"offset": @(offset)}); 58 | } 59 | } 60 | } 61 | } 62 | 63 | #pragma mark 监听scrollView的contentSize改变 64 | - (void)scrollViewContentSizeDidChange:(NSDictionary *)change { 65 | [super scrollViewContentSizeDidChange:change]; 66 | } 67 | 68 | #pragma mark 监听scrollView的拖拽状态改变 69 | - (void)scrollViewPanStateDidChange:(NSDictionary *)change { 70 | [super scrollViewPanStateDidChange:change]; 71 | 72 | } 73 | 74 | #pragma mark 监听控件的刷新状态 75 | - (void)setState:(MJRefreshState)state { 76 | MJRefreshCheckState; 77 | if (_onChangeState) { 78 | _onChangeState(@{@"state": @(state)}); 79 | } 80 | } 81 | 82 | #pragma mark 监听拖拽比例(控件被拖出来的比例) 83 | - (void)setPullingPercent:(CGFloat)pullingPercent { 84 | [super setPullingPercent:pullingPercent]; 85 | // 这里获取的offset不准确,而且下滑在上滑会导致值不更新 86 | } 87 | 88 | @synthesize refreshing; 89 | 90 | @synthesize onRefresh; 91 | 92 | @end 93 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@byron-react-native/refresh-control", 3 | "title": "React Native Refresh Control", 4 | "version": "1.2.5", 5 | "description": "React Native Refresh Control", 6 | "main": "index.js", 7 | "private": false, 8 | "files": [ 9 | "README.md", 10 | "android", 11 | "index.js", 12 | "index.d.ts", 13 | "assets", 14 | "ios", 15 | "react-native-refresh-control.podspec", 16 | "!android/**/build/*" 17 | ], 18 | "scripts": { 19 | "test": "echo \"Error: no test specified\" && exit 1", 20 | "push": "npm publish --access public" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git+https://github.com/472647301/react-native-refresh-control.git", 25 | "baseUrl": "https://github.com/472647301/react-native-refresh-control" 26 | }, 27 | "keywords": [ 28 | "react-native" 29 | ], 30 | "author": { 31 | "name": "byron", 32 | "email": "byron.zhuwenbo@gmail.com" 33 | }, 34 | "license": "MIT", 35 | "licenseFilename": "LICENSE", 36 | "readmeFilename": "README.md", 37 | "peerDependencies": { 38 | "react": ">=16.8.1", 39 | "react-native": ">=0.60.0-rc.0 <1.0.x" 40 | }, 41 | "devDependencies": { 42 | "react": "^16.9.0", 43 | "react-native": "^0.61.5" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /react-native-refresh-control.podspec: -------------------------------------------------------------------------------- 1 | # react-native-refresh-control.podspec 2 | 3 | require "json" 4 | 5 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 6 | 7 | Pod::Spec.new do |s| 8 | s.name = "react-native-refresh-control" 9 | s.version = package["version"] 10 | s.summary = package["description"] 11 | s.description = <<-DESC 12 | react-native-refresh-control 13 | DESC 14 | s.homepage = "https://github.com/472647301/react-native-refresh-control" 15 | # brief license entry: 16 | s.license = "MIT" 17 | # optional - use expanded license entry instead: 18 | # s.license = { :type => "MIT", :file => "LICENSE" } 19 | s.authors = { "byron" => "byron.zhuwenbo@gmail.com" } 20 | s.platforms = { :ios => "9.0" } 21 | s.source = { :git => "https://github.com/472647301/react-native-refresh-control.git", :tag => "#{s.version}" } 22 | 23 | s.source_files = "ios/**/*.{h,c,cc,cpp,m,mm,swift}" 24 | s.requires_arc = true 25 | 26 | s.dependency "React" 27 | # ... 28 | # s.dependency "..." 29 | end 30 | 31 | --------------------------------------------------------------------------------