├── .gitattributes ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── android ├── README.md ├── build.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── lmy │ │ ├── header │ │ ├── AnyHeader.java │ │ ├── AnyHeaderManager.java │ │ ├── ClassicsHeaderManager.java │ │ ├── DefaultHeader.java │ │ ├── DefaultHeaderMananger.java │ │ ├── MaterialHeaderManager.java │ │ └── StoreHouseHeaderManager.java │ │ └── smartrefreshlayout │ │ ├── Events.java │ │ ├── HeaderType.java │ │ ├── RCTSpinnerStyleModule.java │ │ ├── ReactSmartRefreshLayout.java │ │ ├── SmartRefreshLayoutManager.java │ │ ├── SmartRefreshLayoutPackage.java │ │ └── SpinnerStyleConstants.java │ └── res │ └── values │ ├── ids.xml │ └── strings.xml ├── 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 │ ├── 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 │ ├── MJRefresh.bundle │ │ ├── arrow@2x.png │ │ ├── en.lproj │ │ │ └── Localizable.strings │ │ ├── zh-Hans.lproj │ │ │ └── Localizable.strings │ │ └── zh-Hant.lproj │ │ │ └── Localizable.strings │ ├── MJRefresh.h │ ├── MJRefreshConst.h │ ├── MJRefreshConst.m │ ├── NSBundle+MJRefresh.h │ ├── NSBundle+MJRefresh.m │ ├── UIScrollView+MJExtension.h │ ├── UIScrollView+MJExtension.m │ ├── UIScrollView+MJRefresh.h │ ├── UIScrollView+MJRefresh.m │ ├── UIView+MJExtension.h │ └── UIView+MJExtension.m ├── RCTMJRefreshHeader │ ├── RCTMJRefreshHeader.h │ ├── RCTMJRefreshHeader.m │ ├── RCTMJRefreshViewManager.m │ ├── RCTMJScrollContentShadowView.h │ ├── RCTMJScrollContentShadowView.m │ ├── RCTMJScrollContentView.h │ ├── RCTMJScrollContentView.m │ ├── RCTMJScrollContentViewMananger.m │ ├── RCTMJScrollView.h │ ├── RCTMJScrollView.m │ ├── RCTMJScrollViewManager.h │ ├── RCTMJScrollViewManager.m │ └── UIView+Private1.h ├── RefreshControlEnrichment.xcodeproj │ └── project.pbxproj └── RefreshControlEnrichment.xcworkspace │ └── contents.xcworkspacedata ├── package.json ├── react-native-refresh-control-enrichment.podspec └── src ├── androidJS ├── AnyHeader.js ├── DefaultHeader.js ├── SmartRefreshControl.js └── Util.js ├── file ├── androidVideo.gif └── iosVideo.gif ├── fusion.js ├── iosJS ├── MJRefresh.js └── MJScrollView.js └── util └── date.js /.gitattributes: -------------------------------------------------------------------------------- 1 | *.pbxproj -text 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.aar 4 | *.ap_ 5 | *.aab 6 | 7 | # Files for the ART/Dalvik VM 8 | *.dex 9 | 10 | # Java class files 11 | *.class 12 | 13 | # Generated files 14 | bin/ 15 | gen/ 16 | out/ 17 | # Uncomment the following line in case you need and you don't have the release build type files in your app 18 | # release/ 19 | 20 | # Gradle files 21 | .gradle/ 22 | build/ 23 | 24 | # Local configuration file (sdk path, etc) 25 | local.properties 26 | 27 | # Proguard folder generated by Eclipse 28 | proguard/ 29 | 30 | # Log Files 31 | *.log 32 | 33 | # Android Studio Navigation editor temp files 34 | .navigation/ 35 | 36 | # Android Studio captures folder 37 | captures/ 38 | 39 | # IntelliJ 40 | *.iml 41 | .idea/workspace.xml 42 | .idea/tasks.xml 43 | .idea/gradle.xml 44 | .idea/assetWizardSettings.xml 45 | .idea/dictionaries 46 | .idea/libraries 47 | # Android Studio 3 in .gitignore file. 48 | .idea/caches 49 | .idea/modules.xml 50 | # Comment next line if keeping position of elements in Navigation Editor is relevant for you 51 | .idea/navEditor.xml 52 | 53 | # Keystore files 54 | # Uncomment the following lines if you do not want to check your keystore files in. 55 | #*.jks 56 | #*.keystore 57 | 58 | # External native build folder generated in Android Studio 2.2 and later 59 | .externalNativeBuild 60 | .cxx/ 61 | 62 | # Google Services (e.g. APIs or Firebase) 63 | # google-services.json 64 | 65 | # Freeline 66 | freeline.py 67 | freeline/ 68 | freeline_project_description.json 69 | 70 | # fastlane 71 | fastlane/report.xml 72 | fastlane/Preview.html 73 | fastlane/screenshots 74 | fastlane/test_output 75 | fastlane/readme.md 76 | 77 | # Version control 78 | vcs.xml 79 | 80 | # lint 81 | lint/intermediates/ 82 | lint/generated/ 83 | lint/outputs/ 84 | lint/tmp/ 85 | # lint/reports/ 86 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 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 | # node.js 25 | # 26 | node_modules/ 27 | npm-debug.log 28 | 29 | # Don't publish example apps 30 | example/ 31 | # .idea 32 | 33 | .idea/ 34 | .gradle/ 35 | pic 36 | /src/file/ 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 jszh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## react-native-refresh-control-enrichment 2 | [![npm version](https://badge.fury.io/js/react-native-refresh-control-enrichment.svg)](https://badge.fury.io/js/react-native-refresh-control-enrichment) 3 | 4 | [![Anurag's GitHub stats](https://github-readme-stats.vercel.app/api?username=gitSirzh)](https://github.com/gitSirzh/react-native-refresh-control-enrichment) 5 | 6 | Native Refresh 7 | 8 | ## Android 9 | ![](https://github.com/gitSirzh/react-native-refresh-control-enrichment/blob/master/src/file/androidVideo.gif) 10 | 11 | ## IOS 12 | ![](https://github.com/gitSirzh/react-native-refresh-control-enrichment/blob/master/src/file/iosVideo.gif) 13 | 14 | ## Using npm 15 | 16 | ```shell 17 | npm install react-native-refresh-control-enrichment --save 18 | ``` 19 | 20 | or using yarn: 21 | 22 | ```shell 23 | yarn add react-native-refresh-control-enrichment 24 | ``` 25 | 26 | ## Installation 27 | 28 | #### react-native version >= 0.61.5 29 | After that, we need to install the dependencies to use the project on iOS(you can skip this part, if you are using this on Android) 30 | Now run:```cd ios && pod install``` 31 | 32 | ## Using 33 | ```javascript 34 | import ZHRefreshControl, {ZHScrollView} from 'react-native-refresh-control-enrichment'; 35 | 36 | this._finishRefresh = r} 41 | headerHeight={80} // headerHeight 42 | headerBackgroundColor={'#e6e6e6'} // headerBackgroundColor and headerBackgroundImage, do not combine 43 | onRefresh={()=>{ 44 | console.log('======== Refreshing ========'); 45 | setTimeout(()=>{ 46 | // Refreshing End 47 | this._finishRefresh.finishRefresh(); 48 | console.log('======== Refreshing End ========'); 49 | },2000) 50 | }} 51 | /> 52 | } 53 | > 54 | 55 | ⬇️ down 😊 56 | 57 | 58 | ``` 59 | ## Using In FlatList 60 | ```javascript 61 | import {FlatList} from 'react-native'; 62 | import ZHRefreshControl, {ZHScrollView} from 'react-native-refresh-control-enrichment'; 63 | 64 | ( 67 | this._finishRefresh = r} 72 | headerHeight={80} // headerHeight 73 | headerBackgroundColor={'#e6e6e6'} // headerBackgroundColor and headerBackgroundImage, do not combine 74 | onRefresh={()=>{ 75 | console.log('======== Refreshing ========'); 76 | setTimeout(()=>{ 77 | // Refreshing End 78 | this._finishRefresh.finishRefresh(); 79 | console.log('======== Refreshing End ========'); 80 | },2000) 81 | }} 82 | /> 83 | } 84 | {...props} 85 | /> 86 | )} 87 | keyExtractor={(item, key) => key.toString()} 88 | renderItem={({item, index}) => ( 89 | 90 | )} 91 | /> 92 | ``` 93 | 94 | ## Documentation 95 | 96 | ## Props 97 | 98 | ### `onRefresh` 99 | 100 | 刷新时触发 101 | 102 | | Type | Required | 103 | | ---- | -------- | 104 | | function | Yes | 105 | 106 | --- 107 | 108 | ### `refreshState` 109 | 110 | 刷新时状态 111 | 112 | | Type | Required | 113 | | ---- | -------- | 114 | | function | Yes | 115 | 116 | --- 117 | 118 | ### `loadingView` 119 | 120 | 加载中View 121 | 122 | | Type | Default | 123 | | ---- | -------- | 124 | | element | Yes | 125 | 126 | --- 127 | 128 | ### `headerHeight` 129 | 130 | 头部高度 131 | 132 | | Type | Default | 133 | | ---- | -------- | 134 | | number | 60 | 135 | 136 | --- 137 | 138 | ### `centerTop` 139 | 140 | 内容距离顶部高度(一般为状态栏高度) 141 | 142 | | Type | Default | 143 | | ---- | -------- | 144 | | number | 0 | 145 | 146 | --- 147 | 148 | ### `headerBackgroundColor` 149 | 150 | 头部背景色 151 | 152 | | Type | Default | 153 | | ---- | -------- | 154 | | string | #ffffff | 155 | 156 | --- 157 | 158 | ### `headerBackgroundImage` 159 | 160 | 头部背景图片 161 | 162 | | Type | Required | 163 | | ---- | -------- | 164 | | url | Yes | 165 | 166 | --- 167 | 168 | ### `showText` 169 | 170 | 展示刷新状态 171 | 172 | | Type | Default | 173 | | ---- | -------- | 174 | | boolean | Yes | 175 | 176 | --- 177 | 178 | ### `headerTitleStyle` 179 | 180 | 刷新状态字体样式 181 | 182 | | Type | Default | 183 | | ---- | -------- | 184 | | style | Yes | 185 | 186 | --- 187 | 188 | ### `showDate` 189 | 190 | 展示刷新时间 191 | 192 | | Type | Default | 193 | | ---- | -------- | 194 | | boolean | Yes | 195 | 196 | --- 197 | 198 | ### `headerDateStyle` 199 | 200 | 更新时间字体样式 201 | 202 | | Type | Default | 203 | | ---- | -------- | 204 | | style | Yes | 205 | 206 | --- 207 | 208 | ### `titleArray` 209 | 210 | 自定义提示状态,注:数量需要为四个 211 | 212 | | Type | Default | 213 | | ---- | -------- | 214 | | array | yes | 215 | 216 | --- 217 | 218 | ### `pullView` 219 | 220 | 下拉 LoadingView 221 | 222 | | Type | Default | 223 | | ---- | -------- | 224 | | element | Yes | 225 | 226 | --- 227 | 228 | ### `releaseView` 229 | 230 | 释放 LoadingView 231 | 232 | | Type | Default | 233 | | ---- | -------- | 234 | | element | Yes | 235 | 236 | --- 237 | 238 | ### `successView` 239 | 240 | 成功 LoadingView 241 | 242 | | Type | Default | 243 | | ---- | -------- | 244 | | element | Yes | 245 | 246 | --- 247 | 248 | ## Reference library: 249 | #### Android: 250 | https://github.com/react-native-studio/react-native-SmartRefreshLayout 251 | #### iOS: 252 | https://github.com/react-native-studio/react-native-MJRefresh 253 | -------------------------------------------------------------------------------- /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", 28) 9 | buildToolsVersion safeExtGet("buildToolsVersion", "28.0.1") 10 | 11 | defaultConfig { 12 | minSdkVersion safeExtGet("minSdkVersion", 16) 13 | targetSdkVersion safeExtGet("targetSdkVersion", 28) 14 | versionCode 1 15 | versionName "1.0" 16 | } 17 | 18 | lintOptions { 19 | abortOnError false 20 | } 21 | 22 | } 23 | 24 | dependencies { 25 | implementation "com.scwang.smartrefresh:SmartRefreshLayout:1.0.5.1" 26 | implementation "com.scwang.smartrefresh:SmartRefreshHeader:1.0.5.1" 27 | implementation "com.facebook.react:react-native:+" 28 | } 29 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/src/main/java/com/lmy/header/AnyHeader.java: -------------------------------------------------------------------------------- 1 | package com.lmy.header; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.BitmapDrawable; 5 | import androidx.annotation.ColorInt; 6 | import androidx.annotation.NonNull; 7 | import android.view.View; 8 | 9 | import com.facebook.react.views.view.ReactViewGroup; 10 | import com.scwang.smartrefresh.layout.api.RefreshHeader; 11 | import com.scwang.smartrefresh.layout.api.RefreshKernel; 12 | import com.scwang.smartrefresh.layout.api.RefreshLayout; 13 | import com.scwang.smartrefresh.layout.constant.RefreshState; 14 | import com.scwang.smartrefresh.layout.constant.SpinnerStyle; 15 | import com.scwang.smartrefresh.layout.util.DensityUtil; 16 | 17 | /**anyview 18 | * Created by painter.g on 2018/3/9. 19 | */ 20 | 21 | public class AnyHeader extends ReactViewGroup implements RefreshHeader { 22 | private RefreshKernel mRefreshKernel; 23 | private int mBackgroundColor; 24 | private Integer mPrimaryColor; 25 | private SpinnerStyle mSpinnerStyle = SpinnerStyle.Translate; 26 | 27 | public AnyHeader(Context context) { 28 | super(context); 29 | initView(context); 30 | } 31 | 32 | @Override 33 | public void onInitialized(@NonNull RefreshKernel kernel, int height, int extendHeight) { 34 | mRefreshKernel = kernel; 35 | mRefreshKernel.requestDrawBackgroundForHeader(mBackgroundColor); 36 | } 37 | 38 | private void initView(Context context) { 39 | setMinimumHeight(DensityUtil.dp2px(60)); 40 | } 41 | 42 | public void setView(View v){ 43 | addView(v); 44 | } 45 | 46 | @NonNull 47 | public View getView() { 48 | return this;//真实的视图就是自己,不能返回null 49 | } 50 | 51 | @Override 52 | public SpinnerStyle getSpinnerStyle() { 53 | return this.mSpinnerStyle;//指定为平移,不能null 54 | } 55 | 56 | /** 57 | * 设置主题色 58 | * @param colors 59 | */ 60 | @Override 61 | public void setPrimaryColors(int... colors) { 62 | if(colors.length>0) { 63 | if (!(getBackground() instanceof BitmapDrawable) && mPrimaryColor == null) { 64 | setPrimaryColor(colors[0]); 65 | mPrimaryColor = null; 66 | } 67 | } 68 | } 69 | 70 | public AnyHeader setPrimaryColor(@ColorInt int primaryColor) { 71 | mBackgroundColor = mPrimaryColor =primaryColor; 72 | if (mRefreshKernel != null) { 73 | mRefreshKernel.requestDrawBackgroundForHeader(mPrimaryColor); 74 | } 75 | return this; 76 | } 77 | 78 | public AnyHeader setSpinnerStyle(SpinnerStyle style){ 79 | this.mSpinnerStyle = style; 80 | return this; 81 | } 82 | 83 | @Override 84 | public void onPulling(float percent, int offset, int height, int extendHeight) { 85 | 86 | } 87 | 88 | @Override 89 | public void onReleasing(float percent, int offset, int height, int extendHeight) { 90 | 91 | } 92 | 93 | @Override 94 | public void onReleased(RefreshLayout refreshLayout, int height, int extendHeight) { 95 | 96 | } 97 | 98 | @Override 99 | public void onStartAnimator(@NonNull RefreshLayout refreshLayout, int height, int extendHeight) { 100 | 101 | } 102 | 103 | @Override 104 | public int onFinish(@NonNull RefreshLayout refreshLayout, boolean success) { 105 | return 200;//延迟200毫秒之后再弹回 106 | } 107 | 108 | @Override 109 | public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { 110 | 111 | } 112 | 113 | @Override 114 | public boolean isSupportHorizontalDrag() { 115 | return false; 116 | } 117 | 118 | @Override 119 | public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { 120 | 121 | } 122 | 123 | /*@Override 124 | protected void onFinishInflate() { 125 | int childCount = getChildCount(); 126 | for(int i=0;i { 19 | @Override 20 | public String getName() { 21 | return "RCTAnyHeader"; 22 | } 23 | 24 | @Override 25 | protected AnyHeader createViewInstance(ThemedReactContext reactContext) { 26 | return new AnyHeader(reactContext); 27 | } 28 | 29 | /** 30 | * 设置主调色 31 | * @param view 32 | * @param primaryColor 33 | */ 34 | @ReactProp(name = "primaryColor") 35 | public void setPrimaryColor(AnyHeader view, String primaryColor){ 36 | if(primaryColor!=null && !"".equals(primaryColor)){ 37 | view.setPrimaryColor(Color.parseColor(primaryColor)); 38 | } 39 | } 40 | 41 | /** 42 | * 设置spinnerStyle 43 | * @param view 44 | * @param spinnerStyle 45 | */ 46 | @ReactProp(name = "spinnerStyle") 47 | public void setSpinnerStyle(AnyHeader view,String spinnerStyle){ 48 | view.setSpinnerStyle(SpinnerStyleConstants.SpinnerStyleMap.get(spinnerStyle)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /android/src/main/java/com/lmy/header/ClassicsHeaderManager.java: -------------------------------------------------------------------------------- 1 | package com.lmy.header; 2 | 3 | import android.graphics.Color; 4 | 5 | import com.facebook.react.uimanager.SimpleViewManager; 6 | import com.facebook.react.uimanager.ThemedReactContext; 7 | import com.facebook.react.uimanager.annotations.ReactProp; 8 | import com.scwang.smartrefresh.layout.header.ClassicsHeader; 9 | 10 | /** 11 | * Created by painter.g on 2018/3/7. 12 | */ 13 | 14 | public class ClassicsHeaderManager extends SimpleViewManager { 15 | @Override 16 | public String getName() { 17 | return "RCTClassicsHeader"; 18 | } 19 | 20 | @Override 21 | protected ClassicsHeader createViewInstance(ThemedReactContext reactContext) { 22 | return new ClassicsHeader(reactContext); 23 | } 24 | 25 | /** 26 | * 设置主题颜色 27 | * @param view 28 | * @param primaryColor 29 | */ 30 | @ReactProp(name = "primaryColor") 31 | public void setPrimaryColor(ClassicsHeader view,String primaryColor){ 32 | view.setPrimaryColor(Color.parseColor(primaryColor)); 33 | } 34 | 35 | /** 36 | * 设置强调颜色 37 | * @param view 38 | * @param accentColor 39 | */ 40 | @ReactProp(name = "accentColor") 41 | public void setAccentColor(ClassicsHeader view,String accentColor){ 42 | view.setAccentColor(Color.parseColor(accentColor)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /android/src/main/java/com/lmy/header/DefaultHeader.java: -------------------------------------------------------------------------------- 1 | package com.lmy.header; 2 | 3 | import android.content.Context; 4 | import androidx.annotation.ColorInt; 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | import android.util.AttributeSet; 8 | import android.view.Gravity; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.ImageView; 12 | import android.widget.LinearLayout; 13 | import android.widget.RelativeLayout; 14 | import android.widget.TextView; 15 | 16 | import com.facebook.drawee.components.DeferredReleaser; 17 | import com.lmy.smartrefreshlayout.R; 18 | import com.scwang.smartrefresh.layout.api.RefreshHeader; 19 | import com.scwang.smartrefresh.layout.api.RefreshKernel; 20 | import com.scwang.smartrefresh.layout.api.RefreshLayout; 21 | import com.scwang.smartrefresh.layout.constant.RefreshState; 22 | import com.scwang.smartrefresh.layout.constant.SpinnerStyle; 23 | import com.scwang.smartrefresh.layout.internal.ProgressDrawable; 24 | import com.scwang.smartrefresh.layout.internal.pathview.PathsView; 25 | import com.scwang.smartrefresh.layout.util.DensityUtil; 26 | 27 | /** 28 | * Created by painter.g on 2018/3/12. 29 | */ 30 | 31 | public class DefaultHeader extends RelativeLayout implements RefreshHeader { 32 | private TextView mHeaderText;//标题文本 33 | private PathsView mArrowView;//下拉箭头 34 | private ImageView mProgressView;//刷新动画视图 35 | private ProgressDrawable mProgressDrawable;//刷新动画 36 | protected RefreshKernel mRefreshKernel; 37 | protected int mBackgroundColor; 38 | protected int mAccentColor; 39 | 40 | public DefaultHeader(Context context) { 41 | super(context); 42 | this.initView(context); 43 | } 44 | 45 | public DefaultHeader(Context context, @Nullable AttributeSet attrs) { 46 | super(context, attrs); 47 | this.initView(context); 48 | } 49 | 50 | public DefaultHeader(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 51 | super(context, attrs, defStyleAttr); 52 | this.initView(context); 53 | } 54 | 55 | 56 | private void initView(Context context) { 57 | RelativeLayout parent = new RelativeLayout(context); 58 | RelativeLayout.LayoutParams rlParent = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 59 | rlParent.addRule(RelativeLayout.CENTER_IN_PARENT,RelativeLayout.TRUE); 60 | 61 | 62 | RelativeLayout.LayoutParams rlArrowView = new RelativeLayout.LayoutParams(DensityUtil.dp2px(20),DensityUtil.dp2px(20)); 63 | mArrowView = new PathsView(context); 64 | mArrowView.setId(R.id.arrow_view); 65 | mArrowView.parserColors(0xff666666); 66 | mArrowView.parserPaths("M20,12l-1.41,-1.41L13,16.17V4h-2v12.17l-5.58,-5.59L4,12l8,8 8,-8z"); 67 | parent.addView(mArrowView,rlArrowView); 68 | 69 | 70 | RelativeLayout.LayoutParams rlHeaderText= new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 71 | rlHeaderText.addRule(RelativeLayout.RIGHT_OF,mArrowView.getId()); 72 | rlHeaderText.leftMargin = DensityUtil.dp2px(20); 73 | mHeaderText = new TextView(context); 74 | mHeaderText.setText("下拉开始刷新"); 75 | parent.addView(mHeaderText,rlHeaderText); 76 | 77 | RelativeLayout.LayoutParams rlProgressView = new RelativeLayout.LayoutParams(DensityUtil.dp2px(20),DensityUtil.dp2px(20)); 78 | rlProgressView.addRule(RelativeLayout.ALIGN_RIGHT,mArrowView.getId()); 79 | mProgressDrawable = new ProgressDrawable(); 80 | mProgressView = new ImageView(context); 81 | mProgressView.setImageDrawable(mProgressDrawable); 82 | parent.addView(mProgressView,rlProgressView); 83 | 84 | 85 | addView(parent,rlParent); 86 | //addView(mProgressView, DensityUtil.dp2px(20), DensityUtil.dp2px(20)); 87 | //addView(mArrowView, DensityUtil.dp2px(20), DensityUtil.dp2px(20)); 88 | //addView(new View(context), DensityUtil.dp2px(20), DensityUtil.dp2px(20)); 89 | //addView(mHeaderText, LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 90 | setMinimumHeight(DensityUtil.dp2px(60)); 91 | } 92 | 93 | @NonNull 94 | public View getView() { 95 | return this;//真实的视图就是自己,不能返回null 96 | } 97 | 98 | @Override 99 | public SpinnerStyle getSpinnerStyle() { 100 | return SpinnerStyle.Translate;//指定为平移,不能null 101 | } 102 | 103 | @Override 104 | public void onStartAnimator(RefreshLayout layout, int headHeight, int extendHeight) { 105 | mProgressDrawable.start();//开始动画 106 | } 107 | 108 | @Override 109 | public int onFinish(RefreshLayout layout, boolean success) { 110 | mProgressDrawable.stop();//停止动画 111 | if (success) { 112 | mHeaderText.setText("刷新完成"); 113 | } else { 114 | mHeaderText.setText("刷新失败"); 115 | } 116 | return 500;//延迟500毫秒之后再弹回 117 | } 118 | 119 | @Override 120 | public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { 121 | switch (newState) { 122 | case None: 123 | case PullDownToRefresh: 124 | mHeaderText.setText("下拉开始刷新"); 125 | mArrowView.setVisibility(VISIBLE);//显示下拉箭头 126 | mProgressView.setVisibility(GONE);//隐藏动画 127 | mArrowView.animate().rotation(0);//还原箭头方向 128 | break; 129 | case Refreshing: 130 | mHeaderText.setText("正在刷新"); 131 | mProgressView.setVisibility(VISIBLE);//显示加载动画 132 | mArrowView.setVisibility(GONE);//隐藏箭头 133 | break; 134 | case ReleaseToRefresh: 135 | mHeaderText.setText("释放立即刷新"); 136 | mArrowView.animate().rotation(180);//显示箭头改为朝上 137 | break; 138 | } 139 | } 140 | 141 | @Override 142 | public boolean isSupportHorizontalDrag() { 143 | return false; 144 | } 145 | 146 | @Override 147 | public void onInitialized(RefreshKernel kernel, int height, int extendHeight) { 148 | mRefreshKernel = kernel; 149 | mRefreshKernel.requestDrawBackgroundForHeader(mBackgroundColor); 150 | 151 | } 152 | 153 | @Override 154 | public void onHorizontalDrag(float percentX, int offsetX, int offsetMax) { 155 | } 156 | 157 | @Override 158 | public void onPulling(float percent, int offset, int headHeight, int extendHeight) { 159 | } 160 | 161 | @Override 162 | public void onReleasing(float percent, int offset, int headHeight, int extendHeight) { 163 | } 164 | 165 | @Override 166 | public void onReleased(RefreshLayout refreshLayout, int height, int extendHeight) { 167 | 168 | } 169 | 170 | @Override 171 | public void setPrimaryColors(@ColorInt int... colors) { 172 | } 173 | 174 | public DefaultHeader setPrimaryColor(@ColorInt int primaryColor) { 175 | mBackgroundColor = primaryColor; 176 | if (mRefreshKernel != null) { 177 | mRefreshKernel.requestDrawBackgroundForHeader(primaryColor); 178 | } 179 | return this; 180 | } 181 | 182 | public DefaultHeader setAccentColor(int accentColor){ 183 | mAccentColor=accentColor; 184 | if(mArrowView!=null){ 185 | mArrowView.parserColors(accentColor); 186 | } 187 | if(mProgressDrawable!=null) { 188 | mProgressDrawable.setColor(accentColor); 189 | } 190 | mHeaderText.setTextColor(accentColor); 191 | return this; 192 | } 193 | 194 | } 195 | -------------------------------------------------------------------------------- /android/src/main/java/com/lmy/header/DefaultHeaderMananger.java: -------------------------------------------------------------------------------- 1 | package com.lmy.header; 2 | 3 | import android.graphics.Color; 4 | 5 | import com.facebook.react.uimanager.SimpleViewManager; 6 | import com.facebook.react.uimanager.ThemedReactContext; 7 | import com.facebook.react.uimanager.annotations.ReactProp; 8 | 9 | /** 10 | * Created by painter.g on 2018/3/12. 11 | */ 12 | 13 | public class DefaultHeaderMananger extends SimpleViewManager { 14 | @Override 15 | public String getName() { 16 | return "RCTDefaultHeader"; 17 | } 18 | 19 | @Override 20 | protected DefaultHeader createViewInstance(ThemedReactContext reactContext) { 21 | return new DefaultHeader(reactContext); 22 | } 23 | /** 24 | * 设置主题颜色 25 | * @param view 26 | * @param primaryColor 27 | */ 28 | @ReactProp(name = "primaryColor") 29 | public void setPrimaryColor(DefaultHeader view,String primaryColor){ 30 | view.setPrimaryColor(Color.parseColor(primaryColor)); 31 | } 32 | 33 | /** 34 | * 设置强调颜色 35 | * @param view 36 | * @param accentColor 37 | */ 38 | @ReactProp(name = "accentColor") 39 | public void setAccentColor(DefaultHeader view,String accentColor){ 40 | view.setAccentColor(Color.parseColor(accentColor)); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /android/src/main/java/com/lmy/header/MaterialHeaderManager.java: -------------------------------------------------------------------------------- 1 | package com.lmy.header; 2 | 3 | import com.facebook.react.uimanager.ThemedReactContext; 4 | import com.facebook.react.uimanager.ViewGroupManager; 5 | import com.scwang.smartrefresh.header.MaterialHeader; 6 | 7 | /** 8 | * Created by painter.g on 2018/3/8. 9 | */ 10 | 11 | public class MaterialHeaderManager extends ViewGroupManager { 12 | @Override 13 | public String getName() { 14 | return "RCTMaterialHeader"; 15 | } 16 | 17 | @Override 18 | protected MaterialHeader createViewInstance(ThemedReactContext reactContext) { 19 | return new MaterialHeader(reactContext); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /android/src/main/java/com/lmy/header/StoreHouseHeaderManager.java: -------------------------------------------------------------------------------- 1 | package com.lmy.header; 2 | 3 | import android.graphics.Color; 4 | 5 | import com.facebook.react.uimanager.SimpleViewManager; 6 | import com.facebook.react.uimanager.ThemedReactContext; 7 | import com.facebook.react.uimanager.annotations.ReactProp; 8 | import com.scwang.smartrefresh.header.StoreHouseHeader; 9 | 10 | /** 11 | * Created by painter.g on 2018/3/7. 12 | */ 13 | 14 | public class StoreHouseHeaderManager extends SimpleViewManager { 15 | 16 | private String textStr="STOREHOUSE"; 17 | @Override 18 | public String getName() { 19 | return "RCTStoreHouseHeader"; 20 | } 21 | 22 | @Override 23 | protected StoreHouseHeader createViewInstance(ThemedReactContext reactContext) { 24 | return new StoreHouseHeader(reactContext); 25 | } 26 | 27 | /** 28 | * 设置字体颜色 29 | * @param view 30 | * @param textColor 31 | */ 32 | @ReactProp(name = "textColor") 33 | public void setTextColor(StoreHouseHeader view,String textColor){ 34 | view.setTextColor(Color.parseColor(textColor)); 35 | } 36 | 37 | /** 38 | * 设置文字 39 | * @param view 40 | * @param text 41 | */ 42 | @ReactProp(name="text") 43 | public void setText(StoreHouseHeader view,String text){ 44 | textStr=text; 45 | view.initWithString(text); 46 | } 47 | 48 | /** 49 | * 设置字体尺寸 50 | * TODO:似乎还没有作用 51 | * @param view 52 | * @param fontSize 53 | */ 54 | @ReactProp(name="fontSize",defaultInt = 25) 55 | public void setFontSize(StoreHouseHeader view,int fontSize){ 56 | view.initWithString(textStr,fontSize); 57 | } 58 | 59 | /** 60 | * 设置线宽 61 | * @param view 62 | * @param lineWidth 63 | */ 64 | @ReactProp(name = "lineWidth") 65 | public void setLineWidth(StoreHouseHeader view,int lineWidth){ 66 | view.setLineWidth(lineWidth); 67 | } 68 | 69 | /** 70 | * 设置dropHeight 71 | * TODO:似乎还没有作用 72 | * @param view 73 | * @param dropHeight 74 | */ 75 | @ReactProp(name="dropHeight") 76 | public void setDropHeight(StoreHouseHeader view,int dropHeight){ 77 | view.setDropHeight(dropHeight); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /android/src/main/java/com/lmy/smartrefreshlayout/Events.java: -------------------------------------------------------------------------------- 1 | package com.lmy.smartrefreshlayout; 2 | 3 | /** 4 | * Created by lmy2534290808 on 2017/12/2. 5 | */ 6 | 7 | public enum Events { 8 | REFRESH("onSmartRefresh"),//刷新触发 9 | LOAD_MORE("onLoadMore"),//加载更多触发 10 | HEADER_PULLING("onHeaderPulling"),//header下拉触发 11 | HEADER_RELEASING("onHeaderReleasing"),//header刷新完成后触发 12 | PULL_DOWN_TO_REFRESH("onPullDownToRefresh"),//下拉开始刷新 13 | RELEASE_TO_REFRESH("onReleaseToRefresh"),//释放刷新 14 | HEADER_RELEASED("onHeaderReleased"),//释放时进行刷新 15 | FOOTER_MOVING("onFooterMoving");//footer移动时触发 16 | 17 | private final String mName; 18 | 19 | Events(final String name) { 20 | mName = name; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return mName; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /android/src/main/java/com/lmy/smartrefreshlayout/HeaderType.java: -------------------------------------------------------------------------------- 1 | package com.lmy.smartrefreshlayout; 2 | 3 | /** 4 | * Created by painter.g on 2018/3/7. 5 | */ 6 | 7 | public class HeaderType { 8 | public static final String CLASSIC="Classic"; 9 | } 10 | -------------------------------------------------------------------------------- /android/src/main/java/com/lmy/smartrefreshlayout/RCTSpinnerStyleModule.java: -------------------------------------------------------------------------------- 1 | package com.lmy.smartrefreshlayout; 2 | 3 | import com.facebook.react.bridge.ReactApplicationContext; 4 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 5 | import com.scwang.smartrefresh.layout.constant.SpinnerStyle; 6 | 7 | import java.util.Collections; 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | import javax.annotation.Nullable; 12 | 13 | /** 14 | * Created by macbook on 2018/6/13. 15 | */ 16 | 17 | public class RCTSpinnerStyleModule extends ReactContextBaseJavaModule { 18 | private static final String MODULE_NAME = "RCTSpinnerStyleModule"; 19 | 20 | public RCTSpinnerStyleModule(ReactApplicationContext reactContext) { 21 | super(reactContext); 22 | } 23 | 24 | @Override 25 | public String getName() { 26 | return MODULE_NAME; 27 | } 28 | 29 | /** 30 | * 31 | *Translate,//平行移动 特点: HeaderView高度不会改变, 32 | *Scale,//拉伸形变 特点:在下拉和上弹(HeaderView高度改变)时候,会自动触发OnDraw事件 33 | *FixedBehind,//固定在背后 特点:HeaderView高度不会改变, 34 | *FixedFront,//固定在前面 特点:HeaderView高度不会改变, 35 | *MatchLayout//填满布局 36 | * 37 | * @return 38 | */ 39 | @Nullable 40 | @Override 41 | public Map getConstants() { 42 | return Collections.unmodifiableMap(new HashMap(){{ 43 | put("translate", SpinnerStyleConstants.TRANSLATE); 44 | put("fixBehind",SpinnerStyleConstants.FIX_BEHIND); 45 | put("fixFront",SpinnerStyleConstants.FIX_FRONT); 46 | put("scale",SpinnerStyleConstants.SCALE); 47 | put("matchLayout",SpinnerStyleConstants.MATCH_LAYOUT); 48 | }}); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /android/src/main/java/com/lmy/smartrefreshlayout/ReactSmartRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package com.lmy.smartrefreshlayout; 2 | 3 | import android.content.Context; 4 | import android.view.MotionEvent; 5 | 6 | import com.facebook.react.uimanager.events.NativeGestureUtil; 7 | import com.scwang.smartrefresh.layout.SmartRefreshLayout; 8 | 9 | /** 10 | * Created by painter.g on 2018/3/7. 11 | */ 12 | 13 | public class ReactSmartRefreshLayout extends SmartRefreshLayout { 14 | private static final float DEFAULT_CIRCLE_TARGET = 64; 15 | 16 | private boolean mDidLayout = false; 17 | private boolean mRefreshing = false; 18 | private float mProgressViewOffset = 0; 19 | private int mTouchSlop; 20 | private float mPrevTouchX; 21 | private boolean mIntercepted; 22 | public ReactSmartRefreshLayout(Context context) { 23 | super(context); 24 | } 25 | @Override 26 | public void onLayout(boolean changed, int left, int top, int right, int bottom) { 27 | super.onLayout(changed, left, top, right, bottom); 28 | 29 | if (!mDidLayout) { 30 | mDidLayout = true; 31 | 32 | // Update values that must be set after initial layout. 33 | // setProgressViewOffset(mProgressViewOffset); 34 | // setRefreshing(mRefreshing); 35 | } 36 | } 37 | 38 | 39 | @Override 40 | public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 41 | if (getParent() != null) { 42 | getParent().requestDisallowInterceptTouchEvent(disallowIntercept); 43 | } 44 | } 45 | 46 | @Override 47 | public boolean onInterceptTouchEvent(MotionEvent ev) { 48 | if (shouldInterceptTouchEvent(ev) && super.onInterceptTouchEvent(ev)) { 49 | NativeGestureUtil.notifyNativeGestureStarted(this, ev); 50 | return true; 51 | } 52 | return false; 53 | } 54 | 55 | 56 | private boolean shouldInterceptTouchEvent(MotionEvent ev) { 57 | switch (ev.getAction()) { 58 | case MotionEvent.ACTION_DOWN: 59 | mPrevTouchX = ev.getX(); 60 | mIntercepted = false; 61 | break; 62 | 63 | case MotionEvent.ACTION_MOVE: 64 | final float eventX = ev.getX(); 65 | final float xDiff = Math.abs(eventX - mPrevTouchX); 66 | 67 | if (mIntercepted || xDiff > mTouchSlop) { 68 | mIntercepted = true; 69 | return false; 70 | } 71 | } 72 | return true; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /android/src/main/java/com/lmy/smartrefreshlayout/SmartRefreshLayoutManager.java: -------------------------------------------------------------------------------- 1 | package com.lmy.smartrefreshlayout; 2 | 3 | import android.graphics.Color; 4 | import android.view.View; 5 | 6 | import com.facebook.react.bridge.Arguments; 7 | import com.facebook.react.bridge.ReadableArray; 8 | import com.facebook.react.bridge.ReadableMap; 9 | import com.facebook.react.bridge.WritableMap; 10 | import com.facebook.react.common.MapBuilder; 11 | import com.facebook.react.uimanager.ThemedReactContext; 12 | import com.facebook.react.uimanager.ViewGroupManager; 13 | import com.facebook.react.uimanager.annotations.ReactProp; 14 | import com.facebook.react.uimanager.events.RCTEventEmitter; 15 | import com.lmy.header.AnyHeader; 16 | import com.scwang.smartrefresh.layout.api.RefreshFooter; 17 | import com.scwang.smartrefresh.layout.api.RefreshHeader; 18 | import com.scwang.smartrefresh.layout.api.RefreshLayout; 19 | import com.scwang.smartrefresh.layout.constant.RefreshState; 20 | import com.scwang.smartrefresh.layout.listener.OnRefreshListener; 21 | import com.scwang.smartrefresh.layout.listener.SimpleMultiPurposeListener; 22 | import com.scwang.smartrefresh.layout.util.DensityUtil; 23 | 24 | import java.util.List; 25 | import java.util.Map; 26 | 27 | import javax.annotation.Nullable; 28 | 29 | /** 30 | * Created by painter.g on 2018/3/6. 31 | * SmartRefreshLayout插件的封装 32 | * https://github.com/scwang90/SmartRefreshLayout 33 | */ 34 | 35 | public class SmartRefreshLayoutManager extends ViewGroupManager{ 36 | //返回给rn的组件名 37 | protected static final String REACT_CLASS="SmartRefreshLayout"; 38 | 39 | private ReactSmartRefreshLayout smartRefreshLayout; 40 | private RCTEventEmitter mEventEmitter; 41 | private ThemedReactContext themedReactContext; 42 | 43 | private static final String COMMAND_FINISH_REFRESH_NAME="finishRefresh"; 44 | private static final int COMMAND_FINISH_REFRESH_ID=0; 45 | 46 | @Override 47 | public String getName() { 48 | return REACT_CLASS; 49 | } 50 | 51 | @Override 52 | protected ReactSmartRefreshLayout createViewInstance(ThemedReactContext reactContext) { 53 | smartRefreshLayout=new ReactSmartRefreshLayout(reactContext); 54 | smartRefreshLayout.setEnableLoadMore(false);//暂时禁止上拉加载 55 | themedReactContext=reactContext; 56 | mEventEmitter=reactContext.getJSModule(RCTEventEmitter.class); 57 | return smartRefreshLayout; 58 | } 59 | 60 | @Override 61 | public Map getExportedCustomDirectEventTypeConstants() { 62 | MapBuilder.Builder builder = MapBuilder.builder(); 63 | for (Events event : Events.values()) { 64 | builder.put(event.toString(), MapBuilder.of("registrationName", event.toString())); 65 | } 66 | return builder.build(); 67 | } 68 | 69 | @Nullable 70 | @Override 71 | public Map getCommandsMap() { 72 | return MapBuilder.of( 73 | COMMAND_FINISH_REFRESH_NAME,COMMAND_FINISH_REFRESH_ID 74 | ); 75 | } 76 | /** 77 | * 最大显示下拉高度/Header标准高度 78 | * @param view 79 | * @param maxDragRate 80 | */ 81 | @ReactProp(name="maxDragRate",defaultFloat = 2.0f) 82 | public void setMaxDragRate(ReactSmartRefreshLayout view,float maxDragRate){ 83 | view.setHeaderMaxDragRate(maxDragRate); 84 | } 85 | /** 86 | * 显示下拉高度/手指真实下拉高度=阻尼效果 87 | * @param view 88 | * @param dragRate 89 | */ 90 | @ReactProp(name = "dragRate",defaultFloat = 0.5f) 91 | public void setDragRate(ReactSmartRefreshLayout view,float dragRate){ 92 | view.setDragRate(dragRate); 93 | } 94 | /** 95 | * 是否使用越界拖动 96 | * @param view 97 | * @param overScrollDrag 98 | */ 99 | @ReactProp(name="overScrollDrag",defaultBoolean = true) 100 | public void setOverScrollDrag(ReactSmartRefreshLayout view,boolean overScrollDrag){ 101 | view.setEnableOverScrollDrag(overScrollDrag); 102 | } 103 | /** 104 | * 是否启用越界回弹 105 | * @param view 106 | * @param overScrollBounce 107 | */ 108 | @ReactProp(name = "overScrollBounce",defaultBoolean = true) 109 | public void setOverScrollBounce(ReactSmartRefreshLayout view,boolean overScrollBounce){ 110 | view.setEnableOverScrollBounce(overScrollBounce); 111 | } 112 | /** 113 | * 设置为纯滚动 114 | * @param view 115 | * @param pureScroll 116 | */ 117 | @ReactProp(name = "pureScroll",defaultBoolean = false) 118 | public void setPureScroll(ReactSmartRefreshLayout view,boolean pureScroll){ 119 | view.setEnablePureScrollMode(pureScroll); 120 | } 121 | /** 122 | * 通过RefreshLayout设置主题色 123 | * @param view 124 | * @param primaryColor 125 | */ 126 | @ReactProp(name = "primaryColor",defaultInt = Color.TRANSPARENT) 127 | public void setPrimaryColor(ReactSmartRefreshLayout view, int primaryColor){ 128 | view.setPrimaryColors(primaryColor); 129 | } 130 | /** 131 | * 设置headerHeight 132 | * @param view 133 | * @param headerHeight 134 | */ 135 | @ReactProp(name = "headerHeight") 136 | public void setHeaderHeight(ReactSmartRefreshLayout view,float headerHeight){ 137 | if(headerHeight != 0.0f) { 138 | view.setHeaderHeight(headerHeight); 139 | 140 | } 141 | } 142 | /** 143 | * 是否启用下拉刷新功能 144 | * @param view 145 | * @param enableRefresh 146 | */ 147 | @ReactProp(name="enableRefresh",defaultBoolean = true) 148 | public void setEnableRefresh(ReactSmartRefreshLayout view,boolean enableRefresh){ 149 | view.setEnableRefresh(enableRefresh); 150 | } 151 | 152 | /** 153 | * 是否启用自动刷新 154 | * @param view 155 | * @param autoRefresh 156 | */ 157 | @ReactProp(name = "autoRefresh",defaultBoolean = false) 158 | public void setAutoRefresh(ReactSmartRefreshLayout view, ReadableMap autoRefresh){ 159 | boolean isAutoRefresh=false;Integer time=null; 160 | if(autoRefresh.hasKey("refresh")){ 161 | isAutoRefresh=autoRefresh.getBoolean("refresh"); 162 | } 163 | if(autoRefresh.hasKey("time")){ 164 | time=autoRefresh.getInt("time"); 165 | } 166 | if(isAutoRefresh==true){ 167 | if(time!=null && time>0){ 168 | view.autoRefresh(time); 169 | }else{ 170 | view.autoRefresh(); 171 | } 172 | } 173 | } 174 | @Override 175 | public void receiveCommand(ReactSmartRefreshLayout root, int commandId, @Nullable ReadableArray args) { 176 | switch (commandId){ 177 | case COMMAND_FINISH_REFRESH_ID: 178 | int delayed=args.getInt(0); 179 | boolean success=args.getBoolean(1); 180 | if(delayed>=0){ 181 | root.finishRefresh(delayed,success); 182 | }else{ 183 | root.finishRefresh(success); 184 | } 185 | break; 186 | default:break; 187 | } 188 | } 189 | 190 | @Override 191 | public void addView(ReactSmartRefreshLayout parent, View child, int index) { 192 | switch (index){ 193 | case 0: 194 | RefreshHeader header; 195 | if(child instanceof RefreshHeader){ 196 | header=(RefreshHeader)child; 197 | }else{ 198 | header=new AnyHeader(themedReactContext); 199 | ((AnyHeader)header).setView(child); 200 | } 201 | parent.setRefreshHeader(header); 202 | //parent.setRefreshHeader(new MaterialHeader(themedReactContext).setShowBezierWave(true)); 203 | break; 204 | case 1: 205 | parent.setRefreshContent(child); 206 | break; 207 | case 2: 208 | //RefreshFooter footer=(RefreshFooter)child; 209 | //parent.setRefreshFooter(footer); 210 | break; 211 | default:break; 212 | 213 | } 214 | } 215 | 216 | @Override 217 | public void addViews(ReactSmartRefreshLayout parent, List views) { 218 | super.addViews(parent, views); 219 | } 220 | 221 | @Override 222 | protected void addEventEmitters(ThemedReactContext reactContext,final ReactSmartRefreshLayout view) { 223 | 224 | 225 | /** 226 | * 必须设置OnRefreshListener,如果没有设置, 227 | * 则会自动触发finishRefresh 228 | * 229 | * OnRefreshListener和OnSimpleMultiPurposeListener 230 | * 中的onRefresh都会触发刷新,只需写一个即可 231 | */ 232 | view.setOnRefreshListener(new OnRefreshListener() { 233 | @Override 234 | public void onRefresh(RefreshLayout refreshLayout) { 235 | 236 | } 237 | }); 238 | view.setOnMultiPurposeListener(new SimpleMultiPurposeListener() { 239 | private int getTargetId(){ 240 | return view.getId(); 241 | } 242 | 243 | @Override 244 | public void onHeaderPulling(RefreshHeader header, float percent, int offset, int headerHeight, int extendHeight) { 245 | WritableMap writableMap = Arguments.createMap(); 246 | writableMap.putDouble("percent",percent); 247 | writableMap.putDouble("offset",DensityUtil.px2dp(offset)); 248 | writableMap.putDouble("headerHeight",DensityUtil.px2dp(headerHeight)); 249 | mEventEmitter.receiveEvent(getTargetId(),Events.HEADER_PULLING.toString(),writableMap); 250 | } 251 | 252 | @Override 253 | public void onHeaderReleased(RefreshHeader header, int headerHeight, int extendHeight) { 254 | WritableMap writableMap = Arguments.createMap(); 255 | writableMap.putDouble("headerHeight",DensityUtil.px2dp(headerHeight)); 256 | mEventEmitter.receiveEvent(getTargetId(),Events.HEADER_RELEASED.toString(),writableMap); 257 | } 258 | 259 | @Override 260 | public void onHeaderReleasing(RefreshHeader header, float percent, int offset, int headerHeight, int extendHeight) { 261 | WritableMap writableMap = Arguments.createMap(); 262 | writableMap.putDouble("percent",percent); 263 | writableMap.putDouble("offset",DensityUtil.px2dp(offset)); 264 | writableMap.putDouble("headerHeight",DensityUtil.px2dp(headerHeight)); 265 | mEventEmitter.receiveEvent(getTargetId(),Events.HEADER_RELEASING.toString(),writableMap); 266 | } 267 | 268 | @Override 269 | public void onFooterPulling(RefreshFooter footer, float percent, int offset, int footerHeight, int extendHeight) { 270 | WritableMap writableMap = Arguments.createMap(); 271 | writableMap.putDouble("percent",percent); 272 | writableMap.putDouble("offset",DensityUtil.px2dp(offset)); 273 | writableMap.putDouble("footerHeight",DensityUtil.px2dp(footerHeight)); 274 | mEventEmitter.receiveEvent(getTargetId(),Events.FOOTER_MOVING.toString(),writableMap); 275 | } 276 | 277 | @Override 278 | public void onFooterReleasing(RefreshFooter footer, float percent, int offset, int footerHeight, int extendHeight) { 279 | WritableMap writableMap = Arguments.createMap(); 280 | writableMap.putDouble("percent",percent); 281 | writableMap.putDouble("offset",DensityUtil.px2dp(offset)); 282 | writableMap.putDouble("footerHeight",DensityUtil.px2dp(footerHeight)); 283 | mEventEmitter.receiveEvent(getTargetId(),Events.FOOTER_MOVING.toString(),writableMap); 284 | } 285 | 286 | @Override 287 | public void onHeaderStartAnimator(RefreshHeader header, int headerHeight, int extendHeight) { 288 | 289 | } 290 | 291 | @Override 292 | public void onHeaderFinish(RefreshHeader header, boolean success) { 293 | 294 | } 295 | @Override 296 | public void onLoadMore(RefreshLayout refreshLayout) { 297 | mEventEmitter.receiveEvent(getTargetId(),Events.LOAD_MORE.toString(),null); 298 | } 299 | 300 | @Override 301 | public void onRefresh(RefreshLayout refreshLayout) { 302 | mEventEmitter.receiveEvent(getTargetId(),Events.REFRESH.toString(),null); 303 | } 304 | 305 | @Override 306 | public void onStateChanged(RefreshLayout refreshLayout, RefreshState oldState, RefreshState newState) { 307 | switch (newState) { 308 | case None: 309 | case PullDownToRefresh: 310 | mEventEmitter.receiveEvent(getTargetId(),Events.PULL_DOWN_TO_REFRESH.toString(),null); 311 | break; 312 | case Refreshing: 313 | 314 | break; 315 | case ReleaseToRefresh: 316 | mEventEmitter.receiveEvent(getTargetId(),Events.RELEASE_TO_REFRESH.toString(),null); 317 | break; 318 | } 319 | 320 | } 321 | }); 322 | } 323 | private int getTargetId(){ 324 | return smartRefreshLayout.getId(); 325 | } 326 | } 327 | -------------------------------------------------------------------------------- /android/src/main/java/com/lmy/smartrefreshlayout/SmartRefreshLayoutPackage.java: -------------------------------------------------------------------------------- 1 | package com.lmy.smartrefreshlayout; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | import com.lmy.header.AnyHeaderManager; 9 | import com.lmy.header.ClassicsHeaderManager; 10 | import com.lmy.header.DefaultHeaderMananger; 11 | import com.lmy.header.MaterialHeaderManager; 12 | import com.lmy.header.StoreHouseHeaderManager; 13 | 14 | import java.util.Arrays; 15 | import java.util.Collections; 16 | import java.util.List; 17 | 18 | /** 19 | * Created by painter.g on 2018/3/6. 20 | */ 21 | 22 | public class SmartRefreshLayoutPackage implements ReactPackage { 23 | @Override 24 | public List createNativeModules(ReactApplicationContext reactContext) { 25 | return Arrays.asList( 26 | new RCTSpinnerStyleModule(reactContext) 27 | ); 28 | } 29 | //@Override >=0.47已经过期 30 | public List> createJSModules() { 31 | return Collections.emptyList(); 32 | } 33 | @Override 34 | public List createViewManagers(ReactApplicationContext reactContext) { 35 | return Arrays.asList( 36 | new SmartRefreshLayoutManager(), 37 | new ClassicsHeaderManager(), 38 | new StoreHouseHeaderManager(), 39 | new MaterialHeaderManager(), 40 | new AnyHeaderManager(), 41 | new DefaultHeaderMananger() 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /android/src/main/java/com/lmy/smartrefreshlayout/SpinnerStyleConstants.java: -------------------------------------------------------------------------------- 1 | package com.lmy.smartrefreshlayout; 2 | 3 | import com.scwang.smartrefresh.layout.constant.SpinnerStyle; 4 | 5 | import java.util.HashMap; 6 | 7 | /** 8 | * Created by macbook on 2018/6/13. 9 | */ 10 | 11 | public class SpinnerStyleConstants { 12 | public static final String TRANSLATE = "translate"; 13 | public static final String FIX_BEHIND = "fixBehind"; 14 | public static final String SCALE = "scale"; 15 | public static final String FIX_FRONT = "fixFront"; 16 | public static final String MATCH_LAYOUT = "matchLayout"; 17 | public static final HashMap SpinnerStyleMap = new HashMap(){{ 18 | put(TRANSLATE,SpinnerStyle.Translate); 19 | put(FIX_BEHIND,SpinnerStyle.FixedBehind); 20 | put(SCALE,SpinnerStyle.Scale); 21 | put(MATCH_LAYOUT,SpinnerStyle.MatchLayout); 22 | put(FIX_FRONT,SpinnerStyle.FixedFront); 23 | }}; 24 | } 25 | -------------------------------------------------------------------------------- /android/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | SmartRefreshLayout 3 | 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jszh on 2020/5/12. 3 | */ 4 | 5 | import ZHRefreshControl from './src/fusion' 6 | import {ZHScrollView} from './src/iosJS/MJRefresh'; 7 | 8 | export {ZHScrollView} 9 | export default ZHRefreshControl; 10 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshAutoFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoFooter.h 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshFooter.h" 10 | 11 | @interface MJRefreshAutoFooter : MJRefreshFooter 12 | /** 是否自动刷新(默认为YES) */ 13 | @property (assign, nonatomic, getter=isAutomaticallyRefresh) BOOL automaticallyRefresh; 14 | 15 | /** 当底部控件出现多少时就自动刷新(默认为1.0,也就是底部控件完全出现时,才会自动刷新) */ 16 | @property (assign, nonatomic) CGFloat appearencePercentTriggerAutoRefresh MJRefreshDeprecated("请使用triggerAutomaticallyRefreshPercent属性"); 17 | 18 | /** 当底部控件出现多少时就自动刷新(默认为1.0,也就是底部控件完全出现时,才会自动刷新) */ 19 | @property (assign, nonatomic) CGFloat triggerAutomaticallyRefreshPercent; 20 | 21 | /** 是否每一次拖拽只发一次请求 */ 22 | @property (assign, nonatomic, getter=isOnlyRefreshPerDrag) BOOL onlyRefreshPerDrag; 23 | @end 24 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshAutoFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoFooter.m 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshAutoFooter.h" 10 | 11 | @interface MJRefreshAutoFooter() 12 | /** 一个新的拖拽 */ 13 | @property (assign, nonatomic, getter=isOneNewPan) BOOL oneNewPan; 14 | @end 15 | 16 | @implementation MJRefreshAutoFooter 17 | 18 | #pragma mark - 初始化 19 | - (void)willMoveToSuperview:(UIView *)newSuperview 20 | { 21 | [super willMoveToSuperview:newSuperview]; 22 | 23 | if (newSuperview) { // 新的父控件 24 | if (self.hidden == NO) { 25 | self.scrollView.mj_insetB += self.mj_h; 26 | } 27 | 28 | // 设置位置 29 | self.mj_y = _scrollView.mj_contentH; 30 | } else { // 被移除了 31 | if (self.hidden == NO) { 32 | self.scrollView.mj_insetB -= self.mj_h; 33 | } 34 | } 35 | } 36 | 37 | #pragma mark - 过期方法 38 | - (void)setAppearencePercentTriggerAutoRefresh:(CGFloat)appearencePercentTriggerAutoRefresh 39 | { 40 | self.triggerAutomaticallyRefreshPercent = appearencePercentTriggerAutoRefresh; 41 | } 42 | 43 | - (CGFloat)appearencePercentTriggerAutoRefresh 44 | { 45 | return self.triggerAutomaticallyRefreshPercent; 46 | } 47 | 48 | #pragma mark - 实现父类的方法 49 | - (void)prepare 50 | { 51 | [super prepare]; 52 | 53 | // 默认底部控件100%出现时才会自动刷新 54 | self.triggerAutomaticallyRefreshPercent = 1.0; 55 | 56 | // 设置为默认状态 57 | self.automaticallyRefresh = YES; 58 | 59 | // 默认是当offset达到条件就发送请求(可连续) 60 | self.onlyRefreshPerDrag = NO; 61 | } 62 | 63 | - (void)scrollViewContentSizeDidChange:(NSDictionary *)change 64 | { 65 | [super scrollViewContentSizeDidChange:change]; 66 | 67 | // 设置位置 68 | self.mj_y = self.scrollView.mj_contentH; 69 | } 70 | 71 | - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change 72 | { 73 | [super scrollViewContentOffsetDidChange:change]; 74 | 75 | if (self.state != MJRefreshStateIdle || !self.automaticallyRefresh || self.mj_y == 0) return; 76 | 77 | if (_scrollView.mj_insetT + _scrollView.mj_contentH > _scrollView.mj_h) { // 内容超过一个屏幕 78 | // 这里的_scrollView.mj_contentH替换掉self.mj_y更为合理 79 | if (_scrollView.mj_offsetY >= _scrollView.mj_contentH - _scrollView.mj_h + self.mj_h * self.triggerAutomaticallyRefreshPercent + _scrollView.mj_insetB - self.mj_h) { 80 | // 防止手松开时连续调用 81 | CGPoint old = [change[@"old"] CGPointValue]; 82 | CGPoint new = [change[@"new"] CGPointValue]; 83 | if (new.y <= old.y) return; 84 | 85 | // 当底部刷新控件完全出现时,才刷新 86 | [self beginRefreshing]; 87 | } 88 | } 89 | } 90 | 91 | - (void)scrollViewPanStateDidChange:(NSDictionary *)change 92 | { 93 | [super scrollViewPanStateDidChange:change]; 94 | 95 | if (self.state != MJRefreshStateIdle) return; 96 | 97 | UIGestureRecognizerState panState = _scrollView.panGestureRecognizer.state; 98 | if (panState == UIGestureRecognizerStateEnded) {// 手松开 99 | if (_scrollView.mj_insetT + _scrollView.mj_contentH <= _scrollView.mj_h) { // 不够一个屏幕 100 | if (_scrollView.mj_offsetY >= - _scrollView.mj_insetT) { // 向上拽 101 | [self beginRefreshing]; 102 | } 103 | } else { // 超出一个屏幕 104 | if (_scrollView.mj_offsetY >= _scrollView.mj_contentH + _scrollView.mj_insetB - _scrollView.mj_h) { 105 | [self beginRefreshing]; 106 | } 107 | } 108 | } else if (panState == UIGestureRecognizerStateBegan) { 109 | self.oneNewPan = YES; 110 | } 111 | } 112 | 113 | - (void)beginRefreshing 114 | { 115 | if (!self.isOneNewPan && self.isOnlyRefreshPerDrag) return; 116 | 117 | [super beginRefreshing]; 118 | 119 | self.oneNewPan = NO; 120 | } 121 | 122 | - (void)setState:(MJRefreshState)state 123 | { 124 | MJRefreshCheckState 125 | 126 | if (state == MJRefreshStateRefreshing) { 127 | [self executeRefreshingCallback]; 128 | } else if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) { 129 | if (MJRefreshStateRefreshing == oldState) { 130 | if (self.endRefreshingCompletionBlock) { 131 | self.endRefreshingCompletionBlock(); 132 | } 133 | } 134 | } 135 | } 136 | 137 | - (void)setHidden:(BOOL)hidden 138 | { 139 | BOOL lastHidden = self.isHidden; 140 | 141 | [super setHidden:hidden]; 142 | 143 | if (!lastHidden && hidden) { 144 | self.state = MJRefreshStateIdle; 145 | 146 | self.scrollView.mj_insetB -= self.mj_h; 147 | } else if (lastHidden && !hidden) { 148 | self.scrollView.mj_insetB += self.mj_h; 149 | 150 | // 设置位置 151 | self.mj_y = _scrollView.mj_contentH; 152 | } 153 | } 154 | @end 155 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshBackFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackFooter.h 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshFooter.h" 10 | 11 | @interface MJRefreshBackFooter : MJRefreshFooter 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshBackFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackFooter.m 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshBackFooter.h" 10 | 11 | @interface MJRefreshBackFooter() 12 | @property (assign, nonatomic) NSInteger lastRefreshCount; 13 | @property (assign, nonatomic) CGFloat lastBottomDelta; 14 | @end 15 | 16 | @implementation MJRefreshBackFooter 17 | 18 | #pragma mark - 初始化 19 | - (void)willMoveToSuperview:(UIView *)newSuperview 20 | { 21 | [super willMoveToSuperview:newSuperview]; 22 | 23 | [self scrollViewContentSizeDidChange:nil]; 24 | } 25 | 26 | #pragma mark - 实现父类的方法 27 | - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change 28 | { 29 | [super scrollViewContentOffsetDidChange:change]; 30 | 31 | // 如果正在刷新,直接返回 32 | if (self.state == MJRefreshStateRefreshing) return; 33 | 34 | _scrollViewOriginalInset = self.scrollView.mj_inset; 35 | 36 | // 当前的contentOffset 37 | CGFloat currentOffsetY = self.scrollView.mj_offsetY; 38 | // 尾部控件刚好出现的offsetY 39 | CGFloat happenOffsetY = [self happenOffsetY]; 40 | // 如果是向下滚动到看不见尾部控件,直接返回 41 | if (currentOffsetY <= happenOffsetY) return; 42 | 43 | CGFloat pullingPercent = (currentOffsetY - happenOffsetY) / self.mj_h; 44 | 45 | // 如果已全部加载,仅设置pullingPercent,然后返回 46 | if (self.state == MJRefreshStateNoMoreData) { 47 | self.pullingPercent = pullingPercent; 48 | return; 49 | } 50 | 51 | if (self.scrollView.isDragging) { 52 | self.pullingPercent = pullingPercent; 53 | // 普通 和 即将刷新 的临界点 54 | CGFloat normal2pullingOffsetY = happenOffsetY + self.mj_h; 55 | 56 | if (self.state == MJRefreshStateIdle && currentOffsetY > normal2pullingOffsetY) { 57 | // 转为即将刷新状态 58 | self.state = MJRefreshStatePulling; 59 | } else if (self.state == MJRefreshStatePulling && currentOffsetY <= normal2pullingOffsetY) { 60 | // 转为普通状态 61 | self.state = MJRefreshStateIdle; 62 | } 63 | } else if (self.state == MJRefreshStatePulling) {// 即将刷新 && 手松开 64 | // 开始刷新 65 | [self beginRefreshing]; 66 | } else if (pullingPercent < 1) { 67 | self.pullingPercent = pullingPercent; 68 | } 69 | } 70 | 71 | - (void)scrollViewContentSizeDidChange:(NSDictionary *)change 72 | { 73 | [super scrollViewContentSizeDidChange:change]; 74 | 75 | // 内容的高度 76 | CGFloat contentHeight = self.scrollView.mj_contentH + self.ignoredScrollViewContentInsetBottom; 77 | // 表格的高度 78 | CGFloat scrollHeight = self.scrollView.mj_h - self.scrollViewOriginalInset.top - self.scrollViewOriginalInset.bottom + self.ignoredScrollViewContentInsetBottom; 79 | // 设置位置和尺寸 80 | self.mj_y = MAX(contentHeight, scrollHeight); 81 | } 82 | 83 | - (void)setState:(MJRefreshState)state 84 | { 85 | MJRefreshCheckState 86 | 87 | // 根据状态来设置属性 88 | if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) { 89 | // 刷新完毕 90 | if (MJRefreshStateRefreshing == oldState) { 91 | [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{ 92 | self.scrollView.mj_insetB -= self.lastBottomDelta; 93 | 94 | // 自动调整透明度 95 | if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0; 96 | } completion:^(BOOL finished) { 97 | self.pullingPercent = 0.0; 98 | 99 | if (self.endRefreshingCompletionBlock) { 100 | self.endRefreshingCompletionBlock(); 101 | } 102 | }]; 103 | } 104 | 105 | CGFloat deltaH = [self heightForContentBreakView]; 106 | // 刚刷新完毕 107 | if (MJRefreshStateRefreshing == oldState && deltaH > 0 && self.scrollView.mj_totalDataCount != self.lastRefreshCount) { 108 | self.scrollView.mj_offsetY = self.scrollView.mj_offsetY; 109 | } 110 | } else if (state == MJRefreshStateRefreshing) { 111 | // 记录刷新前的数量 112 | self.lastRefreshCount = self.scrollView.mj_totalDataCount; 113 | 114 | [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{ 115 | CGFloat bottom = self.mj_h + self.scrollViewOriginalInset.bottom; 116 | CGFloat deltaH = [self heightForContentBreakView]; 117 | if (deltaH < 0) { // 如果内容高度小于view的高度 118 | bottom -= deltaH; 119 | } 120 | self.lastBottomDelta = bottom - self.scrollView.mj_insetB; 121 | self.scrollView.mj_insetB = bottom; 122 | self.scrollView.mj_offsetY = [self happenOffsetY] + self.mj_h; 123 | } completion:^(BOOL finished) { 124 | [self executeRefreshingCallback]; 125 | }]; 126 | } 127 | } 128 | #pragma mark - 私有方法 129 | #pragma mark 获得scrollView的内容 超出 view 的高度 130 | - (CGFloat)heightForContentBreakView 131 | { 132 | CGFloat h = self.scrollView.frame.size.height - self.scrollViewOriginalInset.bottom - self.scrollViewOriginalInset.top; 133 | return self.scrollView.contentSize.height - h; 134 | } 135 | 136 | #pragma mark 刚好看到上拉刷新控件时的contentOffset.y 137 | - (CGFloat)happenOffsetY 138 | { 139 | CGFloat deltaH = [self heightForContentBreakView]; 140 | if (deltaH > 0) { 141 | return deltaH - self.scrollViewOriginalInset.top; 142 | } else { 143 | return - self.scrollViewOriginalInset.top; 144 | } 145 | } 146 | @end 147 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshComponent.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // 代码地址: http://code4app.com/ios/%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E4%B8%8B%E6%8B%89%E4%B8%8A%E6%8B%89%E5%88%B7%E6%96%B0/52326ce26803fabc46000000 3 | // MJRefreshComponent.h 4 | // MJRefreshExample 5 | // 6 | // Created by MJ Lee on 15/3/4. 7 | // Copyright (c) 2015年 小码哥. All rights reserved. 8 | // 刷新控件的基类 9 | 10 | #import 11 | #import "MJRefreshConst.h" 12 | #import "UIView+MJExtension.h" 13 | #import "UIScrollView+MJExtension.h" 14 | #import "UIScrollView+MJRefresh.h" 15 | #import "NSBundle+MJRefresh.h" 16 | #import 17 | #import 18 | 19 | /** 刷新控件的状态 */ 20 | typedef NS_ENUM(NSInteger, MJRefreshState) { 21 | /** 普通闲置状态 */ 22 | MJRefreshStateIdle = 1, 23 | /** 松开就可以进行刷新的状态 */ 24 | MJRefreshStatePulling, 25 | /** 正在刷新中的状态 */ 26 | MJRefreshStateRefreshing, 27 | /** 即将刷新的状态 */ 28 | MJRefreshStateWillRefresh, 29 | /** 所有数据加载完毕,没有更多的数据了 */ 30 | MJRefreshStateNoMoreData 31 | }; 32 | 33 | /** 进入刷新状态的回调 */ 34 | typedef void (^MJRefreshComponentRefreshingBlock)(void); 35 | /** 开始刷新后的回调(进入刷新状态后的回调) */ 36 | typedef void (^MJRefreshComponentbeginRefreshingCompletionBlock)(void); 37 | /** 结束刷新后的回调 */ 38 | typedef void (^MJRefreshComponentEndRefreshingCompletionBlock)(void); 39 | 40 | /** 刷新控件的基类 */ 41 | @interface MJRefreshComponent : UIView 42 | { 43 | /** 记录scrollView刚开始的inset */ 44 | UIEdgeInsets _scrollViewOriginalInset; 45 | /** 父控件 */ 46 | __weak UIScrollView *_scrollView; 47 | } 48 | #pragma mark - 刷新回调 49 | /** 正在刷新的回调 */ 50 | @property (copy, nonatomic) MJRefreshComponentRefreshingBlock refreshingBlock; 51 | /** 设置回调对象和回调方法 */ 52 | - (void)setRefreshingTarget:(id)target refreshingAction:(SEL)action; 53 | 54 | /** 回调对象 */ 55 | @property (weak, nonatomic) id refreshingTarget; 56 | /** 回调方法 */ 57 | @property (assign, nonatomic) SEL refreshingAction; 58 | /** 触发回调(交给子类去调用) */ 59 | - (void)executeRefreshingCallback; 60 | 61 | #pragma mark - 刷新状态控制 62 | /** 进入刷新状态 */ 63 | - (void)beginRefreshing; 64 | - (void)beginRefreshingWithCompletionBlock:(void (^)(void))completionBlock; 65 | /** 开始刷新后的回调(进入刷新状态后的回调) */ 66 | @property (copy, nonatomic) MJRefreshComponentbeginRefreshingCompletionBlock beginRefreshingCompletionBlock; 67 | /** 结束刷新的回调 */ 68 | @property (copy, nonatomic) MJRefreshComponentEndRefreshingCompletionBlock endRefreshingCompletionBlock; 69 | /** 结束刷新状态 */ 70 | - (void)endRefreshing; 71 | - (void)endRefreshingWithCompletionBlock:(void (^)(void))completionBlock; 72 | /** 是否正在刷新 */ 73 | @property (assign, nonatomic, readonly, getter=isRefreshing) BOOL refreshing; 74 | //- (BOOL)isRefreshing; 75 | /** 刷新状态 一般交给子类内部实现 */ 76 | @property (assign, nonatomic) MJRefreshState state; 77 | 78 | #pragma mark - 交给子类去访问 79 | /** 记录scrollView刚开始的inset */ 80 | @property (assign, nonatomic, readonly) UIEdgeInsets scrollViewOriginalInset; 81 | /** 父控件 */ 82 | @property (weak, nonatomic, readonly) UIScrollView *scrollView; 83 | 84 | #pragma mark - 交给子类们去实现 85 | /** 初始化 */ 86 | - (void)prepare NS_REQUIRES_SUPER; 87 | /** 摆放子控件frame */ 88 | - (void)placeSubviews NS_REQUIRES_SUPER; 89 | /** 当scrollView的contentOffset发生改变的时候调用 */ 90 | - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change NS_REQUIRES_SUPER; 91 | /** 当scrollView的contentSize发生改变的时候调用 */ 92 | - (void)scrollViewContentSizeDidChange:(NSDictionary *)change NS_REQUIRES_SUPER; 93 | /** 当scrollView的拖拽状态发生改变的时候调用 */ 94 | - (void)scrollViewPanStateDidChange:(NSDictionary *)change NS_REQUIRES_SUPER; 95 | 96 | 97 | #pragma mark - 其他 98 | /** 拉拽的百分比(交给子类重写) */ 99 | @property (assign, nonatomic) CGFloat pullingPercent; 100 | /** 根据拖拽比例自动切换透明度 */ 101 | @property (assign, nonatomic, getter=isAutoChangeAlpha) BOOL autoChangeAlpha MJRefreshDeprecated("请使用automaticallyChangeAlpha属性"); 102 | /** 根据拖拽比例自动切换透明度 */ 103 | @property (assign, nonatomic, getter=isAutomaticallyChangeAlpha) BOOL automaticallyChangeAlpha; 104 | @end 105 | 106 | @interface UILabel(MJRefresh) 107 | + (instancetype)mj_label; 108 | - (CGFloat)mj_textWith; 109 | @end 110 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshComponent.m: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // 代码地址: http://code4app.com/ios/%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E4%B8%8B%E6%8B%89%E4%B8%8A%E6%8B%89%E5%88%B7%E6%96%B0/52326ce26803fabc46000000 3 | // MJRefreshComponent.m 4 | // MJRefreshExample 5 | // 6 | // Created by MJ Lee on 15/3/4. 7 | // Copyright (c) 2015年 小码哥. All rights reserved. 8 | // 9 | 10 | #import "MJRefreshComponent.h" 11 | #import "MJRefreshConst.h" 12 | 13 | @interface MJRefreshComponent() 14 | @property (strong, nonatomic) UIPanGestureRecognizer *pan; 15 | @end 16 | 17 | @implementation MJRefreshComponent 18 | #pragma mark - 初始化 19 | - (instancetype)initWithFrame:(CGRect)frame 20 | { 21 | if (self = [super initWithFrame:frame]) { 22 | // 准备工作 23 | [self prepare]; 24 | 25 | // 默认是普通状态 26 | self.state = MJRefreshStateIdle; 27 | } 28 | return self; 29 | } 30 | 31 | - (void)prepare 32 | { 33 | // 基本属性 34 | self.autoresizingMask = UIViewAutoresizingFlexibleWidth; 35 | self.backgroundColor = [UIColor clearColor]; 36 | } 37 | 38 | - (void)layoutSubviews 39 | { 40 | [self placeSubviews]; 41 | 42 | [super layoutSubviews]; 43 | } 44 | 45 | - (void)placeSubviews{} 46 | 47 | - (void)willMoveToSuperview:(UIView *)newSuperview 48 | { 49 | [super willMoveToSuperview:newSuperview]; 50 | 51 | // 如果不是UIScrollView,不做任何事情 52 | if (newSuperview && ![newSuperview isKindOfClass:[UIScrollView class]]) return; 53 | 54 | // 旧的父控件移除监听 55 | [self removeObservers]; 56 | 57 | if (newSuperview) { // 新的父控件 58 | // 设置宽度 59 | self.mj_w = newSuperview.mj_w; 60 | // 设置位置 61 | self.mj_x = -_scrollView.mj_insetL; 62 | 63 | // 记录UIScrollView 64 | _scrollView = (UIScrollView *)newSuperview; 65 | // 设置永远支持垂直弹簧效果 66 | _scrollView.alwaysBounceVertical = YES; 67 | // 记录UIScrollView最开始的contentInset 68 | _scrollViewOriginalInset = _scrollView.mj_inset; 69 | 70 | // 添加监听 71 | [self addObservers]; 72 | } 73 | } 74 | 75 | - (void)drawRect:(CGRect)rect 76 | { 77 | [super drawRect:rect]; 78 | 79 | if (self.state == MJRefreshStateWillRefresh) { 80 | // 预防view还没显示出来就调用了beginRefreshing 81 | self.state = MJRefreshStateRefreshing; 82 | } 83 | } 84 | 85 | #pragma mark - KVO监听 86 | - (void)addObservers 87 | { 88 | NSKeyValueObservingOptions options = NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld; 89 | [self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentOffset options:options context:nil]; 90 | [self.scrollView addObserver:self forKeyPath:MJRefreshKeyPathContentSize options:options context:nil]; 91 | self.pan = self.scrollView.panGestureRecognizer; 92 | [self.pan addObserver:self forKeyPath:MJRefreshKeyPathPanState options:options context:nil]; 93 | } 94 | 95 | - (void)removeObservers 96 | { 97 | [self.superview removeObserver:self forKeyPath:MJRefreshKeyPathContentOffset]; 98 | [self.superview removeObserver:self forKeyPath:MJRefreshKeyPathContentSize]; 99 | [self.pan removeObserver:self forKeyPath:MJRefreshKeyPathPanState]; 100 | self.pan = nil; 101 | } 102 | 103 | - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context 104 | { 105 | // 遇到这些情况就直接返回 106 | if (!self.userInteractionEnabled) return; 107 | 108 | // 这个就算看不见也需要处理 109 | if ([keyPath isEqualToString:MJRefreshKeyPathContentSize]) { 110 | [self scrollViewContentSizeDidChange:change]; 111 | } 112 | 113 | // 看不见 114 | if (self.hidden) return; 115 | if ([keyPath isEqualToString:MJRefreshKeyPathContentOffset]) { 116 | [self scrollViewContentOffsetDidChange:change]; 117 | } else if ([keyPath isEqualToString:MJRefreshKeyPathPanState]) { 118 | [self scrollViewPanStateDidChange:change]; 119 | } 120 | } 121 | 122 | - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change{} 123 | - (void)scrollViewContentSizeDidChange:(NSDictionary *)change{} 124 | - (void)scrollViewPanStateDidChange:(NSDictionary *)change{} 125 | 126 | #pragma mark - 公共方法 127 | #pragma mark 设置回调对象和回调方法 128 | - (void)setRefreshingTarget:(id)target refreshingAction:(SEL)action 129 | { 130 | self.refreshingTarget = target; 131 | self.refreshingAction = action; 132 | } 133 | 134 | - (void)setState:(MJRefreshState)state 135 | { 136 | _state = state; 137 | 138 | // 加入主队列的目的是等setState:方法调用完毕、设置完文字后再去布局子控件 139 | dispatch_async(dispatch_get_main_queue(), ^{ 140 | [self setNeedsLayout]; 141 | }); 142 | } 143 | 144 | #pragma mark 进入刷新状态 145 | - (void)beginRefreshing 146 | { 147 | [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{ 148 | self.alpha = 1.0; 149 | }]; 150 | self.pullingPercent = 1.0; 151 | // 只要正在刷新,就完全显示 152 | if (self.window) { 153 | self.state = MJRefreshStateRefreshing; 154 | } else { 155 | // 预防正在刷新中时,调用本方法使得header inset回置失败 156 | if (self.state != MJRefreshStateRefreshing) { 157 | self.state = MJRefreshStateWillRefresh; 158 | // 刷新(预防从另一个控制器回到这个控制器的情况,回来要重新刷新一下) 159 | [self setNeedsDisplay]; 160 | } 161 | } 162 | } 163 | 164 | - (void)beginRefreshingWithCompletionBlock:(void (^)(void))completionBlock 165 | { 166 | self.beginRefreshingCompletionBlock = completionBlock; 167 | 168 | [self beginRefreshing]; 169 | } 170 | 171 | #pragma mark 结束刷新状态 172 | - (void)endRefreshing 173 | { 174 | dispatch_async(dispatch_get_main_queue(), ^{ 175 | self.state = MJRefreshStateIdle; 176 | }); 177 | } 178 | 179 | - (void)endRefreshingWithCompletionBlock:(void (^)(void))completionBlock 180 | { 181 | self.endRefreshingCompletionBlock = completionBlock; 182 | 183 | [self endRefreshing]; 184 | } 185 | 186 | #pragma mark 是否正在刷新 187 | - (BOOL)isRefreshing 188 | { 189 | return self.state == MJRefreshStateRefreshing || self.state == MJRefreshStateWillRefresh; 190 | } 191 | 192 | #pragma mark 自动切换透明度 193 | - (void)setAutoChangeAlpha:(BOOL)autoChangeAlpha 194 | { 195 | self.automaticallyChangeAlpha = autoChangeAlpha; 196 | } 197 | 198 | - (BOOL)isAutoChangeAlpha 199 | { 200 | return self.isAutomaticallyChangeAlpha; 201 | } 202 | 203 | - (void)setAutomaticallyChangeAlpha:(BOOL)automaticallyChangeAlpha 204 | { 205 | _automaticallyChangeAlpha = automaticallyChangeAlpha; 206 | 207 | if (self.isRefreshing) return; 208 | 209 | if (automaticallyChangeAlpha) { 210 | self.alpha = self.pullingPercent; 211 | } else { 212 | self.alpha = 1.0; 213 | } 214 | } 215 | 216 | #pragma mark 根据拖拽进度设置透明度 217 | - (void)setPullingPercent:(CGFloat)pullingPercent 218 | { 219 | _pullingPercent = pullingPercent; 220 | 221 | if (self.isRefreshing) return; 222 | 223 | if (self.isAutomaticallyChangeAlpha) { 224 | self.alpha = pullingPercent; 225 | } 226 | } 227 | 228 | #pragma mark - 内部方法 229 | - (void)executeRefreshingCallback 230 | { 231 | dispatch_async(dispatch_get_main_queue(), ^{ 232 | if (self.refreshingBlock) { 233 | self.refreshingBlock(); 234 | } 235 | if ([self.refreshingTarget respondsToSelector:self.refreshingAction]) { 236 | MJRefreshMsgSend(MJRefreshMsgTarget(self.refreshingTarget), self.refreshingAction, self); 237 | } 238 | if (self.beginRefreshingCompletionBlock) { 239 | self.beginRefreshingCompletionBlock(); 240 | } 241 | }); 242 | } 243 | @end 244 | 245 | @implementation UILabel(MJRefresh) 246 | + (instancetype)mj_label 247 | { 248 | UILabel *label = [[self alloc] init]; 249 | label.font = MJRefreshLabelFont; 250 | label.textColor = MJRefreshLabelTextColor; 251 | label.autoresizingMask = UIViewAutoresizingFlexibleWidth; 252 | label.textAlignment = NSTextAlignmentCenter; 253 | label.backgroundColor = [UIColor clearColor]; 254 | return label; 255 | } 256 | 257 | - (CGFloat)mj_textWith { 258 | CGFloat stringWidth = 0; 259 | CGSize size = CGSizeMake(MAXFLOAT, MAXFLOAT); 260 | if (self.text.length > 0) { 261 | #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 70000 262 | stringWidth =[self.text 263 | boundingRectWithSize:size 264 | options:NSStringDrawingUsesLineFragmentOrigin 265 | attributes:@{NSFontAttributeName:self.font} 266 | context:nil].size.width; 267 | #else 268 | 269 | stringWidth = [self.text sizeWithFont:self.font 270 | constrainedToSize:size 271 | lineBreakMode:NSLineBreakByCharWrapping].width; 272 | #endif 273 | } 274 | return stringWidth; 275 | } 276 | @end 277 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshFooter.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // 代码地址: http://code4app.com/ios/%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E4%B8%8B%E6%8B%89%E4%B8%8A%E6%8B%89%E5%88%B7%E6%96%B0/52326ce26803fabc46000000 3 | // MJRefreshFooter.h 4 | // MJRefreshExample 5 | // 6 | // Created by MJ Lee on 15/3/5. 7 | // Copyright (c) 2015年 小码哥. All rights reserved. 8 | // 上拉刷新控件 9 | 10 | #import "MJRefreshComponent.h" 11 | 12 | @interface MJRefreshFooter : MJRefreshComponent 13 | /** 创建footer */ 14 | + (instancetype)footerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock; 15 | /** 创建footer */ 16 | + (instancetype)footerWithRefreshingTarget:(id)target refreshingAction:(SEL)action; 17 | 18 | /** 提示没有更多的数据 */ 19 | - (void)endRefreshingWithNoMoreData; 20 | - (void)noticeNoMoreData MJRefreshDeprecated("使用endRefreshingWithNoMoreData"); 21 | 22 | /** 重置没有更多的数据(消除没有更多数据的状态) */ 23 | - (void)resetNoMoreData; 24 | 25 | /** 忽略多少scrollView的contentInset的bottom */ 26 | @property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetBottom; 27 | 28 | /** 自动根据有无数据来显示和隐藏(有数据就显示,没有数据隐藏。默认是NO) */ 29 | @property (assign, nonatomic, getter=isAutomaticallyHidden) BOOL automaticallyHidden MJRefreshDeprecated("不建议使用此属性,开发者请自行控制footer的显示和隐藏。基于安全考虑,在未来的某些版本此属性可能作废"); 30 | @end 31 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshFooter.m: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // 代码地址: http://code4app.com/ios/%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E4%B8%8B%E6%8B%89%E4%B8%8A%E6%8B%89%E5%88%B7%E6%96%B0/52326ce26803fabc46000000 3 | // MJRefreshFooter.m 4 | // MJRefreshExample 5 | // 6 | // Created by MJ Lee on 15/3/5. 7 | // Copyright (c) 2015年 小码哥. All rights reserved. 8 | // 9 | 10 | #import "MJRefreshFooter.h" 11 | #include "UIScrollView+MJRefresh.h" 12 | 13 | @interface MJRefreshFooter() 14 | 15 | @end 16 | 17 | @implementation MJRefreshFooter 18 | #pragma mark - 构造方法 19 | + (instancetype)footerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)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 | - (void)willMoveToSuperview:(UIView *)newSuperview 45 | { 46 | [super willMoveToSuperview:newSuperview]; 47 | 48 | if (newSuperview) { 49 | // 监听scrollView数据的变化 50 | if ([self.scrollView isKindOfClass:[UITableView class]] || [self.scrollView isKindOfClass:[UICollectionView class]]) { 51 | [self.scrollView setMj_reloadDataBlock:^(NSInteger totalDataCount) { 52 | if (self.isAutomaticallyHidden) { 53 | self.hidden = (totalDataCount == 0); 54 | } 55 | }]; 56 | } 57 | } 58 | } 59 | 60 | #pragma mark - 公共方法 61 | - (void)endRefreshingWithNoMoreData 62 | { 63 | dispatch_async(dispatch_get_main_queue(), ^{ 64 | self.state = MJRefreshStateNoMoreData; 65 | }); 66 | } 67 | 68 | - (void)noticeNoMoreData 69 | { 70 | [self endRefreshingWithNoMoreData]; 71 | } 72 | 73 | - (void)resetNoMoreData 74 | { 75 | dispatch_async(dispatch_get_main_queue(), ^{ 76 | self.state = MJRefreshStateIdle; 77 | }); 78 | } 79 | 80 | - (void)setAutomaticallyHidden:(BOOL)automaticallyHidden 81 | { 82 | _automaticallyHidden = automaticallyHidden; 83 | } 84 | @end 85 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshHeader.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // 代码地址: http://code4app.com/ios/%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E4%B8%8B%E6%8B%89%E4%B8%8A%E6%8B%89%E5%88%B7%E6%96%B0/52326ce26803fabc46000000 3 | // MJRefreshHeader.h 4 | // MJRefreshExample 5 | // 6 | // Created by MJ Lee on 15/3/4. 7 | // Copyright (c) 2015年 小码哥. All rights reserved. 8 | // 下拉刷新控件:负责监控用户下拉的状态 9 | 10 | #import "MJRefreshComponent.h" 11 | 12 | @interface MJRefreshHeader : MJRefreshComponent 13 | /** 创建header */ 14 | + (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock; 15 | /** 创建header */ 16 | + (instancetype)headerWithRefreshingTarget:(id)target refreshingAction:(SEL)action; 17 | 18 | /** 这个key用来存储上一次下拉刷新成功的时间 */ 19 | @property (copy, nonatomic) NSString *lastUpdatedTimeKey; 20 | /** 上一次下拉刷新成功的时间 */ 21 | @property (strong, nonatomic, readonly) NSDate *lastUpdatedTime; 22 | 23 | /** 忽略多少scrollView的contentInset的top */ 24 | @property (assign, nonatomic) CGFloat ignoredScrollViewContentInsetTop; 25 | @end 26 | -------------------------------------------------------------------------------- /ios/MJRefresh/Base/MJRefreshHeader.m: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // 代码地址: http://code4app.com/ios/%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E4%B8%8B%E6%8B%89%E4%B8%8A%E6%8B%89%E5%88%B7%E6%96%B0/52326ce26803fabc46000000 3 | // MJRefreshHeader.m 4 | // MJRefreshExample 5 | // 6 | // Created by MJ Lee on 15/3/4. 7 | // Copyright (c) 2015年 小码哥. All rights reserved. 8 | // 9 | 10 | #import "MJRefreshHeader.h" 11 | 12 | @interface MJRefreshHeader() 13 | @property (assign, nonatomic) CGFloat insetTDelta; 14 | @end 15 | 16 | @implementation MJRefreshHeader 17 | #pragma mark - 构造方法 18 | + (instancetype)headerWithRefreshingBlock:(MJRefreshComponentRefreshingBlock)refreshingBlock 19 | { 20 | MJRefreshHeader *cmp = [[self alloc] init]; 21 | cmp.refreshingBlock = refreshingBlock; 22 | return cmp; 23 | } 24 | + (instancetype)headerWithRefreshingTarget:(id)target refreshingAction:(SEL)action 25 | { 26 | MJRefreshHeader *cmp = [[self alloc] init]; 27 | [cmp setRefreshingTarget:target refreshingAction:action]; 28 | return cmp; 29 | } 30 | 31 | #pragma mark - 覆盖父类的方法 32 | - (void)prepare 33 | { 34 | [super prepare]; 35 | 36 | // 设置key 37 | self.lastUpdatedTimeKey = MJRefreshHeaderLastUpdatedTimeKey; 38 | 39 | // 设置高度 40 | self.mj_h = MJRefreshHeaderHeight; 41 | } 42 | 43 | - (void)placeSubviews 44 | { 45 | [super placeSubviews]; 46 | 47 | // 设置y值(当自己的高度发生改变了,肯定要重新调整Y值,所以放到placeSubviews方法中设置y值) 48 | self.mj_y = - self.mj_h- self.ignoredScrollViewContentInsetTop; 49 | } 50 | 51 | - (void)scrollViewContentOffsetDidChange:(NSDictionary *)change 52 | { 53 | [super scrollViewContentOffsetDidChange:change]; 54 | 55 | // 在刷新的refreshing状态 56 | if (self.state == MJRefreshStateRefreshing) { 57 | // 暂时保留 58 | if (self.window == nil) return; 59 | 60 | // sectionheader停留解决 61 | CGFloat insetT = - self.scrollView.mj_offsetY > _scrollViewOriginalInset.top ? - self.scrollView.mj_offsetY : _scrollViewOriginalInset.top; 62 | insetT = insetT > self.mj_h + _scrollViewOriginalInset.top ? self.mj_h + _scrollViewOriginalInset.top : insetT; 63 | self.scrollView.mj_insetT = insetT; 64 | 65 | self.insetTDelta = _scrollViewOriginalInset.top - insetT; 66 | return; 67 | } 68 | 69 | // 跳转到下一个控制器时,contentInset可能会变 70 | _scrollViewOriginalInset = self.scrollView.mj_inset; 71 | 72 | // 当前的contentOffset 73 | CGFloat offsetY = self.scrollView.mj_offsetY; 74 | // 头部控件刚好出现的offsetY 75 | CGFloat happenOffsetY = - self.scrollViewOriginalInset.top; 76 | 77 | // 如果是向上滚动到看不见头部控件,直接返回 78 | // >= -> > 79 | if (offsetY > happenOffsetY) return; 80 | 81 | // 普通 和 即将刷新 的临界点 82 | CGFloat normal2pullingOffsetY = happenOffsetY - self.mj_h; 83 | CGFloat pullingPercent = (happenOffsetY - offsetY) / self.mj_h; 84 | 85 | if (self.scrollView.isDragging) { // 如果正在拖拽 86 | self.pullingPercent = pullingPercent; 87 | if (self.state == MJRefreshStateIdle && offsetY < normal2pullingOffsetY) { 88 | // 转为即将刷新状态 89 | self.state = MJRefreshStatePulling; 90 | } else if (self.state == MJRefreshStatePulling && offsetY >= normal2pullingOffsetY) { 91 | // 转为普通状态 92 | self.state = MJRefreshStateIdle; 93 | } 94 | } else if (self.state == MJRefreshStatePulling) {// 即将刷新 && 手松开 95 | // 开始刷新 96 | [self beginRefreshing]; 97 | } else if (pullingPercent < 1) { 98 | self.pullingPercent = pullingPercent; 99 | } 100 | } 101 | 102 | - (void)setState:(MJRefreshState)state 103 | { 104 | MJRefreshCheckState 105 | 106 | // 根据状态做事情 107 | if (state == MJRefreshStateIdle) { 108 | if (oldState != MJRefreshStateRefreshing) return; 109 | 110 | // 保存刷新时间 111 | [[NSUserDefaults standardUserDefaults] setObject:[NSDate date] forKey:self.lastUpdatedTimeKey]; 112 | [[NSUserDefaults standardUserDefaults] synchronize]; 113 | 114 | // 恢复inset和offset 115 | [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{ 116 | self.scrollView.mj_insetT += self.insetTDelta; 117 | 118 | // 自动调整透明度 119 | if (self.isAutomaticallyChangeAlpha) self.alpha = 0.0; 120 | } completion:^(BOOL finished) { 121 | self.pullingPercent = 0.0; 122 | 123 | if (self.endRefreshingCompletionBlock) { 124 | self.endRefreshingCompletionBlock(); 125 | } 126 | }]; 127 | } else if (state == MJRefreshStateRefreshing) { 128 | dispatch_async(dispatch_get_main_queue(), ^{ 129 | [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{ 130 | CGFloat top = self.scrollViewOriginalInset.top + self.mj_h; 131 | // 增加滚动区域top 132 | self.scrollView.mj_insetT = top; 133 | // 设置滚动位置 134 | CGPoint offset = self.scrollView.contentOffset; 135 | offset.y = -top; 136 | [self.scrollView setContentOffset:offset animated:NO]; 137 | } completion:^(BOOL finished) { 138 | [self executeRefreshingCallback]; 139 | }]; 140 | }); 141 | } 142 | } 143 | 144 | #pragma mark - 公共方法 145 | - (NSDate *)lastUpdatedTime 146 | { 147 | return [[NSUserDefaults standardUserDefaults] objectForKey:self.lastUpdatedTimeKey]; 148 | } 149 | 150 | - (void)setIgnoredScrollViewContentInsetTop:(CGFloat)ignoredScrollViewContentInsetTop { 151 | _ignoredScrollViewContentInsetTop = ignoredScrollViewContentInsetTop; 152 | 153 | self.mj_y = - self.mj_h - _ignoredScrollViewContentInsetTop; 154 | } 155 | 156 | @end 157 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Auto/MJRefreshAutoGifFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoGifFooter.h 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshAutoStateFooter.h" 10 | 11 | @interface MJRefreshAutoGifFooter : MJRefreshAutoStateFooter 12 | @property (weak, nonatomic, readonly) UIImageView *gifView; 13 | 14 | /** 设置state状态下的动画图片images 动画持续时间duration*/ 15 | - (void)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state; 16 | - (void)setImages:(NSArray *)images forState:(MJRefreshState)state; 17 | @end 18 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Auto/MJRefreshAutoGifFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoGifFooter.m 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshAutoGifFooter.h" 10 | 11 | @interface MJRefreshAutoGifFooter() 12 | { 13 | __unsafe_unretained UIImageView *_gifView; 14 | } 15 | /** 所有状态对应的动画图片 */ 16 | @property (strong, nonatomic) NSMutableDictionary *stateImages; 17 | /** 所有状态对应的动画时间 */ 18 | @property (strong, nonatomic) NSMutableDictionary *stateDurations; 19 | @end 20 | 21 | @implementation MJRefreshAutoGifFooter 22 | #pragma mark - 懒加载 23 | - (UIImageView *)gifView 24 | { 25 | if (!_gifView) { 26 | UIImageView *gifView = [[UIImageView alloc] init]; 27 | [self addSubview:_gifView = gifView]; 28 | } 29 | return _gifView; 30 | } 31 | 32 | - (NSMutableDictionary *)stateImages 33 | { 34 | if (!_stateImages) { 35 | self.stateImages = [NSMutableDictionary dictionary]; 36 | } 37 | return _stateImages; 38 | } 39 | 40 | - (NSMutableDictionary *)stateDurations 41 | { 42 | if (!_stateDurations) { 43 | self.stateDurations = [NSMutableDictionary dictionary]; 44 | } 45 | return _stateDurations; 46 | } 47 | 48 | #pragma mark - 公共方法 49 | - (void)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state 50 | { 51 | if (images == nil) return; 52 | 53 | self.stateImages[@(state)] = images; 54 | self.stateDurations[@(state)] = @(duration); 55 | 56 | /* 根据图片设置控件的高度 */ 57 | UIImage *image = [images firstObject]; 58 | if (image.size.height > self.mj_h) { 59 | self.mj_h = image.size.height; 60 | } 61 | } 62 | 63 | - (void)setImages:(NSArray *)images forState:(MJRefreshState)state 64 | { 65 | [self setImages:images duration:images.count * 0.1 forState:state]; 66 | } 67 | 68 | #pragma mark - 实现父类的方法 69 | - (void)prepare 70 | { 71 | [super prepare]; 72 | 73 | // 初始化间距 74 | self.labelLeftInset = 20; 75 | } 76 | 77 | - (void)placeSubviews 78 | { 79 | [super placeSubviews]; 80 | 81 | if (self.gifView.constraints.count) return; 82 | 83 | self.gifView.frame = self.bounds; 84 | if (self.isRefreshingTitleHidden) { 85 | self.gifView.contentMode = UIViewContentModeCenter; 86 | } else { 87 | self.gifView.contentMode = UIViewContentModeRight; 88 | self.gifView.mj_w = self.mj_w * 0.5 - self.labelLeftInset - self.stateLabel.mj_textWith * 0.5; 89 | } 90 | } 91 | 92 | - (void)setState:(MJRefreshState)state 93 | { 94 | MJRefreshCheckState 95 | 96 | // 根据状态做事情 97 | if (state == MJRefreshStateRefreshing) { 98 | NSArray *images = self.stateImages[@(state)]; 99 | if (images.count == 0) return; 100 | [self.gifView stopAnimating]; 101 | 102 | self.gifView.hidden = NO; 103 | if (images.count == 1) { // 单张图片 104 | self.gifView.image = [images lastObject]; 105 | } else { // 多张图片 106 | self.gifView.animationImages = images; 107 | self.gifView.animationDuration = [self.stateDurations[@(state)] doubleValue]; 108 | [self.gifView startAnimating]; 109 | } 110 | } else if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) { 111 | [self.gifView stopAnimating]; 112 | self.gifView.hidden = YES; 113 | } 114 | } 115 | @end 116 | 117 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Auto/MJRefreshAutoNormalFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoNormalFooter.h 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshAutoStateFooter.h" 10 | 11 | @interface MJRefreshAutoNormalFooter : MJRefreshAutoStateFooter 12 | /** 菊花的样式 */ 13 | @property (assign, nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle; 14 | @end 15 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Auto/MJRefreshAutoNormalFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoNormalFooter.m 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshAutoNormalFooter.h" 10 | 11 | @interface MJRefreshAutoNormalFooter() 12 | @property (weak, nonatomic) UIActivityIndicatorView *loadingView; 13 | @end 14 | 15 | @implementation MJRefreshAutoNormalFooter 16 | #pragma mark - 懒加载子控件 17 | - (UIActivityIndicatorView *)loadingView 18 | { 19 | if (!_loadingView) { 20 | UIActivityIndicatorView *loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:self.activityIndicatorViewStyle]; 21 | loadingView.hidesWhenStopped = YES; 22 | [self addSubview:_loadingView = loadingView]; 23 | } 24 | return _loadingView; 25 | } 26 | 27 | - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)activityIndicatorViewStyle 28 | { 29 | _activityIndicatorViewStyle = activityIndicatorViewStyle; 30 | 31 | self.loadingView = nil; 32 | [self setNeedsLayout]; 33 | } 34 | #pragma mark - 重写父类的方法 35 | - (void)prepare 36 | { 37 | [super prepare]; 38 | 39 | self.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; 40 | } 41 | 42 | - (void)placeSubviews 43 | { 44 | [super placeSubviews]; 45 | 46 | if (self.loadingView.constraints.count) return; 47 | 48 | // 圈圈 49 | CGFloat loadingCenterX = self.mj_w * 0.5; 50 | if (!self.isRefreshingTitleHidden) { 51 | loadingCenterX -= self.stateLabel.mj_textWith * 0.5 + self.labelLeftInset; 52 | } 53 | CGFloat loadingCenterY = self.mj_h * 0.5; 54 | self.loadingView.center = CGPointMake(loadingCenterX, loadingCenterY); 55 | } 56 | 57 | - (void)setState:(MJRefreshState)state 58 | { 59 | MJRefreshCheckState 60 | 61 | // 根据状态做事情 62 | if (state == MJRefreshStateNoMoreData || state == MJRefreshStateIdle) { 63 | [self.loadingView stopAnimating]; 64 | } else if (state == MJRefreshStateRefreshing) { 65 | [self.loadingView startAnimating]; 66 | } 67 | } 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoStateFooter.h 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/6/13. 6 | // Copyright © 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshAutoFooter.h" 10 | 11 | @interface MJRefreshAutoStateFooter : MJRefreshAutoFooter 12 | /** 文字距离圈圈、箭头的距离 */ 13 | @property (assign, nonatomic) CGFloat labelLeftInset; 14 | /** 显示刷新状态的label */ 15 | @property (weak, nonatomic, readonly) UILabel *stateLabel; 16 | 17 | /** 设置state状态下的文字 */ 18 | - (void)setTitle:(NSString *)title forState:(MJRefreshState)state; 19 | 20 | /** 隐藏刷新状态的文字 */ 21 | @property (assign, nonatomic, getter=isRefreshingTitleHidden) BOOL refreshingTitleHidden; 22 | @end 23 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshAutoStateFooter.m 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/6/13. 6 | // Copyright © 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshAutoStateFooter.h" 10 | 11 | @interface MJRefreshAutoStateFooter() 12 | { 13 | /** 显示刷新状态的label */ 14 | __unsafe_unretained UILabel *_stateLabel; 15 | } 16 | /** 所有状态对应的文字 */ 17 | @property (strong, nonatomic) NSMutableDictionary *stateTitles; 18 | @end 19 | 20 | @implementation MJRefreshAutoStateFooter 21 | #pragma mark - 懒加载 22 | - (NSMutableDictionary *)stateTitles 23 | { 24 | if (!_stateTitles) { 25 | self.stateTitles = [NSMutableDictionary dictionary]; 26 | } 27 | return _stateTitles; 28 | } 29 | 30 | - (UILabel *)stateLabel 31 | { 32 | if (!_stateLabel) { 33 | [self addSubview:_stateLabel = [UILabel mj_label]]; 34 | } 35 | return _stateLabel; 36 | } 37 | 38 | #pragma mark - 公共方法 39 | - (void)setTitle:(NSString *)title forState:(MJRefreshState)state 40 | { 41 | if (title == nil) return; 42 | self.stateTitles[@(state)] = title; 43 | self.stateLabel.text = self.stateTitles[@(self.state)]; 44 | } 45 | 46 | #pragma mark - 私有方法 47 | - (void)stateLabelClick 48 | { 49 | if (self.state == MJRefreshStateIdle) { 50 | [self beginRefreshing]; 51 | } 52 | } 53 | 54 | #pragma mark - 重写父类的方法 55 | - (void)prepare 56 | { 57 | [super prepare]; 58 | 59 | // 初始化间距 60 | self.labelLeftInset = MJRefreshLabelLeftInset; 61 | 62 | // 初始化文字 63 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterIdleText] forState:MJRefreshStateIdle]; 64 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterRefreshingText] forState:MJRefreshStateRefreshing]; 65 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshAutoFooterNoMoreDataText] forState:MJRefreshStateNoMoreData]; 66 | 67 | // 监听label 68 | self.stateLabel.userInteractionEnabled = YES; 69 | [self.stateLabel addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(stateLabelClick)]]; 70 | } 71 | 72 | - (void)placeSubviews 73 | { 74 | [super placeSubviews]; 75 | 76 | if (self.stateLabel.constraints.count) return; 77 | 78 | // 状态标签 79 | self.stateLabel.frame = self.bounds; 80 | } 81 | 82 | - (void)setState:(MJRefreshState)state 83 | { 84 | MJRefreshCheckState 85 | 86 | if (self.isRefreshingTitleHidden && state == MJRefreshStateRefreshing) { 87 | self.stateLabel.text = nil; 88 | } else { 89 | self.stateLabel.text = self.stateTitles[@(state)]; 90 | } 91 | } 92 | @end -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Back/MJRefreshBackGifFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackGifFooter.h 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshBackStateFooter.h" 10 | 11 | @interface MJRefreshBackGifFooter : MJRefreshBackStateFooter 12 | @property (weak, nonatomic, readonly) UIImageView *gifView; 13 | 14 | /** 设置state状态下的动画图片images 动画持续时间duration*/ 15 | - (void)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state; 16 | - (void)setImages:(NSArray *)images forState:(MJRefreshState)state; 17 | @end 18 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Back/MJRefreshBackGifFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackGifFooter.m 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshBackGifFooter.h" 10 | 11 | @interface MJRefreshBackGifFooter() 12 | { 13 | __unsafe_unretained UIImageView *_gifView; 14 | } 15 | /** 所有状态对应的动画图片 */ 16 | @property (strong, nonatomic) NSMutableDictionary *stateImages; 17 | /** 所有状态对应的动画时间 */ 18 | @property (strong, nonatomic) NSMutableDictionary *stateDurations; 19 | @end 20 | 21 | @implementation MJRefreshBackGifFooter 22 | #pragma mark - 懒加载 23 | - (UIImageView *)gifView 24 | { 25 | if (!_gifView) { 26 | UIImageView *gifView = [[UIImageView alloc] init]; 27 | [self addSubview:_gifView = gifView]; 28 | } 29 | return _gifView; 30 | } 31 | 32 | - (NSMutableDictionary *)stateImages 33 | { 34 | if (!_stateImages) { 35 | self.stateImages = [NSMutableDictionary dictionary]; 36 | } 37 | return _stateImages; 38 | } 39 | 40 | - (NSMutableDictionary *)stateDurations 41 | { 42 | if (!_stateDurations) { 43 | self.stateDurations = [NSMutableDictionary dictionary]; 44 | } 45 | return _stateDurations; 46 | } 47 | 48 | #pragma mark - 公共方法 49 | - (void)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state 50 | { 51 | if (images == nil) return; 52 | 53 | self.stateImages[@(state)] = images; 54 | self.stateDurations[@(state)] = @(duration); 55 | 56 | /* 根据图片设置控件的高度 */ 57 | UIImage *image = [images firstObject]; 58 | if (image.size.height > self.mj_h) { 59 | self.mj_h = image.size.height; 60 | } 61 | } 62 | 63 | - (void)setImages:(NSArray *)images forState:(MJRefreshState)state 64 | { 65 | [self setImages:images duration:images.count * 0.1 forState:state]; 66 | } 67 | 68 | #pragma mark - 实现父类的方法 69 | - (void)prepare 70 | { 71 | [super prepare]; 72 | 73 | // 初始化间距 74 | self.labelLeftInset = 20; 75 | } 76 | 77 | - (void)setPullingPercent:(CGFloat)pullingPercent 78 | { 79 | [super setPullingPercent:pullingPercent]; 80 | NSArray *images = self.stateImages[@(MJRefreshStateIdle)]; 81 | if (self.state != MJRefreshStateIdle || images.count == 0) return; 82 | [self.gifView stopAnimating]; 83 | NSUInteger index = images.count * pullingPercent; 84 | if (index >= images.count) index = images.count - 1; 85 | self.gifView.image = images[index]; 86 | } 87 | 88 | - (void)placeSubviews 89 | { 90 | [super placeSubviews]; 91 | 92 | if (self.gifView.constraints.count) return; 93 | 94 | self.gifView.frame = self.bounds; 95 | if (self.stateLabel.hidden) { 96 | self.gifView.contentMode = UIViewContentModeCenter; 97 | } else { 98 | self.gifView.contentMode = UIViewContentModeRight; 99 | self.gifView.mj_w = self.mj_w * 0.5 - self.labelLeftInset - self.stateLabel.mj_textWith * 0.5; 100 | } 101 | } 102 | 103 | - (void)setState:(MJRefreshState)state 104 | { 105 | MJRefreshCheckState 106 | 107 | // 根据状态做事情 108 | if (state == MJRefreshStatePulling || state == MJRefreshStateRefreshing) { 109 | NSArray *images = self.stateImages[@(state)]; 110 | if (images.count == 0) return; 111 | 112 | self.gifView.hidden = NO; 113 | [self.gifView stopAnimating]; 114 | if (images.count == 1) { // 单张图片 115 | self.gifView.image = [images lastObject]; 116 | } else { // 多张图片 117 | self.gifView.animationImages = images; 118 | self.gifView.animationDuration = [self.stateDurations[@(state)] doubleValue]; 119 | [self.gifView startAnimating]; 120 | } 121 | } else if (state == MJRefreshStateIdle) { 122 | self.gifView.hidden = NO; 123 | } else if (state == MJRefreshStateNoMoreData) { 124 | self.gifView.hidden = YES; 125 | } 126 | } 127 | @end 128 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Back/MJRefreshBackNormalFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackNormalFooter.h 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshBackStateFooter.h" 10 | 11 | @interface MJRefreshBackNormalFooter : MJRefreshBackStateFooter 12 | @property (weak, nonatomic, readonly) UIImageView *arrowView; 13 | /** 菊花的样式 */ 14 | @property (assign, nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle; 15 | @end 16 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Back/MJRefreshBackNormalFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackNormalFooter.m 3 | // MJRefreshExample 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 | 12 | @interface MJRefreshBackNormalFooter() 13 | { 14 | __unsafe_unretained UIImageView *_arrowView; 15 | } 16 | @property (weak, nonatomic) UIActivityIndicatorView *loadingView; 17 | @end 18 | 19 | @implementation MJRefreshBackNormalFooter 20 | #pragma mark - 懒加载子控件 21 | - (UIImageView *)arrowView 22 | { 23 | if (!_arrowView) { 24 | UIImageView *arrowView = [[UIImageView alloc] initWithImage:[NSBundle mj_arrowImage]]; 25 | [self addSubview:_arrowView = arrowView]; 26 | } 27 | return _arrowView; 28 | } 29 | 30 | 31 | - (UIActivityIndicatorView *)loadingView 32 | { 33 | if (!_loadingView) { 34 | UIActivityIndicatorView *loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:self.activityIndicatorViewStyle]; 35 | loadingView.hidesWhenStopped = YES; 36 | [self addSubview:_loadingView = loadingView]; 37 | } 38 | return _loadingView; 39 | } 40 | 41 | - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)activityIndicatorViewStyle 42 | { 43 | _activityIndicatorViewStyle = activityIndicatorViewStyle; 44 | 45 | self.loadingView = nil; 46 | [self setNeedsLayout]; 47 | } 48 | #pragma mark - 重写父类的方法 49 | - (void)prepare 50 | { 51 | [super prepare]; 52 | 53 | self.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; 54 | } 55 | 56 | - (void)placeSubviews 57 | { 58 | [super placeSubviews]; 59 | 60 | // 箭头的中心点 61 | CGFloat arrowCenterX = self.mj_w * 0.5; 62 | if (!self.stateLabel.hidden) { 63 | arrowCenterX -= self.labelLeftInset + self.stateLabel.mj_textWith * 0.5; 64 | } 65 | CGFloat arrowCenterY = self.mj_h * 0.5; 66 | CGPoint arrowCenter = CGPointMake(arrowCenterX, arrowCenterY); 67 | 68 | // 箭头 69 | if (self.arrowView.constraints.count == 0) { 70 | self.arrowView.mj_size = self.arrowView.image.size; 71 | self.arrowView.center = arrowCenter; 72 | } 73 | 74 | // 圈圈 75 | if (self.loadingView.constraints.count == 0) { 76 | self.loadingView.center = arrowCenter; 77 | } 78 | 79 | self.arrowView.tintColor = self.stateLabel.textColor; 80 | } 81 | 82 | - (void)setState:(MJRefreshState)state 83 | { 84 | MJRefreshCheckState 85 | 86 | // 根据状态做事情 87 | if (state == MJRefreshStateIdle) { 88 | if (oldState == MJRefreshStateRefreshing) { 89 | self.arrowView.transform = CGAffineTransformMakeRotation(0.000001 - M_PI); 90 | [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{ 91 | self.loadingView.alpha = 0.0; 92 | } completion:^(BOOL finished) { 93 | self.loadingView.alpha = 1.0; 94 | [self.loadingView stopAnimating]; 95 | 96 | self.arrowView.hidden = NO; 97 | }]; 98 | } else { 99 | self.arrowView.hidden = NO; 100 | [self.loadingView stopAnimating]; 101 | [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{ 102 | self.arrowView.transform = CGAffineTransformMakeRotation(0.000001 - M_PI); 103 | }]; 104 | } 105 | } else if (state == MJRefreshStatePulling) { 106 | self.arrowView.hidden = NO; 107 | [self.loadingView stopAnimating]; 108 | [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{ 109 | self.arrowView.transform = CGAffineTransformIdentity; 110 | }]; 111 | } else if (state == MJRefreshStateRefreshing) { 112 | self.arrowView.hidden = YES; 113 | [self.loadingView startAnimating]; 114 | } else if (state == MJRefreshStateNoMoreData) { 115 | self.arrowView.hidden = YES; 116 | [self.loadingView stopAnimating]; 117 | } 118 | } 119 | 120 | @end 121 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Back/MJRefreshBackStateFooter.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackStateFooter.h 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/6/13. 6 | // Copyright © 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshBackFooter.h" 10 | 11 | @interface MJRefreshBackStateFooter : MJRefreshBackFooter 12 | /** 文字距离圈圈、箭头的距离 */ 13 | @property (assign, nonatomic) CGFloat labelLeftInset; 14 | /** 显示刷新状态的label */ 15 | @property (weak, nonatomic, readonly) UILabel *stateLabel; 16 | /** 设置state状态下的文字 */ 17 | - (void)setTitle:(NSString *)title forState:(MJRefreshState)state; 18 | 19 | /** 获取state状态下的title */ 20 | - (NSString *)titleForState:(MJRefreshState)state; 21 | @end 22 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Footer/Back/MJRefreshBackStateFooter.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshBackStateFooter.m 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/6/13. 6 | // Copyright © 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshBackStateFooter.h" 10 | 11 | @interface MJRefreshBackStateFooter() 12 | { 13 | /** 显示刷新状态的label */ 14 | __unsafe_unretained UILabel *_stateLabel; 15 | } 16 | /** 所有状态对应的文字 */ 17 | @property (strong, nonatomic) NSMutableDictionary *stateTitles; 18 | @end 19 | 20 | @implementation MJRefreshBackStateFooter 21 | #pragma mark - 懒加载 22 | - (NSMutableDictionary *)stateTitles 23 | { 24 | if (!_stateTitles) { 25 | self.stateTitles = [NSMutableDictionary dictionary]; 26 | } 27 | return _stateTitles; 28 | } 29 | 30 | - (UILabel *)stateLabel 31 | { 32 | if (!_stateLabel) { 33 | [self addSubview:_stateLabel = [UILabel mj_label]]; 34 | } 35 | return _stateLabel; 36 | } 37 | 38 | #pragma mark - 公共方法 39 | - (void)setTitle:(NSString *)title forState:(MJRefreshState)state 40 | { 41 | if (title == nil) return; 42 | self.stateTitles[@(state)] = title; 43 | self.stateLabel.text = self.stateTitles[@(self.state)]; 44 | } 45 | 46 | - (NSString *)titleForState:(MJRefreshState)state { 47 | return self.stateTitles[@(state)]; 48 | } 49 | 50 | #pragma mark - 重写父类的方法 51 | - (void)prepare 52 | { 53 | [super prepare]; 54 | 55 | // 初始化间距 56 | self.labelLeftInset = MJRefreshLabelLeftInset; 57 | 58 | // 初始化文字 59 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterIdleText] forState:MJRefreshStateIdle]; 60 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterPullingText] forState:MJRefreshStatePulling]; 61 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterRefreshingText] forState:MJRefreshStateRefreshing]; 62 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshBackFooterNoMoreDataText] forState:MJRefreshStateNoMoreData]; 63 | } 64 | 65 | - (void)placeSubviews 66 | { 67 | [super placeSubviews]; 68 | 69 | if (self.stateLabel.constraints.count) return; 70 | 71 | // 状态标签 72 | self.stateLabel.frame = self.bounds; 73 | } 74 | 75 | - (void)setState:(MJRefreshState)state 76 | { 77 | MJRefreshCheckState 78 | 79 | // 设置状态文字 80 | self.stateLabel.text = self.stateTitles[@(state)]; 81 | } 82 | @end 83 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Header/MJRefreshGifHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshGifHeader.h 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshStateHeader.h" 10 | 11 | @interface MJRefreshGifHeader : MJRefreshStateHeader 12 | @property (weak, nonatomic, readonly) UIImageView *gifView; 13 | 14 | /** 设置state状态下的动画图片images 动画持续时间duration*/ 15 | - (void)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state; 16 | - (void)setImages:(NSArray *)images forState:(MJRefreshState)state; 17 | @end 18 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Header/MJRefreshGifHeader.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshGifHeader.m 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshGifHeader.h" 10 | 11 | @interface MJRefreshGifHeader() 12 | { 13 | __unsafe_unretained UIImageView *_gifView; 14 | } 15 | /** 所有状态对应的动画图片 */ 16 | @property (strong, nonatomic) NSMutableDictionary *stateImages; 17 | /** 所有状态对应的动画时间 */ 18 | @property (strong, nonatomic) NSMutableDictionary *stateDurations; 19 | @end 20 | 21 | @implementation MJRefreshGifHeader 22 | #pragma mark - 懒加载 23 | - (UIImageView *)gifView 24 | { 25 | if (!_gifView) { 26 | UIImageView *gifView = [[UIImageView alloc] init]; 27 | [self addSubview:_gifView = gifView]; 28 | } 29 | return _gifView; 30 | } 31 | 32 | - (NSMutableDictionary *)stateImages 33 | { 34 | if (!_stateImages) { 35 | self.stateImages = [NSMutableDictionary dictionary]; 36 | } 37 | return _stateImages; 38 | } 39 | 40 | - (NSMutableDictionary *)stateDurations 41 | { 42 | if (!_stateDurations) { 43 | self.stateDurations = [NSMutableDictionary dictionary]; 44 | } 45 | return _stateDurations; 46 | } 47 | 48 | #pragma mark - 公共方法 49 | - (void)setImages:(NSArray *)images duration:(NSTimeInterval)duration forState:(MJRefreshState)state 50 | { 51 | if (images == nil) return; 52 | 53 | self.stateImages[@(state)] = images; 54 | self.stateDurations[@(state)] = @(duration); 55 | 56 | /* 根据图片设置控件的高度 */ 57 | UIImage *image = [images firstObject]; 58 | if (image.size.height > self.mj_h) { 59 | self.mj_h = image.size.height; 60 | } 61 | } 62 | 63 | - (void)setImages:(NSArray *)images forState:(MJRefreshState)state 64 | { 65 | [self setImages:images duration:images.count * 0.1 forState:state]; 66 | } 67 | 68 | #pragma mark - 实现父类的方法 69 | - (void)prepare 70 | { 71 | [super prepare]; 72 | 73 | // 初始化间距 74 | self.labelLeftInset = 20; 75 | } 76 | 77 | - (void)setPullingPercent:(CGFloat)pullingPercent 78 | { 79 | [super setPullingPercent:pullingPercent]; 80 | NSArray *images = self.stateImages[@(MJRefreshStateIdle)]; 81 | if (self.state != MJRefreshStateIdle || images.count == 0) return; 82 | // 停止动画 83 | [self.gifView stopAnimating]; 84 | // 设置当前需要显示的图片 85 | NSUInteger index = images.count * pullingPercent; 86 | if (index >= images.count) index = images.count - 1; 87 | self.gifView.image = images[index]; 88 | } 89 | 90 | - (void)placeSubviews 91 | { 92 | [super placeSubviews]; 93 | 94 | if (self.gifView.constraints.count) return; 95 | 96 | self.gifView.frame = self.bounds; 97 | if (self.stateLabel.hidden && self.lastUpdatedTimeLabel.hidden) { 98 | self.gifView.contentMode = UIViewContentModeCenter; 99 | } else { 100 | self.gifView.contentMode = UIViewContentModeRight; 101 | 102 | CGFloat stateWidth = self.stateLabel.mj_textWith; 103 | CGFloat timeWidth = 0.0; 104 | if (!self.lastUpdatedTimeLabel.hidden) { 105 | timeWidth = self.lastUpdatedTimeLabel.mj_textWith; 106 | } 107 | CGFloat textWidth = MAX(stateWidth, timeWidth); 108 | self.gifView.mj_w = self.mj_w * 0.5 - textWidth * 0.5 - self.labelLeftInset; 109 | } 110 | } 111 | 112 | - (void)setState:(MJRefreshState)state 113 | { 114 | MJRefreshCheckState 115 | 116 | // 根据状态做事情 117 | if (state == MJRefreshStatePulling || state == MJRefreshStateRefreshing) { 118 | NSArray *images = self.stateImages[@(state)]; 119 | if (images.count == 0) return; 120 | 121 | [self.gifView stopAnimating]; 122 | if (images.count == 1) { // 单张图片 123 | self.gifView.image = [images lastObject]; 124 | } else { // 多张图片 125 | self.gifView.animationImages = images; 126 | self.gifView.animationDuration = [self.stateDurations[@(state)] doubleValue]; 127 | [self.gifView startAnimating]; 128 | } 129 | } else if (state == MJRefreshStateIdle) { 130 | [self.gifView stopAnimating]; 131 | } 132 | } 133 | @end 134 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Header/MJRefreshNormalHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshNormalHeader.h 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshStateHeader.h" 10 | 11 | @interface MJRefreshNormalHeader : MJRefreshStateHeader 12 | @property (weak, nonatomic, readonly) UIImageView *arrowView; 13 | /** 菊花的样式 */ 14 | @property (assign, nonatomic) UIActivityIndicatorViewStyle activityIndicatorViewStyle; 15 | @end 16 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Header/MJRefreshNormalHeader.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshNormalHeader.m 3 | // MJRefreshExample 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 | 12 | @interface MJRefreshNormalHeader() 13 | { 14 | __unsafe_unretained UIImageView *_arrowView; 15 | } 16 | @property (weak, nonatomic) UIActivityIndicatorView *loadingView; 17 | @end 18 | 19 | @implementation MJRefreshNormalHeader 20 | #pragma mark - 懒加载子控件 21 | - (UIImageView *)arrowView 22 | { 23 | if (!_arrowView) { 24 | UIImageView *arrowView = [[UIImageView alloc] initWithImage:[NSBundle mj_arrowImage]]; 25 | [self addSubview:_arrowView = arrowView]; 26 | } 27 | return _arrowView; 28 | } 29 | 30 | - (UIActivityIndicatorView *)loadingView 31 | { 32 | if (!_loadingView) { 33 | UIActivityIndicatorView *loadingView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:self.activityIndicatorViewStyle]; 34 | loadingView.hidesWhenStopped = YES; 35 | [self addSubview:_loadingView = loadingView]; 36 | } 37 | return _loadingView; 38 | } 39 | 40 | #pragma mark - 公共方法 41 | - (void)setActivityIndicatorViewStyle:(UIActivityIndicatorViewStyle)activityIndicatorViewStyle 42 | { 43 | _activityIndicatorViewStyle = activityIndicatorViewStyle; 44 | 45 | self.loadingView = nil; 46 | [self setNeedsLayout]; 47 | } 48 | 49 | #pragma mark - 重写父类的方法 50 | - (void)prepare 51 | { 52 | [super prepare]; 53 | 54 | self.activityIndicatorViewStyle = UIActivityIndicatorViewStyleGray; 55 | } 56 | 57 | - (void)placeSubviews 58 | { 59 | [super placeSubviews]; 60 | 61 | // 箭头的中心点 62 | CGFloat arrowCenterX = self.mj_w * 0.5; 63 | if (!self.stateLabel.hidden) { 64 | CGFloat stateWidth = self.stateLabel.mj_textWith; 65 | CGFloat timeWidth = 0.0; 66 | if (!self.lastUpdatedTimeLabel.hidden) { 67 | timeWidth = self.lastUpdatedTimeLabel.mj_textWith; 68 | } 69 | CGFloat textWidth = MAX(stateWidth, timeWidth); 70 | arrowCenterX -= textWidth / 2 + self.labelLeftInset; 71 | } 72 | CGFloat arrowCenterY = self.mj_h * 0.5; 73 | CGPoint arrowCenter = CGPointMake(arrowCenterX, arrowCenterY); 74 | 75 | // 箭头 76 | if (self.arrowView.constraints.count == 0) { 77 | self.arrowView.mj_size = self.arrowView.image.size; 78 | self.arrowView.center = arrowCenter; 79 | } 80 | 81 | // 圈圈 82 | if (self.loadingView.constraints.count == 0) { 83 | self.loadingView.center = arrowCenter; 84 | } 85 | 86 | self.arrowView.tintColor = self.stateLabel.textColor; 87 | } 88 | 89 | - (void)setState:(MJRefreshState)state 90 | { 91 | MJRefreshCheckState 92 | 93 | // 根据状态做事情 94 | if (state == MJRefreshStateIdle) { 95 | if (oldState == MJRefreshStateRefreshing) { 96 | self.arrowView.transform = CGAffineTransformIdentity; 97 | 98 | [UIView animateWithDuration:MJRefreshSlowAnimationDuration animations:^{ 99 | self.loadingView.alpha = 0.0; 100 | } completion:^(BOOL finished) { 101 | // 如果执行完动画发现不是idle状态,就直接返回,进入其他状态 102 | if (self.state != MJRefreshStateIdle) return; 103 | 104 | self.loadingView.alpha = 1.0; 105 | [self.loadingView stopAnimating]; 106 | self.arrowView.hidden = NO; 107 | }]; 108 | } else { 109 | [self.loadingView stopAnimating]; 110 | self.arrowView.hidden = NO; 111 | [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{ 112 | self.arrowView.transform = CGAffineTransformIdentity; 113 | }]; 114 | } 115 | } else if (state == MJRefreshStatePulling) { 116 | [self.loadingView stopAnimating]; 117 | self.arrowView.hidden = NO; 118 | [UIView animateWithDuration:MJRefreshFastAnimationDuration animations:^{ 119 | self.arrowView.transform = CGAffineTransformMakeRotation(0.000001 - M_PI); 120 | }]; 121 | } else if (state == MJRefreshStateRefreshing) { 122 | self.loadingView.alpha = 1.0; // 防止refreshing -> idle的动画完毕动作没有被执行 123 | [self.loadingView startAnimating]; 124 | self.arrowView.hidden = YES; 125 | } 126 | } 127 | @end 128 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Header/MJRefreshStateHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshStateHeader.h 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshHeader.h" 10 | 11 | @interface MJRefreshStateHeader : MJRefreshHeader 12 | #pragma mark - 刷新时间相关 13 | /** 利用这个block来决定显示的更新时间文字 */ 14 | @property (copy, nonatomic) NSString *(^lastUpdatedTimeText)(NSDate *lastUpdatedTime); 15 | /** 显示上一次刷新时间的label */ 16 | @property (weak, nonatomic, readonly) UILabel *lastUpdatedTimeLabel; 17 | 18 | #pragma mark - 状态相关 19 | /** 文字距离圈圈、箭头的距离 */ 20 | @property (assign, nonatomic) CGFloat labelLeftInset; 21 | /** 显示刷新状态的label */ 22 | @property (weak, nonatomic, readonly) UILabel *stateLabel; 23 | /** 设置state状态下的文字 */ 24 | - (void)setTitle:(NSString *)title forState:(MJRefreshState)state; 25 | @end 26 | -------------------------------------------------------------------------------- /ios/MJRefresh/Custom/Header/MJRefreshStateHeader.m: -------------------------------------------------------------------------------- 1 | // 2 | // MJRefreshStateHeader.m 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 15/4/24. 6 | // Copyright (c) 2015年 小码哥. All rights reserved. 7 | // 8 | 9 | #import "MJRefreshStateHeader.h" 10 | 11 | @interface MJRefreshStateHeader() 12 | { 13 | /** 显示上一次刷新时间的label */ 14 | __unsafe_unretained UILabel *_lastUpdatedTimeLabel; 15 | /** 显示刷新状态的label */ 16 | __unsafe_unretained UILabel *_stateLabel; 17 | } 18 | /** 所有状态对应的文字 */ 19 | @property (strong, nonatomic) NSMutableDictionary *stateTitles; 20 | @end 21 | 22 | @implementation MJRefreshStateHeader 23 | #pragma mark - 懒加载 24 | - (NSMutableDictionary *)stateTitles 25 | { 26 | if (!_stateTitles) { 27 | self.stateTitles = [NSMutableDictionary dictionary]; 28 | } 29 | return _stateTitles; 30 | } 31 | 32 | - (UILabel *)stateLabel 33 | { 34 | if (!_stateLabel) { 35 | [self addSubview:_stateLabel = [UILabel mj_label]]; 36 | } 37 | return _stateLabel; 38 | } 39 | 40 | - (UILabel *)lastUpdatedTimeLabel 41 | { 42 | if (!_lastUpdatedTimeLabel) { 43 | [self addSubview:_lastUpdatedTimeLabel = [UILabel mj_label]]; 44 | } 45 | return _lastUpdatedTimeLabel; 46 | } 47 | 48 | #pragma mark - 公共方法 49 | - (void)setTitle:(NSString *)title forState:(MJRefreshState)state 50 | { 51 | if (title == nil) return; 52 | self.stateTitles[@(state)] = title; 53 | self.stateLabel.text = self.stateTitles[@(self.state)]; 54 | } 55 | 56 | #pragma mark - 日历获取在9.x之后的系统使用currentCalendar会出异常。在8.0之后使用系统新API。 57 | - (NSCalendar *)currentCalendar { 58 | if ([NSCalendar respondsToSelector:@selector(calendarWithIdentifier:)]) { 59 | return [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]; 60 | } 61 | return [NSCalendar currentCalendar]; 62 | } 63 | 64 | #pragma mark key的处理 65 | - (void)setLastUpdatedTimeKey:(NSString *)lastUpdatedTimeKey 66 | { 67 | [super setLastUpdatedTimeKey:lastUpdatedTimeKey]; 68 | 69 | // 如果label隐藏了,就不用再处理 70 | if (self.lastUpdatedTimeLabel.hidden) return; 71 | 72 | NSDate *lastUpdatedTime = [[NSUserDefaults standardUserDefaults] objectForKey:lastUpdatedTimeKey]; 73 | 74 | // 如果有block 75 | if (self.lastUpdatedTimeText) { 76 | self.lastUpdatedTimeLabel.text = self.lastUpdatedTimeText(lastUpdatedTime); 77 | return; 78 | } 79 | 80 | if (lastUpdatedTime) { 81 | // 1.获得年月日 82 | NSCalendar *calendar = [self currentCalendar]; 83 | NSUInteger unitFlags = NSCalendarUnitYear| NSCalendarUnitMonth | NSCalendarUnitDay |NSCalendarUnitHour |NSCalendarUnitMinute; 84 | NSDateComponents *cmp1 = [calendar components:unitFlags fromDate:lastUpdatedTime]; 85 | NSDateComponents *cmp2 = [calendar components:unitFlags fromDate:[NSDate date]]; 86 | 87 | // 2.格式化日期 88 | NSDateFormatter *formatter = [[NSDateFormatter alloc] init]; 89 | BOOL isToday = NO; 90 | if ([cmp1 day] == [cmp2 day]) { // 今天 91 | formatter.dateFormat = @" HH:mm"; 92 | isToday = YES; 93 | } else if ([cmp1 year] == [cmp2 year]) { // 今年 94 | formatter.dateFormat = @"MM-dd HH:mm"; 95 | } else { 96 | formatter.dateFormat = @"yyyy-MM-dd HH:mm"; 97 | } 98 | NSString *time = [formatter stringFromDate:lastUpdatedTime]; 99 | 100 | // 3.显示日期 101 | self.lastUpdatedTimeLabel.text = [NSString stringWithFormat:@"%@%@%@", 102 | [NSBundle mj_localizedStringForKey:MJRefreshHeaderLastTimeText], 103 | isToday ? [NSBundle mj_localizedStringForKey:MJRefreshHeaderDateTodayText] : @"", 104 | time]; 105 | } else { 106 | self.lastUpdatedTimeLabel.text = [NSString stringWithFormat:@"%@%@", 107 | [NSBundle mj_localizedStringForKey:MJRefreshHeaderLastTimeText], 108 | [NSBundle mj_localizedStringForKey:MJRefreshHeaderNoneLastDateText]]; 109 | } 110 | } 111 | 112 | #pragma mark - 覆盖父类的方法 113 | - (void)prepare 114 | { 115 | [super prepare]; 116 | 117 | // 初始化间距 118 | self.labelLeftInset = MJRefreshLabelLeftInset; 119 | 120 | // 初始化文字 121 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderIdleText] forState:MJRefreshStateIdle]; 122 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderPullingText] forState:MJRefreshStatePulling]; 123 | [self setTitle:[NSBundle mj_localizedStringForKey:MJRefreshHeaderRefreshingText] forState:MJRefreshStateRefreshing]; 124 | } 125 | 126 | - (void)placeSubviews 127 | { 128 | [super placeSubviews]; 129 | 130 | if (self.stateLabel.hidden) return; 131 | 132 | BOOL noConstrainsOnStatusLabel = self.stateLabel.constraints.count == 0; 133 | 134 | if (self.lastUpdatedTimeLabel.hidden) { 135 | // 状态 136 | if (noConstrainsOnStatusLabel) self.stateLabel.frame = self.bounds; 137 | } else { 138 | CGFloat stateLabelH = self.mj_h * 0.5; 139 | // 状态 140 | if (noConstrainsOnStatusLabel) { 141 | self.stateLabel.mj_x = 0; 142 | self.stateLabel.mj_y = 0; 143 | self.stateLabel.mj_w = self.mj_w; 144 | self.stateLabel.mj_h = stateLabelH; 145 | } 146 | 147 | // 更新时间 148 | if (self.lastUpdatedTimeLabel.constraints.count == 0) { 149 | self.lastUpdatedTimeLabel.mj_x = 0; 150 | self.lastUpdatedTimeLabel.mj_y = stateLabelH; 151 | self.lastUpdatedTimeLabel.mj_w = self.mj_w; 152 | self.lastUpdatedTimeLabel.mj_h = self.mj_h - self.lastUpdatedTimeLabel.mj_y; 153 | } 154 | } 155 | } 156 | 157 | - (void)setState:(MJRefreshState)state 158 | { 159 | MJRefreshCheckState 160 | 161 | // 设置状态文字 162 | self.stateLabel.text = self.stateTitles[@(state)]; 163 | 164 | // 重新设置key(重新显示时间) 165 | self.lastUpdatedTimeKey = self.lastUpdatedTimeKey; 166 | } 167 | @end 168 | -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefresh.bundle/arrow@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitSirzh/react-native-refresh-control-enrichment/HEAD/ios/MJRefresh/MJRefresh.bundle/arrow@2x.png -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefresh.bundle/en.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitSirzh/react-native-refresh-control-enrichment/HEAD/ios/MJRefresh/MJRefresh.bundle/en.lproj/Localizable.strings -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefresh.bundle/zh-Hans.lproj/Localizable.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitSirzh/react-native-refresh-control-enrichment/HEAD/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 | "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.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // 代码地址: http://code4app.com/ios/%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E4%B8%8B%E6%8B%89%E4%B8%8A%E6%8B%89%E5%88%B7%E6%96%B0/52326ce26803fabc46000000 3 | 4 | #import "UIScrollView+MJRefresh.h" 5 | #import "UIScrollView+MJExtension.h" 6 | #import "UIView+MJExtension.h" 7 | 8 | #import "MJRefreshNormalHeader.h" 9 | #import "MJRefreshGifHeader.h" 10 | 11 | #import "MJRefreshBackNormalFooter.h" 12 | #import "MJRefreshBackGifFooter.h" 13 | #import "MJRefreshAutoNormalFooter.h" 14 | #import "MJRefreshAutoGifFooter.h" -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefreshConst.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // 代码地址: http://code4app.com/ios/%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E4%B8%8B%E6%8B%89%E4%B8%8A%E6%8B%89%E5%88%B7%E6%96%B0/52326ce26803fabc46000000 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(instead) NS_DEPRECATED(2_0, 2_0, 2_0, 2_0, instead) 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 MJRefreshFastAnimationDuration; 37 | UIKIT_EXTERN const CGFloat MJRefreshSlowAnimationDuration; 38 | 39 | UIKIT_EXTERN NSString *const MJRefreshKeyPathContentOffset; 40 | UIKIT_EXTERN NSString *const MJRefreshKeyPathContentSize; 41 | UIKIT_EXTERN NSString *const MJRefreshKeyPathContentInset; 42 | UIKIT_EXTERN NSString *const MJRefreshKeyPathPanState; 43 | 44 | UIKIT_EXTERN NSString *const MJRefreshHeaderLastUpdatedTimeKey; 45 | 46 | UIKIT_EXTERN NSString *const MJRefreshHeaderIdleText; 47 | UIKIT_EXTERN NSString *const MJRefreshHeaderPullingText; 48 | UIKIT_EXTERN NSString *const MJRefreshHeaderRefreshingText; 49 | 50 | UIKIT_EXTERN NSString *const MJRefreshAutoFooterIdleText; 51 | UIKIT_EXTERN NSString *const MJRefreshAutoFooterRefreshingText; 52 | UIKIT_EXTERN NSString *const MJRefreshAutoFooterNoMoreDataText; 53 | 54 | UIKIT_EXTERN NSString *const MJRefreshBackFooterIdleText; 55 | UIKIT_EXTERN NSString *const MJRefreshBackFooterPullingText; 56 | UIKIT_EXTERN NSString *const MJRefreshBackFooterRefreshingText; 57 | UIKIT_EXTERN NSString *const MJRefreshBackFooterNoMoreDataText; 58 | 59 | UIKIT_EXTERN NSString *const MJRefreshHeaderLastTimeText; 60 | UIKIT_EXTERN NSString *const MJRefreshHeaderDateTodayText; 61 | UIKIT_EXTERN NSString *const MJRefreshHeaderNoneLastDateText; 62 | 63 | // 状态检查 64 | #define MJRefreshCheckState \ 65 | MJRefreshState oldState = self.state; \ 66 | if (state == oldState) return; \ 67 | [super setState:state]; 68 | -------------------------------------------------------------------------------- /ios/MJRefresh/MJRefreshConst.m: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // 代码地址: http://code4app.com/ios/%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E4%B8%8B%E6%8B%89%E4%B8%8A%E6%8B%89%E5%88%B7%E6%96%B0/52326ce26803fabc46000000 3 | #import 4 | 5 | const CGFloat MJRefreshLabelLeftInset = 25; 6 | const CGFloat MJRefreshHeaderHeight = 54.0; 7 | const CGFloat MJRefreshFooterHeight = 44.0; 8 | const CGFloat MJRefreshFastAnimationDuration = 0.25; 9 | const CGFloat MJRefreshSlowAnimationDuration = 0.4; 10 | 11 | NSString *const MJRefreshKeyPathContentOffset = @"contentOffset"; 12 | NSString *const MJRefreshKeyPathContentInset = @"contentInset"; 13 | NSString *const MJRefreshKeyPathContentSize = @"contentSize"; 14 | NSString *const MJRefreshKeyPathPanState = @"state"; 15 | 16 | NSString *const MJRefreshHeaderLastUpdatedTimeKey = @"MJRefreshHeaderLastUpdatedTimeKey"; 17 | 18 | NSString *const MJRefreshHeaderIdleText = @"MJRefreshHeaderIdleText"; 19 | NSString *const MJRefreshHeaderPullingText = @"MJRefreshHeaderPullingText"; 20 | NSString *const MJRefreshHeaderRefreshingText = @"MJRefreshHeaderRefreshingText"; 21 | 22 | NSString *const MJRefreshAutoFooterIdleText = @"MJRefreshAutoFooterIdleText"; 23 | NSString *const MJRefreshAutoFooterRefreshingText = @"MJRefreshAutoFooterRefreshingText"; 24 | NSString *const MJRefreshAutoFooterNoMoreDataText = @"MJRefreshAutoFooterNoMoreDataText"; 25 | 26 | NSString *const MJRefreshBackFooterIdleText = @"MJRefreshBackFooterIdleText"; 27 | NSString *const MJRefreshBackFooterPullingText = @"MJRefreshBackFooterPullingText"; 28 | NSString *const MJRefreshBackFooterRefreshingText = @"MJRefreshBackFooterRefreshingText"; 29 | NSString *const MJRefreshBackFooterNoMoreDataText = @"MJRefreshBackFooterNoMoreDataText"; 30 | 31 | NSString *const MJRefreshHeaderLastTimeText = @"MJRefreshHeaderLastTimeText"; 32 | NSString *const MJRefreshHeaderDateTodayText = @"MJRefreshHeaderDateTodayText"; 33 | NSString *const MJRefreshHeaderNoneLastDateText = @"MJRefreshHeaderNoneLastDateText"; -------------------------------------------------------------------------------- /ios/MJRefresh/NSBundle+MJRefresh.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+MJRefresh.h 3 | // MJRefreshExample 4 | // 5 | // Created by MJ Lee on 16/6/13. 6 | // Copyright © 2016年 小码哥. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NSBundle (MJRefresh) 12 | + (instancetype)mj_refreshBundle; 13 | + (UIImage *)mj_arrowImage; 14 | + (NSString *)mj_localizedStringForKey:(NSString *)key value:(NSString *)value; 15 | + (NSString *)mj_localizedStringForKey:(NSString *)key; 16 | @end 17 | -------------------------------------------------------------------------------- /ios/MJRefresh/NSBundle+MJRefresh.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSBundle+MJRefresh.m 3 | // MJRefreshExample 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 | 12 | @implementation NSBundle (MJRefresh) 13 | + (instancetype)mj_refreshBundle 14 | { 15 | static NSBundle *refreshBundle = nil; 16 | if (refreshBundle == nil) { 17 | // 这里不使用mainBundle是为了适配pod 1.x和0.x 18 | refreshBundle = [NSBundle bundleWithPath:[[NSBundle bundleForClass:[MJRefreshComponent class]] pathForResource:@"MJRefresh" ofType:@"bundle"]]; 19 | } 20 | return refreshBundle; 21 | } 22 | 23 | + (UIImage *)mj_arrowImage 24 | { 25 | static UIImage *arrowImage = nil; 26 | if (arrowImage == nil) { 27 | arrowImage = [[UIImage imageWithContentsOfFile:[[self mj_refreshBundle] pathForResource:@"arrow@2x" ofType:@"png"]] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate]; 28 | } 29 | return arrowImage; 30 | } 31 | 32 | + (NSString *)mj_localizedStringForKey:(NSString *)key 33 | { 34 | return [self mj_localizedStringForKey:key value:nil]; 35 | } 36 | 37 | + (NSString *)mj_localizedStringForKey:(NSString *)key value:(NSString *)value 38 | { 39 | static NSBundle *bundle = nil; 40 | if (bundle == nil) { 41 | // (iOS获取的语言字符串比较不稳定)目前框架只处理en、zh-Hans、zh-Hant三种情况,其他按照系统默认处理 42 | NSString *language = [NSLocale preferredLanguages].firstObject; 43 | if ([language hasPrefix:@"en"]) { 44 | language = @"en"; 45 | } else if ([language hasPrefix:@"zh"]) { 46 | if ([language rangeOfString:@"Hans"].location != NSNotFound) { 47 | language = @"zh-Hans"; // 简体中文 48 | } else { // zh-Hant\zh-HK\zh-TW 49 | language = @"zh-Hant"; // 繁體中文 50 | } 51 | } else { 52 | language = @"en"; 53 | } 54 | 55 | // 从MJRefresh.bundle中查找资源 56 | bundle = [NSBundle bundleWithPath:[[NSBundle mj_refreshBundle] pathForResource:language ofType:@"lproj"]]; 57 | } 58 | value = [bundle localizedStringForKey:key value:value table:nil]; 59 | return [[NSBundle mainBundle] localizedStringForKey:key value:value table:nil]; 60 | } 61 | @end 62 | -------------------------------------------------------------------------------- /ios/MJRefresh/UIScrollView+MJExtension.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // 代码地址: http://code4app.com/ios/%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E4%B8%8B%E6%8B%89%E4%B8%8A%E6%8B%89%E5%88%B7%E6%96%B0/52326ce26803fabc46000000 3 | // UIScrollView+Extension.h 4 | // MJRefreshExample 5 | // 6 | // Created by MJ Lee on 14-5-28. 7 | // Copyright (c) 2014年 小码哥. All rights reserved. 8 | // 9 | 10 | #import 11 | 12 | @interface UIScrollView (MJExtension) 13 | @property (readonly, nonatomic) UIEdgeInsets mj_inset; 14 | 15 | @property (assign, nonatomic) CGFloat mj_insetT; 16 | @property (assign, nonatomic) CGFloat mj_insetB; 17 | @property (assign, nonatomic) CGFloat mj_insetL; 18 | @property (assign, nonatomic) CGFloat mj_insetR; 19 | 20 | @property (assign, nonatomic) CGFloat mj_offsetX; 21 | @property (assign, nonatomic) CGFloat mj_offsetY; 22 | 23 | @property (assign, nonatomic) CGFloat mj_contentW; 24 | @property (assign, nonatomic) CGFloat mj_contentH; 25 | @end 26 | -------------------------------------------------------------------------------- /ios/MJRefresh/UIScrollView+MJExtension.m: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // 代码地址: http://code4app.com/ios/%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E4%B8%8B%E6%8B%89%E4%B8%8A%E6%8B%89%E5%88%B7%E6%96%B0/52326ce26803fabc46000000 3 | // UIScrollView+Extension.m 4 | // MJRefreshExample 5 | // 6 | // Created by MJ Lee on 14-5-28. 7 | // Copyright (c) 2014年 小码哥. All rights reserved. 8 | // 9 | 10 | #import "UIScrollView+MJExtension.h" 11 | #import 12 | 13 | #pragma clang diagnostic push 14 | #pragma clang diagnostic ignored "-Wunguarded-availability-new" 15 | 16 | @implementation UIScrollView (MJExtension) 17 | 18 | static BOOL gt_ios_11_; 19 | + (void)load 20 | { 21 | // 缓存判断值 22 | gt_ios_11_ = ([[[UIDevice currentDevice] systemVersion] compare:@"11" options:NSNumericSearch] != NSOrderedAscending); 23 | } 24 | 25 | - (UIEdgeInsets)mj_inset 26 | { 27 | #ifdef __IPHONE_11_0 28 | if (gt_ios_11_) { 29 | return self.adjustedContentInset; 30 | } 31 | #endif 32 | return self.contentInset; 33 | } 34 | 35 | - (void)setMj_insetT:(CGFloat)mj_insetT 36 | { 37 | UIEdgeInsets inset = self.contentInset; 38 | inset.top = mj_insetT; 39 | #ifdef __IPHONE_11_0 40 | if (gt_ios_11_) { 41 | inset.top -= (self.adjustedContentInset.top - self.contentInset.top); 42 | } 43 | #endif 44 | self.contentInset = inset; 45 | } 46 | 47 | - (CGFloat)mj_insetT 48 | { 49 | return self.mj_inset.top; 50 | } 51 | 52 | - (void)setMj_insetB:(CGFloat)mj_insetB 53 | { 54 | UIEdgeInsets inset = self.contentInset; 55 | inset.bottom = mj_insetB; 56 | #ifdef __IPHONE_11_0 57 | if (gt_ios_11_) { 58 | inset.bottom -= (self.adjustedContentInset.bottom - self.contentInset.bottom); 59 | } 60 | #endif 61 | self.contentInset = inset; 62 | } 63 | 64 | - (CGFloat)mj_insetB 65 | { 66 | return self.mj_inset.bottom; 67 | } 68 | 69 | - (void)setMj_insetL:(CGFloat)mj_insetL 70 | { 71 | UIEdgeInsets inset = self.contentInset; 72 | inset.left = mj_insetL; 73 | #ifdef __IPHONE_11_0 74 | if (gt_ios_11_) { 75 | inset.left -= (self.adjustedContentInset.left - self.contentInset.left); 76 | } 77 | #endif 78 | self.contentInset = inset; 79 | } 80 | 81 | - (CGFloat)mj_insetL 82 | { 83 | return self.mj_inset.left; 84 | } 85 | 86 | - (void)setMj_insetR:(CGFloat)mj_insetR 87 | { 88 | UIEdgeInsets inset = self.contentInset; 89 | inset.right = mj_insetR; 90 | #ifdef __IPHONE_11_0 91 | if (gt_ios_11_) { 92 | inset.right -= (self.adjustedContentInset.right - self.contentInset.right); 93 | } 94 | #endif 95 | self.contentInset = inset; 96 | } 97 | 98 | - (CGFloat)mj_insetR 99 | { 100 | return self.mj_inset.right; 101 | } 102 | 103 | - (void)setMj_offsetX:(CGFloat)mj_offsetX 104 | { 105 | CGPoint offset = self.contentOffset; 106 | offset.x = mj_offsetX; 107 | self.contentOffset = offset; 108 | } 109 | 110 | - (CGFloat)mj_offsetX 111 | { 112 | return self.contentOffset.x; 113 | } 114 | 115 | - (void)setMj_offsetY:(CGFloat)mj_offsetY 116 | { 117 | CGPoint offset = self.contentOffset; 118 | offset.y = mj_offsetY; 119 | self.contentOffset = offset; 120 | } 121 | 122 | - (CGFloat)mj_offsetY 123 | { 124 | return self.contentOffset.y; 125 | } 126 | 127 | - (void)setMj_contentW:(CGFloat)mj_contentW 128 | { 129 | CGSize size = self.contentSize; 130 | size.width = mj_contentW; 131 | self.contentSize = size; 132 | } 133 | 134 | - (CGFloat)mj_contentW 135 | { 136 | return self.contentSize.width; 137 | } 138 | 139 | - (void)setMj_contentH:(CGFloat)mj_contentH 140 | { 141 | CGSize size = self.contentSize; 142 | size.height = mj_contentH; 143 | self.contentSize = size; 144 | } 145 | 146 | - (CGFloat)mj_contentH 147 | { 148 | return self.contentSize.height; 149 | } 150 | @end 151 | #pragma clang diagnostic pop 152 | -------------------------------------------------------------------------------- /ios/MJRefresh/UIScrollView+MJRefresh.h: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // 代码地址: http://code4app.com/ios/%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E4%B8%8B%E6%8B%89%E4%B8%8A%E6%8B%89%E5%88%B7%E6%96%B0/52326ce26803fabc46000000 3 | // UIScrollView+MJRefresh.h 4 | // MJRefreshExample 5 | // 6 | // Created by MJ Lee on 15/3/4. 7 | // Copyright (c) 2015年 小码哥. All rights reserved. 8 | // 给ScrollView增加下拉刷新、上拉刷新的功能 9 | 10 | #import 11 | #import "MJRefreshConst.h" 12 | 13 | @class MJRefreshHeader, MJRefreshFooter; 14 | 15 | @interface UIScrollView (MJRefresh) 16 | /** 下拉刷新控件 */ 17 | @property (strong, nonatomic) MJRefreshHeader *mj_header; 18 | @property (strong, nonatomic) MJRefreshHeader *header MJRefreshDeprecated("使用mj_header"); 19 | /** 上拉刷新控件 */ 20 | @property (strong, nonatomic) MJRefreshFooter *mj_footer; 21 | @property (strong, nonatomic) MJRefreshFooter *footer MJRefreshDeprecated("使用mj_footer"); 22 | 23 | #pragma mark - other 24 | - (NSInteger)mj_totalDataCount; 25 | @property (copy, nonatomic) void (^mj_reloadDataBlock)(NSInteger totalDataCount); 26 | @end 27 | -------------------------------------------------------------------------------- /ios/MJRefresh/UIScrollView+MJRefresh.m: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // 代码地址: http://code4app.com/ios/%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E4%B8%8B%E6%8B%89%E4%B8%8A%E6%8B%89%E5%88%B7%E6%96%B0/52326ce26803fabc46000000 3 | // UIScrollView+MJRefresh.m 4 | // MJRefreshExample 5 | // 6 | // Created by MJ Lee on 15/3/4. 7 | // Copyright (c) 2015年 小码哥. All rights reserved. 8 | // 9 | 10 | #import "UIScrollView+MJRefresh.h" 11 | #import "MJRefreshHeader.h" 12 | #import "MJRefreshFooter.h" 13 | #import 14 | 15 | @implementation NSObject (MJRefresh) 16 | 17 | + (void)exchangeInstanceMethod1:(SEL)method1 method2:(SEL)method2 18 | { 19 | method_exchangeImplementations(class_getInstanceMethod(self, method1), class_getInstanceMethod(self, method2)); 20 | } 21 | 22 | + (void)exchangeClassMethod1:(SEL)method1 method2:(SEL)method2 23 | { 24 | method_exchangeImplementations(class_getClassMethod(self, method1), class_getClassMethod(self, method2)); 25 | } 26 | 27 | @end 28 | 29 | @implementation UIScrollView (MJRefresh) 30 | 31 | #pragma mark - header 32 | static const char MJRefreshHeaderKey = '\0'; 33 | - (void)setMj_header:(MJRefreshHeader *)mj_header 34 | { 35 | if (mj_header != self.mj_header) { 36 | // 删除旧的,添加新的 37 | [self.mj_header removeFromSuperview]; 38 | [self insertSubview:mj_header atIndex:0]; 39 | 40 | // 存储新的 41 | objc_setAssociatedObject(self, &MJRefreshHeaderKey, 42 | mj_header, OBJC_ASSOCIATION_ASSIGN); 43 | } 44 | } 45 | 46 | - (MJRefreshHeader *)mj_header 47 | { 48 | return objc_getAssociatedObject(self, &MJRefreshHeaderKey); 49 | } 50 | 51 | #pragma mark - footer 52 | static const char MJRefreshFooterKey = '\0'; 53 | - (void)setMj_footer:(MJRefreshFooter *)mj_footer 54 | { 55 | if (mj_footer != self.mj_footer) { 56 | // 删除旧的,添加新的 57 | [self.mj_footer removeFromSuperview]; 58 | [self insertSubview:mj_footer atIndex:0]; 59 | 60 | // 存储新的 61 | objc_setAssociatedObject(self, &MJRefreshFooterKey, 62 | mj_footer, OBJC_ASSOCIATION_ASSIGN); 63 | } 64 | } 65 | 66 | - (MJRefreshFooter *)mj_footer 67 | { 68 | return objc_getAssociatedObject(self, &MJRefreshFooterKey); 69 | } 70 | 71 | #pragma mark - 过期 72 | - (void)setFooter:(MJRefreshFooter *)footer 73 | { 74 | self.mj_footer = footer; 75 | } 76 | 77 | - (MJRefreshFooter *)footer 78 | { 79 | return self.mj_footer; 80 | } 81 | 82 | - (void)setHeader:(MJRefreshHeader *)header 83 | { 84 | self.mj_header = header; 85 | } 86 | 87 | - (MJRefreshHeader *)header 88 | { 89 | return self.mj_header; 90 | } 91 | 92 | #pragma mark - other 93 | - (NSInteger)mj_totalDataCount 94 | { 95 | NSInteger totalCount = 0; 96 | if ([self isKindOfClass:[UITableView class]]) { 97 | UITableView *tableView = (UITableView *)self; 98 | 99 | for (NSInteger section = 0; section 11 | 12 | @interface UIView (MJExtension) 13 | @property (assign, nonatomic) CGFloat mj_x; 14 | @property (assign, nonatomic) CGFloat mj_y; 15 | @property (assign, nonatomic) CGFloat mj_w; 16 | @property (assign, nonatomic) CGFloat mj_h; 17 | @property (assign, nonatomic) CGSize mj_size; 18 | @property (assign, nonatomic) CGPoint mj_origin; 19 | @end 20 | -------------------------------------------------------------------------------- /ios/MJRefresh/UIView+MJExtension.m: -------------------------------------------------------------------------------- 1 | // 代码地址: https://github.com/CoderMJLee/MJRefresh 2 | // 代码地址: http://code4app.com/ios/%E5%BF%AB%E9%80%9F%E9%9B%86%E6%88%90%E4%B8%8B%E6%8B%89%E4%B8%8A%E6%8B%89%E5%88%B7%E6%96%B0/52326ce26803fabc46000000 3 | // UIView+Extension.m 4 | // MJRefreshExample 5 | // 6 | // Created by MJ Lee on 14-5-28. 7 | // Copyright (c) 2014年 小码哥. All rights reserved. 8 | // 9 | 10 | #import "UIView+MJExtension.h" 11 | 12 | @implementation UIView (MJExtension) 13 | - (void)setMj_x:(CGFloat)mj_x 14 | { 15 | CGRect frame = self.frame; 16 | frame.origin.x = mj_x; 17 | self.frame = frame; 18 | } 19 | 20 | - (CGFloat)mj_x 21 | { 22 | return self.frame.origin.x; 23 | } 24 | 25 | - (void)setMj_y:(CGFloat)mj_y 26 | { 27 | CGRect frame = self.frame; 28 | frame.origin.y = mj_y; 29 | self.frame = frame; 30 | } 31 | 32 | - (CGFloat)mj_y 33 | { 34 | return self.frame.origin.y; 35 | } 36 | 37 | - (void)setMj_w:(CGFloat)mj_w 38 | { 39 | CGRect frame = self.frame; 40 | frame.size.width = mj_w; 41 | self.frame = frame; 42 | } 43 | 44 | - (CGFloat)mj_w 45 | { 46 | return self.frame.size.width; 47 | } 48 | 49 | - (void)setMj_h:(CGFloat)mj_h 50 | { 51 | CGRect frame = self.frame; 52 | frame.size.height = mj_h; 53 | self.frame = frame; 54 | } 55 | 56 | - (CGFloat)mj_h 57 | { 58 | return self.frame.size.height; 59 | } 60 | 61 | - (void)setMj_size:(CGSize)mj_size 62 | { 63 | CGRect frame = self.frame; 64 | frame.size = mj_size; 65 | self.frame = frame; 66 | } 67 | 68 | - (CGSize)mj_size 69 | { 70 | return self.frame.size; 71 | } 72 | 73 | - (void)setMj_origin:(CGPoint)mj_origin 74 | { 75 | CGRect frame = self.frame; 76 | frame.origin = mj_origin; 77 | self.frame = frame; 78 | } 79 | 80 | - (CGPoint)mj_origin 81 | { 82 | return self.frame.origin; 83 | } 84 | @end 85 | -------------------------------------------------------------------------------- /ios/RCTMJRefreshHeader/RCTMJRefreshHeader.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCTMJRefreshHeader.h 3 | // RCTMJRefreshHeader 4 | // 5 | // Created by Macbook on 2018/6/25. 6 | // Copyright © 2018年 Macbook. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MJRefresh.h" 11 | 12 | @interface RCTMJRefreshHeader : MJRefreshHeader 13 | /** 普通闲置状态 */ 14 | //MJRefreshStateIdle = 1, 15 | /** 松开就可以进行刷新的状态 */ 16 | //MJRefreshStatePulling, 17 | /** 正在刷新中的状态 */ 18 | //MJRefreshStateRefreshing, 19 | /** 即将刷新的状态 */ 20 | //MJRefreshStateWillRefresh, 21 | @property (nonatomic, copy) RCTBubblingEventBlock onMJRefreshIdle; 22 | @property (nonatomic, copy) RCTBubblingEventBlock onMJWillRefresh; 23 | @property (nonatomic, copy) RCTBubblingEventBlock onMJRefresh; 24 | @property (nonatomic, copy) RCTBubblingEventBlock onMJReleaseToRefresh; 25 | @property (nonatomic, copy) RCTBubblingEventBlock onMJPulling; 26 | @end 27 | -------------------------------------------------------------------------------- /ios/RCTMJRefreshHeader/RCTMJRefreshHeader.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCTMJRefreshHeader.m 3 | // RCTMJRefreshHeader 4 | // 5 | // Created by Macbook on 2018/6/25. 6 | // Copyright © 2018年 Macbook. All rights reserved. 7 | // 8 | 9 | #import "RCTMJRefreshHeader.h" 10 | 11 | @implementation RCTMJRefreshHeader 12 | - (void)setState:(MJRefreshState)state 13 | { 14 | MJRefreshCheckState; 15 | 16 | switch (state) { 17 | case MJRefreshStateIdle: 18 | if(self.reactTag)self.onMJRefreshIdle(@{@"target": self.reactTag}); 19 | break; 20 | case MJRefreshStatePulling: 21 | if(self.reactTag)self.onMJReleaseToRefresh(@{@"target":self.reactTag}); 22 | break; 23 | case MJRefreshStateRefreshing: 24 | if(self.reactTag)self.onMJRefresh(@{@"target":self.reactTag}); 25 | break; 26 | default: 27 | break; 28 | } 29 | } 30 | - (void)setPullingPercent:(CGFloat)pullingPercent 31 | { 32 | [super setPullingPercent:pullingPercent]; 33 | if(self.reactTag)self.onMJPulling(@{@"target":self.reactTag,@"percent":[NSNumber numberWithFloat:pullingPercent]}); 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /ios/RCTMJRefreshHeader/RCTMJRefreshViewManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCTMJRefreshViewManager.m 3 | // React 4 | // 5 | // Created by Macbook on 2018/6/21. 6 | // Copyright © 2018年 Facebook. All rights reserved. 7 | // 8 | #import "MJRefresh.h" 9 | #import 10 | #import 11 | #import "RCTMJRefreshHeader.h" 12 | #import 13 | #import 14 | #import 15 | 16 | @interface RCTMJRefreshViewManager:RCTViewManager 17 | @end 18 | 19 | @implementation RCTMJRefreshViewManager 20 | { 21 | RCTMJRefreshHeader *header; 22 | } 23 | 24 | RCT_EXPORT_MODULE() 25 | 26 | -(UIView *)view 27 | { 28 | header=[RCTMJRefreshHeader headerWithRefreshingTarget:self refreshingAction:@selector(loadNewData)]; 29 | return header; 30 | } 31 | - (NSArray *)customDirectEventTypes 32 | { 33 | return @[ 34 | @"onMJRefresh", 35 | @"onMJReleaseToRefresh", 36 | @"onMJRefreshIdle", 37 | @"onMJPulling" 38 | ]; 39 | } 40 | RCT_EXPORT_VIEW_PROPERTY(onMJRefresh, RCTBubblingEventBlock); 41 | RCT_EXPORT_VIEW_PROPERTY(onMJRefreshIdle, RCTBubblingEventBlock); 42 | RCT_EXPORT_VIEW_PROPERTY(onMJReleaseToRefresh, RCTBubblingEventBlock); 43 | RCT_EXPORT_VIEW_PROPERTY(onMJPulling, RCTBubblingEventBlock); 44 | 45 | RCT_EXPORT_METHOD(finishRefresh:(nonnull NSNumber *)reactTag) 46 | { 47 | [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { 48 | RCTMJRefreshHeader *view = viewRegistry[reactTag]; 49 | if (![view isKindOfClass:[RCTMJRefreshHeader class]]) { 50 | RCTLogError(@"Invalid view returned from registry, expecting RCTBarrage, got: %@", view); 51 | } else { 52 | [view endRefreshing]; 53 | } 54 | }]; 55 | } 56 | RCT_EXPORT_METHOD(beginRefresh:(nonnull NSNumber *)reactTag) 57 | { 58 | [self.bridge.uiManager addUIBlock:^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { 59 | RCTMJRefreshHeader *view = viewRegistry[reactTag]; 60 | if (![view isKindOfClass:[RCTMJRefreshHeader class]]) { 61 | RCTLogError(@"Invalid view returned from registry, expecting RCTBarrage, got: %@", view); 62 | } else { 63 | [view beginRefreshing]; 64 | } 65 | }]; 66 | } 67 | -(void)loadNewData 68 | { 69 | 70 | } 71 | - (dispatch_queue_t)methodQueue 72 | { 73 | return dispatch_get_main_queue(); 74 | } 75 | @end 76 | -------------------------------------------------------------------------------- /ios/RCTMJRefreshHeader/RCTMJScrollContentShadowView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCTMJScrollContentShadowView.h 3 | // RCTMJRefreshHeader 4 | // 5 | // Created by Macbook on 2018/7/6. 6 | // Copyright © 2018年 Macbook. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | #import 12 | 13 | @interface RCTMJScrollContentShadowView : RCTShadowView 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /ios/RCTMJRefreshHeader/RCTMJScrollContentShadowView.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "RCTMJScrollContentShadowView.h" 9 | 10 | #import 11 | 12 | #import 13 | 14 | @implementation RCTMJScrollContentShadowView 15 | 16 | - (void)layoutWithMetrics:(RCTLayoutMetrics)layoutMetrics 17 | layoutContext:(RCTLayoutContext)layoutContext 18 | { 19 | if (layoutMetrics.layoutDirection == UIUserInterfaceLayoutDirectionRightToLeft) { 20 | // Motivation: 21 | // Yoga place `contentView` on the right side of `scrollView` when RTL layout is enfoced. 22 | // That breaks everything; it is completely pointless to (re)position `contentView` 23 | // because it is `contentView`'s job. So, we work around it here. 24 | 25 | layoutContext.absolutePosition.x += layoutMetrics.frame.size.width; 26 | layoutMetrics.frame.origin.x = 0; 27 | } 28 | 29 | [super layoutWithMetrics:layoutMetrics layoutContext:layoutContext]; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /ios/RCTMJRefreshHeader/RCTMJScrollContentView.h: -------------------------------------------------------------------------------- 1 | // 2 | // RCTMJScrollContentView.h 3 | // RCTMJRefreshHeader 4 | // 5 | // Created by Macbook on 2018/7/6. 6 | // Copyright © 2018年 Macbook. All rights reserved. 7 | // 8 | #import 9 | @interface RCTMJScrollContentView : RCTView 10 | 11 | @end 12 | 13 | -------------------------------------------------------------------------------- /ios/RCTMJRefreshHeader/RCTMJScrollContentView.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCTMJScrollContentView.m 3 | // RCTMJRefreshHeader 4 | // 5 | // Created by Macbook on 2018/7/6. 6 | // Copyright © 2018年 Macbook. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | #import 12 | #import 13 | 14 | #import "RCTMJScrollView.h" 15 | #import "RCTMJScrollContentView.h" 16 | 17 | 18 | @implementation RCTMJScrollContentView 19 | 20 | - (void)reactSetFrame:(CGRect)frame 21 | { 22 | [super reactSetFrame:frame]; 23 | 24 | RCTMJScrollView *scrollView = (RCTMJScrollView *)self.superview.superview; 25 | 26 | if (!scrollView) { 27 | return; 28 | } 29 | 30 | RCTAssert([scrollView isKindOfClass:[RCTMJScrollView class]], 31 | @"Unexpected view hierarchy of RCTScrollView component."); 32 | 33 | [scrollView updateContentOffsetIfNeeded]; 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /ios/RCTMJRefreshHeader/RCTMJScrollContentViewMananger.m: -------------------------------------------------------------------------------- 1 | // 2 | // RCTMJScrollContentViewMananger.m 3 | // RCTMJRefreshHeader 4 | // 5 | // Created by Macbook on 2018/7/6. 6 | // Copyright © 2018年 Macbook. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | #import "RCTMJScrollContentShadowView.h" 13 | #import "RCTMJScrollContentView.h" 14 | 15 | @interface RCTMJScrollContentViewManager : RCTViewManager 16 | 17 | @end 18 | 19 | 20 | @implementation RCTMJScrollContentViewManager 21 | 22 | RCT_EXPORT_MODULE() 23 | 24 | - (RCTMJScrollContentView *)view 25 | { 26 | return [RCTMJScrollContentView new]; 27 | } 28 | 29 | - (RCTShadowView *)shadowView 30 | { 31 | return [RCTMJScrollContentShadowView new]; 32 | } 33 | 34 | @end 35 | -------------------------------------------------------------------------------- /ios/RCTMJRefreshHeader/RCTMJScrollView.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | #import 11 | #import 12 | #import 13 | #import 14 | 15 | @protocol UIScrollViewDelegate; 16 | 17 | @interface RCTMJScrollView : RCTView 18 | 19 | - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher NS_DESIGNATED_INITIALIZER; 20 | 21 | /** 22 | * The `RCTScrollView` may have at most one single subview. This will ensure 23 | * that the scroll view's `contentSize` will be efficiently set to the size of 24 | * the single subview's frame. That frame size will be determined somewhat 25 | * efficiently since it will have already been computed by the off-main-thread 26 | * layout system. 27 | */ 28 | @property (nonatomic, readonly) UIView *contentView; 29 | 30 | /** 31 | * If the `contentSize` is not specified (or is specified as {0, 0}, then the 32 | * `contentSize` will automatically be determined by the size of the subview. 33 | */ 34 | @property (nonatomic, assign) CGSize contentSize; 35 | 36 | /** 37 | * The underlying scrollView (TODO: can we remove this?) 38 | */ 39 | @property (nonatomic, readonly) UIScrollView *scrollView; 40 | 41 | @property (nonatomic, assign) UIEdgeInsets contentInset; 42 | @property (nonatomic, assign) BOOL automaticallyAdjustContentInsets; 43 | @property (nonatomic, assign) BOOL DEPRECATED_sendUpdatedChildFrames; 44 | @property (nonatomic, assign) NSTimeInterval scrollEventThrottle; 45 | @property (nonatomic, assign) BOOL centerContent; 46 | @property (nonatomic, copy) NSDictionary *maintainVisibleContentPosition; 47 | @property (nonatomic, assign) int snapToInterval; 48 | @property (nonatomic, copy) NSString *snapToAlignment; 49 | 50 | // NOTE: currently these event props are only declared so we can export the 51 | // event names to JS - we don't call the blocks directly because scroll events 52 | // need to be coalesced before sending, for performance reasons. 53 | @property (nonatomic, copy) RCTDirectEventBlock onScrollBeginDrag; 54 | @property (nonatomic, copy) RCTDirectEventBlock onScroll; 55 | @property (nonatomic, copy) RCTDirectEventBlock onScrollEndDrag; 56 | @property (nonatomic, copy) RCTDirectEventBlock onMomentumScrollBegin; 57 | @property (nonatomic, copy) RCTDirectEventBlock onMomentumScrollEnd; 58 | 59 | @end 60 | 61 | @interface RCTMJScrollView (Internal) 62 | 63 | - (void)updateContentOffsetIfNeeded; 64 | 65 | @end 66 | 67 | @interface RCTEventDispatcher (RCTMJScrollView) 68 | 69 | /** 70 | * Send a fake scroll event. 71 | */ 72 | - (void)sendFakeScrollEvent:(NSNumber *)reactTag; 73 | 74 | @end 75 | 76 | -------------------------------------------------------------------------------- /ios/RCTMJRefreshHeader/RCTMJScrollViewManager.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface RCTConvert (UIScrollView) 5 | 6 | + (UIScrollViewKeyboardDismissMode)UIScrollViewKeyboardDismissMode:(id)json; 7 | 8 | @end 9 | 10 | @interface RCTMJScrollViewManager : RCTViewManager 11 | 12 | @end 13 | -------------------------------------------------------------------------------- /ios/RCTMJRefreshHeader/RCTMJScrollViewManager.m: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import "RCTMJScrollViewManager.h" 9 | 10 | #import 11 | #import "RCTMJScrollView.h" 12 | #import 13 | #import 14 | 15 | @interface RCTMJScrollView (Private1) 16 | 17 | - (NSArray *)calculateChildFramesData; 18 | 19 | @end 20 | 21 | @implementation RCTConvert (UIScrollView) 22 | 23 | RCT_ENUM_CONVERTER(UIScrollViewKeyboardDismissMode, (@{ 24 | @"none": @(UIScrollViewKeyboardDismissModeNone), 25 | @"on-drag": @(UIScrollViewKeyboardDismissModeOnDrag), 26 | @"interactive": @(UIScrollViewKeyboardDismissModeInteractive), 27 | // Backwards compatibility 28 | @"onDrag": @(UIScrollViewKeyboardDismissModeOnDrag), 29 | }), UIScrollViewKeyboardDismissModeNone, integerValue) 30 | 31 | RCT_ENUM_CONVERTER(UIScrollViewIndicatorStyle, (@{ 32 | @"default": @(UIScrollViewIndicatorStyleDefault), 33 | @"black": @(UIScrollViewIndicatorStyleBlack), 34 | @"white": @(UIScrollViewIndicatorStyleWhite), 35 | }), UIScrollViewIndicatorStyleDefault, integerValue) 36 | 37 | #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ 38 | RCT_ENUM_CONVERTER(UIScrollViewContentInsetAdjustmentBehavior, (@{ 39 | @"automatic": @(UIScrollViewContentInsetAdjustmentAutomatic), 40 | @"scrollableAxes": @(UIScrollViewContentInsetAdjustmentScrollableAxes), 41 | @"never": @(UIScrollViewContentInsetAdjustmentNever), 42 | @"always": @(UIScrollViewContentInsetAdjustmentAlways), 43 | }), UIScrollViewContentInsetAdjustmentNever, integerValue) 44 | #endif 45 | 46 | @end 47 | 48 | @implementation RCTMJScrollViewManager 49 | 50 | RCT_EXPORT_MODULE() 51 | 52 | - (UIView *)view 53 | { 54 | return [[RCTMJScrollView alloc] initWithEventDispatcher:self.bridge.eventDispatcher]; 55 | } 56 | 57 | RCT_EXPORT_VIEW_PROPERTY(alwaysBounceHorizontal, BOOL) 58 | RCT_EXPORT_VIEW_PROPERTY(alwaysBounceVertical, BOOL) 59 | RCT_EXPORT_VIEW_PROPERTY(bounces, BOOL) 60 | RCT_EXPORT_VIEW_PROPERTY(bouncesZoom, BOOL) 61 | RCT_EXPORT_VIEW_PROPERTY(canCancelContentTouches, BOOL) 62 | RCT_EXPORT_VIEW_PROPERTY(centerContent, BOOL) 63 | RCT_EXPORT_VIEW_PROPERTY(maintainVisibleContentPosition, NSDictionary) 64 | RCT_EXPORT_VIEW_PROPERTY(automaticallyAdjustContentInsets, BOOL) 65 | RCT_EXPORT_VIEW_PROPERTY(decelerationRate, CGFloat) 66 | RCT_EXPORT_VIEW_PROPERTY(directionalLockEnabled, BOOL) 67 | RCT_EXPORT_VIEW_PROPERTY(indicatorStyle, UIScrollViewIndicatorStyle) 68 | RCT_EXPORT_VIEW_PROPERTY(keyboardDismissMode, UIScrollViewKeyboardDismissMode) 69 | RCT_EXPORT_VIEW_PROPERTY(maximumZoomScale, CGFloat) 70 | RCT_EXPORT_VIEW_PROPERTY(minimumZoomScale, CGFloat) 71 | RCT_EXPORT_VIEW_PROPERTY(scrollEnabled, BOOL) 72 | #if !TARGET_OS_TV 73 | RCT_EXPORT_VIEW_PROPERTY(pagingEnabled, BOOL) 74 | RCT_REMAP_VIEW_PROPERTY(pinchGestureEnabled, scrollView.pinchGestureEnabled, BOOL) 75 | RCT_EXPORT_VIEW_PROPERTY(scrollsToTop, BOOL) 76 | #endif 77 | RCT_EXPORT_VIEW_PROPERTY(showsHorizontalScrollIndicator, BOOL) 78 | RCT_EXPORT_VIEW_PROPERTY(showsVerticalScrollIndicator, BOOL) 79 | RCT_EXPORT_VIEW_PROPERTY(scrollEventThrottle, NSTimeInterval) 80 | RCT_EXPORT_VIEW_PROPERTY(zoomScale, CGFloat) 81 | RCT_EXPORT_VIEW_PROPERTY(contentInset, UIEdgeInsets) 82 | RCT_EXPORT_VIEW_PROPERTY(scrollIndicatorInsets, UIEdgeInsets) 83 | RCT_EXPORT_VIEW_PROPERTY(snapToInterval, int) 84 | RCT_EXPORT_VIEW_PROPERTY(snapToAlignment, NSString) 85 | RCT_REMAP_VIEW_PROPERTY(contentOffset, scrollView.contentOffset, CGPoint) 86 | RCT_EXPORT_VIEW_PROPERTY(onScrollBeginDrag, RCTDirectEventBlock) 87 | RCT_EXPORT_VIEW_PROPERTY(onScroll, RCTDirectEventBlock) 88 | RCT_EXPORT_VIEW_PROPERTY(onScrollEndDrag, RCTDirectEventBlock) 89 | RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollBegin, RCTDirectEventBlock) 90 | RCT_EXPORT_VIEW_PROPERTY(onMomentumScrollEnd, RCTDirectEventBlock) 91 | RCT_EXPORT_VIEW_PROPERTY(DEPRECATED_sendUpdatedChildFrames, BOOL) 92 | #if defined(__IPHONE_OS_VERSION_MAX_ALLOWED) && __IPHONE_OS_VERSION_MAX_ALLOWED >= 110000 /* __IPHONE_11_0 */ 93 | RCT_EXPORT_VIEW_PROPERTY(contentInsetAdjustmentBehavior, UIScrollViewContentInsetAdjustmentBehavior) 94 | #endif 95 | 96 | // overflow is used both in css-layout as well as by react-native. In css-layout 97 | // we always want to treat overflow as scroll but depending on what the overflow 98 | // is set to from js we want to clip drawing or not. This piece of code ensures 99 | // that css-layout is always treating the contents of a scroll container as 100 | // overflow: 'scroll'. 101 | RCT_CUSTOM_SHADOW_PROPERTY(overflow, YGOverflow, RCTShadowView) { 102 | #pragma unused (json) 103 | view.overflow = YGOverflowScroll; 104 | } 105 | 106 | RCT_EXPORT_METHOD(getContentSize:(nonnull NSNumber *)reactTag 107 | callback:(RCTResponseSenderBlock)callback) 108 | { 109 | [self.bridge.uiManager addUIBlock: 110 | ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { 111 | 112 | RCTMJScrollView *view = viewRegistry[reactTag]; 113 | if (!view || ![view isKindOfClass:[RCTMJScrollView class]]) { 114 | RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag); 115 | return; 116 | } 117 | 118 | CGSize size = view.scrollView.contentSize; 119 | callback(@[@{ 120 | @"width" : @(size.width), 121 | @"height" : @(size.height) 122 | }]); 123 | }]; 124 | } 125 | 126 | RCT_EXPORT_METHOD(calculateChildFrames:(nonnull NSNumber *)reactTag 127 | callback:(RCTResponseSenderBlock)callback) 128 | { 129 | [self.bridge.uiManager addUIBlock: 130 | ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry) { 131 | 132 | RCTMJScrollView *view = viewRegistry[reactTag]; 133 | if (!view || ![view isKindOfClass:[RCTMJScrollView class]]) { 134 | RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag); 135 | return; 136 | } 137 | 138 | NSArray *childFrames = [view calculateChildFramesData]; 139 | if (childFrames) { 140 | callback(@[childFrames]); 141 | } 142 | }]; 143 | } 144 | 145 | RCT_EXPORT_METHOD(scrollTo:(nonnull NSNumber *)reactTag 146 | offsetX:(CGFloat)x 147 | offsetY:(CGFloat)y 148 | animated:(BOOL)animated) 149 | { 150 | [self.bridge.uiManager addUIBlock: 151 | ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ 152 | UIView *view = viewRegistry[reactTag]; 153 | if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { 154 | [(id)view scrollToOffset:(CGPoint){x, y} animated:animated]; 155 | } else { 156 | RCTLogError(@"tried to scrollTo: on non-RCTScrollableProtocol view %@ " 157 | "with tag #%@", view, reactTag); 158 | } 159 | }]; 160 | } 161 | 162 | RCT_EXPORT_METHOD(scrollToEnd:(nonnull NSNumber *)reactTag 163 | animated:(BOOL)animated) 164 | { 165 | [self.bridge.uiManager addUIBlock: 166 | ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ 167 | UIView *view = viewRegistry[reactTag]; 168 | if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { 169 | [(id)view scrollToEnd:animated]; 170 | } else { 171 | RCTLogError(@"tried to scrollTo: on non-RCTScrollableProtocol view %@ " 172 | "with tag #%@", view, reactTag); 173 | } 174 | }]; 175 | } 176 | 177 | RCT_EXPORT_METHOD(zoomToRect:(nonnull NSNumber *)reactTag 178 | withRect:(CGRect)rect 179 | animated:(BOOL)animated) 180 | { 181 | [self.bridge.uiManager addUIBlock: 182 | ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ 183 | UIView *view = viewRegistry[reactTag]; 184 | if ([view conformsToProtocol:@protocol(RCTScrollableProtocol)]) { 185 | [(id)view zoomToRect:rect animated:animated]; 186 | } else { 187 | RCTLogError(@"tried to zoomToRect: on non-RCTScrollableProtocol view %@ " 188 | "with tag #%@", view, reactTag); 189 | } 190 | }]; 191 | } 192 | 193 | RCT_EXPORT_METHOD(flashScrollIndicators:(nonnull NSNumber *)reactTag) 194 | { 195 | [self.bridge.uiManager addUIBlock: 196 | ^(__unused RCTUIManager *uiManager, NSDictionary *viewRegistry){ 197 | 198 | RCTMJScrollView *view = viewRegistry[reactTag]; 199 | if (!view || ![view isKindOfClass:[RCTMJScrollView class]]) { 200 | RCTLogError(@"Cannot find RCTScrollView with tag #%@", reactTag); 201 | return; 202 | } 203 | 204 | [view.scrollView flashScrollIndicators]; 205 | }]; 206 | } 207 | 208 | @end 209 | 210 | -------------------------------------------------------------------------------- /ios/RCTMJRefreshHeader/UIView+Private1.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2015-present, Facebook, Inc. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | 8 | #import 9 | 10 | @interface UIView (Private1) 11 | 12 | // remove clipped subviews implementation 13 | - (void)react_remountAllSubviews; 14 | - (void)react_updateClippedSubviewsWithClipRect:(CGRect)clipRect relativeToView:(UIView *)clipView; 15 | - (UIView *)react_findClipView; 16 | 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /ios/RefreshControlEnrichment.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | B3E7B58A1CC2AC0600A0062D /* RefreshControlEnrichment.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* RefreshControlEnrichment.m */; }; 11 | /* End PBXBuildFile section */ 12 | 13 | /* Begin PBXCopyFilesBuildPhase section */ 14 | 58B511D91A9E6C8500147676 /* CopyFiles */ = { 15 | isa = PBXCopyFilesBuildPhase; 16 | buildActionMask = 2147483647; 17 | dstPath = "include/$(PRODUCT_NAME)"; 18 | dstSubfolderSpec = 16; 19 | files = ( 20 | ); 21 | runOnlyForDeploymentPostprocessing = 0; 22 | }; 23 | /* End PBXCopyFilesBuildPhase section */ 24 | 25 | /* Begin PBXFileReference section */ 26 | 134814201AA4EA6300B7C361 /* libRefreshControlEnrichment.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRefreshControlEnrichment.a; sourceTree = BUILT_PRODUCTS_DIR; }; 27 | B3E7B5881CC2AC0600A0062D /* RefreshControlEnrichment.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RefreshControlEnrichment.h; sourceTree = ""; }; 28 | B3E7B5891CC2AC0600A0062D /* RefreshControlEnrichment.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RefreshControlEnrichment.m; sourceTree = ""; }; 29 | /* End PBXFileReference section */ 30 | 31 | /* Begin PBXFrameworksBuildPhase section */ 32 | 58B511D81A9E6C8500147676 /* Frameworks */ = { 33 | isa = PBXFrameworksBuildPhase; 34 | buildActionMask = 2147483647; 35 | files = ( 36 | ); 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXFrameworksBuildPhase section */ 40 | 41 | /* Begin PBXGroup section */ 42 | 134814211AA4EA7D00B7C361 /* Products */ = { 43 | isa = PBXGroup; 44 | children = ( 45 | 134814201AA4EA6300B7C361 /* libRefreshControlEnrichment.a */, 46 | ); 47 | name = Products; 48 | sourceTree = ""; 49 | }; 50 | 58B511D21A9E6C8500147676 = { 51 | isa = PBXGroup; 52 | children = ( 53 | B3E7B5881CC2AC0600A0062D /* RefreshControlEnrichment.h */, 54 | B3E7B5891CC2AC0600A0062D /* RefreshControlEnrichment.m */, 55 | 134814211AA4EA7D00B7C361 /* Products */, 56 | ); 57 | sourceTree = ""; 58 | }; 59 | /* End PBXGroup section */ 60 | 61 | /* Begin PBXNativeTarget section */ 62 | 58B511DA1A9E6C8500147676 /* RefreshControlEnrichment */ = { 63 | isa = PBXNativeTarget; 64 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RefreshControlEnrichment" */; 65 | buildPhases = ( 66 | 58B511D71A9E6C8500147676 /* Sources */, 67 | 58B511D81A9E6C8500147676 /* Frameworks */, 68 | 58B511D91A9E6C8500147676 /* CopyFiles */, 69 | ); 70 | buildRules = ( 71 | ); 72 | dependencies = ( 73 | ); 74 | name = RefreshControlEnrichment; 75 | productName = RCTDataManager; 76 | productReference = 134814201AA4EA6300B7C361 /* libRefreshControlEnrichment.a */; 77 | productType = "com.apple.product-type.library.static"; 78 | }; 79 | /* End PBXNativeTarget section */ 80 | 81 | /* Begin PBXProject section */ 82 | 58B511D31A9E6C8500147676 /* Project object */ = { 83 | isa = PBXProject; 84 | attributes = { 85 | LastUpgradeCheck = 0920; 86 | ORGANIZATIONNAME = Facebook; 87 | TargetAttributes = { 88 | 58B511DA1A9E6C8500147676 = { 89 | CreatedOnToolsVersion = 6.1.1; 90 | }; 91 | }; 92 | }; 93 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RefreshControlEnrichment" */; 94 | compatibilityVersion = "Xcode 3.2"; 95 | developmentRegion = English; 96 | hasScannedForEncodings = 0; 97 | knownRegions = ( 98 | en, 99 | ); 100 | mainGroup = 58B511D21A9E6C8500147676; 101 | productRefGroup = 58B511D21A9E6C8500147676; 102 | projectDirPath = ""; 103 | projectRoot = ""; 104 | targets = ( 105 | 58B511DA1A9E6C8500147676 /* RefreshControlEnrichment */, 106 | ); 107 | }; 108 | /* End PBXProject section */ 109 | 110 | /* Begin PBXSourcesBuildPhase section */ 111 | 58B511D71A9E6C8500147676 /* Sources */ = { 112 | isa = PBXSourcesBuildPhase; 113 | buildActionMask = 2147483647; 114 | files = ( 115 | B3E7B58A1CC2AC0600A0062D /* RefreshControlEnrichment.m in Sources */, 116 | ); 117 | runOnlyForDeploymentPostprocessing = 0; 118 | }; 119 | /* End PBXSourcesBuildPhase section */ 120 | 121 | /* Begin XCBuildConfiguration section */ 122 | 58B511ED1A9E6C8500147676 /* Debug */ = { 123 | isa = XCBuildConfiguration; 124 | buildSettings = { 125 | ALWAYS_SEARCH_USER_PATHS = NO; 126 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 127 | CLANG_CXX_LIBRARY = "libc++"; 128 | CLANG_ENABLE_MODULES = YES; 129 | CLANG_ENABLE_OBJC_ARC = YES; 130 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 131 | CLANG_WARN_BOOL_CONVERSION = YES; 132 | CLANG_WARN_COMMA = YES; 133 | CLANG_WARN_CONSTANT_CONVERSION = YES; 134 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 135 | CLANG_WARN_EMPTY_BODY = YES; 136 | CLANG_WARN_ENUM_CONVERSION = YES; 137 | CLANG_WARN_INFINITE_RECURSION = YES; 138 | CLANG_WARN_INT_CONVERSION = YES; 139 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 140 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 141 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 142 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 143 | CLANG_WARN_STRICT_PROTOTYPES = YES; 144 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 145 | CLANG_WARN_UNREACHABLE_CODE = YES; 146 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 147 | COPY_PHASE_STRIP = NO; 148 | ENABLE_STRICT_OBJC_MSGSEND = YES; 149 | ENABLE_TESTABILITY = YES; 150 | GCC_C_LANGUAGE_STANDARD = gnu99; 151 | GCC_DYNAMIC_NO_PIC = NO; 152 | GCC_NO_COMMON_BLOCKS = YES; 153 | GCC_OPTIMIZATION_LEVEL = 0; 154 | GCC_PREPROCESSOR_DEFINITIONS = ( 155 | "DEBUG=1", 156 | "$(inherited)", 157 | ); 158 | GCC_SYMBOLS_PRIVATE_EXTERN = NO; 159 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 160 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 161 | GCC_WARN_UNDECLARED_SELECTOR = YES; 162 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 163 | GCC_WARN_UNUSED_FUNCTION = YES; 164 | GCC_WARN_UNUSED_VARIABLE = YES; 165 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 166 | MTL_ENABLE_DEBUG_INFO = YES; 167 | ONLY_ACTIVE_ARCH = YES; 168 | SDKROOT = iphoneos; 169 | }; 170 | name = Debug; 171 | }; 172 | 58B511EE1A9E6C8500147676 /* Release */ = { 173 | isa = XCBuildConfiguration; 174 | buildSettings = { 175 | ALWAYS_SEARCH_USER_PATHS = NO; 176 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 177 | CLANG_CXX_LIBRARY = "libc++"; 178 | CLANG_ENABLE_MODULES = YES; 179 | CLANG_ENABLE_OBJC_ARC = YES; 180 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 181 | CLANG_WARN_BOOL_CONVERSION = YES; 182 | CLANG_WARN_COMMA = YES; 183 | CLANG_WARN_CONSTANT_CONVERSION = YES; 184 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 185 | CLANG_WARN_EMPTY_BODY = YES; 186 | CLANG_WARN_ENUM_CONVERSION = YES; 187 | CLANG_WARN_INFINITE_RECURSION = YES; 188 | CLANG_WARN_INT_CONVERSION = YES; 189 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 190 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 191 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 192 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 193 | CLANG_WARN_STRICT_PROTOTYPES = YES; 194 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 195 | CLANG_WARN_UNREACHABLE_CODE = YES; 196 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 197 | COPY_PHASE_STRIP = YES; 198 | ENABLE_NS_ASSERTIONS = NO; 199 | ENABLE_STRICT_OBJC_MSGSEND = YES; 200 | GCC_C_LANGUAGE_STANDARD = gnu99; 201 | GCC_NO_COMMON_BLOCKS = YES; 202 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 203 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 204 | GCC_WARN_UNDECLARED_SELECTOR = YES; 205 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 206 | GCC_WARN_UNUSED_FUNCTION = YES; 207 | GCC_WARN_UNUSED_VARIABLE = YES; 208 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 209 | MTL_ENABLE_DEBUG_INFO = NO; 210 | SDKROOT = iphoneos; 211 | VALIDATE_PRODUCT = YES; 212 | }; 213 | name = Release; 214 | }; 215 | 58B511F01A9E6C8500147676 /* Debug */ = { 216 | isa = XCBuildConfiguration; 217 | buildSettings = { 218 | HEADER_SEARCH_PATHS = ( 219 | "$(inherited)", 220 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 221 | "$(SRCROOT)/../../../React/**", 222 | "$(SRCROOT)/../../react-native/React/**", 223 | ); 224 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 225 | OTHER_LDFLAGS = "-ObjC"; 226 | PRODUCT_NAME = RefreshControlEnrichment; 227 | SKIP_INSTALL = YES; 228 | }; 229 | name = Debug; 230 | }; 231 | 58B511F11A9E6C8500147676 /* Release */ = { 232 | isa = XCBuildConfiguration; 233 | buildSettings = { 234 | HEADER_SEARCH_PATHS = ( 235 | "$(inherited)", 236 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include, 237 | "$(SRCROOT)/../../../React/**", 238 | "$(SRCROOT)/../../react-native/React/**", 239 | ); 240 | LIBRARY_SEARCH_PATHS = "$(inherited)"; 241 | OTHER_LDFLAGS = "-ObjC"; 242 | PRODUCT_NAME = RefreshControlEnrichment; 243 | SKIP_INSTALL = YES; 244 | }; 245 | name = Release; 246 | }; 247 | /* End XCBuildConfiguration section */ 248 | 249 | /* Begin XCConfigurationList section */ 250 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "RefreshControlEnrichment" */ = { 251 | isa = XCConfigurationList; 252 | buildConfigurations = ( 253 | 58B511ED1A9E6C8500147676 /* Debug */, 254 | 58B511EE1A9E6C8500147676 /* Release */, 255 | ); 256 | defaultConfigurationIsVisible = 0; 257 | defaultConfigurationName = Release; 258 | }; 259 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "RefreshControlEnrichment" */ = { 260 | isa = XCConfigurationList; 261 | buildConfigurations = ( 262 | 58B511F01A9E6C8500147676 /* Debug */, 263 | 58B511F11A9E6C8500147676 /* Release */, 264 | ); 265 | defaultConfigurationIsVisible = 0; 266 | defaultConfigurationName = Release; 267 | }; 268 | /* End XCConfigurationList section */ 269 | }; 270 | rootObject = 58B511D31A9E6C8500147676 /* Project object */; 271 | } 272 | -------------------------------------------------------------------------------- /ios/RefreshControlEnrichment.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-refresh-control-enrichment", 3 | "title": "React Native Refresh Control Enrichment", 4 | "version": "0.2.0", 5 | "description": "原生下拉刷新组件", 6 | "main": "index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git+https://github.com/gitSirzh/react-native-refresh-control-enrichment.git", 13 | "baseUrl": "https://github.com/gitSirzh/react-native-refresh-control-enrichment" 14 | }, 15 | "keywords": [ 16 | "react-native", 17 | "refresh", 18 | "control", 19 | "ios", 20 | "android" 21 | ], 22 | "author": { 23 | "name": "jszh", 24 | "email": "969883825@qq.com" 25 | }, 26 | "license": "MIT", 27 | "licenseFilename": "LICENSE", 28 | "readmeFilename": "README.md" 29 | } 30 | -------------------------------------------------------------------------------- /react-native-refresh-control-enrichment.podspec: -------------------------------------------------------------------------------- 1 | require "json" 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, "package.json"))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = "react-native-refresh-control-enrichment" 7 | s.version = package["version"] 8 | s.summary = package["description"] 9 | s.description = <<-DESC 10 | react-native-refresh-control-enrichment 11 | DESC 12 | s.homepage = "https://github.com/gitSirzh/react-native-refresh-control-enrichment" 13 | # brief license entry: 14 | s.license = "MIT" 15 | # optional - use expanded license entry instead: 16 | # s.license = { :type => "MIT", :file => "LICENSE" } 17 | s.authors = { "jszh" => "969883825@qq.com" } 18 | s.platforms = { :ios => "9.0" } 19 | s.source = { :git => "https://github.com/gitSirzh/react-native-refresh-control-enrichment.git", :tag => "#{s.version}" } 20 | 21 | s.source_files = "ios/**/*.{h,m,swift}" 22 | s.requires_arc = true 23 | 24 | s.dependency "React" 25 | # ... 26 | # s.dependency "..." 27 | end 28 | 29 | -------------------------------------------------------------------------------- /src/androidJS/AnyHeader.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | StyleSheet, 4 | View, 5 | Text, 6 | requireNativeComponent, 7 | findNodeHandle, 8 | UIManager, 9 | } from 'react-native'; 10 | import PropTypes from 'prop-types'; 11 | import {ViewPropTypes} from './Util' 12 | 13 | const RCTAnyHeader = requireNativeComponent('RCTAnyHeader', RCTAnyHeader); 14 | 15 | class AnyHeader extends Component { 16 | 17 | render() { 18 | return ( 19 | 22 | 23 | ) 24 | } 25 | } 26 | 27 | AnyHeader.propTypes = { 28 | primaryColor:PropTypes.string, 29 | ...ViewPropTypes, 30 | } 31 | export default AnyHeader; -------------------------------------------------------------------------------- /src/androidJS/DefaultHeader.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {requireNativeComponent} from 'react-native'; 3 | import {ViewPropTypes} from './Util'; 4 | import PropTypes from 'prop-types'; 5 | 6 | const RCTDefaultHeader = requireNativeComponent('RCTDefaultHeader', RCTDefaultHeader); 7 | 8 | export default class DefaultHeader extends Component { 9 | static propTypes = { 10 | primaryColor: PropTypes.string, 11 | accentColor: PropTypes.string, 12 | ...ViewPropTypes, 13 | } 14 | 15 | render() { 16 | return () 17 | } 18 | } -------------------------------------------------------------------------------- /src/androidJS/SmartRefreshControl.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | requireNativeComponent, 4 | findNodeHandle, 5 | UIManager, 6 | NativeModules, 7 | Platform, 8 | PanResponder, 9 | } from 'react-native'; 10 | import {ViewPropTypes} from './Util'; 11 | import DefaultHeader from './DefaultHeader'; 12 | import PropTypes from 'prop-types'; 13 | import processColor from 'react-native/Libraries/StyleSheet/processColor'; 14 | import deprecatedPropType from 'react-native/Libraries/Utilities/deprecatedPropType'; 15 | 16 | const SPModule = Platform.OS === 'android' ? NativeModules.SpinnerStyleModule : {}; 17 | 18 | const SmartRefreshLayout = requireNativeComponent('SmartRefreshLayout', SmartRefreshControl); 19 | 20 | class SmartRefreshControl extends Component { 21 | 22 | constructor(props) { 23 | super(props); 24 | this._panResponder = PanResponder.create({ 25 | onMoveShouldSetPanResponderCapture: (evt, gestureState) => { 26 | if (this.shiftPercent >= 0.039 || this.footerShiftPercent >= 0.068) {//满足条件捕获事件 27 | return true; 28 | } 29 | return false; 30 | }, 31 | }); 32 | } 33 | 34 | static constants = { 35 | 'TRANSLATE': SPModule.translate, 36 | 'SCALE': SPModule.scale, 37 | 'FIX_BEHIND': SPModule.fixBehind, 38 | 'FIX_FRONT': SPModule.fixFront, 39 | 'MATCH_LAYOUT': SPModule.matchLayout, 40 | }; 41 | 42 | /** 43 | * 参数格式为{delayed:number,success:bool} 44 | * delayed:延迟刷新 45 | * success:是否刷新成功 46 | * @param params 47 | */ 48 | finishRefresh = ({delayed = -1, success = true} = {delayed: -1, success: true}) => { 49 | this.dispatchCommand('finishRefresh', [delayed, success]); 50 | this.props.onSuccessToRefresh && this.props.onSuccessToRefresh() 51 | }; 52 | dispatchCommand = (commandName, params) => { 53 | UIManager.dispatchViewManagerCommand(this.findNode(), 54 | (UIManager.getViewManagerConfig ? UIManager.getViewManagerConfig('SmartRefreshLayout') : UIManager.SmartRefreshLayout).Commands[commandName], 55 | params); 56 | }; 57 | findNode = () => { 58 | return findNodeHandle(this.refs.refreshLayout); 59 | }; 60 | 61 | shiftPercent = 0;//header位移百分比,默认为0 62 | 63 | footerShiftPercent = 0;// footer位移百分比 64 | /** 65 | * 渲染Header 66 | * @return {*} 67 | */ 68 | renderHeader = () => { 69 | const {HeaderComponent, renderHeader} = this.props; 70 | if (renderHeader) { 71 | return React.isValidElement(renderHeader) ? renderHeader : renderHeader(); 72 | } 73 | if (HeaderComponent) { 74 | return HeaderComponent; 75 | } 76 | return ; 77 | }; 78 | /** 79 | * 刷新时触发 80 | * @private 81 | */ 82 | _onSmartRefresh = () => { 83 | let {onRefresh} = this.props; 84 | onRefresh && onRefresh(); 85 | }; 86 | /** 87 | * 下拉过程 88 | * @param event 89 | * @private 90 | */ 91 | _onHeaderPulling = (event) => { 92 | this.shiftPercent = event.nativeEvent.percent; 93 | let {onHeaderPulling, onHeaderMoving} = this.props; 94 | onHeaderMoving && onHeaderMoving(event); 95 | onHeaderPulling && onHeaderPulling(event); 96 | }; 97 | /** 98 | * 释放过程 99 | * @param event 100 | * @private 101 | */ 102 | _onHeaderReleasing = (event) => { 103 | this.shiftPercent = event.nativeEvent.percent; 104 | let {onHeaderReleasing, onHeaderMoving} = this.props; 105 | onHeaderMoving && onHeaderMoving(event); 106 | onHeaderReleasing && onHeaderReleasing(event); 107 | }; 108 | /** 109 | * 底部位移过程 110 | * @param event 111 | * @private 112 | */ 113 | _onFooterMoving = (event) => { 114 | this.footerShiftPercent = event.nativeEvent.percent; 115 | }; 116 | 117 | render() { 118 | const nativeProps = { 119 | ...this.props, ...{ 120 | onSmartRefresh: this._onSmartRefresh, 121 | onHeaderPulling: this._onHeaderPulling, 122 | onHeaderReleasing: this._onHeaderReleasing, 123 | onFooterMoving: this._onFooterMoving, 124 | primaryColor: processColor(this.props.primaryColor), 125 | }, 126 | }; 127 | return ( 128 | 133 | {this.renderHeader()} 134 | {this.props.children} 135 | 136 | 137 | ); 138 | } 139 | } 140 | 141 | SmartRefreshControl.propTypes = { 142 | onRefresh: PropTypes.func, 143 | onLoadMore: PropTypes.func, 144 | onHeaderPulling: PropTypes.func, 145 | onHeaderReleasing: PropTypes.func, 146 | onHeaderMoving: PropTypes.func,//向外提供的接口 147 | onPullDownToRefresh: PropTypes.func, 148 | onReleaseToRefresh: PropTypes.func, 149 | onHeaderReleased: PropTypes.func, 150 | enableRefresh: PropTypes.bool,//是否启用下拉刷新功能 151 | HeaderComponent: deprecatedPropType(PropTypes.object, 'Use the `renderHeader` prop instead.'), 152 | renderHeader: PropTypes.oneOfType([ 153 | PropTypes.func, 154 | PropTypes.element, 155 | ]), 156 | headerHeight: PropTypes.number, 157 | overScrollBounce: PropTypes.bool,//是否使用越界回弹 158 | overScrollDrag: PropTypes.bool,//是否使用越界拖动,类似IOS样式 159 | pureScroll: PropTypes.bool,//是否使用纯滚动模式 160 | dragRate: PropTypes.number,// 显示下拉高度/手指真实下拉高度=阻尼效果 161 | maxDragRate: PropTypes.number,//最大显示下拉高度/Header标准高度 162 | primaryColor: PropTypes.string, 163 | autoRefresh: PropTypes.shape({ 164 | refresh: PropTypes.bool, 165 | time: PropTypes.number, 166 | }),//是否启动自动刷新 167 | ...ViewPropTypes, 168 | }; 169 | 170 | SmartRefreshControl.defaultProps = { 171 | overScrollBounce: false, 172 | }; 173 | export default SmartRefreshControl; 174 | -------------------------------------------------------------------------------- /src/androidJS/Util.js: -------------------------------------------------------------------------------- 1 | import { 2 | View, 3 | BackHandler, 4 | ViewPropTypes as RNViewPropTypes, 5 | BackAndroid as DeprecatedBackAndroid, 6 | } from 'react-native'; 7 | 8 | export const ViewPropTypes = RNViewPropTypes || View.propTypes; 9 | export const BackAndroid = BackHandler || DeprecatedBackAndroid; 10 | -------------------------------------------------------------------------------- /src/file/androidVideo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitSirzh/react-native-refresh-control-enrichment/HEAD/src/file/androidVideo.gif -------------------------------------------------------------------------------- /src/file/iosVideo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gitSirzh/react-native-refresh-control-enrichment/HEAD/src/file/iosVideo.gif -------------------------------------------------------------------------------- /src/fusion.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jszh on 2020/5/12. 3 | */ 4 | 5 | 'use strict'; 6 | 7 | import React, {Component} from 'react'; 8 | import { 9 | View, 10 | Text, 11 | Platform, 12 | StyleSheet, 13 | ActivityIndicator, 14 | ImageBackground, 15 | } from 'react-native'; 16 | import PropTypes from 'prop-types'; 17 | 18 | import MJRefresh from './iosJS/MJRefresh'; 19 | import {default as SmartRefreshControl} from './androidJS/SmartRefreshControl'; 20 | import {default as AnyHeader} from './androidJS/AnyHeader'; 21 | import dateFormat from './util/date'; 22 | 23 | const TITLE = ['下拉刷新', '释放刷新', '正在刷新', '刷新完成']; 24 | 25 | export default class ZHRefreshControl extends Component { 26 | 27 | constructor(props) { 28 | super(props); 29 | this.state = { 30 | text: props.titleArray ? props.titleArray[0] : TITLE[0], 31 | }; 32 | } 33 | 34 | finishRefresh() { 35 | if (Platform.OS === 'ios') { 36 | this._mjRefresh && this._mjRefresh.finishRefresh(); 37 | } else { 38 | this.rc && this.rc.finishRefresh(); 39 | } 40 | } 41 | 42 | renderHeader() { 43 | let {text} = this.state; 44 | let { 45 | loadingView, 46 | headerHeight, 47 | centerTop, 48 | headerBackgroundColor, 49 | headerBackgroundImage, 50 | pullView, 51 | releaseView, 52 | successView, 53 | headerTitleStyle, 54 | headerDateStyle, 55 | titleArray, 56 | showDate, 57 | showText, 58 | } = this.props; 59 | if (headerBackgroundImage) { 60 | return ( 61 | 63 | 64 | {text === titleArray[2] ? ( 65 | <>{loadingView ? (loadingView) : ()} 66 | ) : text === titleArray[0] ? ( 67 | <>{pullView ? (pullView) : {'⬇️'}} 68 | ) : text === titleArray[1] ? ( 69 | <>{releaseView ? (releaseView) : {'⬆️️️'}} 70 | ) : ( 71 | <>{successView ? (successView) : null} 72 | )} 73 | {showText ? ( 74 | {text} 75 | ) : null} 76 | 77 | {showDate ? (上次更新 {dateFormat()}) : null} 78 | 79 | ); 80 | } else { 81 | return ( 82 | 86 | 87 | {text === titleArray[2] ? ( 88 | <>{loadingView ? (loadingView) : ()} 89 | ) : text === titleArray[0] ? ( 90 | <>{pullView ? (pullView) : {'⬇️'}} 91 | ) : text === titleArray[1] ? ( 92 | <>{releaseView ? (releaseView) : {'⬆️️️'}} 93 | ) : ( 94 | <>{successView ? (successView) : null} 95 | )} 96 | {showText ? ( 97 | {text} 98 | ) : null} 99 | 100 | {showDate ? (上次更新 {dateFormat()}) : null} 101 | 102 | ); 103 | } 104 | } 105 | 106 | render() { 107 | let {onRefresh, refreshState, headerHeight, centerTop, headerBackgroundColor, titleArray} = this.props; 108 | if (Platform.OS === 'ios') { 109 | return ( 110 | this._mjRefresh = ref} 113 | onPulling={e => { 114 | if (e.nativeEvent.percent < 1) { 115 | this.setState({text: titleArray[0]}); 116 | refreshState && refreshState(titleArray[0]); 117 | } 118 | }} 119 | onReleaseToRefresh={() => { 120 | this.setState({text: titleArray[1]}); 121 | refreshState && refreshState(titleArray[1]); 122 | }} 123 | onRefresh={() => { 124 | this.setState({text: titleArray[2]}); 125 | onRefresh && onRefresh(); 126 | refreshState && refreshState(titleArray[2]); 127 | }} 128 | onRefreshIdle={() => { 129 | this.setState({text: titleArray[3]}); 130 | refreshState && refreshState(titleArray[3]); 131 | }} 132 | > 133 | {this.renderHeader()} 134 | 135 | ); 136 | } else { 137 | return ( 138 | this.rc = ref} 141 | primaryColor={headerBackgroundColor} 142 | headerHeight={headerHeight + centerTop} 143 | renderHeader={( 144 | {this.renderHeader()} 145 | )} 146 | onPullDownToRefresh={() => { 147 | this.setState({text: titleArray[0]}); 148 | refreshState && refreshState(titleArray[0]); 149 | }} 150 | onReleaseToRefresh={() => { 151 | this.setState({text: titleArray[1]}); 152 | refreshState && refreshState(titleArray[1]); 153 | }} 154 | onRefresh={() => { 155 | onRefresh && onRefresh(); 156 | this.setState({text: titleArray[2]}); 157 | refreshState && refreshState(titleArray[2]); 158 | }} 159 | onSuccessToRefresh={() => { 160 | this.setState({text: titleArray[3]}); 161 | refreshState && refreshState(titleArray[3]); 162 | }} 163 | /> 164 | ); 165 | } 166 | } 167 | 168 | } 169 | 170 | ZHRefreshControl.defaultProps = { 171 | headerHeight: 60, 172 | centerTop: 0, 173 | headerBackgroundColor: '#ffffff', 174 | headerTitleStyle: {fontSize: 14, color: '#333333'}, 175 | headerDateStyle: {fontSize: 12, color: '#333333'}, 176 | titleArray: TITLE, 177 | showDate: true, 178 | showText: true, 179 | }; 180 | 181 | /** 182 | * loadingView : 左边加载中View 183 | * headerHeight : 头部高度 184 | * centerTop : 内容距离顶部高度(一般为状态栏高度) 185 | * headerBackgroundColor : 头部背景色 186 | * headerBackgroundImage : 头部背景图 187 | * headerTitleStyle : 刷新状态字体样式 188 | * headerDateStyle : 更新时间字体样式 189 | * titleArray : 自定义提示状态,注:数量需要为四个 190 | * pullView : 下拉样式 191 | * releaseView : 释放样式 192 | * successView : 成功样式 193 | * showDate : 展示刷新时间,默认true:展示 194 | * showText : 展示刷新状态,默认true:展示 195 | * onRefresh : 执行刷新回调方法 196 | * refreshState : 刷新状态回调方法 197 | */ 198 | ZHRefreshControl.propTypes = { 199 | loadingView: PropTypes.element, 200 | headerHeight: PropTypes.number, 201 | centerTop: PropTypes.number, 202 | headerBackgroundColor: PropTypes.string, 203 | headerBackgroundImage: PropTypes.oneOfType([ 204 | PropTypes.shape({ 205 | uri: PropTypes.string, 206 | }), 207 | // Opaque type returned by require('./image.jpg') 208 | PropTypes.number, 209 | ]), 210 | pullView: PropTypes.element, 211 | releaseView: PropTypes.element, 212 | successView: PropTypes.element, 213 | headerTitleStyle: PropTypes.object, 214 | headerDateStyle: PropTypes.object, 215 | titleArray: PropTypes.array, 216 | showDate: PropTypes.bool, 217 | showText: PropTypes.bool, 218 | onRefresh: PropTypes.func, 219 | refreshState: PropTypes.func, 220 | }; 221 | 222 | const styles = StyleSheet.create({ 223 | headView: { 224 | justifyContent: 'center', 225 | alignItems: 'center', 226 | }, 227 | }); 228 | 229 | -------------------------------------------------------------------------------- /src/iosJS/MJRefresh.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | View, 4 | requireNativeComponent, 5 | ViewPropTypes as RNViewPropTypes, 6 | findNodeHandle, 7 | UIManager, 8 | Platform, 9 | } from 'react-native'; 10 | import PropTypes from 'prop-types'; 11 | import MJScrollView from './MJScrollView'; 12 | 13 | const ViewPropTypes = RNViewPropTypes || View.propTypes; 14 | const RCTMJRefreshView = Platform.OS === 'ios' ? requireNativeComponent('RCTMJRefreshView', MJRefresh) : null; 15 | 16 | class MJRefresh extends Component { 17 | 18 | _onMJRefresh = () => { 19 | let {onRefresh} = this.props; 20 | onRefresh && onRefresh(); 21 | }; 22 | 23 | _onMJPulling = (e) => { 24 | let {onPulling} = this.props; 25 | onPulling && onPulling(e); 26 | }; 27 | 28 | _onMJReleaseToRefresh = () => { 29 | let {onReleaseToRefresh} = this.props; 30 | onReleaseToRefresh && onReleaseToRefresh(); 31 | }; 32 | 33 | _onMJRefreshIdle = () => { 34 | let {onRefreshIdle} = this.props; 35 | onRefreshIdle && onRefreshIdle(); 36 | }; 37 | 38 | finishRefresh = () => { 39 | this.dispatchCommand('finishRefresh'); 40 | }; 41 | 42 | beginRefresh = () => { 43 | this.dispatchCommand('beginRefresh'); 44 | }; 45 | 46 | dispatchCommand(commandName, params) { 47 | UIManager.dispatchViewManagerCommand(this.findNode(), UIManager.RCTMJRefreshView.Commands[commandName], params); 48 | } 49 | 50 | findNode = () => { 51 | return findNodeHandle(this.refs.refreshView); 52 | }; 53 | 54 | render() { 55 | let {style} = this.props; 56 | return ( 57 | 76 | ); 77 | } 78 | } 79 | 80 | MJRefresh.propTypes = { 81 | onRefresh: PropTypes.func, 82 | onRefreshIdle: PropTypes.func, 83 | onReleaseToRefresh: PropTypes.func, 84 | onPulling: PropTypes.func, 85 | ...ViewPropTypes, 86 | }; 87 | 88 | export const ZHScrollView = MJScrollView; 89 | export default MJRefresh; 90 | -------------------------------------------------------------------------------- /src/iosJS/MJScrollView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { 3 | requireNativeComponent, 4 | ScrollView, 5 | ScrollViewProps, 6 | View, 7 | Platform, 8 | StyleSheet, 9 | } from 'react-native'; 10 | 11 | const warning = require('fbjs/lib/warning'); 12 | const flattenStyle = require('react-native/Libraries/StyleSheet/flattenStyle'); 13 | const invariant = require('fbjs/lib/invariant'); 14 | const ScrollViewStickyHeader = require('react-native/Libraries/Components/ScrollView/ScrollViewStickyHeader'); 15 | const processDecelerationRate = require('react-native/Libraries/Components/ScrollView/processDecelerationRate'); 16 | const RCTMJScrollView = Platform.OS === 'ios' ? requireNativeComponent('RCTMJScrollView', MJScrollView, { 17 | nativeOnly: { 18 | onMomentumScrollBegin: true, 19 | onMomentumScrollEnd: true, 20 | onScrollBeginDrag: true, 21 | onScrollEndDrag: true, 22 | }, 23 | }) : ScrollView; 24 | 25 | const RCTMJScrollContentView = Platform.OS === 'ios' ? requireNativeComponent('RCTMJScrollContentView', View) : ScrollView; 26 | 27 | class MJScrollView extends Component { 28 | render() { 29 | let ScrollViewClass = RCTMJScrollView; 30 | let ScrollContentContainerViewClass = RCTMJScrollContentView; 31 | warning( 32 | !this.props.snapToInterval || !this.props.pagingEnabled, 33 | 'snapToInterval is currently ignored when pagingEnabled is true.', 34 | ); 35 | invariant( 36 | ScrollViewClass !== undefined, 37 | 'ScrollViewClass must not be undefined', 38 | ); 39 | invariant( 40 | ScrollContentContainerViewClass !== undefined, 41 | 'ScrollContentContainerViewClass must not be undefined', 42 | ); 43 | const contentContainerStyle = [ 44 | this.props.horizontal && styles.contentContainerHorizontal, 45 | this.props.contentContainerStyle, 46 | ]; 47 | let style, childLayoutProps; 48 | if (__DEV__ && this.props.style) { 49 | style = flattenStyle(this.props.style); 50 | childLayoutProps = ['alignItems', 'justifyContent'] 51 | .filter((prop) => style && style[prop] !== undefined); 52 | invariant( 53 | childLayoutProps.length === 0, 54 | 'ScrollView child layout (' + JSON.stringify(childLayoutProps) + 55 | ') must be applied through the contentContainerStyle prop.', 56 | ); 57 | } 58 | 59 | let contentSizeChangeProps = {}; 60 | if (this.props.onContentSizeChange) { 61 | contentSizeChangeProps = { 62 | onLayout: this._handleContentOnLayout, 63 | }; 64 | } 65 | 66 | const {stickyHeaderIndices} = this.props; 67 | const hasStickyHeaders = stickyHeaderIndices && stickyHeaderIndices.length > 0; 68 | /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This comment 69 | * suppresses an error when upgrading Flow's support for React. To see the 70 | * error delete this comment and run Flow. */ 71 | const childArray = hasStickyHeaders && React.Children.toArray(this.props.children); 72 | const children = hasStickyHeaders ? 73 | /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This 74 | * comment suppresses an error when upgrading Flow's support for React. 75 | * To see the error delete this comment and run Flow. */ 76 | childArray.map((child, index) => { 77 | /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This 78 | * comment suppresses an error when upgrading Flow's support for React. 79 | * To see the error delete this comment and run Flow. */ 80 | const indexOfIndex = child ? stickyHeaderIndices.indexOf(index) : -1; 81 | if (indexOfIndex > -1) { 82 | const key = child.key; 83 | /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This 84 | * comment suppresses an error when upgrading Flow's support for 85 | * React. To see the error delete this comment and run Flow. */ 86 | const nextIndex = stickyHeaderIndices[indexOfIndex + 1]; 87 | return ( 88 | this._setStickyHeaderRef(key, ref)} 91 | nextHeaderLayoutY={ 92 | /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) 93 | * This comment suppresses an error when upgrading Flow's 94 | * support for React. To see the error delete this comment and 95 | * run Flow. */ 96 | this._headerLayoutYs.get(this._getKeyForIndex(nextIndex, childArray)) 97 | } 98 | onLayout={(event) => this._onStickyHeaderLayout(index, event, key)} 99 | scrollAnimatedValue={this._scrollAnimatedValue}> 100 | {child} 101 | 102 | ); 103 | } else { 104 | return child; 105 | } 106 | }) : 107 | /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This 108 | * comment suppresses an error when upgrading Flow's support for React. 109 | * To see the error delete this comment and run Flow. */ 110 | this.props.children; 111 | const contentContainer = 112 | =0.53.0 site=react_native_fb,react_native_oss) This 115 | * comment suppresses an error when upgrading Flow's support for React. 116 | * To see the error delete this comment and run Flow. */ 117 | ref={this._setInnerViewRef} 118 | style={contentContainerStyle} 119 | removeClippedSubviews={ 120 | // Subview clipping causes issues with sticky headers on Android and 121 | // would be hard to fix properly in a performant way. 122 | Platform.OS === 'android' && hasStickyHeaders ? 123 | false : 124 | this.props.removeClippedSubviews 125 | } 126 | collapsable={false}> 127 | {children} 128 | ; 129 | 130 | const alwaysBounceHorizontal = 131 | this.props.alwaysBounceHorizontal !== undefined ? 132 | this.props.alwaysBounceHorizontal : 133 | this.props.horizontal; 134 | 135 | const alwaysBounceVertical = 136 | this.props.alwaysBounceVertical !== undefined ? 137 | this.props.alwaysBounceVertical : 138 | !this.props.horizontal; 139 | 140 | const DEPRECATED_sendUpdatedChildFrames = 141 | !!this.props.DEPRECATED_sendUpdatedChildFrames; 142 | 143 | const baseStyle = this.props.horizontal ? styles.baseHorizontal : styles.baseVertical; 144 | const props = { 145 | ...this.props, 146 | alwaysBounceHorizontal, 147 | alwaysBounceVertical, 148 | style: [baseStyle, this.props.style], 149 | // Override the onContentSizeChange from props, since this event can 150 | // bubble up from TextInputs 151 | onContentSizeChange: null, 152 | onMomentumScrollBegin: this.scrollResponderHandleMomentumScrollBegin, 153 | onMomentumScrollEnd: this.scrollResponderHandleMomentumScrollEnd, 154 | onResponderGrant: this.scrollResponderHandleResponderGrant, 155 | onResponderReject: this.scrollResponderHandleResponderReject, 156 | onResponderRelease: this.scrollResponderHandleResponderRelease, 157 | /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This 158 | * comment suppresses an error when upgrading Flow's support for React. 159 | * To see the error delete this comment and run Flow. */ 160 | onResponderTerminate: this.scrollResponderHandleTerminate, 161 | onResponderTerminationRequest: this.scrollResponderHandleTerminationRequest, 162 | onScroll: this._handleScroll, 163 | onScrollBeginDrag: this.scrollResponderHandleScrollBeginDrag, 164 | onScrollEndDrag: this.scrollResponderHandleScrollEndDrag, 165 | onScrollShouldSetResponder: this.scrollResponderHandleScrollShouldSetResponder, 166 | onStartShouldSetResponder: this.scrollResponderHandleStartShouldSetResponder, 167 | onStartShouldSetResponderCapture: this.scrollResponderHandleStartShouldSetResponderCapture, 168 | onTouchEnd: this.scrollResponderHandleTouchEnd, 169 | onTouchMove: this.scrollResponderHandleTouchMove, 170 | onTouchStart: this.scrollResponderHandleTouchStart, 171 | scrollEventThrottle: hasStickyHeaders ? 1 : this.props.scrollEventThrottle, 172 | sendMomentumEvents: (this.props.onMomentumScrollBegin || this.props.onMomentumScrollEnd) ? 173 | true : false, 174 | DEPRECATED_sendUpdatedChildFrames, 175 | }; 176 | 177 | const {decelerationRate} = this.props; 178 | if (decelerationRate) { 179 | props.decelerationRate = processDecelerationRate(decelerationRate); 180 | } 181 | 182 | const refreshControl = this.props.refreshControl; 183 | 184 | if (refreshControl) { 185 | // On iOS the RefreshControl is a child of the ScrollView. 186 | // tvOS lacks native support for RefreshControl, so don't include it in that case 187 | return ( 188 | /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This 189 | * comment suppresses an error when upgrading Flow's support for 190 | * React. To see the error delete this comment and run Flow. */ 191 | 192 | {Platform.isTVOS ? null : refreshControl} 193 | {contentContainer} 194 | 195 | ); 196 | } 197 | return ( 198 | /* $FlowFixMe(>=0.53.0 site=react_native_fb,react_native_oss) This 199 | * comment suppresses an error when upgrading Flow's support for React. 200 | * To see the error delete this comment and run Flow. */ 201 | 202 | {contentContainer} 203 | 204 | ); 205 | } 206 | } 207 | 208 | const styles = StyleSheet.create({ 209 | baseVertical: { 210 | flexGrow: 1, 211 | flexShrink: 1, 212 | flexDirection: 'column', 213 | overflow: 'scroll', 214 | }, 215 | baseHorizontal: { 216 | flexGrow: 1, 217 | flexShrink: 1, 218 | flexDirection: 'row', 219 | overflow: 'scroll', 220 | }, 221 | contentContainerHorizontal: { 222 | flexDirection: 'row', 223 | }, 224 | }); 225 | 226 | export default Platform.OS === 'ios' ? MJScrollView : ScrollView; 227 | 228 | 229 | -------------------------------------------------------------------------------- /src/util/date.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by jszh on 2020/5/13. 3 | */ 4 | 5 | export default function dateFormat() { 6 | let date = new Date(new Date() - 0); 7 | let y = date.getFullYear(); //年 8 | let m = date.getMonth() + 1; 9 | m = m < 10 ? ('0' + m) : m; //月 10 | let d = date.getDate(); 11 | d = d < 10 ? ('0' + d) : d; //天 12 | let h = date.getHours(); 13 | h = h < 10 ? ('0' + h) : h; //时 14 | let minute = date.getMinutes(); 15 | minute = minute < 10 ? ('0' + minute) : minute; //分 16 | let second = date.getSeconds(); 17 | second = second < 10 ? ('0' + second) : second; //秒 18 | return m + '-' + d + ' ' + h + ':' + minute; 19 | } 20 | --------------------------------------------------------------------------------