flutter 四布局开发

前言

文本Text /按钮icon等这些基础的用的时候再看就行,主打一个快速入门。
点击右上角运行,但有时候动态更新不行,需要点红色框停止再运行一次。

一、轮播图示例

  1. 创建banner_demo.dart ,文件名要用小写哦
    输入stless生成快捷模版,在爆红的地方用alt+enter快捷键import相应的库。
import 'package:flutter/material.dart';
class BannerDemoApp extends StatelessWidget {
  const BannerDemoApp({super.key});
  @override
  Widget build(BuildContext context) {
    return const Placeholder();
  }
}
  1. 搜索轮播图的实现用的pageview控件。大多数 App 都包含 Tab 换页效果、图片轮动以及抖音上下滑页切换视频功能等等,这些都可以通过 PageView 轻松实现。
 @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Container(
        color: Colors.blue,
        child: PageView.builder(itemBuilder: (_, index) {
          return Center(
              child: Text(
                "当前位置 $index",
                style: const TextStyle(
                    color: Colors.black, fontSize: 23, fontFamily: 'PingFang SC', decoration: TextDecoration.none),
              ));
        },itemCount: 10),//itemCount如果不写,会不断地有新页面。
      ),
    );
  }

可以使用快捷键command+鼠标进入想看的源码的介绍。

  1. 另一种写法
 @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: _BannerPage(),
    );
  }

class _BannerPage extends StatelessWidget {
  const _BannerPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: PageView.builder(itemBuilder: (_, index) {
        return Center(
            child: Text(
          "当前位置 $index",
          style: const TextStyle(
              color: Colors.black, fontSize: 23, fontFamily: 'PingFang SC', decoration: TextDecoration.none),
        ));
      },itemCount: 10),
    );
  }
}
  1. 将布局从外部传入
class BannerDemoApp extends StatelessWidget {
  const BannerDemoApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: _BannerPage(children: [
        Center(
            child: Text(
          "11111",
          style: TextStyle(color: Colors.red, fontSize: 23, fontFamily: 'PingFang SC', decoration: TextDecoration.none),
        )),
        Center(
            child: Text(
          "2222",
          style: TextStyle(
              color: Colors.yellowAccent, fontSize: 23, fontFamily: 'PingFang SC', decoration: TextDecoration.none),
        )),
      ]),
    );
  }
}

class _BannerPage extends StatelessWidget {
  final List children;
  const _BannerPage({super.key, required this.children});

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: PageView.builder(
          itemBuilder: (_, index) {
            return children[index];
          },
          itemCount: children.length),
    );
  }
}
  1. 如果需要一个计时器自动轮播呢?这个时候StatelessWidget就不能满足需求了,需要用StatefulWidget .
    输入stful ,生成模版
class AutoBanner extends StatefulWidget {
  const AutoBanner({super.key});
  @override
  State<AutoBanner> createState() => _AutoBannerState();
}
class _AutoBannerState extends State<AutoBanner> {
final List<Widget> children = [
    const Center(
        child: Text(
          "11111",
          style: TextStyle(color: Colors.red, fontSize: 23, fontFamily: 'PingFang SC', decoration: TextDecoration.none),
        )),
    const Center(
        child: Text(
          "2222",
          style: TextStyle(
              color: Colors.yellowAccent, fontSize: 23, fontFamily: 'PingFang SC', decoration: TextDecoration.none),
        )),
  ];
  @override
  Widget build(BuildContext context) {
    return MaterialApp(home:Container(color: Colors.blue,
    child:  PageView.builder(
      itemCount: children.length,//可有可无
      itemBuilder: (_, index) {
        return children[index];
      }
    )));
  }
}
  1. 在_AutoBannerState类中重写initState方法进行数据的初始化等操作
  late Timer _timer;
  int _currentPage = 0;
  late PageController _pageController;

 @override
  void initState() {
    super.initState();
    _pageController = PageController(keepPage: true)
      ..addListener(() {
        _currentPage = _pageController.page?.round() ?? 0;
      });

    _timer = Timer.periodic(Duration(seconds: 3), (timer) {
      _currentPage++;
      _pageController.animateToPage(
        _currentPage,
        duration: Duration(milliseconds: 300),
        curve: Curves.easeIn,
      );
    });
  }
  1. 对pageView进行更改,变为可循环滚动,且将control赋值给pageView

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Container(
            color: Colors.blue,
            child: PageView.builder(
                controller: _pageController,
                itemBuilder: (_, index) {
                  return children[index % children.length];//改为可循环滚动
                })));
  }
  1. 在dispose中销毁监听
  @override
  void dispose() {
    _timer.cancel();
    _pageController.dispose();
  }
  1. 在用户触摸pageview时不自动滚动,在外层加入Listener监听手势
 @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Container(
            color: Colors.blue,
            child: Listener(
                onPointerDown: (event) {
                  //手指按下,定时取消
                  _timer.cancel();
                },
                onPointerMove: (event) {},
                onPointerUp: (event) {
                  //手指抬起,定时开启
                  startAutoScroll();
                },
                child: PageView.builder(
                    controller: _pageController,
                    itemBuilder: (_, index) {
                      return children[index % widget.children.length]; //改为可循环滚动
                    }))));
  }

小提示:手势的监听还有GestureDetector

区别:如果需要监听和处理低级别的原始指针事件数据,或者需要识别自定义手势,那么使用Listener更合适。
如果你只需要响应一些常见的手势事件,如点击、双击、拖动等,使用GestureDetector会更加方便。

这里用Listener更方便。

  1. 实现staartAutoScroll ,将timer的赋值提出来即可
  void startAutoScroll() {
    _timer = Timer.periodic(Duration(seconds: 3), (timer) {
      _currentPage++;
      _pageController.animateToPage(
        _currentPage,
        duration: Duration(milliseconds: 300),
        curve: Curves.easeIn,
      );
    });
  }
  1. 如果用户看不到页面希望可以暂停滚动。这个没有找到合适的类似安卓的onpause的方法,如果有知道的,欢迎评论哈。

  2. WidgetsBindingObserver判断应用是否处于前后台。处于后台暂停滚动。

class _AutoBannerState extends State<AutoBanner> with WidgetsBindingObserver{
  late Timer _timer;
  int _currentPage = 0;
  late PageController _pageController;

  final List<Widget> children = [...  ];

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _pageController = PageController(keepPage: true)
      ..addListener(() {
        _currentPage = _pageController.page?.round() ?? 0;
      });
    startAutoScroll();
  }

  void startAutoScroll() {
    _timer = Timer.periodic(Duration(seconds: 3), (timer) {
      _currentPage++;
      _pageController.animateToPage(
        _currentPage,
        duration: Duration(milliseconds: 300),
        curve: Curves.easeIn,
      );
    });
  }


  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    super.didChangeAppLifecycleState(state);
    if (state == AppLifecycleState.resumed) {
      startAutoScroll();
    } else if (state == AppLifecycleState.paused) {
      _timer.cancel();
    }
  }

  @override
  void dispose() {
    _timer.cancel();
    _pageController.dispose();
    WidgetsBinding.instance.removeObserver(this);
  }

  @override
  Widget build(BuildContext context) {
    return ....
  }
}

暂时告一段落了,下面是setState用法,和上面轮播图没关系。

setState用法,点击按钮变颜色

bool white = false;

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        home: Center(
            child: GestureDetector(
    
          child: Container(
              height: 48,
              margin: const EdgeInsets.all(12),
              decoration: white
                  ? BoxDecoration(color: Colors.orange, borderRadius: BorderRadius.circular(18))
                  : BoxDecoration(color: Colors.blue, borderRadius: BorderRadius.circular(18)),
              child: Center(
                child: Text("点击这里改变背景色",
                    style: TextStyle(fontSize: 15, color: Colors.white, decoration: TextDecoration.none)),
              )),
          onTap: () {
            setState(() {
              white = !white;
            });
          },
        )));
  }

二、提取子布局

选中一个子布局,将其作为子widget,也就是子view


class ChildWidget extends StatelessWidget {
  const ChildWidget({
    super.key,
  });

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 48,
      child: Center(
          child: customText(16, "文案",
              align: TextAlign.center, weight: FontWeight.w600)),
    );
  }
}

父View中

child: ChildWidget()

那么如果需要点击ChildWidget,更改child中的字体大小怎么做?
和android中开发一样,使用方法回调。在子view中声明变量

  final ValueChanged<int> onChanged;
//也等于
 final void Function(int) onChanged;

子view中

class ChildWidget extends StatelessWidget {
  final void Function(double) onChanged;
  final double textSize;

  const ChildWidget({
    super.key,
    required this.textSize,
    required this.onChanged,
  });

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
        onTap: () {
          onChanged.call(38);
        },
        child: Container(
          height: 48,
          child: Center(child: customText(textSize, "文案", align: TextAlign.center, weight: FontWeight.w600)),
        ));
  }
}

父view中写一个_newValue全局变量。一定要记得setState,不然无法刷新的。

ChildWidget(
          onChanged: (value) {
            setState(() {
              _newValue = value;
            });
          }

三、一些渐变色

  1. 文字改为渐变色

Widget gradientText(double fontSize, String text, List<Color> colors,
    {FontWeight weight = FontWeight.bold, TextAlign align = TextAlign.left}) {
  return ShaderMask(
      shaderCallback: (bounds) {
        return LinearGradient(begin: Alignment.topLeft, end: Alignment.bottomRight, colors: colors)
            .createShader(bounds);
      },
      blendMode: BlendMode.srcATop,
      child: Text(text, style: TextStyle(color: Colors.white, fontSize: fontSize)));
}
  1. 背景渐变色
BoxDecoration gradientBackground(List<Color> colors, BorderRadius? radius) {
  return BoxDecoration(
    gradient: LinearGradient(
      colors: colors,
    ),
    borderRadius: radius,
  );
}

四、高斯模糊

Scaffold(
        appBar: AppBar(title: const Text('高斯模糊示例')),
        body: Container(
            width: 400,
            //只对其子Widget 起作用
            child: ImageFiltered(
                imageFilter: ImageFilter.blur(sigmaX: 2, sigmaY: 2),
                child: Image.network(
                  'http://pic1.win4000.com/wallpaper/9/594cc06f555e8.jpg',
                ))));

五、简便写法

GestureDetector(
   behavior: HitTestBehavior.opaque,//空白区域可点击
                onTap: controller.swipe,
                child: Container(
                  height: 48,
                  width: 200,
                  alignment: Alignment.center,
                  decoration: BoxDecoration(borderRadius: BorderRadius.circular(24.0), color: Colors.red),
                  child: Text("按钮"),
                )))
 swipe() {

  }

六、文字超出一行...

Text(
  'This is a long text that will be truncated',
  overflow: TextOverflow.ellipsis,
)

七、马赛克(毛玻璃)

  ClipRRect(
                                      borderRadius: BorderRadius.circular(8.0),
                                      child: Stack(
                                        fit : StackFit.expand,
                                        alignment: Alignment.center,
                                        children: [
                                          img,
                                          Container(
                                            alignment: Alignment.center,
                                            clipBehavior: Clip.hardEdge,
                                            decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)),
                                            child: BackdropFilter(
                                              filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
                                              child: Container(color: Colors.white.withAlpha(0)),
                                            ),
                                          ),
                                        ],
                                      ),
                                    ),

优化参考:https://www.jianshu.com/p/06b718fde8c4
八、左文字...右紧挨图片

Flexible(
                                        fit: FlexFit.loose,
                                        child:Text(
                                          controller.viewmodel.model.listObs[index].title ?? "",
                                          style: Get.theme.textTheme.titleMedium,
                                          maxLines: 1,
                                          overflow: TextOverflow.ellipsis,
                                        ))

九、带白圈的圆形图片

 SizedBox(
              width: 90,
              height: 90,
              child: Container(
                decoration: BoxDecoration(
                  border: Border.all(
                    width: 2,
                    color: Colors.white,
                  ),
                  shape: BoxShape.circle,
                ),
                child: ClipOval(
                  child: cachedImage(
                      imageUrl: avatar, fit: BoxFit.cover),
                ),
              )),

十、图片左右重叠

Stack(
      alignment: Alignment.center, // 对齐方式
      children: [
        Positioned(
          left: 20, // 调整左侧图片的位置
          child: ClipOval(
            child: Image.network(
              'https://example.com/image1.jpg', // 替换为你的第一张图片URL
              width: 150,
              height: 150,
              fit: BoxFit.cover,
            ),
          ),
        ),
        Positioned(
          right: 20, // 调整右侧图片的位置
          child: ClipOval(
            child: Image.network(
              'https://example.com/image2.jpg', // 替换为你的第二张图片URL
              width: 150,
              height: 150,
              fit: BoxFit.cover,
            ),
          ),
        ),
      ],
    )

十一、加分割线的listview

 ListView.separated(
      itemCount: 20, // 设置 item 的数量
      separatorBuilder: (context, index) => Divider(
        thickness: 1.0,
        color: Colors.grey,
      ),
      itemBuilder: (context, index) {
        return Container(
          height: 52, // 设置每个 item 的高度
          alignment: Alignment.center, // 垂直和水平居中
          child: Text(
            'Item $index', // 显示文本
            style: TextStyle(fontSize: 16.0),
          ),
        );
      },
    )

十二、gridview

GridView.builder(
                 padding: const EdgeInsets.fromLTRB(24, 0, 24, 0),
                 gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                   crossAxisCount: 5, // Number of columns
                   crossAxisSpacing: 12, // Spacing between columns
                   mainAxisSpacing: 12,
                   mainAxisExtent: ((MediaQuery.of(context).size.width - 100
                 )
...
)

十三、pageview禁止左右滑

      physics: NeverScrollableScrollPhysics(),

后记

开发过程,看run命令栏,排查问题


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容