React Native仿美团下拉菜单的实例代码


Posted in Javascript onAugust 08, 2017

本文介绍了React Native仿美团下拉菜单的实例代码,最近也在学习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',

  },

});

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JS 无法通过W3C验证的处理方法
Mar 09 Javascript
jQuery实现列表自动循环滚动鼠标悬停时停止滚动
Sep 06 Javascript
js实现延迟加载的方法
Jun 24 Javascript
jQuery实现图片上传和裁剪插件Croppie
Nov 29 Javascript
AngularJS入门教程之Scope(作用域)
Jul 27 Javascript
AngularJs表单验证实例代码解析
Nov 29 Javascript
VUE开发一个图片轮播的组件示例代码
Mar 06 Javascript
在 Angular 中使用Chart.js 和 ng2-charts的示例代码
Aug 17 Javascript
Node+OCR实现图像文字识别功能
Nov 26 Javascript
js实现图片区域可点击大小随意改变(适用移动端)代码实例
Sep 11 Javascript
vue下axios拦截器token刷新机制的实例代码
Jan 17 Javascript
JavaScrip如果基于url实现图片下载
Jul 03 Javascript
React-Native 组件之 Modal的使用详解
Aug 08 #Javascript
基于jQuery对象和DOM对象和字符串之间的转化实例
Aug 08 #jQuery
超级简易的JS计算器实例讲解(实现加减乘除)
Aug 08 #Javascript
浅谈ES6新增的数组方法和对象
Aug 08 #Javascript
Angularjs上传文件组件flowjs功能
Aug 07 #Javascript
详解Vue的computed(计算属性)使用实例之TodoList
Aug 07 #Javascript
详解express与koa中间件模式对比
Aug 07 #Javascript
You might like
PHP防止刷新重复提交页面的示例代码
2015/11/11 PHP
使用PHPStorm+XDebug搭建单步调试环境
2017/11/19 PHP
学习ExtJS(一) 之基础前提
2009/10/07 Javascript
jquery keypress,keyup,onpropertychange键盘事件
2010/06/25 Javascript
基于jQuery制作迷你背词汇工具
2010/07/27 Javascript
Javascript中Event属性搜集整理
2013/09/17 Javascript
javascript面向对象特性代码实例
2014/06/12 Javascript
jQuery简单入门示例之用户校验demo示例
2016/07/09 Javascript
vue基于better-scroll实现左右联动滑动页面
2020/06/30 Javascript
JS跨浏览器解析XML应用过程详解
2020/10/16 Javascript
Vue使用CDN引用项目组件,减少项目体积的步骤
2020/10/30 Javascript
让python同时兼容python2和python3的8个技巧分享
2014/07/11 Python
Python随机生成信用卡卡号的实现方法
2015/05/14 Python
浅谈python和C语言混编的几种方式(推荐)
2017/09/27 Python
Python3 XML 获取雅虎天气的实现方法
2018/02/01 Python
Python语言的变量认识及操作方法
2018/02/11 Python
python中字符串的操作方法大全
2018/06/03 Python
python3实现指定目录下文件sha256及文件大小统计
2019/02/25 Python
Python实现定制自动化业务流量报表周报功能【XlsxWriter模块】
2019/03/11 Python
Python中的支持向量机SVM的使用(附实例代码)
2019/06/26 Python
Django3.0 异步通信初体验(小结)
2019/12/04 Python
Python如何把字典写入到CSV文件的方法示例
2020/08/23 Python
Python使用grequests并发发送请求的示例
2020/11/05 Python
Python join()函数原理及使用方法
2020/11/14 Python
前端隐藏出边界内容的实现方法
2016/04/14 HTML / CSS
eBay澳大利亚站:eBay.com.au
2018/02/02 全球购物
MAC Cosmetics官方网站:魅可专业艺术彩妆
2019/04/10 全球购物
FC-Moto西班牙:摩托车手最大的购物场所之一
2019/04/11 全球购物
商场经理竞聘演讲稿
2014/01/01 职场文书
战友聚会邀请函
2014/01/18 职场文书
学校百日安全生产活动总结
2014/07/05 职场文书
Nginx配置80端口访问8080及项目名地址方法解析
2021/03/31 Servers
我对PyTorch dataloader里的shuffle=True的理解
2021/05/20 Python
Python道路车道线检测的实现
2021/06/27 Python
Nginx内网单机反向代理的实现
2021/11/07 Servers
《堡垒之夜》联动《刺客信条》 4月7日正式上线
2022/04/06 其他游戏