【Flutter -- 实例】案例二:加深布局的熟练度

【Flutter -- 实例】案例二:加深布局的熟练度_第1张图片

文章目录

    • 前言
    • 布局一
      • 1. 需求
      • 2. 代码
      • 3. 效果图
    • 布局二
      • 1. 需求
      • 2. 代码
      • 3. 效果图
    • 布局三
      • 1. 需求
      • 2. 代码
      • 3. 效果图
    • 布局四
      • 1. 需求
      • 2. 下拉刷新
      • 3. 上拉加载
      • 4. ListView.separated

前言

因为我是从事 Android 开发,学习了 Flutter 之后,发现其布局和在 Android 下布局是不一样的,Android 布局是在 XML 文件下,直观性强一点,基本是整体到局部,首先是确定根布局是用 LinearLayout 还是 RelativeLayout 或者是 ConstraintLayout 等。而在 Flutter 下,都是由 Widget 来拼接起来,很多时候都是 Row+Column 合成,我自己是在草稿上画出用什么 Widget 来拼出需求布局,然后才去实现。

布局一

1. 需求

【Flutter -- 实例】案例二:加深布局的熟练度_第2张图片
很容易看出三块竖直排列,根 Widget 用 Column 来实现,局部第一行是 Text,第二行是 Row 行,但是 Row 并不是都是统一样式,多线程和 Java 深入是带圆角背景的,下面再仔细讲解,第三行是两个文本(作者文本和时间文本),一个图标,第一个文本很容易想到 Expanded,当s时间文本和图标摆放后,其会占满剩余主轴空间。

2. 代码

  • 封装 TextStyle 和 Padding
    首先我看到整个布局下字体的颜色至少四种,有加粗和不加粗的,并且有部分加了padding,还是封装TextStyle和padding:
    /**
     * TextStyle:封装
     * colors:颜色
     * fontsizes:字体大小
     * isFontWeight:是否加粗
     */
    TextStyle getTextStyle(Color colors,double fontsizes,bool isFontWeight){
      return TextStyle(
        color:colors,
        fontSize: fontsizes,
        fontWeight: isFontWeight == true ? FontWeight.bold : FontWeight.normal ,
      );
    }
        /**
     * 组件加上下左右padding
     * w:所要加padding的组件
     * all:加多少padding
     */
    Widget getPadding(Widget w,double all){
      return Padding(
        child:w,
        padding:EdgeInsets.all(all),
      );
    }

    /**
     * 组件选择性加padding
     * 这里用了位置可选命名参数{param1,param2,...}来命名参数,也调用的时候可以不传
     *
     */
    Widget getPaddingfromLTRB(Widget w,{double l,double t,double,r,double b}){
      return Padding(
        child:w,
        padding:EdgeInsets.fromLTRB(l ?? 0,t ?? 0,r ?? 0,b ?? 0),
      );
    }
  • 实现第一行
    因为上面分析,整体是用Column来实现,下面实现第一行 Java synchronized原理总结
    Widget ColumnWidget = Column(
      //主轴上设置居中
      mainAxisAlignment: MainAxisAlignment.center,
      //交叉轴(水平方向)设置从左开始
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        //第一行
        getPaddingfromLTRB(Text('Java synchronized原理总结',
          style: getTextStyle(Colors.black, 16,true),
        ),t:0.0),
      ],
    );
  • 实现第二行
    第二行可以看到多线程Java深入是带渐变效果的圆角,查了网上的资料发现 Container 是有设置圆角和渐变属性的:
    //抽取第二行渐变text效果
    Container getText(String text,LinearGradient linearGradient){
      return Container(
        //距离左边距离10dp
        margin: const EdgeInsets.only(left: 10),
        //约束 相当于直接制定了该Container的宽和高,且它的优先级要高于width和height
        constraints: new BoxConstraints.expand(
          width: 70.0, height: 30.0,),
        //文字居中
        alignment: Alignment.center,
        child: new Text(
            text,
            style:getTextStyle(Colors.white,14,false),
        ),
        decoration: new BoxDecoration(
          color: Colors.blue,
          //圆角
          borderRadius: new BorderRadius.all(new Radius.circular(6.0)),
          //添加渐变
          gradient:linearGradient,
        ),
      );

    }
  • 整合第二行
//第二行
    Widget rowWidget = Row(
      //主轴左边对齐
      mainAxisAlignment: MainAxisAlignment.start,
      //交叉轴(竖直方向)居中
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        Text("分类:",
          style: getTextStyle(Colors.blue,14,true),

        ),
        getText("多线程", l1),
        getText("Java深入", l2),
      ],

    );
    
    //根Widget
    Widget ColumnWidget = Column(
      //主轴上设置居中
      mainAxisAlignment: MainAxisAlignment.center,
      //交叉轴(水平方向)设置从左开始
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        //第一行
        getPaddingfromLTRB(Text('Java synchronized原理总结',
          style: getTextStyle(Colors.black, 16,true),
        ),t:0.0),
        //第二行
        getPaddingfromLTRB(rowWidget,t:10.0),
      ],
    );

  • 实现第三行
  //第三行
    Widget rowthreeWidget = Row(
      mainAxisAlignment: MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
         new Expanded(
             child: Text(
                 "作者:EnjoyMoving",
                 style: getTextStyle(Colors.grey[400], 14, true),
             ),
         ),
         getPaddingfromLTRB(Text(
           '时间:2019-02-02',
           style: getTextStyle(Colors.black, 14, true),
         ), r :10.0),
         getPaddingfromLTRB(Icon(
           Icons.favorite_border,
           color:Colors.grey[400],
         ),r:0.0)
      ],
    );
  • 整体
    //根Widget
    Widget ColumnWidget = Column(
      //主轴上设置居中
      mainAxisAlignment: MainAxisAlignment.center,
      //交叉轴(水平方向)设置从左开始
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        //第一行
        getPaddingfromLTRB(Text('Java synchronized原理总结',
          style: getTextStyle(Colors.black, 16,true),
        ),t:0.0),
        //第二行
        getPaddingfromLTRB(rowWidget,t:10.0),
        //第三行
        getPaddingfromLTRB(rowthreeWidget,t:10.0),

      ],
    );
    return new Scaffold(
        appBar: new AppBar(
          title: new Text('Flutter Demo'),
        ),
        //用card裹住
        body: Card(
              child: Container(
                //高度
                height: 160.0,
                //颜色
                color: Colors.white,
                padding: EdgeInsets.all(10.0),
                child:  Center(
                  child: ColumnWidget,
                )
              ),
          ),
    );

3. 效果图

【Flutter -- 实例】案例二:加深布局的熟练度_第3张图片

布局二

1. 需求

【Flutter -- 实例】案例二:加深布局的熟练度_第4张图片
大致框架是最外层是用 Row,左孩子是图片,右孩子是 Column,其孩子分为五行,最后一行主演还是用Row来实现,上分析图:

2. 代码

  • 实现右边图片
//根Widget 布局二 开始
    //右边图片布局
    Widget LayoutTwoLeft = Container(
        //这次使用裁剪实现圆角矩形
        child:ClipRRect(
          //设置圆角
          borderRadius: BorderRadius.circular(4.0),
          child: Image.network(
            'https://img3.doubanio.com//view//photo//s_ratio_poster//public//p2545472803.webp',
            width: 100.0,
            height: 150.0,
            fit:BoxFit.fill,
          ),

        ),
    );
        //整体
    Widget RowWidget = Row(
      //主轴上设置居中
      mainAxisAlignment: MainAxisAlignment.start,
      //交叉轴(水平方向)设置从左开始
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        LayoutTwoLeft,
      ],
    );

  • 实现圆形头像
    就是用自带的CircleAvatar这个Widget来实现:
    //右下角圆形
    CircleAvatar getCircleAvator(String image_url){
      //圆形头像
      return CircleAvatar(
        backgroundColor: Colors.white,
        backgroundImage: NetworkImage(image_url),
      );
    }
  • 实现右边布局
    右布局就是用一个Column来实现,一列一列往下实现即可:
    //右布局
    Widget LayoutTwoRightColumn = Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        //电影名称
        Text(
          '流浪地球',
          style: getTextStyle(Colors.black, 20.0, true),
        ),

        //豆瓣评分
        Text(
          '豆瓣评分:7.9',
          style: getTextStyle(Colors.black54, 16.0, false),
        ),

        //类型
        Text(
          '类型:科幻、太空、灾难',
          style:getTextStyle(Colors.black54, 16.0, false),
        ),

        //导演
        Text(
          '导演:郭帆',
          style: getTextStyle(Colors.black54, 16.0, false),
        ),

        //主演
        Container(
          margin: EdgeInsets.only(top:8.0),
          child:Row(
            children: <Widget>[
              Text('主演:'),
              //以Row从左到右排列头像
              Row(
                children: <Widget>[
                  Container(
                    margin: EdgeInsets.only(left:2.0),
                    child: getCircleAvator('https://img3.doubanio.com//view//celebrity//s_ratio_celebrity//public//p1533348792.03.webp'),
                  ),
                  Container(
                    margin: EdgeInsets.only(left:12.0),
                    child: getCircleAvator('https://img3.doubanio.com//view//celebrity//s_ratio_celebrity//public//p1501738155.24.webp'),
                  ),
                  Container(
                    margin: EdgeInsets.only(left:12.0),
                    child: getCircleAvator('https://img3.doubanio.com//view//celebrity//s_ratio_celebrity//public//p1540619056.43.webp'),
                  ),

                ],
              ),
            ],
          ),
        ),
      ],
    );
    
    //布局二 右布局 用Expanded占满剩余空间
    Widget LayoutTwoRightExpanded = Expanded(
      child:Container(
        //距离左布局10
        margin:EdgeInsets.only(left:10.0),
        //高度
        height:150.0,
        child: LayoutTwoRightColumn,
      ),
    );

  • 整合
    //整体
    Widget RowWidget = Row(
      //主轴上设置从开始方向对齐
      mainAxisAlignment: MainAxisAlignment.start,
      //交叉轴(水平方向)居中
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        LayoutTwoLeft,
        LayoutTwoRightExpanded,
      ],
    );
        return new Scaffold(
        appBar: new AppBar(
          title: new Text('Flutter Demo'),
        ),
        body: Card(
              child: Container(
                //alignment: Alignment(0.0, 0.0),
                height: 160.0,
                color: Colors.white,
                padding: EdgeInsets.all(10.0),
                child:  Center(
                // 布局一
                // child: ColumnWidget,

                // 布局二
                   child:RowWidget,
                )
              ),
          ),
      );

3. 效果图

【Flutter -- 实例】案例二:加深布局的熟练度_第5张图片

布局三

1. 需求

【Flutter -- 实例】案例二:加深布局的熟练度_第6张图片
根布局直接用 Column,一行一行实现就可以了,这个布局稍微简单一点。

2. 代码

  • 实现第一行
    //布局三开始第一行
    Widget LayoutThreeOne = Row(
       children: <Widget>[
         Expanded(
           child: Row(
             children: <Widget>[
               Text('作者:'),
               Text('HuYounger',
                  style: getTextStyle(Colors.redAccent[400], 14, false),
               ),
             ],
           )
         ),
         //收藏图标
         getPaddingfromLTRB(Icon(Icons.favorite,color:Colors.red),r:10.0),
         //分享图标
         Icon(Icons.share,color:Colors.black),
       ],
    );
  • 实现第三行
    //布局三开始第三行
    Widget LayoutThreeThree = Row(
      children: <Widget>[
        Expanded(
          child: Row(
            children: <Widget>[
              Text('分类:'),
              getPaddingfromLTRB(Text('开发环境/Android',
                  style:getTextStyle(Colors.deepPurpleAccent, 14, false)),l:8.0),
            ],
          ),
        ),
        Text('发布时间:2018-12-13'),
      ],
    );
  • 整合
 //布局三整合
    Widget LayoutThreeColumn = Column(
      //主轴上设置居中
      mainAxisAlignment: MainAxisAlignment.center,
      //交叉轴(水平方向)设置从左开始
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        //第一行
        LayoutThreeOne,
        //第二行
        getPaddingfromLTRB(Text('Android Monitor使用介绍',
              style:getTextStyle(Colors.black, 18, false),
        ),t:10.0),
        //第三行
        getPaddingfromLTRB(LayoutThreeThree,t:10.0),
      ],

    );
 return new Scaffold(
        appBar: new AppBar(
          title: new Text('Flutter Demo'),
        ),
        body: Card(
              child: Container(
                //alignment: Alignment(0.0, 0.0),
                height: 160.0,
                color: Colors.white,
                padding: EdgeInsets.all(10.0),
                child:  Center(
                // 布局一
                // child: ColumnWidget,

                // 布局二
                // child:RowWidget,

                // 布局三
                   child:LayoutThreeColumn,
                )
              ),
          ),
      );
    }

3. 效果图

【Flutter -- 实例】案例二:加深布局的熟练度_第7张图片

布局四

1. 需求

上面实现了基本的布局,有了item后,那必须有ListView,这里简单模拟一下实现一下:

  • 添加 ListView
  • 下拉刷新
  • 上拉加载
  • ListView.separated
    return new Scaffold(
        appBar: new AppBar(
          title: new Text('Flutter Demo'),
        ),
            //ListView提供一个builder属性
            body: ListView.builder(
                //数目
                itemCount: 20,
                //itemBuilder是一个匿名回调函数,有两个参数,BuildContext 和迭代器index
                //和ListView的Item项类似 迭代器从0开始 每调用一次这个函数,迭代器就会加1
                itemBuilder: (BuildContext context,int index){
                  return Column(
                    children: <Widget>[
                      cardWidget,
                    ],
                  );

                }),

      );

2. 下拉刷新

  • 代码
    在 Flutter 已经提供和原生 Android 一样的刷新组件,叫做 RefreshIndicator ,是 MD 风格的,Flutter里面的 ScrollView 和子Widget都可以添加下拉刷新,只要在子Widget的上层包裹一层RefreshIndicator,先看看构造方法:
  const RefreshIndicator({
    Key key,
    @required this.child,
    this.displacement = 40.0,//下拉刷新的距离
    @required this.onRefresh,//下拉刷新回调方法
    this.color,              //进度指示器前景色 默认是系统主题色
    this.backgroundColor,    //背景色
    this.notificationPredicate = defaultScrollNotificationPredicate,
    this.semanticsLabel,     //小部件的标签
    this.semanticsValue,     //加载进度
  })

包裹住ListView,并且定义下拉刷新方法:

    return new Scaffold(
        appBar: new AppBar(
          title: new Text('Flutter Demo'),
        ),
        body: RefreshIndicator(
            //ListView提供一个builder属性
            child: ListView.builder(
                //数目
                itemCount: 20,
                //itemBuilder是一个匿名回调函数,有两个参数,BuildContext 和迭代器index
                //和ListView的Item项类似 迭代器从0开始 每调用一次这个函数,迭代器就会加1
                itemBuilder: (BuildContext context,int index){
                  return Column(
                    children: <Widget>[
                      cardWidget,
                    ],
                  );

                }),
            onRefresh: _onRefresh,),
      );
   //下拉刷新方法
  Future<Null> _onRefresh() async {
      //写逻辑
  }

可以看到上面定义刷新方法_onRefresh,这里先不加任何逻辑。把根Widget继承StatefulWidget,因为后面涉及到状态更新:

class HomeStateful extends StatefulWidget{
  @override
  State<StatefulWidget> createState(){
    return new HomeWidget();
  }

}

class HomeWidget extends State<HomeStateful> {
  //列表要显示的数据
  List list = new List();
  //是否正在加载 刷新
  bool isfresh = false;
  //这个方法只会调用一次,在这个Widget被创建之后,必须调用super.initState()
  @override
  void initState(){
    super.initState();
    //初始化数据
    initData();
  }

  //延迟3秒后刷新
  Future initData() async{
    await Future.delayed(Duration(seconds: 3),(){
      setState(() {
        //用生成器给所有元素赋初始值
        list = List.generate(20, (i){
          return i;
        });
      });
    });
  }
 }

一开始先创建并初始化长度是20的List集合,ListView根据这个集合长度来构建对应数目的Item项,上面代码是初始化3秒后才刷新数据,并加了标记isfresh是否加载刷新,Scafford代码如下:

   //ListView Item
    Widget _itemColumn(BuildContext context,int index){
      if(index <list.length){
        return Column(
          children: <Widget>[
            cardWidget,
          ],
        );

      }

    }
    return new Scaffold(
        appBar: new AppBar(
          title: new Text('Flutter Demo'),
        ),
        body: RefreshIndicator(
            //ListView提供一个builder属性
            child: ListView.builder(
                //集合数目
                itemCount: list.length,
                //itemBuilder是一个匿名回调函数,有两个参数,BuildContext 和迭代器index
                //和ListView的Item项类似 迭代器从0开始 每调用一次这个函数,迭代器就会加1
                itemBuilder: _itemColumn,
            ),
            onRefresh: _onRefresh,),
      );
    }

下面把下拉刷新方法逻辑简单加一下,我这边只是重新将集合清空,然后重新添加8条数据,只是为了看刷新效果:

      //下拉刷新方法
  Future<Null> _onRefresh() async {
      //写逻辑 延迟3秒后执行刷新
      //刷新把isfresh改为true
     isfresh = true;
     await Future.delayed(Duration(seconds: 3),(){
       setState(() {
         //数据清空再重新添加8条数据
         list.clear();
         list.addAll(List.generate(8, (i){
           return i;
         }));
       });
     });
  }

为了看到刷新效果,当刷新的时候,因为isfresh为true,收藏图标♥️改为红色,否则是黑色:

 //布局三开始第一行
    Widget LayoutThreeOne = Row(
       children: <Widget>[
         Expanded(
           child: Row(
             children: <Widget>[
               Text('作者:'),
               Text('HuYounger',
                  style: getTextStyle(Colors.redAccent[400], 14, false),
               ),
             ],
           )
         ),
         //收藏图标 改为以下
         getPaddingfromLTRB(Icon(Icons.favorite,color:isfresh ? Colors.red : Colors.black),r:10.0),
         //分享图标
         Icon(Icons.share,color:Colors.black),
       ],
    );
  • 效果图

3. 上拉加载

在 Flutter 中加载更多的组件没有是提供的,那就要自己来实现,我的思路是,当监听滑到底部时,到底底部就要做加载处理。而ListView 有 ScrollController 这个属性来控制 ListView 的滑动事件,在initState添加监听是否到达底部,并且添加上拉加载更多方法:

class HomeWidget extends State<HomeStateful> {

  //ListView控制器
  ScrollController _controller = ScrollController();
  //这个方法只会调用一次,在这个Widget被创建之后,必须调用super.initState()
  @override
  void initState(){
    super.initState();
    //初始化数据
    initData();
    //添加监听
    _controller.addListener((){
        //这里判断滑到底部第一个条件就可以了,加上不在刷新和不是上滑加载
        if(_controller.position.pixels == _controller.position.maxScrollExtent){
           //滑到底部了
           _onGetMoreData();
        }
    });
  }
 }
 
 //上拉加载更多方法 每次加8条数据
  Future _onGetMoreData() async{
     print('进入上拉加载方法');
     isfresh = false;
     if(list.length <=30){
       await Future.delayed(Duration(seconds: 2),(){
         setState(() {
           //加载数据
           //这里添加8项
             list.addAll(List.generate(8, (i){
               return i;
             }));

         });
       });

     }
  }
  
  //State删除对象时调用Dispose,这是永久性 移除监听 清理环境
  @override
  void dispose(){
    super.dispose();
    _controller.dispose();
  }

最后在ListView.builde下增加controller属性:

    return new Scaffold(
        appBar: new AppBar(
          title: new Text('Flutter Demo'),
        ),
        body: RefreshIndicator(
          onRefresh: _onRefresh,
            //ListView提供一个builder属性
            child: ListView.builder(
                ...
                itemBuilder: _itemColumn,
                //控制器 上拉加载
                controller: _controller,
            ),
            ),
      );

上面代码已经实现下拉加载更多,但是没有任何交互,我们知道,软件当上拉加载都会有提示,那下面增加一个加载更多的提示圆圈:

...
  //是否隐藏底部
  bool isBottomShow = false;
  //加载状态
  String statusShow = '加载中...';
...  
//上拉加载更多方法
  Future _onGetMoreData() async{
     print('进入上拉加载方法');
     isBottomShow = false;
     isfresh = false;
     if(list.length <=30){
       await Future.delayed(Duration(seconds: 2),(){
         setState(() {
           //加载数据
           //这里添加8项
             list.addAll(List.generate(8, (i){
               return i;
             }));
         });
       });
     }else{
       //假设已经没有数据了
       await Future.delayed(Duration(seconds: 3),(){
         setState(() {
           isBottomShow = true;
         });
       });


     }

//显示'加载更多',显示在界面上
  Widget _GetMoreDataWidget(){
     return Center(
       child: Padding(
         padding:EdgeInsets.all(12.0),
         // Offstage就是实现加载后加载提示圆圈是否消失
         child:new Offstage(
         // widget 根据isBottomShow这个值来决定显示还是隐藏
         offstage: isBottomShow,
           child:
           Row(
             mainAxisAlignment: MainAxisAlignment.center,
             crossAxisAlignment: CrossAxisAlignment.center,
             children: <Widget>[
               Text(
                   //根据状态来显示什么
                   statusShow,
                   style:TextStyle(
                     color: Colors.grey[300],
                     fontSize: 16.0,
                   )
               ),
               //加载圆圈
               CircularProgressIndicator(
                 strokeWidth: 2.0,
               )
             ],
           ),
         )

       ),
     );
  }

可以看到,上面用了OffstageWidget里的offstage属性来控制加载提示圆圈是否显示,isBottomShow如果是true,加载圆圈就会消失,false就会显示。并且statusShow来显示加载中的状态,然后要在集合长度加一,也就是给ListView添加尾部:

    return new Scaffold(
        appBar: new AppBar(
          title: new Text('Flutter Demo'),
        ),
        body: RefreshIndicator(
          onRefresh: _onRefresh,
            //ListView提供一个builder属性
            child: ListView.builder(
                //数目 加上尾部加载更多list就要加1了
                itemCount: list.length + 1,
                //itemBuilder是一个匿名回调函数,有两个参数,BuildContext 和迭代器index
                //和ListView的Item项类似 迭代器从0开始 每调用一次这个函数,迭代器就会加1
                itemBuilder: _itemColumn,
                //控制器
                controller: _controller,
            ),
            ),
      );
  • 效果图

4. ListView.separated

基本还可以,把上滑加载的提示圈加上去了,做到这里,我在想,有时候ListView并不是每一条Item养生都是一样的,哪有没有属性是设置在不同位置插入不同的Item呢?答案是有的,那就是ListView.separated,ListView.separated就是在Android中adapter不同类型的itemView。用法如下:

   body: new ListView.separated(
          //普通项
          itemBuilder: (BuildContext context, int index) {
            return new Text("text $index");
          },
          //插入项
          separatorBuilder: (BuildContext context, int index) {
            return new Container(height: 1.0, color: Colors.red);
          },
          //数目
          itemCount: 40),

自己例子实现一下:

//ListView item 布局二
    Widget cardWidget_two = Card(
      child: Container(
        //alignment: Alignment(0.0, 0.0),
          height: 160.0,
          color: Colors.white,
          padding: EdgeInsets.all(10.0),
          child: Center(
            // 布局一
            child: ColumnWidget,
          )
      ),
    );

    return new Scaffold(
        appBar: new AppBar(
          title: new Text('Flutter Demo'),
        ),
        body: RefreshIndicator(
          onRefresh: _onRefresh,
            //ListView提供一个builder属性
              child: ListView.separated(
                  itemBuilder: (BuildContext context,int index){
                    return  _itemColumn(context,index);

                  },
                  separatorBuilder: (BuildContext context,int index){
                    return Column(
                      children: <Widget>[
                        cardWidget_two
                      ],
                    );
                  },
                  itemCount: list.length + 1,
                  controller: _controller,
              ),
  • 效果图
    【Flutter -- 实例】案例二:加深布局的熟练度_第8张图片

你可能感兴趣的:(Flutter,--,实战,flutter,android)