【Flutter实战】六大布局组件及半圆菜单案例

老孟导读:Flutter中布局组件有水平 / 垂直布局组件( RowColumn )、叠加布局组件( StackIndexedStack )、流式布局组件( Wrap )和 自定义布局组件(Flow)。

水平、垂直布局组件

Row 是将子组件以水平方式布局的组件, Column 是将子组件以垂直方式布局的组件。项目中 90% 的页面布局都可以通过 Row 和 Column 来实现。

将3个组件水平排列:

Row(
  children: <Widget>[
    Container(
      height: 50,width: 100,color: Colors.red,),Container(
      height: 50,color: Colors.green,color: Colors.blue,],)

将3个组件垂直排列:

Column(
  mainAxisSize: MainAxisSize.min,children: <Widget>[
    Container(
      height: 50,)

在 Row 和 Column 中有一个非常重要的概念:主轴( MainAxis )交叉轴( CrossAxis ),主轴就是与组件布局方向一致的轴,交叉轴就是与主轴方向垂直的轴。

具体到 Row 组件,主轴 是水平方向,交叉轴 是垂直方向。而 Column 与 Row 正好相反,主轴 是垂直方向,交叉轴 是水平方向。

明白了 主轴 和 交叉轴 概念,我们来看下 mainAxisAlignment 属性,此属性表示主轴方向的对齐方式,默认值为 start,表示从组件的开始处布局,此处的开始位置和 textDirection 属性有关,textDirection 表示文本的布局方向,其值包括 ltr(从左到右) 和 rtl(从右到左),当 textDirection = ltr 时,start 表示左侧,当 textDirection = rtl 时,start 表示右侧,

Container(
  decoration: BoxDecoration(border: Border.all(color: Colors.black)),child: Row(
    children: <Widget>[
      Container(
        height: 50,Container(
        height: 50,)

黑色边框是Row控件的范围,默认情况下Row铺满父组件。

主轴对齐方式有6种,效果如下图:

spaceAround 和 spaceEvenly 区别是:

  • spaceAround :第一个子控件距开始位置和最后一个子控件距结尾位置是其他子控件间距的一半。
  • spaceEvenly : 所有间距一样。

和主轴对齐方式相对应的就是交叉轴对齐方式 crossAxisAlignment ,交叉轴对齐方式默认是居中。Row控件的高度是依赖子控件高度,因此子控件高都一样时,Row的高和子控件高相同,此时是无法体现交叉轴对齐方式,修改3个颜色块高分别为50,100,150,这样Row的高是150,代码如下:

Container(
      decoration: BoxDecoration(border: Border.all(color: Colors.black)),child: Row(
        crossAxisAlignment: CrossAxisAlignment.center,children: <Widget>[
          Container(
            height: 50,Container(
            height: 100,Container(
            height: 150,)

主轴对齐方式效果如下图:

mainAxisSize 表示主轴尺寸,有 min 和 max 两种方式,默认是 maxmin 表示尽可能小,max 表示尽可能大。

Container(
	decoration: BoxDecoration(border: Border.all(color: Colors.black)),child: Row(
		mainAxisSize: MainAxisSize.min,...
	)
)

看黑色边框,正好包裹子组件,而 max 效果如下:

textDirection 表示子组件主轴布局方向,值包括 ltr(从左到右) 和 rtl(从右到左)

Container(
  decoration: BoxDecoration(border: Border.all(color: Colors.black)),child: Row(
    textDirection: TextDirection.rtl,children: <Widget>[
      ...
    ],)

verticalDirection 表示子组件交叉轴布局方向:

  • up :从底部开始,并垂直堆叠到顶部,对齐方式的 start 在底部,end 在顶部。
  • down: 与 up 相反。
Container(
  decoration: BoxDecoration(border: Border.all(color: Colors.black)),child: Row(
    crossAxisAlignment: CrossAxisAlignment.start,verticalDirection: VerticalDirection.up,children: <Widget>[
      Container(
        height: 50,Container(
        height: 100,Container(
        height: 150,)

想一想这种效果完全可以通过对齐方式实现,那么为什么还要有 textDirectionverticalDirection 这两个属性,官方API文档已经解释了这个问题:

This is also used to disambiguate start and end values (e.g. [MainAxisAlignment.start] or [CrossAxisAlignment.end]).

用于消除 MainAxisAlignment.start 和 CrossAxisAlignment.end 值的歧义的。

叠加布局组件

叠加布局组件包含 StackIndexedStack,Stack 组件将子组件叠加显示,根据子组件的顺利依次向上叠加,用法如下:

Stack(
  children: <Widget>[
    Container(
      height: 200,width: 200,Container(
      height: 170,width: 170,Container(
      height: 140,width: 140,color: Colors.yellow,)
  ],)

Stack 对未定位(不被 Positioned 包裹)子组件的大小由 fit 参数决定,默认值是 StackFit.loose ,表示子组件自己决定,StackFit.expand 表示尽可能的大,用法如下:

Stack(
  fit: StackFit.expand,children: <Widget>[
    Container(
      height: 200,)

效果只有黄色(最后一个组件的颜色),并不是其他组件没有绘制,而是另外两个组件被黄色组件覆盖。

Stack 对未定位(不被 Positioned 包裹)子组件的对齐方式由 alignment 控制,默认左上角对齐,用法如下:

Stack(
  alignment: AlignmentDirectional.center,)

通过 Positioned 定位的子组件:

Stack(
  alignment: AlignmentDirectional.center,Positioned(
      left: 30,right: 40,bottom: 50,top: 60,child: Container(
        color: Colors.yellow,)

topbottomleftright 四种定位属性,分别表示距离上下左右的距离。

如果子组件超过 Stack 边界由 overflow 控制,默认是裁剪,下面设置总是显示的用法:

Stack(
  overflow: Overflow.visible,Positioned(
      left: 100,top: 100,height: 150,width: 150,child: Container(
        color: Colors.green,)

IndexedStack 是 Stack 的子类,Stack 是将所有的子组件叠加显示,而 IndexedStack 通过 index 只显示指定索引的子组件,用法如下:

class IndexedStackDemo extends StatefulWidget {
  @override
  _IndexedStackDemoState createState() => _IndexedStackDemoState();
}

class _IndexedStackDemoState extends State<IndexedStackDemo> {
  int _index = 0;

  @override
  Widget build(BuildContext context) {
    return Column(
      children: <Widget>[
        SizedBox(height: 50,_buildIndexedStack(),SizedBox(height: 30,_buildRow(),);
  }

  _buildIndexedStack() {
    return IndexedStack(
      index: _index,children: <Widget>[
        Center(
          child: Container(
            height: 300,width: 300,alignment: Alignment.center,child: Icon(
              Icons.fastfood,size: 60,Center(
          child: Container(
            height: 300,child: Icon(
              Icons.cake,child: Icon(
              Icons.local_cafe,);
  }

  _buildRow() {
    return Row(
      mainAxisAlignment: MainAxisAlignment.center,children: <Widget>[
        IconButton(
          icon: Icon(Icons.fastfood),onPressed: () {
            setState(() {
              _index = 0;
            });
          },IconButton(
          icon: Icon(Icons.cake),onPressed: () {
            setState(() {
              _index = 1;
            });
          },IconButton(
          icon: Icon(Icons.local_cafe),onPressed: () {
            setState(() {
              _index = 2;
            });
          },);
  }
}

流式布局组件

Wrap 为子组件进行水平或者垂直方向布局,且当空间用完时,Wrap 会自动换行,也就是流式布局。

创建多个子控件做为 Wrap 的子控件,代码如下:

Wrap(
  children: List.generate(10,(i) {
    double w = 50.0 + 10 * i;
    return Container(
      color: Colors.primaries[i],height: 50,width: w,child: Text('$i'),);
  }),)

direction 属性控制布局方向,默认为水平方向,设置方向为垂直代码如下:

Wrap(
  direction: Axis.vertical,children: List.generate(4,)

alignment 属性控制主轴对齐方式,crossAxisAlignment 属性控制交叉轴对齐方式,对齐方式只对有剩余空间的行或者列起作用,例如水平方向上正好填充完整,则不管设置主轴对齐方式为什么,看上去的效果都是铺满。

说明 :主轴就是与当前组件方向一致的轴,而交叉轴就是与当前组件方向垂直的轴,如果Wrap的布局方向为水平方向 Axis.horizontal,那么主轴就是水平方向,反之布局方向为垂直方向 Axis.vertical ,主轴就是垂直方向。

Wrap(
	alignment: WrapAlignment.spaceBetween,...
)

主轴对齐方式有6种,效果如下图:

spaceAroundspaceEvenly 区别是:

  • spaceAround:第一个子控件距开始位置和最后一个子控件距结尾位置是其他子控件间距的一半。
  • spaceEvenly:所有间距一样。

设置交叉轴对齐代码如下:

Wrap(
	crossAxisAlignment: WrapCrossAlignment.center,...
)

如果 Wrap 的主轴方向为水平方向,交叉轴方向则为垂直方向,如果想要看到交叉轴对齐方式的效果需要设置子控件的高不一样,代码如下:

Wrap(
  spacing: 5,runSpacing: 3,crossAxisAlignment: WrapCrossAlignment.center,children: List.generate(10,(i) {
    double w = 50.0 + 10 * i;
    double h = 50.0 + 5 * i;
    return Container(
      color: Colors.primaries[i],height: h,)

runAlignment 属性控制 Wrap 的交叉抽方向上每一行的对齐方式,下面直接看 runAlignment 6中方式对应的效果图,

runAlignmentalignment 的区别:

  • alignment :是主轴方向上对齐方式,作用于每一行。
  • runAlignment :是交叉轴方向上将每一行看作一个整体的对齐方式。

spacingrunSpacing 属性控制Wrap主轴方向和交叉轴方向子控件之间的间隙,代码如下:

Wrap(
	spacing: 5,runSpacing: 2,...
)

textDirection 属性表示 Wrap 主轴方向上子组件的方向,取值范围是 ltr(从左到右) 和 rtl(从右到左),下面是从右到左的代码:

Wrap(
	textDirection: TextDirection.rtl,...
)

verticalDirection 属性表示 Wrap 交叉轴方向上子组件的方向,取值范围是 up(向上) 和 down(向下),设置代码如下:

Wrap(
	verticalDirection: VerticalDirection.up,...
)

注意:文字为0的组件是在下面的。

自定义布局组件

大部分情况下,不会使用到 Flow ,但 Flow 可以调整子组件的位置和大小,结合Matrix4绘制出各种酷炫的效果。

Flow 组件对使用转换矩阵操作子组件经过系统优化,性能非常高效。

基本用法如下:

Flow(
  delegate: SimpleFlowDelegate(),children: List.generate(5,(index) {
    return Container(
      height: 100,color: Colors.primaries[index % Colors.primaries.length],)

delegate 控制子组件的位置和大小,定义如下 :

class SimpleFlowDelegate extends FlowDelegate {
  @override
  void paintChildren(FlowPaintingContext context) {
    for (int i = 0; i < context.childCount; ++i) {
      context.paintChild(i);
    }
  }

  @override
  bool shouldRepaint(SimpleFlowDelegate oldDelegate) {
    return false;
  }
}

delegate 要继承 FlowDelegate,重写 paintChildrenshouldRepaint 函数,上面直接绘制子组件,效果如下:

只看到一种颜色并不是只绘制了这一个,而是叠加覆盖了,和 Stack 类似,下面让每一个组件有一定的偏移,SimpleFlowDelegate 修改如下:

class SimpleFlowDelegate extends FlowDelegate {
  @override
  void paintChildren(FlowPaintingContext context) {
    for (int i = 0; i < context.childCount; ++i) {
      context.paintChild(i,transform: Matrix4.translationValues(0,i*30.0,0));
    }
  }

  @override
  bool shouldRepaint(SimpleFlowDelegate oldDelegate) {
    return false;
  }
}

每一个子组件比上一个组件向下偏移30。

仿 掘金-我的效果

效果如下:

到拿到一个页面时,先要将其拆分,上面的效果拆分如下:

总体分为3个部分,水平布局,红色区域圆形头像代码如下:

_buildCircleImg() {
  return Container(
    height: 60,width: 60,decoration: BoxDecoration(
        shape: BoxShape.circle,image: DecorationImage(image: AssetImage('assets/images/logo.png'))),);
}

蓝色区域代码如下:

_buildCenter() {
  return Column(
    mainAxisAlignment: MainAxisAlignment.center,crossAxisAlignment: CrossAxisAlignment.start,children: <Widget>[
      Text('老孟Flutter',style: TextStyle(fontSize: 20),Text('Flutter、Android',style: TextStyle(color: Colors.grey),)
    ],);
}

绿色区域是一个图标,代码如下:

Icon(Icons.arrow_forward_ios,color: Colors.grey,size: 14,

将这3部分组合在一起:

Container(
  color: Colors.grey.withOpacity(.5),child: Container(
    height: 100,color: Colors.white,child: Row(
      children: <Widget>[
        SizedBox(
          width: 15,_buildCircleImg(),SizedBox(
          width: 25,Expanded(
          child: _buildCenter(),Icon(Icons.arrow_forward_ios,SizedBox(
          width: 15,)

最终的效果就是开始我们看到的效果图。

水平展开/收起菜单

使用Flow实现水平展开/收起菜单的功能,代码如下:

class DemoFlowPopMenu extends StatefulWidget {
  @override
  _DemoFlowPopMenuState createState() => _DemoFlowPopMenuState();
}

class _DemoFlowPopMenuState extends State<DemoFlowPopMenu>
    with SingleTickerProviderStateMixin {
  //动画必须要with这个类
  AnimationController _ctrlAnimationPopMenu; //定义动画的变量
  IconData lastTapped = Icons.notifications;
  final List<IconData> menuItems = <IconData>[
    //菜单的icon
    Icons.home,Icons.new_releases,Icons.notifications,Icons.settings,Icons.menu,];

  void _updateMenu(IconData icon) {
    if (icon != Icons.menu) {
      setState(() => lastTapped = icon);
    } else {
      _ctrlAnimationPopMenu.status == AnimationStatus.completed
          ? _ctrlAnimationPopMenu.reverse() //展开和收拢的效果
          : _ctrlAnimationPopMenu.forward();
    }
  }

  @override
  void initState() {
    super.initState();
    _ctrlAnimationPopMenu = AnimationController(
      //必须初始化动画变量
      duration: const Duration(milliseconds: 250),//动画时长250毫秒
      vsync: this,//SingleTickerProviderStateMixin的作用
    );
  }

//生成Popmenu数据
  Widget flowMenuItem(IconData icon) {
    final double buttonDiameter =
        MediaQuery.of(context).size.width * 2 / (menuItems.length * 3);
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8.0),child: RawMaterialButton(
        fillColor: lastTapped == icon ? Colors.amber[700] : Colors.blue,splashColor: Colors.amber[100],shape: CircleBorder(),constraints: BoxConstraints.tight(Size(buttonDiameter,buttonDiameter)),onPressed: () {
          _updateMenu(icon);
        },child: Icon(icon,size: 30.0),);
  }

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Flow(
        delegate: FlowMenuDelegate(animation: _ctrlAnimationPopMenu),children: menuItems
            .map<Widget>((IconData icon) => flowMenuItem(icon))
            .toList(),);
  }
}

FlowMenuDelegate 定义如下:

class FlowMenuDelegate extends FlowDelegate {
  FlowMenuDelegate({this.animation}) : super(repaint: animation);
  final Animation<double> animation;

  @override
  void paintChildren(FlowPaintingContext context) {
    double x = 50.0; //起始位置
    double y = 50.0; //横向展开,y不变
    for (int i = 0; i < context.childCount; ++i) {
      x = context.getChildSize(i).width * i * animation.value;
      context.paintChild(
        i,transform: Matrix4.translationValues(x,y,0),);
    }
  }

  @override
  bool shouldRepaint(FlowMenuDelegate oldDelegate) =>
      animation != oldDelegate.animation;
}

半圆菜单展开/收起

代码如下:

import 'dart:math';

import 'package:flutter/material.dart';

class DemoFlowMenu extends StatefulWidget {
  @override
  _DemoFlowMenuState createState() => _DemoFlowMenuState();
}

class _DemoFlowMenuState extends State<DemoFlowMenu>
    with TickerProviderStateMixin {
  //动画需要这个类来混合
  //动画变量,以及初始化和销毁
  AnimationController _ctrlAnimationCircle;

  @override
  void initState() {
    super.initState();
    _ctrlAnimationCircle = AnimationController(
        //初始化动画变量
        lowerBound: 0,upperBound: 80,duration: Duration(milliseconds: 300),vsync: this);
    _ctrlAnimationCircle.addListener(() => setState(() {}));
  }

  @override
  void dispose() {
    _ctrlAnimationCircle.dispose(); //销毁变量,释放资源
    super.dispose();
  }

  //生成Flow的数据
  List<Widget> _buildFlowChildren() {
    return List.generate(
        5,(index) => Container(
              child: Icon(
                index.isEven ? Icons.timer : Icons.ac_unit,));
  }

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        Positioned.fill(
          child: Flow(
            delegate: FlowAnimatedCircle(_ctrlAnimationCircle.value),children: _buildFlowChildren(),Positioned.fill(
          child: IconButton(
            icon: Icon(Icons.menu),onPressed: () {
              setState(() {
                //点击后让动画可前行或回退
                _ctrlAnimationCircle.status == AnimationStatus.completed
                    ? _ctrlAnimationCircle.reverse()
                    : _ctrlAnimationCircle.forward();
              });
            },);
  }
}

FlowAnimatedCircle 代码如下:

class FlowAnimatedCircle extends FlowDelegate {
  final double radius; //绑定半径,让圆动起来
  FlowAnimatedCircle(this.radius);

  @override
  void paintChildren(FlowPaintingContext context) {
    if (radius == 0) {
      return;
    }
    double x = 0; //开始(0,0)在父组件的中心
    double y = 0;
    for (int i = 0; i < context.childCount; i++) {
      x = radius * cos(i * pi / (context.childCount - 1)); //根据数学得出坐标
      y = radius * sin(i * pi / (context.childCount - 1)); //根据数学得出坐标
      context.paintChild(i,-y,0));
    } //使用Matrix定位每个子组件
  }

  @override
  bool shouldRepaint(FlowDelegate oldDelegate) => true;
}

交流

老孟Flutter博客地址(330个控件用法):http://laomengit.com

欢迎加入Flutter交流群(微信:laomengit)、关注公众号【老孟Flutter】:

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


这篇文章主要讲解了“FlutterComponent动画的显和隐怎么实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究...
这篇文章主要讲解了“flutter微信聊天输入框功能如何实现”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“f...
本篇内容介绍了“Flutter之Navigator的高级用法有哪些”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处...
这篇文章主要介绍“Flutter怎么使用Android原生播放器”,在日常操作中,相信很多人在Flutter怎么使用Android原生播放器问题上存在疑惑,小编查阅了各式资料,整...
Flutter开发的android端如何修改APP名称,logo,版本号,具体的操作步骤:修改APP名称找到文件:android\\app\\src\\main\\AndroidManifest.xml
Flutter路由管理初识路由概念一.路由管理1.1.Route1.2.MaterialPageRoute1.3.Navigator1.4.路由传值1.5 命名路由1.6.命名路由参数传递1.7.适配二、路由钩子三、onUnknownRoute四、结尾初识路由概念路由的概念由来已久,包括网络路由、后端路由,到现在广为流行的前端路由。无论路由的概念如何应用,它的核心是一个路由映射表。比如:名字 detail 映射到 DetailPage 页面等。有了这个映射表之后,我们就可以方便的根据名字来完成路由的转发
前提:针对Android开发者(windows系统下),已安装Git,AndroidStudio(建议4.0+版本)一.下载Flutter SDK地址:https://flutter.dev/docs/development/tools/sdk/releases,在 Stable channel (Windows)里面下最新版本即可。Flutter的渠道版本会不停变动,请以Flutter官网为准。在中国,要想正常获取安装包列表或下载安装包,可能需要翻墙,也可以去Flutter github项目下去下载安
一、变量变量是一个引用,根据Dart中“万物皆对象”原则,即变量存储的都是对象的引用,或者说它们都是指向对象。1.1.声明变量://1.不指定类型var name = 'aaa';//2.明确指定类型String name = 'aaa';因为有类型推导,所以两种实现效果一样,官方推荐在函数内的本地变量尽量使用var声明。在变量类型并不明确的情况下,可以使用dynamic关键字//3.使用dynamic关键字dynamic name = 'aaa';1.2.默认值未初始化的变量
前言Flutter2.0发布不久,对web的支持刚刚进入stable阶段。初学几天,构建web应用时候碰到一些问题,比如中文显示成乱码,然后加载图片出现图片跨域问题:Failed to load network image...Trying to load an image from another domain?1.开启web端构建:使用下面这个命令才可以开启Web端构建的支持flutter config --enable-web提示我们:重新启动编辑器,以便它们读取新设置。2.重
一.Flutter打Android release包的步骤:1.为项目创建一个.jks签名文件(很简单,跳过)2.创建一个文件key.properties,直接复制下面key.properties位置如图:在里面输入一下内容:storePassword=iflytekkeyPassword=iflytekkeyAlias=teachingmachinestoreFile=E:/teacher/app/keys/TeachingMachine.jks输入你自己的passwork以及
1 问题Android原生向js发消息,并且可以携带数据2 实现原理Android原生可以使用RCTEventEmitter来注册事件,然后这里需要指定事件的名字,然后在js那端进行监听同样事件的名字监听,就可以收到消息得到数据Android注册关键代码reactContext.getJSModule(DeviceEventManagerModule.RCT...
1 Flexbox布局1) flexDirection 可以决定布局的主轴,子元素是应该沿着水平轴(row)方向排列,还是沿着竖直轴(column)方向排列2) justifyContent 决定其子元素沿着次轴(与主轴垂直的轴,比如若主轴方向为row,则次轴方向为column)的排列方式 有flex-start、center、flex-end、space-around...
1 实现的功能在网上看React Native文档,我特码就想实现一个页面到另外一个页面的跳转,然后另外一个页面怎么获取参数,特么没找到一个说清楚的,要么太复杂,要么说了不理解,下面是我自己写的一个App.js文件,实现一个Home页面跳到另外Details页面,并且携带了参数怎么在Details页面获取,就是这么简单粗暴.2 测试DemoApp.js文件如下...
1 问题在一个文件构建一个对象,然后在另外一个文件里面new这个对象,通过构造方法传递参数,然后再获取这个参数2 测试代码Student.js文件如下'use strict';import React from 'react'import {NativeModules, NativeEventEmitter, DeviceEventEmitter,Ale...
1 简单部分代码export default class App extends Component&amp;lt;Props&amp;gt; { render() { return ( &amp;lt;View {styles.container}&amp;gt; &amp;lt;View {styles.welcome}&amp;gt; &amp;l...
1 怎么实现发送和接收事件理论上封装了Android原生广播的代码,需要注册和反注册,这里用DeviceEventEmitter实现//增加监听DeviceEventEmitter.addListener//取消监听//this.emitter.remove();这里可也可以通过安卓原生向页面js发送消息,可以参考我的这篇博客React Native之Android原生通过Dev...
1、Component介绍一般Component需要被其它类进行继承,Component和Android一样,也有生命周期英文图片如下2 具体说明1)、挂载阶段constructor()//构造函数,声明之前先调用super(props)componentWillMount()//因为它发生在render()方法前,因此在该方法内同步设置状态...
1 触摸事件普通点击我们可以使用onPress方法,我们可以使用Touchable 系列控件设计我们的按钮TouchableHighlight 背景会在用户手指按下时变暗TouchableNativeFeedback用户手指按下时形成类似墨水涟漪的视觉效果TouchableOpacity指按下时降低按钮的透明度,而不会改变背景的颜色TouchableWithoutFeedbac...
1 问题部分代码如下class HomeScreen extends React.Component { render() { return ( &amp;lt;View {{ flex: 1, alignItems: 'center', justifyContent: 'center' }}&amp;gt; &amp;lt;Text&amp;gt;Home Scre...
1 Props(属性)和State(状态)和简单样式简单使用App.js代码如下/** * Sample React Native App * https://github.com/facebook/react-native * * @format * @flow */import React, {Component} from 'react';import {Pla...