基于Flutter實(shí)現(xiàn)按位置大小比例布局的控件
前言
做視頻監(jiān)控項(xiàng)目時需要需要展示多分屏,比如2x2、3x3、414等等,如果每一種分屏都單獨(dú)實(shí)現(xiàn)會很麻煩,而且不能支持用戶定制。最好的方式還是實(shí)現(xiàn)一個通用的分屏容器,而且采樣比例計算位置大小,可以適配任意尺寸。
一、如何實(shí)現(xiàn)
最直觀的實(shí)現(xiàn)方式是獲取控件寬高然后按比例計算,但是flutter在build的時候無法獲取位置寬高信息,只有繪制之后才能獲取,所以這種方式并不容易實(shí)現(xiàn),比較簡單的方式應(yīng)該是使用Row、Column結(jié)合Flexible。
1、數(shù)值轉(zhuǎn)成分?jǐn)?shù)
需要轉(zhuǎn)換的數(shù)值
final Rect rect; //子控件位置大小,比例值范圍0-1
定義一個分?jǐn)?shù)對象
//分?jǐn)?shù)
class Rational {
int den = 1; //分母
int num = 0; //分子
Rational(this.num, this.den);
//通過double構(gòu)造,accuracy小數(shù)點(diǎn)后精度
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);
}
}轉(zhuǎn)成分?jǐn)?shù)并對齊分母
//將位置大小轉(zhuǎn)成分?jǐn)?shù)
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的比例布局的特性,根據(jù)上面的分?jǐn)?shù)計算出控件比例的位置大小對應(yīng)的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的比例布局的特性,根據(jù)上面的分?jǐn)?shù)計算出控件比例的位置大小對應(yīng)的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,
});
@override
Widget build(BuildContext context) {
//實(shí)現(xiàn)按比例顯示布局
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()),
],
);
}
}
//分?jǐn)?shù)
class Rational {
int den = 1; //分母
int num = 0; //分子
Rational(this.num, this.den);
//通過double構(gòu)造,accuracy小數(shù)點(diǎn)后精度
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、基本用法
設(shè)置子控件位置大小。一般配合stack作為父容器使用
Proportion(
rect: Rect.fromLTRB(0, 0, 0.5, 0.5), //子控件位置大小,(0, 0, 0.5, 0.5)表示左上1/4的區(qū)域
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")),
))
始終保持比例

總結(jié)
以上就是今天要講的內(nèi)容,本文用的是比較簡單的方式實(shí)現(xiàn)了比例布局控件,其主要特點(diǎn)是可以靈活使用,尤其是方便視頻分屏預(yù)覽的實(shí)現(xiàn)。本質(zhì)上也是對一類布局規(guī)則的總結(jié)得出的一個通用的控件,因?yàn)榭紤]到2x2、3x3還是可以寫死的,但是到了4x4、5x5寫死則需要16、25個參數(shù),那就必須改用數(shù)組,也就意味著需要根據(jù)規(guī)則計算位置,那和本文一樣了。所以本文的控件是有實(shí)際使用意義的。
到此這篇關(guān)于基于Flutter實(shí)現(xiàn)按位置大小比例布局的控件的文章就介紹到這了,更多相關(guān)Flutter布局控件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android切換前后臺點(diǎn)擊通知進(jìn)入當(dāng)前頁面
這篇文章主要介紹了Android切換前后臺點(diǎn)擊通知進(jìn)入當(dāng)前頁面,主要講述當(dāng)App退出到后臺的后,怎么點(diǎn)擊通知回到原來按下HOME鍵之前的前臺頁面,需要的朋友可以參考下2023-03-03
Android中okhttp3.4.1+retrofit2.1.0實(shí)現(xiàn)離線緩存
這篇文章主要介紹了Android中okhttp3.4.1結(jié)合retrofit2.1.0實(shí)現(xiàn)離線緩存,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-10-10
android系統(tǒng)在靜音模式下關(guān)閉camera拍照聲音的方法
本文為大家詳細(xì)介紹下android系統(tǒng)如何在靜音模式下關(guān)閉camera拍照聲音,具體的實(shí)現(xiàn)方法如下,感興趣的朋友可以參考下哈2013-07-07
Android編程使用光線傳感器獲取光線強(qiáng)弱的方法【LightSensorManager封裝類】
這篇文章主要介紹了Android編程使用光線傳感器獲取光線強(qiáng)弱的方法,結(jié)合實(shí)例形式分析了Android光線傳感器管理封裝類LightSensorManager的具體實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-11-11
Android設(shè)置默認(rèn)鎖屏壁紙接口的方法
這篇文章主要介紹了Android默認(rèn)鎖屏壁紙接口的設(shè)置方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-01-01
Android實(shí)現(xiàn)數(shù)據(jù)按照時間排序
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)數(shù)據(jù)按照時間排序的方法,具有一定的參考價值,感興趣的小伙伴們可以參考一下2018-09-09

