In the last post, I documented the overall layout framework of the Open source Chinese client based on Flutter. This post is about the static implementation of each page, with specific data loading and storage. I will record it in the next post, hoping that I can give some help to beginners while reviewing the old and new.

The index The article
1 Write an open Source Chinese Client based on Flutter from 0 (1)

Flutter build | nuggets technical essay introduction and development environment
2 Write an open Source Chinese Client based on Flutter from 0 (2)

Dart Grammar Basics
3 Write an open Source Chinese Client based on Flutter from 0 (3)

Introduction to Flutter & Common Widgets
4 Write an open Source Chinese Client based on Flutter from 0 (4)

Foundation of Flutter layout
5 Write an open Source Chinese Client based on Flutter from 0 (5)

Set up the overall layout framework of the App
👉 6 Write an open Source Chinese Client based on Flutter from 0 (6)

Implementation of various static pages
7 Write an open Source Chinese Client based on Flutter from 0 (7)

App network requests and data stores
8 Write an open Source Chinese Client based on Flutter from 0 (8)

Use of plug-ins

The ListView component is the most commonly used component in the Open source Chinese client based on The Flutter. Approximately 80% of the pages need to be presented as lists. The following explains how each page is implemented.

The implementation of the slide menu page

In the previous article, we simply placed a Center component in the slide menu and displayed a line of text. In this article, we need to achieve the slide menu effect as follows:

The head of the slide menu is a cover image, and below it is a list of menus. We can treat the cover image and each menu as items of the ListView, so there is a multi-layout of ListView sub-items involved.

In the code in the previous article, we added a drawer parameter to the MaterialApp directly and added a new drawer object. To organize the code properly, we created a “widgets/” directory in the lib/ directory to store our custom components. Dart: MyDrawer. Dart: MyDrawer. Dart: MyDrawer.

Class MyDrawer extends StatelessWidget {static const double IMAGE_ICON_WIDTH = 30.0; Static const double ARROW_ICON_WIDTH = 16.0; Var rightArrowIcon = new image.asset ()'images/ic_arrow_right.png', width: ARROW_ICON_WIDTH, height: ARROW_ICON_WIDTH, ); // Menu text List menuTitles = ['Release move'.'The black room'.'about'.'set']; // List menuIcons = ['./images/leftmenu/ic_fabu.png'.'./images/leftmenu/ic_xiaoheiwu.png'.'./images/leftmenu/ic_about.png'.'./images/leftmenu/ic_settings.png']; TextStyle menuStyle = new TextStyle(fontSize: 15.0,); // Omit the following code //... }Copy the code

In the build method of the MyDrawer class, return a ListView component:

  @override
  Widget build(BuildContext context) {
    returnConstrainedBox(constraints: const BoxConstraints. Expand (width: 304.0), child: new Material(elevation: 16.0, child: new Container(decoration: new BoxDecoration(color: const color (0xFFFFFFFF)),), child: new ListView.builder( itemCount: menuTitles.length * 2 + 1, itemBuilder: renderRow, ), ), ), ); }Copy the code

ConstraintedBox and Material components in the build method are both direct references to the source code of the Drawer class. The constraints parameter specifies the width of the slide menu, and the elevation parameter controls the size of the shadow behind the Drawer. The default value is 16 (so you don’t need to specify an elevation argument here), the main thing is the ListView’s named constructor build, and the itemCount parameter that represents the number of items, menutitle. length * 2 + 1, Where *2 counts the dividing line into the item, +1 counts the top cover image into the item. Here are the key renderRow methods:

  Widget renderRow(BuildContext context, int index) {
    if (index == 0) {
      // render cover image
      var img = new Image.asset(
        'images/cover_img.jpg', width: 304.0, height: 304.0,);returnNew Container(width: 304.0, height: 304.0, margin: const EdgeInsets. FromLTRB (0.0, 0.0, 0.0, 10.0), child: img,); } index -= 1; // Render the split line if it is oddif (index.isOdd) {
      returnnew Divider(); } // item index = index ~/ 2; Var listItemContent = new Padding(// set the Padding of the item: Const EdgeInsets. FromLTRB (10.0, 15.0, 10.0, 15.0), const EdgeInsets. FromLTRB (10.0, 15.0, 10.0, 15.0), const EdgeInsets. FromLTRB (10.0, 15.0, 10.0, 15.0), <Widget>[// getIconImage(menuIcons[index]), // new Expanded(child: new Text( menuTitles[index], style: menuStyle, ) ), rightArrowIcon ], ), );return new InkWell(
      child: listItemContent,
      onTap: () {
        print("click list item $index"); }); }Copy the code

The renderRow method body is longer, mainly because it involves rendering three different layouts: header cover, divider, and menu item. There are comments in the above code, but there are a few things to note:

  1. The Expanded component was used to render the menu item text, which is similar to adding the Android :layout_weight=”1″ attribute when laying out in Android. The Text component wrapped with Expanded takes up all of the remaining space horizontally except for icon and arrow ICONS;
  2. Finally, an InkWell component is returned for adding click events to menu items, but there is no water ripple effect when clicking on the menu in the Drawer (for unknown reasons).

Information list page implementation

The information list page to be implemented in this paper is as follows:

At the top of the list is a rotograph, which can be swiped left and right to switch between different information. Below is a list showing information such as the title of the information, the date of publication, the number of comments, the infographic and so on.

Realization of rotund diagram

The rotation diagram uses the Built-in TabBarView component of Flutter, which is similar to the ViewPager in Android and can be swiped left and right to switch pages. To organize the code properly, let’s pull out the rotograph as a custom component and create a new slideView. dart file in the Widgets/directory and add the following code:

import 'package:flutter/material.dart'; class SlideView extends StatefulWidget { var data; SlideView(data) {this.data = data; } @override State<StatefulWidget>createState() {// You can pass arguments to SlideViewState in the constructor // or you can access the data variable in SlideView directly from SlideViewState via this.widget.data without passing argumentsreturnnew SlideViewState(data); } } class SlideViewState extends State<SlideView> with SingleTickerProviderStateMixin { // TabController is the controller of the TabBarView component TabController TabController; List slideData; SlideViewState(data) { slideData = data; } @override voidinitState() { super.initState(); TabController = new tabController (length: slideData == null? 0 : slideData.length, vsync: this); } @override voiddispose() {// Dispose tabcontroller.dispose (); super.dispose(); } @override Widget build(BuildContext context) { List<Widget> items = [];if(slideData ! = null && slideData.length > 0) {for(var i = 0; i < slideData.length; i++) { var item = slideData[i]; Var imgUrl = item[/ URL]'imgUrl']; Var title = item[/ url]'title']; Var detailUrl = item[/ URL]'detailUrl']; Items.add (new GestureDetector(onTap: () {// click to go to details}, child: new Stack(// Stack is used to place the header text on the image) children: < span style = "box-sizing: border-box; color: RGB (0, 0, 0); line-height: 21px; font-size: 14px! Important; word-break: break-word! Important;" Mediaquery.of (context).size.width, // set the background to black, add transparency color: const color (0x50000000), // set the title text to the inner margin child: Child: New Text(title, style: new TextStyle(color: Colors. White, fontSize: 15.0),))],)); }}returnnew TabBarView( controller: tabController, children: items, ); }}Copy the code

The main parameters of the TabBarView component are controller and children. The controller represents the controller of the TabBarView and the children represent the pages in the component. The data in SliderView is passed in by the constructor when the object is new. The data is a map array containing the imgUrl Title detailUrl3 fields.

Note: there is no dot page indicator in the round diagram of this project. You can add relevant code by yourself.

A combination of a rotograph and a list

With the custom rotograph component implemented above, you now need to combine this component with the list.

Since the layout of the items in the information list is a bit complicated, it is necessary to split the item into two parts. The left side shows the information title, time, number of comments, and so on, and the right side shows the picture of the information. The first Column of the Column is the header, and the second Column is a Row component with the time, author’s head, number of comments, and so on. Dart: NewsListPage. Dart

import 'package:flutter/material.dart';
import 'package:flutter_osc/widgets/SlideView.dart'; Class NewsListPage extends StatelessWidget {var slideData = []; Var listData = []; var listData = []; TextStyle titleTextStyle = new TextStyle(fontSize: 15.0); SubtitleStyle = new TextStyle(color: const color (0xFFB5BDC0), fontSize: 12.0);NewsListPage() {// Do data initialization here, add some test datafor(int i = 0; i < 3; i++) { Map map = new Map(); // The information title map of the rotation diagram ['title'] = 'Father of Python reveals his resignation, alienating core development team'; // URL map for the details of the rotation diagram ['detailUrl'] = 'https://www.oschina.net/news/98455/guido-van-rossum-resigns'; // The URL map of the round diagram ['imgUrl'] = 'https://static.oschina.net/uploads/img/201807/30113144_1SRR.png';
      slideData.add(map);
    }
    for(int i = 0; i < 30; i++) { Map map = new Map(); // List item title map['title'] = 'J2Cache 2.3.23 released with memcached level-2 cache support '; // The URL map of the author of the list item ['authorImg'] = 'https://static.oschina.net/uploads/user/0/12_50.jpg?t=1421200584000'; // List item time text map['timeStr'] = '2018/7/30'; // Select * from item where map['thumb'] = 'https://static.oschina.net/uploads/logo/j2cache_N3NcX.png'; // List item number of comments map['commCount'] = 5;
      listData.add(map);
    }
  }

  @override
  Widget build(BuildContext context) {
    returnNew ListView.builder(// where itemCount is the itemCount of the ListView: listData.length * 2 + 1, itemBuilder: (context, i) => renderRow(i) ); } // renderRow(I) {// render the rotograph when I is 0if (i == 0) {
      returnNew Container(height: 180.0, child: new SlideView(slideData),); } // if I > 0, I -= 1; // if I is odd, render dividersif (i.isOdd) {
      returnThe new Divider (height: 1.0); } // Round I = I ~/ 2; Var itemData = listData[I]; Var titleRow = new Row(children: <Widget>) [// New Expanded(child: <Widget>) new Text(itemData['title'], style: titleTextStyle), ) ], ); Var timeRow = new Row(children: <Widget>); var timeRow = new Row(children: <Widget>); 20.0, height: 20.0, color: new BoxDecoration(shape: BoxShape. Circle, color: const Color(0xFFECECEC), image: new DecorationImage( image: new NetworkImage(itemData['authorImg']), fit: BoxFit.cover), border: new Border.all( color: const Color(0xFFECECEC), width: New Padding(Padding: const EdgeInsets. FromLTRB (0.0, 0.0, 0.0, 0.0), child: New Text(itemData['timeStr'], style: subtitleStyle,),), // This is the number of comments. The number of comments consists of a comment icon and the specific number of comments, so it is a Row component new Expanded(Flex: 1, child: New Row(// In order for the number of comments to be displayed on the far right, so you need the Expanded outside and MainAxisAlignment. End MainAxisAlignment: MainAxisAlignment. End, children: <Widget>[ new Text("${itemData['commCount']}", style: subtitleStyle),
              new Image.asset('./images/ic_comment.png'Width: 16.0, height: 16.0),],)],); var thumbImgUrl = itemData['thumb']; Var thumbImg = new Container(margin: const EdgeInsets. All (10.0), width: 60.0, height: 60.0); 60.0, color: decoration (shape: BoxShape. Circle, color: const color (0xFFECECEC), image: new DecorationImage( image: new ExactAssetImage('./images/ic_img_default.jpg'), fit: boxfit.cover), border: new border. All (color: const color (0xFFECECEC), width: 2.0,),); // If the above thumbImgUrl is not empty, replace the previous thumbImg default image with a web imageif(thumbImgUrl ! = null && thumbimgurl.length > 0) {thumbImg = new Container(margin: const edgeinset.all (10.0), width: 60.0, height: 0) 60.0, color: decoration (shape: BoxShape. Circle, color: const color (0xFFECECEC), image: new DecorationImage( image: new NetworkImage(thumbImgUrl), fit: BoxFit.cover), border: new Border.all( color: Const Color(0xFFECECEC, width: 2.0,),); } var row = new row (children: <Widget>) [// new Expanded(flex: 1, child: New Padding(Padding: const edgeinset.all (10.0), child: new Column(children: <Widget>[titleRow, new Padding(Padding: const edgeinset.fromltrb (0.0, 8.0, 0.0, 0.0), child: New Padding(Padding: const edgeinset.all (6.0), child: new Container(width: 100.0, height: 80.0, color: const color (0xFFECECEC), child: new Center(child: thumbImg,),)],); // Wrap the row in InkWell to make it clickablereturnnew InkWell( child: row, onTap: () { }, ); }}Copy the code

Tweak the implementation of the list page

Here’s what you want to do with the list:

The DefaultTabController component provided by Flutter is used here. This component is also quite simple to use. Here is the code for TweetsList.

@override Widget build(BuildContext context) {screenWidth = mediaquery.of (context).size.width;return new DefaultTabController(
      length: 2,
      child: new Scaffold(
        appBar: new TabBar(
          tabs: <Widget>[
            new Tab(text: "Move the list"),
            new Tab(text: "Hot move.") ], ), body: new TabBarView( children: <Widget>[getNormalListView(), getHotListView()], )), ); } // Get the common flex list WidgetgetNormalListView() {
    returnnew ListView.builder( itemCount: normalTweetsList.length * 2 - 1, itemBuilder: (context, i) => renderNormalRow(i) ); } // Get the hot Flex list WidgetgetHotListView() {
    returnnew ListView.builder( itemCount: hotTweetsList.length * 2 - 1, itemBuilder: (context, i) => renderHotRow(i), ); } // Render the common flex list Item renderHotRow(I) {if (i.isOdd) {
      returnNew Divider(height: 1.0,); }else {
      i = i ~/ 2;
      returngetRowWidget(hotTweetsList[i]); }} // Render Item renderNormalRow(I) {if (i.isOdd) {
      returnNew Divider(height: 1.0,); }else {
      i = i ~/ 2;
      returngetRowWidget(normalTweetsList[i]); }}Copy the code

In the TabBarView, the children argument is an array representing the different pages. Here we use two methods to return a normal list of inflates and a hot list of inflates. Before we encode the inflates, we define the following variables for later use and initialize them in the constructor of the TweetsList class:

import 'package:flutter/material.dart'; Class TweetsListPage extends StatelessWidget {class TweetsListPage extends StatelessWidget {List hotTweetsList = []; NormalTweetsList = []; // create TextStyle; TextStyle subtitleStyle; // Double screenWidth; // Initialize the data in the constructorTweetsListPage() {authorTextStyle = new TextStyle(fontSize: 15.0, fontWeight: fontweight.bold); SubtitleStyle = new TextStyle(fontSize: 12.0, color: const color (0xFFB5BDC0)); subtitleStyle = new TextStyle(fontSize: 12.0, color: const color (0xFFB5BDC0)); // Add test datafor(int i = 0; i < 20; i++) { Map<String, dynamic> map = new Map(); // Move the release time map['pubDate'] = '2018-7-30'; // Move text content map['body'] = 'Get up at 7:10 am, go out at 40, spend more than 20 minutes to get to the office, have to clock in before 8:30; From 1pm to 6pm, then two hours overtime; I left the office around eight o 'clock and went to the gym to exercise for more than an hour. I got home after ten o 'clock. I prepared lunch for the next day. I cleaned the kitchen. I feel very tired. '; // Move the author name map['author'] = 'sweet'; // The number of active comments map['commentCount'] = 10; // URL map['portrait'] = 'https://static.oschina.net/uploads/user/0/12_50.jpg?t=1421200584000'; // Use commas (,) to separate multiple images from each other ['imgSmall'] = 'https://b-ssl.duitang.com/uploads/item/201508/27/20150827135810_hGjQ8.thumb.700_0.jpeg,https://b-ssl.duitang.com/upload s/item/201508/27/20150827135810_hGjQ8.thumb.700_0.jpeg,https://b-ssl.duitang.com/uploads/item/201508/27/20150827135810_h GjQ8.thumb.700_0.jpeg,https://b-ssl.duitang.com/uploads/item/201508/27/20150827135810_hGjQ8.thumb.700_0.jpeg'; hotTweetsList.add(map); normalTweetsList.add(map); }}}Copy the code

With the test data, the main thing is to implement the list display, and the most troublesome part of the list display is to render the list item. Each item should display the user’s profile picture, the user’s nickname, the time of Posting, the number of comments that have been stirred, and if there is an image that has been stirred, the image should be displayed in a nine-grid mode. Simple analysis to move under the list of the item, it should be in the Column component, Column components released the first line of the display the avatars, nickname, moving time, the second line should be displayed the content of the move, the third line is can show doesn’t show scratchable latex, if you have any pictures, move indicates that there are not, in the fourth row a move comments, Displayed in the lower right corner. Render the list item in small steps:

The first line displays the user’s profile picture, nickname, and date of publication

This line can be displayed as a Row component, as follows:

Var authorRow = new Row(children: <Widget>) [// authorRow = new Row(width: 35.0, height: 35.0)] Height: 0.0000pt; height: 0.0000pt; height: 0.0000pt; height: 0.0000pt; height: 0.0000pt; height: 0.0000pt; height: 0.0000pt; height: 0.0000pt; new DecorationImage( image: new NetworkImage(listItem['portrait']), fit: boxfit.cover), // profile border border: new border. All (color: color.white, width: New Padding(Padding: const EdgeInsets. FromLTRB (6.0, 0.0, 0.0, 0.0), child: new Text(listItem['author'], style: new TextStyle(fontSize: 16.0)), // move the number of comments shown in the rightmost new Expanded(Child: new Row(mainAxisAlignment: MainAxisAlignment.end, children: <Widget>[ new Text('${listItem['commentCount']} ',
            style: subtitleStyle,
          ),
          new Image.asset(
            './images/ic_comment.png', width: 16.0, height: 16.0,)],);Copy the code

The second line shows the move content

This line is just a piece of text, so the code is simple:

Var _body = listItem['body'];
var contentRow = new Row(
  children: <Widget>[
    new Expanded(child: new Text(_body))
  ],
);
Copy the code

The third line, shows the picture in motion, without the picture does not show this line

It’s a little more complicated to display the image in a grid, which is why we used the build method to get the width of the screen to calculate the width of the image in the grid. In addition, the image URL in the nine grid is given in the form of a string, separated by English commas, so we need to do segmentation of the image URL. If there are pictures in motion, there may be 1 to 9. Here is a method to determine the total number of lines when using the grid:

Int getRow(int n) {int a = n % 3; int getRow(int n) {int a = n % 3; Int b = n ~/ 3; / / integerif(a ! = 0) {return b + 1;
    }
    return b;
  }
Copy the code

For example, if there are 9 pictures, 9%3 = 0, then there are 9 ~/3 = 3 rows. If there are 5 pictures, 5%3! = 0, then the number of rows is 5 ~/ 3 and then +1 is two rows.

Here is the code to generate the nine grid image:

ImgSmall = listItem[imgSmall = listItem[imgSmall = listItem['imgSmall'];
    if(imgSmall ! = null && imgSmall. Length > 0) {List<String> List = imgSmall. Split (","); List<String> imgUrlList = new List<String>(); // The image given by openAPI in Open Source China may be relative address, so use the following code to complete the relative addressfor (String s in list) {
        if (s.startsWith("http")) {
          imgUrlList.add(s);
        } else {
          imgUrlList.add("https://static.oschina.net/uploads/space/"+ s); } } List<Widget> imgList = []; List<List<Widget>> rows = []; num len = imgUrlList.length; // Through doubleforLoop to generate each image componentfor(var row = 0; row < getRow(len); List<Widget> rowArr = []; List<Widget> rowArr = [];for(var col = 0; col < 3; Col ++) {num index = row * 3 + col; double cellWidth = (screenWidth - 100) / 3;if (index < len) {
            rowArr.add(new Padding(
              padding: const EdgeInsets.all(2.0),
              child: new Image.network(imgUrlList[index],
                  width: cellWidth, height: cellWidth),
            ));
          }
        }
        rows.add(rowArr);
      }
      for (var row inrows) { imgList.add(new Row( children: row, )); } columns. Add (new padding-in (padding-in: const edgeinset.fromltrb (52.0, 5.0, 10.0, 0.0), child: new Column(children: imgList, ), )); }Copy the code

There is a column variable at the end of the above code, which represents a column layout of the entire item. Before generating the nine-column layout, the first and second rows have been added to the columns:

New Padding(Padding: const EdgeInsets. FromLTRB (10.0, 10.0, 10.0, 2.0)), child: New Padding(Padding: const edgeInset.fromltrb (52.0, 0.0, 10.0, 0.0), child: contentRow, ), ];Copy the code

If there is a picture in motion, then the columns should also add nine lattice picture component.

The fourth line shows the release time of the move. This line layout is simpler:

var timeRow = new Row(
  mainAxisAlignment: MainAxisAlignment.end,
  children: <Widget>[
    new Text(
      listItem['pubDate'], style: subtitleStyle, ) ], ); Add (new Padding(Padding: const edgeinset.fromltrb (0.0, 10.0, 10.0, 6.0), child: timeRow,));Copy the code

Return columns wrapped in an InkWell component:

returnNew InkWell(child: new Column(children: columns,), onTap: () {Copy the code

Implementation of the Discover page

The renderings of the discovery page to be realized in this paper are as follows:

This page is a simple ListView, but slightly different is, in the ListView line some long, some short, some line and space between space, in order to achieve this layout, I use a kind of method is the length of the different line, or the space between the two line, with different string to tag, When rendering the list, rendering the different components according to the different strings, the code is easy to understand, so I put the link to the source code directly here: the source code, which is already commented in detail.

Implementation of the “my” page

The renderings of my page are as follows:

This page is also relatively simple, the head of the green area is also part of the ListView, is the ListView layout, the specific implementation of the way not to detail, directly put the code: source code.

The source code

All source code related to this article is available on GitHub as the V0.2 branch of the Demo-Flutter – OSC project.

Afterword.

This article mainly describes the implementation of various static pages on the Open source Chinese client based on Flutter, limited to the UI, specific network requests, data storage and other logic to be documented in the next article.

My open source project

  1. Google Flutter based on the open source Chinese client, hope you give a Star support, source code:
  • GitHub
  • Yards cloud

  1. Tetris based on the Flutter small game, I hope you give a Star support, source:
  • GitHub
  • Yards cloud

In the previous The next article
Write an open Source Chinese Client based on Flutter from 0 (5)

— Building the overall layout framework of the App
Write an open source Chinese client based on Flutter from 0 (7) –

App network requests and data stores