美文网首页
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