Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

小册 UI & 功能 编写 #30

Open
Nealyang opened this issue Mar 19, 2019 · 0 comments
Open

小册 UI & 功能 编写 #30

Nealyang opened this issue Mar 19, 2019 · 0 comments
Labels

Comments

@Nealyang
Copy link
Owner

前言

如上,我们完成了首页、沸点的UI及功能的编写。小册这部分我们没有涉及到的知识点或许就是头部加上横切bar的效果。本篇完成,我们将实现如下效果:

img

TabBar & TabBarView

横切bar的效果,我们使用widget:TabBar + TabBarView。由于我们的顶部导航栏是不确定的(从接口获取数据),所以这里我们需要新建有状态类StatefulWidget,并且需要实例化 TabController 来设置顶部导航栏的个数。

  • lib/pages/book_page.dart
  @override
  Widget build(BuildContext context) {
    if (_navData.length == 0) {
        return Center(
          child: CircularProgressIndicator(),
        );
      }
    return new Scaffold(
      appBar: new AppBar(
        backgroundColor: Theme.of(context).primaryColor,
        title: new TabBar(
          controller: _tabController,
          tabs: _myTabs,
          indicatorColor: Colors.white,
          isScrollable: true,
        ),
      ),
      body: new TabBarView(
        controller: _tabController,
        children: _myTabView,
      ),
    );
  }
  • build 方法比较简单,一如既往的 Scaffold作为类似前端的HTML标签,撑开整个页面架构。
  • 但是这里title我们实例化了TabBar,controller是需要制定长度的一个TabController实例,tab为我们TabBar的子组件。indicatorColor 为tabBar下面当前tab的指示器。
  • TabBarView也跟Tab类似,注意,TabBar里面的tabs和TabBarView里面的children是一一对应关系。

数据准备

老规矩,我们继续去封装我们当前页面所需要的数据model以及数据请求的方法,但是这里,我们需要封装两套,因为tab是一套,tabView里面数据中的cell也需要一套。也就是上面我们说的TabBar和TabBarView需要一一对应的关系。

定义数据model

  • lib/model/book_nav.dart
  class BookNav {
    String name;
    String id;
    String alias;
  
    BookNav({this.alias, this.id, this.name});
  
    factory BookNav.fromJson(Map<String, dynamic> json) {
      return BookNav(alias: json['alias'], name: json['name'], id: json['id']);
    }
  }
  • lib/model/book_cell.dart
  class BookCell {
    String id;
    int sectionCount;
    int buyCount;
    String img;
    String title;
    String userName;
    double price;
  
    BookCell(
        {this.id,
        this.buyCount,
        this.img,
        this.price,
        this.title,
        this.sectionCount,
        this.userName});
  
    factory BookCell.fromJson(Map<String, dynamic> json) {
      return BookCell(
          id: json['_id'],
          buyCount: json['buyCount'],
          img: json['img'],
          title:json['title'],
          price: json['price'],
          sectionCount: json['section'].length,
          userName: json['userData']['username']);
    }
  }

这里就是定义我们界面每一个豆腐块需要的数据

请求数据

lib/api/api.dart 中,定义我们准备好的请求Url

  // 小册导航
  static const String BOOK_NAV = 'https://xiaoce-timeline-api-ms.juejin.im/v1/getNavList';
  static const String BOOK_LIST = 'https://xiaoce-timeline-api-ms.juejin.im/v1/getListByLastTime';
  • lib/util/net_util.dart
  // 获取小册导航栏
  static Future<List<BookNav>> getBookNavData() async {
    List<BookNav> resultList = [];
    var response = await NetUtils.get(Api.BOOK_NAV);
    var responseList = response['d'];
    for (int i = 0; i < responseList.length; i++) {
      BookNav bookNav;
      try {
        bookNav = BookNav.fromJson(responseList[i]);
      } catch (e) {
        print("error $e at $i");
        continue;
      }
      resultList.add(bookNav);
    }

    return resultList;
  }

  // 获取小册
  static Future<List<BookCell>> getBookListData(
      Map<String, dynamic> params) async {
    List<BookCell> resultList = new List();
    var response = await NetUtils.get(Api.BOOK_LIST, params: params);
    var responseList = response['d'];
    for (int i = 0; i < responseList.length; i++) {
      BookCell bookCell;
      try {
        bookCell = BookCell.fromJson(responseList[i]);
      } catch (e) {
        print("error $e at $i");
        continue;
      }
      resultList.add(bookCell);
    }

    return resultList;
  }

写到这里,细心地同学是否发现,我们data_utils.dart里面的代码有很多是重复的?是否可以抽象出来一个模子出来。当然,这里希望大家写完后仔细思考,有好的想法,欢迎在群里交流。

渲染页面

当我们拿到数据后,我们需要准备TabBar还需要准备TabBarView。

  List<Tab> _myTabs = <Tab>[
    Tab(
      text: '全部',
    )
  ];
  List<BookPageTabView> _myTabView = <BookPageTabView>[
    BookPageTabView(
      alias: 'all',
    )
  ];
    TabController _tabController;

initState的时候,请求数据,然后设置以上三个变量的值,去重新渲染页面。

  getNavList() {
    DataUtils.getBookNavData().then((resultData) {
      resultData.forEach((BookNav bn) {
        _myTabs.add(Tab(
          text: bn.name,
        ));
        _myTabView.add(BookPageTabView(
          alias: bn.alias,
        ));
      });
      if (this.mounted) {
        setState(() {
          _navData = resultData;
        });
        _tabController =
            new TabController(vsync: this, length: _navData.length + 1);
      }
    });
  }
  • 注意这里我们同样在setState之前判断了下当前页面是否monted,原因和之前说的一样。

TabBarView页面

如上,我们完成book_page的外壳,下面来编写TabBarView的页面内容

在pages目录下新建 book_page_tab_view.dart注意上面代码,我们在实例化tab_view page的时候是传递alias参数的,这也是我们请求当前tab下全部小册的重要参数。

  Widget _itemBuilder(context,index){
    return BookListCell(cellData: _bookList[index],);
  }

  @override
  Widget build(BuildContext context) {
    if (_bookList.length == 0) {
      return Center(
        child: CircularProgressIndicator(),
      );
    }
    return ListView.builder(
      itemBuilder: _itemBuilder,
      itemCount: _bookList.length,
    );
  }

页面请求逻辑大致和上面相同,我们依然将每一本小册的cell分离出来作为一个组件

  • lib/widget/book_list_cell.dart
@override
  Widget build(BuildContext context) {
    final Color accentColor = Theme.of(context).accentColor;
    final Color primaryColor = Colors.blueAccent;

    return InkWell(
      onTap: () {
        String url = "https://juejin.im/book/${cellData.id}";
        Application.router.navigateTo(context,
            "/web?url=${Uri.encodeComponent(url)}&title=${Uri.encodeComponent(cellData.title)}");
      },
      child: Container(
        padding: EdgeInsets.symmetric(
            horizontal: Util.setPercentage(0.03, context), vertical: 15.0),
        decoration: BoxDecoration(
          color: Colors.white,
          border: Border(
            bottom: BorderSide(color: accentColor, width: 0.5),
          ),
        ),
        child: Row(
          mainAxisAlignment: MainAxisAlignment.start,
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisSize: MainAxisSize.min,
          children: <Widget>[
            Container(
              padding: EdgeInsets.only(
                right: Util.setPercentage(0.03, context),
              ),
              child: Image.network(
                cellData.img,
                width: Util.setPercentage(0.2, context),
                height: 100,
                fit: BoxFit.contain,
              ),
            ),
            Container(
              width: Util.setPercentage(0.5, context),
              margin: EdgeInsets.only(
                  right: Util.setPercentage(0.01, context)), //0.8
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                mainAxisSize: MainAxisSize.min,
                children: <Widget>[
                  Text(
                    cellData.title,
                    style: TextStyle(
                      color: Color(0xFF34383B),
                      fontSize: 18.0,
                      fontWeight: FontWeight.bold,
                    ),
                    maxLines: 3,
                    overflow: TextOverflow.ellipsis,
                  ),
                  SizedBox(
                    height: 5.0,
                  ),
                  Text(
                    cellData.userName,
                    style: TextStyle(color: Color(0xFF34383B), fontSize: 16.0),
                  ),
                  SizedBox(
                    height: 5.0,
                  ),
                  Row(
                    children: <Widget>[
                      Text(
                        '${cellData.sectionCount}小节',
                        style: TextStyle(color: accentColor),
                      ),
                      InTextDot(),
                      Text(
                        '${cellData.buyCount}人已购买',
                        style: TextStyle(color: accentColor),
                      )
                    ],
                  )
                ],
              ),
            ),
            Container(
                padding:
                    const EdgeInsets.symmetric(horizontal: 15.0, vertical: 5.0),
                decoration: BoxDecoration(
                    color: Color(0xFFF0F7FF),
                    borderRadius: BorderRadius.all(Radius.circular(15.0))),
                child: Text(
                  '¥${cellData.price}',
                  style: TextStyle(color: primaryColor, fontSize: 16.0),
                ))
          ],
        ),
      ),
    );
  }

代码地址:flutter_juejin

总结

如上,我们完成了带有TabBar页面的UI以及功能的编写,后面活动、开源库的UI编写其实也是如此。使用的Widget基本都是我们常用的Widget,代码的编写风格也区域统一。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

1 participant