移动互联技术与实践-实验二

实验二 高级界面设计

一、实验目的

1.掌握布局之间嵌套,完成复杂界面的实现。

2.学习列表控件的使用,并完成复杂列表子项的展示。

3.掌握应用内各界面之间的导航。

4.利用基本的数组、链表、哈希表等数据结构完成内存数据便捷管理。

二、知识要点

1.布局嵌套
要完成复杂美观的界面布局,很多时候要组合或者嵌套多个布局来达到目标,在Flutter 中常见可用于嵌套的布局 widgets 如下:

对齐 widgets

调整 widgets

聚集 widgets

嵌套行和列

Container

GridView

ListView

Stack

2.列表控件
显示数据列表是移动应用程序常见的需求。ListView 是最常用的可滚动组件之一,可以沿一个方向线性排布所有子组件,并且它也支持基于 Sliver 的延迟构建模型。

3.导航控件
AppBar Material Design 的顶部工具栏。

TabBar Material Design 顶部导航栏。

BottomNavigationBar Material Design 底部导航栏。

CupertinoNavigationBar iOS 风格顶部导航栏。

CupertinoTabBar iOS 风格底部选项卡。

三、 实验内容

1.课堂实验

a) 实现底部导航栏,包含“文件”和“我的”,可以在“文件”界面和“我的”界面相互切换。

b) 实现顶部搜索栏布局部分,包含“放大镜”图标和“搜索网盘文件”提醒文本。

c) 实现文件夹目录,包含“图标”、“标题”、“创建时间”;同时实现文件展示,包含“图标”、“文件名”、“创建时间”、“文件大小”,建议使 用 ListView。

d) 点击文件目录可以点击进入二级网盘目录,并在进入后的页面标题栏显示该目录名,显示二级目标下的文件夹目录或者文件目录。

2.课后练习

a) 文件页面的搜索框获取焦点或者点击开始输入时,跳转到新的搜索页面。

b) 搜索页面实现搜索输入框和右侧取消按钮,输入框输入的关键字暂时仅支持添加到搜索历史功能。

c) 实现搜索历史的展示。

d) 手动输入四到五个搜索历史以顺序排列,右侧含有删除按钮,点击后删除当前选择的搜索历史记录。

e) 页面底部有“清空历史记录”按钮,点击后清空所有的搜索历史记录。

f) 点击取消,关闭当前搜索页面,返回到文件页面。

四、实验结果
1)、课堂实验

1、代码

//由于组件获得输入焦点或提交搜索关键词时需要和其它组件交互  自定义两个回调函数
typedef SearchInputOnFocusCallback = void Function();
typedef SearchInputOnSubmittedCallback = void Function(String value);

// 搜索组件
class SearchInputWidget extends StatelessWidget {

  TextEditingController textController;

  SearchInputOnFocusCallback onTab;
  SearchInputOnSubmittedCallback onSubmitted;

  SearchInputWidget({this.onSubmitted,this.onTab,this.textController});

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: textController,
      onTap: onTab,
      onSubmitted: onSubmitted,
      decoration: InputDecoration(
          hintText: '搜索网盘文件',
          filled: true,
          fillColor: Color.fromARGB(255, 240, 240, 240),
          contentPadding: EdgeInsets.only(left:0),
          border:OutlineInputBorder(
              borderSide: BorderSide.none,
              borderRadius: BorderRadius.circular(20)
          ) ,
          prefixIcon: Icon(
            Icons.search,
            color: Colors.black26,
          )

      ),
    );
  }
}


typedef FilesListOnFileTapCallback = void Function(DiskFile file);

// 文件列表组件
class FilesListWidget extends StatelessWidget {

  List<DiskFile> disFiles = List<DiskFile>();// 列表

  FilesListOnFileTapCallback onFileTap;

  FilesListWidget(this.disFiles,{this.onFileTap});

  @override
  Widget build(BuildContext context) {
    return this.disFiles.length == 0
        ? Column(
      children: [
        SizedBox(height: 200.0,),
        Image.asset("assets/ic_gc_main_empty_folder.png"),
        Text('当前目录下没有文件')
      ],
    ) : ListView.builder(
      physics: BouncingScrollPhysics(),
      shrinkWrap: true,
      itemCount: this.disFiles.length,
      itemBuilder: (context,index){
        if(this.disFiles[index].isDir == 0){// 文件
          return _bulidFileItem(this.disFiles[index]);
        }else{// 文件夹
          return _buildFolderItem(this.disFiles[index]);
        }
      },
    );
  }

  Widget _buildFolderItem(DiskFile file){// 展示目录信息
    return InkWell(
      child: Container(
        padding: EdgeInsets.only(top: 10.0),
        alignment: Alignment.center,
        decoration: BoxDecoration(
            border: Border(
                bottom: BorderSide(
                    width: 0.5,
                    color: Color(0xffe5e5e5)
                )
            )
        ),
        child: ListTile(
          leading: Image.asset("assets/ic_gc_main_empty_folder.png"),
          title: Row(
            children: [
              Expanded(
                child: Text(file.serverFilename),
              )
            ],
          ),
          // subtitle: Text(Utils.getDateTime(file.serverCtime)),
          trailing: Icon(Icons.chevron_right),
        ),
      ),
      onTap: (){
        if(null != this.onFileTap) this.onFileTap(file);
      },
    );
  }

  Widget _bulidFileItem(DiskFile file){// 展示文件信息
    return InkWell(
      child: Container(
        padding: EdgeInsets.only(top: 10.0,bottom: 5.0),
        alignment: Alignment.center,
        decoration: BoxDecoration(
            border: Border(
                bottom: BorderSide(
                    width: 0.5,
                    color: Color(0xffe5e5e5)
                )
            )
        ),
        child: ListTile(
          leading: Image.asset("assets/file.png"),
          title: Text(file.serverFilename),
        ),
      ),
      onTap: (){
        if(null != this.onFileTap) this.onFileTap(file);
      },
    );
  }

}

2、结果截图

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2)、课后练习

1、代码

自定义的历史记录组件

通过构造函数传入历史记录内容和删除组件的回调函数(如果想要实现删除组件的功能,key 是必要的)

//定义与父组件交互的回调函数
typedef DeleteWidgetCallback = void Function(SearchHistoryWidget widget);

//自定义历史记录组件
class SearchHistoryWidget extends StatefulWidget {

  final String title;

  final DeleteWidgetCallback deleteWidget;

  //实现删除组件的功能,key是必要的
  SearchHistoryWidget(Key key,{this.title,@required this.deleteWidget}) : super(key:key);

  @override
  _SearchHistoryWidgetState createState() => _SearchHistoryWidgetState(this.title);
}

class _SearchHistoryWidgetState extends State<SearchHistoryWidget> {

  String title;

  _SearchHistoryWidgetState(this.title);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: InkWell(
        onTap: (){
          print(this.title);
        },
        child: Row(
          children: [
            Container(
              child: IconButton(
                icon: Icon(Icons.restore),
                onPressed: (){},
              ),
            ),
            Expanded(
              child: Text(this.title),
            ),
            Container(
              child: IconButton(
                icon: Icon(Icons.delete),
                onPressed: (){
                  widget.deleteWidget(widget);
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}
SearchPage 类

import 'package:flutter/material.dart';
import 'package:flutter_app02/widget/Widgets.dart';
import 'package:shared_preferences/shared_preferences.dart';// 实现本地存储


class SearchPage extends StatefulWidget {
  @override
  _SearchPageState createState() => _SearchPageState();
}

class _SearchPageState extends State<SearchPage> {

  TextEditingController _textController = TextEditingController();// 文本控制器  用于清空文本框

  List<String> hisList = new List();// 存储历史记录的列表

  List<SearchHistoryWidget> widgetList = new List();// 存储SearchHistoryWidget的列表

  @override
  void initState(){
    // 读取本地存储的历史记录 并显示
    _getHistory();
    super.initState();
  }

  @override
  void dispose(){
    print("组件销毁");
    _saveHistory();
    super.dispose();
  }

  //获取本地存储的历史记录
  void _getHistory() async{
    final prefs = await SharedPreferences.getInstance();
    setState(() {
      hisList = prefs.getStringList('hisList') ?? [];
      showHistory(hisList);
    });
  }

  //将当前的历史记录存储进本地
  void _saveHistory() async{
    final prefs = await SharedPreferences.getInstance();
    prefs.setStringList("hisList", hisList);
  }

  // 搜索框提交事件
  void _onSubmittedSearch(value){
    //搜索文件
    searchFile(value);
    //添加到历史记录
    addToHistory(value);
  }

  // 文件搜索
  void searchFile(value){

  }

  // 添加到历史记录
  void addToHistory(value){
    setState(() {
      _textController.clear();// 清空文本框
      hisList.add(value);
      showHistory(hisList);// 展示列表文件
    });
  }

  //展示历史记录
  void showHistory(list){
    widgetList.clear();
    list.forEach((element){
      widgetList.add(SearchHistoryWidget(
        UniqueKey(),
        title: element,
        deleteWidget: (v){
          //v 是待删除组件的title
          setState(() {
            hisList.remove(v.title);
            print(hisList);
            showHistory(hisList);
          });
        },
      ));
    });
  }

  // 清空历史记录文本
  Widget _clearHistoryText(){
    return Container(
      alignment: Alignment(0,-1),
      margin: EdgeInsets.only(bottom: 10.0),
      child: Stack(
        children: [
          InkWell(
            child: Text(
              "清空历史记录",
              style: TextStyle(
                  fontSize: 16.0,
                  color: Colors.blue
              ),
            ),
            onTap: (){
              print("清空历史记录");
              setState(() {
                hisList.clear();
                widgetList.clear();
              });
            },
          )
        ],
      ),
    );
  }
  // 搜索文本框
  Widget _searchTextField(){
    return Row(
      children: [
        Expanded(
          child: SearchInputWidget(onSubmitted: _onSubmittedSearch,textController: _textController,),
        ),
        Container(
          padding: EdgeInsets.only(left: 15.0,right: 15.0),
          child: InkWell(
            child: Text(
              "取消",
              style: TextStyle(
                  color: Colors.blue,
                  fontSize: 20.0
              ),
            ),
            onTap: (){
              Navigator.pop(context);
            },
          ),
        )
      ],
    );
  }
  // 搜索历史文本框
  Widget _searchHistoryText(){
    return Container(
      padding: EdgeInsets.only(left: 10.0),
      alignment: Alignment(-1,0),
      child: Text(
        "搜索历史",
        style: TextStyle(
            fontSize: 16.0,
            fontWeight: FontWeight.w900,
            color: Colors.black
        ),
      ),
    );
  }
  // 搜索历史列表
  Widget _searchHistoryBody(){
    return SingleChildScrollView(
      child: Column(
        children: widgetList,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Column(
            children: [
              SizedBox(height: 25,),
              _searchTextField(),
              SizedBox(height: 10,),
              _searchHistoryText(),
              _searchHistoryBody(),
              Spacer(),// 相当于弹簧效果,只有在不可以滑动的时候,弹簧才有效果
              _clearHistoryText(),
            ]
        ),
      )
    );
  }
}

2、结果截图
在这里插入图片描述
在这里插入图片描述

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值