????小菜在學(xué)習(xí) Flutter 過(guò)程中,有特別需求是對(duì)于文本過(guò)長(zhǎng)的內(nèi)容需要展示固定行數(shù)歼跟,而在文本右下角有提示用戶點(diǎn)擊展開(kāi)和收起和媳;小菜嘗試自定義一個(gè)可折疊收縮的 ACEFoldTextView;
ACEFoldTextView
????小菜首先簡(jiǎn)單梳理了一下設(shè)計(jì)流程哈街,如下圖所示留瞳;
- 當(dāng)文本內(nèi)容所占據(jù)行數(shù)小于等于限制的最大行數(shù)時(shí),默認(rèn)展示整個(gè)文本內(nèi)容叹卷,不會(huì)有【展開(kāi)/收起】撼港;
- 當(dāng)文本內(nèi)容所占據(jù)行數(shù)大于限制的最大行數(shù)時(shí),默認(rèn)展示最大行數(shù)內(nèi)容骤竹,并在右下角顯示【展開(kāi)】提示;
- 點(diǎn)擊【展開(kāi)】區(qū)域時(shí)往毡,當(dāng)文本內(nèi)容最后一行內(nèi)容與【展開(kāi)】區(qū)域占據(jù)內(nèi)容寬度之和小于最大寬度時(shí)蒙揣,默認(rèn)展示【收起】;
-
點(diǎn)擊【展開(kāi)】區(qū)域時(shí)开瞭,當(dāng)文本內(nèi)容最后一行內(nèi)容與【展開(kāi)】區(qū)域占據(jù)內(nèi)容寬度之和大于等于最大寬度時(shí)懒震,【收起】區(qū)域換行展示;
1. 透明漸變【展開(kāi)/收起】
????小菜整體通過(guò) Stack 層級(jí)嵌套方式在右下角顯示可點(diǎn)擊的【展開(kāi)/收起】文本區(qū)嗤详,為了提高顯示效果个扰,并防止完全遮擋內(nèi)容文本,小菜嘗試了兩種方式來(lái)實(shí)現(xiàn)顏色透明度漸變葱色;
1.1 ShaderMask 著色器
????小菜之前有重點(diǎn)介紹過(guò) ShaderMask 著色器递宅,可以對(duì)子 Widget 進(jìn)行顏色處理,包括遮罩層特效展示苍狰;小菜設(shè)置了一個(gè) LinearGradient 線性漸變办龄,但 ShaderMask 是對(duì)整個(gè)子 Widget 遮罩層生效,可能會(huì)影響 Text 文本顯示效果淋昭,需要 Stack 層級(jí)使用俐填;
_transparentWid02() => ShaderMask(
shaderCallback: (bounds) => LinearGradient(
colors: [_bgColor.withOpacity(0.0), _bgColor],
).createShader(bounds),
child: Container(
alignment: Alignment.centerRight,
color: Colors.white,
width: _kMoreWidth,
child: Text((_temLines > _maxLines) ? '展開(kāi)' : '收起',
style: TextStyle(color: Theme.of(context).accentColor, fontSize: widget.textStyle?.fontSize ?? 14.0))));
1.2 Container BoxDecoration
????第二種就是常用的 Container 配合設(shè)置 BoxDecoration 設(shè)置線性漸變色;該方式使用更為便捷翔忽;
_transparentWid01() => Container(
alignment: Alignment.centerRight,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [_bgColor.withOpacity(0.0), _bgColor],
end: FractionalOffset(0.5, 0.5))),
width: _kMoreWidth,
child: Text((_temLines > _maxLines) ? '展開(kāi)' : '收起',
style: TextStyle(color: Theme.of(context).accentColor, fontSize: widget.textStyle?.fontSize ?? 14.0)));
2. Text 文本內(nèi)容折疊
????小菜想實(shí)現(xiàn)文本折疊英融,首先需要預(yù)先得知 Text 文本在范圍內(nèi)占據(jù)的行數(shù),一般都需要通過(guò) TextPainter 等方式獲刃健驶悟;小菜嘗試了兩種方式進(jìn)行判斷;
2.1 TextPainter.didExceedMaxLines
????小菜之前也有簡(jiǎn)單了解過(guò) TextPainter 與 TextSpan 的應(yīng)用贬丛,主要用于文本的繪制撩银,當(dāng)設(shè)置 maxLines 之后,可以通過(guò) didExceedMaxLines 判斷文本內(nèi)容是否已經(jīng)超行豺憔;小菜之后會(huì)對(duì) TextPainter 再深入研究一下额获;
_checkOverMaxLines01(maxLines, maxWidth) {
final textSpan = TextSpan(text: _textStr, style: widget.textStyle);
final textPainter = TextPainter(text: textSpan, textDirection: TextDirection.ltr, maxLines: maxLines);
textPainter.layout(maxWidth: widget.maxWidth ?? MediaQuery.of(context).size.width);
return textPainter.didExceedMaxLines;
}
2.2 LineMetrics
????didExceedMaxLines 可以直接獲取文本內(nèi)容是否超行够庙,但無(wú)法獲取每行文本信息等;于是小菜嘗試了 computeLineMetrics() 方式獲取 LineMetrics 基線度量抄邀;可以獲取每行內(nèi)容所占據(jù)的寬高等耘眨;
????當(dāng)然 LineMetrics 也無(wú)法獲取每行文本內(nèi)容,以及在兩種文本對(duì)齊方式共用時(shí)有注意事項(xiàng)境肾,小菜之后會(huì)進(jìn)一步研究剔难;
????Tips: 在使用 computeLineMetrics() 獲取 LineMetrics 信息時(shí),需要注意 TextPainter 必須設(shè)置好 textDirection 文本對(duì)齊方式奥喻,以及在 layout 布局之后才可以獲扰脊;
_checkOverMaxLines02(maxWidth) {
final textSpan = TextSpan(text: _textStr, style: widget.textStyle);
final textPainter = TextPainter(text: textSpan, textDirection: TextDirection.ltr);
textPainter.layout(maxWidth: widget.maxWidth ?? MediaQuery.of(context).size.width);
_lines = textPainter.computeLineMetrics();
return _lines;
}
3. ACEFoldTextView
????有了前面兩步的基礎(chǔ)环鲤,小菜將其結(jié)合起來(lái)纯趋,生成自定義 ACEFoldTextView;通過(guò) LinearBuilder 約束子 Text 延遲加載冷离;通過(guò) LineMetrics 獲取最后一行文本長(zhǎng)度吵冒,與默認(rèn)【展開(kāi)】所在 Widget 計(jì)算總和,之后判斷是否占據(jù)超過(guò)限制最大寬度西剥;當(dāng)超過(guò)最大寬度時(shí)痹栖,小菜將文本添加一個(gè) \n 強(qiáng)制換行;
return LayoutBuilder(builder: (context, size) {
_isOverFlow = _checkOverMaxLines01(_maxLines, widget.maxWidth);
_temLines = _checkOverMaxLines02(widget.maxWidth)?.length;
return (_temLines <= _maxLines)
? _itemText() : Stack(children: <Widget>[_itemText(), _moreText()]);
});
_moreText() => Positioned(
bottom: 0, right: 0,
child: GestureDetector(
child: _transparentWid02(),
onTap: () => setState(() {
if (_temLines > _maxLines) {
if (_lines.last.width + _kMoreWidth >= widget.maxWidth) {
_maxLines = _temLines + 1;
_textStr = '${widget.text}\n';
} else {
_maxLines = _temLines;
}
} else if (_temLines == _maxLines) {
_maxLines = widget.maxLines;
}
})));
????小菜對(duì) ACEFoldTextView 的繪制到此為止瞭空,其中涉及到 TextPainter 內(nèi)容較淺顯揪阿,小菜之后會(huì)進(jìn)一步學(xué)習(xí)研究;如有錯(cuò)誤匙铡,請(qǐng)多多指導(dǎo)图甜!
來(lái)源: 阿策小和尚