最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Flutter高級(jí)玩法Flow位置自定義

 更新時(shí)間:2023年03月03日 09:34:24   作者:張風(fēng)捷特烈  
這篇文章主要為大家介紹了Flutter高級(jí)玩法Flow位置自定義實(shí)現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

前言

Flow布局是一個(gè)超級(jí)強(qiáng)大的布局,但應(yīng)該很少有人用,因?yàn)槿胧值拈T(mén)檻還是有的

Flow的屬性很簡(jiǎn)單,只有FlowDelegate類(lèi)型的delegate和組件列表children,

可能很多人看到delegate就揮揮手:臣妾做不到,今天就來(lái)掰扯一下這個(gè)FlowDelegate.

class Flow extends MultiChildRenderObjectWidget {
  Flow({
    Key key,
    @required this.delegate,
    List<Widget> children = const <Widget>[],
  }) : assert(delegate != null),

第一幕、開(kāi)場(chǎng)-演員入臺(tái)

1. 展示舞臺(tái)

我們的第一個(gè)舞臺(tái)是一個(gè)200*200的灰色 box,由FlowDemo組件出當(dāng)主角

void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
        title: 'Flutter Demo',
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: Scaffold(
          appBar: AppBar(),
          body: Center(child: HomePage()),
        ));
  }
}
class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      width: 200,
      height: 200,
      color: Colors.grey.withAlpha(66),
      alignment: Alignment.center,
      child: FlowDemo(),
    );
  }
}

2. Flow出場(chǎng)

FlowDemo中使用Flow組件,包含四個(gè)box

四個(gè)box變成依次是60.0(紅), 50.0(黃), 40.0(藍(lán)), 30.0(綠)

class FlowDemo extends StatelessWidget {
  final sides = [60.0, 50.0, 40.0, 30.0];
  final colors = [Colors.red,Colors.yellow,Colors.blue,Colors.green];
  @override
  Widget build(BuildContext context) {
    return Flow(
      delegate: _Delegate(),
      children: sides.map((e) => _buildItem(e)).toList(),
    );
  }
  Widget _buildItem(double e) {
    return Container(
      width: e,
      alignment: Alignment.center,
      height: e,
      color: colors[sides.indexOf(e)],  
      child: Text('$e'),
    );
  }
}

3. FlowDelegate出場(chǎng)

Flow布局需要一個(gè)FlowDelegate類(lèi)型的delegate對(duì)象
但是Flutter中并沒(méi)有其實(shí)現(xiàn)類(lèi),所以想玩Flow,只有一條路:自定義

class _Delegate extends FlowDelegate {
  @override
  void paintChildren(FlowPaintingContext context) {
  }
  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return true;
  }
}

4. paintChildren方法和FlowPaintingContext對(duì)象

paintChildren顧名思義是用來(lái)畫(huà)孩子的
 

FlowPaintingContext也就是繪制的上下文,即繪制的信息

那就輕輕的瞄一眼FlowPaintingContext里面有啥吧:

一共有四個(gè)東西: size、childCount、getChildSize、paintChild

---->[源碼:flutter/lib/src/rendering/flow.dart:23]----
abstract class FlowPaintingContext {
  Size get size;//父親尺寸
  int get childCount;//孩子個(gè)數(shù)
  Size getChildSize(int i);//第i個(gè)孩子尺寸
  //繪制孩子
  void paintChild(int i, { Matrix4 transform, double opacity = 1.0 });
}

接下來(lái)用代碼測(cè)試一下這幾個(gè)屬性看看,不出所料

默認(rèn)是繪制在父容器的左上角。

class _Delegate extends FlowDelegate {
  @override
  void paintChildren(FlowPaintingContext context) {
    print("父容器尺寸:${context.size}");
    print("孩子個(gè)數(shù):${context.childCount}");
    for(int i=0;i<context.childCount;i++){
      print("第$i個(gè)孩子尺寸:${context.getChildSize(i)}");
    }
  }

第二幕、排兵布陣

前面只是將組件排在了左上角,那如何對(duì)進(jìn)行其他排布呢?

1. paintChild與Matrix4

paintChild時(shí)可以傳入transform的Matrix4對(duì)象進(jìn)行變換

在這里基本上只用了Matrix4的平移translationValues功能,
至于Matrix4的具體用法,那又是一個(gè)故事了 這里讓黃色的box移到右上角,即X方向平移(父寬-己寬):

  @override
  void paintChildren(FlowPaintingContext context) {
    var size = context.size;
    for (int i = 0; i < context.childCount; i++) {
      if (i == 1) {
        var tr = context.getChildSize(i);
        context.paintChild(i,
            transform:
                Matrix4.translationValues(size.width - tr.width, 0, 0.0));
      } else {
        context.paintChild(i);
      }
    }
  }

現(xiàn)在讓四個(gè)組件排布在父親的四角,如下:

class _AngleDelegate extends FlowDelegate {
  Matrix4 m4;
  @override
  void paintChildren(FlowPaintingContext context) {
    var size = context.size;
    for (int i = 0; i < context.childCount; i++) {
      var cSize = context.getChildSize(i);
      if (i == 1) {
        m4 = Matrix4.translationValues(size.width - cSize.width, 0, 0.0);
      } else if (i == 2) {
        m4 = Matrix4.translationValues(0, size.height - cSize.height, 0.0);
      } else if (i == 3) {
        m4 = Matrix4.translationValues(size.width - cSize.width, size.height - cSize.height, 0.0);
      }
      context.paintChild(i, transform: m4);
    }
  }
  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return true;
  }
}

2. Flow布局的封裝

如果需要一個(gè)排布四角的組件,可以基于上面的Delegate做一個(gè)組件
雖然用處很有限,但原來(lái)了解一下Flow還是挺好的。

class AngleFlow extends StatelessWidget {
  final List<Widget> children;
  AngleFlow({@required this.children}) : assert(children.length == 4);
  @override
  Widget build(BuildContext context) {
    return Flow(
      delegate: _AngleDelegate(),
      children: children,
    );
  }
}
class _AngleDelegate extends FlowDelegate {
  Matrix4 m4;
  @override
  void paintChildren(FlowPaintingContext context) {
    var size = context.size;
    for (int i = 0; i < context.childCount; i++) {
      var cSize = context.getChildSize(i);
      if (i == 1) {
        m4 = Matrix4.translationValues(size.width - cSize.width, 0, 0.0);
      } else if (i == 2) {
        m4 = Matrix4.translationValues(0, size.height - cSize.height, 0.0);
      } else if (i == 3) {
        m4 = Matrix4.translationValues(
            size.width - cSize.width, size.height - cSize.height, 0.0);
      }
      context.paintChild(i, transform: m4);
    }
  }
  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return true;
  }
}

3. 圓形的Flow布局

其實(shí)可以看出,F(xiàn)low的核心就是根據(jù)信息來(lái)計(jì)算位置
所以,所有的布局都可以通過(guò)Flow進(jìn)行實(shí)現(xiàn)。
除此之外對(duì)應(yīng)一些特定情況的布局,使用Flow會(huì)非常簡(jiǎn)單,比如:

class CircleFlow extends StatelessWidget {
  final List<Widget> children;
  CircleFlow({@required this.children});
  @override
  Widget build(BuildContext context) {
    return Flow(
      delegate: _CircleFlowDelegate(),
      children: children,
    );
  }
}
class _CircleFlowDelegate extends FlowDelegate {
  @override //繪制孩子的方法
  void paintChildren(FlowPaintingContext context) {
    double radius = context.size.shortestSide / 2;
    var count = context.childCount;
    var perRad = 2 * pi / count;
    for (int i = 0; i < count; i++) {
      print(i);
      var cSizeX = context.getChildSize(i).width / 2;
      var cSizeY = context.getChildSize(i).height / 2;
      var offsetX = (radius - cSizeX) * cos(i * perRad) + radius;
      var offsetY = (radius - cSizeY) * sin(i * perRad) + radius;
      context.paintChild(i,
          transform: Matrix4.translationValues(
              offsetX - cSizeX, offsetY - cSizeY, 0.0));
    }
  }
  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return true;
  }
}

第三幕、當(dāng)Flow遇到Animation

全面說(shuō)Flow最重要的就是進(jìn)行定位,而動(dòng)畫(huà)的本質(zhì)是若干個(gè)變動(dòng)的數(shù)字
那么兩者自然是郎才女貌,情投意合

1.圓形布局 + 旋轉(zhuǎn)

前面圓形布局靠的是計(jì)算某個(gè)組件偏轉(zhuǎn)的角度

那么想要實(shí)現(xiàn)旋轉(zhuǎn)是非常簡(jiǎn)單的,由于有角度的狀態(tài),所以StatefulWidget

class CircleFlow extends StatefulWidget {
  final List<Widget> children;
  CircleFlow({@required this.children});
  @override
  _CircleFlowState createState() => _CircleFlowState();
}
class _CircleFlowState extends State<CircleFlow>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  double rad = 0.0;
  @override
  void initState() {
    _controller =
        AnimationController(duration: Duration(milliseconds: 3000), vsync: this)
          ..addListener(() => setState(() =>
              rad = _controller.value*pi*2));
    _controller.forward();
    super.initState();
  }
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return  Flow(
        delegate: _CircleFlowDelegate(rad),
        children: widget.children,
    );
  }
}

在構(gòu)造_CircleFlowDelegate時(shí)傳入角度,在offsetX、offsetY 時(shí)加上角度就行了

class _CircleFlowDelegate extends FlowDelegate {
  final double rad;
  _CircleFlowDelegate(this.rad);
  @override //繪制孩子的方法
  void paintChildren(FlowPaintingContext context) {
    double radius = context.size.shortestSide / 2;
    var count = context.childCount;
    var perRad = 2 * pi / count ;
    for (int i = 0; i < count; i++) {
      print(i);
      var cSizeX = context.getChildSize(i).width / 2;
      var cSizeY = context.getChildSize(i).height / 2;
      var offsetX = (radius - cSizeX) * cos(i * perRad+ rad) + radius;
      var offsetY = (radius - cSizeY) * sin(i * perRad+ rad) + radius;
      context.paintChild(i,
          transform: Matrix4.translationValues(
              offsetX - cSizeX, offsetY - cSizeY, 0.0));
    }
  }
  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return true;
  }
}

2.圓形布局 + 偏移

能實(shí)現(xiàn)出來(lái)我還是蠻激動(dòng)的。定義了menu為中間的組件

children為周?chē)慕M件,點(diǎn)擊中間組件,執(zhí)行動(dòng)畫(huà),

在進(jìn)行定位時(shí),讓offsetX和offsetY乘以分率后加半徑,這樣就會(huì)向中心靠攏,

反之?dāng)U散,我取名為BurstFlow,意為綻放

class BurstFlow extends StatefulWidget {
  final List<Widget> children;
  final Widget menu;
  BurstFlow({@required this.children, @required this.menu});
  @override
  _BurstFlowState createState() => _BurstFlowState();
}
class _BurstFlowState extends State<BurstFlow>
    with SingleTickerProviderStateMixin {
  AnimationController _controller;
  double _rad = 0.0;
  bool _closed = true;
  @override
  void initState() {
    _controller =
        AnimationController(duration: Duration(milliseconds: 1000), vsync: this)
          ..addListener(() => setState(() =>    _rad = (_closed ? (_controller.value) :1- _controller.value)))
          ..addStatusListener((status) {
            if (status == AnimationStatus.completed) {
              _closed = !_closed;
            }
          });
    super.initState();
  }
  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
  @override
  Widget build(BuildContext context) {
    return Flow(
      delegate: _CircleFlowDelegate(_rad),
      children: [
        ...widget.children,
        InkWell(
            onTap: () {
              _controller.reset();
              _controller.forward();
            },
            child: widget.menu)
      ],
    );
  }
}
class _CircleFlowDelegate extends FlowDelegate {
  final double rad;
  _CircleFlowDelegate(this.rad);
  @override //繪制孩子的方法
  void paintChildren(FlowPaintingContext context) {
    double radius = context.size.shortestSide / 2;
    var count = context.childCount - 1;
    var perRad = 2 * pi / count;
    for (int i = 0; i < count; i++) {
      print(i);
      var cSizeX = context.getChildSize(i).width / 2;
      var cSizeY = context.getChildSize(i).height / 2;
      var offsetX = rad * (radius - cSizeX) * cos(i * perRad) + radius;
      var offsetY = rad * (radius - cSizeY) * sin(i * perRad) + radius;
      context.paintChild(i,
          transform: Matrix4.translationValues(
              offsetX - cSizeX, offsetY - cSizeY, 0.0));
    }
    context.paintChild(context.childCount - 1,
        transform: Matrix4.translationValues(
            radius - context.getChildSize(context.childCount - 1).width / 2,
            radius - context.getChildSize(context.childCount - 1).height / 2,
            0.0));
  }
  @override
  bool shouldRepaint(FlowDelegate oldDelegate) {
    return true;
  }
}

另外可以對(duì)周?chē)慕M件排布進(jìn)行設(shè)計(jì),可以是半圓弧收方放、

四分之一圓弧收方、甚至是指定角度弧排列

周?chē)慕M件也可以進(jìn)行透明度的漸變,這些都是可以?xún)?yōu)化的點(diǎn)

這里就不再說(shuō)了,跟你們一些空間,各位可以自行優(yōu)化。

布局重在定位,而Flow是定位之王,我的位置我做主。好了,這篇就到這里吧,更多關(guān)于Flutter Flow位置自定義的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Android存儲(chǔ)字符串?dāng)?shù)據(jù)到txt文件

    Android存儲(chǔ)字符串?dāng)?shù)據(jù)到txt文件

    這篇文章主要為大家詳細(xì)介紹了Android存儲(chǔ)字符串?dāng)?shù)據(jù)到txt文件,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-10-10
  • Android 權(quán)限(permission)整理

    Android 權(quán)限(permission)整理

    本文主要介紹Android 權(quán)限的整理,在開(kāi)發(fā)Android應(yīng)用的時(shí)候,根據(jù)需求的不同,會(huì)用到不同的權(quán)限,這里整理了很多,有需要的同學(xué)可以參考下
    2016-07-07
  • Android實(shí)現(xiàn)文件壓縮與解壓工具類(lèi)

    Android實(shí)現(xiàn)文件壓縮與解壓工具類(lèi)

    這篇文章主要為大家詳細(xì)介紹了如何使用Android實(shí)現(xiàn)一個(gè)文件壓縮與解壓工具類(lèi),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下
    2024-04-04
  • 使用newInstance()來(lái)實(shí)例化fragment并傳遞數(shù)據(jù)操作

    使用newInstance()來(lái)實(shí)例化fragment并傳遞數(shù)據(jù)操作

    這篇文章主要介紹了使用newInstance()來(lái)實(shí)例化fragment并傳遞數(shù)據(jù)操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2020-08-08
  • Android利用Espresso進(jìn)行UI自動(dòng)化測(cè)試的方法詳解

    Android利用Espresso進(jìn)行UI自動(dòng)化測(cè)試的方法詳解

    因?yàn)槲沂歉鉧ndroid開(kāi)發(fā)的,所以被分到了自動(dòng)化測(cè)試小組,所以了解了一些UI自動(dòng)化測(cè)試。下面這篇文章主要給大家介紹了關(guān)于Android利用Espresso進(jìn)行UI自動(dòng)化測(cè)試的相關(guān)資料,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。
    2017-12-12
  • Android開(kāi)發(fā)之判斷有無(wú)虛擬按鍵(導(dǎo)航欄)的實(shí)例

    Android開(kāi)發(fā)之判斷有無(wú)虛擬按鍵(導(dǎo)航欄)的實(shí)例

    下面小編就為大家分享一篇Android開(kāi)發(fā)之判斷有無(wú)虛擬按鍵(導(dǎo)航欄)的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2018-01-01
  • 基于Android在布局中動(dòng)態(tài)添加view的兩種方法(總結(jié))

    基于Android在布局中動(dòng)態(tài)添加view的兩種方法(總結(jié))

    下面小編就為大家?guī)?lái)一篇基于Android在布局中動(dòng)態(tài)添加view的兩種方法(總結(jié))。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-10-10
  • Android banner詳解用法案例

    Android banner詳解用法案例

    android-banner實(shí)現(xiàn)了一般banner循環(huán)輪播的效果,一頁(yè)只顯示一張圖片,也可以一頁(yè)顯示一張圖和相鄰兩個(gè)圖片的一部分,此項(xiàng)目?jī)H僅是banner展示圖片,沒(méi)有多余的諸如指示器、頁(yè)面切換動(dòng)畫(huà)等效果代碼,詳見(jiàn)效果圖和案例代碼
    2021-11-11
  • 深入理解Activity之間的數(shù)據(jù)傳遞

    深入理解Activity之間的數(shù)據(jù)傳遞

    本篇文章是對(duì)Activity之間的數(shù)據(jù)傳遞進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下
    2013-05-05
  • Android實(shí)現(xiàn)彈出列表、單選、多選框

    Android實(shí)現(xiàn)彈出列表、單選、多選框

    這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)彈出列表、單選、多選框,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-10-10

最新評(píng)論

保山市| 塔河县| 布尔津县| 修文县| 花莲市| 左贡县| 萍乡市| 东乡| 东阿县| 阜新市| 金阳县| 昆山市| 托克逊县| 伊通| 新巴尔虎左旗| 息烽县| 壤塘县| 成安县| 郑州市| 和田市| 南岸区| 志丹县| 五常市| 孟津县| 抚远县| 苍山县| 高州市| 彰武县| 仲巴县| 米脂县| 广丰县| 龙门县| 丹阳市| 岱山县| 泰和县| 明水县| 高邑县| 洛浦县| 明光市| 舒城县| 疏勒县|