這篇文章的前世今生
這篇文章是我在草稿箱裡面撈起來,說起來有點歷史了,先說說我和Flutter的一些“淵源”吧。
最早接觸Flutter
是在2019年初,當時公司的產品App使用uni-app
這個框架開發的,當時uni-app
的使用者還比較少,社群不夠完善,再加上本身是web的性質,導致App的長列表會出現一些效能問題,由於技術團隊不夠全面,沒有做Android
和IOS
開發的同學,後來我和Leader做過一些技術調研後,將目光轉移了當時移動跨端框架黑馬Flutter
上面,後來我們毅然決然使用Flutter
對App進行重新開發。當時第一次接觸Flutter
很不習慣,奈何臨危受命,沒有退路可言,從寫第一行dart
程式碼開始到完成整個App的開發我和Leader用了將近2個星期。前面兩天熟悉新的程式碼風格和開發模式,慢慢上手到一路過關斬將,時常開發到深夜,用兩週的時間快速的學會一門新框架以及實戰,這兩週也是我那一年感覺最充實的時光,實踐是檢驗真理的唯一途徑,也是最有效的途徑(致敬那些奮鬥的歲月)。
重獲新生的App上線後,效能和使用者體驗上都有很大的提升。
這篇文章來由的前因?
後來陸陸續續對App進行了方方面面的優化,同時用Flutter
開發了一些新的App,對Flutter
的理解日漸加深。直到後來換了新公司,基本上沒怎麼用到Flutter
,於是有點日漸荒廢的感覺,有一天突然想起了它,老臉一紅,說的有一種想起前女友的感覺,於是心血來潮寫了一個Music App。期間有一些突然想記錄下來的想法,就有了這篇文章,沒錯這是一篇思想雜籍,沒有系統性,想到哪說到哪。因此之前也一直沒有發出來,現在在想想知識就是用來分享的,萬一它解開了正在困擾某位碼友的難題呢,能讓碼友有一點收穫可能就是這篇文章最好的歸宿了,至少重見天日了。
那後果呢?
這篇文章可能有些地方描繪的會比較簡單,作者會盡量去溫故並持續更新優化這篇雜籍!!!
有什麼錯誤的地方歡迎大佬指證,真的好久沒寫Flutter了!
正文開始!
關於一些動效方面的心得
animation的動畫值為0-1
絕大多數的效果(防止打臉),過程都是從0-1,這個有點抽象,大多數武當弟子沒明白太極生兩儀,兩儀生四象,四象生八卦(八卦怎麼演變我也不知道了)...的真正含義,那張三丰等人就能悟出其中精髓,成就大師,指日可待。扯遠了...
打個比方:
- 0 水平移動到 600 是 0-1? // 0*600 - 1*600 有問題嘛?
少俠?
get?
點到為止...
1.初始化函式
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 先輸入
}
複製程式碼
2.儘量使用Model傳參
- 使用model傳參能夠在程式編譯的時候就發現程式中關於引數傳錯的錯誤。
class DemoModel{
final Color color;
final String title;
DemoModel(this.color,this.title);
}
class Demo extends StatelessWidget{
final DemoModel model;
Demo({this.model})
}
複製程式碼
3.ClipOval切割元件做出不同層的圖片同一層顯示的效果,來看段程式碼
// 使用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就可以同時顯示了出來了。
複製程式碼
4.通常固定值用全英文大寫變數接受
// 閱讀性強
final BUBBLE_WIDTH = 55.0;
複製程式碼
5.使用Transform做簡單動畫
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
}
複製程式碼
6.關於列舉值的使用方法
enum SlideDirection { leftToRight, rightToLeft, none }
SlideDirection direction = SlideDirection.leftToright;
複製程式碼
7.從透明到顯示的過程 可以使用Opacity元件,也是0-1過程。
8.最頂層手勢事件的實現其中一種方式
Stack:[
page(),
GestureDetector(
onHorizontalDragStart: onDragStart, // 觸碰螢幕
onHorizontalDragUpdate: onDragUpdate, // 觸碰螢幕並滑動
onHorizontalDragEnd: onDragEnd, // 離開螢幕
);
]
複製程式碼
9.關於類基礎使用
class Demo{
final double value;
Demo({
this.value
}){
// 初始化函式
print('執行');
}
fn1(){
// 方法體
}
fn2(){
//方法體
}
}
//使用
Demo a = new Demo();
a.fn1();
a.fn2();
複製程式碼
10.關於StreamController的使用
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())
}
}
複製程式碼
11.關於SizeBox的其他小用法
元素之間的間距(部分情況下靈活試用)可以使用SizeBox實現而避免再次巢狀。
- 兩個元素之間有20.0的間距
Row(
children:[
A(),
SizeBox(
width:20.0
),
B()
...
]
)
複製程式碼
12. 關於Position.fill()在Stack中的使用
Position.fill()會撐滿Stack
Stack(
children:[
Text("小子,想擋住我?"),
Position.fill(
// 這裡要注意,必須要有child,Position.fll才會生效,才會當爹!!!
child:Text('我小,但我爹遮天蔽日')
)
]
)
複製程式碼
13.PageView 一個非常好用的滾動檢視列表(簡單版的輪播)
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(
...
)
}
)
)
}
}
複製程式碼
14.FittedBox的使用
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"),
),
),
),
複製程式碼
15.AspectRatio的使用
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使用在需要有高寬固定比例的檢視上。
16.ConstrainedBox的使用
可以簡單理解為一個限制到高寬的容器,比如設定了最大高度,最小高度,最大寬度,最小寬度。 看程式碼:
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。
17.RepaintBoundary元件可以實現截圖效果
用 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,
),
)
],
),
),
)
}
複製程式碼
18.CircleAvatar元件的使用
看英文單詞的意思也能看出來用這個元件可以實現圓形頭像的效果。但是要設定 backgroundImage 或者 backgroundColor 屬性才能實現圓形效果, child 屬性可以理解為在頭像上層設定widgets(頭像掛件)。看程式碼:
CircleAvatar(
radius: 40.0,
backgroundColor: Colors.red,
child: Text("test"),
)
複製程式碼
19.IndexedStack元件的使用
IndexedStack繼承自Stack,作用是顯示第index個child元件,其他child元件是不可見的,但所有的元件的狀態都會被保持(還可以通過OffStage元件去做狀態保持,但他們的缺點是開銷比較大,在頁面載入初始化的時候所有子元件都會被例項化)。
IndexedStack(
index: currentIndex, // 顯示的index
children: widgetList, // 子元件列表
)
複製程式碼
20.AutomaticKeepAliveClientMixin類
這個類的作用在於保持頁面狀態,我們可以讓頁面繼承這個類並重寫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) {
...
}
複製程式碼
21.InheritedWidget
正在更新...