首页功能

首页模块分为上下两个部分,上半部分为滚动的banner,下半部分为文章列表,下面就来看下各个部分是如何实现的。

Jetpack版Wan-Android项目地址:Android Jetpack架构开发组件化应用实战 欢迎star

Flutter版Wan-Android项目地址:Flutter版Wan-Android 欢迎star

Banner

首页需要引入banner插件,大家所需要的插件可以到 https://pub.dev 中查找。 我们在这里引入 flutter_swiper 插件。siwiper支持多种布局,无限轮播,Android和iOS双端适配。

下面看下具体使用:

Container(
        height: 180,
        child: Swiper(
          itemBuilder: (BuildContext context, int index) {
            return GestureDetector( //监听手势点击事件
              onTap: () {
                Navigator.of(context).push(MaterialPageRoute(
                    builder: (context) => WebView(
                          url: bannerList[index].url,
                          title: bannerList[index].title,
                        )));
              },
              child: new Image.network( //加载banner图片
                bannerList[index].imagePath,
                fit: BoxFit.fill,
              ),
            );
          },
          itemCount: bannerList.length, //banner显示个数
          autoplay: true, //是否自动滚动
          pagination: new SwiperPagination(), //指示器
        ),
      )

简单的几个属性就可以完成一个banner了。具体用法请移步到官方文档 这里不再赘述。

文章列表

文章列表使用的是listview来实现,主要的功能要有下拉刷新和上来加载更多。而且点击后可以跳转到具体的详情页面。每个listview的item采用card布局来实现, 卡片内部逻辑采用的是Column(text,text,row(text,text),row(text,text)) 这样的接口来布局,

GestureDetector(
      onTap: () => Navigator.of(context).push(MaterialPageRoute(
          builder: (context) => WebView(
                url: widget.articleItem.link,
                title: widget.articleItem.title,
              ))),
      child: Card(
          elevation: 5,
          margin: EdgeInsets.only(left: 10, right: 10, top: 8),
          child: Container(
            padding: EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Container(
                  child: Text(
                    widget.articleItem.title,
                    maxLines: 1,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(color: Colors.black, fontSize: 16),
                  ),
                ),
                Container(
                  margin: EdgeInsets.only(top: 5, bottom: 10),
                  child: Text(
                    widget.articleItem.desc == ""
                        ? widget.articleItem.title * 2
                        : widget.articleItem.desc,
                    maxLines: 2,
                    overflow: TextOverflow.ellipsis,
                    style: TextStyle(color: Colors.black54, fontSize: 14),
                  ),
                ),
                Row(
                  children: <Widget>[
                    _itemTags(widget.articleItem.superChapterName,
                        widget.articleItem.projectLink),
                    _itemTags(widget.articleItem.chapterName,
                        widget.articleItem.projectLink)
                  ],
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    Container(
                      child: Text(
                        '作者:${widget.articleItem.author}',
                        style: TextStyle(fontSize: 12),
                      ),
                    ),
                    Text(
                      widget.articleItem.niceDate,
                      style: TextStyle(fontSize: 12),
                    ),
                  ],
                )
              ],
            ),
          )),
    )

其中将tag标签方法进行抽取

_itemTags(String name, String projectLink) {
    return GestureDetector(
      onTap: () => projectLink != ""
          ? Navigator.of(context).push(MaterialPageRoute(
              builder: (context) => WebView(
                    url: projectLink,
                    title: name,
                  )))
          : Toast.show("暂无项目地址", context,
              duration: Toast.LENGTH_SHORT, gravity: Toast.CENTER),
      child: Container(
        height: 22,
        alignment: Alignment.center,
        margin: EdgeInsets.only(bottom: 10, right: 10),
        padding: EdgeInsets.fromLTRB(5, 1, 5, 1),
        decoration: BoxDecoration(
            border: Border.all(color: Colors.green),
            borderRadius: BorderRadius.all(Radius.circular(4))),
        child: Text(
          name,
          style: TextStyle(fontSize: 12),
        ),
      ),
    );
  }

下面就是将banner布局添加给listview的第0个元素即可

_homePageItem(int index) {
    return index == 0
        ? _banner()
        : ArticleCard(
            articleItem: articleData[index],
          );
  }

下面就是给列表添加下拉刷新和上拉加载功能,flutter本身以及自带了下拉刷新的插件了,RefreshIndicator,他需要一个listview的child,和一个onRefresh回调方法,这个回调方法返回的是一个Future,

但是flutter并没有提供上拉加载的功能,那么这个要怎么实现呢? listview有一个参数control参数需要传ScrollController,他有一个listener事件用来监听listview的滑动,在此listener中我们可以根据页面滑动的像素是否已经到达最大值来控制上拉加载功能。

_scrollController.addListener(() {
      if (_scrollController.position.pixels ==
          _scrollController.position.maxScrollExtent) {
      		//继续调用接口请求数据并添加在list中
      }
    })

下拉刷新和上拉加载功能已经实现了,当用户一直上拉加载,整个页面已经加载了n页了,这个时候如果能提供一个一键回到顶部是不是很爽呢? 那么这个功能怎么实现呢? 此时我们可以使用NotificationListener来监听listview的滚动事件,当用户滑动到移动偏移量的时候就显示回到顶部按钮,否则就隐藏按钮。 首先这个按钮使用FloatingActionButton来实现,然后在NotificationListener的onNotification中获取用户滑动的偏移量:

NotificationListener(
	    onNotification: (onNotification) {
	      if (onNotification is ScrollUpdateNotification) {
	        if (onNotification.depth == 0) {//防止banner滚动监听 
	          setState(() {
	            scrollPixels = onNotification.metrics.pixels; //获取偏移量
	          });
	        }
	      }
	    }

FloatingActionButton代码:

floatingActionButton: scrollPixels > SCROLL_PIXELS_OFFSET  //SCROLL_PIXELS_OFFSET是约定的偏移量
        ? FloatingActionButton(
            onPressed: () {
              _scrollController.animateTo(0,
                  duration: Duration(milliseconds: 100),
                  curve: Curves.ease); //滑动到第0个元素,
            },
            backgroundColor: Colors.lightBlue,
            child: Icon(
              Icons.arrow_upward,
              color: Colors.white,
            ),
          )
        : null  //返回null代表隐藏

以上就是首页的主要实现功能,具体代码详见github项目: