React Native仿某電商店鋪首頁
前言:有朋友需要實現一個仿某電商app店鋪首頁的效果,花了一點點時間簡單的實現了一下,最後的效果大概是這樣的:
1、可以看到,當底部的滑動控制元件滑動的時候tabview滑動到指定的位置後懸浮。
2、當底部的滑動控制元件滑動的時候狀態列view透明度從0到1的轉變。
3、當底部的滑動控制元件滑動的時候背景view需要根據滑動的距離縮放最後跟狀態列view重合。
當然,需求遠不止這些,我只是簡單的實現了一下功能,覺得有必要把自己實現的東西記錄下來,就當筆記了額~ 大牛勿噴。
思路:
1、底部滑動元件滑動的時候回撥,通知headerview(縮放)跟statusview(透明度)重新整理,也就是說headerview跟scrollview在一個view中,一上一下,然後scrollview滑動的時候改變headerview的高度,這時scrollview的高度也在不斷的增加,當達到指定的位置後scrollview固定不動。
2、scrollview的高度一直固定不動,絕對定位到狀態列view下面,tabview相對定位到headerview底部,scrollview滑動的時候回撥,tabview跟隨著scrollview滾動,(其實scrollview高度一直固定不變的,但是scrollview的內容模組給了一個初始marintop值,使內容模組恰好在tabview的下面,雖然加了一個預設的marintop,但是當scrollview滾動的時候,tabview是隨著scrollview滾動的,所以預設的margintop是看不出來的)
有人說了, 第一種方式soeasy就實現了,幹嘛還用第二種方案呢? 哈哈~~ 我也想簡單的就實現了,但是其中遇到的問題就是,headerview跟scrollview是擺在一個view中的,headerview的高度固定,scrollview的高度為flex:1,所以headerview高度變化也意味著scrollview的高度在變,但是在android中,如果scrollview正在滑動並且高度也在改變的時候,會抖動。。。 很抖!!! 唯一不抖動的方式就是不要改變scrollview的高度,所以才會有了方案二,方案二中我一直在強調不改變scrollview的高度。
哈哈~~ 說多了小夥伴應該累了,我們直接上程式碼了哈,第一種方式我就不掩飾了,ios很好,android很抖。我就直接上第二種方式了:
demo中用到了ScrollableTabView這個第三方控制元件,我就不解釋用法了,小夥伴自己去查,很多用法的文章。
首先我們建立一個叫StoreDetails的組建:
render方法
render() {
return (
<View style={styles.container}>
<StatusBar
translucent={true}
barStyle={'light-content'}
backgroundColor={'transparent'}/>
{/*渲染內容view*/}
{this._renderContent()}
{/*渲染headerview*/}
{this._renderHeader()}
{/*渲染頂部搜尋view*/}
{this._renderTopBar()}
</View>
);
}
頭部背景view是懸浮在頭部的,style樣式為:
headerContainer: {
width: ScreenUtils.screenW,
height: ScreenUtils.scaleSize(273),
justifyContent: 'flex-end',
position: 'absolute',
top: 0,
},
/**
* 渲染頭部
* @private
*/
_renderHeader() {
let icon = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1496299246419&di=f6d9e7d99236cb4319782d95cbd7f740&imgtype=0&src=http%3A%2F%2Fwww.pptbz.com%2FSoft%2FUploadSoft%2F200911%2F2009110521380826.jpg';
let icon2 = 'http://pic28.nipic.com/20130503/9252150_153601831000_2.jpg';
return (
<Image
ref={(ref)=>this.topView = ref}
style={styles.headerContainer}
source={{uri: icon2}}>
<View style={styles.headerBottomStyle}>
<Image
style={styles.storeIcon}
source={{uri: icon}}
/>
<View style={styles.headerDescStyle}>
<Text allowFontScaling={false} style={styles.storeTitleStyle}>蘋果旗艦店</Text>
<Text allowFontScaling={false} style={styles.storeDescStyle}>此處是促銷資訊</Text>
</View>
<View style={styles.headerObseverStyle}>
<Text allowFontScaling={false} style={styles.obseverDeStyle}>已有2000人關注</Text>
</View>
</View>
</Image>
);
}
頂部搜尋view也是懸浮在view中的,樣式為:
headerTopContainer: {
flexDirection: 'row',
alignItems: 'center',
position: 'absolute',
paddingTop: getStatusHeight(),
paddingBottom: ScreenUtils.scaleSize(20),
left: 0,
right: 0,
},
/**
* 渲染topbar
* @private
*/
_renderTopBar() {
return (
<View
style={styles.headerTopContainer}
ref={(ref)=>this.statusView = ref}
onLayout={(event)=> {
let height = event.nativeEvent.layout.height;
if (this.statusHeight !== height) {
this.statusHeight = height;
this.contentView.setNativeProps({
style: {
top: (height),
backgroundColor: 'red'
}
})
}
}}
>
<TouchableOpacity style={styles.headerBackStyle} onPress={()=> {
Actions.pop();
}} activeOpacity={1}>
<Image
style={styles.leftArrowDefaultStyle}
source={require('../../../../../foundation/Img/icons/Icon_back_.png')}
/>
</TouchableOpacity>
<View style={styles.headerCenterContainer}>
<Image
style={styles.headerQueryStyle}
source={require('../../../../../foundation/Img/icons/question.png')}
/>
<TextInput
style={styles.queryTextStyle}
selectionColor={Colors.text_dark_grey}
underlineColorAndroid={'transparent'}
placeholder={'搜尋'}
placeholderTextColor={Colors.text_light_grey}
onFocus={this._jumpToSearch}
/>
</View>
<View style={styles.headerRightContainer}>
<TouchableOpacity onPress={this._jumpToCart} activeOpacity={1}>
<Image
style={styles.navRightCartStyle}
source={require('../../../../../foundation/Img/home/store/Icon_cart_.png')}
/>
</TouchableOpacity>
<TouchableOpacity activeOpacity={1}>
<Image
style={styles.navRightShareStyle}
source={require('../../../../../foundation/Img/home/store/Icon_more_.png')}
/>
</TouchableOpacity>
</View>
</View>
);
}
最後是主體內容模組了,也是懸浮在view中的,樣式為:
contentStyle: {
width: ScreenUtils.screenW,
bottom: 0,
position: 'absolute',
},
畫一個這樣的頁面想必小夥伴很easy的就實現了哈~
當搜尋欄view渲染完畢後,我們需要拿到搜尋欄view的高度,然後設定內容view的top值:
/**
* 渲染topbar
* @private
*/
_renderTopBar() {
return (
<View
style={styles.headerTopContainer}
ref={(ref)=>this.statusView = ref}
onLayout={(event)=> {
//獲取頂部搜尋view的高度
let height = event.nativeEvent.layout.height;
if (this.statusHeight !== height) {
this.statusHeight = height;
//設定內容view的top值 this.contentView.setNativeProps({
style: {
top: (height),
backgroundColor: 'red'
}
})
}
}}
>
.......
可以看到我們的內容view渲染的時候預設加了一個margintop:
//77為搜尋欄view的高度,我這就直接寫死了,這個值是需要動態傳入的
<View style={{ marginTop: topHeight - 77}}>
<Text>{title+'1'}</Text>
.....
</View>
然後我們的tabview也是懸浮在view中,預設一個top值:
//77為搜尋欄view的高度,我這就直接寫死了,這個值是需要動態傳入的,還給了一個zindex,就是為了浮在ScrollableTabView這個第三方控制元件中。
renderTabBar={(props) =>
<View
ref={(ref)=>this.tabViews = ref}
style={{position: 'relative', top: topHeight - 77, zIndex: 10}}
>
<DefaultTabBar
{...props}
/>
</View>
}
最後出現的效果應該是這樣的:
這個時候scrollview的滑動是沒有任何效果的,因為我們都還沒進行處理哈~
好啦! 接下來我們就來處理一下scrollview的滑動事件:
<ScrollView
style={{flex: 1,backgroundColor:'green'}}
key={index}
tabLabel={title}
scrollEventThrottle={1}
onScroll={this._onScroll.bind(this)}//滑動回撥
overScrollMode={'never'}
>
/**
*
* @private
*/
_onScroll(e) {
//獲取滑動個的y軸偏移量
let y = e.nativeEvent.contentOffset.y;
//y軸滑動到頂部的臨界值,此時需要懸浮
let limit = topHeight - this.statusHeight;
//當y軸滑動的距離<=臨界值的時候,headerview的高度=(headerview的初始高度-滑動的高度)
if (y <= limit) {
//headerview的高度=(headerview的初始高度-滑動的高度)
this.topView.setNativeProps({
style: {
height: topHeight - y,
}
});
//tabview的top值需要隨著scrollview的滑動改變(headerview的初始高度-搜尋欄的高度-scrollview的滑動高度)
this.tabViews.setNativeProps({
style: {
top:(topHeight-this.statusHeight-y)
}
});
//當y軸滑動的距離>臨界值的時候,headerview的高度=(搜尋欄的高度)
} else {
//tabview的top為0
this.tabViews.setNativeProps({
style: {
top:0
}
});
//headerview的高度=(搜尋欄的高度)
this.topView.setNativeProps({
style: {
height: this.statusHeight,
}
});
}
//通過(y/limit)(scrollview滑動的y值/臨界值)算出一個offset,給搜尋欄view設定背景色,
//背景色是rgba(0,0,0,offset),
this.statusView.setNativeProps({
style: {
backgroundColor: `rgba(0,0,0,${y / limit})`,
}
})
}
ios跟android的效果差不多,只是android沒有bounce的效果,哈哈~ 看了一下某東也是沒有的,心理平衡了~ 個人覺得實現起來思路還是有點問題了,不過也算是可以簡單搞定產品了。 各位小夥伴有啥好的思路可以告知一下哈~ 歡迎交流!! 歡迎入群!!!