Android Flutter自适应瀑布流案例详解

目录
  • Flutter自适应瀑布流
  • 根据效果图可以分为四步:
    • 1.图片自适应:
    • 2.自适应标签:
    • 3.上拉刷新和下拉加载
    • 4.底部的点赞按钮

Flutter自适应瀑布流

前言:在电商app经常会看到首页商品推荐的瀑布流,或者类似短视频app首页也是瀑布流,这些都是需要自适应的,才能给用户带来好的体验

(具体代码请联系我,当天会回复)

话不多说先上效果图:

根据效果图可以分为四步:

  1. 图片自适应
  2. 自适应标签
  3. 上拉刷新和下拉加载
  4. 底部的点赞按钮可以去掉或者自己修改样式,我这里使用的like_button库

注:本文使用的库:为啥这么多呢,因为我把图片缓存这样东西都加上了,单纯的瀑布流就用waterfall_flow

waterfall_flow: ^3.0.1
extended_image: any
extended_sliver: any
ff_annotation_route_library: any
http_client_helper: any
intl: any
like_button: any
loading_more_list: any
pull_to_refresh_notification: any
url_launcher: any

1.图片自适应:

Widget image = Stack(
  children: <Widget>[
    ExtendedImage.network(
      item.imageUrl,
      shape: BoxShape.rectangle,
      //clearMemoryCacheWhenDispose: true,
      border: Border.all(color: Colors.grey.withOpacity(0.4), width: 1.0),
      borderRadius: const BorderRadius.all(
        Radius.circular(10.0),
      ),
      loadStateChanged: (ExtendedImageState value) {
        if (value.extendedImageLoadState == LoadState.loading) {
          Widget loadingWidget = Container(
            alignment: Alignment.center,
            color: Colors.grey.withOpacity(0.8),
            child: CircularProgressIndicator(
              strokeWidth: 2.0,
              valueColor:
                  AlwaysStoppedAnimation<Color>(Theme.of(c).primaryColor),
            ),
          );
          if (!konwSized) {
            //todo: not work in web
            loadingWidget = AspectRatio(
              aspectRatio: 1.0,
              child: loadingWidget,
            );
          }
          return loadingWidget;
        } else if (value.extendedImageLoadState == LoadState.completed) {
          item.imageRawSize = Size(
              value.extendedImageInfo.image.width.toDouble(),
              value.extendedImageInfo.image.height.toDouble());
        }
        return null;
      },
    ),
    Positioned(
      top: 5.0,
      right: 5.0,
      child: Container(
        padding: const EdgeInsets.all(3.0),
        decoration: BoxDecoration(
          color: Colors.grey.withOpacity(0.6),
          border: Border.all(color: Colors.grey.withOpacity(0.4), width: 1.0),
          borderRadius: const BorderRadius.all(
            Radius.circular(5.0),
          ),
        ),
        child: Text(
          '${index + 1}',
          textAlign: TextAlign.center,
          style: const TextStyle(fontSize: fontSize, color: Colors.white),
        ),
      ),
    )
  ],
);
if (konwSized) {
    image = AspectRatio(
      aspectRatio: item.imageSize.width / item.imageSize.height,
      child: image,
    );
  } else if (item.imageRawSize != null) {
    image = AspectRatio(
      aspectRatio: item.imageRawSize.width / item.imageRawSize.height,
      child: image,
    );
  }
 return Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: <Widget>[
      image,
      const SizedBox(
        height: 5.0,
      ),
      buildTagsWidget(item),
      const SizedBox(
        height: 5.0,
      ),
      buildBottomWidget(item),
    ],
  );
}

2.自适应标签:

Widget buildTagsWidget(
  TuChongItem item, {
  int maxNum = 6,
}) {
  const double fontSize = 12.0;
  return Wrap(
      runSpacing: 5.0,
      spacing: 5.0,
      children: item.tags.take(maxNum).map<Widget>((String tag) {
        final Color color = item.tagColors[item.tags.indexOf(tag)];
        return Container(
          padding: const EdgeInsets.all(3.0),
          decoration: BoxDecoration(
            color: color,
            border: Border.all(color: Colors.grey.withOpacity(0.4), width: 1.0),
            borderRadius: const BorderRadius.all(
              Radius.circular(5.0),
            ),
          ),
          child: Text(
            tag,
            textAlign: TextAlign.start,
            style: TextStyle(
                fontSize: fontSize,
                color: color.computeLuminance() < 0.5
                    ? Colors.white
                    : Colors.black),
          ),
        );
      }).toList());
}

3.上拉刷新和下拉加载

class PullToRefreshHeader extends StatelessWidget {
  const PullToRefreshHeader(this.info, this.lastRefreshTime, {this.color});
  final PullToRefreshScrollNotificationInfo info;
  final DateTime lastRefreshTime;
  final Color color;
  @override
  Widget build(BuildContext context) {
    if (info == null) {
      return Container();
    }
    String text = '';
    if (info.mode == RefreshIndicatorMode.armed) {
      text = 'Release to refresh';
    } else if (info.mode == RefreshIndicatorMode.refresh ||
        info.mode == RefreshIndicatorMode.snap) {
      text = 'Loading...';
    } else if (info.mode == RefreshIndicatorMode.done) {
      text = 'Refresh completed.';
    } else if (info.mode == RefreshIndicatorMode.drag) {
      text = 'Pull to refresh';
    } else if (info.mode == RefreshIndicatorMode.canceled) {
      text = 'Cancel refresh';
    }

    final TextStyle ts = const TextStyle(
      color: Colors.grey,
    ).copyWith(fontSize: 13);

    final double dragOffset = info?.dragOffset ?? 0.0;

    final DateTime time = lastRefreshTime ?? DateTime.now();
    final double top = -hideHeight + dragOffset;
    return Container(
      height: dragOffset,
      color: color ?? Colors.transparent,
      //padding: EdgeInsets.only(top: dragOffset / 3),
      //padding: EdgeInsets.only(bottom: 5.0),
      child: Stack(
        children: <Widget>[
          Positioned(
            left: 0.0,
            right: 0.0,
            top: top,
            child: Row(
              mainAxisAlignment: MainAxisAlignment.center,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Expanded(
                  child: Container(
                    alignment: Alignment.centerRight,
                    child: RefreshImage(top),
                    margin: const EdgeInsets.only(right: 12.0),
                  ),
                ),
                Column(
                  children: <Widget>[
                    Text(
                      text,
                      style: ts,
                    ),
                    Text(
                      'Last updated:' +
                          DateFormat('yyyy-MM-dd hh:mm').format(time),
                      style: ts.copyWith(fontSize: 12),
                    )
                  ],
                ),
                Expanded(
                  child: Container(),
                ),
              ],
            ),
          )
        ],
      ),
    );
  }
}

class RefreshImage extends StatelessWidget {
  const RefreshImage(this.top);
  final double top;
  @override
  Widget build(BuildContext context) {
    const double imageSize = 40;
    return ExtendedImage.asset(
      Assets.assets_fluttercandies_grey_png,
      width: imageSize,
      height: imageSize,
      afterPaintImage: (Canvas canvas, Rect rect, ui.Image image, Paint paint) {
        final double imageHeight = image.height.toDouble();
        final double imageWidth = image.width.toDouble();
        final Size size = rect.size;
        final double y = (1 - min(top / (refreshHeight - hideHeight), 1)) *
            imageHeight;

        canvas.drawImageRect(
            image,
            Rect.fromLTWH(0.0, y, imageWidth, imageHeight - y),
            Rect.fromLTWH(rect.left, rect.top + y / imageHeight * size.height,
                size.width, (imageHeight - y) / imageHeight * size.height),
            Paint()
              ..colorFilter =
                  const ColorFilter.mode(Color(0xFFea5504), BlendMode.srcIn)
              ..isAntiAlias = false
              ..filterQuality = FilterQuality.low);

        //canvas.restore();
      },
    );
  }
}

4.底部的点赞按钮

LikeButton(
  size: 18.0,
  isLiked: item.isFavorite,
  likeCount: item.favorites,
  countBuilder: (int count, bool isLiked, String text) {
    final ColorSwatch<int> color =
        isLiked ? Colors.pinkAccent : Colors.grey;
    Widget result;
    if (count == 0) {
      result = Text(
        'love',
        style: TextStyle(color: color, fontSize: fontSize),
      );
    } else {
      result = Text(
        count >= 1000 ? (count / 1000.0).toStringAsFixed(1) + 'k' : text,
        style: TextStyle(color: color, fontSize: fontSize),
      );
    }
    return result;
  },
  likeCountAnimationType: item.favorites < 1000
      ? LikeCountAnimationType.part
      : LikeCountAnimationType.none,
  onTap: (bool isLiked) {
    return onLikeButtonTap(isLiked, item);
  },
)

这样自适应的瀑布流就完成了。

到此这篇关于Android Flutter自适应瀑布流案例详解的文章就介绍到这了,更多相关Android Flutter自适应瀑布流内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2021-09-11

Android如何在原生App中嵌入Flutter

本文参考文档Add Flutter to existing apps. 首先有一个可以运行的原生项目 第一步:新建Flutter module Terminal进入到项目根目录,执行flutter create -t module 'module名字'例如:flutter create -t module flutter-native 执行完毕,就会发现项目目录下生成了一个module 第二步:同步Flutter module依赖 进入到新生成的Flutter module目录下的.androi

详解Flutter 调用 Android Native 的方法

Flutter 调用 Android Native 的方法,是通过MethodChannel的方式来实现的: 在Android端: 创建一个Class,实现FlutterPlugin和MethodCallHandler接口 重写onAttachedToEngine(),onDetachedFromEngine(),onMethodCall() onAttachedToEngine()中,根据自定义的CHANNEL_NAME创建MethodChannel, onDetachedFromEngine

Flutter 和 Android 互相传递数据的实现

(一)Android代码设置 1,打开Android Studio 创建一个应用程序,包名dev.android.book 2, 创建一个MyApplication ,应用在AndroidManifest.xml文件中的application的android:name属性上 3,创建FlutterEngine的实例,然后把这个实例添加到缓存的FlutterEngine当中 4,创建MethodChannel的实例,指定一个此实例的唯一字符串,例如dev.android.book/add 5, 设

Android 集成Flutter

目录 Android 集成Flutter 1, Hello Flutter 2, 引入 Flutter 模块 3,使用Flutter 3.1 添加依赖 3.2 运行Flutter页面 3.2.1 添加Flutter页面 4,Flutter APK 解析 5,踩过的坑 Android 集成Flutter Flutter 作为 Google 开源的新一代跨平台.高性能 UI 框架,旨在帮助开发者高效地构建出跨平台的.UI 与交互体验一致的精美应用,推出后一直倍受开发者的青睐. 当需要开发一个全新的应

Flutter中嵌入Android 原生TextView实例教程

前言 本篇文章 中写到的是 flutter 调用了Android 原生的 TextView 案例 添加原生组件的流程基本上可以描述为: 1 android 端实现原生组件PlatformView提供原生view 2 android 端创建PlatformViewFactory用于生成PlatformView 3 android 端创建FlutterPlugin用于注册原生组件 4 flutter 平台嵌入 原生view 1 创建原生组件 创建在fLutter工程时会生成几个文件夹,lib是放fl

Android原生项目集成Flutter解决方案

了解一下如何在 Android 原生项目中集成 Flutter 生成配置 在原生项目根目录执行命令 flutter create -t module --org {package_name} {module_name} // 此处 module_name 的命令遵循 Android 子 module 的命名即可.不能有中划线. // 比如, flutter create -t module --org com.engineer.mini.flutter flutter_sub // 此处 mod

Android原生项目集成React Native的方法

开发环境准备 首先按照开发环境搭建教程来安装React Native在安卓平台上所需的一切依赖软件(比如npm). 在应用中添加JS代码 在项目的根目录中运行: $ npm init $ npm install --save react react-native $ curl -o .flowconfig https://raw.githubusercontent.com/facebook/react-native/master/.flowconfig npm init创建了一个空的node模块

Android插件化-RePlugin项目集成与使用详解

前言:前一段时间新开源了一种全面插件化的方案-- RePlugin,之前一种都在关注 DroidPlugin 并且很早也在项目中试用了,但最终没有投入到真正的生产环节,一方面是项目中没有特别需要插件化的需求,另一方面也考虑到 DroidPlugin 不是特别稳定,Android系统每更新一次 DroidPlugin 可能就会出现一些 Bug,毕竟 Hook 了 Android 原生的太多东西,系统一旦更新引发 Bug 是在所难免的.当然,这些并不能否认 DroidPlugin 的优秀,它的原理和

Android原生嵌入React Native详解

1.首先集成的项目目录 我使用的是直接按照react-native init Project 的格式来导入的,也就是说,我的Android项目目录是跟node_modules是在一个目录下的. 我们init完项目之后,项目初始化完成了,这时候我们可以用命令react-native run-android直接运行项目,至于怎么调试,之前已经说过. 说一下我们怎么开发和运行分开吧,我们开发一般会选择webstrom,开发后我们会Android和ios的编译分开. 启动npm 下面说一下android

Android编程之内存溢出解决方案(OOM)实例总结

本文实例总结了Android编程之内存溢出解决方案(OOM).分享给大家供大家参考,具体如下: 在最近做的工程中发现加载的图片太多或图片过大时经常出现OOM问题,找网上资料也提供了很多方法,但自己感觉有点乱,特此,今天在不同型号的三款安卓手机上做了测试,因为有效果也有结果,今天小马就做个详细的总结,以供朋友们共同交流学习,也供自己以后在解决OOM问题上有所提高,提前讲下,片幅有点长,涉及的东西太多,大家耐心看,肯定有收获的,里面的很多东西小马也是学习参考网络资料使用的,先来简单讲下下: 一般我们

React-native桥接Android原生开发详解

在开发RN的漫漫长河中,早晚有那么一天要接触到安卓的原生开发,笔者来介绍一下其中的酸甜苦辣.对于一个不懂android的小白来说,刚开始有点难,不过都是万事开头难.语言是想通的,原理也是大径若一. 开发过程中是要集成高德的导航功能,没有找到好的轮子的,只要写原生代码,然后在用JS去调用原生的导航模块. 首先注册模块 其意义在与将类注册到RN中,才能用JS去调用 public class AnExampleReactPackage implements ReactPackage { @Overri

如何在Android App中集成支付宝和微信支付功能

前言 本文主要介绍如何在 Android App 里集成支付宝和微信支付的功能,文中将实现的步骤一步步介绍的非常详细,对同样遇到这个问题的朋友相信会是一个很好的参考,下面话不多说了,来一起看看详细的介绍吧. 集成支付宝支付 没想到现在 App 里集成支付宝是这么的简单,我还折腾了好久- 好了,开始,假设你已经完成了支付宝那些繁杂的申请啥的工作,进入开发了. 首先,去下载官方的 DEMO : App支付客户端DEMO&SDK. 导入开发资源 解压后把里面的 jar 包拿出来放到你工程的 lib 目

Javaweb项目session超时解决方案

在Java Web开发中,Session为我们提供了很多方便,Session是由浏览器和服务器之间维护的.Session超时理解为:浏览器和服务器之间创建了一个Session,由于客户端长时间(休眠时间)没有与服务器交互,服务器将此Session销毁,客户端再一次与服务器交互时之前的Session就不存在了. 0.需求 需要对所有的/web/**请求进行登录拦截,Session超时时跳转到登录页面. 1.引入 一般来说,在项目使用中都会配置Session超时时间,如果不配置,则默认值为30分钟,

Android Studio项目中导入开源库的方法

前两天,谷歌发布了Android Studio 1.0的正式版,也有更多的人开始迁移到Android Studio进行开发.然而,网上很多的开源库,控件等还是以前的基于Eclipse进行开发,很多人不知道怎么导入到自己的基于Android Studio项目中来,微博上也有人私信我,让我来写写,正好今天回来的比较早,就写写吧.主要介绍一下常见的一些导包的场景. 前言 复制代码 代码如下: --project   //项目目录   |   build.gradle  //项目的gradle配置文件

详解Android原生json和fastjson的简单使用

android原生操作json数据 主要是两个类 JSONObject 操作对象     JONSArray操作json数组 对象转json //创建学生对象 Student student=new Student(); student.setAge(23); student.setClazz("六年级"); student.setName("王二麻子"); //创建JSONObject JSONObject jsonObject=new JSONObject();