React Native仿美团下拉菜单

前端之家收集整理的这篇文章主要介绍了React Native仿美团下拉菜单前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

本篇博客转自:http://blog.csdn.net/xiangzhihong8/article/details/76862097

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

要实现上面的效果,在原生中比较好做,直接使用PopWindow组件即可。如果使用React Native开发上面的效果,需要注意几个问题:
1、 在下拉的时候有动画过度效果
2、下拉菜单出现后点击菜单项,菜单项可选择,并触发对应的事件;
3、下拉菜单中的项目可以配置;

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

 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
const Check = ()=>{ return ( <Surface @H_404_49@ width@H_404_49@={18}@H_404_49@ height@H_404_49@={12}@H_404_49@ >@H_404_49@ Group@H_404_49@ scale@H_404_49@={0.03}@H_404_49@>@H_404_49@ Shape @H_404_49@ fill@H_404_49@={COLOR_HIGH}@H_404_49@ d@H_404_49@={`M494,52c-13-13-33-13-46,0L176,324L62,211c-13-13-33-13-46,0s-13,33,46l137,136c6,6,15,10,23,10s17-4,23-10L494,99@H_404_49@ C507@H_404_49@,86@H_404_49@,102)">507@H_404_49@,102)">65@H_404_49@,102)">494@H_404_49@,102)">52z@H_404_49@`} />@H_404_49@ </Group@H_404_49@>@H_404_49@ Surface@H_404_49@>@H_404_49@ ); }

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

5
 
 this@H_404_49@.state@H_404_49@.fadeInOpacity@H_404_49@,@H_301_104@{ toValue@H_404_49@: value,duration : 250@H_404_49@,@H_404_49@@H_404_49@@H_404_49@}

运行效果

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

16
  
  
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • /** * https://github.com/facebook/react-native * @flow@H_404_49@ 首页标题栏 */@H_404_49@ import@H_404_49@ React,{Component} from 'react'@H_404_49@; import@H_404_49@ {Platform,View,Dimensions,Text,StyleSheet,TouchableOpacity,Image} from 'react-native'@H_404_49@; import@H_404_49@ px2dp from '../util/Utils'@H_404_49@ const isIOS = Platform.OS == "ios"@H_404_49@ const {width,height} = Dimensions.get('window'@H_404_49@) const headH = px2dp(isIOS ? 64@H_404_49@ : 44@H_404_49@) export default@H_404_49@ class@H_404_49@ FoodActionBar@H_404_49@ extends@H_404_49@ Component@H_404_49@ {@H_404_49@ constructor(props) { super@H_404_49@(props); this@H_404_49@.state = { showPop: false@H_404_49@,} } renderHeader() { return@H_404_49@ ( <View style={styles.headerStyle}> <TouchableOpacity style={styles.action} > <Image style={styles.scanIcon}/> </TouchableOpacity> <TouchableOpacity style={styles.searchBar}> <Image source={require('../images/ic_search.png'@H_404_49@)} style={styles.iconStyle}/> <Text style={{fontSize: 13@H_404_49@,color: "#666"@H_404_49@,marginLeft: 5@H_404_49@}}>输入商家名、品类和商圈</Text> </TouchableOpacity> <TouchableOpacity style={styles.action} onPress={() => { this@H_404_49@.setState({ showPop: !this@H_404_49@.state.showPop }) }}> <Image style={styles.scanIcon} source={require('../images/icon_address.png'@H_404_49@)}/> </TouchableOpacity> </View> ) } render() { return@H_404_49@ ( <View> {this@H_404_49@.renderHeader()} </View> ); } } const styles = StyleSheet.create({ headerStyle: { backgroundColor: "#ffffff"@H_404_49@,height: headH,paddingTop: px2dp(isIOS ? 20@H_404_49@ : 0@H_404_49@),flexDirection: 'row'@H_404_49@,alignItems: 'center'@H_404_49@,},searchBar: { flex:1@H_404_49@,height: 30@H_404_49@,borderRadius: 19@H_404_49@,backgroundColor:'#e9e9e9'@H_404_49@,102)">10@H_404_49@,justifyContent: 'flex-start'@H_404_49@,alignSelf: 16@H_404_49@,0)">'#ffffff'@H_404_49@,iconStyle: { width: 22@H_404_49@,action: { flexDirection: 10@H_404_49@ },scanIcon: { width: 28@H_404_49@,scanText: { fontSize: 14@H_404_49@,});

    TopMenu.js

    99
      
      
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
  • 206
  • 207
  • 208
  • 209
  • 210
  • 211
  • 212
  • 213
  • 214
  • 215
  • 216
  • 217
  • 218
  • 219
  • 220
  • 221
  • 222
  • 223
  • 224
  • 225
  • 226
  • 227
  • 228
  • 229
  • 230
  • 231
  • 232
  • 233
  • 234
  • 235
  • 236
  • 237
  • 238
  • 239
  • 240
  • 241
  • 242
  • 243
  • 244
  • 245
  • 246
  • 247
  • 248
  • 249
  • 250
  • 251
  • 252
  • 253
  • 254
  • 255
  • 256
  • 257
  • 258
  • 259
  • 260
  • 261
  • 262
  • 263
  • 264
  • 265
  • 266
  • 267
  • 268
  • 269
  • 270
  • 271
  • 272
  • 273
  • 274
  • 275
  • 276
  • 277
  • 278
  • 279
  • 280
  • 281
  • 282
  • 283
  • 284
  • 285
  • 286
  • 287
  • 288
  • 289
  • 290
  • 291
  • 292
  • 293
  • 294
  • 295
  • 296
  • 297
  • 298
  • 299
  • 300
  • 301
  • 302
  • 303
  • 304
  • 305
  • 306
  • 307
  • 308
  • 309
  • 310
  • 311
  • 312
  • 313
  • 314
  • 315
  • 316
  • 317
  • 318
  • 319
  • 320
  • 321
  • 322
  • 323
  • 324
  • 325
  • 326
  • 327
  • 328
  • 329
  • 330
  • 331
  • 332
  • 333
  • 334
  • 335
  • 336
  • 337
  • 338
  • 339
  • 340
  • 341
  • 342
  • 343
  • 344
  • 345
  • 346
  • 347
  • 348
  • 349
  • 350
  • 351
  • 352
  • 353
  • 354
  • 355
  • 356
  • 357
  • 358
  • 359
  • 360
  • 361
  • 362
  • 363
  • 364
  • 365
  • 366
  • 367
  • 368
  • 369
  • 370
  • 371
  • 372
  • 373
  • 374
  • 375
  • 376
  • 377
  • /** * Sample React Native App * https@H_404_49@://gi@H_404_49@thub.com/facebook/react-native@H_404_49@ * @flow@H_404_49@ */ import@H_404_49@ React,0)">'react'@H_404_49@; import@H_404_49@ { AppRegistry,Animated,ScrollView,PixelRatio,TouchableWithoutFeedback,TouchableHighlight,ART,View } from 'react-native'@H_404_49@; const@H_404_49@ {Surface,Shape,Path,Group} = ART; const@H_404_49@ {width,0)">'window'@H_404_49@); const@H_404_49@ T_WIDTH = 7@H_404_49@; const@H_404_49@ T_HEIGHT = 4@H_404_49@; const@H_404_49@ COLOR_HIGH = '#00bea9'@H_404_49@; const@H_404_49@ COLOR_NORMAL = '#6c6c6c'@H_404_49@; const@H_404_49@ LINE = 1@H_404_49@ / PixelRatio.get(); Triangle@H_404_49@ React@H_404_49@.Component@H_404_49@ {@H_404_49@ render() { var@H_404_49@ path; var@H_404_49@ fill; if@H_404_49@ (this@H_404_49@.props.selected) { fill = COLOR_HIGH; path = new@H_404_49@ Path() .moveTo(T_WIDTH / 2@H_404_49@,0@H_404_49@) .lineTo(0@H_404_49@,T_HEIGHT) .lineTo(T_WIDTH,T_HEIGHT) .close(); } else@H_404_49@ { fill = COLOR_NORMAL; path = new@H_404_49@ Path() .moveTo(0@H_404_49@) .lineTo(T_WIDTH,102)">0@H_404_49@) .lineTo(T_WIDTH / return@H_404_49@ ( <Surface width={T_WIDTH} height={T_HEIGHT}> <Shape d={path} stroke="#00000000"@H_404_49@ fill={fill} strokeWidth={0@H_404_49@}/> </Surface> ) } } const@H_404_49@ TopMenuItem@H_404_49@ = (props)@H_404_49@ =>@H_404_49@ { const@H_404_49@ onPress@H_404_49@ = ()@H_404_49@ =>@H_404_49@ { props.onSelect(props.index); } return@H_404_49@ ( <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@H_404_49@ Subtitle@H_404_49@ = (props)@H_404_49@ =>@H_404_49@ { let@H_404_49@ textStyle = props.selected ? [styles.tableItemText,styles.highlight,styles.marginHigh] : [styles.tableItemText,styles.margin]; let@H_404_49@ rightTextStyle = props.selected ? [styles.tableItemText,styles.highlight] : styles.tableItemText; let@H_404_49@ onPress@H_404_49@ = ()@H_404_49@ =>@H_404_49@ { props.onSelectMenu(props.index,props.subindex,props.data); } return@H_404_49@ ( <TouchableHighlight onPress={onPress} underlayColor="#f5f5f5"@H_404_49@> <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@H_404_49@ Title@H_404_49@ = let@H_404_49@ onPress@H_404_49@ = "#f5f5f5"@H_404_49@> <View style={styles.titleItem}> {props.selected && <Check />} <Text style={textStyle}>{props.data.title}</Text> </View> </TouchableHighlight> ); }; const@H_404_49@ Check@H_404_49@ = ()@H_404_49@ =>@H_404_49@ { return@H_404_49@ ( <Surface width={18@H_404_49@} height={12@H_404_49@} > <Group scale={0.03@H_404_49@}> <Shape fill={COLOR_HIGH} d={`M494,102)">52@H_404_49@c-13@H_404_49@-33@H_404_49@-46@H_404_49@,102)">0@H_404_49@L176,102)">324@H_404_49@L62,102)">211@H_404_49@c-0@H_404_49@s-33@H_404_49@,102)">46@H_404_49@l137,102)">136@H_404_49@c6,102)">6@H_404_49@,102)">15@H_404_49@,102)">23@H_404_49@,102)">10@H_404_49@s17-4@H_404_49@,102)">23@H_404_49@-10@H_404_49@L494,102)">99@H_404_49@ C507,102)">52@H_404_49@z@H_404_49@`} /> </Group> </Surface> ); } export@H_404_49@ default@H_404_49@ TopMenu@H_404_49@ super@H_404_49@(props); let@H_404_49@ array = props.config; let@H_404_49@ top = []; let@H_404_49@ maxHeight = []; let@H_404_49@ subselected = []; let@H_404_49@ height = []; //@H_404_49@最大高度 var@H_404_49@ max = parseInt((height - 80@H_404_49@) * 0.8@H_404_49@ / 43@H_404_49@); for@H_404_49@ (let@H_404_49@ i = let@H_404_49@ item = array[i]; top[i] = item.data[item.selectedIndex].title; maxHeight[i] = Math.min(item.data.length,max) * 43@H_404_49@; subselected[i] = item.selectedIndex; height[i] = new@H_404_49@ Animated.Value(0@H_404_49@); } //@H_404_49@分析数据 this@H_404_49@.state = { top@H_404_49@: top,maxHeight@H_404_49@: maxHeight,0)">subselected@H_404_49@: subselected,0)">height@H_404_49@: height,0)">fadeInOpacity@H_404_49@: selectedIndex@H_404_49@: null@H_404_49@ }; } componentDidMount() { } createAnimation@H_404_49@ = (index,height)@H_404_49@ =>@H_404_49@ { return@H_404_49@ Animated.timing( this@H_404_49@.state.height[index],{ toValue@H_404_49@: height,0)">duration@H_404_49@: 250@H_404_49@ } ); } createFade@H_404_49@ = (value)@H_404_49@ =>@H_404_49@ { this@H_404_49@.state.fadeInOpacity,0)">toValue@H_404_49@: value,102)">250@H_404_49@,} ); } onSelect@H_404_49@ = (index)@H_404_49@ =>@H_404_49@ { if@H_404_49@ (index === this@H_404_49@.state.selectedIndex) { //@H_404_49@消失 this@H_404_49@.hide(index); } else@H_404_49@ { this@H_404_49@.setState({selectedIndex@H_404_49@: index,0)">current@H_404_49@: index}); this@H_404_49@.onShow(index); } } hide@H_404_49@ = 404_49@ =>@H_404_49@ { let@H_404_49@ opts = {null@H_404_49@,0)">current@H_404_49@: index}; if@H_404_49@ (subselected !== undefined@H_404_49@) { this@H_404_49@.state.subselected[index] = subselected; this@H_404_49@.state.top[index] = this@H_404_49@.props.config[index].data[subselected].title; opts = {current@H_404_49@: index,0)">subselected@H_404_49@: this@H_404_49@.state.subselected.concat()}; } this@H_404_49@.setState(opts); this@H_404_49@.onHide(index); } onShow@H_404_49@ = (index)@H_404_49@ =>@H_404_49@ { Animated.parallel([this@H_404_49@.createAnimation(index,this@H_404_49@.state.maxHeight[index]),136)">this@H_404_49@.createFade(1@H_404_49@)]).start(); } onHide@H_404_49@ = (index)@H_404_49@ =>@H_404_49@ { //@H_404_49@其他的设置为0@H_404_49@ this@H_404_49@.state.height.length; i < c; ++i) { if@H_404_49@ (index != i) { this@H_404_49@.state.height[i].setValue(0@H_404_49@); } } Animated.parallel([0@H_404_49@)]).start(); } onSelectMenu@H_404_49@ = 404_49@ =>@H_404_49@ { this@H_404_49@.hide(index,subindex); this@H_404_49@.props.onSelectMenu && this@H_404_49@.props.onSelectMenu(index,data); } renderList@H_404_49@ = (d,index)@H_404_49@ =>@H_404_49@ { let@H_404_49@ subselected = this@H_404_49@.state.subselected[index]; let@H_404_49@ Comp = null@H_404_49@; if@H_404_49@ (d.type == 'title'@H_404_49@) { Comp = Title; } else@H_404_49@ { Comp = Subtitle; } let@H_404_49@ enabled = this@H_404_49@.state.selectedIndex == index || this@H_404_49@.state.current == index; return@H_404_49@ ( <Animated.View key={index} pointerEvents={enabled ? 'auto'@H_404_49@ : 'none'@H_404_49@} style={[styles.content,{opacity@H_404_49@: enabled ? 1@H_404_49@ : height@H_404_49@: this@H_404_49@.state.height[index]}]}> <ScrollView style={styles.scroll}> {d.data.map((data,subindex) => { return@H_404_49@ <Comp onSelectMenu={this@H_404_49@.onSelectMenu} index={index} subindex={subindex} data={data} selected={subselected == subindex} key={subindex}/> })@H_404_49@} </ScrollView@H_404_49@> </Animated@H_404_49@.View@H_404_49@> ); } render@H_404_49@()@H_404_49@ { let@H_404_49@ list@H_404_49@ = null@H_404_49@; if@H_404_49@ (this@H_404_49@.state.selectedIndex !== null@H_404_49@)@H_404_49@ { list@H_404_49@ = this@H_404_49@.props@H_404_49@.config@H_404_49@[this@H_404_49@.state@H_404_49@.selectedIndex@H_404_49@].data@H_404_49@; } console@H_404_49@.log@H_404_49@(list)@H_404_49@; return@H_404_49@ ( <View style={{flex: 1@H_404_49@}}> <View style={styles.topMenu}> {this@H_404_49@.state.top.map((t,index) => { return@H_404_49@ <TopMenuItem key={index} index={index} onSelect={this@H_404_49@.onSelect} label={t} selected={this@H_404_49@.state.selectedIndex === index}/> })} </View> {this@H_404_49@.props.renderContent()} <View style={styles.bgContainer} pointerEvents={null@H_404_49@ ? "auto"@H_404_49@ : "none"@H_404_49@}> <Animated.View style={[styles.bg,{opacity: this@H_404_49@.state.fadeInOpacity}]}/> {this@H_404_49@.props.config.map((d,136)">return@H_404_49@ this@H_404_49@.renderList(d,index); })} </View> </View> )@H_404_49@; } } const@H_404_49@ styles@H_404_49@ = StyleSheet@H_404_49@.create@H_404_49@({ scroll: {flex: '#fff'@H_404_49@},bgContainer: {position: 'absolute'@H_404_49@,top: 40@H_404_49@,width: width,height: height},bg: {flex: 'rgba(50,50,0.2)'@H_404_49@},content: { position: 10@H_404_49@},margin: {marginLeft: 28@H_404_49@},titleItem: { height: 43@H_404_49@,paddingRight: '#eee'@H_404_49@,tableItem: { height: 'space-between'@H_404_49@ },tableItemText: {fontWeight: '300'@H_404_49@,fontSize: 14@H_404_49@},row: { flexDirection: 'row'@H_404_49@ },item: { flex: 3@H_404_49@,color: COLOR_HIGH },menuText: { marginRight: '#bdbdbd'@H_404_49@,borderBottomWidth: '#f2f2f2'@H_404_49@ },})@H_404_49@; @H_404_49@

    主类FoodView.js:

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

    猜你在找的React相关文章