這篇文章是我在草稿箱裏面撈起來,提及來有點歷史了,先說說我和Flutter的一些「淵源」吧。web
最先接觸Flutter
是在2019年初,當時公司的產品App使用uni-app
這個框架開發的,當時uni-app
的用戶還比較少,社區不夠完善,再加上自己是web的性質,致使App的長列表會出現一些性能問題,因爲技術團隊不夠全面,沒有作Android
和IOS
開發的同窗,後來我和Leader作過一些技術調研後,將目光轉移了當時移動跨端框架黑馬Flutter
上面,後來咱們毅然決然使用Flutter
對App進行從新開發。當時第一次接觸Flutter
很不習慣,奈何臨危受命,沒有退路可言,從寫第一行dart
代碼開始到完成整個App的開發我和Leader用了將近2個星期。前面兩天熟悉新的代碼風格和開發模式,慢慢上手到一路過關斬將,時常開發到深夜,用兩週的時間快速的學會一門新框架以及實戰,這兩週也是我那一年感受最充實的時光,實踐是檢驗真理的惟一途徑,也是最有效的途徑(致敬那些奮鬥的歲月)。markdown
重獲新生的App上線後,性能和用戶體驗上都有很大的提高。app
這篇文章來由的前因?框架
後來陸陸續續對App進行了方方面面的優化,同時用Flutter
開發了一些新的App,對Flutter
的理解日漸加深。直到後來換了新公司,基本上沒怎麼用到Flutter
,因而有點日漸荒廢的感受,有一天忽然想起了它,老臉一紅,說的有一種想起前女朋友的感受,因而心血來潮寫了一個Music App。期間有一些忽然想記錄下來的想法,就有了這篇文章,沒錯這是一篇思想雜籍,沒有系統性,想到哪說到哪。所以以前也一直沒有發出來,如今在想一想知識就是用來分享的,萬一它解開了正在困擾某位碼友的難題呢,能讓碼友有一點收穫可能就是這篇文章最好的歸宿了,至少重見天日了。less
那後果呢?async
這篇文章可能有些地方描繪的會比較簡單,做者會盡可能去溫故並持續更新優化這篇雜籍!!!ide
有什麼錯誤的地方歡迎大佬指證,真的很久沒寫Flutter了!函數
正文開始!佈局
絕大多數的效果(防止打臉),過程都是從0-1,這個有點抽象,大多數武當弟子沒明白太極生兩儀,兩儀生四象,四象生八卦(八卦怎麼演變我也不知道了)...的真正含義,那張三丰等人就能悟出其中精髓,成就大師,指日可待。扯遠了...性能
打個比方:
少俠?
get?
點到爲止...
class Demo extends StatelessWidget{
Demo(){
doSomething()
}
}
class Demo extends StatefulWidget{
@override
_DemoState createState = > _DemoState();
}
class _DemoState extends extends State<Demo>{
_DemoState(){
print('_DemoState先輸出');
doSomething();
}
@override
void initState(){
print('initState 先輸入');
super.initState();
}
// 結果
// _DemoState先輸出
// initState 先輸入
}
複製代碼
class DemoModel{
final Color color;
final String title;
DemoModel(this.color,this.title);
}
class Demo extends StatelessWidget{
final DemoModel model;
Demo({this.model})
}
複製代碼
// 使用Stack將圖片放入層級
Stack:[
Picture1(),
Picture2()
]
// 這樣只能看到Picture2 ,咱們將代碼改造一下...
Stack:[
Picture1(),
ClipOval(
clipper:CircleRevealClipper(參數),
child:Picture2()
)
]
class CircleRevealClipper extends CustomClipper<Rect>{
final double param;
CircleRevealClipper(this.param);
@override
Rect getClip(Size size) {
// 這裏繪製切割形狀(路線)
}
@override
@override
bool shouldReclip(CustomClipper<Rect> oldClipper) {
// TODO: implement shouldReclip
return true;
}
}
// 這樣Picture1 和 Picture2就能夠同時顯示了出來了。
複製代碼
// 閱讀性強
final BUBBLE_WIDTH = 55.0;
複製代碼
var translation = 數值;
Transform(
transform: Matrix4.translationValues(translation, 0.0, 0.0),
child: Widget()
)
配合AnimationController
completionAnimationController = new AnimationController(
duration: duration,
vsync: vsync,
)
..addListener(() {
print(completionAnimationController.value);
// completionAnimationController.value值爲 0-1
}
複製代碼
enum SlideDirection { leftToRight, rightToLeft, none }
SlideDirection direction = SlideDirection.leftToright;
複製代碼
Stack:[
page(),
GestureDetector(
onHorizontalDragStart: onDragStart, // 觸碰屏幕
onHorizontalDragUpdate: onDragUpdate, // 觸碰屏幕並滑動
onHorizontalDragEnd: onDragEnd, // 離開屏幕
);
]
複製代碼
class Demo{
final double value;
Demo({
this.value
}){
// 初始化函數
print('執行');
}
fn1(){
// 方法體
}
fn2(){
//方法體
}
}
//使用
Demo a = new Demo();
a.fn1();
a.fn2();
複製代碼
class Demo{
final double value;
Demo(this.value);
}
class Example extends StatefulWidget{
@override
_ExampleState createState => _ExampleState();
}
class _ExampleState extends State<Example>{
StreamController<Demo> slideUpdateStream;
_ExampleState(){
slideUpdateStream = new StreamController<Demo>();
// 設置監聽
slideUpdateStream.stream.listen((Demo d){
// doSomething...
})
}
}
//觸發
class Trigger extends StatelessWidget{
StreamController<Demo> demo;
Trigger(){
// 觸發
demo.add(new Demo())
}
}
複製代碼
元素之間的間距(部分狀況下靈活試用)可使用SizeBox實現而避免再次嵌套。
Row(
children:[
A(),
SizeBox(
width:20.0
),
B()
...
]
)
複製代碼
Position.fill()會撐滿Stack
Stack(
children:[
Text("小子,想擋住我?"),
Position.fill(
// 這裏要注意,必需要有child,Position.fll纔會生效,纔會當爹!!!
child:Text('我小,但我爹遮天蔽日')
)
]
)
複製代碼
class Example extends StatefulWidget {
@override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example>{
var currentPage = 1.0;
PageController controller = PageController(initialPage: 0);
@override
void initState() {
// TODO: implement initState
controller.addListener(() {
// 值得注意的是,這裏controller.page的值是一個我稱之爲過程值,而不是結果值,好比 從0頁-1頁,是從0.000-0.99-1.0
// 並且當滑動沒有滑動到下一頁的時候,值會回彈(0.1-0.3-0.45-0.2-0.0)來利用這個特性 能夠用來實現一個回彈的動畫效果。
setState(() {
currentPage = controller.page;
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
// 其餘更多用法請自行百度
body:PageView.builder(
itemCount: images.length,
controller: controller,
reverse: true,
itemBuilder: (context, index) {
return Contain(
...
)
}
)
)
}
}
複製代碼
FittedBox會在本身的尺寸範圍內縮放而且調整child位置,使得child適合其尺寸。FittedBox的fit屬性有點像 寫CSS的時候的background-size屬性(感受將子元素當作了圖片進行佈局)。
Container(
color: Colors.amberAccent,
width: 300.0,
height: 300.0,
child: new FittedBox(
fit: BoxFit.contain,
alignment: Alignment.topLeft,
child: new Container(
color: Colors.red,
child: new Text("FittedBox"),
),
),
),
複製代碼
AspectRatio首先會在佈局限制條件容許的範圍內儘量的擴展,widget的高度是由寬度和比率決定的,高寬值大體如一下代碼計算
if (width有限制) {
height = width / _aspectRatio;
width = constraints.maxWidth;
} else if(height有限制) {
height = constraints.maxHeight;
width = height * _aspectRatio;
}else{
height = constraints.maxHeight;
width = constraints.maxWidth;
}
複製代碼
注意:aspectRatio屬性不能爲空
通常AspectRatio使用在須要有高寬固定比例的視圖上。
能夠簡單理解爲一個限制到高寬的容器,好比設置了最大高度,最小高度,最大寬度,最小寬度。 看代碼:
ConstrainedBox(
constraints: const BoxConstraints(
minWidth: 220.0,
minHeight: 100.0,
maxWidth: 250.0,
maxHeight: 150.0,
),
child: new Container(
width: 300.0,
height: 200.0,
color: Colors.red,
),
);
複製代碼
最終顯示的子元素高200,寬250,just so。
用 RepaintBoundary 包裹想要截取的部分(經過key控制),RenderRepaintBoundary 將 RepaintBoundary 包裹的部分取出來,而後經過 .toImage() 方法轉化爲 ui.Image 對象,而後使用 .toByteData() 將 image 轉化爲 byteData 。最後經過 File('路徑').writeAsBytes(byteData) 存儲爲文件對象。看代碼:
GlobalKey rootWidgetKey = GlobalKey();
List<Uint8List> images = List();
_interceptPng() async {
try {
RenderRepaintBoundary boundary = rootWidgetKey.currentContext.findRenderObject();
var image = await boundary.toImage(pixelRatio: 3.0);
ByteData byteData = await image.toByteData(format: ImageByteFormat.png);
Uint8List pngBytes = byteData.buffer.asUint8List();
images = [pngBytes];
setState((){})
// 或者將圖片存起來
// File('路徑').writeAsBytes(pngBytes);
} catch (e) {
print(e);
}
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('RepaintBoundary Demo'),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.camera),
tooltip: '截圖',
onPressed: () async {
await this._interceptPng();
},
),
body: Column(
children: <Widget>[
RepaintBoundary(
key: rootWidgetKey,
child: Container(
height:100.0,
width:30.0,
color:Colors.green
)
),
Expanded(
child: ListView.builder(
itemBuilder: (context, index) {
return Image.memory(
images[index],
width: 100.0,
height: 200.0,
);
},
itemCount: images.length,
scrollDirection: Axis.vertical,
),
)
],
),
),
)
}
複製代碼
看英文單詞的意思也能看出來用這個組件能夠實現圓形頭像的效果。可是要設置 backgroundImage 或者 backgroundColor 屬性才能實現圓形效果, child 屬性能夠理解爲在頭像上層設置widgets(頭像掛件)。看代碼:
CircleAvatar(
radius: 40.0,
backgroundColor: Colors.red,
child: Text("test"),
)
複製代碼
IndexedStack繼承自Stack,做用是顯示第index個child組件,其餘child組件是不可見的,但全部的組件的狀態都會被保持(還能夠經過OffStage組件去作狀態保持,但他們的缺點是開銷比較大,在頁面加載初始化的時候全部子組件都會被實例化)。
IndexedStack(
index: currentIndex, // 顯示的index
children: widgetList, // 子組件列表
)
複製代碼
這個類的做用在於保持頁面狀態,咱們可讓頁面繼承這個類並重寫wantKeepAlive爲true便可,代碼以下:
class TestPage extends StatefulWidget {
@override
_TestPageState createState() => _TestPageState();
}
class _TestPageState extends State<TestPage> with AutomaticKeepAliveClientMixin {
// 重寫屬性通常能夠經過錯誤修復提示插件直接生成重寫屬性,修改值對應值便可
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
...
}
複製代碼
正在更新...