flutter开发实战-类似微博帖子列表及下拉刷新上拉加载效果

flutter开发实战-类似微博帖子列表及下拉刷新上拉加载效果

在之前处理类似微博帖子列表及下拉刷新上拉加载效果,刷新使用的是EasyRefresh

一、引入EasyRefresh与likeButton

在工程的pubspec.yaml中引入插件

    # 下拉刷新、上拉更多
  easy_refresh: ^3.3.2+1
  pull_to_refresh: ^2.0.0

需要使用EasyRefreshController来控制处理刷新,初始化

@override
  void initState() {
    super.initState();
    _controller = EasyRefreshController(
      controlFinishRefresh: true,
      controlFinishLoad: true,
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

需要在onRefresh与onLoad来处理下拉刷新上拉加载数据。

二、类似微博帖子列表及下拉刷新上拉加载效果

类似微博帖子列表,这里定义帖子item,每个帖子中可能包括多张图片。

NoteItem:

class NoteItem {
  String? feedId;
  String? coverImageUrl;
  String? title;
  String? textContent;
  String? username;
  String? avatarUrl;
  String? time;
  String? likeNum;
  bool? liked;
  String? categoryName;
  List<NoteImage>? images;
}

class NoteImage {
  String? imageUrl;
  String? imageWidth;
  String? imageHeight;
}

定义每个帖子的Widget:NoteListItemWidget,NoteListItemWidget结构是用户头像,描述文本,帖子图片,点赞、评论、分享栏。

NoteListItemWidget:

import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_app_demolab/display/note_item.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:like_button/like_button.dart';

class NoteListItemWidget extends StatefulWidget {
  NoteListItemWidget({
    super.key,
    required this.noteItem,
  });

  final NoteItem noteItem;

  @override
  _NoteListItemWidgetState createState() => _NoteListItemWidgetState();
}

class _NoteListItemWidgetState extends State<NoteListItemWidget> {
  @override
  Widget build(BuildContext context) {
    return Container(
      margin: EdgeInsets.symmetric(vertical: 5.0, horizontal: 0.0),
      padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 15.0),
      //边框设置
      decoration: new BoxDecoration(
        //背景
        color: Color(0xFFFFFFFF),
        //设置四周圆角 角度 这里的角度应该为 父Container height 的一半
        borderRadius: BorderRadius.only(
            topLeft: Radius.circular(10.0), topRight: Radius.circular(10.0)),
        //设置四周边框
        border: new Border.all(width: 1, color: Color(0xFFf1f1f1)),
      ),
      child: Column(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          NoteListAuthorBar(
              username: widget.noteItem.username,
              avatarUrl: widget.noteItem.avatarUrl,
              showTime: widget.noteItem.time
          ),
          Text(
            widget.noteItem.textContent??"",
            maxLines: 5,
            overflow: TextOverflow.ellipsis,
            style: TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.normal,
              color: Color(0xFF666666),
            ),
          ),
          NoteListImage(images: (widget.noteItem.images != null
              ? widget.noteItem.images
              : [])),
          NoteListBottom(
              liked: widget.noteItem.liked,
              likeNum: widget.noteItem.likeNum,
              categoryName: widget.noteItem.categoryName
          ),
        ],
      ),
    );
  }
}

// 顶部header
class NoteListAuthorBar extends StatelessWidget {
  const NoteListAuthorBar({
    super.key,
    this.username,
    this.avatarUrl,
    this.showTime,
  });

  final String? username;
  final String? avatarUrl;
  final String? showTime;

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.only(bottom: 10),
      height: 60,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          CircleAvatar(
            backgroundImage: NetworkImage(avatarUrl ?? ""),
          ),
          Padding(
            padding: EdgeInsets.only(left: 10),
          ),
          Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Text(
                username ?? "",
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(
                  fontSize: 16,
                  fontWeight: FontWeight.bold,
                  color: Color(0xFF444444),
                ),
              ),
              Padding(
                padding: EdgeInsets.only(top: 5),
              ),
              Text(
                showTime ?? "",
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                style: TextStyle(
                  fontSize: 12,
                  fontWeight: FontWeight.normal,
                  color: Color(0xFF444444),
                ),
              ),
            ],
          )
        ],
      ),
    );
  }
}

// 显示图片
class NoteListImage extends StatelessWidget {
  const NoteListImage({
    super.key,
    this.images,
  });

  final List<NoteImage>? images;

  Widget layoutOfImages(List images, BuildContext context) {
    double screenWidth = MediaQuery
        .of(context)
        .size
        .width;
    double containW = screenWidth - 30;
    if (images.length == 1) {
      NoteImage noteImage = images[0];
      double imgWidth = 0;
      double imgHeight = 0;
      if (noteImage.imageWidth != null &&
          noteImage.imageWidth!.isNotEmpty &&
          noteImage.imageHeight != null &&
          noteImage.imageHeight!.isNotEmpty) {
        imgWidth = double.parse(noteImage.imageWidth!);
        imgHeight = double.parse(noteImage.imageHeight!);
        if (imgWidth > 0 && imgHeight > 0) {
          if (imgWidth < 1.0 && imgHeight < 1.0) {
            double maxLen = containW / 2.0;
            double showW = maxLen;
            double showH = maxLen;
            return Container(
              height: showH,
              width: showW,
              child: CachedNetworkImage(
                imageUrl: noteImage.imageUrl ?? "",
                placeholder: (context, url) =>
                    Center(child: CupertinoActivityIndicator()),
                errorWidget: (context, url, error) => Icon(Icons.error),
                fit: BoxFit.cover,
              ),
            );
          } else {
            double imageScale = imgWidth / imgHeight;
            double len1_3 = (containW - 20) / 3.0;
            double maxLen = len1_3 * 2 + 10;
            double showW = 0;
            double showH = 0;

            if (imageScale > 1.0) {
              // 横图
              showW = maxLen;
              showH = showW / imageScale;
            } else {
              // 竖图
              showH = maxLen;
              showW = imageScale * showH;
            }

            return Container(
              height: showH,
              width: showW,
              child: CachedNetworkImage(
                imageUrl: noteImage.imageUrl??"",
                placeholder: (context, url) =>
                    Center(child: CupertinoActivityIndicator()),
                errorWidget: (context, url, error) => Icon(Icons.error),
                fit: BoxFit.cover,
              ),
            );
          }
        }
      }
    } else if (images.length >= 2) {
      double showImageW = 0;
      if (images.length == 2 || images.length == 4) {
        showImageW = (containW - 10) / 2.0;
      } else {
        showImageW = (containW - 20) / 3.0;
      }
      List<Widget> imageWidgets = [];

      for (int index = 0; index < images.length; index++) {
        NoteImage noteImage = images[index];
        Widget widget = Container(
            height: showImageW,
            width: showImageW,
            child: CachedNetworkImage(
              imageUrl: noteImage.imageUrl??"",
              placeholder: (context, url) =>
                  Center(child: CupertinoActivityIndicator()),
              errorWidget: (context, url, error) => Icon(Icons.error),
              fit: BoxFit.cover,
            )
        );
        imageWidgets.add(widget);
      }

      return Container(
        alignment: Alignment.center,
        width: containW,
        child: Wrap(
          spacing: 8.0,
          // 主轴(水平)方向间距
          runSpacing: 8.0,
          // 纵轴(垂直)方向间距
          alignment: WrapAlignment.start,
          //沿主轴方向居中
          crossAxisAlignment: WrapCrossAlignment.center,
          children: imageWidgets,
        ),
      );
    }

    return Container();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: EdgeInsets.symmetric(vertical: 10, horizontal: 0),
      child: layoutOfImages(this.images??[], context),
    );
  }
}

// 显示底部
class NoteListBottom extends StatelessWidget {
  const NoteListBottom({
    super.key,
    this.liked,
    this.likeNum,
    this.categoryName,
  });

  final bool? liked;
  final String? likeNum;
  final String? categoryName;

  @override
  Widget build(BuildContext context) {
    return Container(
      height: 60,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        crossAxisAlignment: CrossAxisAlignment.center,
        children: [
          Container(
            padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0),
            //边框设置
            decoration: new BoxDecoration(
              //背景
              color: Color(0xFFFFFFFF),
              //设置四周圆角 角度 这里的角度应该为 父Container height 的一半
              borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(10.0),
                  topRight: Radius.circular(10.0)),
              //设置四周边框
              border: new Border.all(width: 1, color: Color(0xFFf1f1f1)),
            ),
            child: Text(
              this.categoryName??"",
              maxLines: 1,
              overflow: TextOverflow.ellipsis,
              style: TextStyle(
                fontSize: 12,
                fontWeight: FontWeight.normal,
                color: Color(0xFF444444),
              ),
            ),
          ),
          Container(
            padding: EdgeInsets.symmetric(vertical: 5.0, horizontal: 10.0),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.end,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: [
                LikeButton(
                  likeBuilder: (bool isLiked) {
                    return Icon(Icons.thumb_up,
                        color: (isLiked ? Colors.deepOrange : Color(
                            0xFFA9A9A9)));
                  },
                  likeCount: int.parse(this.likeNum??"0"),
                  isLiked: this.liked,
                ),
                TextButton(
                  child: Wrap(
                    // 可以通过设置两个基础组件的间距
                    alignment: WrapAlignment.center, //沿主轴方向居中
                    crossAxisAlignment: WrapCrossAlignment.center,
                    spacing: 5,
                    children: [
                      Icon(Icons.comment, color: Color(0xFFA9A9A9)),
                      Text("评论",
                          style:
                          TextStyle(fontSize: 12, color: Color(0xFF888888)))
                    ],
                  ),
                  onPressed: () {},
                ),
                TextButton(
                  child: Wrap(
                    // 可以通过设置两个基础组件的间距
                    alignment: WrapAlignment.center, //沿主轴方向居中
                    crossAxisAlignment: WrapCrossAlignment.center,
                    spacing: 5,
                    children: [
                      Icon(Icons.share, color: Color(0xFFA9A9A9)),
                      Text("分享",
                          style:
                          TextStyle(fontSize: 12, color: Color(0xFF888888)))
                    ],
                  ),
                  onPressed: () {},
                ),
              ],
            ),
          )
        ],
      ),
    );
  }
}

最后,我们在page中处理类似帖子列表DisplayPage

DisplayPage:

import 'dart:math';

import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/material.dart';
import 'package:flutter_app_demolab/display/note_item.dart';
import 'package:flutter_app_demolab/display/note_item_widget.dart';

class DisplayPage extends StatefulWidget {
  const DisplayPage({super.key});

  @override
  State<DisplayPage> createState() => _DisplayPageState();
}

class _DisplayPageState extends State<DisplayPage> {
  int _count = 10;
  late EasyRefreshController _controller;

  bool isLoading = false;
  ScrollController scrollController = ScrollController();
  List<NoteItem> list = [];

  List<NoteImage> noteImages = [];

  String randomBit() {
    String scopeF = '0123456789'; //首位
    String result = '';
    result = scopeF[Random().nextInt(scopeF.length)];
    return result;
  }

  @override
  void initState() {
    initItems();
    super.initState();
    _controller = EasyRefreshController(
      controlFinishRefresh: true,
      controlFinishLoad: true,
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  void initItems() {
    for (int index = 0; index < 12; index++) {
      NoteImage model = NoteImage();
      noteImages.add(model);

      if (index == 0) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202010/11/20201011085349_R2CjU.thumb.1000_0.jpeg";
        model.imageWidth = "580";
        model.imageHeight = "880";
      } else if (index == 1) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202011/15/20201115080806_7ddba.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "580";
      } else if (index == 2) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202011/15/20201115100436_7ffc6.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "560";
      } else if (index == 3) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/item/202006/09/20200609232715_yvqkd.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "380";
      } else if (index == 4) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202012/03/20201203144923_425bc.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "430";
      } else if (index == 5) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202010/11/20201011085351_3d2cS.thumb.1000_0.jpeg";
        model.imageWidth = "580";
        model.imageHeight = "850";
      } else if (index == 6) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202009/28/20200928184639_sxrum.thumb.1000_0.jpeg";
        model.imageWidth = "580";
        model.imageHeight = "810";
      } else if (index == 7) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202012/03/20201203145208_55d8c.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "430";
      } else if (index == 8) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/item/202006/09/20200609232715_pjsin.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "860";
      } else if (index == 9) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202012/03/20201203144929_cd406.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "450";
      } else if (index == 10) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202012/03/20201203144705_40405.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "450";
      } else if (index == 11) {
        model.imageUrl =
        "https://c-ssl.duitang.com/uploads/blog/202012/03/20201203144708_d5287.thumb.1000_0.jpg";
        model.imageWidth = "580";
        model.imageHeight = "750";
      }
    }

    for (int index = 0; index < 20; index++) {
      NoteItem model = NoteItem();
      model.liked = false;
      list.add(model);

      if (index % 5 == 0) {
        model.username = "可可";
        model.avatarUrl =
        "https://c-ssl.duitang.com/uploads/item/202007/22/20200722212206_ifscm.thumb.1000_0.jpg";
        model.time = "08/01 13:51";
        model.textContent = "等轮到你讲话时再说,认真聆听对方的谈话,千万别打断对方讲话,耐心等着,轮到你时再讲。";
        model.likeNum = "861380";
        model.categoryName = "兽圈";
      } else if (index % 5 == 2) {
        model.username = "琪琪";
        model.avatarUrl =
        "https://c-ssl.duitang.com/uploads/item/202007/22/20200722212209_insvm.thumb.1000_0.png";
        model.time = "08/01 13:51";
        model.textContent = "记得有活动,在活动现场等你哦~";
        model.likeNum = "50999";
        model.categoryName = "娃圈";
      } else if (index % 5 == 4) {
        model.username = "悦悦";
        model.avatarUrl =
        "https://c-ssl.duitang.com/uploads/item/202007/19/20200719102754_jrcoe.thumb.1000_0.jpg";
        model.time = "08/01 13:51";
        model.textContent = "说一说你想要的形象模型,晒一晒你的喜欢";
        model.likeNum = "6150";
        model.categoryName = "Pia戏圈";
      } else {
        model.username = "爱丽多啦";
        model.avatarUrl =
        "https://c-ssl.duitang.com/uploads/item/202007/19/20200719102756_ihfku.thumb.1000_0.jpg";
        model.time = "08/01 13:51";
        model.textContent = "你永远不懂我喜欢的东西,555";
        model.likeNum = "1507";
        model.categoryName = "语c圈";
      }

      List<NoteImage> tmpImages = [];
      String randomf = randomBit();
      String randomt = randomBit();
      int f = int.parse(randomf);
      int t = int.parse(randomt);
      print("f:$f");
      print("t:$t");

      if (f < t) {
        tmpImages.addAll(noteImages.sublist(f, t));
      } else if (f > t) {
        tmpImages.addAll(noteImages.sublist(t, f));
      } else {
        tmpImages.addAll(noteImages.sublist(t, t+1));
      }
      model.images = tmpImages;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('EasyRefresh'),
      ),
      body: EasyRefresh(
        controller: _controller,
        header: const ClassicHeader(),
        footer: const ClassicFooter(),
        onRefresh: () async {
          await Future.delayed(const Duration(seconds: 4));
          if (!mounted) {
            return;
          }
          list = [];
          initItems();
          setState(() {
            _count = 10;
          });
          _controller.finishRefresh();
          _controller.resetFooter();
        },
        onLoad: () async {
          await Future.delayed(const Duration(seconds: 4));
          if (!mounted) {
            return;
          }
          initItems();
          setState(() {
            _count += 5;
          });
          _controller.finishLoad(
              _count >= 200 ? IndicatorResult.noMore : IndicatorResult.success);
        },
        child: ListView.builder(
          itemBuilder: (context, index) {
            return NoteListItemWidget(noteItem: this.list[index]);
          },
          itemCount: this.list.length,
        ),
      ),
    );
  }
}

效果图如下:



三、小结

flutter开发实战-类似微博帖子列表及下拉刷新上拉加载效果

学习记录,每天不停进步。

相关推荐
好想有猫猫4 小时前
【51单片机】LCD1602液晶显示屏
c语言·单片机·嵌入式硬件·51单片机·1024程序员节
一头小火烧4 小时前
flutter打包签名问题
flutter
sunly_4 小时前
Flutter:异步多线程结合
flutter
AiFlutter4 小时前
Flutter网络通信-封装Dio
flutter
B.-4 小时前
Flutter 应用在真机上调试的流程
android·flutter·ios·xcode·android-studio
有趣的杰克4 小时前
Flutter【04】高性能表单架构设计
android·flutter·dart
网安_秋刀鱼5 小时前
PHP代码审计 - SQL注入
sql·web安全·网络安全·php·1024程序员节
yaosheng_VALVE12 小时前
稀硫酸介质中 V 型球阀的材质选择与选型要点-耀圣
运维·spring cloud·自动化·intellij-idea·材质·1024程序员节
网安_秋刀鱼15 小时前
java组件安全
web安全·网络安全·1024程序员节
earthzhang202115 小时前
《深入浅出HTTPS》读书笔记(7):安全的密码学Hash算法
网络·网络协议·http·https·1024程序员节