温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

React Native如何仿美团下拉菜单

发布时间:2021-07-09 10:41:50 来源:亿速云 阅读:278 作者:小新 栏目:web开发

小编给大家分享一下React Native如何仿美团下拉菜单,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!

在很多产品中都会涉及到下拉菜单选择功能,用的最好的当属美团了,其效果如下:

React Native如何仿美团下拉菜单

要实现上面的效果,在原生中比较好做,直接使用PopWindow组件即可。如果使用React Native开发上面的效果,需要注意几个问题:

1、 在下拉的时候有动画过度效果;

2、下拉菜单出现后点击菜单项,菜单项可选择,并触发对应的事件;

3、下拉菜单中的项目可以配置;

要实现弹框效果,我们马上回想到使用Model组件,而要绘制打钩图标和下拉三角,我们首先想到使用ART实现,当然选择使用图标也是可以的。例如使用ART绘制对勾的代码如下:

const Check = ()=>{  return (    <Surface     width={18}     height={12}     >     <Group scale={0.03}>       <Shape         fill={COLOR_HIGH}         d={`M494,52c-13-13-33-13-46,0L176,324L62,211c-13-13-33-13-46,0s-13,33,0,46l137,136c6,6,15,10,23,10s17-4,23-10L494,99    C507,86,507,65,494,52z`}       />     </Group>    </Surface>  ); }

下拉动画的实现上,需要使用Animated。例如,背景颜色变化需要使用Animated.timing。

 this.state.fadeInOpacity,          {   toValue: value,            duration : 250,   }

运行效果:

React Native如何仿美团下拉菜单

本示例设计三个文件:导航栏FoodActionBar.js,下拉弹框TopMenu.js和文件主类FoodView.js。

FoodActionBar.js

/**  * https://github.com/facebook/react-native  * @flow 首页的标题栏  */ import React, {Component} from 'react'; import {Platform, View, Dimensions, Text, StyleSheet, TouchableOpacity, Image} from 'react-native'; import px2dp from '../util/Utils' const isIOS = Platform.OS == "ios" const {width, height} = Dimensions.get('window') const headH = px2dp(isIOS ? 64 : 44) export default class FoodActionBar extends Component {   constructor(props) {     super(props);     this.state = {       showPop: false,     }   }   renderHeader() {     return (       <View style={styles.headerStyle}>         <TouchableOpacity style={styles.action} >           <Image style={styles.scanIcon}/>         </TouchableOpacity>         <TouchableOpacity style={styles.searchBar}>           <Image source={require('../images/ic_search.png')} style={styles.iconStyle}/>           <Text style={{fontSize: 13, color: "#666", marginLeft: 5}}>输入商家名、品类和商圈</Text>         </TouchableOpacity>         <TouchableOpacity style={styles.action} onPress={() => { this.setState({ showPop: !this.state.showPop }) }}>           <Image style={styles.scanIcon}               source={require('../images/icon_address.png')}/>         </TouchableOpacity>       </View>     )   }   render() {     return (       <View>         {this.renderHeader()}       </View>     );   } } const styles = StyleSheet.create({   headerStyle: {     backgroundColor: "#ffffff",     height: headH,     paddingTop: px2dp(isIOS ? 20 : 0),     flexDirection: 'row',     alignItems: 'center',   },   searchBar: {     flex:1,     height: 30,     borderRadius: 19,     backgroundColor:'#e9e9e9',     marginLeft: 10,     flexDirection: 'row',     justifyContent: 'flex-start',     alignItems: 'center',     alignSelf: 'center',     paddingLeft: 10,   },   text: {     fontSize: 16,     color: '#ffffff',     justifyContent: 'center',   },   iconStyle: {     width: 22,     height: 22,   },   action: {     flexDirection: 'row',     justifyContent: 'center',     alignItems: 'center',     marginLeft:10,     marginRight:10   },   scanIcon: {     width: 28,     height: 28,     alignItems: 'center',   },   scanText: {     fontSize: 14,     color: '#ffffff',     justifyContent: 'center',     alignItems: 'center',   }, });

TopMenu.js

/**  * Sample React Native App  * https://github.com/facebook/react-native  * @flow  */ import React, {Component} from 'react'; import {   AppRegistry,   StyleSheet,   Animated,   ScrollView,   Dimensions,   PixelRatio,   Text,   TouchableWithoutFeedback,   TouchableHighlight,   ART,   View } from 'react-native'; const {Surface, Shape, Path, Group} = ART; const {width, height} = Dimensions.get('window'); const T_WIDTH = 7; const T_HEIGHT = 4; const COLOR_HIGH = '#00bea9'; const COLOR_NORMAL = '#6c6c6c'; const LINE = 1 / PixelRatio.get(); class Triangle extends React.Component {   render() {     var path;     var fill;     if (this.props.selected) {       fill = COLOR_HIGH;       path = new Path()         .moveTo(T_WIDTH / 2, 0)         .lineTo(0, T_HEIGHT)         .lineTo(T_WIDTH, T_HEIGHT)         .close();     } else {       fill = COLOR_NORMAL;       path = new Path()         .moveTo(0, 0)         .lineTo(T_WIDTH, 0)         .lineTo(T_WIDTH / 2, T_HEIGHT)         .close();     }     return (       <Surface width={T_WIDTH} height={T_HEIGHT}>         <Shape d={path} stroke="#00000000" fill={fill} strokeWidth={0}/>       </Surface>     )   } } const TopMenuItem = (props) => {   const onPress = () => {     props.onSelect(props.index);   }   return (     <TouchableWithoutFeedback onPress={onPress}>       <View style={styles.item}>         <Text style={props.selected ? styles.menuTextHigh : styles.menuText}>{props.label}</Text>         <Triangle selected={props.selected}/>       </View>     </TouchableWithoutFeedback>   ); }; const Subtitle = (props) => {   let textStyle = props.selected ?     [styles.tableItemText, styles.highlight, styles.marginHigh] :     [styles.tableItemText, styles.margin];   let rightTextStyle = props.selected ? [styles.tableItemText, styles.highlight] : styles.tableItemText;   let onPress = () => {     props.onSelectMenu(props.index, props.subindex, props.data);   }   return (     <TouchableHighlight onPress={onPress} underlayColor="#f5f5f5">       <View style={styles.tableItem}>         <View style={styles.row}>           {props.selected && <Check />}           <Text style={textStyle}>{props.data.title}</Text>         </View>         <Text style={rightTextStyle}>{props.data.subtitle}</Text>       </View>     </TouchableHighlight>   ); }; const Title = (props) => {   let textStyle = props.selected ?     [styles.tableItemText, styles.highlight, styles.marginHigh] :     [styles.tableItemText, styles.margin];   let rightTextStyle = props.selected ? [styles.tableItemText, styles.highlight] : styles.tableItemText;   let onPress = () => {     props.onSelectMenu(props.index, props.subindex, props.data);   }   return (     <TouchableHighlight onPress={onPress} underlayColor="#f5f5f5">       <View style={styles.titleItem}>         {props.selected && <Check />}         <Text style={textStyle}>{props.data.title}</Text>       </View>     </TouchableHighlight>   ); }; const Check = () => {   return (     <Surface       width={18}       height={12}     >       <Group scale={0.03}>         <Shape           fill={COLOR_HIGH}           d={`M494,52c-13-13-33-13-46,0L176,324L62,211c-13-13-33-13-46,0s-13,33,0,46l137,136c6,6,15,10,23,10s17-4,23-10L494,99    C507,86,507,65,494,52z`}         />       </Group>     </Surface>   ); } export default class TopMenu extends Component {   constructor(props) {     super(props);     let array = props.config;     let top = [];     let maxHeight = [];     let subselected = [];     let height = [];     //最大高度     var max = parseInt((height - 80) * 0.8 / 43);     for (let i = 0, c = array.length; i < c; ++i) {       let item = array[i];       top[i] = item.data[item.selectedIndex].title;       maxHeight[i] = Math.min(item.data.length, max) * 43;       subselected[i] = item.selectedIndex;       height[i] = new Animated.Value(0);     }     //分析数据     this.state = {       top: top,       maxHeight: maxHeight,       subselected: subselected,       height: height,       fadeInOpacity: new Animated.Value(0),       selectedIndex: null     };   }   componentDidMount() {   }   createAnimation = (index, height) => {     return Animated.timing(       this.state.height[index],       {         toValue: height,         duration: 250       }     );   }   createFade = (value) => {     return Animated.timing(       this.state.fadeInOpacity,       {         toValue: value,         duration: 250,       }     );   }   onSelect = (index) => {     if (index === this.state.selectedIndex) {       //消失       this.hide(index);     } else {       this.setState({selectedIndex: index, current: index});       this.onShow(index);     }   }   hide = (index, subselected) => {     let opts = {selectedIndex: null, current: index};     if (subselected !== undefined) {       this.state.subselected[index] = subselected;       this.state.top[index] = this.props.config[index].data[subselected].title;       opts = {selectedIndex: null, current: index, subselected: this.state.subselected.concat()};     }     this.setState(opts);     this.onHide(index);   }   onShow = (index) => {     Animated.parallel([this.createAnimation(index, this.state.maxHeight[index]), this.createFade(1)]).start();   }   onHide = (index) => {     //其他的设置为0     for (let i = 0, c = this.state.height.length; i < c; ++i) {       if (index != i) {         this.state.height[i].setValue(0);       }     }     Animated.parallel([this.createAnimation(index, 0), this.createFade(0)]).start();   }   onSelectMenu = (index, subindex, data) => {     this.hide(index, subindex);     this.props.onSelectMenu && this.props.onSelectMenu(index, subindex, data);   }   renderList = (d, index) => {     let subselected = this.state.subselected[index];     let Comp = null;     if (d.type == 'title') {       Comp = Title;     } else {       Comp = Subtitle;     }     let enabled = this.state.selectedIndex == index || this.state.current == index;     return (       <Animated.View key={index} pointerEvents={enabled ? 'auto' : 'none'}               style={[styles.content, {opacity: enabled ? 1 : 0, height: this.state.height[index]}]}>         <ScrollView style={styles.scroll}>           {d.data.map((data, subindex) => {             return <Comp               onSelectMenu={this.onSelectMenu}               index={index}               subindex={subindex}               data={data}               selected={subselected == subindex}               key={subindex}/>           })}         </ScrollView>       </Animated.View>     );   }   render() {     let list = null;     if (this.state.selectedIndex !== null) {       list = this.props.config[this.state.selectedIndex].data;     }     console.log(list);     return (       <View style={{flex: 1}}>         <View style={styles.topMenu}>           {this.state.top.map((t, index) => {             return <TopMenuItem               key={index}               index={index}               onSelect={this.onSelect}               label={t}               selected={this.state.selectedIndex === index}/>           })}         </View>         {this.props.renderContent()}         <View style={styles.bgContainer} pointerEvents={this.state.selectedIndex !== null ? "auto" : "none"}>           <Animated.View style={[styles.bg, {opacity: this.state.fadeInOpacity}]}/>           {this.props.config.map((d, index) => {             return this.renderList(d, index);           })}         </View>       </View>     );   } } const styles = StyleSheet.create({   scroll: {flex: 1, backgroundColor: '#fff'},   bgContainer: {position: 'absolute', top: 40, width: width, height: height},   bg: {flex: 1, backgroundColor: 'rgba(50,50,50,0.2)'},   content: {     position: 'absolute',     width: width   },   highlight: {     color: COLOR_HIGH   },   marginHigh: {marginLeft: 10},   margin: {marginLeft: 28},   titleItem: {     height: 43,     alignItems: 'center',     paddingLeft: 10,     paddingRight: 10,     borderBottomWidth: LINE,     borderBottomColor: '#eee',     flexDirection: 'row',   },   tableItem: {     height: 43,     alignItems: 'center',     paddingLeft: 10,     paddingRight: 10,     borderBottomWidth: LINE,     borderBottomColor: '#eee',     flexDirection: 'row',     justifyContent: 'space-between'   },   tableItemText: {fontWeight: '300', fontSize: 14},   row: {     flexDirection: 'row'   },   item: {     flex: 1,     flexDirection: 'row',     alignItems: 'center',     justifyContent: 'center',   },   menuTextHigh: {     marginRight: 3,     fontSize: 13,     color: COLOR_HIGH   },   menuText: {     marginRight: 3,     fontSize: 13,     color: COLOR_NORMAL   },   topMenu: {     flexDirection: 'row',     height: 40,     borderTopWidth: LINE,     borderTopColor: '#bdbdbd',     borderBottomWidth: 1,     borderBottomColor: '#f2f2f2'   }, });

主类FoodView.js:

/**  * Sample React Native App  * https://github.com/facebook/react-native  * @flow  */ import React, {Component} from 'react'; import {   AppRegistry,   StyleSheet,   TouchableOpacity,   Dimensions,   Text,   View } from 'react-native'; const {width, height} = Dimensions.get('window'); import FoodActionBar from "./pop/FoodActionBar"; import Separator from "./util/Separator"; import TopMenu from "./pop/TopMenu"; const CONFIG = [   {     type:'subtitle',     selectedIndex:1,     data:[       {title:'全部', subtitle:'1200m'},       {title:'自助餐', subtitle:'300m'},       {title:'自助餐', subtitle:'200m'},       {title:'自助餐', subtitle:'500m'},       {title:'自助餐', subtitle:'800m'},       {title:'自助餐', subtitle:'700m'},       {title:'自助餐', subtitle:'900m'},     ]   },   {     type:'title',     selectedIndex:0,     data:[{       title:'智能排序'     }, {       title:'离我最近'     }, {       title:'好评优先'     }, {       title:'人气最高'     }]   } ]; export default class FoodView extends Component {   constructor(props){     super(props);     this.state = {       data:{}     };   }   renderContent=()=>{     return (       <TouchableOpacity >         <Text style={styles.text}>index:{this.state.index} subindex:{this.state.subindex} title:{this.state.data.title}</Text>       </TouchableOpacity>     );     // alert(this.state.data.title)   };   onSelectMenu=(index, subindex, data)=>{     this.setState({index, subindex, data});   };   render() {     return (       <View style={styles.container}>         <FoodActionBar/>         <Separator/>         <TopMenu style={styles.container} config={CONFIG} onSelectMenu={this.onSelectMenu} renderContent={this.renderContent}/>       </View>     );   } } const styles = StyleSheet.create({   container: {     flex: 1,     width:width,     backgroundColor: '#F5FCFF',   },   text: {     fontSize:20,     marginTop:100,     justifyContent: 'center',     alignItems: 'center',   }, });

以上是“React Native如何仿美团下拉菜单”这篇文章的所有内容,感谢各位的阅读!相信大家都有了一定的了解,希望分享的内容对大家有所帮助,如果还想学习更多知识,欢迎关注亿速云行业资讯频道!

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI