Android Flutter 构建布局UI实战(二)

这几天一直在学习, 今天有时间整理一下学习的内容用于记录与分享,详细的控件使用描述有兴趣的可以去官网上看,我这边自己写了一个很简单的小demo,包含了一些基础的知识。


surprised.png

记录的知识点:
· 1 底部菜单导航
· 2 页面的跳转
· 3 ListView
· 4 吐司(这个是内部实现引用的,具体flutter自带的框架,暂时不清楚)
· 5 涉及到一些布局的书写(属性)
· 6 res资源的引用
· 7 涉及到的一些widget使用介绍,或在注解或在代码片段后。

项目效果图:

home.png

一、页面的跳转
我自己写了一个页面就放了一个RaisedButton,跳转到首页。


transition.png

RaisedButton就是一个button,实现onPressed监听btn事件。
Navigator这个是用来进行跳转页面的。

涉及到的一些需要介绍的控件我用都**来表示注解了, //看不清楚
import 'package:flutter/material.dart'; **基本多是这个包
import 'package:flutter_app/MainActivity.dart'; **我跳转的首页面

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget{
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(**MaterialApp个人理解为程序的渲染入口
      title: 'Hello World',
      theme: new ThemeData( **全局主题只是由应用程序根MaterialApp创建的Theme来表示
        primaryColor: Colors.lightBlue,
      ),
      home: new RandomWords(),**调用方法体
    );

  }
}

class RandomWordsState  extends State<RandomWords>{

**Scaffold 是 Material library 中提供的一个widget,
 它提供了默认的导航栏、标题和包含主屏幕widget树的body属性。widget树可以很复杂。
** Center这个空间居中
** RaisedButton  = button
** Navigator页面的跳转,差不多都是这个写法,固定
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Center(
        child: new RaisedButton(
            child: new Text('登录'),
            onPressed: (){
          Navigator.push(context, new MaterialPageRoute(builder: (context)=>new MainActivity()));
        }),
      ),
    );
  }
}

class RandomWords extends StatefulWidget {
  @override
  ** =>单行函数或方法的简写
  createState() => new RandomWordsState();
}

其中StatelessWidget它是表示所有的属性都是最终的,可以理解为属性不可变。
StatefulWidget 我觉得可以理解为android中某一个自定义的方法(代码书写ui),方法体的内容是可变的,当然它也是一个widget。在flutter中书写一个这样的方法,就需要按照上述代码中的方式来书写。

二、MainActivity页面

  • images文件的引用。

页面中包含了一些图片资源,记录一下images的引用方式。

项目结构图.png

一开始Flutter是没有images文件夹的,自己创建一个,跟android ios保持同级。
pubspec.yaml文件中进行images的关联,pubspec.yaml这个可以理解为build.gradle。

引用界面.png

在Flutter的节点下新增(引用全目录 ,若单张全名称包含后缀):
assets:
- images/
添加完毕后在右上角有同步按钮,别忘了


image.png

package get 加载引入的包
package upgradle 升级包
flutter upgradle 整理升级 包括Dart SDK version等
flutter doctor 检测需要安装的东西

  • 包的引用
    当初在看官方的时候,引用了一个english_words的包,项目中没有用,但是 这边记录一下引用包的方式。


    image.png

lib/main.dartimport 'package:english_words/english_words.dart';就可以了,需要注意的是,在pubspec.yaml中添加了之后记得package get,在弹出的message窗口中Process finished with exit code 0 表示引用成功。

  • 底部导航 BottomNavigationBarItem

在看代码之前:

此处简要一下代码的书写逻辑。
因为页面是可变可调整的,所以我肯定需要书写StatefulWidget。
接着 初始了切换的图片,文字等资源
在BottomNavigationBarItem中主要是通过 下标 切换图片和文字的显示,当然也包含切换页面,切换页面的书写方式类似android中的fragment,属于独立页面,配合使用IndexedStack进行切换页面的显示与隐藏。

具体的代码含义,我在注释里进行介绍。

import 'package:flutter/cupertino.dart'; **底部导航切换,需导入
import 'package:flutter/material.dart'; **上面注解介绍过
import 'package:flutter_app/page/homeinfo.dart';**fragment页面
import 'package:flutter_app/page/myinfo.dart';**fragment页面

void main() => runApp(new MainActivity());

class MainActivity extends StatelessWidget {
  **这块没啥介绍的, 同上 
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Hello World',
      theme: new ThemeData(
        primaryColor: Colors.blue,
      ),
      home: new RandomWords(),
    );
  }
}

class RandomWordsState extends State<RandomWords> {
  int _tabIndex = 0;  ** 默认当前页

//  static const double IMAGE_ICON_WIDTH = 30.0; 标题上的返回按钮
//  static const double ARROW_ICON_HEIGHT = 16.0; 标题上的返回按钮

  final normalTextColor = new TextStyle(color: const Color(0xff969696)); **默认的颜色
  final selectTextColor = new TextStyle(color: const Color(0xff63ca6c)); **选择的颜色

  var tabImage;  **切换的image
  var _body;   **IndexedStack的对象
  var tabNameList = ['首页', '地图', '我的']; **底部导航名称
  var titleNameList = ['动服务平台', '地图', '我的']; **标题名称
  
//  var leftIcon;标题上的返回按钮
//  RandomWordsState(){
//    leftIcon = setImages("images/icon_left.png");
//  }

**统一设置image属性 ,path为images的引用路径
  Image getImagePath(path) {
    return new Image.asset(
      path,
      width: 20.0,
      height: 20.0,
    );
  }

**切换图片的初始化,包括切换页面的body初始 , getImagePath为统一设置的images属性。
  void initData() {
    if (tabImage == null) {
      tabImage = [
        [
          getImagePath('images/activity_home_unchecked.png'),
          getImagePath('images/activity_home_checked.png')
        ],
        [
          getImagePath('images/activity_map_unchecked.png'),
          getImagePath('images/activity_map_checked.png')
        ],
        [
          getImagePath('images/activity_mine_unchecked.png'),
          getImagePath('images/activity_mine_checked.png')
        ],
      ];
    }

    ** children这个我个人理解它是一个组合控件,像是一个容器,可以包含很多不同的Ui,然后拼凑到一起。
    _body = new IndexedStack(
      children: <Widget>[new HomeInfo(), new MyInfo(), new MyInfo()],
      index: _tabIndex,
    );
  }


** 根据下标返回 text的颜色值
  TextStyle getTabTextStyle(int curIndex) {
    if (curIndex == _tabIndex) {
      return selectTextColor;
    }
    return normalTextColor;
  }
** 调用getTabTextStyle 根据下标设置text的颜色值
  Text getTabTitle(int curIndex) {
    return new Text(tabNameList[curIndex], style: getTabTextStyle(curIndex));
  }

** 返回当前下标的images中的 所选图片
  Image getTabIcon(int curIndex) {
    if (curIndex == _tabIndex) {
      return tabImage[curIndex][1];
    }
    return tabImage[curIndex][0];
  }



  //设置iamge的位置
//  Widget setImages(path) {
//    return new Padding(
//        padding: const EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
//        child: new Image.asset(path,
//            width: IMAGE_ICON_WIDTH, height: ARROW_ICON_HEIGHT));
//  }


** AppBar标题{title标题文字 {Center标题位置 child{标题内容} } }
** body切换的index页面
** bottomNavigationBar底部导航{items导航数组{0,1,2}}
**onTap点击(Index作为点击返回值) {setState通知框架状态已经改变{_tabIndex 赋值当前Index}}
  @override
  Widget build(BuildContext context) {
    initData();
    return new MaterialApp(

      home: new Scaffold(
        appBar: new AppBar(
            title: new Center(
              child: new Text(titleNameList[_tabIndex],
                  style: new TextStyle(color: Colors.white)),
//              child: new Row(
//                children: <Widget>[
//                  leftIcon,
//                  new Text(tabNameList[_tabIndex],
//                      style: new TextStyle(color: Colors.white))
//                ],
//              ),
            ),
            iconTheme: new IconThemeData(color: Colors.white)),
        body: _body,
        bottomNavigationBar: new CupertinoTabBar(
          items: <BottomNavigationBarItem>[
            new BottomNavigationBarItem(
                icon: getTabIcon(0),
                title: getTabTitle(0)),
            new BottomNavigationBarItem(
                icon: getTabIcon(1),
                title: getTabTitle(1)),
            new BottomNavigationBarItem(
                icon: getTabIcon(2),
                title: getTabTitle(2)),
          ],
          currentIndex: _tabIndex,
          onTap: (index) {
            setState(() {
              _tabIndex = index;
            });
          },
        ),
      ),
    );
  }
}

class RandomWords extends StatefulWidget {
  @override
  createState() => new RandomWordsState();
}

底部切换基本就这些,你要是只是想测试一下底部切换效果也可以像我indexedStack那样一样,除了首页,剩下的复用。

  • HomeInfo.dart页面


    HomeInfo.png

考虑到页面的布局,我分成了2个层级,网格显示是一个层级,报表是另外的一个层级,可以说是ListView 的两个item,只不过是不同的item布局。当然这只是我个人的想法,经过思考后在StatefulWidget,我返回的就是一个ListView .

var title = ["项目信息", "农村公路建设统计报表", "路网结构改造统计报表"];

 @override
  Widget build(BuildContext context) {
    var listview = new ListView.builder(
        itemCount: title.length, itemBuilder: (context, i) => renderRow(i));
    return listview;
  }

ListView 初始化:
itemcount 网格是一个单独的布局,另外的两个可以复用布局。
renderRow是我自定义的方法
i 算是0 、1、 2,其中1、2布局复用。

renderRow(int i) {
    if (i == 0) { ** 此处i=0初始化网格的样式。
      var projectInfo = new Container(
//          color: const Color.fromRGBO(255, 255, 255, 255.0),
        decoration: new BoxDecoration(
          color: Colors.white,
        ),
        child: new Center(
          child: new Column(
            children: <Widget>[
              new Text(
                title[0],
                textAlign: TextAlign.left,
              ),
              new Container(
                color: const Color.fromRGBO(240, 248, 255, 200.0),
                child: new Row(
                  children: <Widget>[
                    new Expanded(
                        flex: 1,
                        child: new Container(
                          margin: const EdgeInsets.only(
                              top: 10.0, right: 5.0, left: 10.0, bottom: 10.0),
                          decoration: new BoxDecoration(
                              border: new Border.all(
                                  width: 1.0, color: Colors.black12),
                              borderRadius: const BorderRadius.all(
                                  const Radius.circular(10.0))),
                          height: 100.0,
                          child: new Center(
                            child: new Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: <Widget>[
                                new IconButton(
                                    icon: new Image.asset(
                                      "images/icon_way.png",
                                      width: 50.0,
                                      height: 50.0,
                                    ),
                                    onPressed: () {
                                      showShort("农村公路建设类项目");
                                    }),
                                new Center(child: new Text("农村公路建设类项目"))
                              ],
                            ),
                          ),
                        )),
                    new Expanded(
                        flex: 1,
                        child: new Container(
                          margin: const EdgeInsets.only(
                              top: 10.0, right: 10.0, left: 5.0, bottom: 10.0),
                          decoration: new BoxDecoration(
                              border: new Border.all(
                                  width: 1.0, color: Colors.black12),
                              borderRadius: const BorderRadius.all(
                                  const Radius.circular(10.0))),
                          height: 100.0,
                          child: new Center(
                              child: new Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: <Widget>[
                              new IconButton(
                                  icon: new Image.asset(
                                    "images/icon_reform.png",
                                    width: 50.0,
                                    height: 50.0,
                                  ),
                                  onPressed: () {
                                    showShort("农村公路建设类项目");
                                  }),
                              new Center(child: new Text("危桥改造类项目"))
                            ],
                          )),
                        ))
                  ],
                ),
              ),
              new Container(
                child: new Row(
                  children: <Widget>[
                    new Expanded(
                        flex: 1,
                        child: new Container(
                          margin: const EdgeInsets.only(
                              top: 0.0, right: 5.0, left: 10.0, bottom: 10.0),
                          decoration: new BoxDecoration(
                              border: new Border.all(
                                  width: 1.0, color: Colors.black12),
                              borderRadius: const BorderRadius.all(
                                  const Radius.circular(10.0))),
                          height: 100.0,
                          child: new Center(
                            child: new Column(
                              mainAxisAlignment: MainAxisAlignment.center,
                              children: <Widget>[
                                new IconButton(
                                    icon: new Image.asset(
                                      "images/icon_security.png",
                                      width: 50.0,
                                      height: 50.0,
                                    ),
                                    onPressed: () {
                                      showShort("县乡安防工程类项目");
                                    }),
                                new Center(child: new Text("县乡安防工程类项目"))
                              ],
                            ),
                          ),
                        )),
                    new Expanded(
                        flex: 1,
                        child: new Container(
                          margin: const EdgeInsets.only(
                              top: 0.0, right: 10.0, left: 5.0, bottom: 10.0),
                          decoration: new BoxDecoration(
                              border: new Border.all(
                                  width: 1.0, color: Colors.black12),
                              borderRadius: const BorderRadius.all(
                                  const Radius.circular(10.0))),
                          height: 100.0,
                          child: new Center(
                              child: new Column(
                            mainAxisAlignment: MainAxisAlignment.center,
                            children: <Widget>[
                              new IconButton(
                                  icon: new Image.asset(
                                    "images/icon_security_green.png",
                                    width: 50.0,
                                    height: 50.0,
                                  ),
                                  onPressed: () {
                                    showShort("村道安防工程类项目");
                                  }),
                              new Center(child: new Text("村道安防工程类项目"))
                            ],
                          )),
                        ))
                  ],
                ),
              ),
            ],
          ),
        ),
      );
      return new GestureDetector(
        child: projectInfo,
      );
    }

    new Container(
      child: new Text(title[i]),
    );

    var listCountItem = new Padding(
      padding: const EdgeInsets.fromLTRB(15.0, 10.0, 0.0, 10.0),
      child: new Column(
        children: <Widget>[
          new Container(
            child: new Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                new Text(title[i]),
                new Container(
                  height: 200.0,
                  child: new ListView(
                    children: <Widget>[
                      new ListTile(
                        leading: new Icon(Icons.map),
                        title: new Text('Maps'),
                      ),
                      new ListTile(
                        leading: new Icon(Icons.photo_album),
                        title: new Text('Album'),
                      ),
                      new ListTile(
                        leading: new Icon(Icons.phone),
                        title: new Text('Phone'),
                      ),
                    ],
                  ),
                )
              ],
            ),
          ),
//          new ListView(
//            children: <Widget>[
//              new ListTile(
//                title: new Text('123123'),
//              )
//            ],
//          )
        ],
      ),
    );

    return new InkWell(
      child: listCountItem,
      onTap: () {
        showShort("1111");
      },
    );
  }

部分控件属性介绍:

  • Container也是一个widget,允许自定义其子widget。
    个人理解就是一个容器,可以添加一些别的widget组合成想要的ui样式 。可添加填充,边距,边框或背景色。
  • BoxDecoration这个类似shape可以操作内填充颜色,圆角等
  • Expanded 这个可以理解为权重,flex表示当前包裹控件所在父布局的权重比例。
  • children: <Widget>[]这个属性相当于多个Item一样,数组中的每一个值都可以看做成一个Item,具体什么样的ui样式看你自己怎么写。
  • mainAxisAlignment和crossAxisAlignment属性用来对齐其子项。 对于行(Row)来说,主轴是水平方向,横轴垂直方向。对于列(Column)来说,主轴垂直方向,横轴水平方向。具体的详细参数,对照官网。
  • GestureDetector这个是用来检测用户做出的手势,点击的时候会回调onTap,我这边没有写onTap,我是单独在iconButton中做的点击处理。写的有点问题,不过顺带的介绍一下这个。
  • InkWell实现水波纹,边框效果,跟BoxDecorationc差不多。

关于吐司showShort,分享实现方式(改的原生):

  • Android


    Android.png
 new MethodChannel(getFlutterView(), "com.coofee.flutterdemoapp/sdk/toast")
            .setMethodCallHandler(new MethodChannel.MethodCallHandler() {
              @Override
              public void onMethodCall(MethodCall methodCall, MethodChannel.Result result) {
                if ("show".equals(methodCall.method)) {
                  String text = methodCall.argument("text");
                  int duration = methodCall.argument("duration");
                  Toast.makeText(MainActivity.this, text, duration).show();
                }
              }
            });
  • IOS


    image.png
#include "AppDelegate.h"
#include "GeneratedPluginRegistrant.h"

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application
    didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
  [GeneratedPluginRegistrant registerWithRegistry:self];
  // Override point for customization after application launch.

  FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;

    FlutterMethodChannel* toastChannel = [FlutterMethodChannel
                                              methodChannelWithName:@"com.coofee.flutterdemoapp/sdk/toast"
                                              binaryMessenger:controller];

    [toastChannel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
        if ([@"show" isEqualToString:call.method]) {
            // 展示toast;
            NSLog(@"显示toast....")
        }
    }];

  return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

  • Flutter
import 'package:flutter/services.dart';

// 下划线开头的变量只在当前package中可见。
const _toast = const MethodChannel('com.coofee.flutterdemoapp/sdk/toast');

const int _LENGTH_SHORT = 0;

const int _LENGTH_LONG = 1;

void show(String text, int duration) async {
  try {
    await _toast.invokeMethod("show", {'text': text, 'duration': duration});
  } on Exception catch (e) {
    print(e);
  } on Error catch (e) {
    print(e);
  }
}

void showShort(String text) {
  show(text, _LENGTH_SHORT);
}

void showLong(String text) {
  show(text, _LENGTH_LONG);
}

  • MyInfo.dart页面


    MyInfo.png

MyInfo相对上一个页面要简单不少,主要是ListView,剩下的就是一些资源的初始化。

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app/utils/toastdart.dart';


class MyInfo extends StatefulWidget {
  @override
  createState() => new MyInfoState();
}

class MyInfoState extends State<MyInfo> {
  static const double IMAGE_ICON_WIDTH = 30.0;
  static const double ARROW_ICON_WIDTH = 16.0;

  var inons = [];
  var titleTextStyle = new TextStyle(fontSize: 16.0);
  var title = ["用户指南", "地图设置", "路网数据", "数据备份", "项目数据更新", "关于系统", "退出登录"];
  var images = [
    "images/one.png",
    "images/two.png",
    "images/three.png",
    "images/four.png",
    "images/five.png",
    "images/six.png",
    "images/senven.png",
  ];
  var rightIcon = new Image.asset(
    "images/icon_right.png",
    width: IMAGE_ICON_WIDTH,
    height: ARROW_ICON_WIDTH,
  );

  MyInfoState() {
    for (int i = 0; i < images.length; i++) {
      inons.add(setImages(images[i]));
    }
  }

  //设置iamge的位置
  Widget setImages(path) {
    return new Padding(
        padding: const EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
        child: new Image.asset(path,
            width: IMAGE_ICON_WIDTH, height: ARROW_ICON_WIDTH));
  }

  @override
  Widget build(BuildContext context) {
    var listview = new ListView.builder(
        itemCount: title.length , itemBuilder: (context, i) => renderRow(i));
    return listview;
  }

  renderRow(int i) {
      String itemName =  title[i];
      var itemCount =  new Padding(
        padding: const EdgeInsets.fromLTRB(25.0, 25.0, 25.0, 25.0),
        child: new Row(
          children: <Widget>[
            inons[i],
            new Expanded(
                child: new Text(
                  itemName,
                  style: titleTextStyle,
                )),
            rightIcon
          ],
        ),
      );
      return new InkWell(
        child: itemCount,
        onTap: (){
          //toast
          showShort(itemName);
//          Navigator.of(context).push(new MaterialPageRoute(
//              builder: (context)=> new MainActivity()));
        },
      );
  }
}

EdgeInsets类似Android里面的margin。

总结:
万物皆Widget。
若看的不太舒服,望见谅···

github:https://github.com/BINBINXIAO/FlutterDemo

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

推荐阅读更多精彩内容