前言:有朋友需要实现一个仿某电商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的效果,哈哈~ 看了一下某东也是没有的,心理平衡了~ 个人觉得实现起来思路还是有点问题了,不过也算是可以简单搞定产品了。 各位小伙伴有啥好的思路可以告知一下哈~ 欢迎交流!! 欢迎入群!!!