React-Native仿某電商商品詳情頁面

來源:互聯網
上載者:User

前言: 一周又過去了,一直在趕需求,除了自己利用空餘時間學習一下外壓根就沒時間去研究新東西,唉~程式猿就是這樣,活到老學到老!! 廢話不多說了,公司產品覺得某電商的商品詳情頁面很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:[
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.