Flutter2空安全適配指南

一巫橄、走進(jìn)空安全(空安全最小必備知識(shí))

從Flutter 2開(kāi)始椰棘,F(xiàn)lutter便在配置中默認(rèn)啟用了空安全,通過(guò)將空檢查合并到類(lèi)型系統(tǒng)中壳坪,可以在開(kāi)發(fā)過(guò)程中捕獲這些錯(cuò)誤暇昂,從而防止再生產(chǎn)環(huán)境導(dǎo)致的崩潰莺戒。

什么是空安全

時(shí)至今日,空安全已經(jīng)是一個(gè)屢見(jiàn)不鮮的話題急波,目前像主流的編程語(yǔ)言Kotlin从铲、Swift、Rust 等都對(duì)空安全有自己的支持澄暮。Dart從2.12版本開(kāi)始支持了空安全名段,通過(guò)空安全開(kāi)發(fā)人員可以有效避免null錯(cuò)誤崩潰∑茫空安全性可以說(shuō)是Dart語(yǔ)言的重要補(bǔ)充伸辟,它通過(guò)區(qū)分可空類(lèi)型和非可空類(lèi)型進(jìn)一步增強(qiáng)了類(lèi)型系統(tǒng)。

引入空安全的好處

  • 可以將原本運(yùn)行時(shí)的空值引用錯(cuò)誤將變?yōu)榫庉嫊r(shí)的分析錯(cuò)誤馍刮;
  • 增強(qiáng)程序的健壯性信夫,有效避免由Null而導(dǎo)致的崩潰;
  • 跟隨Dart和Flutter的發(fā)展趨勢(shì)卡啰,為程序的后續(xù)迭代不留坑静稻;

空安全最小必備知識(shí)

  • 空安全的原則
  • 引入空安全前后Dart類(lèi)型系統(tǒng)的變化
  • 可空(?)類(lèi)型的使用
  • 延遲初始化(late)的使用
  • 空值斷言操作符(!)的使用
空安全的原則

Dart 的空安全支持基于以下三條核心原則:

  • 默認(rèn)不可空:除非您將變量顯式聲明為可空,否則它一定是非空的類(lèi)型碎乃;
  • 漸進(jìn)遷移:您可以自由地選擇何時(shí)進(jìn)行遷移,多少代碼會(huì)進(jìn)行遷移惠奸;
  • 完全可靠:Dart 的空安全是非趁肥模可靠的,意味著編譯期間包含了很多優(yōu)化佛南,
    • 如果類(lèi)型系統(tǒng)推斷出某個(gè)變量不為空梗掰,那么它 永遠(yuǎn) 不為空。當(dāng)您將整個(gè)項(xiàng)目和其依賴(lài)完全遷移至空安全后嗅回,您會(huì)享有健全性帶來(lái)的所有優(yōu)勢(shì)——更少的 BUG及穗、更小的二進(jìn)制文件以及更快的執(zhí)行速度。
引入空安全前后Dart類(lèi)型系統(tǒng)的變化

在引入空安全前Dart的類(lèi)型系統(tǒng)是這樣的:

image

這意味著在之前绵载,所有的類(lèi)型都可以為Null埂陆,也就是Nul類(lèi)型被看作是所有類(lèi)型的子類(lèi)苛白。

在引入空安全之后:

image

可以看出,最大的變化是將Null類(lèi)型獨(dú)立出來(lái)了焚虱,這意味著Null不在是其它類(lèi)型的子類(lèi)型购裙,所以對(duì)于一個(gè)非Null類(lèi)型的變量傳遞一個(gè)Null值時(shí)會(huì)報(bào)類(lèi)型轉(zhuǎn)換錯(cuò)誤。

提示:在使用了空安全的Flutter或Dart項(xiàng)目中你會(huì)經(jīng)尘樵裕看到?.躏率、!、late的大量應(yīng)用民鼓,那么他們分別是什么又改如何使用呢薇芝?請(qǐng)看下文的分析

可空(?)類(lèi)型的使用

我們可以通過(guò)將?跟在類(lèi)型的后面來(lái)表示它后面的變量或參數(shù)可接受Null:

class CommonModel {
  String? firstName; //可空的成員變量
  int getNameLen(String? lastName /*可空的參數(shù)*/) {
    int firstLen = firstName?.length ?? 0;
    int lastLen = lastName?.length ?? 0;
    return firstLen + lastLen;
  }
}

對(duì)于可空的變量或參數(shù)在使用的時(shí)候需要通過(guò)Dart 的避空運(yùn)算符?.來(lái)進(jìn)行訪問(wèn),否則會(huì)拋出編譯錯(cuò)誤丰嘉。

當(dāng)程序啟用空安全后夯到,類(lèi)的成員變量默認(rèn)是不可空的,所以對(duì)于一個(gè)非空的成員變量需要指定其初始化方式:

class CommonModel {
  List names=[];//定義時(shí)初始化
  final List colors;//在構(gòu)造方法中初始化
  late List urls;//延時(shí)初始化
  CommonModel(this.colors);
  ...

延遲初始化(late)的使用

對(duì)于無(wú)法在定義時(shí)進(jìn)行初始化供嚎,并且又想避免使用?.黄娘,那么延遲初始化可以幫到你。通過(guò)late修飾的變量克滴,可以讓開(kāi)發(fā)者選擇初始化的時(shí)機(jī)逼争,并且在使用這個(gè)變量時(shí)可以不用?.

  late List urls;//延時(shí)初始化
  setUrls(List urls){
    this.urls=urls;
  }
  int getUrlLen(){
    return urls.length;
  }

延時(shí)初始化雖然能為我們編碼帶來(lái)一定便利劝赔,但如果使用不當(dāng)會(huì)帶來(lái)空異常的問(wèn)題誓焦,所以在使用的時(shí)候一定保證賦值和訪問(wèn)的順序,切莫顛倒着帽。

延遲初始化(late)使用范式

在Flutter中State的initState方法中初始化的一些變量是比較適合使用late來(lái)進(jìn)行延時(shí)初始化的杂伟,因?yàn)樵赪idget生命周期中initState方法是最先執(zhí)行的,所以它里面初始化的變量通過(guò)late修飾后既能保障使用時(shí)的便利仍翰,又能防止空異常赫粥,下面就以Flutter從入門(mén)到進(jìn)階-語(yǔ)音搜索模塊為例來(lái)看下具體的用法:

class _SpeakPageState extends State<SpeakPage>
    with SingleTickerProviderStateMixin {
  String speakTips = '長(zhǎng)按說(shuō)話';
  String speakResult = '';
  late Animation<double> animation;
  late AnimationController controller;

  @override
  void initState() {
    controller = AnimationController(
        super.initState();
        vsync: this, duration: Duration(milliseconds: 1000));
    animation = CurvedAnimation(parent: controller, curve: Curves.easeIn)
      ..addStatusListener((status) {
        if (status == AnimationStatus.completed) {
          controller.reverse();
        } else if (status == AnimationStatus.dismissed) {
          controller.forward();
        }
      });
  }
  ...

空值斷言操作符(!)的使用

當(dāng)我們排除變量或參數(shù)的可空的可能后,可以通過(guò)!來(lái)告訴編譯器這個(gè)可空的變量或參數(shù)不可空予借,這對(duì)我們進(jìn)行方法傳參或?qū)⒖煽諈?shù)傳遞給一個(gè)不可空的入?yún)r(shí)特別有用:

  Widget get _listView {
    return ListView(
      children: <Widget>[
        _banner,
        Padding(
          padding: EdgeInsets.fromLTRB(7, 4, 7, 4),
          child: LocalNav(localNavList: localNavList),
        ),
        if (gridNavModel != null)
          Padding(
              padding: EdgeInsets.fromLTRB(7, 0, 7, 4),
              child: GridNav(gridNavModel: gridNavModel!)),
        Padding(
            padding: EdgeInsets.fromLTRB(7, 0, 7, 4),
            child: SubNav(subNavList: subNavList)),
        if (salesBoxModel != null)
          Padding(
              padding: EdgeInsets.fromLTRB(7, 0, 7, 4),
              child: SalesBox(salesBox: salesBoxModel!)),
      ],
    );
  }

上述代碼是Flutter從入門(mén)到進(jìn)階-首頁(yè)模塊根據(jù)gridNavModelsalesBoxModel模塊數(shù)據(jù)是否為空時(shí)動(dòng)態(tài)創(chuàng)建的列表越平,在確保變量不為空的情況下使用了空值斷言操作符!

除此之外灵迫,!還有一個(gè)常見(jiàn)的用處:

bool isEmptyList(Object object) {
  if (object is! List) return false;
  return object.isEmpty;
}

用在這里表示取反秦叛,上述代碼等價(jià)于:

bool isEmptyList(Object object) {
  if (!(object is List)) return false;
  return object.isEmpty;
}

二、Flutter如何做空安全適配

Step 1: 啟用空安全

Flutter 2默認(rèn)啟用了空安全瀑粥,所以通過(guò)Flutter 2創(chuàng)建的項(xiàng)目是已經(jīng)開(kāi)啟了空安全的檢查的挣跋,另外,小伙伴也可以可以通過(guò)下面命令來(lái)查看你的Flutter SDK版本:

flutter doctor

那么狞换,如何手動(dòng)開(kāi)啟和關(guān)閉空區(qū)安全的避咆?

environment:
  sdk: ">=2.12.0 <3.0.0" //sdk >=2.12.0表示開(kāi)啟空安全檢查

提示:一旦項(xiàng)目開(kāi)啟了空安全檢查舟肉,那么你的代碼包括項(xiàng)目所依賴(lài)的三方插件必須是要支持空安全的否則是無(wú)法正常編譯的。

如果想關(guān)閉空安全檢查牌借,可以將SDK的支持范圍調(diào)整到2.12.0以下即可度气,如:

environment:
  sdk: ">=2.7.0 <3.0.0"

Step 2: 進(jìn)行空安全適配

開(kāi)啟空安全之后,然后運(yùn)行下項(xiàng)目你會(huì)看到很多的報(bào)錯(cuò)膨报,然后定位到報(bào)錯(cuò)的文件磷籍,通過(guò)本章所講技能進(jìn)行適配。首先需要對(duì)文件進(jìn)行分類(lèi):

  • 自定義Widget(包含你所創(chuàng)建的Flutter頁(yè)面)如:
    • Flutter高級(jí)進(jìn)階實(shí)戰(zhàn) 仿嗶哩嗶哩APP中的:登錄现柠、注冊(cè)院领、首頁(yè)、收藏够吩、排行比然、詳情等頁(yè)面,以及video_card.dart周循、login_effect.dart强法、hi_tab.darthi_banner.dart等自定義widget湾笛。
    • Flutter從入門(mén)到進(jìn)階 實(shí)戰(zhàn)攜程網(wǎng)App中的:搜索饮怯、旅拍、我的嚎研、首頁(yè)等頁(yè)面蓖墅,以及grid_nav.dartloading_container.dart临扮、local_nav.dart论矾、sales_box.dartsearch_bar.dart杆勇、sub_nav.dart等自定義widget贪壳。
  • 數(shù)據(jù)模型(Model)如:
    • Flutter高級(jí)進(jìn)階實(shí)戰(zhàn) 仿嗶哩嗶哩APP中的:home_mo.dartnotice_mo.dart蚜退、profile_mo.dart闰靴、ranking_mo.dartvideo_detail_mo.dart关霸、video_model.dart等model传黄。
    • Flutter從入門(mén)到進(jìn)階 實(shí)戰(zhàn)攜程網(wǎng)App中的:common_model.dart杰扫、config_model.dart队寇、grid_nav_model.darthome_model.dart章姓、sales_box_model.dart佳遣、seach_model.dart识埋、travel_model.darttravel_tab_model.dart等model零渐。
  • 單例如:
    • Flutter高級(jí)進(jìn)階實(shí)戰(zhàn) 仿嗶哩嗶哩APP中的:hi_navigator.dart窒舟、hi_cache.darthi_net.dart等诵盼。

然后結(jié)合著后面對(duì)應(yīng)小節(jié)的教程進(jìn)行適配即可惠豺。

三、自定義Widget的空安全適配技巧

自定義Widget的空安全適配分兩種情況:

  • Widget的空安全適配
  • State的空安全適配

Widget的空安全適配

對(duì)于自定的Widget無(wú)論是頁(yè)面的某控件還是整個(gè)頁(yè)面风宁,通常都會(huì)為Widget定義一些屬性洁墙。在進(jìn)行空安全適配時(shí)要對(duì)屬性進(jìn)行一下分類(lèi):

  • 可空的屬性:通過(guò)?進(jìn)行修飾
  • 不可空的屬性:在構(gòu)造函數(shù)中設(shè)置默認(rèn)值或者通過(guò)required進(jìn)行修飾
class WebView extends StatefulWidget {
  String? url;
  final String? statusBarColor;
  final String? title;
  final bool? hideAppBar;
  final bool backForbid;

  WebView(
      {this.url,
      this.statusBarColor,
      this.title,
      this.hideAppBar,
      this.backForbid = false})
      ...

提示:如果構(gòu)造方法中使用了@required那么需要改成required

State的空安全適配

State的空安全適配主要是根據(jù)它的成員變量是否可空進(jìn)行分類(lèi):

  • 可空的變量:通過(guò)?進(jìn)行修飾
  • 不可空的變量:可采用以下兩種方式進(jìn)行適配
    • 定義時(shí)初始化
    • 使用late修飾為延時(shí)變量

四戒财、數(shù)據(jù)模型(Model)空安全適配技巧

數(shù)據(jù)模型(Model)空安全適配主要以下兩種情況:

  • 含有命令構(gòu)造函數(shù)的模型
  • 含有命名工廠構(gòu)造函數(shù)的模型

含有命令構(gòu)造函數(shù)的模型

含有命令構(gòu)造函數(shù)的模型的空安全適配技巧:

適配前:

///旅拍頁(yè)模型
class TravelItemModel {
  int totalCount;
  List<TravelItem> resultList;
  TravelItemModel.fromJson(Map<String, dynamic> json) {
    totalCount = json['totalCount'];
    if (json['resultList'] != null) {
      resultList = new List<TravelItem>();
      json['resultList'].forEach((v) {
        resultList.add(new TravelItem.fromJson(v));
      });
    }
  }
  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['totalCount'] = this.totalCount;
    if (this.resultList != null) {
      data['resultList'] = this.resultList.map((v) => v.toJson()).toList();
    }
    return data;
  }
}

適配之前首先要和服務(wù)端協(xié)商好热监,模型中那些字段可空,那些字段是一定會(huì)下發(fā)的饮寞。對(duì)于這個(gè)案例假如:totalCount字段是一定會(huì)下發(fā)的孝扛,resultList字段是不能保證一定會(huì)下發(fā),那么我們可以這樣來(lái)適配:

適配后:

///旅拍頁(yè)模型
class TravelItemModel {
  late int totalCount; 
  List<TravelItem>? resultList;

  //命名構(gòu)造方法
  TravelItemModel.fromJson(Map<String, dynamic> json) {
    totalCount = json['totalCount'];
    if (json['resultList'] != null) {
      resultList = new List<TravelItem>.empty(growable: true);
      json['resultList'].forEach((v) {
        resultList!.add(new TravelItem.fromJson(v));
      });
    }
  }

  Map<String, dynamic> toJson() {
    final Map<String, dynamic> data = new Map<String, dynamic>();
    data['totalCount'] = this.totalCount;
    data['resultList'] = this.resultList!.map((v) => v.toJson()).toList();
    return data;
  }
}
  • 對(duì)于一定會(huì)下發(fā)的字段我們通過(guò)late來(lái)修飾為延遲初始化的字段以方便訪問(wèn)
  • 對(duì)于不能保證一定會(huì)下發(fā)的字段幽崩,我們通過(guò)?將其修飾為可空的變量

含有命名工廠構(gòu)造函數(shù)的模型

命名工廠構(gòu)造函的數(shù)據(jù)模型也是比較常見(jiàn)的數(shù)據(jù)模型之一苦始,公共數(shù)據(jù)模型為例來(lái)分享含有命名工廠構(gòu)造函的數(shù)據(jù)模型的空安全適配技巧:

適配前:

class CommonModel {
  final String icon;
  final String title;
  final String url;
  final String statusBarColor;
  final bool hideAppBar;

  CommonModel(
      {this.icon, this.title, this.url, this.statusBarColor, this.hideAppBar});

  factory CommonModel.fromJson(Map<String, dynamic> json) {
    return CommonModel(
      icon: json['icon'],
      title: json['title'],
      url: json['url'],
      statusBarColor: json['statusBarColor'],
      hideAppBar: json['hideAppBar']
    );
  }
}

含有命名工廠構(gòu)造函數(shù)的模型通常需要有自己的構(gòu)造函數(shù),構(gòu)造函數(shù)通常采用可選參數(shù)歉铝,所以在進(jìn)行適配時(shí)首先要明確哪些字段一定不為空盈简,哪些字段可空,確認(rèn)好之后就可以進(jìn)行下面適配了:

適配后:

class CommonModel {
  final String? icon;
  final String? title;
  final String url;
  final String? statusBarColor;
  final bool? hideAppBar;

  CommonModel(
      {this.icon,
      this.title,
      required this.url,
      this.statusBarColor,
      this.hideAppBar});
  //命名工廠構(gòu)造函數(shù)必須要有返回值太示,類(lèi)似static 函數(shù)無(wú)法訪問(wèn)成員變量和方法
  factory CommonModel.fromJson(Map<String, dynamic> json) {
    return CommonModel(
      icon: json['icon'],
      title: json['title'],
      url: json['url'],
      statusBarColor: json['statusBarColor'],
      hideAppBar: json['hideAppBar']
    );
  }
}
  • 對(duì)于可空的字段通過(guò)?進(jìn)行修飾
  • 對(duì)于不可空的字段柠贤,需要在構(gòu)造方法中在對(duì)應(yīng)的字段前面添加required修飾符來(lái)表示這個(gè)參數(shù)是必傳參數(shù)

五、單例空安全適配技巧

單例是Flutter開(kāi)發(fā)中使用最廣的一種設(shè)計(jì)模式类缤,那么單例該如何適配空安全呢臼勉?

接下來(lái)就以[Flutter高級(jí)進(jìn)階實(shí)戰(zhàn)中緩存模塊]單例的空安全適配技巧

適配前:

///緩存管理類(lèi)
class HiCache {
  SharedPreferences prefs;
  static HiCache _instance;
  HiCache._() {
    init();
  }
  HiCache._pre(SharedPreferences prefs) {
    this.prefs = prefs;
  }
  static Future<HiCache> preInit() async {
    if (_instance == null) {
      var prefs = await SharedPreferences.getInstance();
      _instance = HiCache._pre(prefs);
    }
    return _instance;
  }

  static HiCache getInstance() {
    if (_instance == null) {
      _instance = HiCache._();
    }
    return _instance;
  }

  void init() async {
    if (prefs == null) {
      prefs = await SharedPreferences.getInstance();
    }
  }
  setString(String key, String value) {
    prefs.setString(key, value);
  }

  setDouble(String key, double value) {
    prefs.setDouble(key, value);
  }

  setInt(String key, int value) {
    prefs.setInt(key, value);
  }

  setBool(String key, bool value) {
    prefs.setBool(key, value);
  }

  setStringList(String key, List<String> value) {
    prefs.setStringList(key, value);
  }

  T get<T>(String key) {
    return prefs?.get(key) ?? null;
  }
}

適配后:

class HiCache {
  SharedPreferences? prefs;
  static HiCache? _instance;
  HiCache._() {
    init();
  }
  HiCache._pre(SharedPreferences prefs) {
    this.prefs = prefs;
  }
  static Future<HiCache> preInit() async {
    if (_instance == null) {
      var prefs = await SharedPreferences.getInstance();
      _instance = HiCache._pre(prefs);
    }
    return _instance!;
  }

  static HiCache getInstance() {
    if (_instance == null) {
      _instance = HiCache._();
    }
    return _instance!;
  }

  void init() async {
    if (prefs == null) {
      prefs = await SharedPreferences.getInstance();
    }
  }

  setString(String key, String value) {
    prefs?.setString(key, value);
  }

  setDouble(String key, double value) {
    prefs?.setDouble(key, value);
  }

  setInt(String key, int value) {
    prefs?.setInt(key, value);
  }

  setBool(String key, bool value) {
    prefs?.setBool(key, value);
  }

  setStringList(String key, List<String> value) {
    prefs?.setStringList(key, value);
  }

  remove(String key) {
    prefs?.remove(key);
  }

  T? get<T>(String key) {
    var result = prefs?.get(key);
    if (result != null) {
      return result as T;
    }
    return null;
  }
}

核心適配的地方主要有兩點(diǎn):

  • 因?yàn)槭菓袧h模式的單例,所以單例instance設(shè)置成可空
  • getInstance中因?yàn)闀?huì)有null時(shí)創(chuàng)建單例餐弱,所以返回instance時(shí)將其轉(zhuǎn)換成非空

六宴霸、插件的空安全適配問(wèn)題

三方插件的空安全適配問(wèn)題

目前在Dart的官方插件平臺(tái)上的主流插件都陸續(xù)進(jìn)行了空安全支持,如果你的項(xiàng)目開(kāi)啟了空安全那么所有使用的插件也必須是要支持空安全的膏蚓,否則會(huì)導(dǎo)致無(wú)法編譯:

Xcode's output:
?
    Error: Cannot run with sound null safety, because the following dependencies
    don't support null safety:

     - package:flutter_splash_screen

遇到這個(gè)問(wèn)題后可以到Dart的官方插件平臺(tái)查看這個(gè)flutter_splash_screen插件是否有支持了空安全的版本瓢谢。如果插件支持了空安全插件平臺(tái)會(huì)為其打上空安全的標(biāo):

image

如果你所使用的某個(gè)插件還不支持空安全,而且你又必須要使用這個(gè)插件驮瞧,那么可以通過(guò)上文所講的方式來(lái)關(guān)閉空安全檢查氓扛。

我的插件該如何適配空安全?

通過(guò)Flutter進(jìn)階拓展:開(kāi)發(fā)包和插件開(kāi)發(fā)的學(xué)習(xí),有不少小伙伴已經(jīng)開(kāi)發(fā)并發(fā)布了一些插件采郎,那么該如何為你所開(kāi)發(fā)的插件適配空安全呢千所?

回顧我對(duì)一些插件的適配的整個(gè)過(guò)程來(lái)講,可以分為三個(gè)關(guān)鍵步驟:

  • 開(kāi)啟空安全
  • 代碼適配:進(jìn)行編譯蒜埋,對(duì)編譯的報(bào)錯(cuò)進(jìn)行空安全適配
  • 發(fā)布:將適配后的代碼發(fā)布到插件市場(chǎng)
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末淫痰,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子整份,更是在濱河造成了極大的恐慌待错,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件烈评,死亡現(xiàn)場(chǎng)離奇詭異朗鸠,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)础倍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門(mén)烛占,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人沟启,你說(shuō)我怎么就攤上這事忆家。” “怎么了德迹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵芽卿,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我胳搞,道長(zhǎng)卸例,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任肌毅,我火速辦了婚禮筷转,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘悬而。我一直安慰自己呜舒,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布笨奠。 她就那樣靜靜地躺著袭蝗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪般婆。 梳的紋絲不亂的頭發(fā)上到腥,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音蔚袍,去河邊找鬼乡范。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的篓足。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼闰蚕,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼栈拖!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起没陡,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤涩哟,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后盼玄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體贴彼,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年埃儿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了器仗。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡童番,死狀恐怖精钮,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情剃斧,我是刑警寧澤轨香,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站幼东,受9級(jí)特大地震影響臂容,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜根蟹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一脓杉、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧简逮,春花似錦丽已、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至督赤,卻和暖如春嘁灯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背躲舌。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工丑婿, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓羹奉,卻偏偏與公主長(zhǎng)得像秒旋,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子诀拭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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

  • 邂逅FLutter 萬(wàn)物皆是Widget 一般縮進(jìn)2個(gè)空格 文字居中 Widget Center() Materi...
    JackLeeVip閱讀 3,176評(píng)論 0 4
  • 前言:學(xué)習(xí) Flutter 有一段時(shí)間了迁筛,本篇文章主要是記錄下 Flutter 學(xué)習(xí)歷程的一些心得和開(kāi)發(fā)體驗(yàn),羅列...
    沉江小魚(yú)閱讀 4,741評(píng)論 3 30
  • 你有沒(méi)有想過(guò)開(kāi)發(fā)一款電子游戲耕挨?那你來(lái)對(duì)地方了细卧。這是一系列持續(xù)更新的2D簡(jiǎn)易移動(dòng)游戲的開(kāi)發(fā)教程。 這篇教程是之前一篇...
    _敏訥閱讀 3,315評(píng)論 0 2
  • 學(xué)習(xí)最忌盲目筒占,無(wú)計(jì)劃贪庙,零碎的知識(shí)點(diǎn)無(wú)法串成系統(tǒng)。學(xué)到哪翰苫,忘到哪止邮,面試想不起來(lái)。這里我整理了Flutter面試中最常...
    Nayuta閱讀 3,402評(píng)論 0 1
  • 表情是什么奏窑,我認(rèn)為表情就是表現(xiàn)出來(lái)的情緒农尖。表情可以傳達(dá)很多信息。高興了當(dāng)然就笑了良哲,難過(guò)就哭了盛卡。兩者是相互影響密不可...
    Persistenc_6aea閱讀 125,053評(píng)論 2 7