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

news2024/9/23 11:14:34

文章目录

  • 前言
  • 一、如何实现?
    • 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-1
  final 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],
      ), //右中1
      Proportion(
        rect: const Rect.fromLTWH(0.75, 0.5, 0.25, 0.25),
        child: children[3],
      ), //右中2
      Proportion(
        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],
      ), //中下2
      Proportion(
        rect: const Rect.fromLTWH(0.25, 0.75, 0.25, 0.25),
        child: children[6],
      ), //中下1
      Proportion(
        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.coloradmin.cn/o/839671.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

vue2-vue中key的原理

vue中key是什么&#xff1f;它有什么作用&#xff1f;原理是什么&#xff1f; 1、key是什么&#xff1f; 先考虑两个实际场景 当我们使用v-for时&#xff0c;需要给单元加上key 用new Date()生成的时间戳作为key&#xff0c;手动强制触发重新渲染。 在上面两种场景中&#xf…

一百四十三、Linux——Linux的CentOS 7系统语言由中文改成英文

一、目的 之前安装CentOS 7系统的时候把语言设置成中文&#xff0c;结果Linux文件夹命名出现中文乱码的问题&#xff0c;于是决定把Linux系统语言由中文改成英文 二、实施步骤 &#xff08;一&#xff09;到etc目录下&#xff0c;找到配置文件locale.conf # cd /etc/ # ls…

总结七大排序!

排序总览 外部排序&#xff1a;依赖硬盘&#xff08;外部存储器&#xff09;进行的排序。对于数据集合的要求特别高&#xff0c;只能在特定场合下使用&#xff08;比如一个省的高考成绩排序&#xff09;。包括桶排序&#xff0c;基数排序&#xff0c;计数排序&#xff0c;都是o…

C++ STL快速应用

STL 容器 STL容器有共同的操作接口&#xff0c;包括初始化操作、判空、查看大小、比较元素、销毁、交换&#xff0c;这些操作都是一样的接口。 对于访问遍历元素&#xff08;增删改查&#xff09;&#xff0c;都可以使用迭代器&#xff08;正向&#xff09;进行操作&#xff0c…

信号平滑或移动平均滤波研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

【雕爷学编程】Arduino动手做(184)---快餐盒盖,极低成本搭建机器人实验平台2

吃完快餐粥&#xff0c;除了粥的味道不错之外&#xff0c;我对个快餐盒的圆盖子产生了兴趣&#xff0c;能否做个极低成本的简易机器人呢&#xff1f;也许只需要二十元左右 知识点&#xff1a;轮子&#xff08;wheel&#xff09; 中国词语。是用不同材料制成的圆形滚动物体。简…

JDK17环境下安装Nacos

1.配置好jdk17环境 命令台java -version显示17版本 2.下载并安装Nacos 下载地址&#xff1a;Releases alibaba/nacos GitHub 安装完本地解压 解压完到nacos的bin目录下&#xff0c;执行.\startup.cmd -m standalone启动即可。 用过好几种方式&#xff0c;比如启动startup…

一百四十五、Kettle——查看Kettle在Windows本地和在Linux上生成的.kettle文件夹位置

&#xff08;一&#xff09;目的 查看kettle连数据库后自动生成的.kettle文件夹在Windows本地和在Linux中的位置&#xff0c; 这个文件很重要&#xff01;&#xff01;&#xff01; &#xff08;二&#xff09;.kettle文件夹在Windows本地的位置 C:\Users\Administrator\.k…

ClickHouse SQL与引擎--基本使用(一)

1.查看所有的数据库 show databases; 2.创建库 CREATE DATABASE zabbix ENGINE Ordinary; ATTACH DATABASE ck_test ENGINE Ordinary;3.创建本地表 CREATE TABLE IF NOT EXISTS test01(id UInt64,name String,time UInt64,age UInt8,flag UInt8 ) ENGINE MergeTree PARTI…

英特尔傲腾CAS报错unknown error cache acceleration software could not start cache

英特尔傲腾CAS报错unknown error cache acceleration software could not start cache 文章目录 英特尔傲腾CAS报错unknown error cache acceleration software could not start cache我是怎么遇到这个问题的我是如何解决的实验步骤打Primo Cache蓝屏补丁拔掉原来的系统盘开关机…

【力扣】21. 合并两个有序链表 <链表指针>

【力扣】21. 合并两个有序链表 将两个升序链表合并为一个新的升序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 示例1 输入&#xff1a;l1 [1,2,4], l2 [1,3,4] 输出&#xff1a;[1,1,2,3,4,4] 示例 2 输入&#xff1a;l1 [], l2 [] 输出&#xff1a;…

Java List(列表)

List 是一个有序、可重复的集合&#xff0c;集合中每个元素都有其对应的顺序索引。List 集合允许使用重复元素&#xff0c;可以通过索引来访问指定位置的集合元素。List 集合默认按元素的添加顺序设置元素的索引&#xff0c;第一个添加到 List 集合中的元素的索引为 0&#xff…

如何从 Android 设备恢复已删除的文件?

从 Android 设备恢复已删除的文件很简单&#xff0c;但您需要了解内部恢复和SD 卡恢复之间的区别。 目前销售的大多数 Android 设备都配备了 SD 卡插槽&#xff08;通常为 microSD&#xff09;&#xff0c;可以轻松添加额外的存储空间。该存储空间可用于存储照片、视频、文档&a…

机器学习概述及其主要算法

目录 1、什么是机器学习 2、数据集 2.1、结构 3、算法分类 4、算法简介 4.1、K-近邻算法 4.2、贝叶斯分类 4.3、决策树和随机森林 4.4、逻辑回归 4.5、神经网络 4.6、线性回归 4.7、岭回归 4.8、K-means 5、机器学习开发流程 6、学习框架 1、什么是机器学习 机器…

SprinMVC获取请求参数

SprinMVC获取请求参数 Spring MVC 提供的获取请求参数的方式 通过 HttpServletRequest 获取请求参数通过控制器方法的形参获取请求参数使用 RequestParam 注解获取请求参数通过实体类对象获取请求参数&#xff08;推荐&#xff09; 通过ServlstAPI获取 将HttpServletRequest…

Qt开发,去掉Qt生成动态库的版本号

一、问题描述 1、Qt在生成动态库时&#xff0c;于Mac下默认生成带版本号的库&#xff0c;于Windows下默认生成不带版本号的库。 二、问题分析 生成带完整版本号的库文件及相关软链接 三、解决方案 以插件方式生成动态库&#xff0c;在.pro文件中添加以下配置参数 CONFIG …

C高级【day3】

思维导图&#xff1a; 判断家目录下&#xff0c;普通文件的个数和目录文件的个数&#xff1a; #!/bin/bashvar1(ls -l ~/ | cut -d r -f 1 | grep -i -) var2(ls -l ~/ | cut -d r -f 1 | grep -i d) #echo ${var1[*]} #echo ${var2[*]}echo 普通文件个数&#xff1a;${#var…

vue2-$nextTick有什么作用?

1、$nextTick是什么&#xff1f; 官方定义&#xff1a;在下次DOM更新循环结束之后执行延迟回调。在修改数据之后立即使用这个方法&#xff0c;获取更新后的DOM。 解释&#xff1a;Vue在更新DOM时是异步执行的&#xff0c;当数据发生变化时&#xff0c;Vue将开启一个异步更新的队…

Linux学习之正则表达式元字符和grep命令

cat /etc/redhat-release看到操作系统的版本是CentOS Linux release 7.6.1810 (Core)&#xff0c;uname -r可以看到内核版本是3.10.0-957.21.3.el7.x86_64。 正则表达式是一种搜索字符串的模式&#xff0c;通俗点理解&#xff0c;也就是普通字符和元字符共同组成的字符集合匹…

(四)Node.js - npm与包

1. 什么是包 Node.js中的第三方模块又叫做包。 不同于Node.js中的内置模块与自定义模块&#xff0c;包是由第三方个人或团队开发出来的&#xff0c;免费供所有人使用。 由于Node.js的内置模块进提供了一些底层的API&#xff0c;导致在基于内置模块进行项目开发时&#xff0c…