FlutterApp首页实现

首页实现总结

目录

  • 根布局
  • Banner图
  • AppBar
  • 卡片布局
image.png

根布局

根布局使用Scaffold,这个是material包下的组件,它是一个路由页的骨架,可以非常容易的拼装出一个完整的页面。进入首页先显示加载,通过网络访问得到数据后关闭,还要支持下拉刷新的功能。
技术点:
MediaQuery.removePadding移除系统栏Padding
RefreshIndicator控制下拉刷新。ListView滚动的时候,并且检查滚动的深度为0,防止子View的滚动造成性能损耗。
ListView利用显示的自列表来构造 List。此构造函数适合于具有少量子元素的列表视图,因为构造列表需要为可能显示在列表视图中的每个子元素执行工作,而不仅仅是那些实际可见的子元素。
_handleRefresh 是Flutter中的异步函数,在未来某个时刻执行。主要用来获取网络数据。

@override
  Widget build(BuildContext context) {
    return Scaffold(
        backgroundColor: Color(0xfff2f2f2),
        body: LoadingContainer(// 加载页面
            isLoading: _loading,// 控制加载页面
            child: Stack(// ListView与AppBar 类似Stack结构摆放
              children: [
                MediaQuery.removePadding(// 去掉系统栏的Padding
                  // 去掉顶部屏幕适配
                  removeTop: true,
                  context: context,
                  child: RefreshIndicator(// 下拉刷新
                      child: NotificationListener(
                        onNotification: (scrollNotification) {// 监听下拉的状态
                          if (scrollNotification is ScrollUpdateNotification &&
                              scrollNotification.depth == 0) {
                            // 优化, 防止scrollNotification=0的时候也监听,只有在ScrollUpdateNotification更新的时候才监听并且只监听ListView的滚动滚动且是列表滚动的时候
                            _onScroll(scrollNotification.metrics.pixels);
                          }
                        },
                        child: _listView,
                      ),
                      onRefresh: _handleRefresh),
                ),
                _appBar,
              ],
            )
        )
    );
  }

  Future _handleRefresh() async {// Fullter中的异步,返回一个Future
    try {
      HomeModel mode = await HomeDao.fetch();
      setState(() {// 更新Model
        localNavList = mode.localNavList;
        subNavList = mode.subNavList;
        gridNavModel = mode.gridNav;
        salesBoxModel = mode.salesBox;
        bannerList = mode.bannerList;
        _loading = false;// 显示视图
      });
    } catch (e) {
      print(e);
      _loading = false;
    }
    return null;
  }

载入布局LoadingContainer:
此Widget由外部isLoading控制显示隐藏,内部持有子Widget的引用,cover控制是否覆盖整个页面

@override
 Widget build(BuildContext context) {
   return !cover // // 是否覆盖整个页面
       ? isLoading ? _loadingView : child
       : Stack(
           children: [child, isLoading ? _loadingView : null],
         );
 }

1. Banner图

引入flutter_swiper库。进入到pubspec.yaml文件里添加,同时,引入三方库也是一样。图片通过网络加载。
pagination: SwiperController 用于控制 Swiper的index属性, 停止和开始自动播放. 通过 new SwiperController() 创建一个SwiperController实例,并保存,以便将来能使用。

image.png

Widget get _banner {
    return Container(// Container设置固定高度
      height: 160,
      child: Swiper(//第三方控件
        itemCount: bannerList.length,// 长度
        autoplay: true,// 开启自动播放
        itemBuilder: (BuildContext context, int index) {
          return GestureDetector(// 返回一个可以点击的图片
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(builder: (context) {
                  CommonModel model = bannerList[index];
                  return WebView(
                    url: model.url,
                    title: model.title,
                    hideAppBar: model.hideAppBar,
                  );
                }),
              );
            },
            child: Image.network(bannerList[index].icon, fit: BoxFit.fill),
          );
        },
        pagination: SwiperPagination(), // 指示器
      ),
    );
  }

2.AppBar

流程分析:AppBar 在下滑的过程中透明度发生改变,AppBar可以选择城市,进入搜索页面。
decoration(装饰器)DecoratedBox可以在其子widget绘制前(或后)绘制一个装饰Decoration(如背景、边框、渐变等)。主要由外部的appBarAlpha值控制透明度。
控制透明度逻辑:监听ListView滑动的距离,滑动过程中不断改变透明值,通过setState()更新视图。

image.png
Widget get _appBar {
    return Column(
      // 上面输入,下面阴影
      children: [
        Container(
          decoration: BoxDecoration(
              gradient: LinearGradient(//线性渐变
            // AppBar 渐变遮罩背景, 由透明到
            colors: [Color(0x66000000), Colors.transparent],
            begin: Alignment.topCenter,
            end: Alignment.bottomCenter,
          )),
          child: Container(
            padding: EdgeInsets.fromLTRB(0, 30, 0, 0),
            height: 80.0,
            decoration: BoxDecoration(
                color:
                    Color.fromARGB((appBarAlpha * 255).toInt(), 255, 255, 255)
            ),
            child: SearchBar(
              searchBarType: appBarAlpha > 0.2
                  ? SearchBarType.homeLight
                  : SearchBarType.home,
              inputBoxClick: _jumpToSearch,
              speakClick: _jumpToSpeak,
              defaultText: SEARCH_BAR_DEFAULT_TEXT,
              leftButtonClick: () {},
            ),
          ),
        ),
        Container(
          // 阴影设置
          height: appBarAlpha > 0.2 ? 0.5 : 0,
          decoration: BoxDecoration(
              boxShadow: [BoxShadow(color: Colors.black12, blurRadius: 0.5)]),// 设置阴影
        )
      ],
    );
  }
// 滚动的逻辑
 _onScroll(offset) {
    var alpha = offset / APPBAR_SCROLL_OFFSET;
    if (alpha < 0) {
      // alpha 值的保护
      alpha = 0;
    } else if (alpha > 1) {
      alpha = 1;
    }
    setState(() {
      appBarAlpha = alpha;
    });
    print(appBarAlpha);
  }

SeacherBar
对InputDecoration进行了封装,拥有多种成员函数,是否禁止搜索、按钮隐藏、背景颜色、函数回调等多种功能。
final void Function() leftButtonClick; 将函数从其他地方传入,加大扩展性。
_wrapTap() 封装函数对可点击的按钮进行封装
ValueChanged 文本内容变换监听,用于改变按钮的样式
优化:原因:由于每次文本变化需要从网络拉取数据,可能造成用户输入的内容不是真正需要显示的内容。
解决:在网络请求方法保存keyword。检查请求来的keyword是否一样。检查Widget重绘的次数,并且解决BUG。

_wrapTap(Widget child, void Function() callback) {
  return GestureDetector(
     onTap: () {
       if (callback != null) callback();
     },
     child: child,
   );
 }

  SearchDao.fetch(url, text).then((SearchModel model) {
      // 只有当当前输入的内容与服务端返回的内容一致才渲染
      if (model.keyword == keyword) {// 检验keyword
        setState(() {
          searchModel = model;
        });
      }
    }).catchError((e) {
      print(e);
    });

3. 卡片布局

布局分成三部分,再将其中一部分分为,一个大卡片两个小卡片。
PhysicalModel 实现卡片圆角
FractionallySizedBox:撑满父布局,FractionallySizedBox控件会根据现有空间,来调整child的尺寸,所以说child就算设置了具体的尺寸数值,也不起作用。

image.png

  @override
  Widget build(BuildContext context) {

    return PhysicalModel(// 
      color: Colors.transparent,
      borderRadius: BorderRadius.circular(6),// 圆角
      clipBehavior: Clip.antiAlias, // 裁切
      child: Column(
        children: _gridNavItems(context),
      ),
    );
  }

4. 底部卡片

顶部是可点击的图片与文本,下面是三组卡片,第一组高度较大
Image: BoxFit.fill:会拉伸填充满显示空间,图片本身长宽比会发生变化,图片会变形。
width: MediaQuery.of(context).size.width:获得手机屏幕宽度,需要计算padding

image.png

参考

  • flutter 轮播组件 Swiper
  • flutter widget: ListView
  • Flutter 布局(四)- Baseline、FractionallySizedBox、IntrinsicHeight、IntrinsicWidth详解
  • BoxDecoration
  • Image

你可能感兴趣的:(FlutterApp首页实现)