Flutter implements controls that are laid out in proportion to position and size


foreword

When doing video surveillance projects, it is necessary to display multiple split screens, such as 2x2, 3x3, 414, etc. It will be troublesome if each split screen is implemented separately, and it cannot support user customization. The best way is to implement a general-purpose split-screen container, and the sampling ratio calculates the position size, which can be adapted to any size.


1. How to achieve it?

The most intuitive way to achieve it is to get the width and height of the control and then calculate it proportionally, but flutter cannot get the position width and height information when building, it can only be obtained after drawing, so this way is not easy to implement, the simpler way should be to use Row, Column combined with Flexible.

1. Convert the value into a score

the value to be converted

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

define a score object

//分数
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);
  }
}

Convert to fractions and align denominators

    //将位置大小转成分数
    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 layout horizontal

We use the automatic layout of Row and the characteristics of the proportional layout of Flexible to calculate the flex value corresponding to the position and size of the control ratio according to the above scores.

 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 layout vertical

We use the automatic layout of Column and the characteristics of the proportional layout of Flexible to calculate the flex value corresponding to the position and size of the control ratio according to the above scores.

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

2. Complete code

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);
  }
}

Common layout (optional)
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,
          );
        },
      )
    ];
  }
}

3. Example of use

1. Basic usage

Set the position and size of the child control. Generally used with stack as the parent container

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

2. Four split screen

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")),
              ))

insert image description here

3. Six split screen

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")),
              ))

insert image description here

4. Octal screen

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")),
              ))

insert image description here

5. Nine split screen

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")),
              ))

insert image description here

6. 414 split screens

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")),
              ))

insert image description here
always keep the ratio
insert image description here


Summarize

The above is what I want to talk about today. This article uses a relatively simple way to implement the proportional layout control. Its main feature is that it can be used flexibly, especially to facilitate the realization of video split-screen preview. In essence, it is also a general-purpose control derived from a summary of a class of layout rules, because considering that 2x2 and 3x3 can still be hard-coded, but when it comes to 4x4 and 5x5 hard-coded, 16 or 25 parameters are needed, so you must use it instead An array means that the position needs to be calculated according to the rules, which is the same as this article. Therefore, the controls in this article have practical significance.

Guess you like

Origin blog.csdn.net/u013113678/article/details/132035825