文科生也編程 - GetX的簡單使用

GetX簡介

GetX是Flutter的輕便而強大的解決方案,它結合了高性能狀態管理,智能依賴注入和快速實用的路由管理。

更新時間:4.18

一、響應式控件

(一)Obx

關鍵詞:StatelessWidget、.obs、Obx()

[ 例:一個加法按鈕 —— 使用.obs變量和Obx對象構建響應式控件 ]

下面這個例子,是基于可觀察的變量和控件Obx構建的一個簡單視圖,點擊按鈕可以改變頁面中的數字;

該例子表明GetX框架可在頁面未進行setState()刷新的情況下,實現由build方法構建的控件的狀態更新。

import 'package:flutter/material.dart';
import 'package:get/get.dart';

// ignore: must_be_immutable
class GetXPage extends StatelessWidget {
  var count = 0.obs;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
          child: Obx(() => Text("$count"))
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => count++,
        child: Icon(Icons.add),
      ),
    );
  }
}

如果沒有使用GetX框架(即變量count無后綴.obs 或Text沒有包含在控件Obx中),則點擊按鈕+,屏幕中的數字不會增加;這是由于,方法build僅執行了一次,onPressed所引起的 count值的變化 并沒有進入控件Text,因而屏幕中的數字顯示不會變化。

[ 編寫步驟 ]

  • 使用StatelessWidget創建Widget對象;
  • 聲明一個"可觀察變量"count,在其后綴.obs,此變量將使用于控件Obx中 ;
  • 使用 控件Obx() 包裹 包含變量count的控件Text() ,常用格式為Obx( () => 目標更新狀態的控件 )

注:使用動態類型的可觀察變量無法用于有類型要求的構造傳參;

(二)GetX

關鍵詞:.obs、GetX()

[ 例:使用別人家的按鈕(一) —— 基于可觀察變量的視圖-邏輯分離 ]

使用GetX框架可以加載并使用非當前類的控制器來更新當前視圖控件的狀態,實現視圖與邏輯的分離;

import 'package:flutter/material.dart';
import 'package:get/get.dart';

class Controller {
  var count = 0.obs;

  void increment() {
    count++;
  }
}

class TestGetX extends StatelessWidget {
  final controller = Controller();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Padding(
            padding: EdgeInsets.all(170),
            child: GetX(builder: (_) => Text('clicks: ${controller.count}')),
          ),
          FloatingActionButton(
            child: Icon(Icons.add),
            onPressed: () => controller.increment(),
          ),
        ],
      ),
    );
  }
}

通過導包本身即能夠在他類構造實例以訪問類中的變量和方法;但如果沒有使用GetX框架,方法increment所引起的 count的值變化,將無法傳遞更新到控件,實現視圖狀態刷新;除了構造實例創建控制器外,該例子的實現還需要將相應變量聲明為 "可觀察的" 。

[ 編寫步驟 ]

1、在控制器中定義 可觀察變量

  • 定義業務邏輯類Controller;
  • 變量count將保存需要更新的數據,因而后綴".obs ";
  • 方法increment能夠引起變量count的值變化;
  • 變量count的"可觀察"特性,意味著當其值發生變化,變量能夠自動響應并更新值變化;

2、將 包含可觀察變量的視圖控件作為 控件GetX的子控件;

  • 創建視圖頁面:使用StatelessWidget;
  • 構建響應控件:將目標更新狀態或顯示變化的控件 傳入 控件GetX的屬性builder中;

3、使用控制器更新變量的值,觀察視圖控件的 狀態響應和變化;

  • 創建動作控件:在FloatingActionButton的onPressed屬性中設置更新觸發
  • 每次點擊將執行一次controller的方法increment;
  • 當count發生變化時,由builder構建的顯示變量count的控件狀態將響應更新;

注:使用控件GetX的前提是,聲明變量可觀察,并非定義繼承GetxController及使用Get.put進行注入;

(三)GetxController & GetBuilder

關鍵詞:GetxController、update、Get.put()、GetBuilder<s>()

[ 例:使用別人家的按鈕(二) —— 使用GetBuilder構建視圖-邏輯分離的響應式控件 ]

使用GetBuilder實現響應控件與使用GetX的差別在于:后者基于變量可觀察,而前者則通過加載指定控制器來更新控件狀態;

import 'package:get/get.dart';

class Controller extends GetxController {
  var count = 0;

  void increment() {
    count++;
    update();
  }
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'test_controller.dart';

class TestGetBuilder extends StatelessWidget {
  final controller = Get.put(Controller());

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.all(170.0),
            child: GetBuilder<Controller>(
                builder: (_) => Text(
                      'clicks: ${controller.count}',
                    )),
          ),
          FloatingActionButton(
            child: Icon(Icons.add),
            onPressed: () => controller.increment(),
          ),
        ],
      ),
    );
  }
}

通過GetBuilder實現響應式控件,控制器必須繼承自GetxController,所定義的目標狀態變量之后無需后綴".obs ",但需要定義方法update;并且加載指定控制器,不僅需要使用Get.put進行注入,而且GetBuilder還需要通過指定泛型綁定目標注入的控制器。

[ 編寫步驟 ]

  • 定義邏輯控制類Controller,繼承自GetxController
    • 定義變量count,保存狀態數據
    • 定義方法increment,改變狀態數據
    • 在方法increment中,執行方法update,更新數據狀態;
  • 使用StatelessWidget創建視圖,將目標顯示變化的控件作為GetBuilder的構建對象;
    • 通過 Get.put() 加載 Controlle實例,控制器將對當前Widget下的所有子路由可用;
    • 將響應變化的控件傳入,泛型指定為Controller的GetBuilder的屬性builder中;
    • 創建動作控件FloatingActionButton,在onPressed中設置每次觸擊將執行controller的方法increment;
    • 當count發生變化,由GetBuilder構建的控件將響應變化并更新狀態;

(四)Get put & find

關鍵詞:Get.put、Get.find

[ 例:最直觀的跨組件狀態通信 —— 在這個頁面觸發事件,在另一個頁面狀態變化 ]

我們在例子-使用別人家的按鈕(二) 中,已了解到Get x框架中的Get.put可以用來注入非當前類的控制器;使用GetBuilder構建響應式控件,不僅需要通過Get.put進行控制器注入,還要求控制器必須作為GetxController實現類實例,且控件GetBuilder必須指定了泛型才能夠真正獲取到控制器,實現在當前視圖使用另一個類中定義的業務邏輯對控件狀態進行變化和更新;

但Get.find使這些步驟得到了魔術般的簡化:使用Get.find能夠發現并獲取到已經通過Get.put注入的控制器,它不僅并不要求控制器來自GetxController的實現類,也無須使用Obx、GetX、GetBuilder等控件 —— 通過Get.find獲取的控制器可以直接應用于當前Widget下的所有子路由,實現真正意義上的跨組件狀態通信。

import 'package:flutter/material.dart';
import 'package:get/get.dart';

class Controller {
  var count = 0;
}

class FirstPage extends StatelessWidget {
  final controller = Get.put(Controller());

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      child: Text('Next Route'),
      onPressed: () {
        controller.count++;
      },
    );
  }
}

class SecondPage extends StatelessWidget {
  final Controller ctrl = Get.find();

  @override
  Widget build(context) {
    return Scaffold(body: Center(child: Text("${ctrl.count}")));
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(context) => Scaffold(
        body: SlidePage(),
      );
}

// ignore: must_be_immutable
class SlidePage extends StatelessWidget {
  List<Widget> pages = <Widget>[FirstPage(), SecondPage()];

  @override
  Widget build(BuildContext context) {
    return PageView.custom(
      scrollDirection: Axis.vertical,
      childrenDelegate: SliverChildBuilderDelegate(
        (BuildContext context, int index) {
          return pages[index];
        },
        childCount: 2,
      ),
    );
  }
}

使用Get.find的局限在于,它必須應用在兩個或兩個以上的widget中,若在同一widget中將無法實現視圖的狀態響應和刷新,這是因為,Get.find的使用前提是:獲取已經通過Get.put在另一個widget完成了注入的控制器。

[ 編寫步驟 ]

1、定義業務邏輯類Controller

  • 定義變量count,保存狀態數據(可以不用".obs "或方法update)
  • Controller可以不必繼承GetxController

2、使用 Get.put 注入控制器,通過控制改變count數據

  • 使用StatelessWidget創建界面

  • 創建控制器:在Get.put()構造Controller實例controller,使后者對當下所有子路由可用;

3、使用 Get.find 獲取控制器,共享其中的數據狀態

  • 調用Get.find從另一個Widget中即頁面FirstPage中,發現已注入的控制器;

    Get作為GetInterface子類私有對象的引用,調用父類拓展中的find,等同于一個GetInstance調用find

  • 通過find方法初始化控制器,并將其保存為ctrl;然后通過ctrl共享變量count中的數據狀態;

4、實現跨頁面的狀態聯動:創建滑動頁面,點擊頁面FirstPage中的Button,頁面SecondPgae中展示的數值將發生變化;

(五)Get.to

[ 例:使用Get.to實現路由跳轉 ]

import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() {
  runApp(GetMyApp());
}

// 使用Get.to來進行頁面路由,需要在GetMaterialApp下構建控件樹
class GetMyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      home: GetPage(),
      // SlidePage(),
    );
  }
}

// 創建Get式可路由頁面
class GetPage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // 通過RaisedButton按鈕onPressed事件觸發跳轉
    return RaisedButton(
      child: Text('Next Route'),
      onPressed: () {
        // 將目標調整頁面作為參數對象傳入Get.to;
        Get.to(TargetPage());
      },
    );
  }
}

// 創建目標跳轉頁
class TargetPage extends StatelessWidget {
  @override
  Widget build(context) {
    return Scaffold(body: Center(child: Text("TargetPage")));
  }
}

[ 整合例:使用Get.to + Get.put + Get.find 實現頁面跳轉計數 ]

import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() {
  runApp(MyApp());

  // testPrint();
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      home: GetPageTwo(),
      // SlidePage(),
    );
  }
}

class Controller extends GetxController {
  var count = 0;
}

class GetPageTwo extends StatelessWidget {
  final controller = Get.put(Controller());

  @override
  Widget build(BuildContext context) {
    return RaisedButton(
      child: Text('Next Route'),
      onPressed: () {
        controller.count++;
        Get.to(Second());
      },
    );
  }
}

class Second extends StatelessWidget {
  final Controller ctrl = Get.find();

  @override
  Widget build(context) {
    return Scaffold(body: Center(child: Text("${ctrl.count}")));
  }
}

二、可伸縮應用程序

(一)Bindings

Bindings是擁有可伸縮應用程序的第一步;

Bindings是管理依賴注入的類,將依賴注入抽離出widget樹,使視圖層更純粹地用于布置widget控件,使代碼更整潔、有條理;

注入在Bindings的控制器,允許任何沒有上下文的地方被訪問;

打開頁面上的Bindings文件,將可以清楚地看將注入頁面的內容;

import 'package:get/get.dart';

class HomeBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut<Controller>(() => Controller());
  }
}

使用作為私有實例GetInstance的引用Get,能夠調用其父類GetInterface拓展中的方法lazyPut

[ 編寫步驟 ]

  • 創建一個類HomeBinding,繼承Bindings,并重寫方法dependencies(Bindings是抽象類,方法dependencies須重寫);
  • 在HomeBinding使用Get.lazyPut(),惰性地注入一個Controller實例,注意類型一致;
  • Bindings類的實例可與路由綁定,綁定后所注入的控制器將對所有子路由可用;

(二)GetView

GetView,是具有“ controller”屬性的StatelessWidget;

在可伸縮應用程序中使用GetView創建界面;

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'home_controller.dart';

class Home extends GetView<HomeController> {
  @override
  Widget build(context) => Scaffold(
      appBar: AppBar(title: Text("counter")),
      body: Center(
        child: Obx(() => Text("${controller.count}")),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: controller.increment,
      ));
}

[ 編寫步驟 ]

  • 創建頁面類Home,繼承自GetView,后者通過指定泛型來確定目標控制器來源;
  • 導入控制器類型文件,在GetView頁面中不需要構造控制器,因而也不需要Get.put()和Get.find();
  • 將有數據狀態更新的控件構造放到obx()中進行,當count發生變化時,obx將自動響應接收新的Text();
  • 設置操作控件FloatingActionButton,在onPressed設置每次觸擊將調用一次controller的方法increment;

(三)GetPage & GetMaterialApp

將GetMaterialApp設置為widget視圖頂端;

控件GetPage可以用來構造一個能夠設置具體依賴注入綁定的命名路由頁面;

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../home_view.dart';
import '../home_bindings.dart';

void main() {
  runApp(GetMaterialApp(
    initialRoute: '/home',
    getPages: [
      GetPage(name: '/home', page: () => Home(), binding: HomeBinding()),
    ],
  ));
}

[ 編寫步驟 ]

  • 使用GetMaterialApp代替MaterialApp(記住,盡可能地避免使用StatefulWidget);
  • 在屬性getPages中構造路由控件GetPage,命名為'/home',它將跳轉至頁面Home;
  • 屬性initialRoute,將對'/home'路由進行初始化并綁定它的依賴注入為HomeBinding;

(四)構建可伸縮應用程序的基本步驟

1、定義業務邏輯類Controller

import 'package:get/get.dart';

class HomeController extends GetxController {
  var count = 0.obs;

  void increment() => count++;
}

2、定義Bindings類,注入依賴

import 'package:get/get.dart';
import 'home_controller.dart';

class HomeBinding implements Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => HomeController());
  }
}

3、使用GetView創建界面

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'home_controller.dart';

class Home extends GetView<HomeController> {
  @override
  Widget build(context) => Scaffold(
      appBar: AppBar(title: Text("counter")),
      body: Center(
        child: Obx(() => Text("${controller.count}")),
      ),
      floatingActionButton: FloatingActionButton(
        child: Icon(Icons.add),
        onPressed: controller.increment,
      ));
}

4、將GetMaterialApp設置為widget視圖頂端;

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../home_view.dart';
import '../home_bindings.dart';

void main() {
  runApp(GetMaterialApp(
    initialRoute: '/home',
    getPages: [
      GetPage(name: '/home', page: () => Home(), binding: HomeBinding()),
    ],
  ));
}

附:使用GetX制作的一個計數器

官方文檔例

import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() {
  runApp(GetMaterialApp(
    // It is not mandatory to use named routes, but dynamic urls are interesting.
    initialRoute: '/home',
    defaultTransition: Transition.native,
    translations: MyTranslations(),
    locale: Locale('pt', 'BR'),
    getPages: [
      //Simple GetPage
      GetPage(name: '/home', page: () => First()),
      // GetPage with custom transitions and bindings
      GetPage(
        name: '/second',
        page: () => Second(),
        customTransition: SizeTransitions(),
        binding: SampleBind(),
      ),
      // GetPage with default transitions
      GetPage(
        name: '/third',
        transition: Transition.cupertino,
        page: () => Third(),
      ),
    ],
  ));
}

class MyTranslations extends Translations {
  @override
  Map<String, Map<String, String>> get keys => {
    'en': {
      'title': 'Hello World %s',
    },
    'en_US': {
      'title': 'Hello World from US',
    },
    'pt': {
      'title': 'Olá de Portugal',
    },
    'pt_BR': {
      'title': 'Olá do Brasil',
    },
  };
}

class Controller extends GetxController {
  int count = 0;
  void increment() {
    count++;
    // use update method to update all count variables
    update();
  }
}

class First extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        leading: IconButton(
          icon: Icon(Icons.add),
          onPressed: () {
            Get.snackbar("Hi", "I'm modern snackbar");
          },
        ),
        title: Text("title".trArgs(['John'])),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            GetBuilder<Controller>(
                init: Controller(),
                // You can initialize your controller here the first time. Don't use init in your other GetBuilders of same controller
                builder: (_) => Text(
                  'clicks: ${_.count}',
                )),
            RaisedButton(
              child: Text('Next Route'),
              onPressed: () {
                Get.toNamed('/second');
              },
            ),
            RaisedButton(
              child: Text('Change locale to English'),
              onPressed: () {
                Get.updateLocale(Locale('en', 'UK'));
              },
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
          child: Icon(Icons.add),
          onPressed: () {
            Get.find<Controller>().increment();
          }),
    );
  }
}

class Second extends GetView<ControllerX> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('second Route'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            GetX<ControllerX>(
              // Using bindings you don't need of init: method
              // Using Getx you can take controller instance of "builder: (_)"
              builder: (_) {
                print("count1 rebuild");
                return Text('${_.count1}');
              },
            ),
            GetX<ControllerX>(
              builder: (_) {
                print("count2 rebuild");
                return Text('${controller.count2}');
              },
            ),
            GetX<ControllerX>(builder: (_) {
              print("sum rebuild");
              return Text('${_.sum}');
            }),
            GetX<ControllerX>(
              builder: (_) => Text('Name: ${controller.user.value.name}'),
            ),
            GetX<ControllerX>(
              builder: (_) => Text('Age: ${_.user.value.age}'),
            ),
            RaisedButton(
              child: Text("Go to last page"),
              onPressed: () {
                Get.toNamed('/third', arguments: 'arguments of second');
              },
            ),
            RaisedButton(
              child: Text("Back page and open snackbar"),
              onPressed: () {
                Get.back();
                Get.snackbar(
                  'User 123',
                  'Successfully created',
                );
              },
            ),
            RaisedButton(
              child: Text("Increment"),
              onPressed: () {
                Get.find<ControllerX>().increment();
              },
            ),
            RaisedButton(
              child: Text("Increment"),
              onPressed: () {
                Get.find<ControllerX>().increment2();
              },
            ),
            RaisedButton(
              child: Text("Update name"),
              onPressed: () {
                Get.find<ControllerX>().updateUser();
              },
            ),
            RaisedButton(
              child: Text("Dispose worker"),
              onPressed: () {
                Get.find<ControllerX>().disposeWorker();
              },
            ),
          ],
        ),
      ),
    );
  }
}

class Third extends GetView<ControllerX> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      floatingActionButton: FloatingActionButton(onPressed: () {
        controller.incrementList();
      }),
      appBar: AppBar(
        title: Text("Third ${Get.arguments}"),
      ),
      body: Center(
          child: Obx(() => ListView.builder(
              itemCount: controller.list.length,
              itemBuilder: (context, index) {
                return Text("${controller.list[index]}");
              }))),
    );
  }
}

class SampleBind extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut<ControllerX>(() => ControllerX());
  }
}

class User {
  User({this.name = 'Name', this.age = 0});
  String name;
  int age;
}

class ControllerX extends GetxController {
  final count1 = 0.obs;
  final count2 = 0.obs;
  final list = [56].obs;
  final user = User().obs;

  updateUser() {
    user.update((value) {
      value.name = 'Jose';
      value.age = 30;
    });
  }

  /// Once the controller has entered memory, onInit will be called.
  /// It is preferable to use onInit instead of class constructors or initState method.
  /// Use onInit to trigger initial events like API searches, listeners registration
  /// or Workers registration.
  /// Workers are event handlers, they do not modify the final result,
  /// but it allows you to listen to an event and trigger customized actions.
  /// Here is an outline of how you can use them:

  /// made this if you need cancel you worker
  Worker _ever;

  @override
  onInit() {
    /// Called every time the variable $_ is changed
    _ever = ever(count1, (_) => print("$_ has been changed (ever)"));

    everAll([count1, count2], (_) => print("$_ has been changed (everAll)"));

    /// Called first time the variable $_ is changed
    once(count1, (_) => print("$_ was changed once (once)"));

    /// Anti DDos - Called every time the user stops typing for 1 second, for example.
    debounce(count1, (_) => print("debouce$_ (debounce)"),
        time: Duration(seconds: 1));

    /// Ignore all changes within 1 second.
    interval(count1, (_) => print("interval $_ (interval)"),
        time: Duration(seconds: 1));
  }

  int get sum => count1.value + count2.value;

  increment() => count1.value++;

  increment2() => count2.value++;

  disposeWorker() {
    _ever.dispose();
    // or _ever();
  }

  incrementList() => list.add(75);
}

class SizeTransitions extends CustomTransition {
  @override
  Widget buildTransition(
      BuildContext context,
      Curve curve,
      Alignment alignment,
      Animation<double> animation,
      Animation<double> secondaryAnimation,
      Widget child) {
    return Align(
      alignment: Alignment.center,
      child: SizeTransition(
        sizeFactor: CurvedAnimation(
          parent: animation,
          curve: curve,
        ),
        child: child,
      ),
    );
  }
}

官方學習文檔:https://pub.flutter-io.cn/packages/get#about-get

編程小白,如有謬誤,歡迎指出,由衷感謝!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,156評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,401評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,069評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,873評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,635評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,128評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,203評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,365評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,881評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,733評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,935評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,475評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,172評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,582評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,821評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,595評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,908評論 2 372

推薦閱讀更多精彩內容

  • import requests import pygal from pygal.styleimport Light...
    fa1fb4968d57閱讀 169評論 0 0
  • 1 需求概述 1.1項目背景 隨著《中共中央國務院關于進一一步加強城市規劃建設管理工作的若干意見》的公布,描述了“...
    SuperHaHaS_2bc2閱讀 87評論 1 0
  • 前言 使用Bloc的時候,有一個讓我至今為止十分在意的問題,無法真正的跨頁面交互!在反復的查閱官方文檔后,使用一個...
    小呆呆666閱讀 8,918評論 19 26
  • 為什么在生活中,我們往往聽到這樣的話: 你不嫌丟人,我還嫌丟人呢。 看你那沒出息的樣子! 這么點事情都做不好,干什...
    蘇黛_love閱讀 356評論 0 0
  • 今天感恩節哎,感謝一直在我身邊的親朋好友。感恩相遇!感恩不離不棄。 中午開了第一次的黨會,身份的轉變要...
    迷月閃星情閱讀 10,590評論 0 11