Flutter开发 -写一个历史记录的页面

616 阅读4分钟

首先,大家先看一下效果图:
在这里插入图片描述

看起来就是一行行的显示,但是小伙伴千万不要觉得使用Row就能解决,还涉及到换行呢,你总不至于要一个个算长度来换行吧?所以这里推荐使用Wrap,来看下Wrap部分怎么使用:

Wrap(
        spacing: 12.0, // 主轴(水平)方向间距
        runSpacing: 0, // 纵轴(垂直)方向间距
        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(),
      ),

使用Wrap的好处就是可以满一行后自动换行,且最大的item不会超过一行,这里item显示的样式可以根据具体的UI来给,也可以自定义Container来做。博主这里使用的是官方提供的组件Chip,默认就是椭圆形的,但是缺点是高度不可调,宽度会随着文字变化而变化,看看怎么用吧:

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

在Chip里面有这些属性:

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,

可以设置左右的icon,下面给个案例:

Chip(
            backgroundColor: Color(0xFFF7F7F7),
            avatar: Icon(
              xxxxx,
              size: 14,
              color: Color(0xFFC9CCCF),
            ),
            label: '搜索商品',style: TextStyle(fontSize: 12, color: Color(0xFF202020),)),
            padding: EdgeInsets.all(5),
            ///设置右侧icon和文字之间的间距
            labelPadding: EdgeInsets.only(right: 50),
            deleteIcon: Icon(
              xxxx,
              color: Color(0xFFC7C7C7),
              size: 14,
            ),
            deleteIconColor: Colors.red,
            onDeleted: () {

            },
          ),

这样一来,是不是基本上核心部分就解决了?
完整的实现逻辑代码放在下面,比较长,有需要的可以参考:

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();
  /// 搜索输入框hint内容
  String get _hint => '搜索店铺或商品';

  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>[
        ///搜索历史头部标题和删除按钮
        searchHisHeaderView(),
        ///搜索历史item
        searchHisItemView(),
        ///剩余空间
        _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),
    );
  }

  /// 构建搜索顶部栏
  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,
                      ),
                      /// 搜索关键字输入栏
                      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), //边角为20
                            ),
                            borderSide: BorderSide(
                              color: Color(0xFFE2E4EB),
                              width: 0.5, //边线宽度为1
                            ),
                          ),
                          fillColor: Color(0xFFFEFEFE),
                          focusedBorder: OutlineInputBorder(
                            borderRadius: BorderRadius.all(
                              Radius.circular(10), //边角为20
                            ),
                            borderSide: BorderSide(
                              color: Color(0xFFE2E4EB),
                              width: 0.5, //边线宽度为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('搜索',
                          style:
                          TextStyle(fontSize: 17, color: Color(0xFF3479FD)))),
                  onTap: () {
                    goToSearch(_keyword ?? "");
                  },
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }

  Widget searchHisHeaderView() {
    print('隐藏头');
    if ( _keyword.length > 0) {
      return Offstage();
    }
    var length = _keywords?.length ?? 0;
    if (length == 0) {
      return Offstage();
    }
    return new Row(
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: <Widget>[
          new Container(
            child: Text(
              '搜索历史',
              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();
    }
    var length = _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, // 主轴(水平)方向间距
        runSpacing: 0, // 纵轴(垂直)方向间距
        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(),
      ),
    );
  }

  /// 构建页面剩余空间
  Widget _searchBottomView() {
    if (_keyword.length > 0) {
      return Offstage();
    }
    var length = _keywords?.length ?? 0;
    if (length == 0) {
      return Offstage();
    }
    return Expanded(
      child: Container(
        margin: EdgeInsets.only(top: 10),
        color: Colors.grey[100],
      ),
    );
  }

  ///点击搜索
  Future<void> goToSearch(String content) async {
    if (content.length == 0) return;
    content = content?.trim() ?? '';
    ///在不失去焦点的情况下隐藏键盘
    SystemChannels.textInput.invokeMethod('TextInput.hide');
    if (content.isNotEmpty) {
      ///非空才记录数据
      var hisList = _keywords ?? [];
      ///删除已经存在的记录
      hisList.remove(content);
      ///新记录放前面
      hisList.insert(0, content);
      ///保留最多15条记录
      if (hisList.length > 15) {
        hisList.removeLast();
      }

      ///刷新记录
      refreshHisLists(hisList);
    }
  }

  ///设置搜索关键字
  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 ?? [];
    });
  }

  ///查询搜索关键字
  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;
}

SpUtil是一个第三方提供的库,需要自己引入哦:

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.1

以上,你学会了么?快来自己试试吧。