Flutter跨平台移动端开发丨SingleChildScrollView、ListView、GridView、CustomScrollView、ScrollController

目录

  1. SingleChildScrollView(可滑动 View)
  2. ListView(列表 View)
  3. GridView(网格 View)
  4. CustomScrollView(自定义滑动 View)
  5. ScrollController(控制器)

SingleChildScrollView(可滑动 View)

SingleChildScrollView 类似 Android 中的 scrollview ,且同样的只可包含有一个子元素

  const SingleChildScrollView({
    Key key,
    this.scrollDirection = Axis.vertical,
    this.reverse = false,
    this.padding,
    bool primary,
    this.physics,
    this.controller,
    this.child,
    this.dragStartBehavior = DragStartBehavior.down,
  }) : assert(scrollDirection != null),
       assert(dragStartBehavior != null),
       assert(!(controller != null && primary == true),
          'Primary ScrollViews obtain their ScrollController via inheritance from a PrimaryScrollController widget. '
          'You cannot both set primary to true and pass an explicit controller.'
       ),
       primary = primary ?? controller == null && identical(scrollDirection, Axis.vertical),
       super(key: key);
  • key:当前元素的唯一标识符(类似于 Android 中的 id)
  • scrollDirection:滚动方向,默认是垂直
  • reverse:是否按照阅读方向相反的方向滑动。
  • padding:填充距离
  • primary:是否使用 widget 树中默认的 PrimaryScrollController 。当滑动方向为垂直方向(scrollDirection值为Axis.vertical)并且controller没有指定时,primary默认为true
  • physics:此属性接受一个ScrollPhysics对象,它决定可滚动Widget如何响应用户操作,比如用户滑动完抬起手指后,继续执行动画;或者滑动到边界时,如何显示。默认情况下,Flutter会根据具体平台分别使用不同的ScrollPhysics对象,应用不同的显示效果,如当滑动到边界时,继续拖动的话,在iOS上会出现弹性效果,而在Android上会出现微光效果。如果你想在所有平台下使用同一种效果,可以显式指定,Flutter SDK中包含了两个ScrollPhysics的子类可以直接使用:
    ClampingScrollPhysics→Android下微光效果 / BouncingScrollPhysics→iOS下弹性效果
  • controller:此属性接受一个ScrollController对象。ScrollController的主要作用是控制滚动位置和监听滚动事件
  • child:子元素
import 'package:flutter/material.dart';

/**
 * @des Scroll Widget
 * @author liyongli 20190506
 * */
class SingleChildScrollViewWidget extends StatefulWidget{

  @override
  State<StatefulWidget> createState() => new _StackState();

}

/**
 * @des Scroll Widget State
 * @author liyongli 20190506
 * */
class _StackState extends State<SingleChildScrollViewWidget>{

  String numberStr = "12345678909876543210123456789";

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(

        appBar: new AppBar(
          title: new Text("Scroll Widget"),
        ),

        body: Scrollbar(
          child: SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Center(
              child: Row(
                children: numberStr.split("").map((c) => Text(c, textScaleFactor: 2.0,)).toList(),
              ),
            ),
          ),
        )
      ),
    );
  }
}
import 'package:flutter/material.dart';

/**
 * @des Scroll Widget
 * @author liyongli 20190506
 * */
class SingleChildScrollViewWidget extends StatefulWidget{

  @override
  State<StatefulWidget> createState() => new _StackState();

}

/**
 * @des Scroll Widget State
 * @author liyongli 20190506
 * */
class _StackState extends State<SingleChildScrollViewWidget>{

  String numberStr = "1234567890987654321";

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(

        appBar: new AppBar(
          title: new Text("Scroll Widget"),
        ),

        body: Scrollbar(
          child: SingleChildScrollView(
            child: Center(
              child: Column(
                children: numberStr.split("").map((c) => Text(c, textScaleFactor: 2.0,)).toList(),
              ),
            ),
          ),
        )
      ),
    );
  }
}

ListView(列表 View)

ListView 可以构建一个列表视图

  ListView({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    this.itemExtent,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    List<Widget> children = const <Widget>[],
    int semanticChildCount,
    DragStartBehavior dragStartBehavior = DragStartBehavior.down,
  }) : childrenDelegate = SliverChildListDelegate(
         children,
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
         addSemanticIndexes: addSemanticIndexes,
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? children.length,
         dragStartBehavior: dragStartBehavior,
       );
  • key:当前元素的唯一标识符(类似于 Android 中的 id)
  • scrollDirection:滚动方向,默认是垂直
  • reverse:是否按照阅读方向相反的方向滑动。
  • controller:控制器对象,主要作用是控制滚动位置和监听滚动事件
  • primary:是否使用 widget 树中默认的 PrimaryScrollController 。当滑动方向为垂直方向(scrollDirection值为Axis.vertical)并且controller没有指定时,primary默认为true
  • physics:此属性接受一个ScrollPhysics对象,它决定可滚动Widget如何响应用户操作,比如用户滑动完抬起手指后,继续执行动画;或者滑动到边界时,如何显示。默认情况下,Flutter会根据具体平台分别使用不同的ScrollPhysics对象,应用不同的显示效果,如当滑动到边界时,继续拖动的话,在iOS上会出现弹性效果,而在Android上会出现微光效果。如果你想在所有平台下使用同一种效果,可以显式指定,Flutter SDK中包含了两个ScrollPhysics的子类可以直接使用:
    ClampingScrollPhysics→Android下微光效果 / BouncingScrollPhysics→iOS下弹性效果
  • shrinkWrap:表示是否根据子 widget 的总长度设置 listview 的长度,默认为 false。
  • padding:填充距离
  • itemExtent:强制 listview 的 children 的长度 为 itemExtent 的值。指定 itemExtent 的值比让子元素决定自身长度在绘制时更高效,特别是在滚动位置频繁变化的状态下,因为设置 itemExtent 可以让滚动系统提前知道列表的长度。
  • addAutomaticKeepAlives:表示是否将列表项包裹在 AutomaticKeepAlive widget 中。(在懒加载时,如果设置了包裹那么在此列表项滑出屏幕外时不会被GC。如果此列表项需要自己维护 KeepAlive 状态,那么此参数需为 false)
  • addRepaintBoundaries:表示是否将列表项包裹在 RepaintBoundary 中。(当选择将列表项包裹在 RepaintBoundary 时,在滚动过程中可以避免重绘,如果此列表项需要自己维护 KeepAlive 状态,那么此参数需为 false)
  • addSemanticIndexes:表示是否给子元素添加索引,默认为 true
  • cacheExtent:设置预加载的区域,范围在窗口可见范围之前与之后。如果设置为 0.0,表示关闭预加载
  • children:列表项集合
  • semanticChildCount:提供语义信息的孩子的数量
item 数量固定的 listview 示例

listview 构造方法中的参数 children 表示子列表集,使用这种方式构建列表需要我们提前准备好子 widget 集合。这种方式只适合实现少量且数量固定的列表展示需求

import 'package:flutter/material.dart';


/**
 * @des Listview Widget
 * @author liyongli 20190506
 * */
class ListViewWidget extends StatefulWidget{

  @override
  State<StatefulWidget> createState() {
    return new _ListViewWigetState();
  }

}

/**
 * @des Listview Widget State
 * @author liyongli 20190506
 * */
class _ListViewWigetState extends State<ListViewWidget>{

  @override
  Widget build(BuildContext context) {
    return Scaffold(

        appBar: new AppBar(
          title: new Text("Scroll Widget"),
        ),

        body: ListView(
          shrinkWrap: true,
          padding: const EdgeInsets.all(20.0),
          children: <Widget>[
            const Text("1111111111111111111111111111111111", style: TextStyle(color: Colors.blue)),
            const Text("2222222222222222222222222222222222", style: TextStyle(color: Colors.blue)),
            const Text("3333333333333333333333333333333333", style: TextStyle(color: Colors.blue)),
            const Text("4444444444444444444444444444444444", style: TextStyle(color: Colors.blue)),
            const Text("5555555555555555555555555555555555", style: TextStyle(color: Colors.blue)),
            const Text("6666666666666666666666666666666666", style: TextStyle(color: Colors.blue)),
            const Text("7777777777777777777777777777777777", style: TextStyle(color: Colors.blue)),
            const Text("8888888888888888888888888888888888", style: TextStyle(color: Colors.blue)),
          ],
        ),
    );
  }

}
ListView.builder

当 listview 的列表项较多或数量未知时,就需要使用 ListView.builder 来构建列表了

import 'package:flutter/material.dart';


/**
 * @des Listview.builder Widget
 * @author liyongli 20190506
 * */
class ListViewBuilderWidget extends StatefulWidget{

  @override
  State<StatefulWidget> createState() {
    return new _ListViewBuilderWidget ();
  }

}

/**
 * @des Listview.builder Widget State
 * @author liyongli 20190506
 * */
class _ListViewBuilderWidget  extends State<ListViewBuilderWidget>{

  @override
  Widget build(BuildContext context) {
    return Scaffold(

        appBar: new AppBar(
          title: new Text("ListviewBuilder Widget"),
        ),

        body: ListView.builder(
            itemCount: 100,
            itemExtent: 50.0, //强制高度为50.0
            itemBuilder: (BuildContext context, int index) {
              return ListTile(title: Text(" $index - "));
            }
        ),
    );
  }

}
ListView.separated

当 listview 的 item 间需要分割线时,我们就需要用到 ListView.separated

import 'package:flutter/material.dart';


/**
 * @des Listview.builder Widget
 * @author liyongli 20190506
 * */
class ListViewBuilderWidget extends StatefulWidget{

  @override
  State<StatefulWidget> createState() {
    return new _ListViewBuilderWidget ();
  }

}

/**
 * @des Listview.builder Widget State
 * @author liyongli 20190506
 * */
class _ListViewBuilderWidget  extends State<ListViewBuilderWidget>{

  @override
  Widget build(BuildContext context) {
    return Scaffold(

        appBar: new AppBar(
          title: new Text("ListviewBuilder Widget"),
        ),

        body: ListView.separated(
            itemCount: 100,
            itemBuilder: (BuildContext context, int index) {
              return ListTile(title: Text(" $index - ", style: TextStyle(color: Colors.blue),));
            },

            separatorBuilder: (BuildContext context, int index) {
              return Divider(color: Colors.blue, height: 10,);
            }
        ),
    );
  }

}
ListView 分页加载

工程 yaml 文件中要添加 english_words 的依赖

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  english_words: ^3.1.0
import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';


/**
 * @des Listview  Widget
 * @author liyongli 20190507
 * */
class InfiniteListView extends StatefulWidget {
  @override
  _InfiniteListViewState createState() => new _InfiniteListViewState();
}

/**
 * @des Listview  Widget State
 * @author liyongli 20190507
 * */
class _InfiniteListViewState extends State<InfiniteListView> {
  static const loadingTag = "##loading##"; //表尾标记
  var _words = <String>[loadingTag];

  @override
  void initState() {
    _retrieveData();
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text("listview 分页加载"),
        ),

        body: ListView.separated(
          itemCount: _words.length,
          itemBuilder: (context, index) {
            //如果到了表尾
            if (_words[index] == loadingTag) {
              //不足100条,继续获取数据
              if (_words.length - 1 < 100) {
                //获取数据
                _retrieveData();
                //加载时显示loading
                return Container(
                  padding: const EdgeInsets.all(16.0),
                  alignment: Alignment.center,
                  child: SizedBox(
                      width: 24.0,
                      height: 24.0,
                      child: CircularProgressIndicator(strokeWidth: 2.0)
                  ),
                );
              } else {
                //已经加载了100条数据,不再获取数据。
                return Container(
                    alignment: Alignment.center,
                    padding: EdgeInsets.all(16.0),
                    child: Text("没有更多了", style: TextStyle(color: Colors.grey),)
                );
              }
            }
            //显示单词列表项
            return ListTile(title: Text(_words[index], style: TextStyle(color: Colors.blue),));
          },
          separatorBuilder: (context, index) => Divider(height: 1, color: Colors.blue,),
        ),
      ),

    );
  }

  void _retrieveData() {
    Future.delayed(Duration(seconds: 2)).then((e) {
      _words.insertAll(_words.length - 1,
          //每次生成20个单词
          generateWordPairs().take(20).map((e) => e.asPascalCase).toList()
      );
      setState(() {
        //重新构建列表
      });
    });
  }

}

GridView(网格 View)

GridView 可以构建一个网格列表视图

  GridView.builder({
    Key key,
    Axis scrollDirection = Axis.vertical,
    bool reverse = false,
    ScrollController controller,
    bool primary,
    ScrollPhysics physics,
    bool shrinkWrap = false,
    EdgeInsetsGeometry padding,
    @required this.gridDelegate,
    @required IndexedWidgetBuilder itemBuilder,
    int itemCount,
    bool addAutomaticKeepAlives = true,
    bool addRepaintBoundaries = true,
    bool addSemanticIndexes = true,
    double cacheExtent,
    int semanticChildCount,
  }) : assert(gridDelegate != null),
       childrenDelegate = SliverChildBuilderDelegate(
         itemBuilder,
         childCount: itemCount,
         addAutomaticKeepAlives: addAutomaticKeepAlives,
         addRepaintBoundaries: addRepaintBoundaries,
         addSemanticIndexes: addSemanticIndexes,
       ),
       super(
         key: key,
         scrollDirection: scrollDirection,
         reverse: reverse,
         controller: controller,
         primary: primary,
         physics: physics,
         shrinkWrap: shrinkWrap,
         padding: padding,
         cacheExtent: cacheExtent,
         semanticChildCount: semanticChildCount ?? itemCount,
       );
  • key:当前元素的唯一标识符(类似于 Android 中的 id)
  • scrollDirection:滚动方向,默认是垂直
  • reverse:是否按照阅读方向相反的方向滑动。
  • controller:控制器对象,主要作用是控制滚动位置和监听滚动事件
  • primary:是否使用 widget 树中默认的 PrimaryScrollController 。当滑动方向为垂直方向(scrollDirection值为Axis.vertical)并且controller没有指定时,primary默认为true
  • physics:此属性接受一个ScrollPhysics对象,它决定可滚动Widget如何响应用户操作,比如用户滑动完抬起手指后,继续执行动画;或者滑动到边界时,如何显示。默认情况下,Flutter会根据具体平台分别使用不同的ScrollPhysics对象,应用不同的显示效果,如当滑动到边界时,继续拖动的话,在iOS上会出现弹性效果,而在Android上会出现微光效果。如果你想在所有平台下使用同一种效果,可以显式指定,Flutter SDK中包含了两个ScrollPhysics的子类可以直接使用:
    ClampingScrollPhysics→Android下微光效果 / BouncingScrollPhysics→iOS下弹性效果
  • shrinkWrap:表示是否根据子 widget 的总长度设置 listview 的长度,默认为 false。
  • padding:填充距离
  • itemCount:子元素数量
  • addAutomaticKeepAlives:表示是否将列表项包裹在 AutomaticKeepAlive widget 中。(在懒加载时,如果设置了包裹那么在此列表项滑出屏幕外时不会被GC。如果此列表项需要自己维护 KeepAlive 状态,那么此参数需为 false)
  • addRepaintBoundaries:表示是否将列表项包裹在 RepaintBoundary 中。(当选择将列表项包裹在 RepaintBoundary 时,在滚动过程中可以避免重绘,如果此列表项需要自己维护 KeepAlive 状态,那么此参数需为 false)
  • addSemanticIndexes:表示是否给子元素添加索引,默认为 true
  • cacheExtent:设置预加载的区域,范围在窗口可见范围之前与之后。如果设置为 0.0,表示关闭预加载
  • semanticChildCount:提供语义信息的孩子的数量
GridView 固定列数
import 'package:flutter/material.dart';

/**
 * @des GridView 固定列数
 * @author liyongli 20190508
 * */
class GridViewTest extends StatefulWidget{

  @override
  State<StatefulWidget> createState() => new _GridViewState();

}

/**
 * @des GridView Widget State
 * @author liyongli 20190508
 * */
class _GridViewState extends State<GridViewTest>{

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(

        appBar: new AppBar(
          title: new Text("GridView 固定列数"),
        ),

        body: GridView.count(
          crossAxisCount: 4,
          childAspectRatio: 0.7,
          children: <Widget>[
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
          ],
        ),
      ),
    );
  }
}
import 'package:flutter/material.dart';

/**
 * @des GridView 等分宽度
 * @author liyongli 20190508
 * */
class GridViewTest extends StatefulWidget{

  @override
  State<StatefulWidget> createState() => new _GridViewState();

}

/**
 * @des GridView Widget State
 * @author liyongli 20190508
 * */
class _GridViewState extends State<GridViewTest>{

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(

        appBar: new AppBar(
          title: new Text("GridView 等分宽度"),
        ),

        body: GridView.extent(
          maxCrossAxisExtent: 200,
          childAspectRatio: 1.0,
          children: <Widget>[
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
            Icon(Icons.add_a_photo),
          ],
        ),
      ),
    );
  }
}
GridView 分页加载
import 'package:flutter/material.dart';

/**
 * @des GridView 分页加载
 * @author liyongli 20190508
 * */
class GridViewTest extends StatefulWidget{

  @override
  State<StatefulWidget> createState() => new _GridViewState();

}

/**
 * @des GridView Widget State
 * @author liyongli 20190508
 * */
class _GridViewState extends State<GridViewTest>{

  List<IconData> _iconList = [];

  @override
  void initState() {
    _getData();
  }

  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      home: new Scaffold(

        appBar: new AppBar(
          title: new Text("GridView 分页加载"),
        ),

        body: GridView.builder(
          itemCount: _iconList.length,
            gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 5, childAspectRatio: 1.0),
            itemBuilder: (context, index){
              if(index == _iconList.length - 1 && _iconList.length < 200){
                _getData();
              }
              return Icon(_iconList[index]);
            }
        )
      ),
    );
  }

  // 请求数据
  void _getData(){
    Future.delayed(Duration(milliseconds: 200)).then((e) {
      setState(() {
        _iconList.addAll([
          Icons.add_a_photo,
          Icons.add_a_photo,
          Icons.add_a_photo,
          Icons.add_a_photo, Icons.cake,
          Icons.add_a_photo
        ]);
      });
    });
  }
}

CustomScrollView(自定义滑动 View)

Sliver

Sliver 是分片、分区的意思。当我们需要将不同的可滑动组件组合在一起时,就需要使用此对象来完成。ListView 和 GridView 都有对应的组合对象如:SliverList 和 SliverGrid。

import 'package:flutter/material.dart';

/**
 * @des CustomScrollView Widget
 * @author liyongli 20190509
 * */
class CustomScrollViewTest extends StatelessWidget{


  @override
  Widget build(BuildContext context) {
    return Material(
      child: CustomScrollView(
        slivers: <Widget>[

          // 跟随页面滑动的导航栏
          SliverAppBar(
            pinned: true, // 是否固定
            expandedHeight: 200.0, // 高度
            flexibleSpace: FlexibleSpaceBar(
              title: Text("title"),
              background: Image.asset("images/custom_scroll_title.png", fit: BoxFit.cover,)
            ),
          ),

          // Grid
          SliverPadding(
            padding: EdgeInsets.all(10.0),
            sliver: SliverGrid(
              gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                crossAxisCount: 3, // 4行
                mainAxisSpacing: 20.0,
                crossAxisSpacing: 10.0,
                childAspectRatio: 5.0,
              ),
              delegate: SliverChildBuilderDelegate(
                  (BuildContext context, int index){
                    return Container(
                      alignment: Alignment.center,
                      color: Colors.blue,
                      child: Text("grid Item $index", style: TextStyle(color: Colors.white),),
                    );
                  },
                childCount: 12
              ),
            ),
          ),

          // List
          SliverFixedExtentList(
            itemExtent: 20,
            delegate: SliverChildBuilderDelegate((BuildContext context , int index){
              return Container(
                alignment: Alignment.center,
                color: Colors.blue,
                child: Text("list Item $index", style: TextStyle(color: Colors.white),),
              );
            },
              childCount: 30
            ),
          ),
        ],
      ),
    );
  }

}

ScrollController(控制器)

可设置滑动 View 的滚动位置,还可监听并获取滑动 View 的滚动状态及数据

  ScrollController({
    double initialScrollOffset = 0.0,
    this.keepScrollOffset = true,
    this.debugLabel,
  }) : assert(initialScrollOffset != null),
       assert(keepScrollOffset != null),
       _initialScrollOffset = initialScrollOffset;
  • initialScrollOffset:初始位置
  • keepScrollOffset:是否保存滚动位置
  • ScrollController.jumpTo(0.0):直接滚动至指定位置
  • ScrollController.animateTo(0.0, duration: Duration(milliseconds: 500), curve: Curves.decelerate):带动画滚动至指定位置
import 'package:flutter/material.dart';

/**
 * des ScrollController Test
 * @author liyongli 20190513
 * */
class ScrollControllerTest extends StatefulWidget{

  @override
  State<StatefulWidget> createState() {
    return new _ScrollControllerTestState();
  }

}

/**
 * des ScrollController Test State
 * @author liyongli 20190513
 * */
class _ScrollControllerTestState extends State<ScrollControllerTest>{

  ScrollController _controller = new ScrollController();
  double oldOffset = -1;

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

    _controller.addListener((){
      print("$_controller.offset" + " / " + "$oldOffset");

      if(oldOffset > _controller.offset){
        print("向下滑");
      }else{
        print("向上滑");
      }

      oldOffset = _controller.offset;
    });

  }

  @override
  void dispose() {
    super.dispose();
    _controller.dispose(); // 释放资源
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(

        appBar: AppBar(
          title: Text("ScrollController"),

        ),

        body: Scrollbar(
            child: ListView.builder(
                controller: _controller, // 控制器
                itemCount: 50, // item count
                itemExtent: 100.0, // item height
                itemBuilder: (context, index){
                  return ListTile(title:Text("$index", style: TextStyle(color: Colors.blue),));
                }
            )
        ),

        floatingActionButton: FloatingActionButton(
          onPressed: _up,
          child: Icon(Icons.arrow_upward)
        ),
      ),
    );
  }

//  滚动
  void _up(){
    // 带动画滚动
    _controller.animateTo(0.0, duration: Duration(milliseconds: 500), curve: Curves.decelerate);
    // 无动画滚动
//    _controller.jumpTo(0.0);
  }

}

本篇到此完结,更多 Flutter 跨平台移动端开发 原创内容持续更新中~

期待您 关注 / 点赞 / 收藏 向着 大前端工程师 晋级!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270

推荐阅读更多精彩内容