Flutter —— 弹性盒子布局&状态管理&项目搭建

Flutter —— 弹性盒子布局&状态管理&项目搭建

1. Stack

创建一个StackDemo。

class StackDemo extends StatelessWidget {
  const StackDemo({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      color:Colors.yellow,
      alignment:Alignment(0,0),
      child: Stack(
        
      ),
    );
  }
}

为Stack添加三个子部件Container,然后里面添加Icon。

   return Container(
      color:Colors.yellow,
      alignment:Alignment(0,0),
      child: Stack(
        children: [
          Container(
            color: Colors.white,
            width: 100,
            height: 200,
            child: Icon(Icons.add),
          ),
          Container(
            color: Colors.white,
            width: 100,
            height: 200,
            child: Icon(Icons.seven_k),
          ),
          Container(
            color: Colors.white,
            width: 100,
            height: 200,
            child: Icon(Icons.qr_code),
          ),
        ],
      ),
    );

运行后发现这里只显示了一个QRcode,这是因为stack是允许子组件堆叠。因为这里大小相同所以被最后一个子部件遮挡住了。
Flutter —— 弹性盒子布局&状态管理&项目搭建
修改大小后,发现这里确实堆叠了起来。
Flutter —— 弹性盒子布局&状态管理&项目搭建

2. Positioned

Positioned用于根据Stack的四个角来确定子组件的位置。将刚才的三个container用 Positioned包起来,然后设置位置属性。

   Positioned(

              child: Container(
            color: Colors.white,
            width: 200,
            height: 200,
            child: Icon(Icons.add),
          )),
          Positioned(
              left: 20,
              top: 20,
              child: Container(
            color: Colors.red,
            width: 100,
            height: 100,
            child: Icon(Icons.seven_k),
          )),
          Positioned(
            right: 0,
              child: Container(
            color: Colors.blue,
            width: 50,
            height: 50,
            child: Icon(Icons.qr_code),
          )),

发现位置按照想要的位置属性改变了。

Flutter —— 弹性盒子布局&状态管理&项目搭建
位置的变化还可以使用margin。那么什么时候用margin什么时候用positioned呢?如果只是为了挪动位置,那么就用positioned,如果要和外界的部件保持边距,就用margin。如果用margin的话,如果外面是一个Expand,那么会因为margin的变化而增加大小。

3. AspectRatio

AspectRatio可以提供父部件一个宽高比。AspectRatio中aspectRatio是必传的属性。

 return Container(
      color: Colors.yellow,
      alignment: Alignment(0, 0),
      child: Container(
        color:Colors.red,
        height: 150,
        width: 100,
        child: AspectRatio(
          aspectRatio: 1/2
        ),
      ),
    );

运行后发现没有效果。这是因为父部件同时给height和width赋值的情况下,AspectRatio就不起效果了。
Flutter —— 弹性盒子布局&状态管理&项目搭建
把height或者width去掉后就会有效果了。
Flutter —— 弹性盒子布局&状态管理&项目搭建

4. 状态管理

状态是一个概念。在iOS中,任何控件都有一些响应,响应后都会改变样式。在flutter中只要部件有变化,就需要改变状态。在flutter中是增量渲染,如果属性发生则会重新构建Widget树,即重新创建新的 Widget 实例来替换旧的 Widget 实例,所以允许 Widget 的属性变化是没有意义的,因为一旦 Widget 自己的属性变了自己就会被替换。这也是为什么 Widget 中定义的属性必须是 final 的原因。Flutter中页面逻辑和数据逻辑是分开的,它会保留数据,替换页面。Flutter设置了一个保留数据逻辑的widget就是StatefulWidget,StatefulWidget里面封装了数据逻辑和页面逻辑。

创建一个StatelessWidget,在点击的时候进行count++。

class StateManagerDemo extends StatelessWidget {
  //const StateManagerDemo({Key? key}) : super(key: key);

  int count = 0;

  @override
  Widget build(BuildContext context) {
    return
      Scaffold(
        backgroundColor: Colors.grey[100],
        appBar: AppBar(
          title: const Text("Layout Demo"),
        ),
        body:  Center(
          child: Chip(label:Text("$count"),),
        ),
        floatingActionButton: FloatingActionButton(
          onPressed: () {
            count++;
            print("$count");
          },
          child: const Icon(Icons.add),
        ),
      );

  }
}

运行后发现count发生改变,但是ui没有变化。

Flutter —— 弹性盒子布局&状态管理&项目搭建
这个时候就需要使用StatefulWidget了。

class StateManagerDemo extends StatefulWidget {
  const StateManagerDemo({Key? key}) : super(key: key);

  @override
  _StateManagerDemoState createState() => _StateManagerDemoState();
}

class _StateManagerDemoState extends State<StateManagerDemo> {
  int count = 0;
  @override
  Widget build(BuildContext context) {
    return   Scaffold(
      backgroundColor: Colors.grey[100],
      appBar: AppBar(
        title: const Text("Layout Demo"),
      ),
      body:  Center(
        child: Chip(label:Text("$count"),),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          count++;
          print("$count");
        },
        child: const Icon(Icons.add),
      ),
    );
  }
}

这个时候点击后还是没有变化,但是在热重载之后,中间的数字就变了,说明数据还是被保留了下来。因为这里还需要在走一遍build方法才能变化,所以需要把count++放在setState里面,这样页面就会跟着变化了,因为调用setState就会重新build一次,build里面还是增量渲染。

  onPressed: () {
          setState(() {
            count++;

          });
          print("$count");
        },

_StateManagerDemoState类是私有的类,外界无法访问状态管理者,_StateManagerDemoState不能直接接受数据。StateManagerDemo里面则是有对外暴露的所有接口。_StateManagerDemoState可以访问到StateManagerDemo里面的数据然后进行保留。也就是说,数据的管理在_StateManagerDemoState里面。

5. 项目搭建

5.1 底部导航栏

创建一个新工程。将MyHomePage改成下面的结构。

class MyHomePage extends StatelessWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      
    );
  }
}

将MyHomePage返回的Widget改成Scaffold,并且添加bottomNavigationBar。

 return Scaffold(
      bottomNavigationBar: BottomNavigationBar(
        type:   BottomNavigationBarType.fixed,
        fixedColor: Colors.green,
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.chat), label: "微信"),
          BottomNavigationBarItem(icon: Icon(Icons.bookmark), label: "通讯录"),
          BottomNavigationBarItem(icon: Icon(Icons.history), label: "发现"),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: "我"),
        ],
      ),
    );

接下来要切换选中的item,那么就需要保存数据,那么MyHomePage就要变成StatefulWidget,并且在BottomNavigationBar里面的onTap调用setState修改_currentIndex。

class MyHomePage extends StatefulWidget {
 const MyHomePage({Key? key}) : super(key: key);

 @override
 _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
 int _currentIndex = 0;

 @override
 Widget build(BuildContext context) {
   return Scaffold(
     bottomNavigationBar: BottomNavigationBar(
       type:   BottomNavigationBarType.fixed,
       currentIndex: _currentIndex,
       fixedColor: Colors.green,
       items: const [
         BottomNavigationBarItem(icon: Icon(Icons.chat), label: "微信"),
         BottomNavigationBarItem(icon: Icon(Icons.bookmark), label: "通讯录"),
         BottomNavigationBarItem(icon: Icon(Icons.history), label: "发现"),
         BottomNavigationBarItem(icon: Icon(Icons.person), label: "我"),
       ],
       onTap: (index){
         setState(() {
           _currentIndex = index;
         });
       },
     ),
   );
 }
}

接下来创建四个界面,这里先都设置为StatefulWidget。但是平时开发一般整个页面是StatelessWidget的,只有在需要改变的页面才设置为StatefulWidget。然后一家一个包含四个页面的List,在Scaffold中的body使用位置为currentIndex 的页面。

class _MyHomePageState extends State<MyHomePage> {
  int _currentIndex = 0;
  List<Widget> _pages = [ChatPage(),FriendsPage(),DiscoverPage(),MinePage()];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        type:   BottomNavigationBarType.fixed,
        currentIndex: _currentIndex,
        fixedColor: Colors.green,
        items: const [
          BottomNavigationBarItem(icon: Icon(Icons.chat), label: "微信"),
          BottomNavigationBarItem(icon: Icon(Icons.bookmark), label: "通讯录"),
          BottomNavigationBarItem(icon: Icon(Icons.history), label: "发现"),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: "我"),
        ],
        onTap: (index){
          setState(() {
            _currentIndex = index;
          });
        },
      ),
    );
  }
}

这时候点击的话,就可以切换页面了。

Flutter —— 弹性盒子布局&状态管理&项目搭建
看到上面点击切换的时候,会有动画,我们不需要这个动画,那么可以在MaterialApp把其去掉。在MaterialApp中的ThemeData修改highlightColor和splashColor,这样动画就消失了。可以修改primarySwatch来修改主题色。

 return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        highlightColor: const Color.fromRGBO(1, 0, 0, 0.0),
        splashColor:  const Color.fromRGBO(1, 0, 0, 0.0),
        primarySwatch: Colors.green,
      ),
      home: const MyHomePage(),
    );
  }

这里还有一个字体变大的动画,那么可以在BottomNavigationBar修改selectedFontSize。

 selectedFontSize: 12,

5.2 程序的名字以及图标

来到android——app——src——main的AndroidManifest.xml。这里可以修改程序的名字以及图标。

  android:label="哈喽哈喽"

要使用图标的话就要先导入图片。点开上面的res,然后看到这里有,hdpi(相当于iOS的1x),mdpi(相当于iOS的1.5x),xhdpi(相当于iOS的2x),xxhdpi(相当于iOS的3x),xxxhdpi(相当于iOS的4x)。将图片复制到对应的文件夹,命名不能是驼峰以及包含中文。

android:icon="@mipmap/app_icon">

Flutter —— 弹性盒子布局&状态管理&项目搭建
启动图片在drawable以及drawable-v21里面配置。

  <item>
        <bitmap
            android:gravity="center"
            android:src="@mipmap/launch_image" />
    </item>

5.3 Assets

Flutter APP 安装包中会包含代码和 assets(资源)两部分。Assets 是会打包到程序安装包中的,可在运行时访问。常见类型的 assets 包括静态数据(例如JSON文件)、配置文件、图标和图片等。

这里添加一个Images文件夹,然后在images文件夹里面放图片。
Flutter —— 弹性盒子布局&状态管理&项目搭建
Flutter 使用pubspec.yaml 文件来管理应用程序所需的资源,包的管理也是在pubspec.yaml 文件中的。在文件中添加引用,这里的格式非常重要,多一个少一个空格都不行。
Flutter —— 弹性盒子布局&状态管理&项目搭建
接下来就可以去 BottomNavigationBar将icon修改成自己的图片,这里需要设置activeIcon来区分选中和未选中状态。

 BottomNavigationBarItem(icon: Image(image: AssetImage("images/tabbar_chat.png"),height: 20,width: 20,),activeIcon: Image(image: AssetImage("images/tabbar_chat_hl.png"),height: 20,width: 20,), label: "微信"),
BottomNavigationBarItem(icon: Image(image: AssetImage("images/tabbar_friends.png"),height: 20,width: 20,),activeIcon: Image(image: AssetImage("images/tabbar_friends_hl.png"),height: 20,width: 20,), label: "通讯录"),
BottomNavigationBarItem(icon: Image(image: AssetImage("images/tabbar_discover.png"),height: 20,width: 20,),activeIcon: Image(image: AssetImage("images/tabbar_discover_hl.png"),height: 20,width: 20,), label: "发现"),
BottomNavigationBarItem(icon: Image(image: AssetImage("images/tabbar_mine.png"),height: 20,width: 20,), activeIcon: Image(image: AssetImage("images/tabbar_mine_hl.png"),height: 20,width: 20,),label: "我"),
上一篇:vue 图标选择器


下一篇:phpstrom 配置getter和setter