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 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,46l137,136c6,6,15,10,23,10s17-4,23-10L494,99 C507,86,102)">507,102)">65,102)">494,102)">52z`} /> </Group> Surface> ); }

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

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

运行效果

本示例设计三个文件:导航栏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 首页标题栏 */ 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',102)">10,justifyContent: 'flex-start',alignSelf: 16,0)">'#ffffff',iconStyle: { width: 22,action: { flexDirection: 10 },scanIcon: { width: 28,scanText: { fontSize: 14,});

    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://github.com/facebook/react-native * @flow */ import React,0)">'react'; import { AppRegistry,Animated,ScrollView,PixelRatio,TouchableWithoutFeedback,TouchableHighlight,ART,View } from 'react-native'; const {Surface,Shape,Path,Group} = ART; const {width,0)">'window'); const T_WIDTH = 7; const T_HEIGHT = 4; const COLOR_HIGH = '#00bea9'; const COLOR_NORMAL = '#6c6c6c'; const LINE = 1 / PixelRatio.get(); Triangle 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) .lineTo(T_WIDTH,102)">0) .lineTo(T_WIDTH / 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 = let onPress = "#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,102)">52c-13-33-46,102)">0L176,102)">324L62,102)">211c-0s-33,102)">46l137,102)">136c6,102)">6,102)">15,102)">23,102)">10s17-4,102)">23-10L494,102)">99 C507,102)">52z`} /> </Group> </Surface> ); } export default TopMenu 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 = 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,0)">subselected: subselected,0)">height: height,0)">fadeInOpacity: selectedIndex: null }; } componentDidMount() { } createAnimation = (index,height) => { return Animated.timing( this.state.height[index],{ toValue: height,0)">duration: 250 } ); } createFade = (value) => { this.state.fadeInOpacity,0)">toValue: value,102)">250,} ); } onSelect = (index) => { if (index === this.state.selectedIndex) { //消失 this.hide(index); } else { this.setState({selectedIndex: index,0)">current: index}); this.onShow(index); } } hide = let opts = {null,0)">current: index}; if (subselected !== undefined) { this.state.subselected[index] = subselected; this.state.top[index] = this.props.config[index].data[subselected].title; opts = {current: index,0)">subselected: this.state.subselected.concat()}; } this.setState(opts); this.onHide(index); } onShow = (index) => { Animated.parallel([this.createAnimation(index,this.state.maxHeight[index]),136)">this.createFade(1)]).start(); } onHide = (index) => { //其他的设置为0 this.state.height.length; i < c; ++i) { if (index != i) { this.state.height[i].setValue(0); } } Animated.parallel([0)]).start(); } onSelectMenu = this.hide(index,subindex); this.props.onSelectMenu && this.props.onSelectMenu(index,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 : 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={null ? "auto" : "none"}> <Animated.View style={[styles.bg,{opacity: this.state.fadeInOpacity}]}/> {this.props.config.map((d,136)">return this.renderList(d,index); })} </View> </View> ); } } const styles = StyleSheet.create({ scroll: {flex: '#fff'},bgContainer: {position: 'absolute',top: 40,width: width,height: height},bg: {flex: 'rgba(50,50,0.2)'},content: { position: 10},margin: {marginLeft: 28},titleItem: { height: 43,paddingRight: '#eee',tableItem: { height: 'space-between' },tableItemText: {fontWeight: '300',fontSize: 14},row: { flexDirection: 'row' },item: { flex: 3,color: COLOR_HIGH },menuText: { marginRight: '#bdbdbd',borderBottomWidth: '#f2f2f2' },});

    主类FoodView.js:

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

    猜你在找的React相关文章