首頁 > 軟體

利用Android封裝一個有趣的Loading元件

2022-08-08 14:02:01

前言

在上一篇普通的載入千篇一律,有趣的 loading 萬里挑一 中,我們介紹了使用Path類的PathMetrics屬性來控制繪製點在路徑上運動來實現比較有趣的loading效果。有評論說因為是黑色背景,所以看著好看。黑色背景確實顯得高階一點,但是並不是其他配色也不行,本篇我們來封裝一個可以自定義設定前景色和背景色的Loading元件。

元件定義

loading元件共定義4個入口引數:

  • 前景色:繪製圖形的前景色;
  • 背景色:繪製圖形的背景色;
  • 圖形尺寸:繪製圖形的尺寸;
  • 載入文字:可選,如果有文字就顯示,沒有就不顯示。

得到的Loading元件類如下所示:

class LoadingAnimations extends StatefulWidget {
  final Color bgColor;
  final Color foregroundColor;
  String? loadingText;
  final double size;
  LoadingAnimations(
      {required this.foregroundColor,
      required this.bgColor,
      this.loadingText,
      this.size = 100.0,
      Key? key})
      : super(key: key);

  @override
  _LoadingAnimationsState createState() => _LoadingAnimationsState();
}

圓形Loading

我們先來實現一個圓形的loading,效果如下所示。

這裡繪製了兩組沿著一個大圓運動的軸對稱的實心圓,半徑依次減小,圓心間距隨著動畫時間逐步拉大。實際上實現的核心還是基於PathPathMetrics。具體實現程式碼如下:

_drawCircleLoadingAnimaion(
      Canvas canvas, Size size, Offset center, Paint paint) {
  final radius = boxSize / 2;
  final ballCount = 6;
  final ballRadius = boxSize / 15;

  var circlePath = Path()
    ..addOval(Rect.fromCircle(center: center, radius: radius));

  var circleMetrics = circlePath.computeMetrics();
  for (var pathMetric in circleMetrics) {
    for (var i = 0; i < ballCount; ++i) {
      var lengthRatio = animationValue * (1 - i / ballCount);
      var tangent =
          pathMetric.getTangentForOffset(pathMetric.length * lengthRatio);

      var ballPosition = tangent!.position;
      canvas.drawCircle(ballPosition, ballRadius / (1 + i), paint);
      canvas.drawCircle(
          Offset(size.width - tangent.position.dx,
              size.height - tangent.position.dy),
          ballRadius / (1 + i),
          paint);
    }
  }
}

其中路徑比例為lengthRatio,通過animationValue乘以一個係數使得實心圓的間距越來越大 ,同時通過Offset(size.width - tangent.position.dx, size.height - tangent.position.dy)繪製了一組對對稱的實心圓,這樣整體就有一個圓形的效果了,動起來也會更有趣一點。

橢圓運動Loading

橢圓和圓形沒什麼區別,這裡我們搞個漸變的效果看看,利用之前介紹過的Paintshader可以實現漸變色繪製效果。

實現程式碼如下所示。

final ballCount = 6;
final ballRadius = boxSize / 15;

var ovalPath = Path()
  ..addOval(Rect.fromCenter(
      center: center, width: boxSize, height: boxSize / 1.5));
paint.shader = LinearGradient(
  begin: Alignment.topLeft,
  end: Alignment.bottomRight,
  colors: [this.foregroundColor, this.bgColor],
).createShader(Offset.zero & size);
var ovalMetrics = ovalPath.computeMetrics();
for (var pathMetric in ovalMetrics) {
  for (var i = 0; i < ballCount; ++i) {
    var lengthRatio = animationValue * (1 - i / ballCount);
    var tangent =
        pathMetric.getTangentForOffset(pathMetric.length * lengthRatio);

    var ballPosition = tangent!.position;
    canvas.drawCircle(ballPosition, ballRadius / (1 + i), paint);
    canvas.drawCircle(
        Offset(size.width - tangent.position.dx,
            size.height - tangent.position.dy),
        ballRadius / (1 + i),
        paint);
  }
}

當然,如果漸變色的顏色更豐富一點會更有趣些。

貝塞爾曲線Loading

通過貝塞爾曲線構建一條Path,讓一組圓形沿著貝塞爾曲線運動的Loading效果也很有趣。

原理和圓形的一樣,首先是構建貝塞爾曲線Path,程式碼如下。

var bezierPath = Path()
  ..moveTo(size.width / 2 - boxSize / 2, center.dy)
  ..quadraticBezierTo(size.width / 2 - boxSize / 4, center.dy - boxSize / 4,
      size.width / 2, center.dy)
  ..quadraticBezierTo(size.width / 2 + boxSize / 4, center.dy + boxSize / 4,
      size.width / 2 + boxSize / 2, center.dy)
  ..quadraticBezierTo(size.width / 2 + boxSize / 4, center.dy - boxSize / 4,
      size.width / 2, center.dy)
  ..quadraticBezierTo(size.width / 2 - boxSize / 4, center.dy + boxSize / 4,
      size.width / 2 - boxSize / 2, center.dy);

這裡實際是構建了兩條貝塞爾曲線,先從左邊到右邊,然後再折回來。之後就是運動的實心圓了,這個只是數量上多了,ballCount30,這樣效果看著就有一種拖影的效果。

var ovalMetrics = bezierPath.computeMetrics();
for (var pathMetric in ovalMetrics) {
  for (var i = 0; i < ballCount; ++i) {
    var lengthRatio = animationValue * (1 - i / ballCount);
    var tangent =
        pathMetric.getTangentForOffset(pathMetric.length * lengthRatio);

    var ballPosition = tangent!.position;
    canvas.drawCircle(ballPosition, ballRadius / (1 + i), paint);
    canvas.drawCircle(
        Offset(size.width - tangent.position.dx,
            size.height - tangent.position.dy),
        ballRadius / (1 + i),
        paint);
  }
}

這裡還可以改變運動方向,實現一些其他的效果,例如下面的效果,第二組圓球的繪製位置實際上是第一組圓球的x、y座標的互換。

var lengthRatio = animationValue * (1 - i / ballCount);
var tangent =
    pathMetric.getTangentForOffset(pathMetric.length * lengthRatio);

var ballPosition = tangent!.position;
canvas.drawCircle(ballPosition, ballRadius / (1 + i), paint);
canvas.drawCircle(Offset(tangent.position.dy, tangent.position.dx),
    ballRadius / (1 + i), paint);

元件使用

我們來看如何使用我們定義的這個元件,使用程式碼如下,我們用Future延遲模擬了一個載入效果,在載入過程中使用loading指示載入過程,載入完成後顯示圖片。

class _LoadingDemoState extends State<LoadingDemo> {
  var loaded = false;

  @override
  void initState() {
    super.initState();
    Future.delayed(Duration(seconds: 5), () {
      setState(() {
        loaded = true;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: Text('Loading 使用'),
      ),
      body: Center(
        child: loaded
            ? Image.asset(
                'images/beauty.jpeg',
                width: 100.0,
              )
            : LoadingAnimations(
                foregroundColor: Colors.blue,
                bgColor: Colors.white,
                size: 100.0,
              ),
      ),
    );
  }

最終執行的效果如下,原始碼已提交至:繪圖相關原始碼,檔名為loading_animations.dart

總結

本篇介紹了Loading元件的封裝方法,核心要點還是利用Path和動畫控制繪製元素的運動軌跡來實現更有趣的效果。在實際應用過程中,也可以根據互動設計的需要,做一些其他有趣的載入動效,提高等待過程的趣味性。

到此這篇關於利用Android封裝一個有趣的Loading元件的文章就介紹到這了,更多相關Android封裝Loading元件內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com