I have also written several blog posts about Flutter before. Recently, I have spent some time studying Flutter and completed a highly imitation Dacheang App project (the interface used in the project is captured from online real App, which can achieve the same effect as the online project). I have also accumulated some tips and knowledge points. I would like to record and share it here. I also hope that the Flutter ecosystem will become better and better (Flutter development App is really efficient and the development experience is very good 🙂).

The following blog post will be summarized in four parts:

  • Feature preview of project completion
  • Project structure analysis
  • Detailed overview of project functions (knowledge points used)
  • Small skills accumulation summary

Feature preview of project completion

First of all, let’s take a quick preview of the completed functions and operation effects of the project through a video, as follows

Because the above video is put on CDN, there are many people watching it, and a lot of fees are generated. Therefore, the video may not be able to play!

If the video fails to play, please go here and click to watch (click gear Settings to hide the black edge)

If you are interested in the project and want to know how to achieve it, you can go to my GitHub Clone source code to check.

This video is recorded on a real computer, because the voice search function requires recording, and the simulator cannot record. Of course, iOS and Andorid can run, and the effect is the same, as shown in the figure:

iOS Andorid

Project structure analysis

Second, to comb through the directory structure of the project and understand what each file does, let’s take a look at the first level directory, as follows:

├ ─ ─ the README, md# description file├ ─ ─ android# Android hosting environment├ ─ ─ the buildProject build directory, which is done automatically by Flutter├ ─ ─ flutter_ctrip. On iml ├ ─ ─ fonts# create a directory for storing fonts├ ─ ─ images# create your own directory for storing pictures├ ─ ─ the ios# iOS hosting environment├ ─ ─ lib# Flutter execution files, self-written code are here├ ─ ─ pubspec. LockUsed to record the locked plug-in version├ ─ ─ pubspec yamlPlugins and resource profiles└ ─ ─test       # test directory
Copy the code

There is no need to explain this. Most of flutter is generated and managed. We need to focus on the lib directory.

Let’s look at the secondary directory again, as follows (focus on the lib directory)

Bass Exercises ── Android │ ├─ Bass Exercises for Android. Iml... │ └ ─ ─ Settings. Gradle ├ ─ ─ build │ ├ ─ ─ app... │ └ ─ ─ snapshot_blob. Bin. D.f ingerprint ├ ─ ─ flutter_ctrip. On iml ├ ─ ─ fonts │ ├ ─ ─ PingFang - Italic. The vera.ttf │ ├ ─ ─ PingFang - Regular. The vera.ttf │ └ ─ ─ PingFang_Bold. The vera.ttf ├ ─ ─ images │ ├ ─ ─ the grid - nav - items - dingzhi. PNG... │ └ ─ ─ yuyin. PNG ├ ─ ─ ios │ ├ ─ ─ Flutter... │ └ ─ ─ ServiceDefinitions. Json ├ ─ ─ lib │ ├ ─ ─ the daoRequest interface class│ ├ ─ ─ the main dart# flutter entry file│ ├ ─ ─ modelThe dart class converts the JSON data returned by the server into the DART class│ ├ ─ ─ the navigator# bottom bar Navigation route at the bottom of the home page│ ├ ─ ─ pages# All pages│ ├ ─ ─ the plugin# Encapsulated plug-ins│ ├ ─ ─ util# utility class, avoid duplicate code, packaged into a utility class for each page call│ └ ─ ─ the widgetEncapsulated components├── Heavy Exercises. Heavy Exercisestest└ ─ ─ widget_test. DartCopy the code

Let’s take a look at the secondary directory in the lib directory and see how many files were created and how much code was written.

├ ─ ─ dao / │ ├ ─ ─ destination_dao. Dart * │ ├ ─ ─ destination_search_dao. Dart * │ ├ ─ ─ home_dao. Dart │ ├ ─ ─ search_dao. Dart * │ ├ ─ ─ │ ├─ Trave_search_dao.dart * │ ├─ Trave_search_dao.dart * │ ├─ Trave_search_dao.dart * │ ├─ Trave_search_dao.dart * │ ├─ Trave_search_Dao.dart * │ ├─ trave_search_Dao.dart * │ ├─ trave_search_Dao.dart * │ ├─ trave_search_Dao.dart * │ ├─ Travel_params_dao. Dart * │ └ ─ ─ travel_tab_dao. Dart * ├ ─ ─. Main dart ├ ─ ─ model / │ ├ ─ ─ common_model. Dart │ ├ ─ ─ │ ├─ Destination_model │ ├─ Destination_Model │ ├─ Destination_Model │ ├─ Destination_Model │ ├─ destination_Model │ ├─ │ ├── Bass Exercises for Travel_hot_Keyword_Model Travel_model. Dart * │ ├─ Travel_Search_Hot_Model. Dart * │ ├─ Travel_Search_Model └ ─ ─ travel_tab_model. Dart ├ ─ ─ the navigator / │ └ ─ ─ tab_navigater. Dart ├ ─ ─ pages / │ ├ ─ ─ destination_page. Dart │ ├ ─ ─ Check out the destination_Search_Page.flag school ── Flag school. Check out the destination_Search_Page.flag school ── Flag school Test_page. Dart │ ├ ─ ─ travel_page. Dart │ ├ ─ ─ travel_search_page. Dart │ └ ─ ─ travel_tab_page. Dart * ├ ─ ─ the plugin / │ ├ ─ ─ ├─ ├─ plain exercises, class exercises, class exercises, class exercises, class exercises, class exercises, class exercises, class exercises, class exercises, class exercises └ ─ ─ navigator_util. Dart * └ ─ ─ widget / ├ ─ ─ grid_nav. Dart ├ ─ ─ grid_nav_new. Dart ├ ─ ─ loading_container. Dart ├ ─ ─ Local_nav. Dart ├ ─ ─ sales_box. Dart ├ ─ ─ scalable_box. Dart ├ ─ ─ search_bar. Dart * ├ ─ ─ sub_nav. Dart └ ─ ─ webview. DartCopy the code

The whole project is above these files (specific will not be a one by one analysis, such as, interested, you can clone source code to run, naturally cleared).

Detailed overview of project functions (knowledge points used)

First of all, to take a look at the home page functions and knowledge points, home page focus on the following functions:

  • The fading appBbar
  • Search for encapsulation of components
  • Voice search page
  • Banner component
  • Floating icon navigation
  • Gradient irregular grid navigation with background images

The fading appBbar

First, take a look at the specific effect, as shown in the picture:

appBar

When the appBar background color changes from transparent to white or white to transparent, a NotificationListener component of flutter is used. The NotificationListener component of flutter listens for component tree bubble events when the component (subcomponent) wrapped by flutter changes. The Notification callback is triggered so that it can dynamically change the transparency (alpha) of the appBar by listening to the page scroll as follows:

NotificationListener(
  onNotification: (scrollNotification) {
    if (scrollNotification is ScrollUpdateNotification &&
        scrollNotification.depth == 0) {
      _onScroll(scrollNotification.metrics.pixels);
    }
    return true;
  },
  child: ...
Copy the code

Tips:

A value of 0 for scrollnotification. depth represents the child component (listening only on the child component, not the grandchild component);

ScrollNotification is ScrollUpdateNotification to determine whether a component has been updated. The notifications life cycle is described in the following ways:

  • The ScrollStartNotification component starts to scroll
  • The ScrollUpdateNotification component location has changed
  • The ScrollEndNotification component stops scrolling
  • UserScrollNotification unclear

Here, we do not explore too deeply, if you want to know more view the source code.

_onScroll method code is as follows:

  void _onScroll(offset) {
    double alpha = offset / APPBAR_SCROLL_OFFSET;  // APPBAR_SCROLL_OFFSET constant, value: 100; Offset Indicates the rolling distance

    // Set alpha between 0 and 1
    if (alpha < 0) {
      alpha = 0;
    } else if (alpha > 1) {
      alpha = 1;
    }
    setState(() {
      appBarAlpha = alpha;
    });
    print(alpha);
  }
Copy the code

Search for encapsulation of components

The search component looks like this:

searchBar

Here is the code that calls searchBar on the home page:

SearchBar(
  searchBarType: appBarAlpha > 0.2  //searchBar's classes are dark and light
      ? SearchBarType.homeLight
      : SearchBarType.home,
  inputBoxClick: _jumpToSearch,     // Click the callback function
  defaultText: SEARCH_BAR_DEFAULT_TEXT,   // Prompt text
  leftButtonClick: () {},           // Click the callback function on the left side of the button
  speakClick: _jumpToSpeak,         // Click the microphone callback function
  rightButtonClick: _jumpToUser,    // Click the callback function on the right side
),
Copy the code

TextField is used to listen for changes in the TextField. It is used to listen for user input to request interface data. For implementation details, check the source code: click to view searchBar source code

Voice search page

The effect of the voice search page is shown as follows: The simulator cannot record, so the normal process cannot be displayed. If the recording recognition is successful, the search page will be returned, and the normal process can be seen in the project preview video.

speak

The voice search function uses baidu’s language recognition SDK. After Native access, it communicates with the Native terminal through MethodChannel, which will not be emphasized here (Native knowledge will be involved here).

Focus on the animation implementation when the record button is clicked using the AnimatedWidget as follows:

class AnimatedWear extends AnimatedWidget {
  final bool isStart;
  static final _opacityTween = Tween<double>(begin: 0.5, end: 0); // Set the transparency change value
  static final _sizeTween = Tween<double>(begin: 90, end: 260);   // Set the diffusion value of the circular line

  AnimatedWear({Key key, this.isStart, Animation<double> animation})
      : super(key: key, listenable: animation);

  @override
  Widget build(BuildContext context) {
    final Animation<double> animation = listenable;  // ListEnable inherits the AnimatedWidget, which is essentially the controller, and automatically listens for component changes
    return Container(
      height: 90,
      width: 90,
      child: Stack(
        overflow: Overflow.visible,
        alignment: Alignment.center,
        children: <Widget>[
          ...
          // Make the circle transparent and set the border
          Positioned(
            left: -((_sizeTween.evaluate(animation) - 90) / 2), // set the left offset dynamically according to _sizeTween
            top: -((_sizeTween.evaluate(animation) - 90) / 2), // dynamically set the top offset according to _sizeTween
            child: Opacity(
              opacity: _opacityTween.evaluate(animation),      // Dynamically set the transparency value according to _opacityTween
              child: Container(
                width: isStart ? _sizeTween.evaluate(animation) : 0./ / set the width
                height: _sizeTween.evaluate(animation),              / / set high
                decoration: BoxDecoration(
                    color: Colors.transparent,
                    borderRadius: BorderRadius.circular(
                        _sizeTween.evaluate(animation) / 2),
                    border: Border.all(
                      color: Color(0xa8000000(() (() (() (() [() (() [() (() }}Copy the code

Other details, such as: click prompts recording, recording failure prompts, click the recording button appears translucent black round frame, stop after disappearing, etc., please view the source code.

Banner component

The effect is as follows:

banner

Banner uses flutter flutter_swiper as follows:

Swiper(
  itemCount: bannerList.length,              // The number of images to scroll
  autoplay: true.// Auto play
  pagination: SwiperPagination(              / / indicator
      builder: SquareSwiperPagination(
        size: 6.// The size of the indicator
        activeSize: 6.// The size of the activation status indicator
        color: Colors.white.withAlpha(80),   / / color
        activeColor: Colors.white,           // The color of the active state
      ),
    alignment: Alignment.bottomRight,        // Alignment
    margin: EdgeInsets.fromLTRB(0.0.14.28), / / margin
  ),
  itemBuilder: (BuildContext context, int index) { / / the constructor
    returnGestureDetector( onTap: () { CommonModel model = bannerList[index]; Navigator.push( context, MaterialPageRoute( builder: (context) => WebView( url: model.url, ), ), ); }, child: Image.network( bannerList[index].icon, fit: BoxFit.fill, ), ); },),Copy the code

For details about how to use Flutter, go to pub.dev, the official plugin library for Flutter: Click flutter_swiper.

Tips:

Note that I’ve tweaked the indicator styles a bit. Flutter_swiper only provides three indicator styles, as follows:

  • Dots = const DotSwiperPaginationBuilder (), round
  • Fraction = const FractionPaginationBuilder (), the percentage of types, such as: 1/6, said 6 pages of the first page
  • The rect = const RectSwiperPaginationBuilder (), the rectangle

There is no active state of the long ellipse, in fact, according to the gourd gourd gourd gourd gourd, their own implementation of a long ellipse type, if you know more details, you can click to view the source of the long ellipse indicator

Floating icon navigation

Icon navigation effect as shown below:

The Icon navigation floats above the banner and actually uses the Stack component of the flutter. The Stack component allows its children to be stacked. It is usually used with the Positioned component.

ListView(
  children: <Widget>[
    Container(
      child: Stack(
        children: <Widget>[
          Container( ... ), // Put the banner code herePositioned( ... ) .// This is the icon navigation, which shows the position by positioning], ), ), Container( ... ) .// Put grid navigation and other things here],),Copy the code

Gradient irregular grid navigation with background images

The grid navigation effect is shown as follows:

As shown in the figure, the grid navigation is divided into three rows and four columns, while the first row is divided into three columns. The width of the first column of each row is larger than the other three columns, and the other three columns are equal. Each row has gradient color, and the first and second columns have background pictures. The Column component of a flutter can arrange its children vertically and the Row component can arrange its children horizontally. The layout code is as follows:

Column(                      // Put the Column component on the outside
  children: <Widget>[
    Container(               // The first line wraps the Container and sets its gradient
      height: 72,
      decoration: BoxDecoration(
        gradient: LinearGradient(colors: [  // Set gradient
          Color(0xfffa5956),
          Color(0xffef9c76).withAlpha(45) ]), ), child: Row( ... ) ./ / the first line
    ),
    Padding(
      padding: EdgeInsets.only(top: 1),  // Set the interval between lines
    ),
    Container(
      height: 72,
      decoration: BoxDecoration(
        gradient: LinearGradient(colors: [  // Set gradient
          Color(0xff4b8fed),
          Color(0xff53bced), ]), ), child: Row( ... ) ./ / the second line
    ),
    Padding(
      padding: EdgeInsets.only(top: 1),   // Set the interval between lines
    ),
    Container(
      height: 72,
      decoration: BoxDecoration(
        gradient: LinearGradient(colors: [  // Set gradient
          Color(0xff34c2aa),
          Color(0xff6cd557), ]), ), child: Row( ... ) ./ / the third row),,),Copy the code

In fact, the specific implementation details are many, such as:

  • How to set the width of the first column is large, other equal;
  • The first row and the last column are twice as wide;
  • The background picture and floating red bubble tip in the first and second columns;

Here I will not elaborate, otherwise the length is too long, if you want to know more details click to view the source code

Secondly, take a look at the destination page functions and knowledge points used, focusing on the following functions:

  • Left and right layout tabBarListView
  • Destination search page

Left and right layout tabBarListView

The specific effect is shown in the picture: Click the left TAB to switch to the page, slide left or right to switch to the page, click to expand to display more, etc

destination

In fact, tabBar and TabBarView components have been officially provided to achieve the effect of top and bottom layout (travel photo page is implemented with this), but it cannot achieve left and right layout, and is not flexible, so I use vertical_Tabs plug-in, the code is as follows:

VerticalTabView(
    tabsWidth: 88,
    tabsElevation: 0,
    indicatorWidth: 0,
    selectedTabBackgroundColor: Colors.white,
    backgroundColor: Colors.white,
    tabTextStyle: TextStyle(
      height: 60,
      color: Color(0xff333333),
    ),
    tabs: tabs,
    contents: tabPages,
  ),
),
Copy the code

For details, please click Vertical_Tabs

Here are some things to note: On display more spans the implementation of the component, because, the component used in many other components and dynamic rendering, according to data interface and the own existence state of change, in this case, it is best to him individually encapsulated into a component (widget), otherwise, it is difficult to control the change of the state of their own, click no effect, Or click to affect other components.

Destination search page

The result is shown in the picture: Click on the search result, for example, click on ‘one-day tour’, and the relevant data of ‘one-day tour’ will be searched

destination

The destination search page, which is mostly code for layout and docking interfaces, won’t be covered here.

Then it is the page functions and knowledge points used, focusing on the realization of the following functions:

  • Left and right layout tabBarListView
  • Waterfall card
  • Travel photography search page

Left and right layout tabBarListView

Effect as shown in the picture: slide left or right to switch pages, pull up to load more, pull down to refresh, etc

travel

TabBar and TabBarView are the components that flutter provides:

Container(
  color: Colors.white,
  padding: EdgeInsets.only(left: 2),
  child: TabBar(
    controller: _controller,
    isScrollable: true,
    labelColor: Colors.black,
    labelPadding: EdgeInsets.fromLTRB(8.6.8.0),
    indicatorColor: Color(0xff2FCFBB),
    indicatorPadding: EdgeInsets.all(6),
    indicatorSize: TabBarIndicatorSize.label,
    indicatorWeight: 2.2,
    labelStyle: TextStyle(fontSize: 18),
    unselectedLabelStyle: TextStyle(fontSize: 15),
    tabs: tabs.map<Tab>((Groups tab) {
      return Tab(
        text: tab.name,
      );
    }).toList(),
  ),
),
Flexible(
    child: Container(
  padding: EdgeInsets.fromLTRB(6.3.6.0),
  child: TabBarView(
      controller: _controller,
      children: tabs.map((Groups tab) {
        returnTravelTabPage( travelUrl: travelParamsModel? .url, params: travelParamsModel? .params, groupChannelCode: tab? .code, ); }).toList()), )),Copy the code

Waterfall card

The waterfall stream card uses the flutter_staggered_grid_view plugin as follows:

StaggeredGridView.countBuilder(
  controller: _scrollController,
  crossAxisCount: 4, itemCount: travelItems? .length ??0,
  itemBuilder: (BuildContext context, int index) => _TravelItem(
        index: index,
        item: travelItems[index],
      ),
  staggeredTileBuilder: (int index) => new StaggeredTile.fit(2),
  mainAxisSpacing: 2.0,
  crossAxisSpacing: 2.0,),Copy the code

For more information, click on Flutter_staggered_grid_view.

Travel photography search page

The effect is shown in the picture: First, the label of popular travel photos will be displayed. Click to search for relevant content, and enter keywords to search for relevant travel photos information, location, scenic spots, users, etc

travel-search

Travel photography search page, and most of the layout and docking interface code, here is no longer described.

Small skills accumulation summary

The following are the knowledge points I used in the project, here to record and share, I hope to help you.

PhysicalModel

PhysicalModel can trim a Container with a background image. For example, if you want to set a rounded corner for an image in a Container, setting the decoration of the Container’s borderRadius does not work. This is where PhysicalModel comes in:

PhysicalModel(
  borderRadius: BorderRadius.circular(6),  // Set rounded corners
  clipBehavior: Clip.antiAlias,            // Clipping behavior
  color: Colors.transparent,               / / color
  elevation: 5.// Set the shadow
  child: Container(
        child: Image.network(
          picUrl,
          fit: BoxFit.cover,
        ),
      ),
),
Copy the code

LinearGradient

Add gradient to the container, used in grid navigation, appBar, etc., with the following code:

Container(
  height: 72,
  decoration: BoxDecoration(
    gradient: LinearGradient(colors: [
      Color(0xff4b8fed),
      Color(0xff53bced), ]), ), child: ... ) .Copy the code

Color(int.parse(‘0xff’ + gridNavItem.startColor))

The Color value is converted to Color, or if there is no variable, Color(0xff53bced),

  • Ox: The flutter requirements can be fixed
  • Ff: stands for opacity. If you don’t know how to set it, you can use the color remover, or withwithalpha (a)
  • 53bCED: Regular 6-bit RGB value

Expanded, FractionallySizedBox

Expanded allows child components to fill the parent container and is often used with Row and Column components. The FractionallySizedBox allows a child component to fill up or outgrow its parent container. It can be used alone, and its size is affected by the widthFactor and heightFactor width factors

MediaQuery.removePadding

Mediaquery. removePadding can be used to remove the margin of a component. Some components have built-in margins.

MediaQuery.removePadding(
  removeTop: true,
  context: context,
  child: ...
)
Copy the code

MediaQuery.of(context).size.width

Mediaquery.of (context).size. Width gets the width of the screen, and mediaQuery.of (context).size. Height gets the height of the screen; For example, if you think of a line with an average of 3 equal parts: 0.3 * mediaQuery.of (context).sie.width, use it in the tag component of the destination page, as follows:

Container(
  alignment: Alignment.center,
  ...
  width: 0.3*MediaQuery.of(context).size.width - 12.// The screen is divided in thirds, with -12 to give space in the middle
  height: 40. child: ... ) .Copy the code

Theme.of(context).platform == TargetPlatform.iOS

Depending on the operating system type, sometimes you might want to use it to make a different layout for Android and iOS.

with AutomaticKeepAliveClientMixin

Flutter in the switch when the page reloads the data every time, and if you want to keep the page state, not reload, will need to use the AutomaticKeepAliveClientMixin, code is as follows: (It is used in the photography page, so that tabBar and tabBarView do not reload when switching)

class TravelTabPage extends StatefulWidget {...// Need to override wantKeepAlive and set it to true
  @override
  bool get wantKeepAlive => true;
}
Copy the code

with SingleTickerProviderStateMixin

When mixed with SingleTickerProviderStateMixin can switch on the page, use the animation effects, such as:

bottomNavigationBar: BottomNavigationBar(
          currentIndex: _currentIndex,
          onTap: (index) {
            _controller.animateToPage( // _controller is the PageController of PageView
                index,
                curve: Curves.easeIn, duration: Duration(milliseconds: 260)); setState(() { _currentIndex = index; }); },...).Copy the code

SystemUiOverlayStyle

SystemUiOverlayStyle enables status bar immersion, setting global configuration as follows:

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    TextStyle textStyle = TextStyle(fontSize: 20);
    SystemUiOverlayStyle style = SystemUiOverlayStyle(
        statusBarColor: Colors.transparent,
        statusBarIconBrightness: Brightness.light
    );
    SystemChrome.setSystemUIOverlayStyle(style);

    returnMaterialApp( ... }}Copy the code

Single page Settings are as follows:

class _HomePageState extends State<HomePage> {...@override
  Widget build(BuildContext context) {
    SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light); // Set the status bar to be immersive
    return Scaffold(
      backgroundColor: Color(0xfffafafc), body: LoadingContainer( ... ) .Copy the code

Temporarily can think of these commonly used knowledge points, if there is a new will slowly supplement.

Blog address: Lishaoy.net

Blog Notes address: h.lishaoy.net

GitHub address: github.com/persilee/fl…