Flutter RichText支持图片显示和自定义图片效果

//size = 30.0/26.0 * fontSize
///final double size = 30.0;
///fontSize 26 and text height =30.0
//final double fontSize = 26.0;

double dpToFontSize(double dp) {
return dp / 30.0 * 26.0;
}

图片文字那么必然要有图片了,那么我们就提供个ImageProvider来装载图片,因为做过extended image,这部分不要太熟悉了,对image不了解的同学可以去看看 这个 全能的Image

当然我没有忘记给大家准备网络图片缓存的ImageProvider,以及清除它们的方法clearExtendedTextDiskCachedImages

CachedNetworkImage(this.url,
{this.scale = 1.0,
this.headers,
this.cache: false,
this.retries = 3,
this.timeLimit,
this.timeRetry = const Duration(milliseconds: 100)})
: assert(url != null),
assert(scale != null);

/// Clear the disk cache directory then return if it succeed.
/// timespan to compute whether file has expired or not
Future clearExtendedTextDiskCachedImages({Duration duration}) async

需要注意的是,因为ImageSpan没法获取到BuildContext,所以我们需要在Extended text build的时候,把ImageProvider 所需要的ImageConfiguration准备好

void _createImageConfiguration(List textSpan, BuildContext context) {
textSpan.forEach((ts) {
if (ts is ImageSpan) {
ts.createImageConfiguration(context);
} else if (ts.children != null) {
_createImageConfiguration(ts.children, context);
}
});
}

接下来就要到核心绘画文字的类里面去了ExtendedRenderParagraph 在Paint方法中,在画字之前我们来处理这个图片(反正文字是透明的,而且0的width,只是有个与前后文字的距离(图片的宽)),在绘画图片的时候,我把画布移动到offset的地方,就是整个文字开始绘画的点,方便后面计算的绘画

void paint(PaintingContext context, Offset offset) {
_paintSpecialText(context, offset);
_paint(context, offset);
}

void _paintSpecialText(PaintingContext context, Offset offset) {
final Canvas canvas = context.canvas;

canvas.save();
///move to extended text
canvas.translate(offset.dx, offset.dy);

///we have move the canvas, so rect top left should be (0,0)
final Rect rect = Offset(0.0, 0.0) & size;
_paintSpecialTextChildren([text], canvas, rect);
canvas.restore();
}

在_paintSpecialTextChildren中,循环找寻ImageSpan. 注意使用getOffsetForCaret方法,我们来判断这个TextSpan是否已经是文本溢出了。

Offset topLeftOffset = getOffsetForCaret(
TextPosition(offset: textOffset),
rect,
);
//skip invalid or overflow
if (topLeftOffset == null ||
(textOffset != 0 && topLeftOffset == Offset.zero)) {
return;
}

textOffset起始为0,当跳过一个TextSpan,我们加上该TextSpan的offset,然后继续查找

textOffset += ts.toPlainText().length;

如果是一个ImageSpan,首先因为这个\u200B 没有宽度,而宽度是我们设置的letterSpacing,所以这个图片绘画的地方应该要向前移动width / 2.0

if (ts is ImageSpan) {
///imageSpanTransparentPlaceholder \u200B has no width, and we define image width by
///use letterSpacing,so the actual top-left offset of image should be subtract letterSpacing(width)/2.0
Offset imageSpanOffset = topLeftOffset - Offset(ts.width / 2.0, 0.0);

if (!ts.paint(canvas, imageSpanOffset)) {
//image not ready
ts.resolveImage(
listener: (ImageInfo imageInfo, bool synchronousCall) {
if (synchronousCall)
ts.paint(canvas, imageSpanOffset);
else {
if (owner == null || !owner.debugDoingPaint) {
markNeedsPaint();
}
}
});
}
}

ImageSpan的paint方法,如果图片还没加载,那么我们需要resolveImage并且监听回调,在回调的时候,如果是一个同步的回调,那么这个时候Canvas应该不没有被dispose掉,那么我们就直接画上。否则判断owner,并且设置markNeedsPaint,让整个Text再次绘画。

上面就是怎么在文本中加入一个图片,然而产品可不是那么好对付的,产品说,那个图片给我加个圆角,加个Border,加个加载效果,给弄成圆形的,巴拉巴拉…说累了,你就直接按照下面的图来做吧。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

看到这样的需求,我的表情为

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

不过其实掌握了Canvas的一些技巧之后,这点事情难不倒我,加上2个回调,在绘画图片之前和之后,做你想要做的任何事情。

///you can paint your placeholder or clip
///any thing you want
final BeforePaintImage beforePaintImage;

///you can paint border,shadow etc
final AfterPaintImage afterPaintImage;

比如说在图片加载之后来个loading 占位,你可以这样做

ImageSpan(CachedNetworkImage(imageTestUrls.first), beforePaintImage:
(Canvas canvas, Rect rect, ImageSpan imageSpan) {
bool hasPlaceholder = drawPlaceholder(canvas, rect, imageSpan);
if (!hasPlaceholder) {
clearRect(rect, canvas);
}
return false;
},

画个背景,画个字,so easy

bool drawPlaceholder(Canvas canvas, Rect rect, ImageSpan imageSpan) {
bool hasPlaceholder = imageSpan.imageSpanResolver.imageInfo?.image == null;

if (hasPlaceholder) {
canvas.drawRect(rect, Paint()…color = Colors.grey);
var textPainter = TextPainter(
text: TextSpan(text: “loading”, style: TextStyle(fontSize: 10.0)),
textAlign: TextAlign.center,
textScaleFactor: 1,
textDirection: TextDirection.ltr,
maxLines: 1)
…layout(maxWidth: rect.width);

textPainter.paint(
canvas,
Offset(rect.left + (rect.width - textPainter.width) / 2.0,
rect.top + (rect.height - textPainter.height) / 2.0));
}
return hasPlaceholder;
}

void clearRect(Rect rect, Canvas canvas) {
///if don’t save layer
///BlendMode.clear will show black
///maybe this is bug for blendMode.c
lear
canvas.saveLayer(rect, Paint());
canvas.drawRect(rect, Paint()…blendMode = BlendMode.clear);
canvas.restore();

最后

小编这些年深知大多数初中级Android工程师,想要提升自己,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。

[外链图片转存中…(img-YWdX6rzp-1719052907338)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取
望能够帮助到想自学提升又不知道该从何学起的朋友。**

[外链图片转存中…(img-YWdX6rzp-1719052907338)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人

都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

资料⬅专栏获取

  • 28
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值