IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Flutter使用Draggable与自定义RenderObject实现图片添加标签,随意拖动位置效果 -> 正文阅读

[移动开发]Flutter使用Draggable与自定义RenderObject实现图片添加标签,随意拖动位置效果

本篇文章这里主要是讲一下整个功能的一个实现思路和使用到的技术点,更加详细的还请参阅源码

一、实现的效果图如下


二、实现的功能与使用到技术点

  • 功能
    • 标签拖动的时候显为一个圆点
    • 标签只能在图片显示的范围内拖动
    • 标签可以拖动到指定位置删除
    • 标签拖动到左边或者右边,根据剩余宽度自动改变标签布局方向
  • 技术点
    • Draggable拖动组件
    • 自定义RenderObjectRenderBox 参与组件绘制、摆放流程
    • 图片使用BoxFit后,计算在容器内的实际位置

三、图片真实大小、在屏幕上的位置获取

  • 对效果图进行分析,标注利于描述
  • 组件的拖动效果实现
Widget label = LabelWidget();
Draggable(
  ///需要拖动的组件
  child: label,
  ///当拖动时,显示的组件
  feedback: Material(color: Colors.transparent, child: _feedbackWidget()),
  ///当拖动时,原来位置显示的组件
  childWhenDragging: Offstage(child: label),
  ///拖动位置更新回调
  onDragUpdate:(detail){}
  ///拖动结束位置回调
  onDragEnd: (detail){}
),

1、由于Draggable组件在拖动的时候,是可以在整个屏幕上进行拖动;所以在回调里拿到的Offset位置是相对于屏幕左上角的。
2、所以要判断拖动后的位置是否处于图片内,需要知道图片矩形在整个屏幕的位置信息;然后利用Rect的contains()函数判断点是否在矩形内即可。

1、 计算图片矩形在屏幕上的位置,由上图可知;我们需要先知道Stack在屏幕上的位置与大小:

  • 获取 Stack在屏幕上的位置与大小,这个比较简单给stack设置个key,然后在第一帧绘制完进行获取
@override
void initState() {
  super.initState();
  WidgetsBinding.instance!.addPostFrameCallback((callback) {
    containerSize = _getWidgetSize(_stackKey);
    containerOffset = _location(_stackKey);
  });
}

///获取组件的大小
Size _getWidgetSize(GlobalKey key) {
  return key.currentContext!.size!;
}
///获取组件在屏幕上的位置
Offset _location(GlobalKey key) {
  RenderBox? renderBox = key.currentContext!.findRenderObject() as RenderBox?;
  return renderBox!.localToGlobal(Offset.zero);
}
  • 通过上面操作:就已经获取到了上图标注的A点坐标了Offset A = containerOffset;
  • 那怎么获取图片的大小位置呢?

流程:获取图片大小 ——> 计算图片的宽高、高宽比 ——> 根据Stack容器大小计算图片真实显示大小 ——> 根据图片真实大小计算在Stack内的位置

2、 获取图片大小,这里使用到了extended_image库

ExtendedImage.network(
  '图片地址',
  fit: BoxFit.contain,
  loadStateChanged: (ExtendedImageState state) {
    if (state.extendedImageLoadState == LoadState.completed) {
       /// 加载成功如下代码就可以拿到图片高宽了
       state.extendedImageInfo?.image;
    }
  },
),
  • 由于上面使用到了图片缩放模式BoxFit.contain,所以图片会根据给定的容器大小来显示图片;那么要怎么计算呢?这个答案就要从源码里找到答案了
    • 代码位于flutterSDk/packages/flutter/lib/src/painting/box_fit.dart文件中的applyBoxFit函数
      在这里插入图片描述
    • inputSize指的就是图片大小,outputSize指的就是给定的容器大小里,有了计算公式就简单了

3、计算图片在给定容器内的真实大小

///计算图片的真实大小
///[image] 图片模式
///[BoxFit.contain] 计算模式
Size _calcImgSize(ui.Image? image) {
  Size result = Size.zero;
  double imageAspectRatio = image.width.toDouble() / image.height.toDouble();
  double containerRatio = containerSize!.width / containerSize!.height;
  if (containerRatio > imageAspectRatio) {
    result =
        Size(imageAspectRatio * containerSize!.height, containerSize!.height);
  } else {
    result = Size(containerSize!.width, image.height.toDouble() * containerSize!.width /
            image.width.toDouble());
  }
  return result;
}

3、计算图片在容器内的位置,也就是上图B点的位置;有了B点的位置也就能够得到图片实际位置在屏幕上的矩形了

图片是在给定的Stack中居中的,有了Stack和图片大小就可以计算到图片的起点坐标(imgStartOffset)

Size realImgSize = _calcImgSize(image);
double imgOffsetX = (containerSize!.width - realImgSize.width) / 2;
double imgOffsetY = (containerSize!.height - realImgSize.height) / 2;
///这个就是B点的位置坐标(相对于A点位原点)
Offset imgStartOffset = Offset(imgOffsetX, imgOffsetY);
///计算图片左上角在屏幕上的位置
Offset imgOffset = containerOffset + imgStartOffset;
///图片矩形在屏幕上的位置
Rect rect = imgOffset & realImgSize;

三、标签拖动效果实现

  • 开始拖动的时候需要隐藏标签的文字部分,在拖动完成的时候在显示出来
  • 标签只能拖动圆点,文字部分是不能拖动的
  • 拖动结束的时候根据位置和剩余宽度决定文字居左还是居右

1、这里重点讲一下标签这个组件,自定义RenderObject达到只能拖动圆点部分

在这里插入图片描述

从上图可以看出,Draggable组件直接大小就只有红色部分,文字部分不占据任何大小;这样除了红色部分可以响应触摸事件,其它部分都是无法响应的。

  • 标签组件结构如下
class LabelWidget extends StatefulWidget {

  @override
  State<StatefulWidget> createState() {
    return LabelWidgetState();
  }
}

class LabelWidgetState extends State<LabelWidget> {
 
  @override
  Widget build(BuildContext context) {
  	///省略部分代码....
    return ShopGoodsLabelRenderObjectWidget();
  }
  ///省略部分代码....
}

class ShopGoodsLabelRenderObjectWidget extends SingleChildRenderObjectWidget {

  @override
  RenderObject createRenderObject(BuildContext context) {
    return ShopGoodsLabelBox();
  }
  ///省略部分代码....
}

class ShopGoodsLabelBox extends RenderProxyBox with RenderProxyBoxMixin {

  ///省略部分代码....
  
  @override
  void performLayout() {
    super.performLayout();
    realSize = size;
    ///回调原本真实大小
    sizeCallback?.call(realSize);
    ///修改RenderObject的大小
    size = circleSize;
  }

  @override
  void paint(PaintingContext context, Offset offset) {
    ///绘制元素
    if (child != null) context.paintChild(child!, offset);
  }
}
  • 重写performLayout()修改size的大小为上面红色矩形的大小就可以实现了,同时将真实大小通知出去;在拖动结束的时候判断标签文字部分是显示在左边还是右边需要使用到。
  • 重写paint()绘制内容,当标签文字在左边显示的时候需要自己计算offset的值进行绘制

2、标签拖动位置计算,拖动删除处理

  • 前面一开始已经将图片矩形在屏幕上的位置计算出来了,那只要在拖动结束的时候判断位置是否在矩形内即可,如下:
///伪代码
imgRect.contains(offset);
  • 同理,标签删除也只需要知道删除矩形在屏幕上的位置就可以了

四、对添加好的标签进行展示

  • 展示的逻辑和添加的逻辑基本一致
    • 知道图片的宽高比、高宽比,用于计算图片的真实位置
    • 计算标签点(x,y)在图片上所占的比例;然后用比例值和显示时图片的大小计算标签显示的位置
  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-27 11:58:42  更:2021-08-27 12:00:15 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:41:37-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码