From f6a76cf98561d0c5d574c9c6c4ca9add087831dd Mon Sep 17 00:00:00 2001 From: race604 Date: Fri, 2 Oct 2015 23:43:44 +0800 Subject: [PATCH] Add swipe to refresh --- ListScreen.js | 17 ++- README.md | 2 +- SwipeRereshLayout.js | 92 +++++++++++++++ .../view}/MyReactPackage.java | 5 +- .../swiperefresh/ReactSwipeRefreshLayout.java | 56 +++++++++ .../ReactSwipeRefreshLayoutManager.java | 108 ++++++++++++++++++ .../view/swiperefresh/event/RefreshEvent.java | 32 ++++++ .../view/webview}/ObservableWebView.java | 2 +- .../view/webview}/ReactWebViewManager.java | 2 +- .../com/race604/zhihu/daily/MainActivity.java | 2 +- 10 files changed, 311 insertions(+), 7 deletions(-) create mode 100644 SwipeRereshLayout.js rename android/app/src/main/java/com/race604/{rn/component => react/view}/MyReactPackage.java (74%) create mode 100644 android/app/src/main/java/com/race604/react/view/swiperefresh/ReactSwipeRefreshLayout.java create mode 100644 android/app/src/main/java/com/race604/react/view/swiperefresh/ReactSwipeRefreshLayoutManager.java create mode 100644 android/app/src/main/java/com/race604/react/view/swiperefresh/event/RefreshEvent.java rename android/app/src/main/java/com/race604/{rn/component => react/view/webview}/ObservableWebView.java (97%) rename android/app/src/main/java/com/race604/{rn/component => react/view/webview}/ReactWebViewManager.java (97%) diff --git a/ListScreen.js b/ListScreen.js index 79b82a0..6933283 100644 --- a/ListScreen.js +++ b/ListScreen.js @@ -20,6 +20,7 @@ var TimerMixin = require('react-timer-mixin'); var StoryItem = require('./StoryItem'); var ThemesList = require('./ThemesList'); var DataRepository = require('./DataRepository'); +var SwipeRefreshLayoutAndroid = require('./SwipeRereshLayout'); var API_LATEST_URL = 'http://news.at.zhihu.com/api/4/news/latest'; var API_HISTORY_URL = 'http://news.at.zhihu.com/api/4/news/before/'; @@ -159,6 +160,8 @@ var ListScreen = React.createClass({ theme: this.state.theme, dataSource: dataSouce, }); + + this.swipeRefreshLayout && this.swipeRefreshLayout.finishRefresh(); }) .catch((error) => { console.error(error); @@ -168,6 +171,7 @@ var ListScreen = React.createClass({ theme: this.state.theme, dataSource: this.state.dataSource.cloneWithRows([]), }); + this.swipeRefreshLayout && this.swipeRefreshLayout.finishRefresh(); }) .done(); }, @@ -250,9 +254,14 @@ var ListScreen = React.createClass({ /> ); }, + onRefresh: function() { + this.onSelectTheme(this.state.theme); + }, render: function() { var content = this.state.dataSource.getRowCount() === 0 ? - 加载失败 : + + {this.state.isLoading ? '正在加载...' : '加载失败'} + : this.drawer.openDrawer()} onActionSelected={this.onActionSelected} /> - {content} + { this.swipeRefreshLayout = swipeRefreshLayout; }} + onRefresh={this.onRefresh}> + {content} + diff --git a/README.md b/README.md index 2c1d2c9..ff42eb5 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ In this project, I used most of technology in React Native: 1. ~~Add local storage to cache data~~ (done) 2. Add List header banner (Like ViewPager?) 3. ~~Add splash animation~~ (done) -4. Add swipe/pull to refresh +4. ~~Add swipe/pull to refresh~~ (done) 5. Working on incomplete features 6. **iOS compatible** diff --git a/SwipeRereshLayout.js b/SwipeRereshLayout.js new file mode 100644 index 0000000..5abd9f3 --- /dev/null +++ b/SwipeRereshLayout.js @@ -0,0 +1,92 @@ +'use strict'; + +var React = require('react-native'); +var { + requireNativeComponent, + PropTypes, + StyleSheet, + View, +} = React; + +var createReactNativeComponentClass = require('createReactNativeComponentClass'); +var ReactNativeViewAttributes = require('ReactNativeViewAttributes'); +var RCTUIManager = require('NativeModules').UIManager; + +var NativeMethodsMixin = require('NativeMethodsMixin'); + +var RK_SWIPE_REF = 'swiperefreshlayout'; +var INNERVIEW_REF = 'innerView'; + +var SwipeRefreshLayoutAndroid = React.createClass({ + propTypes: { + onRefresh: PropTypes.func, + }, + + mixins: [NativeMethodsMixin], + + getInnerViewNode: function() { + return this.refs[INNERVIEW_REF].getInnerViewNode(); + }, + + render: function() { + var childrenWrapper = + + {this.props.children} + ; + return ( + + {childrenWrapper} + + ); + }, + + _onRefresh: function() { + if (this.props.onRefresh) { + this.props.onRefresh(); + } + }, + + startRefresh: function() { + RCTUIManager.dispatchViewManagerCommand( + this._getSwipeRefreshLayoutHandle(), + RCTUIManager.AndroidSwipeRefreshLayout.Commands.startRefresh, + null + ); + }, + + finishRefresh: function() { + RCTUIManager.dispatchViewManagerCommand( + this._getSwipeRefreshLayoutHandle(), + RCTUIManager.AndroidSwipeRefreshLayout.Commands.finishRefresh, + null + ); + }, + + _getSwipeRefreshLayoutHandle: function() { + return React.findNodeHandle(this.refs[RK_SWIPE_REF]); + }, +}); + +var styles = StyleSheet.create({ + base: { + flex: 1, + }, + mainSubview: { + position: 'absolute', + top: 0, + left: 0, + right: 0, + bottom: 0, + }, +}); + +var AndroidSwipeRefreshLayout = createReactNativeComponentClass({ + validAttributes: ReactNativeViewAttributes.UIView, + uiViewClassName: 'AndroidSwipeRefreshLayout', +}); + +module.exports = SwipeRefreshLayoutAndroid; diff --git a/android/app/src/main/java/com/race604/rn/component/MyReactPackage.java b/android/app/src/main/java/com/race604/react/view/MyReactPackage.java similarity index 74% rename from android/app/src/main/java/com/race604/rn/component/MyReactPackage.java rename to android/app/src/main/java/com/race604/react/view/MyReactPackage.java index 8aed673..3d40214 100644 --- a/android/app/src/main/java/com/race604/rn/component/MyReactPackage.java +++ b/android/app/src/main/java/com/race604/react/view/MyReactPackage.java @@ -1,8 +1,10 @@ -package com.race604.rn.component; +package com.race604.react.view; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.shell.MainReactPackage; import com.facebook.react.uimanager.ViewManager; +import com.race604.react.view.swiperefresh.ReactSwipeRefreshLayoutManager; +import com.race604.react.view.webview.ReactWebViewManager; import java.util.ArrayList; import java.util.List; @@ -18,6 +20,7 @@ public List createViewManagers(ReactApplicationContext reactContext List result = new ArrayList<>(); result.addAll(main); result.add(new ReactWebViewManager()); + result.add(new ReactSwipeRefreshLayoutManager()); return result; } diff --git a/android/app/src/main/java/com/race604/react/view/swiperefresh/ReactSwipeRefreshLayout.java b/android/app/src/main/java/com/race604/react/view/swiperefresh/ReactSwipeRefreshLayout.java new file mode 100644 index 0000000..f5cc65e --- /dev/null +++ b/android/app/src/main/java/com/race604/react/view/swiperefresh/ReactSwipeRefreshLayout.java @@ -0,0 +1,56 @@ +package com.race604.react.view.swiperefresh; + +import android.support.v4.widget.SwipeRefreshLayout; +import android.util.Log; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ScrollView; + +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.uimanager.events.NativeGestureUtil; + +/** + * Created by Jing on 15/9/30. + */ +public class ReactSwipeRefreshLayout extends SwipeRefreshLayout { + + private static final String TAG = "NativeView"; + private ScrollView mScrollChild = null; + + public ReactSwipeRefreshLayout(ReactContext context) { + super(context); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + + if (mScrollChild != null && mScrollChild.getScrollY() > 0) { + return false; + } + + if (super.onInterceptTouchEvent(ev)) { + NativeGestureUtil.notifyNativeGestureStarted(this, ev); + return true; + } + + return false; + } + + @Override + protected void onLayout(boolean changed, int left, int top, int right, int bottom) { + super.onLayout(changed, left, top, right, bottom); + mScrollChild = findScrollChild(this); + } + + private ScrollView findScrollChild(View root) { + View child = root; + while (child instanceof ViewGroup) { + child = ((ViewGroup) child).getChildAt(0); + if (child instanceof ScrollView) { + return (ScrollView) child; + } + } + return null; + } +} diff --git a/android/app/src/main/java/com/race604/react/view/swiperefresh/ReactSwipeRefreshLayoutManager.java b/android/app/src/main/java/com/race604/react/view/swiperefresh/ReactSwipeRefreshLayoutManager.java new file mode 100644 index 0000000..1d86a9b --- /dev/null +++ b/android/app/src/main/java/com/race604/react/view/swiperefresh/ReactSwipeRefreshLayoutManager.java @@ -0,0 +1,108 @@ +package com.race604.react.view.swiperefresh; + +import android.os.SystemClock; +import android.support.v4.widget.SwipeRefreshLayout; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; + +import com.facebook.react.bridge.JSApplicationIllegalArgumentException; +import com.facebook.react.bridge.ReadableArray; +import com.facebook.react.common.MapBuilder; +import com.facebook.react.uimanager.ThemedReactContext; +import com.facebook.react.uimanager.UIManagerModule; +import com.facebook.react.uimanager.ViewGroupManager; +import com.facebook.react.uimanager.events.EventDispatcher; +import com.race604.react.view.swiperefresh.event.RefreshEvent; + +import java.util.Map; + +import javax.annotation.Nullable; + +/** + * Created by Jing on 15/9/30. + */ +public class ReactSwipeRefreshLayoutManager extends ViewGroupManager { + + private static final String REACT_CLASS = "AndroidSwipeRefreshLayout"; + private static final String TAG = "NativeView"; + + public static final int START_REFRESH = 1; + public static final int FINISH_REFRESH = 2; + + @Override + public String getName() { + return REACT_CLASS; + } + + @Override + protected void addEventEmitters(ThemedReactContext reactContext, ReactSwipeRefreshLayout view) { + view.setOnRefreshListener( + new SwipeRefreshEventEmitter( + view, + reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher())); + } + + @Override + protected ReactSwipeRefreshLayout createViewInstance(ThemedReactContext reactContext) { + return new ReactSwipeRefreshLayout(reactContext); + } + + @Override + public boolean needsCustomLayoutForChildren() { + return true; + } + + @Nullable + @Override + public Map getCommandsMap() { + return MapBuilder.of("startRefresh", START_REFRESH, "finishRefresh", FINISH_REFRESH); + } + + @Override + public void receiveCommand(ReactSwipeRefreshLayout root, int commandId, + @Nullable ReadableArray args) { + switch (commandId) { + case START_REFRESH: + root.setRefreshing(true); + return; + case FINISH_REFRESH: + root.setRefreshing(false); + return; + } + } + + @Nullable + @Override + public Map getExportedCustomDirectEventTypeConstants() { + return MapBuilder.of( + RefreshEvent.EVENT_NAME, MapBuilder.of("registrationName", "onRefresh")); + } + + @Override + public void addView(ReactSwipeRefreshLayout parent, View child, int index) { + if (getChildCount(parent) >= 2) { + throw new + JSApplicationIllegalArgumentException("The SwipeRefreshLayout cannot have more than one children"); + } + + parent.addView(child, index); + } + + public static class SwipeRefreshEventEmitter implements SwipeRefreshLayout.OnRefreshListener { + + private final SwipeRefreshLayout mSwipeRefreshLayout; + private final EventDispatcher mEventDispatcher; + + public SwipeRefreshEventEmitter(SwipeRefreshLayout layout, EventDispatcher dispatcher) { + mSwipeRefreshLayout = layout; + mEventDispatcher = dispatcher; + } + + @Override + public void onRefresh() { + mEventDispatcher.dispatchEvent( + new RefreshEvent(mSwipeRefreshLayout.getId(), SystemClock.uptimeMillis())); + } + } +} diff --git a/android/app/src/main/java/com/race604/react/view/swiperefresh/event/RefreshEvent.java b/android/app/src/main/java/com/race604/react/view/swiperefresh/event/RefreshEvent.java new file mode 100644 index 0000000..ea7b745 --- /dev/null +++ b/android/app/src/main/java/com/race604/react/view/swiperefresh/event/RefreshEvent.java @@ -0,0 +1,32 @@ +package com.race604.react.view.swiperefresh.event; + +import com.facebook.react.bridge.Arguments; +import com.facebook.react.uimanager.events.Event; +import com.facebook.react.uimanager.events.RCTEventEmitter; + +/** + * Created by Jing on 15/10/1. + */ +public class RefreshEvent extends Event { + + public static final String EVENT_NAME = "topSwipeRefresh"; + + public RefreshEvent(int viewTag, long timestampMs) { + super(viewTag, timestampMs); + } + + @Override + public String getEventName() { + return EVENT_NAME; + } + + @Override + public short getCoalescingKey() { + return 0; + } + + @Override + public void dispatch(RCTEventEmitter rctEventEmitter) { + rctEventEmitter.receiveEvent(getViewTag(), getEventName(), Arguments.createMap()); + } +} diff --git a/android/app/src/main/java/com/race604/rn/component/ObservableWebView.java b/android/app/src/main/java/com/race604/react/view/webview/ObservableWebView.java similarity index 97% rename from android/app/src/main/java/com/race604/rn/component/ObservableWebView.java rename to android/app/src/main/java/com/race604/react/view/webview/ObservableWebView.java index 5815e61..690f079 100644 --- a/android/app/src/main/java/com/race604/rn/component/ObservableWebView.java +++ b/android/app/src/main/java/com/race604/react/view/webview/ObservableWebView.java @@ -1,4 +1,4 @@ -package com.race604.rn.component; +package com.race604.react.view.webview; import android.annotation.TargetApi; import android.content.Context; diff --git a/android/app/src/main/java/com/race604/rn/component/ReactWebViewManager.java b/android/app/src/main/java/com/race604/react/view/webview/ReactWebViewManager.java similarity index 97% rename from android/app/src/main/java/com/race604/rn/component/ReactWebViewManager.java rename to android/app/src/main/java/com/race604/react/view/webview/ReactWebViewManager.java index 73c6ec5..33b6a52 100644 --- a/android/app/src/main/java/com/race604/rn/component/ReactWebViewManager.java +++ b/android/app/src/main/java/com/race604/react/view/webview/ReactWebViewManager.java @@ -1,4 +1,4 @@ -package com.race604.rn.component; +package com.race604.react.view.webview; import com.facebook.react.uimanager.CatalystStylesDiffMap; import com.facebook.react.uimanager.SimpleViewManager; diff --git a/android/app/src/main/java/com/race604/zhihu/daily/MainActivity.java b/android/app/src/main/java/com/race604/zhihu/daily/MainActivity.java index e97632e..ec70291 100644 --- a/android/app/src/main/java/com/race604/zhihu/daily/MainActivity.java +++ b/android/app/src/main/java/com/race604/zhihu/daily/MainActivity.java @@ -8,7 +8,7 @@ import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactRootView; import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler; -import com.race604.rn.component.MyReactPackage; +import com.race604.react.view.MyReactPackage; import com.rctzhihudaily.BuildConfig; import java.io.File;