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

推荐阅读更多精彩内容

  • import requests import pygal from pygal.styleimport Light...
    fa1fb4968d57阅读 153评论 0 0
  • 1 需求概述 1.1项目背景 随着《中共中央国务院关于进一一步加强城市规划建设管理工作的若干意见》的公布,描述了“...
    SuperHaHaS_2bc2阅读 81评论 1 0
  • 前言 使用Bloc的时候,有一个让我至今为止十分在意的问题,无法真正的跨页面交互!在反复的查阅官方文档后,使用一个...
    小呆呆666阅读 8,644评论 19 26
  • 为什么在生活中,我们往往听到这样的话: 你不嫌丢人,我还嫌丢人呢。 看你那没出息的样子! 这么点事情都做不好,干什...
    苏黛_love阅读 291评论 0 0
  • 今天感恩节哎,感谢一直在我身边的亲朋好友。感恩相遇!感恩不离不弃。 中午开了第一次的党会,身份的转变要...
    迷月闪星情阅读 10,498评论 0 11