React-Native仿某电商商品详情页面

前言: 一周又过去了,一直在赶需求,除了自己利用空余时间学习一下外压根就没时间去研究新东西,唉~程序猿就是这样,活到老学到老!! 废话不多说了,公司产品觉得某电商的商品详情页面很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

猜你喜欢

转载自blog.csdn.net/vv_bug/article/details/79781226