Flutter RichText支持自定義文本溢出效果

extended text 相關文章

以前介紹過了Extended text的圖片功能 ,今天要講的仍是跟產品設計有關係,老規矩上圖 git

產品說,那個文本溢出的點點點後面給我加個雞腿,想什麼啊,是加個 「全文」字樣,點擊以後跳轉到全文去。github

就像下面這種同樣 canvas

pub package

首先,我看了下Text的源碼,發現這個...是被寫死了的,傳遞給了TextPaintermarkdown

const String _kEllipsis = '\u2026';
複製代碼

而後再向裏面看,就是引擎繪畫的代碼了。。看不到了。。是我太弱了。 在google上搜索了下,發現也有問這個問題26748,上面也是說了。。須要把源碼複製出來,把_kEllipsis改爲你想要的,可是。。這個是個字符串啊。。那個好比說藍色怎麼弄?好比說點擊怎麼弄?ide

想來想去,一個字就是畫,在Canvas上面盡情畫。svg

首先,我定義了一個TextSpan 用於用戶自定義文本溢出效果post

overFlowTextSpan: OverFlowTextSpan(children: <TextSpan>[
                      TextSpan(text: ' \u2026 '),
                      TextSpan(
                          text: "more detail",
                          style: TextStyle(
                            color: Colors.blue,
                          ),
                          recognizer: TapGestureRecognizer()
                            ..onTap = () {
                              launch(
                                  "https://github.com/fluttercandies/extended_text");
                            })
                    ], background: Theme.of(context).canvasColor),
複製代碼

而後咱們直接來到ExtendedRenderParagraph的paint方法ui

@override
  void paint(PaintingContext context, Offset offset) {
    _paintSpecialText(context, offset);
    _paint(context, offset);
    _paintTextOverflow(context, offset);
  }
複製代碼

這個效果確定須要在畫好字以後,再來魔改google

void _paintTextOverflow(PaintingContext context, Offset offset) {
    if (_hasVisualOverflow && overFlowTextSpan != null) {
      final Canvas canvas = context.canvas;

      ///we will move the canvas, so rect top left should be (0,0)
      final Rect rect = Offset(0.0, 0.0) & size;
      var textPainter = overFlowTextSpan.layout(_textPainter);
      assert(textPainter.width <= rect.width,);
     
  }
複製代碼

首先咱們須要layout一下咱們的overFlowTextSpan,若是你定義的太長,已經超出一行了的話,那麼抱歉 spa

老動做,把畫布移動到整個文字的左上角,根據overFlowTextSpanOffset的左上角,計算出最近的TextPosition。

canvas.save();

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

      final Offset overFlowTextSpanOffset = Offset(
          rect.width - textPainter.width, rect.height - textPainter.height);

      ///find TextPosition near overflow
      TextPosition overflowOffset =
          getPositionForOffset(overFlowTextSpanOffset);
複製代碼

再經過這個找出最近文字的top-left,這樣才能保證不會剪切到半個或者不徹底的文字。

///find overflow TextPosition that not clip the original text
      Offset finalOverflowOffset = _findFinalOverflowOffset(
          rect, rect.width - textPainter.width, overflowOffset.offset);

 Offset _findFinalOverflowOffset(Rect rect, double x, int endTextOffset) {
    Offset endOffset = getOffsetForCaret(
      TextPosition(offset: endTextOffset, affinity: TextAffinity.upstream),
      rect,
    );
    //overflow
    if (endOffset == null || (endTextOffset != 0 && endOffset == Offset.zero)) {
      return _findFinalOverflowOffset(rect, x, endTextOffset - 1);
    }

    if (endOffset.dx > x) {
      return _findFinalOverflowOffset(rect, x, endTextOffset - 1);
    }
    return endOffset;
  } 
複製代碼

這樣子咱們就找到咱們須要在哪一個文字的位置把OverFlowTextSpan繪畫出來,而且想辦法把OverFlowTextSpan下一層的文字給清除或者遮擋住。

首先嚐試是用BlendMode.clear來清除指定區域的文字,失敗,不知道爲何, 我看別人也是這樣子寫的,能清除掉Canvas上面的內容,若是有哪一個兄弟知道,請必定要告訴我,感謝萬分。

///why BlendMode.clear not clear the text
// canvas.saveLayer(overFlowTextSpanRect, Paint());
// canvas.drawRect(
// overFlowTextSpanRect,
// Paint()
// ..blendMode = BlendMode.clear);
// canvas.restore();
複製代碼

那麼只能畫一層跟Canvas同樣的顏色來遮住文字了。這裏默認使用的是

Theme.of(context).canvasColor
複製代碼

而後咱們再畫上OverFlowTextSpan

textPainter.paint(
          canvas, Offset(finalOverflowOffset.dx, overFlowTextSpanOffset.dy));
複製代碼

最後咱們要處理一下點擊事件,保存textPainter繪畫的點(相對整個系統座標的)

overFlowTextSpan.textPainterHelper.saveOffset(Offset(
          offset.dx + finalOverflowOffset.dx,
          offset.dy + overFlowTextSpanOffset.dy));
複製代碼

在handleEvent方法中,咱們加入如下代碼,若是找到了對應註冊了recognizer的TextSpan,咱們就給它觸發,而且return(由於overFlowTextSpan在原來的字的上一層)

if (overFlowTextSpan != null) {
      final TextPosition position =
          overFlowTextSpan.textPainterHelper.getPositionForOffset(offset);
      final TextSpan span =
          overFlowTextSpan.textPainterHelper.getSpanForPosition(position);

      if (span?.recognizer != null) {
        span.recognizer.addPointer(event);
        return;
      }
    }
複製代碼

_offset是咱們剛纔保持的相對整個系統座標的點,咱們須要把傳入的Offset減掉 _offset,這樣這個overFlowTextSpan的相對本身的座標系纔是以(0,0)開始的,最後用這個TextPosition找到對應的TextSpan,大功告成。

///method for [OverFlowTextSpan]
  ///offset int coordinate system
  Offset _offset;
  void saveOffset(Offset offset) {
    _offset = offset;
  }

  ///method for [OverFlowTextSpan]
  TextPosition getPositionForOffset(Offset offset) {
    return painter.getPositionForOffset(offset - _offset);
  }

  ///method for [OverFlowTextSpan]
  TextSpan getSpanForPosition(TextPosition position) {
    return painter.text.getSpanForPosition(position);
  }
複製代碼

除了清除(覆蓋)文字的那個部分,其餘應該都是比較完美的解決方案,期待你們能帶來更多點子,改進 Extended Text

最後放上 Github Extended_Text,若是你有什麼不明白的地方,請告訴我,歡迎加入Flutter Candies,一塊兒生產可愛的Flutter 小糖果(QQ羣:181398081)

相關文章
相關標籤/搜索