最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Android?Flutter繪制扇形圖詳解

 更新時間:2022年05月19日 10:48:20   作者:Eau_z  
在開發(fā)過程中通常會遇到一些不規(guī)則的UI,比如不規(guī)則的線條,多邊形,統(tǒng)計圖表等等,用那些通用組件通過組合的方式無法進(jìn)行實現(xiàn),這就需要我們自己進(jìn)行繪制。本文將利用Flutter繪制扇形圖,感興趣的可以學(xué)習(xí)一下

簡介

在開發(fā)過程中通常會遇到一些不規(guī)則的UI,比如不規(guī)則的線條,多邊形,統(tǒng)計圖表等等,用那些通用組件通過組合的方式無法進(jìn)行實現(xiàn),這就需要我們自己進(jìn)行繪制??梢酝ㄟ^使用CuntomPaint組件并結(jié)合畫筆CustomPainter去進(jìn)行手動繪制各種圖形。

CustomPaint介紹

CustomPaint是一個繼承SingleChildRenderObjectWidget的Widget,這里主要介紹幾個重要參數(shù):

child:CustomPaint的子組件。

painter: 畫筆,繪制的圖形會顯示在child后面。

foregroundPainter:前景畫筆,繪制的圖形會顯示在child前面。

size:繪制區(qū)域大小。

CustomPainter介紹

CustomPainter是一個抽象類,通過自定義一個類繼承自CustomPainter,重寫paintshouldRepaint方法,具體繪制主要在paint方法里。

paint介紹

主要兩個參數(shù):

Canvas:畫布,可以用于繪制各種圖形。

Size:繪制區(qū)域的大小。

void paint(Canvas canvas, Size size)

shouldRepaint介紹

在Widget重繪前會調(diào)用該方法確定時候需要重繪,shouldRepaint返回ture表示需要重繪,返回false表示不需要重繪。

bool shouldRepaint(CustomPainter oldDelegate)

示例

這里我們通過繪制一個餅狀圖來演示繪制的整體流程。

使用CustomPaint

首先,使用CustomPaint,繪制大小為父組件最大值,傳入自定義painter。

@override
Widget build(BuildContext context) {
    return CustomPaint(
      size: Size.infinite,
      painter: PieChartPainter(),
    );
}

自定義Painter

自定義PieChartPainter繼承CustomPainter

class PieChartPainters extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return oldDelegate != this;
  }
}

繪制

接著我們來實現(xiàn)paint方法進(jìn)行繪制

@override
void paint(Canvas canvas, Size size) {
    //移動到中心點
    canvas.translate(size.width / 2, size.height / 2);
    //繪制餅狀圖
    _drawPie(canvas, size);
    //繪制扇形分割線
    _drawSpaceLine(canvas);
    //繪制中心圓
    _drawHole(canvas, size);
}

繪制餅狀圖

我們以整個畫布的中點為圓點,然后計算出每個扇形的角度區(qū)域,通過canvas.drawArc繪制扇形。

void _drawPie(Canvas canvas, Size size) {
    var startAngle = 0.0;
    var sumValue = models.fold<double>(0.0, (sum, model) => sum + model.value);
    for (var model in models) {
      Paint paint = Paint()
        ..style = PaintingStyle.fill
        ..color = model.color;
      var sweepAngle = model.value / sumValue * 360;
      canvas.drawArc(Rect.fromCircle(radius: model.radius, center: Offset.zero),
          startAngle * pi / 180, sweepAngle * pi / 180, true, paint);

      //為每一個區(qū)域繪制延長線和文字
      _drawLineAndText(
          canvas, size, model.radius, startAngle, sweepAngle, model);

      startAngle += sweepAngle;
    }
}

繪制延長線以及文本

延長線的起點為扇形區(qū)域邊緣中點位置,長度為一個固定的長度,轉(zhuǎn)折點坐標(biāo)通過半徑加這個固定長度和三角函數(shù)進(jìn)行計算,然后通過轉(zhuǎn)折點的位置決定橫線終點的方向,而橫線的長度則根據(jù)文字的寬度決定,然后通過canvas.drawLine進(jìn)行繪制直線。

文本繪制使用TextPainter.paint進(jìn)行繪制,paint方法里面最終是通過canvas.drawParagraph進(jìn)行繪制的。

最后再在文字的前面通過canvas.drawCircle繪制一個小圓點。

 void _drawLineAndText(Canvas canvas, Size size, double radius,
      double startAngle, double sweepAngle, PieChartModel model) {
    var ratio = (sweepAngle / 360.0 * 100).toStringAsFixed(2);

    var top = Text(model.name);
    var topTextPainter = getTextPainter(top);

    var bottom = Text("$ratio%");
    var bottomTextPainter = getTextPainter(bottom);

    // 繪制橫線
    // 計算開始坐標(biāo)以及轉(zhuǎn)折點的坐標(biāo)
    var startX = radius * (cos((startAngle + (sweepAngle / 2)) * (pi / 180)));
    var startY = radius * (sin((startAngle + (sweepAngle / 2)) * (pi / 180)));

    var firstLine = radius / 5;
    var secondLine =
        max(bottomTextPainter.width, topTextPainter.width) + radius / 4;
    var pointX = (radius + firstLine) *
        (cos((startAngle + (sweepAngle / 2)) * (pi / 180)));
    var pointY = (radius + firstLine) *
        (sin((startAngle + (sweepAngle / 2)) * (pi / 180)));

    // 計算坐標(biāo)在左邊還是在右邊
    // 并計算橫線結(jié)束坐標(biāo)
    // 如果結(jié)束坐標(biāo)超過了繪制區(qū)域,則改變結(jié)束坐標(biāo)的值
    var marginOffset = 20.0; // 距離繪制邊界的偏移量
    var endX = 0.0;
    if (pointX - startX > 0) {
      endX = min(pointX + secondLine, size.width / 2 - marginOffset);
      secondLine = endX - pointX;
    } else {
      endX = max(pointX - secondLine, -size.width / 2 + marginOffset);
      secondLine = pointX - endX;
    }

    Paint paint = Paint()
      ..style = PaintingStyle.fill
      ..strokeWidth = 1
      ..color = Colors.grey;

    // 繪制延長線
    canvas.drawLine(Offset(startX, startY), Offset(pointX, pointY), paint);
    canvas.drawLine(Offset(pointX, pointY), Offset(endX, pointY), paint);

    // 文字距離中間橫線上下間距偏移量
    var offset = 4;
    var textWidth = bottomTextPainter.width;
    var textStartX = 0.0;
    textStartX =
        _calculateTextStartX(pointX, startX, textWidth, secondLine, textStartX, offset);
    bottomTextPainter.paint(canvas, Offset(textStartX, pointY + offset));

    textWidth = topTextPainter.width;
    var textHeight = topTextPainter.height;
    textStartX =
        _calculateTextStartX(pointX, startX, textWidth, secondLine, textStartX, offset);
    topTextPainter.paint(canvas, Offset(textStartX, pointY - offset - textHeight));

    // 繪制文字前面的小圓點
    paint.color = model.color;
    canvas.drawCircle(
        Offset(textStartX - 8, pointY - 4 - topTextPainter.height / 2),
        4,
        paint);
}

繪制扇形分割線

在繪制完扇形之后,然后在扇形的開始的那條邊上繪制一條直線,起點為圓點,長度為扇形半徑,終點的位置根據(jù)半徑和扇形開始的那條邊的角度用三角函數(shù)進(jìn)行計算,然后通過canvas.drawLine進(jìn)行繪制。

void _drawSpaceLine(Canvas canvas) {
    var sumValue = models.fold<double>(0.0, (sum, model) => sum + model.value);
    var startAngle = 0.0;
    for (var model in models) {
      _drawLine(canvas, startAngle, model.radius);
      startAngle += model.value / sumValue * 360;
    }
}

void _drawLine(Canvas canvas, double angle, double radius) {
    var endX = cos(angle * pi / 180) * radius;
    var endY = sin(angle * pi / 180) * radius;
    Paint paint = Paint()
      ..style = PaintingStyle.fill
      ..color = Colors.white
      ..strokeWidth = spaceWidth;
    canvas.drawLine(Offset.zero, Offset(endX, endY), paint);
  }

繪制內(nèi)部中心圓

這里可以通過傳入的參數(shù)判斷是否需要繪制這個圓,使用canvas.drawCircle進(jìn)行繪制一個與背景色一致的圓。

void _drawHole(Canvas canvas, Size size) {
    if (isShowHole) {
      holePath.reset();
      Paint paint = Paint()
        ..style = PaintingStyle.fill
        ..color = Colors.white;
      canvas.drawCircle(Offset.zero, holeRadius, paint);
    }
}

觸摸事件處理

接下來我們來處理點擊事件,當(dāng)我們點擊某一個扇形區(qū)域時,此扇形需要突出顯示,如下圖:

重寫hitTest方法

注意這個方法的返回值決定是否響應(yīng)事件。

默認(rèn)情況下返回null,事件不會向下傳遞,也不會進(jìn)行處理; 如果返回true則當(dāng)前組件進(jìn)行處理事件; 如果返回false則當(dāng)前組件不會響應(yīng)點擊事件,會向下一層傳遞;

我直接在這里處理點擊事件,通過該方法傳入的offset確定點擊的位置,如果點擊位置是在圓形區(qū)域內(nèi)并且不在中心圓內(nèi)則處理事件同時判斷所點擊的具體是哪個扇形,反之則恢復(fù)默認(rèn)狀態(tài)。

@override
bool? hitTest(Offset offset) {
    if (oldTapOffset.dx==offset.dx && oldTapOffset.dy==offset.dy) {
      return false;
    }
    oldTapOffset = offset;
    for (int i = 0; i < paths.length; i++) {
      if (paths[i].contains(offset) &&
          !holePath.contains(offset)) {
        onTap?.call(i);
        oldTapOffset = offset;
        return true;
      }
    }
    onTap?.call(-1);
    return false;
}

至此,我們通過onTap向上傳遞出點擊的是第幾個扇形,然后進(jìn)行處理,更新UI就可以了。

動畫實現(xiàn)

這里通過Widget繼承ImplicitlyAnimatedWidget來實現(xiàn),ImplicitlyAnimatedWidget是一個抽象類,繼承自StatefulWidget,既然是StatefulWidget那肯定還有一個StateState繼承AnimatedWidgetBaseState(此類繼承自ImplicitlyAnimatedWidgetState),感興趣的小伙伴可以直接去看源碼

實現(xiàn)AnimatedWidgetBaseState里面的forEachTween方法,主要是用于來更新Tween的初始值。

@override
void forEachTween(TweenVisitor<dynamic>visitor) {
   customPieTween = visitor(customPieTween, end, (dynamic value) {
      return CustomPieTween(begin: value, end: end);
    }) as CustomPieTween;
}

自定義CustomPieTween繼承自Tween,重寫lerp方法,對需要做動畫的參數(shù)進(jìn)行處理

class CustomPieTween extends Tween<List<PieChartModel>> {
  CustomPieTween({List<PieChartModel>? begin, List<PieChartModel>? end})
      : super(begin: begin, end: end);

  @override
  List<PieChartModel> lerp(double t) {
    List<PieChartModel> list = [];
    begin?.asMap().forEach((index, model) {
      list.add(model
        ..radius = lerpDouble(model.radius, end?[index].radius ?? 100.0, t));
    });
    return list;
  }

  double lerpDouble(double radius, double radius2, double t) {
    if (radius == radius2) {
      return radius;
    }
    var d = (radius2 - radius) * t;
    var value = radius + d;
    return value;
  }
}

以上就是Android Flutter繪制扇形圖詳解的詳細(xì)內(nèi)容,更多關(guān)于Android Flutter扇形圖的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 如何玩轉(zhuǎn)Android矢量圖VectorDrawable

    如何玩轉(zhuǎn)Android矢量圖VectorDrawable

    這篇文章主要教大家如何玩轉(zhuǎn)Android矢量圖VectorDrawable,對矢量圖感興趣的小伙伴們可以參考一下
    2016-05-05
  • Android開發(fā)實戰(zhàn)鬧鐘項目

    Android開發(fā)實戰(zhàn)鬧鐘項目

    這篇文章主要介紹了Android開發(fā)實戰(zhàn)鬧鐘項目,根據(jù)我們手機(jī)鬧鐘設(shè)計的一個鬧鐘APP,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-09-09
  • Android中用StaticLayout實現(xiàn)文本繪制自動換行詳解

    Android中用StaticLayout實現(xiàn)文本繪制自動換行詳解

    StaticLayout是android中處理文字換行的一個工具類,StaticLayout已經(jīng)實現(xiàn)了文本繪制換行處理,下面這篇文章主要介紹了Android中用StaticLayout實現(xiàn)文本繪制自動換行的相關(guān)資料,需要的朋友可以參考。
    2017-03-03
  • item高度不同時Recyclerview獲取滑動距離的方法

    item高度不同時Recyclerview獲取滑動距離的方法

    這篇文章主要介紹了item高度不同時Recyclerview獲取滑動距離的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-11-11
  • android中Context深入詳解

    android中Context深入詳解

    本文主要給大家深入的講解一下android中Context知識以及繼承關(guān)系,有助于大家更好的理解Context。
    2017-11-11
  • Android Studio 3.0的下載安裝教程

    Android Studio 3.0的下載安裝教程

    Android Studio從3.0版本新增了許多功能,當(dāng)然首當(dāng)其沖就是從3.0版本新增了對 Kotlin 開發(fā)語言的支持,除此之外還有其他一些新功能,今天我們主要來看看如何更新Android Studio 3.0
    2017-10-10
  • android實現(xiàn)文件下載功能

    android實現(xiàn)文件下載功能

    這篇文章主要為大家詳細(xì)介紹了android實現(xiàn)文件下載功能,android在網(wǎng)絡(luò)上下載文件,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11
  • Android衛(wèi)星菜單效果的實現(xiàn)方法

    Android衛(wèi)星菜單效果的實現(xiàn)方法

    這篇文章主要介紹了Android衛(wèi)星菜單效果的實現(xiàn)方法,需要的朋友可以參考下
    2017-05-05
  • Android TextView 去掉自適應(yīng)默認(rèn)的fontpadding的實現(xiàn)方法

    Android TextView 去掉自適應(yīng)默認(rèn)的fontpadding的實現(xiàn)方法

    這篇文章主要介紹了Android TextView 去掉自適應(yīng)默認(rèn)的fontpadding的實現(xiàn)方法的相關(guān)資料,希望通過本文大家能夠掌握這部分內(nèi)容,需要的朋友可以參考下
    2017-09-09
  • Android開發(fā)之電話撥號器實例詳解

    Android開發(fā)之電話撥號器實例詳解

    這篇文章主要介紹了Android開發(fā)之電話撥號器,結(jié)合實例形式詳細(xì)分析了Android電話撥號器的實現(xiàn)步驟與具體代碼,并附帶了一個Android開放電話撥號器的學(xué)習(xí)筆記,需要的朋友可以參考下
    2015-12-12

最新評論

页游| 玉门市| 仪征市| 福海县| 安阳县| 亚东县| 石阡县| 太谷县| 阿合奇县| 霍城县| 舒兰市| 铜山县| 长垣县| 漳州市| 怀远县| 滨州市| 巴东县| 石渠县| 如皋市| 蒙城县| 漳州市| 陕西省| 文安县| 阿图什市| 凤山县| 秦安县| 黑山县| 玉环县| 建平县| 珠海市| 盐城市| 元阳县| 乐陵市| 岢岚县| 黄陵县| 隆化县| 长汀县| 屯昌县| 江都市| 荥阳市| 赞皇县|