Flutter 137: 圖解自定義 ACEFoldTextView 折疊文本

????小菜在學習 Flutter 過程中,有特別需求是對于文本過長的內容需要展示固定行數,而在文本右下角有提示用戶點擊展開和收起;小菜嘗試自定義一個可折疊收縮的 ACEFoldTextView

ACEFoldTextView

????小菜首先簡單梳理了一下設計流程,如下圖所示;

  • 當文本內容所占據行數小于等于限制的最大行數時,默認展示整個文本內容,不會有【展開/收起】;
  • 當文本內容所占據行數大于限制的最大行數時,默認展示最大行數內容,并在右下角顯示【展開】提示;
  • 點擊【展開】區域時,當文本內容最后一行內容與【展開】區域占據內容寬度之和小于最大寬度時,默認展示【收起】;
  • 點擊【展開】區域時,當文本內容最后一行內容與【展開】區域占據內容寬度之和大于等于最大寬度時,【收起】區域換行展示;


1. 透明漸變【展開/收起】

????小菜整體通過 Stack 層級嵌套方式在右下角顯示可點擊的【展開/收起】文本區,為了提高顯示效果,并防止完全遮擋內容文本,小菜嘗試了兩種方式來實現顏色透明度漸變;

1.1 ShaderMask 著色器

????小菜之前有重點介紹過 ShaderMask 著色器,可以對子 Widget 進行顏色處理,包括遮罩層特效展示;小菜設置了一個 LinearGradient 線性漸變,但 ShaderMask 是對整個子 Widget 遮罩層生效,可能會影響 Text 文本顯示效果,需要 Stack 層級使用;

_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) ? '展開' : '收起',
            style: TextStyle(color: Theme.of(context).accentColor, fontSize: widget.textStyle?.fontSize ?? 14.0))));

1.2 Container BoxDecoration

????第二種就是常用的 Container 配合設置 BoxDecoration 設置線性漸變色;該方式使用更為便捷;

_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) ? '展開' : '收起',
        style: TextStyle(color: Theme.of(context).accentColor, fontSize: widget.textStyle?.fontSize ?? 14.0)));

2. Text 文本內容折疊

????小菜想實現文本折疊,首先需要預先得知 Text 文本在范圍內占據的行數,一般都需要通過 TextPainter 等方式獲取;小菜嘗試了兩種方式進行判斷;

2.1 TextPainter.didExceedMaxLines

????小菜之前也有簡單了解過 TextPainterTextSpan 的應用,主要用于文本的繪制,當設置 maxLines 之后,可以通過 didExceedMaxLines 判斷文本內容是否已經超行;小菜之后會對 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 可以直接獲取文本內容是否超行,但無法獲取每行文本信息等;于是小菜嘗試了 computeLineMetrics() 方式獲取 LineMetrics 基線度量;可以獲取每行內容所占據的寬高等;

????當然 LineMetrics 也無法獲取每行文本內容,以及在兩種文本對齊方式共用時有注意事項,小菜之后會進一步研究;

????Tips: 在使用 computeLineMetrics() 獲取 LineMetrics 信息時,需要注意 TextPainter 必須設置好 textDirection 文本對齊方式,以及在 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

????有了前面兩步的基礎,小菜將其結合起來,生成自定義 ACEFoldTextView;通過 LinearBuilder 約束子 Text 延遲加載;通過 LineMetrics 獲取最后一行文本長度,與默認【展開】所在 Widget 計算總和,之后判斷是否占據超過限制最大寬度;當超過最大寬度時,小菜將文本添加一個 \n 強制換行;

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;
                }
              })));

????ACEFoldTextView 案例源碼


????小菜對 ACEFoldTextView 的繪制到此為止,其中涉及到 TextPainter 內容較淺顯,小菜之后會進一步學習研究;如有錯誤,請多多指導!

來源: 阿策小和尚

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,533評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,055評論 3 414
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,365評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,561評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,346評論 6 404
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,889評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 42,978評論 3 439
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,118評論 0 286
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,637評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,558評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,739評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,246評論 5 355
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 43,980評論 3 346
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,362評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,619評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,347評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,702評論 2 370

推薦閱讀更多精彩內容