Skip to content

Commit

Permalink
Add swipe to refresh
Browse files Browse the repository at this point in the history
  • Loading branch information
race604 committed Oct 2, 2015
1 parent ec4388f commit f6a76cf
Show file tree
Hide file tree
Showing 10 changed files with 311 additions and 7 deletions.
17 changes: 15 additions & 2 deletions ListScreen.js
Expand Up @@ -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/';
Expand Down Expand Up @@ -159,6 +160,8 @@ var ListScreen = React.createClass({
theme: this.state.theme,
dataSource: dataSouce,
});

this.swipeRefreshLayout && this.swipeRefreshLayout.finishRefresh();
})
.catch((error) => {
console.error(error);
Expand All @@ -168,6 +171,7 @@ var ListScreen = React.createClass({
theme: this.state.theme,
dataSource: this.state.dataSource.cloneWithRows([]),
});
this.swipeRefreshLayout && this.swipeRefreshLayout.finishRefresh();
})
.done();
},
Expand Down Expand Up @@ -250,9 +254,14 @@ var ListScreen = React.createClass({
/>
);
},
onRefresh: function() {
this.onSelectTheme(this.state.theme);
},
render: function() {
var content = this.state.dataSource.getRowCount() === 0 ?
<View style={styles.centerEmpty}><Text>加载失败</Text></View> :
<View style={styles.centerEmpty}>
<Text>{this.state.isLoading ? '正在加载...' : '加载失败'}</Text>
</View> :
<ListView
ref="listview"
dataSource={this.state.dataSource}
Expand Down Expand Up @@ -281,7 +290,11 @@ var ListScreen = React.createClass({
actions={toolbarActions}
onIconClicked={() => this.drawer.openDrawer()}
onActionSelected={this.onActionSelected} />
{content}
<SwipeRefreshLayoutAndroid
ref={(swipeRefreshLayout) => { this.swipeRefreshLayout = swipeRefreshLayout; }}
onRefresh={this.onRefresh}>
{content}
</SwipeRefreshLayoutAndroid>
</View>
</DrawerLayoutAndroid>

Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -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**

Expand Down
92 changes: 92 additions & 0 deletions 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 =
<View ref={INNERVIEW_REF} style={styles.mainSubview} collapsable={false}>
{this.props.children}
</View>;
return (
<AndroidSwipeRefreshLayout
{...this.props}
ref={RK_SWIPE_REF}
style={styles.base}
onRefresh={this._onRefresh}>
{childrenWrapper}
</AndroidSwipeRefreshLayout>
);
},

_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;
@@ -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;
Expand All @@ -18,6 +20,7 @@ public List<ViewManager> createViewManagers(ReactApplicationContext reactContext
List<ViewManager> result = new ArrayList<>();
result.addAll(main);
result.add(new ReactWebViewManager());
result.add(new ReactSwipeRefreshLayoutManager());

return result;
}
Expand Down
@@ -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;
}
}
@@ -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<ReactSwipeRefreshLayout> {

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<String, Integer> 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()));
}
}
}
@@ -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<RefreshEvent> {

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());
}
}
@@ -1,4 +1,4 @@
package com.race604.rn.component;
package com.race604.react.view.webview;

import android.annotation.TargetApi;
import android.content.Context;
Expand Down
@@ -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;
Expand Down
Expand Up @@ -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;
Expand Down

0 comments on commit f6a76cf

Please sign in to comment.