前言: 一周又过去了,一直在赶需求,除了自己利用空余时间学习一下外压根就没时间去研究新东西,唉~程序猿就是这样,活到老学到老!! 废话不多说了,公司产品觉得某电商的商品详情页面很nice,问我们能不能实现(宝宝心里苦),于是研究了一波,下面把我研究的东西分享一下,小伙伴有啥好的实现方式还谢谢分享一下哦~拜谢!
先看一下最终实现的效果:
ios/android
简单来说就是分为两个部分(上下),两个都是(scrollview、flatlist)等滑动组件,第一个scrollview滑动到底部的时候,继续上拉显示第二个scrollview,第二个scrollview下拉到顶部的时候,继续下拉回到第一个scrollview并且第一个scrollview回到顶部.
下面说一下我的大体思路:
第一种方式:
利用rn手势,然后监听scrollview的滑动事件,我们都知道rn中的事件传递都是从父控件一层一层往下传递,所以父控件能够拦截scrollview也就是子控件的能力,当scrollview滑动到底部或者顶部的时候拦截scrollview的事件,把事件给父控件,然后通过控制父控件的垂直偏移量首先分页功能,说了那么多理论的东西小伙伴估计都累了,我们后面结合代码一起来说一下.
第二种方式:
封装一个native的组件实现上拉和下拉的操作,rn只需要做一些简单的监听就可以.
两种方式对比来看,能用rn实现最好,因为rn存在的目的也就是为了实现跨平台目的,都用native实现了还用rn干嘛!! 话虽然这样说,但是rn还是给开发人员提供了自定义的方法,用第二种方式实现有点就是性能和体验上要优于rn实现,我接下来会结合两种方式来实现.
先说一下第一种方式
第一步(把页面分为上下两部分):
render() {
return (
<View
style={[styles.container]}
>
{/*第一部分*/}
<Animated.View
style={[styles.container1,{
marginTop:this._aniBack.interpolate({
inputRange:[0,1],
outputRange:[0,-SCREEN_H],
})
}]}
{...this._panResponder.panHandlers}
>
<Animated.View
ref={(ref) => this._container1 = ref}
style={{width: SCREEN_W, height: SCREEN_H,
marginTop:this._aniBack1.interpolate({
inputRange:[0,1],
outputRange:[0,-100],
})}}
>
<ScrollView
ref={(ref)=>this._scroll1=ref}
bounces={false}
scrollEventThrottle={10}
onScroll={this._onScroll.bind(this)}
overScrollMode={'never'}
>
{this._getContent()}
</ScrollView>
</Animated.View>
<View style={{width:SCREEN_W,height:100,backgroundColor:'white',alignItems:'center',justifyContent:'center'}}>
<Text>上拉查看详情</Text>
</View>
</Animated.View>
{/*第二部分*/}
<View
style={styles.container2}
{...this._panResponder2.panHandlers}
>
<Animated.View
ref={(ref) => this._container2 = ref}
style={{
width:SCREEN_W,height:100,backgroundColor:'white',alignItems:'center',justifyContent:'center',
marginTop:this._aniBack2.interpolate({
inputRange:[0,1],
outputRange:[-100,0],
})
}}
>
<Text>下拉回到顶部</Text>
</Animated.View>
<View
style={{width: SCREEN_W, height: SCREEN_H,}}
>
<ScrollView
ref={(ref)=>this._scroll2=ref}
bounces={false}
scrollEventThrottle={10}
onScroll={this._onScroll2.bind(this)}
overScrollMode={'never'}
>
{this._getContent()}
</ScrollView>
</View>
</View>
</View>
);
}
代码我待会会贴出来,原理很简单,我大体说一下我的实现思路,运行代码你会发现,页面是显示了一个红色页面(也就是上部分).
让我们把第一个页面的marginTop调为-SCREEN_H(屏幕高度)的时候,我们会看到第二屏蓝色页面
所以我们只需要在第一个红色页面的scrollview滑动到底部的时候,然后拦截事件,手指抬起的时候,让第一个页面的marginTop从(0到-屏幕高度)的转变,我们同时给个动画实现.那么问题来了,我们该怎么监听scrollview到达顶部或者底部呢?我们又该怎么拦截scrollview的事件呢?
监听scrollview到达顶部或者底部:
到达顶部我们都知道,当scrollview的y轴偏移量=0的时候我们就认为scrollview到达顶部了,转为代码就是:
_onScroll2(event){
this._reachEnd2=false;
let y = event.nativeEvent.contentOffset.y;
if(y<=0){
//到达顶部了
this._reachEnd2=true;
}
}
到达底部也就是当(子控件的高度=y轴滑动的距离+父控件的高度)的时候,转为代码为:
_onScroll(event){
this._reachEnd1=false;
let y = event.nativeEvent.contentOffset.y;
let height = event.nativeEvent.layoutMeasurement.height;
let contentHeight = event.nativeEvent.contentSize.height;
if (contentHeight > height && (y + height >= contentHeight)) {
//到达顶部了
this._reachEnd1=true;
}
}
父控件拦截子控件的事件:
我们在onMoveShouldSetPanResponderCapture返回true,父控件就是拦截掉滑动事件,然后交给自己处理(onPanResponderMove),那么我们红色页面(也就是第一页)的scrollview到达底部的时候,再往上拉的时候,我们拦截事件
_handleMoveShouldSetPanResponderCapture(event: Object, gestureState: Object,): boolean {
console.log('_handleMoveShouldSetPanResponderCapture');
console.log(gestureState.dy);
//当滑动到底部并且继续往上拉的时候
return this._reachEnd1&&gestureState.dy<0;
}
我们第二个页面(也就是蓝色页面)当scrollview滑动到顶部并且继续往下拉的时候,拦截事件:
_handleMoveShouldSetPanResponderCapture2(event: Object, gestureState: Object,): boolean {
console.log(gestureState.dy);
console.log('_handleMoveShouldSetPanResponderCapture2');
//当滑动到顶部并且继续往下拉的时候
return this._reachEnd2&&gestureState.dy>=0;
}
好啦~我们第一个页面的父控件拿到滑动事件后,我们继续往上拉,也就是把往上拉的距离赋给我们的“上拉查看详情“组件了:
_handlePanResponderMove(event: Object, gestureState: Object): void {
//防止事件拦截不准,我们把scrollview的scrollEnabled:false设置为false
this._scroll1.setNativeProps({
scrollEnabled:false
})
let nowLeft =gestureState.dy*0.5;
//控制一个页面的“上拉查看详情“组件显示
this._container1.setNativeProps({
marginTop:nowLeft
})
console.log(‘_handlePanResponderMove’,gestureState.dy);
<Animated.View
ref={(ref) => this._container1 = ref}
style={{width: SCREEN_W, height: SCREEN_H,
marginTop:this._aniBack1.interpolate({
inputRange:[0,1],
outputRange:[0,-100],
})}}
>
<ScrollView
ref={(ref)=>this._scroll1=ref}
bounces={false}
scrollEventThrottle={10}
onScroll={this._onScroll.bind(this)}
overScrollMode={'never'}
>
{this._getContent()}
</ScrollView>
</Animated.View>
<View style={{width:SCREEN_W,height:100,backgroundColor:'white',alignItems:'center',justifyContent:'center'}}>
<Text>上拉查看详情</Text>
</View>
代码很简单,我就不一一解释了,小伙伴自己去运行看看哈,下面是第一种方式的所有实现代码,直接运行就可以了:
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
TouchableOpacity,
Dimensions,
ScrollView,
PanResponder,
Animated,
StatusBar,
} from 'react-native';
const SCREEN_W = Dimensions.get('window').width;
const SCREEN_H = Dimensions.get('window').height-(Platform.OS==='android'?StatusBar.currentHeight:0);
import SwipeRow from './SwipeRow';
export default class App extends Component {
// 构造
constructor(props) {
super(props);
// 初始状态
// 初始状态
this._panResponder = PanResponder.create({
onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture.bind(this),
onStartShouldSetResponderCapture:this._handleMoveShouldSetPanResponderCapture.bind(this),
onPanResponderMove: this._handlePanResponderMove.bind(this),
onPanResponderRelease: this._handlePanResponderEnd.bind(this),
onPanResponderGrant:this._handlePanGrant.bind(this),
onPanResponderTerminate:()=>{
console.log('onPanResponderTerminate');
this._aniBack1.setValue(0);
Animated.spring(this._aniBack1,{
toValue:1
}).start(()=>{
this._handlePanResponderEnd();
});
},
onShouldBlockNativeResponder: (event, gestureState) => false,//表示是否用 Native 平台的事件处理,默认是禁用的,全部使用 JS 中的事件处理,注意此函数目前只能在 Android 平台上使用
});
this._panResponder2 = PanResponder.create({
onMoveShouldSetPanResponderCapture: this._handleMoveShouldSetPanResponderCapture2.bind(this),
onStartShouldSetResponderCapture:this._handleMoveShouldSetPanResponderCapture2.bind(this),
onPanResponderMove: this._handlePanResponderMove2.bind(this),
onPanResponderRelease: this._handlePanResponderEnd2.bind(this),
onPanResponderGrant:this._handlePanGrant2.bind(this),
onPanResponderTerminate:()=>{
this._container2.setNativeProps({
marginTop:0
})
this._aniBack2.setValue(0);
Animated.spring(this._aniBack2,{
toValue:1
}).start(()=>{
this._handlePanResponderEnd2();
});
console.log('onPanResponderTerminate2');
},
onShouldBlockNativeResponder: (event, gestureState) => false,//表示是否用 Native 平台的事件处理,默认是禁用的,全部使用 JS 中的事件处理,注意此函数目前只能在 Android 平台上使用
});
this._reachEnd1=false;
this._reachEnd2=true;
this._aniBack=new Animated.Value(0);
this._aniBack1=new Animated.Value(0);
this._aniBack2=new Animated.Value(0);
}
_handlePanGrant(event: Object, gestureState: Object,){
this._scroll1.setNativeProps({
scrollEnabled:false
})
}
_handleMoveShouldSetPanResponderCapture(event: Object, gestureState: Object,): boolean {
console.log('_handleMoveShouldSetPanResponderCapture');
console.log(gestureState.dy);
return this._reachEnd1&&gestureState.dy<0;
}
_handlePanResponderMove(event: Object, gestureState: Object): void {
this._scroll1.setNativeProps({
scrollEnabled:false
})
let nowLeft =gestureState.dy*0.5;
this._container1.setNativeProps({
marginTop:nowLeft
})
console.log('_handlePanResponderMove',gestureState.dy);
}
_handlePanResponderEnd(event: Object, gestureState: Object): void {
this._aniBack.setValue(0);
this._scroll1.setNativeProps({
scrollEnabled: true
})
this._scroll1.scrollTo({y:0},true);
Animated.timing(this._aniBack, {
duration: 500,
toValue: 1
}).start();
this._aniBack1.setValue(1);
Animated.spring(this._aniBack1, {
toValue: 0
}).start();
}
_handleMoveShouldSetPanResponderCapture2(event: Object, gestureState: Object,): boolean {
console.log(gestureState.dy);
console.log('_handleMoveShouldSetPanResponderCapture2');
return this._reachEnd2&&gestureState.dy>=0;
}
_handlePanResponderMove2(event: Object, gestureState: Object): void {
console.log('_handlePanResponderMove2');
let nowLeft =gestureState.dy*0.5;
this._scroll2.setNativeProps({
scrollEnabled:false
})
this._container2.setNativeProps({
marginTop:-100+nowLeft
})
console.log('_handlePanResponderMove2',gestureState.dy);
}
_handlePanGrant2(event: Object, gestureState: Object,){
this._scroll2.setNativeProps({
scrollEnabled:false
})
}
_handlePanResponderEnd2(event: Object, gestureState: Object): void {
this._aniBack.setValue(1);
this._scroll2.setNativeProps({
scrollEnabled: true
})
Animated.timing(this._aniBack, {
duration: 500,
toValue: 0
}).start();
this._aniBack2.setValue(1);
Animated.spring(this._aniBack2, {
toValue: 0
}).start();
}
render() {
return (
<View
style={[styles.container]}
>
{/*第一部分*/}
<Animated.View
style={[styles.container1,{
marginTop:this._aniBack.interpolate({
inputRange:[0,1],
outputRange:[0,-SCREEN_H],
})
}]}
{...this._panResponder.panHandlers}
>
<Animated.View
ref={(ref) => this._container1 = ref}
style={{width: SCREEN_W, height: SCREEN_H,
marginTop:this._aniBack1.interpolate({
inputRange:[0,1],
outputRange:[0,-100],
})}}
>
<ScrollView
ref={(ref)=>this._scroll1=ref}
bounces={false}
scrollEventThrottle={10}
onScroll={this._onScroll.bind(this)}
overScrollMode={'never'}
>
{this._getContent()}
</ScrollView>
</Animated.View>
<View style={{width:SCREEN_W,height:100,backgroundColor:'white',alignItems:'center',justifyContent:'center'}}>
<Text>上拉查看详情</Text>
</View>
</Animated.View>
{/*第二部分*/}
<View
style={styles.container2}
{...this._panResponder2.panHandlers}
>
<Animated.View
ref={(ref) => this._container2 = ref}
style={{
width:SCREEN_W,height:100,backgroundColor:'white',alignItems:'center',justifyContent:'center',
marginTop:this._aniBack2.interpolate({
inputRange:[0,1],
outputRange:[-100,0],
})
}}
>
<Text>下拉回到顶部</Text>
</Animated.View>
<View
style={{width: SCREEN_W, height: SCREEN_H,}}
>
<ScrollView
ref={(ref)=>this._scroll2=ref}
bounces={false}
scrollEventThrottle={10}
onScroll={this._onScroll2.bind(this)}
overScrollMode={'never'}
>
{this._getContent()}
</ScrollView>
</View>
</View>
</View>
);
}
_onScroll(event){
this._reachEnd1=false;
let y = event.nativeEvent.contentOffset.y;
let height = event.nativeEvent.layoutMeasurement.height;
let contentHeight = event.nativeEvent.contentSize.height;
if (contentHeight > height && (y + height >= contentHeight)) {
this._reachEnd1=true;
}
}
_onScroll2(event){
this._reachEnd2=false;
let y = event.nativeEvent.contentOffset.y;
if(y<=0){
this._reachEnd2=true;
}
}
_getContent(){
let contents=[];
for (let i = 0; i < 50; i++) {
contents.push(
<Text style={{color:'#fff',marginTop:10}} key={'item'+i}>content-->{i}</Text>
);
}
return contents;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
width: '100%',
// transform:[{translateY:-SCREEN_H}]
},
container1:{
width:SCREEN_W,
height:SCREEN_H,
backgroundColor:'red'
},
container2:{
width:SCREEN_W,
height:SCREEN_H,
backgroundColor:'blue',
overflow:'hidden'
},
});
简单说一下第二种方式(我之后会把项目的全部代码贴出来):
第一步(你需要封装一个上拉和下拉刷新的控件给rn,)为了方便我就直接去引用了一个第三方的下拉框架:
Android智能下拉刷新框架-SmartRefreshLayout
感谢这哥们的无私奉献~~我是怀着膜拜的心态看完了这哥们代码,大牛一枚,鉴定完毕~!! 哈哈~ 小伙伴也可以去看看哈,我就不解释这框架了,直接拿过来用了.
最后rn用的时候很简单:
/**
* @author YASIN
* @version [React-Native Ocj V01, 2018/3/28]
* @date 17/2/23
* @description App2
*/
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
TouchableOpacity,
Dimensions,
ScrollView,
PanResponder,
Animated,
StatusBar,
} from 'react-native';
const SCREEN_W = Dimensions.get('window').width;
const SCREEN_H = Dimensions.get('window').height - (Platform.OS === 'android' ? StatusBar.currentHeight : 0);
import SmallLayout from './SmartRefreshLayout';
export default class App2 extends Component {
// 构造
constructor(props) {
super(props);
// 初始状态
this.state = {
loadMore1: false,
loadMore1: false,
};
this.aniBack = new Animated.Value(0);
}
render() {
return (
<Animated.View
style={[styles.container, {
marginTop: this.aniBack.interpolate({
inputRange: [0, 1],
outputRange: [0, -SCREEN_H],
})
}]}
>
{/*第一部分*/}
<SmallLayout
style={{width: '100%', height: SCREEN_H, backgroundColor: 'red'}}
loadMore={this.state.loadMore1}
refreshEnable={false}
onLoadMore={() => {
this._startAniNext();
}}
>
<ScrollView
>
{this._getContent()}
</ScrollView>
</SmallLayout>
{/*第二部分*/}
<SmallLayout
key={'small2'}
style={{width: '100%', height: SCREEN_H, backgroundColor: 'blue'}}
refreshing={this.state.loadMore2}
loadMoreEnable={false}
onRefresh={()=>{
this._startAniBackTop();
}}
>
<ScrollView
>
{this._getContent()}
</ScrollView>
</SmallLayout>
</Animated.View>
);
}
_startAniBackTop() {
this.aniBack.setValue(1);
Animated.timing(this.aniBack, {
duration: 1000,
toValue: 0,
}).start(() => {
});
}
_startAniNext() {
this.aniBack.setValue(0);
Animated.timing(this.aniBack, {
duration: 1000,
toValue: 1,
}).start(() => {
});
}
_getContent() {
let contents = [];
for (let i = 0; i < 50; i++) {
contents.push(
<Text style={{color: '#fff', marginTop: 10}} key={'item' + i}>content-->{i}</Text>
);
}
return contents;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
width: '100%',
}
});
是不是很简单?对的,简简单单100行代码就搞定了,然后效果咋样?
事件衔接很平滑,体验比rn好!当然,我只实现了android部分,小伙伴懂ios的也可以把ios的实现一下~~
我简单说一下与native的桥接(原谅我只能以android为例子)
第一步(新建一个SmartRefreshLayoutManager继承ViewGroupManager,然后提供rn调用的属性跟方法还有回调):
/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
* <p>
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.swipedemo;
import android.support.annotation.NonNull;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.module.annotations.ReactModule;
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewGroupManager;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.scwang.smartrefresh.layout.SmartRefreshLayout;
import com.scwang.smartrefresh.layout.api.RefreshLayout;
import com.scwang.smartrefresh.layout.listener.OnLoadMoreListener;
import com.scwang.smartrefresh.layout.listener.OnRefreshListener;
import java.util.Map;
/**
* ViewManager for {@link com.facebook.react.views.swiperefresh.ReactSwipeRefreshLayout} which allows the user to "pull to refresh" a
* child view. Emits an {@code onRefresh} event when this happens.
*/
@ReactModule(name = SmartRefreshLayoutManager.REACT_CLASS)
public class SmartRefreshLayoutManager extends ViewGroupManager<SmartRefreshLayout> {
protected static final String REACT_CLASS = "AndroidSmartRefreshLayout";
@ReactProp(name = "refreshing")
public void setRefreshing(SmartRefreshLayout view, boolean refreshing) {
if (refreshing) {
view.autoRefresh();
} else {
view.finishRefresh();
}
}
@ReactProp(name = "loadMore")
public void setLoadMore(SmartRefreshLayout view, boolean refreshing) {
if (refreshing) {
view.autoLoadMore();
} else {
view.finishLoadMore();
}
}
@ReactProp(name = "loadMoreEnable")
public void setLoadMoreEnable(SmartRefreshLayout view, boolean enable) {
view.setEnableLoadMore(enable);
}
@ReactProp(name = "refreshEnable")
public void setRefreshEnable(SmartRefreshLayout view, boolean enable) {
view.setEnableRefresh(enable);
}
@Override
protected SmartRefreshLayout createViewInstance(ThemedReactContext reactContext) {
final SmartRefreshLayout smartRefreshLayout = new SmartRefreshLayout(reactContext);
smartRefreshLayout.setOnLoadMoreListener(new OnLoadMoreListener() {
@Override
public void onLoadMore(@NonNull final RefreshLayout refreshLayout) {
dispatchEvent(smartRefreshLayout,new LoadMoreEvent(smartRefreshLayout.getId()));
}
});
smartRefreshLayout.setOnRefreshListener(new OnRefreshListener() {
@Override
public void onRefresh(@NonNull RefreshLayout refreshLayout) {
dispatchEvent(smartRefreshLayout,new RefreshEvent(smartRefreshLayout.getId()));
}
});
//触发自动刷新
smartRefreshLayout.setRefreshHeader(new CustHeader(reactContext));
// smartRefreshLayout.setEnableOverScrollBounce(true);
smartRefreshLayout.setEnableOverScrollDrag(false);
smartRefreshLayout.setEnableAutoLoadMore(false);
smartRefreshLayout.setRefreshFooter(new CustFooter(reactContext));
return smartRefreshLayout;
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
return MapBuilder.<String, Object>builder()
.put("topRefresh", MapBuilder.of("registrationName", "onRefresh"))
.put("bottomRefresh", MapBuilder.of("registrationName", "onLoadMore"))
.build();
}
protected static void dispatchEvent(SmartRefreshLayout refreshLayout, Event event) {
ReactContext reactContext = (ReactContext) refreshLayout.getContext();
EventDispatcher eventDispatcher =
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher();
eventDispatcher.dispatchEvent(event);
}
}
我只能说还是需要一点android基础才能看懂这代码的,哈哈~ 原谅我又装了一个b, 会android的我就不用解释这是啥了~~不懂的可以查看原生给rn封装的自带的下拉刷新组件:
哈哈~ 我也是仿照自带的AndroidSwipeRefreshLayout组件封装的,所以android的童鞋没事研究一下源码还是很有必要的.
好啦~ 逼装完了,我们继续哈,
然后新建一个叫SmartRefreshLayoutPackage类继承ReactPackage把SmartRefreshLayoutManager添加进去:
package com.swipedemo;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Created by yinqingyang on 2018/3/28.
*/
public class SmartRefreshLayoutPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Arrays.<ViewManager>asList(new SmartRefreshLayoutManager());
}
}
最后将我们的SmartRefreshLayoutPackage添加进rn,我们来到我们的MainApplication:
public class MainApplication extends Application implements ReactApplication {
static {
SmartRefreshLayout.setDefaultRefreshHeaderCreator(new DefaultRefreshHeaderCreator() {
@NonNull
@Override
public RefreshHeader createRefreshHeader(@NonNull Context context, @NonNull RefreshLayout layout) {
layout.setPrimaryColorsId(R.color.colorPrimaryDark, android.R.color.white);//全局设置主题颜色
return new ClassicsHeader(context).setTimeFormat(new DynamicTimeFormat("更新于 %s")).setArrowResource(R.mipmap.ic_launcher);
}
});
SmartRefreshLayout.setDefaultRefreshFooterCreator(new DefaultRefreshFooterCreator() {
@NonNull
@Override
public RefreshFooter createRefreshFooter(@NonNull Context context, @NonNull RefreshLayout layout) {
return new ClassicsFooter(context);
}
});
}
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
//添加我们的自定义package
new SmartRefreshLayoutPackage()
);
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
好啦!我们已经搞定原生部分了,那么我们的rn怎么才能拿到我们原生提供的view呢?
回到rn
第一步:我们创建一个SmartRefreshLayout.android.js文件,提供跟native 一样的属性:
export default class SmartRefreshLayout extends Component {
static propTypes = {
...ViewPropTypes,
refreshing: PropTypes.bool,
loadMore: PropTypes.bool,
onLoadMore: PropTypes.func,
onRefresh: PropTypes.func,
loadMoreEnable: PropTypes.bool,
refreshEnable: PropTypes.bool,
};
static defaultProps = {
refreshing: false,
loadMore: false,
refreshEnable: true,
loadMoreEnable: true,
};
记住!! native有的属性,我们定义的view一定要有.
然后通过rn自带的requireNativeComponent方法把native的view桥接过来:
const AndroidSmartRefreshLayout = requireNativeComponent('AndroidSmartRefreshLayout', SmartRefreshLayout);
最后渲染出来:
render() {
const {children, style, loadMore, refreshing, refreshEnable, loadMoreEnable} = this.props;
return (
<AndroidSmartRefreshLayout
ref={ref => {
this._nativeRef = ref;
}}
style={[style]}
onLoadMore={this._onLoadMore}
onRefresh={this._onRefresh}
loadMore={loadMore}
refreshEnable={refreshEnable}
loadMoreEnable={loadMoreEnable}
refreshing={refreshing}
>
{React.Children.toArray(children)}
</AndroidSmartRefreshLayout>
);
}
全部代码:
/**
* @author YASIN
* @version [React-Native Ocj V01, 2018/3/28]
* @date 17/2/23
* @description SmartRefreshLayout.android
*/
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
} from 'react-native';
const PropTypes = require('prop-types');
const ViewPropTypes = require('ViewPropTypes');
const requireNativeComponent = require('requireNativeComponent');
export default class SmartRefreshLayout extends Component {
static propTypes = {
...ViewPropTypes,
refreshing: PropTypes.bool,
loadMore: PropTypes.bool,
onLoadMore: PropTypes.func,
onRefresh: PropTypes.func,
loadMoreEnable: PropTypes.bool,
refreshEnable: PropTypes.bool,
};
static defaultProps = {
refreshing: false,
loadMore: false,
refreshEnable: true,
loadMoreEnable: true,
};
// 构造
constructor(props) {
super(props);
this._lastNativeLoadMore = false;
this._lastNativeRefresh = false;
}
render() {
const {children, style, loadMore, refreshing, refreshEnable, loadMoreEnable} = this.props;
return (
<AndroidSmartRefreshLayout
ref={ref => {
this._nativeRef = ref;
}}
style={[style]}
onLoadMore={this._onLoadMore}
onRefresh={this._onRefresh}
loadMore={loadMore}
refreshEnable={refreshEnable}
loadMoreEnable={loadMoreEnable}
refreshing={refreshing}
>
{React.Children.toArray(children)}
</AndroidSmartRefreshLayout>
);
}
componentDidMount() {
this._lastNativeLoadMore = this.props.refreshing;
};
componentDidUpdate(prevProps: { loadMore: boolean }) {
// RefreshControl is a controlled component so if the native refreshing
// value doesn't match the current js refreshing prop update it to
// the js value.
if (this.props.loadMore !== prevProps.loadMore) {
this._lastNativeLoadMore = this.props.loadMore;
} else if (this.props.loadMore !== this._lastNativeLoadMore) {
this._nativeRef.setNativeProps({loadMore: this.props.loadMore});
this._lastNativeLoadMore = this.props.loadMore;
}
if (this.props.refreshing !== prevProps.refreshing) {
this._lastNativeRefresh = this.props.refreshing;
} else if (this.props.refreshing !== this._lastNativeRefresh) {
this._nativeRef.setNativeProps({refreshing: this.props.refreshing});
this._lastNativeRefresh = this.props.refreshing;
}
}
_onLoadMore = (event) => {
this._lastNativeLoadMore = true;
this.props.onLoadMore && this.props.onLoadMore();
// The native component will start refreshing so force an update to
// make sure it stays in sync with the js component.
this.forceUpdate();
}
_onRefresh=(event)=>{
this._lastNativeRefresh = true;
this.props.onRefresh && this.props.onRefresh();
// The native component will start refreshing so force an update to
// make sure it stays in sync with the js component.
this.forceUpdate();
}
}
const AndroidSmartRefreshLayout = requireNativeComponent('AndroidSmartRefreshLayout', SmartRefreshLayout);
好啦! 我们按照我们第一种实现方式的部分代码实现一下:
/**
* @author YASIN
* @version [React-Native Ocj V01, 2018/3/28]
* @date 17/2/23
* @description App2
*/
import React, {Component} from 'react';
import {
Platform,
StyleSheet,
Text,
View,
TouchableOpacity,
Dimensions,
ScrollView,
PanResponder,
Animated,
StatusBar,
} from 'react-native';
const SCREEN_W = Dimensions.get('window').width;
const SCREEN_H = Dimensions.get('window').height - (Platform.OS === 'android' ? StatusBar.currentHeight : 0);
import SmallLayout from './SmartRefreshLayout';
export default class App2 extends Component {
// 构造
constructor(props) {
super(props);
// 初始状态
this.state = {
loadMore1: false,
loadMore1: false,
};
this.aniBack = new Animated.Value(0);
}
render() {
return (
<Animated.View
style={[styles.container, {
marginTop: this.aniBack.interpolate({
inputRange: [0, 1],
outputRange: [0, -SCREEN_H],
})
}]}
>
{/*第一部分*/}
<SmallLayout
style={{width: '100%', height: SCREEN_H, backgroundColor: 'red'}}
loadMore={this.state.loadMore1}
refreshEnable={false}
onLoadMore={() => {
this._startAniNext();
}}
>
<ScrollView
>
{this._getContent()}
</ScrollView>
</SmallLayout>
{/*第二部分*/}
<SmallLayout
key={'small2'}
style={{width: '100%', height: SCREEN_H, backgroundColor: 'blue'}}
refreshing={this.state.loadMore2}
loadMoreEnable={false}
onRefresh={()=>{
this._startAniBackTop();
}}
>
<ScrollView
>
{this._getContent()}
</ScrollView>
</SmallLayout>
</Animated.View>
);
}
_startAniBackTop() {
this.aniBack.setValue(1);
Animated.timing(this.aniBack, {
duration: 1000,
toValue: 0,
}).start(() => {
});
}
_startAniNext() {
this.aniBack.setValue(0);
Animated.timing(this.aniBack, {
duration: 1000,
toValue: 1,
}).start(() => {
});
}
_getContent() {
let contents = [];
for (let i = 0; i < 50; i++) {
contents.push(
<Text style={{color: '#fff', marginTop: 10}} key={'item' + i}>content-->{i}</Text>
);
}
return contents;
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
width: '100%',
}
});
最后运行代码就可以看到我们之前截图的效果了.
欢迎交流,欢迎入群~~
项目github地址:
https://github.com/913453448/SwipeDemo