基于Flutter實現(xiàn)按位置大小比例布局的控件
前言
做視頻監(jiān)控項目時需要需要展示多分屏,比如2x2、3x3、414等等,如果每一種分屏都單獨實現(xiàn)會很麻煩,而且不能支持用戶定制。最好的方式還是實現(xiàn)一個通用的分屏容器,而且采樣比例計算位置大小,可以適配任意尺寸。
一、如何實現(xiàn)
最直觀的實現(xiàn)方式是獲取控件寬高然后按比例計算,但是flutter在build的時候無法獲取位置寬高信息,只有繪制之后才能獲取,所以這種方式并不容易實現(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ù)點后精度 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) { //實現(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ù)點后精度 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)容,本文用的是比較簡單的方式實現(xiàn)了比例布局控件,其主要特點是可以靈活使用,尤其是方便視頻分屏預(yù)覽的實現(xiàn)。本質(zhì)上也是對一類布局規(guī)則的總結(jié)得出的一個通用的控件,因為考慮到2x2、3x3還是可以寫死的,但是到了4x4、5x5寫死則需要16、25個參數(shù),那就必須改用數(shù)組,也就意味著需要根據(jù)規(guī)則計算位置,那和本文一樣了。所以本文的控件是有實際使用意義的。
到此這篇關(guān)于基于Flutter實現(xiàn)按位置大小比例布局的控件的文章就介紹到這了,更多相關(guān)Flutter布局控件內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android應(yīng)用中Back鍵的監(jiān)聽及處理實例
在Android應(yīng)用中處理Back鍵按下事件,多種實現(xiàn)方法如下,感興趣的朋友可以了解下哈2013-06-06Android實現(xiàn)ListView分頁自動加載數(shù)據(jù)的方法
這篇文章主要介紹了Android實現(xiàn)ListView分頁自動加載數(shù)據(jù)的方法,涉及Android生成listview列表的相關(guān)技巧,需要的朋友可以參考下2015-12-12詳解Android App卸載后跳轉(zhuǎn)到指定的反饋頁面的方法
這篇文章主要介紹了Android App卸載后跳轉(zhuǎn)到指定的反饋頁面的方法,關(guān)鍵點是相關(guān)線程要判斷在目錄被消除以前作出響應(yīng),需要的朋友可以參考下2016-04-04android 獲取本機(jī)的IP地址和mac物理地址的實現(xiàn)方法
本文主要介紹android 獲取本機(jī)的IP地址和mac物理地址的實現(xiàn)方法,這里提供示例代碼,實現(xiàn)功能,有需要的小伙伴可以參考下2016-09-09Android網(wǎng)絡(luò)連接判斷與相關(guān)處理
這篇文章主要為大家詳細(xì)介紹了Android網(wǎng)絡(luò)連接判斷操作,幫助大家判斷WIFI網(wǎng)絡(luò)是否可用,判斷MOBILE網(wǎng)絡(luò)是否可用,感興趣的小伙伴們可以參考一下2016-08-08