First of all, we have a look at the effect:

It looks like a Row, but don’t think you can use Row to solve the problem. You don’t have to count the length of a Row to break a line. So Wrap is recommended. Here’s how to use the Wrap part:

Wrap(
        spacing: 12.0.// Spindle (horizontal) direction spacing
        runSpacing: 0.// Spacing in the vertical direction
        alignment: WrapAlignment.start,
        children: _keywords
            .map<Widget>((e) = > new GestureDetector(
          child: Chip(
            backgroundColor: Color(0xFFF5F5F5),
            label: new Text(
              e,
              style: TextStyle(color: Color(0xFF7C7070), fontSize: 13),),),onTap: () {
            setState(() {
              this._keyword = e;
              textEditingController.text = e;
            });
            goToSearch(this._keyword);
          },
        ))
            .toList(),
      ),
Copy the code

The advantage of using Wrap is that it can automatically Wrap a line after a full line, and the largest item can not exceed one line. The style of item display can be given according to the specific UI or customized Container. The default Chip is oval, but the height is not adjustable. The width will change with the text. Here is how to use it:

Chip(
            backgroundColor: Color(0xFFF5F5F5),
            label: new Text(
              e,
              style: TextStyle(color: Color(0xFF7C7070), fontSize: 13),),),Copy the code

Within the Chip there are these attributes:

Key key,
    this.avatar,
    @required this.label,
    this.labelStyle,
    this.labelPadding,
    this.deleteIcon,
    this.onDeleted,
    this.deleteIconColor,
    this.deleteButtonTooltipMessage,
    this.shape,
    this.clipBehavior = Clip.none,
    this.focusNode,
    this.autofocus = false.this.backgroundColor,
    this.padding,
    this.materialTapTargetSize,
    this.elevation,
    this.shadowColor,
Copy the code

The left and right ICONS can be set. Here is an example:

Chip(
            backgroundColor: Color(0xFFF7F7F7),
            avatar: Icon(
              xxxxx,
              size: 14.color: Color(0xFFC9CCCF),),label: 'Search for goods'.style: TextStyle(fontSize: 12.color: Color(0xFF202020))),padding: EdgeInsets.all(5),
            /// Set the spacing between the icon on the right and the text
            labelPadding: EdgeInsets.only(right: 50),
            deleteIcon: Icon(
              xxxx,
              color: Color(0xFFC7C7C7),
              size: 14,),deleteIconColor: Colors.red,
            onDeleted: () {},),Copy the code

So, is that basically the core? Complete implementation logic code in the following, relatively long, there is a need to refer to:

import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:sp_util/sp_util.dart';

class SearchHistory extends StatefulWidget {
  const SearchHistory({Key key}) : super(key: key);
  @override
  _SearchHistoryState createState() => _SearchHistoryState();
}

class _SearchHistoryState extends State<SearchHistory>with AutomaticKeepAliveClientMixin<SearchHistory>{
  var type;
  String _keyword = ' ';
  bool _hasFocus = true;
  List<String> _keywords = new List();
  final FocusNode _focusNode = FocusNode();
  /// search the input box for hint content
  String get _hint => 'Search for stores or products';

  final TextEditingController textEditingController =
  new TextEditingController();

  @override
  void initState() {
    // TODO: implement initState
    this._keywords.. addAll(_getSearchKeywords()); _focusNode.addListener(() { setState(() { _hasFocus = _focusNode.hasFocus; }); });super.initState();
  }
  void _serachData(String text) async {
    goToSearch(text);
  }

  @override
  void dispose(){ _focusNode? .dispose();super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    var columns = Column(
      children: <Widget>[
        /// Search for the history header title and delete button
        searchHisHeaderView(),
        /// Search history item
        searchHisItemView(),
        /// Free space
        _searchBottomView(),
      ],
    );
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: PreferredSize(
        preferredSize: Size.fromHeight(44),
        child: SafeArea(child: searchBoxView()),
      ),
      body: Container(
          color: Color.fromRGBO(249.249.250.1),
          child: columns),
    );
  }

  /// Build the search top bar
  Widget searchBoxView() {
    return new Column(
      children: <Widget>[
        PreferredSize(
          child: new Container(
            height: 43.5.padding: new EdgeInsets.all(0),
            child: new Row(
              mainAxisAlignment: MainAxisAlignment.spaceBetween,
              children: <Widget>[
                InkWell(
                  child: Container(
                      child: IconButton(
                        icon: Icon(Icons.arrow_back, color:Colors.black),
                        onPressed: () = > Navigator.of(context).pop(),
                      )
                  ),
                  onTap: () { Navigator.pop(context); },),new Expanded(
                  child: ConstrainedBox(
                      constraints: BoxConstraints(
                        maxHeight: 38,),/// Search the keyword input field
                      child: new TextField(
                        autofocus: true.focusNode: _focusNode,
                        textAlignVertical: TextAlignVertical.center,
                        style: new TextStyle(fontSize: 15.0.color: Colors.black),
                        decoration: new InputDecoration(
                          contentPadding: EdgeInsets.fromLTRB(10.0.10.0),
                          filled: true.enabledBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.all(
                              Radius.circular(10), // The edges are 20
                            ),
                            borderSide: BorderSide(
                              color: Color(0xFFE2E4EB),
                              width: 0.5.// The edge width is 1),),fillColor: Color(0xFFFEFEFE),
                          focusedBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.all(
                              Radius.circular(10), // The edges are 20
                            ),
                            borderSide: BorderSide(
                              color: Color(0xFFE2E4EB),
                              width: 0.5.// The edge width is 1),),hintText: _hint,
                          hintStyle: TextStyle(color: Colors.grey),
                          alignLabelWithHint: true.prefixIcon: new Icon(
                            Icons.search,
                            size: 17.color: Color(0xFF8e8e93),),suffixIcon: Offstage(
                              offstage: _keyword.length == 0.child: GestureDetector(
                                  child: Container(
                                    width: 38.height: 38.alignment: Alignment.center,
                                    color: Colors.transparent,
                                    child: new Icon(
                                      Icons.delete,
                                      size: 18.color: Color(0xFF8e8e93),),),onTap: () {
                                    setState(() {
                                      _keyword = ' ';
                                      textEditingController.text = ' '; }); })),),onChanged: (String content) {
                          print(content);
                          _keyword = content;
                          setState(() {
                          });
                        },
                        onSubmitted: (String content) {
                          goToSearch(content ?? "");
                        },
                        onTap: () {
                          setState(() {
                          });
                        },
                        controller: textEditingController,
                      )),
                ),
                InkWell(
                  borderRadius: BorderRadius.all(Radius.circular(4)),
                  autofocus: false.child: Container(
                      padding: EdgeInsets.symmetric(horizontal: 18.vertical: 0),
                      child: Text('search'.style:
                          TextStyle(fontSize: 17.color: Color(0xFF3479FD)))),
                  onTap: () {
                    goToSearch(_keyword ?? ""); }, [, [, [, [, [, [, [, [, [, } Widget searchHisHeaderView() {
    print('Hide the head');
    if ( _keyword.length > 0) {
      return Offstage();
    }
    varlength = _keywords? .length ??0;
    if (length == 0) {
      return Offstage();
    }
    return new Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: <Widget>[
          new Container(
            child: Text(
              'Search History'.style: TextStyle(
                  fontSize: 17.color: Color(0xFF202020),
                  fontWeight: FontWeight.bold),
            ),
            height: 36.margin: EdgeInsets.only(left: 16.top: 10),
          ),
          InkWell(
            child: new Container(
                alignment: Alignment.center,
                margin: EdgeInsets.all(10),
                child: Icon(
                  Icons.delete,
                  size: 13,),width: 30.0.height: 30.0.decoration: BoxDecoration(
                    shape: BoxShape.rectangle,
                    borderRadius: BorderRadius.circular(15.0),
                    color: Color(0xFFF7F7F7))),
            onTap: () {
              refreshHisLists(null); }),]); } Widget searchHisItemView() {
    if ( _keyword.length > 0) {
      return Offstage();
    }
    varlength = _keywords? .length ??0;
    if (length == 0) {
      return Offstage();
    }
    return Container(
      width: double.infinity,
      padding: EdgeInsets.only(left: 16.right: 16),
      child: Wrap(
        spacing: 12.0.// Spindle (horizontal) direction spacing
        runSpacing: 0.// Spacing in the vertical direction
        alignment: WrapAlignment.start,
        children: _keywords
            .map<Widget>((e) = > new GestureDetector(
          child: Chip(
            backgroundColor: Color(0xFFF5F5F5),
            label: new Text(
              e,
              style: TextStyle(color: Color(0xFF7C7070), fontSize: 13),),),onTap: () {
            setState(() {
              this._keyword = e;
              textEditingController.text = e;
            });
            goToSearch(this._keyword);
          },
        ))
            .toList(),
      ),
    );
  }

  /// Build the remaining page space
  Widget _searchBottomView() {
    if (_keyword.length > 0) {
      return Offstage();
    }
    varlength = _keywords? .length ??0;
    if (length == 0) {
      return Offstage();
    }
    return Expanded(
      child: Container(
        margin: EdgeInsets.only(top: 10),
        color: Colors.grey[100],),); }/// Click search
  Future<void> goToSearch(String content) async {
    if (content.length == 0) return; content = content? .trim() ??' ';
    /// Hide the keyboard without losing focus
    SystemChannels.textInput.invokeMethod('TextInput.hide');
    if (content.isNotEmpty) {
      /// data is recorded only when it is null
      var hisList = _keywords ?? [];
      /// Delete existing records
      hisList.remove(content);
      // The new record comes first
      hisList.insert(0, content);
      // keep a maximum of 15 records
      if (hisList.length > 15) {
        hisList.removeLast();
      }

      /// Refresh the recordrefreshHisLists(hisList); }}/// Set the search keyword
  void refreshHisLists(List<String> keywords) {
    String key = 'search_history';
    if (keywords == null) {
      SpUtil.putString(key, null);
    } else {
      SpUtil.putString(key, json.encode(keywords));
    }
    setState(() {
      _keywords = keywords ?? [];
    });
  }

  // query the search keyword
  List<String> _getSearchKeywords() {
    String key = 'search_history';
    String value = SpUtil.getString(key, defValue: null);
    if (value == null) {
      return [];
    }
    return (json.decode(value) as List<dynamic>).cast<String> (); } @override// TODO: implement wantKeepAlive
  bool get wantKeepAlive => true;
}
Copy the code

SpUtil is a library provided by a third party.

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  sp_util: ^ 1.0.1Copy the code

Have you learned the above? Come and try it for yourself.