iOS开发Flutter探索-自定义Cell+ListView()渲染(6)

前言

今天我们来看一下在Flutter中如何自定义Widget,同时针对前几篇文章中涉及到的知识在这里做下练习。我们以在iOS下创建自定义Cell的思路来创建Widget,并将它应用到ListView 中,点击cell 实现一个页面跳转的效果。
先看下效果图:


效果图.gif

主要就是参考微信的一个简单的List页面

自定义Widget(自定义Cell)

  • 布局分析
Simulator Screen Shot - iPhone 11 Pro Max - 2020-06-16 at 22.19.48.png

我这里用到的是Container+Row 的布局方式,其实也可以通过Stack布局,方式非常多,大家灵活应用。这里主要目的是对Flutter-UI的练习。

  • 自定义Widget

在iOS中,定制这样一个这样的cell 是不是分分钟的事情,算是非常简单的一种了;这个cell上可变的部分包括4部分:最左边的icon(UIImageView),标题title(UILabel),右边的灰色子标题subTitle(UILabel),小红点状态icon(UIImageView),还有不可变的小箭头(UIImageView);然后我们将可变的部分通过对外暴露一个方法来动态获取这些参数,并显示在屏幕。
在flutter中,我们通过自定义Widget来实现,这里我们自定义一个StatefulWidget(内部有状态改变,所以这里使用有状态的Widget),直接上代码:

import 'package:flutter/material.dart';

class ExamCell extends StatefulWidget {
  //这里定义了4个变量,用来外界传入
  final String title;       // 成员变量
  final String subTitle;
  final String imgName;
  final String subImgName;
  
   // ExamCell 的构造方法  给外界暴露的方法 就像使用Text() 一样
    //写法的规则就是这样
   //快速生成:当我们只定义了上面的成员变量时,会提示报错,
   // 此时选中报错的成员变量然后 option+回车 就自动生成以下构造方法
  const ExamCell({          
    Key key,
    @required this.title,    //@required 类似于ios Protocol @required 表示必传参数
    this.subTitle,              //未标记@required为可选参数
    @required this.imgName,
    this.subImgName
  }) : super(key: key);
  
  @override
  _ExamCellState createState() => _ExamCellState();

}

class _ExamCellState extends State<ExamCell> {
  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.all(10),  //给整个容器添加一个内边框10,避免贴着屏幕
      height: 54,    // heightForRow
      color: Colors.white,   //cell.backgroundColor
      child: Row(  //整体一个横向布局
        mainAxisAlignment: MainAxisAlignment.spaceBetween, //主轴约束
        children: <Widget>[
          Container(  // left部分:image+label
            child: Row(   
              children: [
                Image(  // UIImageView
                  image: AssetImage(widget.imgName), //注意:通过widget.ivar 访问内部的成员变量(self.ivar)
                  width:20,
                ),
                SizedBox(     //这里元素间的间隔先用SizeBox
                  width: 15,
                ),
                Text(widget.title),   //UILabel
              ],
            ),
          ),
          Container(  // right部分:label+image+image
            child: Row(
              children: <Widget>[
                Text(
                  widget.subTitle != null ? widget.subTitle : '', //注意:给属性赋值是可以写逻辑判断的
                  style: TextStyle(color: Colors.grey),
                ),
                widget.subImgName != null ? Container(   //不为null给一个有内容的Container()
                  child: Image(
                    image: AssetImage(widget.subImgName),
                    width: 15,
                  ),
                  margin: EdgeInsets.only(left: 10,right: 10),
                ) : Container(),
                Image(
                  image: AssetImage('images/icon_right.png'),
                  width: 15,
                )
              ],
            ),
          ),
        ],
      ),
    );
  }
}

1.在State类中,通过widget.来访问widget类中的成员变量;
2.在渲染widget时可以添加逻辑判断;

  • 使用ExamCell
import 'package:flutter/material.dart';
import 'package:flutterappzeze/pages/cells/exam_cell.dart';

class ExamPage extends StatefulWidget {
  @override
  _ExamPageState createState() => _ExamPageState();
}

class _ExamPageState extends State<ExamPage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('水平测试'),
      ),
      body: Container(
        child: ListView(
          children: <Widget>[  // Cells 因为现在是学习UI 我们就直接Cell往里生怼
            ExamCell(       // 这里就是我们自定义的cell 构造方法接受4个参数
              imgName: 'images/pyq.png',
              subImgName: '',
              title: '泽泽圈',
              subTitle: '',
            ),
            SizedBox(height: 10), //暂时使用SizeBox 来达到类似iOS GroupStyle的Cell分组效果
            ExamCell(       // 这里就是我们自定义的cell 构造方法接受4个参数
              imgName: 'images/pyq.png',
              subImgName: '',
              title: '泽泽的二圈',
              subTitle: '',
            ),
            SizedBox(height: 10),
            ExamCell(       // 这里就是我们自定义的cell 构造方法接受4个参数
              imgName: 'images/shop.png',
              subImgName: '',
              title: 'zeze',
              subTitle: '',
            ),
            SizedBox(height: 10),
            ExamCell(       // 这里就是我们自定义的cell 构造方法接受4个参数
              imgName: 'images/sys.png',
              subImgName: '',
              title: 'family伐木类',
              subTitle: '',
            ),
            SizedBox(height: 10),
            ExamCell(       // 这里就是我们自定义的cell 构造方法接受4个参数
              imgName: 'images/icon_game.png',
              subImgName: 'images/badge.png',
              title: '泽泽的游戏',
              subTitle: '玩游戏赢大奖',
            ),
          ],
        ),
      )
    );
  }
}

ExamPage中使用我们自定义的widget,使用也非常简单,通过我们的构造方法ExamCell(imageName: subImageName: title: subTitle:),so easy!这里有个问题就是我们直接在ListView()中这样添加Cell是不合理的,如果有100行岂不是要累死😆,这里只是演示用法。其实在ListView()中也是有复用机制的,这里先不做介绍。

  • 实现点击ExamCell并跳转页面+正向传参
    这里有两个问题:
    1.在ios开发中celld点击,我们通常是实现tableView:didSelectAtIndexPath:就可以实现了,但在flutter中默认Widget是不具备事件响应能力的,它如果想具备事件响应需要用GestureDetector()将widget包裹起来,GestureDetector()能响应onTap(),onTapDown(),onTapCancel()等事件;这就又类似于在iOS下的CALayerUIView的关系了,有意思!
    这里看下_DiscoverCellState类的代码
class _ExamCellState extends State<ExamCell> {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: (){
        print("onTap");
      },
      onTapDown: (TapDownDetails details){
        print("onTapDown");
      },
      onTapCancel: (){
        print("onTapCancel");
      },
      child: Container(
          //这里省略一些代码...就是cell内部的具体UI元素定义
          //现在将这部分赋值给了GestureDetector的child
      )     
  );
}

当点击屏幕时,控制台输出:

flutter: onTapDown
flutter: onTap
flutter: onTapDown
flutter: onTap
flutter: onTapDown
flutter: onTap
flutter: onTapDown
flutter: onTapCancel

2.页面跳转:在ios下我们可以通过ViewController的present或者push的方式实现页面跳转,并直接给controller实例赋值实现正向传参,在flutter中又是怎么实现呐是不是也有类似的方式?他就是Navigator,这篇文章先不对这个玩意儿做详细的介绍,我会在后面的文章中涉及,到时候再详细说,这里就先简单的使用一下;

class _ExamCellState extends State<ExamCell> {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: (){
        print("onTap");
        Navigator.of(context).push(MaterialPageRoute(
            builder: (BuildContext context) => TestPage(
              title: widget.title,
            )));//跳转
  //下面的写法也是一个意思,在flutter中如果方法内部只有一行代码
  // 默认使用=>的方式 ,如上。
//        Navigator.of(context).push(MaterialPageRoute(
//          builder: (BuildContext context){
//            return TestPage(
//              title: widget.title,
//            );
//          }
//        ));
      },
      child: Container(
          //这里省略一些代码...就是cell内部的具体UI元素定义
          //现在将这部分赋值给了GestureDetector的child
      )     
  );
}

这里就是通过点击我的ExamCell跳转到一个TestPage的一个Widget页面,同时将当前cell的title带入。

总结

熟能生巧,在Flutter中UI布局非常灵活,当我们掌握了它们之后就可以在具体的业务上应用自如了,到目前为止并没有什么技术含量或者难以理解的东西,完全就是在熟悉Flutter下的语法规则。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。