你可能不知道的「Flutter」知識點,會持續更新...

lucasFu發表於2021-07-13

這篇文章的前世今生

這篇文章是我在草稿箱裡面撈起來,說起來有點歷史了,先說說我和Flutter的一些“淵源”吧。

最早接觸Flutter是在2019年初,當時公司的產品App使用uni-app這個框架開發的,當時uni-app的使用者還比較少,社群不夠完善,再加上本身是web的性質,導致App的長列表會出現一些效能問題,由於技術團隊不夠全面,沒有做AndroidIOS開發的同學,後來我和Leader做過一些技術調研後,將目光轉移了當時移動跨端框架黑馬Flutter上面,後來我們毅然決然使用FlutterApp進行重新開發。當時第一次接觸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控制),RenderRepaintBoundaryRepaintBoundary 包裹的部分取出來,然後通過 .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

正在更新...

相關文章