美文网首页
Flutter系列04: 漂亮的列表UI和详情页面 (第二部分)

Flutter系列04: 漂亮的列表UI和详情页面 (第二部分)

作者: 渣渣曦 | 来源:发表于2020-08-26 18:17 被阅读0次

    本文为列表UI和详情页面的第二部门,集中写动态内容,导航,详情页,优化字体,图标,文本。

    增加图片及字体

    增加明细页面中用到的图片和雅黑字体(Raleway fonts)到assets中,创建fonts目录,下载ttf文件至该目录中。增加图片到assets目录中。打开pubspec.yaml文件增加以下内容

      assets:
       - drive-steering-wheel.jpg
    
      fonts:
        - family: Raleway
          fonts:
            - asset: fonts/Raleway-Regular.ttf
            - asset: fonts/Raleway-Thin.ttf
              style: normal
    

    动态列表内容

    创建展示课程对象的类来建立课程列表项的动态内容。为了简化开发,课程价格数据类型为整型,其他(标题、年级、内容)为字符串类型。创建lesson.dart文件,构造体类代码如下:

    class Lesson {
      String title;
      String level;
      double indicatorValue;
      int price;
      String content;
    
      Lesson(
          {this.title, this.level, this.indicatorValue, this.price, this.content});
    }
    

    lib文件夹下建立model文件夹保存该文件,使用前使用以下代码导入lesson.dart

    import 'package:beautiful_list/model/lesson.dart';
    

    通常,数据需要通过API请求获取。本案例为方便直接在main.dart文件里调用getLessions()返回课程列表

    List getLessons(){
      return [
        Lesson(
          title: "Flutter入门",
          level: "初级",
          indicatorValue: 0.33,
          price: 20,
          content:
            "首先花几分钟阅读本节中的信息。 启动您的应用程序,然后单击“设置”菜单。 在设置页面上,单击保存按钮。 您应该在页面中间看到一个圆形的进度指示器显示,并且由于启动了modal,因此无法单击用户界面元素。"
        ),
        Lesson(
          title: "Angular入门",
          level: "初级",
          indicatorValue: 0.33,
          price: 50,
          content:
            "首先花几分钟阅读本节中的信息。 启动您的应用程序,然后单击“设置”菜单。 在设置页面上,单击保存按钮。 您应该在页面中间看到一个圆形的进度指示器显示,并且由于启动了modal,因此无法单击用户界面元素。"
        ),
        Lesson(
          title: "webRTC实战",
          level: "中级",
          indicatorValue: 0.66,
          price: 30,
          content:
            "首先花几分钟阅读本节中的信息。 启动您的应用程序,然后单击“设置”菜单。 在设置页面上,单击保存按钮。 您应该在页面中间看到一个圆形的进度指示器显示,并且由于启动了modal,因此无法单击用户界面元素。"
        ),
        Lesson(
          title: "golang并发设计",
          level: "中级",
          indicatorValue: 0.33,
          price: 50,
          content:
            "首先花几分钟阅读本节中的信息。 启动您的应用程序,然后单击“设置”菜单。 在设置页面上,单击保存按钮。 您应该在页面中间看到一个圆形的进度指示器显示,并且由于启动了modal,因此无法单击用户界面元素。"
        ),
        Lesson(
          title: "设计模式",
          level: "高级",
          indicatorValue: 1.0,
          price: 50,
          content:
            "首先花几分钟阅读本节中的信息。 启动您的应用程序,然后单击“设置”菜单。 在设置页面上,单击保存按钮。 您应该在页面中间看到一个圆形的进度指示器显示,并且由于启动了modal,因此无法单击用户界面元素。"
        ),
        Lesson(
          title: "分布式计算",
          level: "高级",
          indicatorValue: 1.0,
          price: 50,
          content:
            "首先花几分钟阅读本节中的信息。 启动您的应用程序,然后单击“设置”菜单。 在设置页面上,单击保存按钮。 您应该在页面中间看到一个圆形的进度指示器显示,并且由于启动了modal,因此无法单击用户界面元素。"
        ),
        Lesson(
          title: "机器学习",
          level: "高级",
          indicatorValue: 1.0,
          price: 50,
          content:
            "首先花几分钟阅读本节中的信息。 启动您的应用程序,然后单击“设置”菜单。 在设置页面上,单击保存按钮。 您应该在页面中间看到一个圆形的进度指示器显示,并且由于启动了modal,因此无法单击用户界面元素。"
        ),
      ];
    }
    

    接下来重构main.dart文件。首先增加一个List类型的变量。初始化课程变量指向getLessons()函数来加载所有的课程。

    class ListPage extends StatelessWidget {
      final String title;
      List lessons;
      ListPage({Key key, this.title}) : super(key: key);
      @override
      Widget build(BuildContext context) {
        lessons = getLessons();
        ....
        final makeBody = Container(
          child: ListView.builder(
            scrollDirection: Axis.vertical,
            shrinkWrap: true,
            itemCount: lessons.length,
            itemBuilder: (BuildContext context, int index){
              return makeCard(lessons[index]);
            }
          ),
        );
    

    重构下面的final变量为class方法:makeCard变为makeCard(),makeListTile变为makeListTile(lesson)

         Card makeCard(Lesson lesson) => Card(
              elevation: 8.0,
              margin: EdgeInsets.symmetric(horizontal: 10.0, vertical: 6.0),
              child: Container(
                decoration: BoxDecoration(color: Color.fromRGBO(64, 75, 96, .9)),
                child: makeListTile(lesson),
              ),
            );
    

    makeListTile变量变为makeListTile(Lesson lesson)方法。对于更复杂的应用来说,这种自定义组件可以更好的增加重用性。使用课程对象取代原有硬核值,使用LinearProgressIndicator组件取代图片indicator,增加Padding包含文本组件,变更如下:

       ListTile makeListTile(Lesson lesson) => ListTile(
              contentPadding:
                  EdgeInsets.symmetric(horizontal: 20.0, vertical: 10.0),
              leading: Container(
                padding: EdgeInsets.only(right: 12.0),
                decoration: BoxDecoration(
                    border: Border(
                        right: BorderSide(width: 1.0, color: Colors.white24))),
                child: Icon(Icons.autorenew, color: Colors.white),
              ),
              title: Text(
                lesson.title,
                style: TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
              ),
              subtitle: Row(
                children: <Widget>[
                  Expanded(
                      flex: 1,
                      child: Container(
                        // tag: 'hero',
                        child: LinearProgressIndicator(
                            backgroundColor: Color.fromRGBO(209, 224, 224, 0.2),
                            value: lesson.indicatorValue,
                            valueColor: AlwaysStoppedAnimation(Colors.green)),
                      )),
                  Expanded(
                    flex: 4,
                    child: Padding(
                        padding: EdgeInsets.only(left: 10.0),
                        child: Text(lesson.level,
                            style: TextStyle(color: Colors.white))),
                  )
                ],
              ),
              trailing:
                  Icon(Icons.keyboard_arrow_right, color: Colors.white, size: 30.0),
              onTap: () {
    
                Navigator.push(
                    context, MaterialPageRoute(builder: (context) => DetailPage()));
              },
            );
    

    运行应用如下


    image.png

    导航

    Flutter提供navigator导航组件。ListTile包含onTap()函数,增加以下代码至该函数中

    onTap: () {
           Navigator.push(context,
                    MaterialPageRoute(
                        builder: (context) => DetailPage(lesson: lesson)
                    )
          );
    }
    

    代码执行从当前视图跳转到详情页面。

    详情页

    image.png
    创建detail_page.dart文件并创建无状态组件命名为DetailPage
    import 'package:beautiful_list/model/lesson.dart';
    import 'package:flutter/material.dart';
    
    class DetailPage extends StatelessWidget {
      final Lesson lesson;
      DetailPage({Key key, this.lesson}) : super(key: key);
      @override
      
      Widget build(BuildContext context) {
         return Scaffold(
          body: Column(
            children: <Widget>[topContent, bottomContent],
          ),
        );
      }
    }
    

    接着创建头部内容(topContent)和底部内容(bottomContent)。头部内容为Stack组件,children包含背景图,container文本和一个固定位置(positioned组件)返回按键。

        final topContent = Stack(
          children: <Widget>[
            Container(
                padding: EdgeInsets.only(left: 10.0),
                height: MediaQuery.of(context).size.height * 0.5,
                decoration: BoxDecoration(
                  image: DecorationImage(
                    image: AssetImage("drive-steering-wheel.jpg"),
                    fit: BoxFit.cover,
                  ),
                )),
            Container(
              height: MediaQuery.of(context).size.height * 0.5,
              padding: EdgeInsets.all(40.0),
              width: MediaQuery.of(context).size.width,
              decoration: BoxDecoration(color: Color.fromRGBO(58, 66, 86, .9)),
              child: Center(
                child: topContentText,
              ),
            ),
            Positioned(
              left: 8.0,
              top: 60.0,
              child: InkWell(
                onTap: () {
                  Navigator.pop(context);
                },
                child: Icon(Icons.arrow_back, color: Colors.white),
              ),
            )
          ],
        );
    

    topContentText为一个Column组件,如图

    image.png
    代码如下
       final topContentText = Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            SizedBox(height: 80.0),
            Icon(
              Icons.directions_car,
              color: Colors.white,
              size: 30.0,
            ),
            Container(
              width: 90.0,
              child: Divider(color: Colors.green),
            ),
            SizedBox(height: 10.0),
            Text(
              lesson.title,
              style: TextStyle(color: Colors.white, fontSize: 35.0),
            ),
            SizedBox(height: 10.0),
            Row(
              mainAxisAlignment: MainAxisAlignment.start,
              children: <Widget>[
                Expanded(flex: 1, child: levelIndicator),
                Expanded(
                    flex: 6,
                    child: Padding(
                        padding: EdgeInsets.only(left: 10.0),
                        child: Text(
                          lesson.level,
                          style: TextStyle(color: Colors.white),
                        ))),
                Expanded(flex: 2, child: coursePrice)
              ],
            ),
          ],
        );
    

    增加level indicator级别显示符和course prices课程价格。

        final levelIndicator = Container(
          child: Container(
            child: LinearProgressIndicator(
                backgroundColor: Color.fromRGBO(209, 224, 224, 0.2),
                value: lesson.indicatorValue,
                valueColor: AlwaysStoppedAnimation(Colors.green)),
          ),
        );
    
        final coursePrice = Container(
          padding: const EdgeInsets.all(7.0),
          alignment: Alignment.center,
          decoration: BoxDecoration(
              border: Border.all(color: Colors.white),
              borderRadius: BorderRadius.circular(5.0)),
          child: Text(
            // "\$20",
            "\$" + lesson.price.toString(),
            style: TextStyle(color: Colors.white),
          ),
        );
    

    底部内容代码

        final bottomContentText = Text(
          lesson.content,
          style: TextStyle(fontSize: 18.0),
        );
        final readButton = Container(
            padding: EdgeInsets.symmetric(vertical: 16.0),
            width: MediaQuery.of(context).size.width,
            child: RaisedButton(
              onPressed: () => {},
              color: Color.fromRGBO(58, 66, 86, 1.0),
              child:
                  Text("学习该课程", style: TextStyle(color: Colors.white)),
            ));
        final bottomContent = Container(
          // height: MediaQuery.of(context).size.height,
          width: MediaQuery.of(context).size.width,
          // color: Theme.of(context).primaryColor,
          padding: EdgeInsets.all(30.0),
          child: Center(
            child: Column(
              children: <Widget>[bottomContentText, readButton],
            ),
          ),
        );
    

    detail_page.dart完整代码

    import 'package:beautiful_list/model/lesson.dart';
    import 'package:flutter/material.dart';
    
    class DetailPage extends StatelessWidget {
      final Lesson lesson;
      DetailPage({Key key, this.lesson}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        final levelIndicator = Container(
          child: Container(
            child: LinearProgressIndicator(
                backgroundColor: Color.fromRGBO(209, 224, 224, 0.2),
                value: lesson.indicatorValue,
                valueColor: AlwaysStoppedAnimation(Colors.green)),
          ),
        );
    
        final coursePrice = Container(
          padding: const EdgeInsets.all(7.0),
          alignment: Alignment.center,
          decoration: BoxDecoration(
              border: Border.all(color: Colors.white),
              borderRadius: BorderRadius.circular(5.0)),
          child: Text(
            // "\$20",
            "\$" + lesson.price.toString(),
            style: TextStyle(color: Colors.white),
          ),
        );
        final topContentText = Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            SizedBox(height: 80.0),
            Icon(
              Icons.directions_car,
              color: Colors.white,
              size: 30.0,
            ),
            Container(
              width: 90.0,
              child: Divider(color: Colors.green),
            ),
            SizedBox(height: 10.0),
            Text(
              lesson.title,
              style: TextStyle(color: Colors.white, fontSize: 35.0),
            ),
            SizedBox(height: 10.0),
            Row(
              mainAxisAlignment: MainAxisAlignment.start,
              children: <Widget>[
                Expanded(flex: 1, child: levelIndicator),
                Expanded(
                    flex: 6,
                    child: Padding(
                        padding: EdgeInsets.only(left: 10.0),
                        child: Text(
                          lesson.level,
                          style: TextStyle(color: Colors.white),
                        ))),
                Expanded(flex: 2, child: coursePrice)
              ],
            ),
          ],
        );
        final topContent = Stack(
          children: <Widget>[
            Container(
                padding: EdgeInsets.only(left: 10.0),
                height: MediaQuery.of(context).size.height * 0.5,
                decoration: BoxDecoration(
                  image: DecorationImage(
                    image: AssetImage("drive-steering-wheel.jpg"),
                    fit: BoxFit.cover,
                  ),
                )),
            Container(
              height: MediaQuery.of(context).size.height * 0.5,
              padding: EdgeInsets.all(40.0),
              width: MediaQuery.of(context).size.width,
              decoration: BoxDecoration(color: Color.fromRGBO(58, 66, 86, .9)),
              child: Center(
                child: topContentText,
              ),
            ),
            Positioned(
              left: 8.0,
              top: 60.0,
              child: InkWell(
                onTap: () {
                  Navigator.pop(context);
                },
                child: Icon(Icons.arrow_back, color: Colors.white),
              ),
            )
          ],
        );
        final bottomContentText = Text(
          lesson.content,
          style: TextStyle(fontSize: 18.0),
        );
        final readButton = Container(
            padding: EdgeInsets.symmetric(vertical: 16.0),
            width: MediaQuery.of(context).size.width,
            child: RaisedButton(
              onPressed: () => {},
              color: Color.fromRGBO(58, 66, 86, 1.0),
              child:
                  Text("学习该课程", style: TextStyle(color: Colors.white)),
            ));
        final bottomContent = Container(
          // height: MediaQuery.of(context).size.height,
          width: MediaQuery.of(context).size.width,
          // color: Theme.of(context).primaryColor,
          padding: EdgeInsets.all(30.0),
          child: Center(
            child: Column(
              children: <Widget>[bottomContentText, readButton],
            ),
          ),
        );
        return Scaffold(
            body: Column(
          children: [topContent, bottomContent],
        ));
      }
    }
    

    运行代码,结果如下:


    image.png

    相关文章

      网友评论

          本文标题:Flutter系列04: 漂亮的列表UI和详情页面 (第二部分)

          本文链接:https://www.haomeiwen.com/subject/owkfsktx.html