vlambda博客
学习文章列表

从 Flutter 2.0 开始学 - 构建关注展示界面

关注界面效果

在 Flutter 中一切皆 Widget,再大的界面,都可以划分成一个一个小的组件,拼成一个大的组件。

根据数据渲染,每个小的组件可以负责不同的业务逻辑。

组件划分

编码

ATHFollowContent

import 'follow_item.dart';

class ATHFollowContent extends StatefulWidget {
  @override
  _ATHFollowContentState createState() => _ATHFollowContentState();
}

class _ATHFollowContentState extends State<ATHFollowContent>
    with AutomaticKeepAliveClientMixin 
{
  // 总数

  @override
  Widget build(BuildContext context) {
    super.build(context);
    List<ATHFollowItemModel> followItemList = MockFollowItemData.followItemList;
    return SliverList(
      delegate: SliverChildBuilderDelegate(
        (context, index) {
          ATHFollowItemModel followItem = followItemList[index];
          return ATHFollowItem(
            followItem: followItem,
          );
        },
        childCount: followItemList.length,
      ),
    ),);
  }

  @override
  bool get wantKeepAlive => true;
}

  • AutomaticKeepAliveClientMixin 接口,使用当前组件保持状态
  • 实现  bool get wantKeepAlive => true; 函数
  • 通过 ATHFollowItem 创建,构建每一条 Item

ATHFollowItem

class ATHFollowItem extends StatelessWidget {
  final ATHFollowItemModel followItem;

  const ATHFollowItem({Key key, this.followItem}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          ATHFollowUserInfo(followItem: followItem),
          ATHFollowContent(followItem: followItem),
          followItem.images.length > 0
              ? ATHFollowGridImage(followItem: followItem)
              : SizedBox(),
          ATHFollowComment(followItem: followItem),
          ATHFollowOperation(followItem: followItem),
          Padding(
            padding: EdgeInsets.symmetric(vertical: kSize20),
            child: ATHItemDivider(),
          ),
        ],
      ),
    );
  }
}

ATHFollowUserInfo

  • 显示用户头像,介绍信息
class ATHFollowUserInfo extends StatelessWidget {
  final ATHFollowItemModel followItem;

  const ATHFollowUserInfo({Key key, this.followItem}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Row(
      crossAxisAlignment: CrossAxisAlignment.center,
      children: <Widget>[
        Container(
          child: ATHUserAvatar(
            avatarPath: followItem.poetryItem.poetryAvatar,
          ),
        ),
        Expanded(
          child: Container(
            padding: EdgeInsets.only(left: kSize24),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Row(
                  children: <Widget>[
                    Text(
                      followItem.poetryItem.poetryAuthor ?? "",
                      style: TextStyle(color: kColor33, fontSize: kSize30),
                    ),
                  ],
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  children: <Widget>[
                    Text(
                      followItem.createTime ?? "",
                      style: TextStyle(color: kColor33),
                    ),
                  ],
                )
              ],
            ),
          ),
        )
      ],
    );
  }
}

ATHFollowContent

  • 显示内容
class ATHFollowContent extends StatelessWidget {
  final ATHFollowItemModel followItem;

  const ATHFollowContent({Key key, this.followItem}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: <Widget>[
        Padding(
          padding: EdgeInsets.symmetric(vertical: kSize20),
          child: Text(
            followItem.poetryItem.poetryTitle ?? "",
            style: TextStyle(fontSize: kSize32),
          ),
        ),
        Text(followItem.poetryItem.poetryContent ?? "",
            style: TextStyle(color: kColor66, fontSize: kSize32))
      ],
    );
  }
}

ATHFollowGridImage

  • 显示图片,多张图片,为一个 GridView
class ATHFollowGridImage extends StatelessWidget {
  final ATHFollowItemModel followItem;

  const ATHFollowGridImage({Key key, this.followItem}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    List<String> imageList = followItem.images;
    return GridView.custom(
      shrinkWrap: true,
      padding: EdgeInsets.only(top: kSize20),
      physics: NeverScrollableScrollPhysics(),
      gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
        crossAxisCount: 3,
        mainAxisSpacing: kSize20,
        crossAxisSpacing: kSize20,
      ),
      childrenDelegate: SliverChildBuilderDelegate((context, position) {
        String imagePath = imageList[position];
        return Container(
          child: ClipRRect(
            borderRadius: BorderRadius.all(Radius.circular(kSize8)),
            child: Image.asset(
              imagePath,
              fit: BoxFit.cover,
            ),
          ),
        );
      }, childCount: imageList.length),
    );
  }
}

ATHFollowComment

  • 评论组件
class ATHFollowComment extends StatelessWidget {
  final ATHFollowItemModel followItem;

  const ATHFollowComment({Key key, this.followItem}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Container(
      width: double.infinity,
      margin: EdgeInsets.only(top: kSize20),
      padding: EdgeInsets.all(kSize16),
      decoration: BoxDecoration(
          color: kColorF1F1F1,
          borderRadius: BorderRadius.all(Radius.circular(kSize8))),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: <Widget>[
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: <Widget>[
              Row(
                crossAxisAlignment: CrossAxisAlignment.center,
                children: <Widget>[
                  SvgPicture.asset('assets/icons/icon_hot.svg', width: kSize30),
                  Text(
                    101 >= 100 ? "热评" : "",
                    style: TextStyle(color: kColor33, fontSize: kSize22),
                  )
                ],
              ),
              Text(
                "${followItem.commentItem.commentLike}赞",
                style: TextStyle(color: kColor33),
              )
            ],
          ),
          Padding(
            padding: EdgeInsets.only(top: kSize8),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Row(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: <Widget>[
                    Text(
                      followItem.commentItem.commentAuthor + " : " ?? "",
                      style: TextStyle(color: kPrimaryColor),
                    ),
                    Text(
                      followItem.commentItem.commentContent ?? "",
                      style: TextStyle(color: kColor33),
                    ),
                  ],
                ),
                Text(
                  followItem.commentItem.commentTime ?? "",
                  style: TextStyle(color: kColor33, fontSize: kSize24),
                ),
              ],
            ),
          )
        ],
      ),
    );
  }
}

ATHFollowOperation

  • 可以操作按钮,显示分享、评论、点赞...
class ATHFollowOperation extends StatelessWidget {
  final ATHFollowItemModel followItem;

  const ATHFollowOperation({Key key, this.followItem}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: EdgeInsets.only(top: kSize20),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
              padding:
                  EdgeInsets.symmetric(horizontal: kSize10, vertical: kSize4),
              decoration: BoxDecoration(
                  borderRadius: BorderRadius.all(Radius.circular(kSize8)),
                  shape: BoxShape.rectangle,
                  color: kColorF1F1F1),
              child: Text(
                followItem.tag ?? "",
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(fontSize: kSize24, color: kColor99),
              )),
          Padding(
            padding: EdgeInsets.only(top: kSize16),
            child: ATHOperatorView(),
          ),
        ],
      ),
    );
  }
}

构建模拟数据

  • 由数据驱动,界面展示根据数据
class MockFollowItemData {
  static List<ATHFollowItemModel> followItemList = [
    ATHFollowItemModel(
      poetryItem: ATHPoetryItemModel(
        poetryAuthor: "杜甫-少陵野老",
        poetryTitle: "登高",
        poetryAvatar: "assets/images/dufu.jpg",
        poetryContent:
            "风急天高猿啸哀,渚清沙白鸟飞回。\n无边落木萧萧下,不尽长江滚滚来。\n万里悲秋常作客,百年多病独登台。\n艰难苦恨繁霜鬓,潦倒新停浊酒杯。",
      ),
      images: [
        'assets/images/qrcode_430.jpg',
        'assets/images/qrcode_430.jpg',
        'assets/images/qrcode_430.jpg',
      ],
      commentItem: ATHCommentItemModel(
          commentAuthor: "江景",
          commentAuthorAvatar: "assets/images/libai.jpg",
          commentContent: "哈哈哈,可以可以。",
          commentLike: 831,
          commentTime: "752-02-23 23:23:23"),
      createTime: "752-02-22 22:22:22",
      tag: "萧瑟荒凉之感",
      likeCount: 678,
    ),
    ATHFollowItemModel(
      poetryItem: ATHPoetryItemModel(
        poetryAuthor: "李白-诗仙",
        poetryTitle: "三五七言 / 秋风词",
        poetryAvatar: "assets/images/libai.jpg",
        poetryContent:
            "秋风清,秋月明,\n落叶聚还散,寒鸦栖复惊。\n相思相见知何日?此时此夜难为情!\n入我相思门,知我相思苦,\n长相思兮长相忆,短相思兮无穷极,\n早知如此绊人心,何如当初莫相识。",
      ),
      images: [
        'assets/images/qrcode_430.jpg',
      ],
      commentItem: ATHCommentItemModel(
          commentAuthor: "杜甫-少陵野老",
          commentAuthorAvatar: "assets/images/libai.jpg",
          commentContent: "emm,有点意思",
          commentLike: 9831,
          commentTime: "752-10-23 02:00:13"),
      createTime: "752-10-23 01:00:13",
      tag: "高悬天空明月",
      likeCount: 1678,
    ),
  ];
}

添加到 home 到 tabContents

TabBarView _buildTabBarView() {
    List<Widget> tabContents = [];
    for (int index = 0; index < _tabsText.length; index++) {
      if (index == 0) {
        // 添加这个
        tabContents.add(ATHFollowContent());
      } else if (index == 1) {
        tabContents.add(ATHRecommendContent());
      } else {
        tabContents.add(Center(child: Text(_tabsText[index])));
      }
    }
    return TabBarView(controller: _tabController, children: tabContents);
  }
  • 完成这一步可以编译构建看效果啦 ~

连载往期文章