flutter基礎(chǔ)集錦

View篇

有幾種視圖框架

總體來說有兩種泣洞,Column和Row,前者表示豎直方向默色,后者表示水平方向球凰。

怎么實現(xiàn)類似wrap_content和match_parent的效果

Widget parent = Container(
  width: 360,
  height: 360,
  color: Colors.lightGreen,
  child: Column(
    mainAxisSize: MainAxisSize.max,
    children: <Widget>[
      Container(
        width: 120,
        height: 86,
        color: Colors.brown,
      ),
      Container(
        width: 120,
        height: 86,
        color: Colors.red,
      ),
      Container(
        width: 120,
        height: 86,
        color: Colors.cyan,
      ),
    ],
  ),
);

可以看到Container里面包含的Column面積設(shè)置的多大就展示多大的區(qū)域,主要起作用的是mainAxisSize和mainAxisAlignment參數(shù)腿宰。

image

mainAxisSize

表示剩余空間的占用情況呕诉,是最大限度(max)還是最小限度(min)的占用剩余空間

mainAxisAlignment

表示Column或Row里面子布局相互之間的空間應(yīng)該怎么分布。

  • MainAxisAlignment.start 表示所有子控件往首部的區(qū)域集中吃度,剩余空間集中在尾部
  • MainAxisAlignment.spaceBetween 表示所有子控件之間平分剩余的空間甩挫,不包含首尾
  • MainAxisAlignment.center 表示剩余的空間平均分配到子控件的首胃,而子控件之間沒有空間
  • MainAxisAlignment.spaceEvenly 表示子控件之間椿每,包含首尾都要均分剩余空間
  • MainAxisAlignment.end 表示所有子控件往尾部的區(qū)域集中伊者,剩余空間集中在首部
  • MainAxisAlignment.spaceAround 剩余空間除以子控件數(shù)量 + 1,子控件之間的間距是等分的间护,多出來的空間除以2亦渗,分布于子控件的首尾。比如剩余空間是42兑牡,有2個子控件央碟,控件之間的間隔是42 / (2 + 1) = 14,控件之間的間距是14,而兩個控件前后的間隔是14 / 2 = 7.

crossAxisAlignment

這個參數(shù)決定了與排布方向垂直方向子控件的位置。與mainAxisAlignment是相對的亿虽。

image

還是利用上面的代碼做例子說明:

Widget parent = Container(
  width: 360,
  height: 360,
  color: Colors.lightGreen,
  child: Column(
    mainAxisSize: MainAxisSize.max,
    mainAxisAlignment: MainAxisAlignment.spaceAround,
    crossAxisAlignment: CrossAxisAlignment.end,
    children: <Widget>[
      Container(
        width: 120,
        height: 86,
        color: Colors.brown,
      ),
      Container(
        width: 120,
        height: 86,
        color: Colors.red,
      ),
      Container(
        width: 120,
        height: 86,
        color: Colors.cyan,
      ),
    ],
  ),
);

子控件之間和首尾都有了空間菱涤,另外子控件對齊最右邊。如下:


image

到這里洛勉,我們就知道粘秆,Column的Main方向是豎直的峦筒,Cross方向是水平的晓褪,而Row正好相反虚茶,Main方向是水平的耕挨,Cross方向是豎直的帆精。

綜述一下抑月,要實現(xiàn)wrap_content效果就設(shè)置mainAxisSize為min竹观,并且mainAxisAlignment設(shè)置為start;要實現(xiàn)match_parent就設(shè)置mainAxisSize為max土辩,并且mainAxisAlignment不要設(shè)置為start或end或center输拇。

怎么設(shè)置視圖的寬高

一般情況下使用Container里面的width和height設(shè)置寬高摘符。

當然了,如果不想設(shè)置具體的寬高策吠,只想設(shè)置一個大體的約束逛裤,可以設(shè)置Container里面的constraints屬性,這個屬性對應(yīng)的類是BoxConstraints類猴抹,這個類有四個參數(shù)可以設(shè)置带族,如下:

BoxConstraints(
    minHeight: 360, 
    minWidth: 360, 
    maxWidth: 360, 
    maxHeight: 360);

怎么設(shè)置控件的背景顏色

Container里面有color參數(shù)可以設(shè)置背景顏色,前提是Container沒有顯式的設(shè)置decoration屬性蟀给。

decoration屬性用于為Container設(shè)置裝飾蝙砌,這個裝飾屬性對應(yīng)的是BoxDecoration類,如果設(shè)置了這個屬性就不能在Container中設(shè)置color的值了坤溃,只能通過decoration設(shè)置拍霜。

怎么設(shè)置點擊之后的波紋效果

InkWell可以滿足需求,父Widget用InkWell薪介,然后child參數(shù)設(shè)置為要點擊的子Widget祠饺。

怎么設(shè)置字體以及富文本

  • 普通文本
Text(
    "flutter教程",
    textAlign: TextAlign.center,
    style: TextStyle(
      fontSize: 12,
      color: Colors.lightGreenAccent,
      fontWeight: FontWeight.bold,
      decoration: TextDecoration.lineThrough,
    ),
)
  • 富文本
RichText(
    text: TextSpan(
        text: "first text",
        style: TextStyle(color: Colors.white, fontSize: 18),
        children: <TextSpan>[
          TextSpan(
            text: "second text",
            style: TextStyle(color: Colors.red, fontSize: 12),
          ),
        ],
    ),
);

怎么設(shè)置控件圓角

Container(
    decoration: BoxDecoration(
        borderRadius: BorderRadius.all(Radius.circular(12)))
);

怎么將控件背景設(shè)置為我想要的icon

Container(
  decoration: BoxDecoration(
    image: DecorationImage(
      image: AssetImage("images/your image source.png"),
      fit: BoxFit.cover,
    ),
  ),
);

怎么設(shè)置控件背景漸變

Container(
  decoration: BoxDecoration(
    gradient: LinearGradient(
        begin: Alignment.centerLeft,//起始方向
        end: Alignment.centerRight,//終止方向
        colors: [
          ColorUtil.hexToColor("#FF8602"),
          ColorUtil.hexToColor("#FE3F01")
    ]),
  ),
);

怎么為控件設(shè)置點擊事件

Widget backWidget = GestureDetector(
  child: Image.asset(
    "images/your image source",
    width: 24,
    height: 24,
  ),
  onTap: () => Navigator.of(context).pop(),//your own action
);

控件怎么居中

  • 第一種方法
Align(
    alignment: Alignment.center,
    child: Text(
      "flutter教程",
      textAlign: TextAlign.center,
      style: TextStyle(
        fontSize: 12,
        color: Colors.lightGreenAccent,
        fontWeight: FontWeight.bold,
        decoration: TextDecoration.lineThrough,
      ),
    ),
);
  • 第二種方法
Center(
    child: Text(
      "flutter教程",
      textAlign: TextAlign.center,
      style: TextStyle(
        fontSize: 12,
        color: Colors.lightGreenAccent,
        fontWeight: FontWeight.bold,
        decoration: TextDecoration.lineThrough,
      ),
    ),
)

控件怎么設(shè)置上下左右的margin

Widget parent = Container(
  margin: EdgeInsets.only(top: 40, bottom:40, left:40, right:40),
  child: childWidget,
);

控件怎么設(shè)置padding

Padding(
    child: Text("your own widget")
    padding: EdgeInsets.only(top: 40, bottom:40, left:40, right:40),
),

圖片怎么設(shè)置圓角

ClipRRect(
  borderRadius: new BorderRadius.circular(8.0),
  child: Image.network(
    goodsImage,
    width: 80,
    height: 80,
   ),
),

列表怎么用

  • ListView.builder
ListView.builder(
    itemBuilder: (context, index) {
      return Text("your child list item widget");
    },
    itemCount: dataSet.length,
),
  • ListView.separated可以設(shè)置子item之間的分割,separatorBuilder屬性返回間隔widget
Widget parent = ListView.separated(
    itemBuilder: (BuildContext context, int index) {
      return Text("this is index $index");
    },
    separatorBuilder: (BuildContext context, int index) {
      return Container(
        width: 360,
        height: 8,
        color: Colors.green,
      );
    },
    itemCount: 20);
  • ListView.custom可以自定義子item的展現(xiàn)模式
Widget parent = ListView.custom(
  childrenDelegate: SliverChildBuilderDelegate(
    (BuildContext context, int index) {
      return Text("this is index $index");
    },
    childCount: 20,
  ),
);

滾動視圖怎么用

  • SingleChildScrollView 只能包含有一個child
Widget parent = SingleChildScrollView(
  child: Column(
    children: <Widget>[
      Container(
        color: Colors.blue,
        child: Text("this is 1"),
        width: 360,
        height: 240,
      ),
      Container(
        color: Colors.red,
        child: Text("this is 2"),
        width: 360,
        height: 240,
      ),
      Container(
        color: Colors.green,
        child: Text("this is 3"),
        width: 360,
        height: 240,
      ),
      Container(
        color: Colors.cyan,
        child: Text("this is 4"),
        width: 360,
        height: 240,
      ),
      Container(
        color: Colors.yellow,
        child: Text("this is 5"),
        width: 360,
        height: 240,
      ),
    ],
  ),
);
  • CustomScrollView 這個控件比較強大汁政,里面可以有多個child widget,并且必須是sliver屬性的控件道偷。

不包含appBar

CustomScrollView(
    controller: _scrollController,
    slivers: <Widget>[
      SliverList(
          delegate: SliverChildBuilderDelegate(
        (context, index) {
          return Text("child item");
        },
        childCount: dataSet.length,
      )),
      SliverToBoxAdapter(child: footerView),
    ],
);

其實有sliver屬性的控件還有SliverGrid,SliverAppBar等记劈。

  • NestedScrollView 有收縮伸展appBar能力的滾動控件
Widget childContent = NestedScrollView(
    headerSliverBuilder: (context, innerBoxIsScrolled) => [
          SliverOverlapAbsorber(
            child: SliverSafeArea(
              top: false,
              sliver: SliverAppBar(
                backgroundColor: Colors.white,
                expandedHeight: expandedHeight,
                flexibleSpace: featureParentWidget,
                pinned: true,
                floating: true,
                elevation: 179,
                bottom: PreferredSize(
                  child: sortItemWidget,
                  preferredSize: Size(double.infinity, 42),
                ),
              ),
            ),
            handle:
              NestedScrollView.sliverOverlapAbsorberHandleFor(context),
          ),
        ],
    body: body);

expandedHeight 指可以擴展的高度

flexibleSpace 在appbar里面還可以設(shè)置彈性空間的widget勺鸦,比如希望appbar伸展之后的背景是一張圖片,就可以在這里設(shè)置

pinned
appbar是否應(yīng)該保留在滾動視圖頂部

floating
用戶向appbar滾動時目木,是否應(yīng)立即顯示appbar换途。

其他參數(shù)參考:

https://api.flutter.dev/flutter/material/SliverAppBar-class.html

各個參數(shù)的示例圖片如下:


image

TabLayout + ViewPager怎么實現(xiàn)

使用TabBar和TabBarView實現(xiàn),具體實現(xiàn)方式如下:

  • TabBar實現(xiàn)title
Widget titleBar = Container(
  height: 48,
  color: Colors.green,
  margin: EdgeInsets.only(top: 24),
  child: TabBar(
    tabs: titleArray.map((f) {
      return Text(
        f,
        style: TextStyle(color: Colors.red, fontSize: 14),
      );
    }).toList(),
    onTap: _tabClicked,
    controller: _controller,
    indicatorSize: TabBarIndicatorSize.label,
    isScrollable: false,
    indicatorColor: Colors.pink,
  ),
);
  • TabBarView實現(xiàn)
Widget contentView = Container(
    color: Colors.amber,
    child: TabBarView(
      controller: _controller,
      children: titleArray.map((f) {
        return Center(
          child: Text(
            "this is $f",
            style: TextStyle(color: Colors.red, fontSize: 24),
          ),
        );
      }).toList(),
    ));

一個實現(xiàn)了Tab標題,一個實現(xiàn)了Tab下的具體頁面內(nèi)容军拟,兩者產(chǎn)生關(guān)系的紐帶是_controller變量剃执,這個變量是TabController類型。

全部實現(xiàn)代碼如下:

class TabViewDemoWidget extends StatelessWidget {
  TabController _controller;
  var initSelectedIndex = 0;

  final titleArray = ["A", "B", "C", "D", "E"];

  _tabScrollListener() {
    if (_controller.index != initSelectedIndex) {
      //todo tab changed
    }
  }

  @override
  Widget build(BuildContext context) {
    _controller = TabController(
      initialIndex: initSelectedIndex, //初始選中頁
      length: 5, //總頁數(shù)
      vsync: ScrollableState(),
    );

    _controller.addListener(_tabScrollListener);

    Widget titleBar = Container(
      height: 48,
      color: Colors.green,
      margin: EdgeInsets.only(top: 24),
      child: TabBar(
        tabs: titleArray.map((f) {
          return Text(
            f,
            style: TextStyle(color: Colors.red, fontSize: 14),
          );
        }).toList(),
        onTap: _tabClicked,
        controller: _controller,
        indicatorSize: TabBarIndicatorSize.label,
        isScrollable: false,
        indicatorColor: Colors.pink,
      ),
    );

    Widget contentView = Container(
        color: Colors.amber,
        child: TabBarView(
          controller: _controller,
          children: titleArray.map((f) {
            return Center(
              child: Text(
                "this is $f",
                style: TextStyle(color: Colors.red, fontSize: 24),
              ),
            );
          }).toList(),
        ));
    return Scaffold(
      appBar: PreferredSize(
        child: titleBar,
        preferredSize: Size(double.infinity, 48),
      ),
      body: contentView,
    );
  }

  _tabClicked(int index) {}
}

底部導(dǎo)航欄怎么實現(xiàn)

Scaffold有一個bottomNavigationBar屬性懈息,設(shè)置這個屬性就可以實現(xiàn)肾档,具體實現(xiàn)如下:

Scaffold(
    body: Container(),//define your own body content
    bottomNavigationBar: BottomNavigationBar(
        currentIndex: _selectedIndex,
        type: BottomNavigationBarType.fixed,
        selectedFontSize: 12,
        unselectedFontSize: 12,
        unselectedItemColor: Color.fromARGB(0XFF, 0X40, 0X40, 0X40),
        selectedItemColor: Color.fromARGB(0XFF, 0XFE, 0X3F, 0X01),
        items: [
          BottomNavigationBarItem(
              activeIcon: Container(
                width: 24,
                height: 24,
                child: Image.asset("images/main_activity_main_pressed.png"),
              ),
              icon: Container(
                width: 24,
                height: 24,
                child: Image.asset("images/main_activity_main_normal.png"),
              ),
              title: Text(
                "主頁",
              )),
          BottomNavigationBarItem(
              activeIcon: Container(
                width: 24,
                height: 24,
                child: Image.asset(
                    "images/main_activity_classify_pressed.png"),
              ),
              icon: Container(
                width: 24,
                height: 24,
                child:
                    Image.asset("images/main_activity_classify_normal.png"),
              ),
              title: Text(
                "分類",
              )),
        ],
        onTap: onItemTaped),
  );

onTap 屬性要給一個item點擊的響應(yīng)事件,攜帶index參數(shù)辫继。當這個方法觸發(fā)之后可以做狀態(tài)改變操作怒见。

AlertDialog怎么實現(xiàn)

flutter自身有AlertDialog Widget,但是可能不滿足我們的需求姑宽,可以自定義

Dialog Widget里面有一個child屬性遣耍,可以設(shè)置自己想要的對話框形式,通過showDialog方法將對話框顯示出來低千。

class DialogDemoWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return WillPopScope(
        onWillPop: () => _willPopScope(context),
        child: Scaffold(
          body: Center(
              child: Container(
            child: Text("show dialog demo"),
          )),
        ));
  }

  Future<bool> _willPopScope(BuildContext context) async {
    return showDialog(
            context: context,
            builder: (context) {
              //design your own dialog content
              Widget dialogChild = Container(
                width: 302,
                height: 242,
                child: Center(
                  child: Text("this is dialog"),
                ),
              );
              return Dialog(
                backgroundColor: null,
                elevation: 24,
                shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.all(Radius.circular(24))),
                child: dialogChild,
              );
            }) ??
        false;
  }
}

ProgressDialog怎么實現(xiàn)

ProgressDialog可以通過PopupRoute實現(xiàn)配阵,他的原理是在已有的路由上面再繪制一層。

借鑒別人的實現(xiàn)示血,加自己的改造

///加載彈框
class ProgressDialog {
  static bool _isShowing = false;

  ///展示
  static void showProgress(BuildContext context, {Widget child}) {
    if (child == null) {//define your own child
      child = Theme(
          data: Theme.of(context)
              .copyWith(accentColor: ColorUtil.hexToColor("#FFFE7501")),
          child: Stack(
            children: <Widget>[
              Center(
                child: Container(
                  width: 46,
                  height: 46,
                  child: new CircularProgressIndicator(
                    strokeWidth: 3,
                  ),
                ),
              ),
              Center(
                  child: Text(
                    "加載中",
                    style: TextStyle(
                        fontSize: 12, color: ColorUtil.hexToColor("#FF202020")),
                  )),
            ],
          ));
    }

    if (!_isShowing) {
      _isShowing = true;
      Navigator.push(
        context,
        _PopRoute(
          child: _Progress(
            child: child,
          ),
        ),
      );
    }
  }

  ///隱藏
  static void dismiss(BuildContext context) {
    if (context == null) {
      return;
    }
    if (_isShowing) {
      Navigator.of(context).pop();
      _isShowing = false;
    }
  }
}

///Widget
class _Progress extends StatelessWidget {
  final Widget child;

  _Progress({
    Key key,
    @required this.child,
  })  : assert(child != null),
        super(key: key);

  @override
  Widget build(BuildContext context) {
    return Material(
        color: Colors.transparent,
        child: Center(
          child: child,
        ));
  }
}

///Route
class _PopRoute extends PopupRoute {
  final Duration _duration = Duration(milliseconds: 300);
  Widget child;

  _PopRoute({@required this.child});

  @override
  Color get barrierColor => null;

  @override
  bool get barrierDismissible => true;

  @override
  String get barrierLabel => null;

  @override
  Widget buildPage(BuildContext context, Animation<double> animation,
      Animation<double> secondaryAnimation) {
    return child;
  }

  @override
  Duration get transitionDuration => _duration;
}

點擊控件之后怎么響應(yīng)事件更新視圖

Widget從大體上可以分為StatelessWidget和StatefullWidget,其中StatefullWidget可以根據(jù)setState方法觸發(fā)對整個視圖的刷新。

粗略地救拉,StatefullWidget的生命周期可以分為initState,build,dispose难审。

在調(diào)用了setState方法之后,會觸發(fā)build方法使子widget重新構(gòu)建亿絮。

方法如下:

class SetStateDemoWidget extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return SetStateDemoWidgetState();
  }
}

class SetStateDemoWidgetState extends State<SetStateDemoWidget> {
  var index = 0;

  @override
  void initState() {
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    Widget numberText = Text(
      "$index",
      style: TextStyle(fontSize: 18, color: Colors.cyan),
    );
    Widget addButton = Container(
      margin: EdgeInsets.only(top: 16),
      child: FlatButton(
        onPressed: _buttonClicked,
        child: Icon(
          Icons.add,
          size: 36,
        ),
      ),
    );

    return Scaffold(
      body: Container(
        width: double.infinity,
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.max,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            numberText,
            addButton,
          ],
        ),
      ),
    );
  }

  _buttonClicked() {
    setState(() {
      ++index;
    });
  }
}

在_buttonClicked方法響應(yīng)了點擊事件之后告喊,再調(diào)用setState方法將index自增,此時會觸發(fā)build重新渲染view tree.

InheritedWidget怎么用

InheritedWidget用來解決父Widget的數(shù)據(jù)變化時通知子Widget派昧。

具體用法是黔姜,父Widget繼承自InheritedWidget,并定義一個static方法用來對外提供對象實例蒂萎,另外有一個child屬性用于設(shè)置子Widget秆吵,還要設(shè)置數(shù)據(jù),用于給這個子widget引用五慈。同時覆寫是否刷新數(shù)據(jù)的方法纳寂。當數(shù)據(jù)變化時,子widget就收到通知了泻拦。使用方法如下:

///先繼承InheritedWidget
class InheritedData extends InheritedWidget {
  final String data;//用于給子Widget使用

  InheritedData({
    this.data,
    Widget child,//子Widget
  }) : super(child: child);
  //提供static方法給外部使用
  static InheritedData of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<InheritedData>();
  }
  //判斷是否通知子Widget
  @override
  bool updateShouldNotify(InheritedData old) => true;
}

///子Widget定義
class TestInheritedDataWidget extends StatefulWidget {
  @override
  _TestInheritedDataWidgetState createState() =>
      _TestInheritedDataWidgetState();
}

class _TestInheritedDataWidgetState extends State<TestInheritedDataWidget> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(
      //引用父Widget的數(shù)據(jù)
        "${InheritedData.of(context).data}",
        style: TextStyle(fontSize: 18, color: Colors.pink),
      ),
    );
  }

  @override
  void didChangeDependencies() {
    super.didChangeDependencies();
    //收到通知
    print("didChangeDependencies invoked");
  }
}

class ParentWidget extends StatefulWidget {
  @override
  _ParentWidgetState createState() => _ParentWidgetState();
}

class _ParentWidgetState extends State<ParentWidget> {
  var data = "you are beautiful";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: GestureDetector(
        child: Container(
          color: Colors.cyan,
          width: double.infinity,
          child: InheritedData(
            data: data,
            child: TestInheritedDataWidget(),
          ),
        ),
        onTap: _buttonClicked,
      ),
    );
  }

  _buttonClicked() {
    setState(() {
    //點擊按鈕之后數(shù)據(jù)發(fā)生變化
      data = "in white";
    });
  }
}

LinearLayout的sumWeight以及單個child的weight怎么實現(xiàn)

用Expanded Widget可以實現(xiàn)這種效果毙芜。源碼里面針對Expanded的注釋是它作為Row,Column争拐,F(xiàn)lex的子Widget腋粥,里面可以含有多個子Widget,并且每個子widget通過flex來分配剩余的空間。

用下面這個例子說明:

class FlexWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        width: double.infinity,
        child: Row(
          mainAxisAlignment: MainAxisAlignment.center,
          mainAxisSize: MainAxisSize.max,
          crossAxisAlignment: CrossAxisAlignment.center,
          children: <Widget>[
            Expanded(
              child: Container(
                color: Colors.pink,
              ),
              flex: 1,
            ),
            Expanded(
              child: Container(
                color: Colors.lightGreenAccent,
              ),
              flex: 2,
            ),
          ],
        ),
      ),
    );
  }
}

運行下看效果可以知道隘冲,粉色占據(jù)了三分之一金赦,淺綠色占據(jù)了三分之二的空間。

視圖比較復(fù)雜对嚼,寫的代碼比較長夹抗,導(dǎo)致代碼結(jié)尾有海量的反括號,怎么優(yōu)化

還是以上面實現(xiàn)weight效果的代碼做示例纵竖,可以看到代碼后邊有9個反括號漠烧,看起來比較累,容易造成心理壓力靡砌。

class FlexWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var child1 = Expanded(
      child: Container(
        color: Colors.pink,
      ),
      flex: 1,
    );

    var child2 = Expanded(
      child: Container(
        color: Colors.lightGreenAccent,
      ),
      flex: 2,
    );

    var child3 = Row(
      mainAxisAlignment: MainAxisAlignment.center,
      mainAxisSize: MainAxisSize.max,
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        child1,
        child2,
      ],
    );

    var child4 = Container(
      width: double.infinity,
      child: child3,
    );

    return Scaffold(
      body: child4,
    );
  }
}

把每個child拆出來作為一個widget賦值給一個變量已脓,這樣就可以將一些小的邏輯分出來,避免每個子widget的業(yè)務(wù)邏輯堆積在一起通殃,使得邏輯看起來比較清晰度液。

FrameLayout怎么實現(xiàn)

Stack配合Positioned使用,可以實現(xiàn)FrameLayout的效果画舌。

class StackDemoWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    var child = Stack(
      children: <Widget>[
        Container(
          width: 480,
          height: 480,
          color: Colors.pink,
        ),
        Positioned(
          child: Container(
            width: 360,
            height: 360,
            color: Colors.amber,
          ),
          top: 84,
          bottom: 84,
        ),
        Positioned(
          child: Container(
            width: 240,
            height: 240,
            color: Colors.deepOrange,
          ),
          right: 12,
          left: 12,
        )
      ],
    );

    var child1 = Center(
      child: Container(
        child: child,
        width: 480,
        height: 480,
      ),
    );
    return Scaffold(
      body: child1,
    );
  }
}

Positioned的左右上下屬性是相對于Stack邊緣的偏移堕担,如果設(shè)置左右或上下會影響到Positioned的寬高。

數(shù)據(jù)處理篇

Android的SharePreference在這里怎么用

在工程下的pubspec.yaml文件中添加引用:

dependencies:
  shared_preferences: ^0.5.6

然后在命令行執(zhí)行:

flutter pub get

然后在代碼中就可以使用SharePreference的功能了曲聂。flutter中的SharePreference跟Android里面的一樣霹购,可能也會阻塞UI線程,需要使用async修飾相關(guān)方法朋腋。使用舉例:

import 'package:shared_preferences/shared_preferences.dart';

class SharedPreferenceUtil {
  static SharedPreferences _prefs;

  SharedPreferenceUtil._privateConstructor();

  static final SharedPreferenceUtil instance =
      SharedPreferenceUtil._privateConstructor();

  Future<SharedPreferences> get prefs async {
    if (_prefs != null) return _prefs;
    _prefs = await SharedPreferences.getInstance();
    return prefs;
  }

  static const String KEY_SEARCH_HISTORY = "search_history";
  static const String KEY_USER_TOKEN = "token";

  void setValue(String key, String value) async {
    SharedPreferences sharedPreferences = await instance.prefs;
    sharedPreferences.setString(key, value);
  }

  Future<String> getValue(String key) async {
    SharedPreferences sharedPreferences = await instance.prefs;
    return sharedPreferences.getString(key);
  }
}

在這里我們把SharedPreferences抽象出來齐疙,做一個單例對外提供。所有的set和get必須用async修飾旭咽,因為他是阻塞式的贞奋。調(diào)用的地方舉例:

SharedPreferenceUtil.instance
    .getValue(SharedPreferenceUtil.KEY_USER_TOKEN)
    .then((value) {
  //do something
});

這里getValue之后,然后調(diào)用then方法穷绵,意思是執(zhí)行完了轿塔,在then方法里面獲取到返回的值,可以做接下來的操作了请垛。

Android的數(shù)據(jù)庫怎么用

在工程下的pubspec.yaml文件中添加引用:

dependencies:
  sqflite: ^1.1.7+3

然后在命令行執(zhí)行:

flutter pub get

然后再代碼中就可以使用數(shù)據(jù)庫的相關(guān)功能了催训。

使用示例:

import 'package:flutter_hello/database/user_info_impl.dart';
import 'package:sqflite/sqflite.dart';
import 'package:path/path.dart';

class DataBaseHelper {
  static Database _database;

  static final _databaseName = "coupon.db";
  static final _databaseVersion = 1;

  static const String _CREATE_TABLE = "CREATE TABLE IF NOT EXISTS ";

  DataBaseHelper._privateConstructor();

  static final DataBaseHelper instance = DataBaseHelper._privateConstructor();

  Future<Database> get database async {
    if (_database != null) return _database;
    _database = await _initDatabase();
    return _database;
  }

  _initDatabase() async {
    String path = join(await getDatabasesPath(), _databaseName);
    return await openDatabase(path,
        version: _databaseVersion, onCreate: _onCreate);
  }

  Future _onCreate(Database db, int version) async {
    await db.execute(_CREATE_TABLE +
        UserInfoImpl.TABLE_USER_INFO +
        UserInfoImpl.generateCreateSql());
  }

  Future<int> insert(String dbName, Map<String, dynamic> data) async {
    Database db = await instance.database;
    return db.insert(
      dbName,
      data,
    );
  }

  Future<List<Map<String, dynamic>>> query(String dbName) async {
    Database db = await instance.database;
    final List<Map<String, dynamic>> maps = await db.query(dbName);
    return maps;
  }

  Future<void> update(String tableName, Map<String, dynamic> data, String keyId,
      String equalValue) async {
    Database db = await instance.database;
    await db.update(
      tableName,
      data,
      where: "$keyId = ?",
      whereArgs: [equalValue],
    );
  }

  Future<void> delete(String tableName, String keyId, String equalValue) async {
    Database db = await instance.database;
    await db.delete(
      tableName,
      where: "$keyId = ?",
      whereArgs: [equalValue],
    );
  }
}

這是一個數(shù)據(jù)庫操作的類,將增刪改查抽象出來宗收,做具體的操作漫拭。在做操作之前獲取數(shù)據(jù)庫操作句柄database,其實他也是一個單例混稽,使用了單例模式采驻。

具體操作類實現(xiàn)之后审胚,如果我們要操作具體的某個表就比較方便了。比如針對user_info這個表的操作類UserInfoImpl礼旅,我們可以這樣操作:

insertUserInfo(UserInfoBean userInfoBean) {
    DataBaseHelper.instance.insert(TABLE_USER_INFO, userInfoBean.toJson());
}

Future<UserInfoBean> getUserInfo() async {
    List<Map<String, dynamic>> lists =
        await DataBaseHelper.instance.query(TABLE_USER_INFO);
    if (lists != null && lists.length > 0) {
      return UserInfoBean.fromJson(lists[0]);
    }
    return null;
}

deleteUserInfo(String userId) {
    DataBaseHelper.instance.delete(TABLE_USER_INFO, "id", userId);
}

updateUserInfo(UserInfoBean bean) {
    DataBaseHelper.instance
        .update(TABLE_USER_INFO, bean.toJson(), "id", bean.id.toString());
}

Json數(shù)據(jù)怎么處理

在工程下的pubspec.yaml文件中添加引用:

//庫版本可能已經(jīng)更新膳叨,可以在https://pub.dev/packages/網(wǎng)站搜索最新的版本
dev_dependencies:
  build_runner: ^1.0.0
  json_serializable: ^2.0.0

然后在命令行執(zhí)行:

flutter pub get

接下來就可以處理json數(shù)據(jù)了。處理json數(shù)據(jù)主要分三個步驟:

  • 根據(jù)網(wǎng)絡(luò)請求的字段痘系,定義對應(yīng)的類及屬性字段
  • 在類中添加序列化和反序列化接口
  • 通過flutter指令生成對應(yīng)的賦值處理

用以下json數(shù)據(jù)舉例說明:

{
    "total_count":12,
    "total_page":1,
    "page":1,
    "data":[]
}

首先定義類及屬性字段菲嘴,文件名是withdraw_detail_bean.dart

import 'package:json_annotation/json_annotation.dart';

part 'withdraw_detail_bean.g.dart';

@JsonSerializable()
class WithdrawDetailBean {
  WithdrawDetailBean(this.data, this.totalPage, this.totalCount, this.page);

  @JsonKey(name: "total_count")
  int totalCount;
  @JsonKey(name: "total_page")
  int totalPage;
  int page;
  List<WithDrawItemBean> data;

  factory WithdrawDetailBean.fromJson(Map<String, dynamic> json) =>
      _$WithdrawDetailBeanFromJson(json);

  Map<String, dynamic> toJson() => _$WithdrawDetailBeanToJson(this);
}

首先要添加part

'withdraw_detail_bean.g.dart',因為part關(guān)鍵字可以幫助創(chuàng)建一個模塊化的汰翠,可共享的代碼庫龄坪。后面執(zhí)行指令的時候可以生成一個withdraw_detail_bean.g.dart文件,里面包含了當前數(shù)據(jù)序列化相關(guān)的處理代碼复唤。

其次在定義的類前面添加JsonSerializable注解健田。

定義構(gòu)造函數(shù),定義與返回數(shù)據(jù)對應(yīng)的key佛纫,通過JsonKey注解妓局,可以簡化key對應(yīng)的變量命名。

定義序列化和反序列化的接口呈宇,用于外部調(diào)用

最后一步就是執(zhí)行生成處理json的代碼好爬,用指令實現(xiàn),分兩種攒盈。

一種是一次生成抵拘,后面再有修改的話,再執(zhí)行型豁,再生成:

flutter packages pub run build_runner build

另外一種是持續(xù)生成,一旦修改了json定義類尚蝌,就會馬上自動生成對應(yīng)的處理類:

flutter packages pub run build_runner watch

最后可以看到data數(shù)組里面定義了一個WithDrawItemBean類迎变,同樣的WithDrawItemBean類,需要再像上面這樣定義一遍就可以了飘言。

網(wǎng)絡(luò)請求

網(wǎng)絡(luò)請求一般借助現(xiàn)有的庫實現(xiàn)衣形,http庫對于各種方式的網(wǎng)絡(luò)請求支持比較好,網(wǎng)址是:https://pub.dev/packages/http姿鸿。

get請求

這里以最復(fù)雜的情況做例子谆吴,query + header:

Future<NetResponseBean> _getResponse(
      String baseUrl, String api, Map<String, String> queryMap) async {
    var _client = http.Client();
    if (!api.contains("?")) {
      api = api + "?";
    }
    try {
      var url = baseUrl + api + _getQuery(queryMap);
    
      Map<String, String> headers = Map();
      _getCommonHeaders(headers);
      http.Response response = await _client.get(url, headers: headers);
      if (response.statusCode == HttpStatus.ok) {
        var body = response.body;
        Map<String, dynamic> data = jsonDecode(body);
        NetResponseBean responseData = NetResponseBean.fromJson(data);
        if (responseData != null) {
          return responseData;
        }
      }
    } catch (exception, stackTrace) {
      print("_getResponse stackTrace:$stackTrace");
    } finally {
      if (_client != null) {
        _client.close();
      }
    }
    return null;
}

第一步,獲取client對象苛预,這個對象在finally里面必須關(guān)掉句狼,跟Android的操作是一樣的。

第二步热某,拼接query到url中腻菇。

第三步胳螟,填充header到一個Map集合中。

這幾步走完了調(diào)用對應(yīng)的接口筹吐,填充對應(yīng)的參數(shù)就可以了糖耸。

檢查返回碼,ok的話丘薛,獲取body數(shù)據(jù)嘉竟,轉(zhuǎn)換成json格式,然后用對應(yīng)json類序列化就可以使用返回的數(shù)據(jù)了洋侨。

post請求

這里以最復(fù)雜的情況舉例舍扰,header + request body

Future<NetResponseBean> _getPostResponse(
      String api, Map<String, String> parameters) async {
    var _client = http.Client();
    var url = _BAE_URL + api;
    try {
      //設(shè)置header
      Map<String, String> headersMap = new Map();
      _getCommonHeaders(headersMap);
      http.Response response = await _client.post(url,
          headers: headersMap, body: parameters, encoding: Utf8Codec());
      if (response.statusCode == 200) {
        String bodyData = response.body;
        if (bodyData != null) {
          Map<String, dynamic> data = jsonDecode(bodyData);
          NetResponseBean response = NetResponseBean.fromJson(data);
          return response;
        }
      } else {
        print('_getPostResponse error');
      }
    } catch (exception) {
      print("_getPostResponse fail:" + exception.toString());
    } finally {
      if (_client != null) {
        _client.close();
      }
    }
    return null;
}

跟上面get請求的流程差別不大,主要是接口調(diào)用不同凰兑,另外需要注意的是這里body是dynamic類型妥粟,傳的是Map集合。當然也可以傳json字符串吏够,一個常見的場景是bean類轉(zhuǎn)化為json勾给,然后傳遞,對應(yīng)的處理是:

var body = json.encode(jsonBean);

上傳

Future requestUploadFile(File file, Function callBack) async {
    var url = _URL + "your sub url";
    var client = http.MultipartRequest("post", Uri.parse(url));
    http.MultipartFile.fromPath(
      'file',
      file.path,
    ).then((http.MultipartFile file) {
      _getOSCommonHeaders(client.headers);
      client.files.add(file);
      client.fields["description"] = "descriptiondescription";
      client.send().then((http.StreamedResponse response) {
        if (response.statusCode == 200) {
          response.stream.transform(utf8.decoder).join().then((String string) {
            print("requestUploadFile:$string");
            Map<String, dynamic> data = jsonDecode(string);
            if (data != null && data.length > 0) {
              NetResponseBean uploadResponse = NetResponseBean.fromJson(data);
              if (uploadResponse != null) {
                if (uploadResponse.code == 0) {
                  UploadImageResultBean uploadImageResultBean =
                      UploadImageResultBean.fromJson(uploadResponse.data);
                  callBack(uploadImageResultBean.fileUrl, 0);
                } else {
                  callBack("", uploadResponse.code);
                }
              }
            }
          });
        }
      }).catchError((error) {
        print("requestUploadFile error:$error");
      });
    });
  }

代碼如上锅知,一般性的上傳操作播急。

下載

Future<File> _downloadFile(String url, String filename) async {
    http.Client _client = new http.Client();
    try {
      var req = await _client.get(Uri.parse(url));
      var bytes = req.bodyBytes;
      String dir = (await getApplicationDocumentsDirectory()).path;
      File file = new File('$dir/$filename');
      await file.writeAsBytes(bytes);
      return file;
    } finally {
      _client.close();
    }
}

上面是比較簡單的下載,下面從別處抄一個帶進度的:

https://gist.github.com/ajmaln/c591cfb71d66bb6e688fe7027cbbe606

import 'dart:typed_data';
import 'dart:io';

import 'package:http/http.dart';
import 'package:path_provider/path_provider.dart';


downloadFile(String url, {String filename}) async {
  var httpClient = http.Client();
  var request = new http.Request('GET', Uri.parse(url));
  var response = httpClient.send(request);
  String dir = (await getApplicationDocumentsDirectory()).path;
  
  List<List<int>> chunks = new List();
  int downloaded = 0;
  
  response.asStream().listen((http.StreamedResponse r) {
    
    r.stream.listen((List<int> chunk) {
      // Display percentage of completion
      debugPrint('downloadPercentage: ${downloaded / r.contentLength * 100}');
      
      chunks.add(chunk);
      downloaded += chunk.length;
    }, onDone: () async {
      // Display percentage of completion
      debugPrint('downloadPercentage: ${downloaded / r.contentLength * 100}');
      
      // Save the file
      File file = new File('$dir/$filename');
      final Uint8List bytes = Uint8List(r.ntentLength);
      int offset = 0;
      for (List<int> chunk in chunks) {
        bytes.setRange(offset, offset + chunk.length, chunk);
        offset += chunk.length;
      }
      await file.writeAsBytes(bytes);   
      return;       
   });
}

公眾號:


微信公眾號二維碼.jpg
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
禁止轉(zhuǎn)載售睹,如需轉(zhuǎn)載請通過簡信或評論聯(lián)系作者桩警。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市昌妹,隨后出現(xiàn)的幾起案子捶枢,更是在濱河造成了極大的恐慌,老刑警劉巖飞崖,帶你破解...
    沈念sama閱讀 222,252評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烂叔,死亡現(xiàn)場離奇詭異,居然都是意外死亡固歪,警方通過查閱死者的電腦和手機蒜鸡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,886評論 3 399
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來牢裳,“玉大人逢防,你說我怎么就攤上這事∑蜒叮” “怎么了忘朝?”我有些...
    開封第一講書人閱讀 168,814評論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長伶椿。 經(jīng)常有香客問我辜伟,道長氓侧,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,869評論 1 299
  • 正文 為了忘掉前任导狡,我火速辦了婚禮约巷,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘旱捧。我一直安慰自己独郎,他們只是感情好,可當我...
    茶點故事閱讀 68,888評論 6 398
  • 文/花漫 我一把揭開白布枚赡。 她就那樣靜靜地躺著氓癌,像睡著了一般。 火紅的嫁衣襯著肌膚如雪贫橙。 梳的紋絲不亂的頭發(fā)上贪婉,一...
    開封第一講書人閱讀 52,475評論 1 312
  • 那天,我揣著相機與錄音卢肃,去河邊找鬼疲迂。 笑死,一個胖子當著我的面吹牛莫湘,可吹牛的內(nèi)容都是我干的尤蒿。 我是一名探鬼主播,決...
    沈念sama閱讀 41,010評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼幅垮,長吁一口氣:“原來是場噩夢啊……” “哼腰池!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起忙芒,我...
    開封第一講書人閱讀 39,924評論 0 277
  • 序言:老撾萬榮一對情侶失蹤示弓,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后呵萨,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體避乏,經(jīng)...
    沈念sama閱讀 46,469評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,552評論 3 342
  • 正文 我和宋清朗相戀三年甘桑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片歹叮。...
    茶點故事閱讀 40,680評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡跑杭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出咆耿,到底是詐尸還是另有隱情德谅,我是刑警寧澤,帶...
    沈念sama閱讀 36,362評論 5 351
  • 正文 年R本政府宣布萨螺,位于F島的核電站窄做,受9級特大地震影響愧驱,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜椭盏,卻給世界環(huán)境...
    茶點故事閱讀 42,037評論 3 335
  • 文/蒙蒙 一组砚、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掏颊,春花似錦糟红、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,519評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至准浴,卻和暖如春事扭,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背乐横。 一陣腳步聲響...
    開封第一講書人閱讀 33,621評論 1 274
  • 我被黑心中介騙來泰國打工求橄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人晰奖。 一個月前我還...
    沈念sama閱讀 49,099評論 3 378
  • 正文 我出身青樓谈撒,卻偏偏與公主長得像,于是被迫代替她去往敵國和親匾南。 傳聞我的和親對象是個殘疾皇子啃匿,可洞房花燭夜當晚...
    茶點故事閱讀 45,691評論 2 361

推薦閱讀更多精彩內(nèi)容

  • Flutter是Google開發(fā)的一套全新的跨平臺、開源UI框架(本質(zhì)上就是sdk)蛆楞。 支持iOS溯乒、Android...
    HarveyLegend閱讀 8,218評論 1 43
  • 今天開始裆悄,我要努力了。 明天開始臂聋,我要背單詞了光稼。 這頓之后,我要開始減肥了孩等。 改變的決心下了無數(shù)個艾君,卻一次都沒開始...
    思遠舟閱讀 453評論 0 0
  • 春天漸逝,夏天悄來肄方!又到了露肉的季節(jié)冰垄!發(fā)福利的時候到了!剛才在場內(nèi)遇一小美女权她!穿一黃色套裙虹茶,外層是一紗窗逝薪,內(nèi)層是一...
    那一見的風情521閱讀 165評論 0 1
  • 之前不小心把JAVA分類寫成了java發(fā)布了,然后又改了回來蝴罪,并且手動地在博客public/categories/...
    vzardlloo閱讀 797評論 0 1
  • 這兩天是和財富相關(guān)的時間洲炊,好朋友讓我?guī)退龁栆幌仑敻唬? 不用說感局,這個問題自然要幫忙的。 和她做完簡...
    郗紅嘉閱讀 215評論 1 3