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 相关文章推荐
不一样的文字闪烁 轮番闪烁
Nov 11 Javascript
Tips 带三角可关闭的文字提示
Oct 06 Javascript
基于jQuery的history历史记录插件
Dec 11 Javascript
给超链接添加特效鼠标移动展示提示信息且随鼠标移动
Oct 17 Javascript
用javascript删除当前行,添加行(示例代码)
Nov 25 Javascript
jquery中each方法示例和常用选择器
Jul 08 Javascript
JavaScript与jQuery实现的闪烁输入效果
Feb 18 Javascript
jQuery实现的点击标题文字切换字体效果示例【测试可用】
Apr 26 jQuery
浅谈使用mpvue开发小程序需要注意和了解的知识点
May 23 Javascript
解决layui动态添加的元素click等事件触发不了的问题
Sep 20 Javascript
JS运算符简单用法示例
Jan 19 Javascript
js数组中去除重复值的几种方法
Aug 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的GD库imagettftext函数解决中文乱码问题
2015/01/24 PHP
php函数实现判断是否移动端访问
2015/03/03 PHP
php多线程并发实现方法
2016/09/30 PHP
Laravel框架集成UEditor编辑器的方法图文与实例详解
2019/04/17 PHP
javascript 获取页面的高度及滚动条的位置的代码
2010/05/06 Javascript
js复制网页内容并兼容各主流浏览器的代码
2013/12/17 Javascript
ThinkJS中如何使用MongoDB的CURD操作
2016/12/13 Javascript
基于jQuery实现简单人工智能聊天室
2017/02/10 Javascript
canvas红包照片实例分享
2017/02/28 Javascript
Vue单文件组件的如何使用方式介绍
2017/07/28 Javascript
JS中offset和匀速动画详解
2018/02/06 Javascript
关于vue中的ajax请求和axios包问题
2018/04/19 Javascript
layui框架table 数据表格的方法级渲染详解
2018/08/19 Javascript
原生js实现五子棋游戏
2020/05/28 Javascript
在NodeJs中使用node-schedule增加定时器任务的方法
2020/06/08 NodeJs
Python脚本实现代码行数统计代码分享
2015/03/10 Python
Python  pip安装lxml出错的问题解决办法
2017/02/10 Python
利用pandas将numpy数组导出生成excel的实例
2018/06/14 Python
用于业余项目的8个优秀Python库
2018/09/21 Python
Python根据文件名批量转移图片的方法
2018/10/21 Python
python 判断参数为Nonetype类型或空的实例
2018/10/30 Python
PyTorch实现ResNet50、ResNet101和ResNet152示例
2020/01/14 Python
一个入门级python爬虫教程详解
2021/01/27 Python
纯css3实现的鼠标悬停动画按钮
2014/12/23 HTML / CSS
Travelstart沙特阿拉伯:廉价航班、豪华酒店和实惠的汽车租赁优惠
2019/04/06 全球购物
兰蔻俄罗斯官方网站:Lancome俄罗斯
2019/12/09 全球购物
管理信息系学生的自我评价
2014/01/11 职场文书
少先队学雷锋活动总结范文
2014/03/09 职场文书
横幅标语大全
2014/06/17 职场文书
增员口号大全
2014/06/18 职场文书
个人主要事迹材料
2014/08/26 职场文书
2014年银行工作总结范文
2014/11/12 职场文书
2014年档案室工作总结
2014/12/01 职场文书
独生子女证明范本
2015/06/19 职场文书
环保主题班会教案
2015/08/13 职场文书
2016高考寄语集锦
2015/12/04 职场文书