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 相关文章推荐
javascript编程起步(第六课)
Feb 27 Javascript
Jqyery中同等与js中windows.onload的应用
May 10 Javascript
JavaScript中的几个关键概念的理解-原型链的构建
May 12 Javascript
滚动图片效果 jquery实现回旋滚动效果
Jan 08 Javascript
非html5实现js版弹球游戏示例代码
Sep 22 Javascript
js获取时间精确到秒(年月日)
Mar 16 Javascript
JavaScript数组实现数据结构中的队列与堆栈
May 26 Javascript
jQuery EasyUI datagrid在翻页以后仍能记录被选中行的实现代码
Aug 15 Javascript
微信小程序 在线支付功能的实现
Mar 14 Javascript
React组件refs的使用详解
Feb 09 Javascript
通过jquery.cookie.js实现记住用户名、密码登录功能
Jun 20 jQuery
electron实现静默打印的示例代码
Aug 12 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中cookie的作用域
2008/03/27 PHP
ThinkPHP模板替换与系统常量及应用实例教程
2014/08/22 PHP
php获取当月最后一天函数分享
2015/02/02 PHP
利用PHP将图片转换成base64编码的实现方法
2016/09/13 PHP
PHP7.1实现的AES与RSA加密操作示例
2018/06/15 PHP
laravel 解决多库下的DB::transaction()事务失效问题
2019/10/21 PHP
你必须知道的Javascript知识点之&quot;this指针&quot;的应用
2013/04/23 Javascript
Javascript 浮点运算的问题分析与解决方法
2013/08/27 Javascript
javaScript 计算两个日期的天数相差(示例代码)
2013/12/27 Javascript
jQuery提交多个表单的小技巧
2014/07/27 Javascript
jQuery中ajax的post()方法用法实例
2014/12/26 Javascript
网页禁用右键菜单和鼠标拖动选择方法小结
2015/02/25 Javascript
属于你的jQuery提示框(Tip)插件
2016/01/20 Javascript
JavaScript添加随滚动条滚动窗体的方法
2016/02/23 Javascript
JavaScript编写页面半透明遮罩效果的简单示例
2016/05/09 Javascript
JS中BOM相关知识点总结(必看篇)
2016/11/22 Javascript
解决同一页面中两个iframe互相调用jquery,js函数的方法
2016/12/12 Javascript
Vue实现百度下拉提示搜索功能
2017/06/21 Javascript
vue悬浮可拖拽悬浮按钮的实例代码
2019/08/20 Javascript
Weex开发之WEEX-EROS开发踩坑(小结)
2019/10/16 Javascript
详解javascript void(0)
2020/07/13 Javascript
python3.6连接MySQL和表的创建与删除实例代码
2017/12/28 Python
Python实现按中文排序的方法示例
2018/04/25 Python
解决sublime+python3无法输出中文的问题
2018/12/12 Python
python读取ini配置的类封装代码实例
2020/01/08 Python
css sprite简单实例
2016/05/23 HTML / CSS
Interrail法国:乘火车探索欧洲,最受欢迎的欧洲铁路通票
2019/08/27 全球购物
Oracle的内存结构(Memory structures)
2015/06/10 面试题
初三新学期计划书
2014/05/03 职场文书
团拜会策划方案
2014/06/07 职场文书
二年级语文上册复习计划
2015/01/19 职场文书
奖学金个人总结
2015/03/04 职场文书
同事打架检讨书
2015/05/06 职场文书
培训感想范文
2015/08/07 职场文书
党员廉政准则心得体会
2016/01/20 职场文书
Python数据可视化之绘制柱状图和条形图
2021/05/25 Python