2022年9月6日 星期二

Flutter學習-10 Flow布局練習

Flow可以透過自定義的方式來分配布局的位置,Flow一定要給予兩個參數方法,一為delegate,delegate裡可設計元件繪製的方法,另一個為childred,決定裡面要放置的元件。


可見如下範例:

 import 'package:flutter/material.dart';


//buttonSize是子物件的size
const double buttonSize =80;

void main() {
runApp(const MyApp());
}

class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);

@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key, required this.title}) : super(key: key);

final String title;

@override
State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {


@override
Widget build(BuildContext context) {

return Scaffold(
appBar: AppBar(

title: Text(widget.title),
),
//body裡放置的是自己定義的LinerFlowWidget方法
body:LinerFlowWidget(), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}

class LinerFlowWidget extends StatefulWidget {
LinerFlowWidget({Key? key}) : super(key: key);

@override
State<LinerFlowWidget> createState() => _LinerFlowWidgetState();
}

class _LinerFlowWidgetState extends State<LinerFlowWidget> with SingleTickerProviderStateMixin{

//建立一個動畫控制器,加上late延遲給予初始值
late AnimationController controller;

@override
void initState() {
//initState於程式第一次執行時,將動畫控制器初始化,並設定每0.3秒執行一次
controller=AnimationController(
//設定執行週期,每0.3秒執行一次
duration: Duration(milliseconds: 300),
//這裡的this要配上面加入with SingleTickerProviderStateMixin,這裡我忘了為什麼要這樣配合了,只記得這樣寫,有空再研究
vsync: this);

// TODO: implement initState
super.initState();
}

@override
void dispose() {
//設定回收機制,不使用時回收記憶體
controller.dispose();
// TODO: implement dispose
super.dispose();
}



@override
Widget build(BuildContext context) {
//使用Flow布局,Flow裡面一定要放的兩個參數,一為delegate,delegate告知程式繪製的方法
//另一個為childred在布局裡的子元件,在這裡有mail call notifications三個icons
return Flow(
//FlowMenuDelegate是自己寫的繪製方法,參數的controller動畫的控制器,這裡寫可以在每個ticker定時的監聽動畫的變化,然後持續的執行
delegate: FlowMenuDelegate(controller:controller),
//這裡透過list.map的方法,將原本list中的四個部件,逐一放進buildItem執行一次,然後回傳FloatingActionButton類型的widget
//並將回傳的四個FloatingActionButton類型widget,存進<IconData>這個陣列裡面。
children: <IconData>[
Icons.menu,
Icons.call,
Icons.mail,
Icons.notifications,
].map<Widget>(buildItem).toList(),
);
}

//這裡的參數icon指的是上面menu mail.call nofifications四個icon
Widget buildItem(IconData icon) {
return SizedBox(
width:buttonSize,
height: buttonSize,
child: FloatingActionButton(
elevation: 0,
splashColor: Colors.black,
child: Icon(icon,color: Colors.white,size: 45,),
onPressed: (){
//點選之後,當動畫狀態已經完成的時候,執行反轉動畫reverse(),假如沒有動畫不是完成,就執行一次動畫forward()
if(controller.status==AnimationStatus.completed){
controller.reverse();
}else{
controller.forward();
}

},),
);
}
}


//這裡繼承了FlowDelegate,FlowDelegate提供了兩個方法
// 一是paintChildren,
//二是shouldRepaint,決定如果到相同的子物件是否重複繪製

class FlowMenuDelegate extends FlowDelegate{
//可以透過下面paintChildren參數中的content,取得子物件的下列的數值,並透過這些取得的數值,
//context.size 父物件的大小
//context.size 子物件的數量
//context.getChildSize(int i) 取得個別子物件的大小
//paintChild(int i, { Matrix4 transform, double opacity = 1.0 }); 決定繪製子類別的方法
// 上面的參數,i是指要繪製第幾個子物件,transformMatrix4的繪製方法,opacity 是透明度

final Animation<double> controller;
//建立FlowMenuDelegate的建構式,要求一定要給予controller這個命名參數
const FlowMenuDelegate({required this.controller}):super(repaint: controller);

@override
void paintChildren(FlowPaintingContext context) {
//size為父件的大小,
final size=context.size;
//xStart為第一個按鈕的起始位置x軸,由父物件的寬度-子物件的長度。
final xStart=size.width-buttonSize;
//Start為第一個按鈕的起始位置y軸,由父物件的長度-子物件的長度。
final yStart=size.height-buttonSize;

//這裡用-1是因為menu是第一個,先執行的先放,這樣最後一個執行是menu,它就會在最上面。
for(int i=context.childCount-1;i>=0;i--){
//margin為子物件和另一個子物件的間距
final margin=8;
//取得個別子物件的寬度,這裡就上面設定的80
final childSize=context.getChildSize(i)!.width;
//dx為子物件的長度加上間距,乘上i,可以控制每個子物件的位置
final dx=(childSize+margin)*i;
//子物件的x軸的位置=剛才設定的起點位置開始倒推,第1個是起始的位置,第二個是起始的位置-(80+8)*1,第三個是起始的位置-(80+8)*2....依此類推
//這裡的controller.value是指動畫持續不斷傳過來的數值,起點為0,終點為1,每次傳過來就會執行一次的setState,一直更新子元件的位置,因此會產生一個動畫的效果
final x=xStart-dx*controller.value;
//y軸保持不變,以這個程式就是全部的長度-80的位置
final y=yStart;
//x軸為x,y軸為y,及z軸為0的地方將圖呈現出來
context.paintChild(i,transform:Matrix4.translationValues(x, y, 0),);
}

// TODO: implement paintChildren
}

//這裡設置回傳true,不論何時都重複繪製
@override
bool shouldRepaint(covariant FlowDelegate oldDelegate) {
return true;
}
}

沒有留言:

張貼留言