【Flutter从入门到实战】⑪、豆瓣案例-1、星星评分Widget、虚线Widget、TabbarWidget、BottomNavigationBarItem的封装、初始化配置抽取

Flutter从入门到实战
一共分为23个系列
①(Flutter、Dart环境搭建篇) 共3个内容 已更新
②(Dart语法1 篇) 共4个内容 已更新
③(Dart语法2 篇) 共2个内容 已更新
④(Flutter案例开发篇) 共4个内容 已更新
⑤(Flutter的StatelessWidget 共3个内容 已更新
⑥(Flutter的基础Widget篇) 共2个内容 已更新
⑦(布局Widget篇) 共1个内容 已更新
⑧(Flex、Row、Column以及Flexible、Stack篇) 共1个内容 已更新
⑨(滚动的Widget篇) 共4个内容 已更新
⑩(Dart的Future和网络篇) 共3个内容 已更新
⑪(豆瓣案例-1篇) 共3个内容 已更新
⑫(豆瓣案例-2篇) 共3个内容 已更新

官方文档说明

官方视频教程
Flutter的YouTube视频教程-小部件

请添加图片描述


项目搭建前配置

由于我的工程项目用于学习操作
为了方便管理 我直接在项目里面创建豆瓣项目的案例
但是不能同时启动两个main.dart

项目配置其他启动的main.dart

请添加图片描述

①、Flutter 星星评分Widget

封装成一个Widget 考虑用的是
StatelessWidget还是StateFulWidget
考虑点

  1. 这个Widget是否可以控制的。比如点击改变状态
  2. 如果是能点击、改变状态的话 就封装成FulWidget
  3. 如果只是单纯做展示的话 就封装成lessWidget

考虑点
需要传递什么

  1. 评分的图片
  2. 评分的评分数 当前分数、最大分数
  3. 评分的数量
  4. 评分的尺寸大小
  5. 评分之间的间距
  6. 评分的颜色 未选中、已选中
  7. 评分的点击事件

需要用到什么系统的Widget
Stack (全部星星的包裹)
Row (每个星星的整体)
Icon (星星的图标)
Text (星星的评分数)
CustomClipper 需要用到系统的裁剪功能 比如 0.2星星的情况

Flutter 星星评分Widget 带注释的代码
// 1.星星评分Widget
class YHStarRating extends StatefulWidget {
  final double? rating; // 评分数
  final double maxRating; // 最大评分数

  final int count; // 评分总数量
  final double size; // 评分大小

  final Color unselectedColor; // 未选中颜色
  final Color selectedColor; // 选中的颜色

  final Widget unselectedImage; // 未选中图片
  final Widget selectedImage; // 选中图片

  YHStarRating({
    // 必传参数  this.rating 评分数
    @required this.rating,
    this.maxRating = 10,
    this.count = 5,
    this.size = 30,
    this.unselectedColor = const Color(0xffbbbbbb),
    this.selectedColor = const Color(0xffff0000),
    Widget? unselectedImage,
    Widget? selectedImage,

  }): this.unselectedImage  = unselectedImage ?? Icon(Icons.star_border,color: unselectedColor,size: size),
        this.selectedImage = selectedImage ?? Icon(Icons.star, color: selectedColor, size: size);// 判断有没有图片传递进行。如果没有使用默认的Icon创建的星星

  // const YHStarRating({Key? key}) : super(key: key);



  @override
  State<YHStarRating> createState() => _YHStarRatingState();
}

class _YHStarRatingState extends State<YHStarRating> {


  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        builderStarRow(buildUnselectedStar()),
        builderStarRow(buildSelectedStar()),
        // Row(
        //   // 由于内容是填充尽可能大 。所以需要设置内容为最小
        //   mainAxisSize: MainAxisSize.min,
        //   // 动态决定多少个星星 并且封装函数
        //   children:buildUnselectedStar(),
        // ),
        // Row(
        //   // 由于内容是填充尽可能大 。所以需要设置内容为最小
        //   mainAxisSize: MainAxisSize.min,
        //   children: buildSelectedStar(),
        // )
      ],
    );
  }


  // 封装函数 提高阅读代码
  // 默认星星
  List<Widget> buildUnselectedStar(){
    return List.generate(widget.count, (index) {
      return widget.unselectedImage;
      // return  Icon(Icons.star_border,color: widget.unselectedColor,size: widget.size);
    });
   }

  // 选中星星的函数
  List<Widget> buildSelectedStar() {
    // 案例
    // 比如一共是10分 那么 评分 8分 就是4个满星 1个空星
    // 比如一共是10分 那么 评分是 5 分 那么就是2个满星 一个半星

    // 先计算一个星是多少分
    // 总分数 / 总个数 = 每个星的分数
    // 10 / 5 = 每个星的分数

    // 1.创建stars
    List<Widget> stars = [];
    // final star = Icon(Icons.star, color: widget.selectedColor, size: widget.size);
    final star = widget.selectedImage;
    // 2. 构建满填充的 star
    double oneValue = widget.maxRating / widget.count;
    int entireCount = (widget.rating! / oneValue).floor(); // 使用向下取整 不管是4.9 都只能是4个
    for (var i = 0; i < entireCount; i++)
      {
        stars.add(star);
      }
    // 3. 构建部分填充的 star 进行裁剪(ClipRect)
    // 计算部分填充的值
    // 总星星个数 - 总整个填充的
    // 比如总评分数是3.5 - 3 = 0.5
    // (widget.rating! / oneValue) - entireCount
    // 然后计算占据的比例
    // ((widget.rating! / oneValue) - entireCount) *widget.size
    double leftWidth =  ((widget.rating! / oneValue) - entireCount) * widget.size;

    final halfStar =  ClipRect(
      clipper: YHStarClipper(leftWidth),
        child:star);
    stars.add(halfStar);

    // 满分超出范围的处理
    if(stars.length > widget.count)
      {
        return stars.sublist(0,widget.count);// 裁剪
      }
    return stars;
    }

  // Row的函数抽取
  Row builderStarRow(List<Widget> children){
    return Row(mainAxisSize: MainAxisSize.min,children:children);
  }
}

// 自定义裁剪
// 我们星星当做一个矩形来进行裁剪 所以需要在CustomClipper后面进行一个声明 Rect
class YHStarClipper extends CustomClipper<Rect>{
  double width;
    YHStarClipper(this.width);

 // 自定义裁剪 必须实现两个抽象方法
  @override
  Rect getClip(Size size) {
    // TODO: implement getClip
    // 裁剪的位置
    return Rect.fromLTRB(0, 0, width, size.height); // size.height 是Widget的高度
  }
  
  // 重新裁剪
  @override
  bool shouldReclip(covariant YHStarClipper oldClipper) {
    // TODO: implement shouldReclip
    // 当旧的宽度 和 当前的宽度不一致才需要重新裁剪
    return oldClipper.width != this.width;
  }
}

Flutter 星星评分Widget 不带注释的代码
// 1.星星评分Widget
class YHStarRating extends StatefulWidget {
  final double? rating; // 评分数
  final double maxRating; // 最大评分数
  final int count; // 评分总数量
  final double size; // 评分大小
  final Color unselectedColor; // 未选中颜色
  final Color selectedColor; // 选中的颜色
  final Widget unselectedImage; // 未选中图片
  final Widget selectedImage; // 选中图片

  YHStarRating({
    @required this.rating,
    this.maxRating = 10,
    this.count = 5,
    this.size = 30,
    this.unselectedColor = const Color(0xffbbbbbb),
    this.selectedColor = const Color(0xffff0000),
    Widget? unselectedImage,
    Widget? selectedImage,
  }): this.unselectedImage  = unselectedImage ?? Icon(Icons.star_border,color: unselectedColor,size: size),
        this.selectedImage = selectedImage ?? Icon(Icons.star, color: selectedColor, size: size);
  @override
  State<YHStarRating> createState() => _YHStarRatingState();
}

class _YHStarRatingState extends State<YHStarRating> {

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        builderStarRow(buildUnselectedStar()),
        builderStarRow(buildSelectedStar()),
      ],
    );
  }


  // 封装函数 提高阅读代码
  // 默认星星
  List<Widget> buildUnselectedStar(){
    return List.generate(widget.count, (index) {
      return widget.unselectedImage;
      // return  Icon(Icons.star_border,color: widget.unselectedColor,size: widget.size);
    });
   }

  // 选中星星的函数
  List<Widget> buildSelectedStar() {
    List<Widget> stars = [];
    final star = widget.selectedImage;
    double oneValue = widget.maxRating / widget.count;
    int entireCount = (widget.rating! / oneValue).floor(); 
    for (var i = 0; i < entireCount; i++)
      {
        stars.add(star);
      }
    double leftWidth =  ((widget.rating! / oneValue) - entireCount) * widget.size;

    final halfStar =  ClipRect(
      clipper: YHStarClipper(leftWidth),
        child:star);
    stars.add(halfStar);

    // 满分超出范围的处理
    if(stars.length > widget.count)
      {
        return stars.sublist(0,widget.count);
      }
    return stars;
    }

  Row builderStarRow(List<Widget> children){
    return Row(mainAxisSize: MainAxisSize.min,children:children);
  }
}

// 自定义裁剪
class YHStarClipper extends CustomClipper<Rect>{
  double width;
    YHStarClipper(this.width);

  @override
  Rect getClip(Size size) {
    return Rect.fromLTRB(0, 0, width, size.height);
  }
  
  @override
  bool shouldReclip(covariant YHStarClipper oldClipper) {
    return oldClipper.width != this.width;
  }
}

Flutter 星星评分Widget使用方式
class YHiOSHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("豆瓣"),
      ),
      body: Center(
       	// 评分数、最大评分数、星星个数
        child: YHStarRating(rating: 7,maxRating: 5, count: 5),
      ),
    );
  }
}
效果图 - Flutter 星星评分Widget 带注释的代码

请添加图片描述

②、Flutter 虚线Widget

考虑点

  1. 虚线的方向
  2. 虚线的方向的宽度
  3. 虚线的方向的高度
  4. 虚线的颜色

使用到系统的Widget

  1. 方向
  2. sabox 每一根虚线
class YHiOSHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("豆瓣"),
      ),
      body: Center(
        child:Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Container(
              width: 200,
              child: YHDasheLine(dashedWidth: 8,count: 10),
            ),
            Container(
              height: 200,
              child: YHDasheLine(axis: Axis.vertical, dashedHeight: 5,count: 15),
            ),
          ],
        ),

      ),
    );
  }
}



// 1.虚线
// 只负责展示 所以直接使用StatelessWidget 即可
class YHDasheLine extends StatelessWidget {
  // 1.提供的方向
  final Axis axis;
  final double dashedWidth; // 虚线的宽度
  final double dashedHeight; // 虚线的高度
  final int count; // 虚线的数量
  final Color color; // 虚线的颜色

  YHDasheLine({
    this.axis = Axis.horizontal,
    this.dashedWidth = 1,
    this.dashedHeight = 1,
    this.count = 10,
    this.color = Colors.red,
  });
  @override
  Widget build(BuildContext context) {
    return Flex(
        direction: axis,
        // 间距 使用 主轴对齐方式
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        // 用不上索引 使用 _ ti
        children: List.generate(count, ( _ ) {
          return SizedBox(
            width: dashedWidth,
            height: dashedHeight,
            // SizedBox没有 颜色属性 使用 Child 给SizedBox进行一个装饰
            child: DecoratedBox(
              decoration: BoxDecoration(color: color),
            ),
          );
        }),
    );
  }
}

效果图 Flutter 虚线Widget

请添加图片描述

③、Flutter TabbarWidget

考虑点
使用系统哪些Widget去实现

  1. Stack 能将内容进行包裹
  2. 使用 IndexedStack 进行包裹page页面
  3. 进行main、初始化配置、多个page页面进行抽取
    总结
    item的封装、页面和页面内容的封装、初始化列表的封装
    如果有网络请求、UI 建议多个文件夹管理(core,UI)

1.main.dart

import 'package:flutter/material.dart';
import 'package:learn_flutter/douban/pages/main/bottom_bar_item.dart';
// import 'package:learn_flutter/douban/pages/main/initialize_item.dart';// 绝对引入
import 'initialize_item.dart';// 相对引入 相当于在main.dart这个文件夹 引入initialize_item.dart  说明 initialize_item.dart和main.dart在同一个文件夹

class YHMainPage extends StatefulWidget {

  @override
  State<YHMainPage> createState() => _YHMainPageState();
}

class _YHMainPageState extends State<YHMainPage> {
  int _currentIndex = 0; // 记录当前tabbar点击的索引

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _currentIndex,
        children: config_pages
      ),
      bottomNavigationBar: BottomNavigationBar(
        // 点击选择默认字体会有一个放大和缩小
        // 因为系统设置 选中为 14 默认为 12
        selectedFontSize: 14,
        unselectedFontSize: 14,
        // selectedItemColor: Colors.red,
        // unselectedItemColor: Colors.blue,
        // item点击 记录切换
        currentIndex: _currentIndex,
        // 当item 超过4个就需要设置type
        type: BottomNavigationBarType.fixed,
        items: config_items,
        onTap: (index){
          setState(() {
            _currentIndex = index;
          });
        },
      ),
    );
  }

//1.BottomNavigationBarItem 封装函数
  BottomNavigationBarItem buildBottomItem(iconName,title){
    return BottomNavigationBarItem(
        label: title,
        icon: Image.asset("assets/images/tabbar/$iconName.png",width: 32),
        activeIcon: Image.asset("assets/images/tabbar/${iconName}_active.png",width: 32)
    );
  }
}

// 2.BottomNavigationBarItem 封装类


2. BottomNavigationBarItem 封装

import 'package:flutter/material.dart';

class YHBottomBarItem extends BottomNavigationBarItem {
  YHBottomBarItem(String iconName,String title):super(
    label: title,
    icon: Image.asset("assets/images/tabbar/$iconName.png",width: 32),
    activeIcon:  Image.asset("assets/images/tabbar/${iconName}_active.png",width: 32),
  );
}

3. 多Page页面封装

3.1 home.dart
import 'package:flutter/material.dart';
import 'home_content.dart';

class YHHomePage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("首页"),
      ),
      body: YHHomeContent(),
    );
  }
}


3.2 home_content.dart
import 'package:flutter/material.dart';

class YHHomeContent extends StatefulWidget {

  @override
  State<YHHomeContent> createState() => _YHHomeContentState();
}

class _YHHomeContentState extends State<YHHomeContent> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text("首页内容",style: TextStyle(fontSize: 30,color: Colors.red)),
    );
  }
}

3.3 subject.dart
import 'package:flutter/material.dart';
import 'subject_content.dart';

class YHSubjectPage extends StatelessWidget {

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text("首页"),
      ),
      body: YHSubjectContent(),
    );
  }
}


3.4 subject_content.dart
import 'package:flutter/material.dart';

class YHSubjectContent extends StatefulWidget {

  @override
  State<YHSubjectContent> createState() => _YHHomeContentState();
}

class _YHHomeContentState extends State<YHSubjectContent> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text("书影音内容",style: TextStyle(fontSize: 30,color: Colors.red)),
    );
  }
}

4.初始化配置

import 'package:flutter/cupertino.dart';
import 'package:learn_flutter/douban/pages/home/home.dart';
import '../subject/subject.dart';
import 'bottom_bar_item.dart';

// 初始化抽取

// 1. tabbar item
List<YHBottomBarItem> config_items = [
  YHBottomBarItem("home", "首页"),
  YHBottomBarItem("subject", "书影音"),
  YHBottomBarItem("group", "小组"),
  YHBottomBarItem("mall", "市集"),
  YHBottomBarItem("profile", "我的"),
];

// 1. 每个tabbar的页面
List<Widget> config_pages = [
  YHHomePage(),
  YHSubjectPage(),
  YHHomePage(),
  YHSubjectPage(),
  YHHomePage(),

];

效果图 - Tabbar

请添加图片描述

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

宇夜iOS

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值