当前位置: 首页 > news >正文

Flutter 实现按位置大小比例布局的控件

文章目录

  • 前言
  • 一、如何实现?
    • 1、数值转成分数
    • 2、Row+Flexible布局横向
    • 3、Column+Flexible布局纵向
  • 二、完整代码
  • 三、使用示例
    • 1、基本用法
    • 2、四分屏
    • 3、六分屏
    • 4、八分屏
    • 5、九分屏
    • 6、414分屏
  • 总结


前言

做视频监控项目时需要需要展示多分屏,比如2x2、3x3、414等等,如果每一种分屏都单独实现会很麻烦,而且不能支持用户定制。最好的方式还是实现一个通用的分屏容器,而且采样比例计算位置大小,可以适配任意尺寸。


一、如何实现?

最直观的实现方式是获取控件宽高然后按比例计算,但是flutter在build的时候无法获取位置宽高信息,只有绘制之后才能获取,所以这种方式并不容易实现,比较简单的方式应该是使用Row、Column结合Flexible。

1、数值转成分数

需要转换的数值

 final Rect rect; //子控件位置大小,比例值范围0-1

定义一个分数对象

//分数
class Rational {int den = 1; //分母int num = 0; //分子Rational(this.num, this.den);//通过double构造,accuracy小数点后精度factory Rational.fromDouble(double d, {int accuracy = 5}) {int den = 1;while (d > d.toInt() && accuracy-- > 0) {d *= 10;den *= 10;}return Rational(d.toInt(), den);}
}

转成分数并对齐分母

    //将位置大小转成分数final width = Rational.fromDouble(rect.width);final x = Rational.fromDouble(rect.left);final height = Rational.fromDouble(rect.height);final y = Rational.fromDouble(rect.top);//对齐分母if (width.den != x.den) {final den = width.den;width.den *= x.den;width.num *= x.den;x.den *= den;x.num *= den;}//对齐分母if (height.den != y.den) {final den = height.den;height.den *= y.den;height.num *= y.den;y.den *= den;y.num *= den;}

2、Row+Flexible布局横向

我们利用Row的自动布局,以及Flexible的比例布局的特性,根据上面的分数计算出控件比例的位置大小对应的flex值即可。

 Row(children: [Flexible(flex: x.num,child: Container(),),Flexible(flex: width.num,child: child/*子控件,加上纵向布局则是Column*/),Flexible(flex: width.den - width.num - x.num, child: Container()),],);}

3、Column+Flexible布局纵向

我们利用Column的自动布局,以及Flexible的比例布局的特性,根据上面的分数计算出控件比例的位置大小对应的flex值即可。

Column(children: [Flexible(flex: y.num,child: Container(),),Flexible(flex: height.num, child: child/*子控件*/),Flexible(flex: height.den - height.num - y.num,child: Container(),),],)

二、完整代码

proportion.dart

import 'package:flutter/material.dart';//比例布局控件,
class Proportion extends StatelessWidget {final Rect rect; //位置大小,比例值范围0-1final Widget child;const Proportion({super.key,this.rect = const Rect.fromLTWH(0, 0, 1, 1),required this.child,});Widget build(BuildContext context) {//实现按比例显示布局final width = Rational.fromDouble(rect.width);final x = Rational.fromDouble(rect.left);final height = Rational.fromDouble(rect.height);final y = Rational.fromDouble(rect.top);if (width.den != x.den) {final den = width.den;width.den *= x.den;width.num *= x.den;x.den *= den;x.num *= den;}if (height.den != y.den) {final den = height.den;height.den *= y.den;height.num *= y.den;y.den *= den;y.num *= den;}return Row(children: [Flexible(flex: x.num,child: Container(),),Flexible(flex: width.num,child: Column(children: [Flexible(flex: y.num,child: Container(),),Flexible(flex: height.num, child: child),Flexible(flex: height.den - height.num - y.num,child: Container(),),],),),Flexible(flex: width.den - width.num - x.num, child: Container()),],);}
}//分数
class Rational {int den = 1; //分母int num = 0; //分子Rational(this.num, this.den);//通过double构造,accuracy小数点后精度factory Rational.fromDouble(double d, {int accuracy = 5}) {int den = 1;while (d > d.toInt() && accuracy-- > 0) {d *= 10;den *= 10;}return Rational(d.toInt(), den);}
}

常用布局(可选)
proportions.dart

import 'package:flutter/material.dart';import 'proportion.dart';//常用布局,需配合stack作为父容器使用
class Proportions {Proportions._();//全屏static List<Proportion> fullScreen({required Widget child,}) =>[Proportion(rect: const Rect.fromLTWH(0, 0, 1, 1),child: child,)];//二分屏static List<Proportion> halfScreen({required Widget left,required Widget right,}) =>[Proportion(rect: const Rect.fromLTWH(0, 0, 0.5, 1),child: left,),Proportion(rect: const Rect.fromLTWH(0.5, 0, 0.5, 1),child: right,),];//四分屏static List<Proportion> quadScreen({required List<Widget> children,}) {return [Proportion(rect: const Rect.fromLTWH(0, 0, 0.5, 0.5),child: children[0],), //左上Proportion(rect: const Rect.fromLTWH(0.5, 0, 0.5, 0.5),child: children[1],), //右上Proportion(rect: const Rect.fromLTWH(0, 0.5, 0.5, 0.5),child: children[2],), //左下Proportion(rect: const Rect.fromLTWH(0.5, 0.5, 0.5, 0.5),child: children[3],), //右下];}//6  分屏static List<Proportion> sixScreen({required List<Widget> children,}) {return [Proportion(rect: const Rect.fromLTWH(0, 0, 0.666, 0.666),child: children[0],), //左上Proportion(rect: const Rect.fromLTWH(0.666, 0, 0.333, 0.333),child: children[1],), //右上Proportion(rect: const Rect.fromLTWH(0.666, 0.333, 0.333, 0.333),child: children[2],), //右中Proportion(rect: const Rect.fromLTWH(0.666, 0.666, 0.333, 0.333),child: children[3],), //右下Proportion(rect: const Rect.fromLTWH(0.333, 0.666, 0.333, 0.333),child: children[4],), //中下Proportion(rect: const Rect.fromLTWH(0, 0.666, 0.333, 0.333),child: children[5],), //左下];}//8  分屏static List<Proportion> eightScreen({required List<Widget> children,}) {return [Proportion(rect: const Rect.fromLTWH(0, 0, 0.75, 0.75),child: children[0],), //左上Proportion(rect: const Rect.fromLTWH(0.75, 0, 0.25, 0.25),child: children[1],), //右上Proportion(rect: const Rect.fromLTWH(0.75, 0.25, 0.25, 0.25),child: children[2],), //右中1Proportion(rect: const Rect.fromLTWH(0.75, 0.5, 0.25, 0.25),child: children[3],), //右中2Proportion(rect: const Rect.fromLTWH(0.75, 0.75, 0.25, 0.25),child: children[4],), //右下Proportion(rect: const Rect.fromLTWH(0.5, 0.75, 0.25, 0.25),child: children[5],), //中下2Proportion(rect: const Rect.fromLTWH(0.25, 0.75, 0.25, 0.25),child: children[6],), //中下1Proportion(rect: const Rect.fromLTWH(0, 0.75, 0.25, 0.25),child: children[7],), //左下];}//9  分屏static List<Proportion> nightScreen({required List<Widget> children,}) {int n = 0;return [...children.getRange(0, 9).map((element) {final i = n++;return Proportion(rect: Rect.fromLTWH((i % 3) * 0.333,(i ~/ 3) * 0.333,0.333,0.333,),child: element,);},)];}//16  分屏static List<Proportion> sixteenScreen({required List<Widget> children,}) {int n = 0;return [...children.getRange(0, 16).map((element) {final i = n++;return Proportion(rect: Rect.fromLTWH((i % 4) * 0.25, (i ~/ 4) * 0.25, 0.25, 0.25),child: element,);},)];}//414分屏static List<Proportion> fourOneFourScreen({required List<Widget> children,}) {int n = 0;return [//左4...children.getRange(0, 4).map((element) {final i = n++;return Proportion(rect: Rect.fromLTWH((i ~/ 4) * 0.25, (i % 4) * 0.25, 0.25, 0.25),child: element,);},),//中间Proportion(rect: const Rect.fromLTWH(0.25, 0, 0.5, 1),child: children[4],),//右边4...children.getRange(5, 9).map((element) {final i = n++ + 8;return Proportion(rect: Rect.fromLTWH((i ~/ 4) * 0.25, (i % 4) * 0.25, 0.25, 0.25),child: element,);},)];}
}

三、使用示例

1、基本用法

设置子控件位置大小。一般配合stack作为父容器使用

    Proportion(rect: Rect.fromLTRB(0, 0, 0.5, 0.5), //子控件位置大小,(0, 0, 0.5, 0.5)表示左上1/4的区域child: ColoredBox(color: Colors.red), //子控件);

2、四分屏

final List<int> _nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];
 Stack(children: Proportions.quadScreen(children: [..._nums.map((e) => Container(constraints: const BoxConstraints.expand(),decoration: BoxDecoration(border: Border.all(color: Colors.deepPurple.shade300)),child: Center(child: Text("video $e")),))

在这里插入图片描述

3、六分屏

final List<int> _nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];
 Stack(children: Proportions.sixScreen(children: [..._nums.map((e) => Container(constraints: const BoxConstraints.expand(),decoration: BoxDecoration(border: Border.all(color: Colors.deepPurple.shade300)),child: Center(child: Text("video $e")),))

在这里插入图片描述

4、八分屏

final List<int> _nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];
 Stack(children: Proportions.eightScreen(children: [..._nums.map((e) => Container(constraints: const BoxConstraints.expand(),decoration: BoxDecoration(border: Border.all(color: Colors.deepPurple.shade300)),child: Center(child: Text("video $e")),))

在这里插入图片描述

5、九分屏

final List<int> _nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];
 Stack(children: Proportions.nightScreen(children: [..._nums.map((e) => Container(constraints: const BoxConstraints.expand(),decoration: BoxDecoration(border: Border.all(color: Colors.deepPurple.shade300)),child: Center(child: Text("video $e")),))

在这里插入图片描述

6、414分屏

final List<int> _nums = [1, 2, 3, 4, 5, 6, 7, 8, 9];
 Stack(children: Proportions.fourOneFourScreen(children: [..._nums.map((e) => Container(constraints: const BoxConstraints.expand(),decoration: BoxDecoration(border: Border.all(color: Colors.deepPurple.shade300)),child: Center(child: Text("video $e")),))

在这里插入图片描述
始终保持比例
在这里插入图片描述


总结

以上就是今天要讲的内容,本文用的是比较简单的方式实现了比例布局控件,其主要特点是可以灵活使用,尤其是方便视频分屏预览的实现。本质上也是对一类布局规则的总结得出的一个通用的控件,因为考虑到2x2、3x3还是可以写死的,但是到了4x4、5x5写死则需要16、25个参数,那就必须改用数组,也就意味着需要根据规则计算位置,那和本文一样了。所以本文的控件是有实际使用意义的。

http://www.lryc.cn/news/115329.html

相关文章:

  • ES6 - 对象新增的一些常用方法
  • 半导体存储电路
  • web前端之CSS操作
  • Python SQLAlchemy ( ORM )
  • 鉴源实验室丨汽车网络安全运营
  • 分布式链路追踪之SkyWalking详解和实战
  • 【工程实践】使用EDA(Easy Data Augmentation)做数据增强
  • ClickHouse(十三):Clickhouse MergeTree系列表引擎 - ReplicingMergeTree
  • 机器学习笔记之优化算法(十)梯度下降法铺垫:总体介绍
  • Selenium 根据元素文本内容定位
  • 第17章-Spring AOP经典应用场景
  • Leetcode周赛 | 2023-8-6
  • ts中interface自定义结构约束和对类的约束
  • Oracle单实例升级补丁
  • 力扣初级算法(二分查找)
  • 探索未来:直播实时美颜SDK在增强现实(AR)直播中的前景
  • SQL 单行子查询 、多行子查询、单行函数、聚合函数 IN 、ANY 、SOME 、ALL
  • 【第一阶段】kotlin的range表达式
  • 网络防御(5)
  • gradle 命令行单元测试执行问题
  • 剑指Offer12.矩阵中的路径 C++
  • 金鸣识别将无表格线的图片转为excel的几个常用方案
  • 刚刚更新win11,记事本消失怎么处理?你需要注意些什么?
  • 【QT】 QTabWidgetQTabBar控件样式设计(QSS)
  • 【个人记录】CentOS7 编译安装最新版本Git
  • 【Linux】计算机网络的背景和协议分层
  • 代理模式:静态代理+JDK/CGLIB 动态代理
  • gps虚拟定位 AnyGo for Mac 中文
  • LLM reasoners 入门实验 24点游戏
  • 【LeetCode 算法】Maximum Absolute Sum of Any Subarray 任意子数组和的绝对值的最大值-前缀和