カスタム進行状況インジケータの作成


免責事項:私はいくつかの人々が混乱している場合にだけこれを追加を考えれば.画像はトリミングされた画像であり、実際のウィジェットは円の部分であり、丸い角を持つ長方形ではありません.
こんにちは.今日、私は私が私が私が学んだものを共有したいと思ったプロジェクトで最近遭遇した何かをカバーしています、そして、うまくいけば、人々もそれから学ぶことができます!私はどのようにカスタム進行状況インジケータを作成するつもりです.悪いタイトルと悪いイントロは、これは私が言及しているものの例です:

このブログのために、私はあなたがフラッタウィジェットの基本的な知識を持っていると仮定し、すべての詳細に移動しません.カスタムペイントはここで主な焦点になります.また、私がこれに飛び込む前に、私がここで働いているプロジェクトをチェックアウトしてください.

https://github.com/Dan-Y-Ko/Flutter-Dart-Playground/tree/master/flutter/ui/banking_app_ui 関係するコア装置


使用されるウィジェットは以下の通りです.
  • コンテナ
  • スタック
  • カスタムペイント
  • 基礎


    私達は実際の円を作りたい.これを行うには、簡単なコンテナを使用できます.しかし、我々はまた、同様にスタックにそれをラップする必要があるので、我々のカスタム進行状況インジケータと重複したい.コードは次のようになります(簡単です).
    import 'package:flutter/material.dart';
    
    const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
          debugShowCheckedModeBanner: false,
          home: const Scaffold(
            body: Center(
              child: ProgressIndicatorButton(),
            ),
          ),
        );
      }
    }
    
    class ProgressIndicatorButton extends StatelessWidget {
      const ProgressIndicatorButton({
        Key? key,
      }) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        const buttonSize = 80.0;
        const borderWidth = 2.0;
    
        return Stack(
          children: [
            Container(
              width: buttonSize,
              height: buttonSize,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                border: Border.all(
                  color: Colors.white,
                  width: borderWidth,
                ),
              ),
            ),
          ],
        );
      }
    }
    
    出力は次のようになります.

    カスタムペイント


    これが楽しいことだ!私はあなたの三角とあなたのユニットサークル(ちょうど冗談を覚えて、Googleは周りです).私はちょうどあなたが文字通りカスタムペイントで何かを作成することができますし、あらゆる可能なシナリオ上で行くことができるので、私はそれの1つの側面をカバーしているので、かなり長いブログで終わることを免責事項を追加したい.
    カスタムペイントのウィジェットを取る必要があります主なものは、カスタム画家の実装です.

    どのようにカスタム画家を作成するには?


    カスタム画家の実装は以下の通りである.
    カスタム画家クラスを拡張する
  • は、ペンキ塗り方法
  • を実行します
  • 実装のショルダーペイント方法
  • ペイントメソッドを行く前に、どのような肩をペイントしましょう.それは音として本質的です.これはブール値を返します、そして、我々がこの習慣画家の新しいインスタンスを作成したいならば、我々は真実を返しなければなりません.しかし、この例ではfalseを設定します.

    ペイントメソッドの破壊


    ペイントメソッドは、サイズを取り、実際の図面を行うには、キャンバスを使用します.あなたが作ることができる異なったものについてもっと知るために、見てください: .あなたが作成することができます多くのことがあります:四角形、円、線、さらにはカスタムパス.この例ではアークに注目します.
    void paint(Canvas canvas, Size size) {
        // 2
        final paint = Paint()
          // 3
          ..color = Colors.blue
          // 4
          ..strokeCap = StrokeCap.butt
          // 5
          ..style = PaintingStyle.stroke
          // 6
          ..strokeWidth = width;
        // 7
        final center = Offset(size.width / 2, size.height / 2);
        // 8
        final radius = (size.width / 2) - (width / 2);
        // 1
        canvas.drawArc(
          Rect.fromCircle(center: center, radius: radius),
          startAngle,
          sweepAngle,
          false,
          paint,
        );
      }
    
    前述のように
  • では、drawarcメソッドを使用しているので、アークを作成しています.第1引数はrectクラスで、いくつかのオプションがありますが、円とrectが欲しいです.FromMallは、その効果を与えます.他の引数は少し議論されるでしょう.2番目の引数は、アークを開始する場所を指定します.3番目の引数は、アークを終了する場所を指定します.4番目の引数は、アークを接続するために中心を使いたいかどうかを指定します.これは境界線から中心まで線を作ります.番目の引数は、ペイントクラスから作成されたものです.参照のためにここで見てください:https://api.flutter.dev/flutter/dart-ui/Canvas-class.html
  • ペイントクラスは、カスタム進行状況インジケータの視覚効果を担当しています.これはdrawarcメソッドに渡される必要があります.
  • これは色を与えます.
  • にいくつかのオプションがあり、ここで選択するものは、アークの「終わり」がどのように見えるかを決めるでしょう.たとえば、trokecap.ラウンドは円弧で私たちの弧をキャップします.あなたがまだ混乱しているならば、私が本当に説明する方法が本当にわからないので、見てください:https://api.flutter.dev/flutter/dart-ui/Canvas/drawArc.html
  • 私たちはここで一杯かストロークを使用できます.この例ではストロークを使用するので、特定の幅にわたって色を塗りたい.
  • は、図5において、5×5でハンドストロークし、ストロークの厚さを決定する.PaintingStyleと一緒に行く幅を指定します.ストローク.
  • オフセットは、それぞれ、x軸とy軸の特定の点を指定します.この中心値はrectに渡される.フロムサークル.
  • 円全体の幅と「境界線」の幅を差し引いて半径を取得します.この半径値はrectに渡される.フロムサークル.
  • startangleとsweepangleに入る前に、これはあなたのコードがこれまでのように見えるべきです.
    import 'package:flutter/material.dart';
    // import 'dart:math' as math;
    
    const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
          debugShowCheckedModeBanner: false,
          home: const Scaffold(
            body: Center(
              child: ProgressIndicatorButton(),
            ),
          ),
        );
      }
    }
    
    class ProgressIndicatorButton extends StatelessWidget {
      const ProgressIndicatorButton({
        Key? key,
    //     required this.startAngle,
    //     required this.endAngle,
      }) : super(key: key);
    
    //   final double startAngle;
    //   final double endAngle;
    
      @override
      Widget build(BuildContext context) {
        const buttonSize = 80.0;
        const borderWidth = 2.0;
    
        return Stack(
          children: [
            Container(
              width: buttonSize,
              height: buttonSize,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                border: Border.all(
                  color: Colors.white,
                  width: borderWidth,
                ),
              ),
            ),
            SizedBox(
              width: buttonSize,
              height: buttonSize,
              child: CustomPaint(
                painter: ProgressIndicatorPainter(
                  width: borderWidth,
    //               startAngle: startAngle,
    //               sweepAngle: endAngle,
                ),
                child: Center(
                  child: Container(
                    width: buttonSize - 20.0,
                    height: buttonSize - 20.0,
                    decoration: const BoxDecoration(
                      color: Colors.blue,
                      shape: BoxShape.circle,
                    ),
                    child: const Center(
                      child: Icon(
                        Icons.done,
                        size: 30.0,
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ],
        );
      }
    }
    
    class ProgressIndicatorPainter extends CustomPainter {
      const ProgressIndicatorPainter({
        required this.width,
    //     required this.startAngle,
    //     required this.sweepAngle,
      }) : super();
    
      final double width;
    //   final double startAngle;
    //   final double sweepAngle;
    
      @override
      void paint(Canvas canvas, Size size) {
        final paint = Paint()
          ..color = Colors.blue
          ..strokeCap = StrokeCap.butt
          ..style = PaintingStyle.stroke
          ..strokeWidth = width;
        final center = Offset(size.width / 2, size.height / 2);
        final radius = (size.width / 2) - (width / 2);
    //     canvas.drawArc(
    //       Rect.fromCircle(center: center, radius: radius),
    //       startAngle,
    //       sweepAngle,
    //       false,
    //       paint,
    //     );
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) => false;
    }
    
    私が持っているか、エラーがあると同じコードをコメントアウトしてください.
    出力は次のようになります.
    https://api.flutter.dev/flutter/dart-ui/StrokeCap-class.html

    何がstartangleとsweepangleですか?


    高レベルの概観では、startangleとsweepangleは、私たちの弧が始まるところで、そして、(以前に言及されたように)終わるところを決定するでしょう.
  • startangle :デフォルトでは、開始位置は単位円で0ラジアンである.注意するもう一つの点は、反時計回りの代わりに、この弧の方向が時計回りであるということです.何かを複雑にする代わりに、私はただ単位円を参照して、すべてにマイナス記号を加えました.例えば、単位円のπ/2位置でスタート位置を求めたいなら、startangleを-π/2とします.
  • weepangle :ここで値がどのような値になっているのかは、startangleに追加され、ARCSが終了する場所になります.例えば、単位円を参照すると、π/2から0までの弧が欲しかったら、-π/2のstartangleとπ/2のsweepangleを必要とします.π/2+π/2=0.うん、数学の授業!
  • 最終結果


    最初に参照した例に戻る

    どのようにそれについて行くか?さて、進行状況インジケータを変更するには、私たちだけでstartangleとsweepangleとティンカーする必要があります.単位円の参照は2π/3から始まり,11π/6で終わる.残念ながら、このようにカスタムstartangleを使用するときに、sweepangleを理解する簡単な方法を理解できませんでした.単に追加は本当に常に動作しません.ここでどのように私はそれに近づいている.象限Iでは、π/2のように完全な量子ドットを持っています.象限iiとivでは,2π/6スライスを持つ.そのため,π/6+π/6+π/2が5π/6である.これは、我々のsweepangleです.以下の完全なコード:
    import 'package:flutter/material.dart';
    import 'dart:math' as math;
    
    const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
          debugShowCheckedModeBanner: false,
          home: const Scaffold(
            body: Center(
              child: ProgressIndicatorButton(
                startAngle: -2 * math.pi / 3,
                endAngle: 5 * math.pi / 6,
              ),
            ),
          ),
        );
      }
    }
    
    class ProgressIndicatorButton extends StatelessWidget {
      const ProgressIndicatorButton({
        Key? key,
        required this.startAngle,
        required this.endAngle,
      }) : super(key: key);
    
      final double startAngle;
      final double endAngle;
    
      @override
      Widget build(BuildContext context) {
        const buttonSize = 80.0;
        const borderWidth = 2.0;
    
        return Stack(
          children: [
            Container(
              width: buttonSize,
              height: buttonSize,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                border: Border.all(
                  color: Colors.white,
                  width: borderWidth,
                ),
              ),
            ),
            SizedBox(
              width: buttonSize,
              height: buttonSize,
              child: CustomPaint(
                painter: ProgressIndicatorPainter(
                  width: borderWidth,
                  startAngle: startAngle,
                  sweepAngle: endAngle,
                ),
                child: Center(
                  child: Container(
                    width: buttonSize - 20.0,
                    height: buttonSize - 20.0,
                    decoration: const BoxDecoration(
                      color: Colors.blue,
                      shape: BoxShape.circle,
                    ),
                    child: const Center(
                      child: Icon(
                        Icons.done,
                        size: 30.0,
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ],
        );
      }
    }
    
    class ProgressIndicatorPainter extends CustomPainter {
      const ProgressIndicatorPainter({
        required this.width,
        required this.startAngle,
        required this.sweepAngle,
      }) : super();
    
      final double width;
      final double startAngle;
      final double sweepAngle;
    
      @override
      void paint(Canvas canvas, Size size) {
        final paint = Paint()
          ..color = Colors.blue
          ..strokeCap = StrokeCap.butt
          ..style = PaintingStyle.stroke
          ..strokeWidth = width;
        final center = Offset(size.width / 2, size.height / 2);
        final radius = (size.width / 2) - (width / 2);
        canvas.drawArc(
          Rect.fromCircle(center: center, radius: radius),
          startAngle,
          sweepAngle,
          false,
          paint,
        );
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) => false;
    }
    

    ノート


    ラジアンの代わりに度を使いたいなら、それはかなり簡単です.カスタムペインに度数を設定し、DrawArcメソッドに引数として追加する前にラジアンに変換します.他のすべてのコンセプトは同じですが、実際の値は異なります.次のコードを実装します.
    import 'package:flutter/material.dart';
    import 'dart:math' as math;
    
    const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          theme: ThemeData.dark().copyWith(scaffoldBackgroundColor: darkBlue),
          debugShowCheckedModeBanner: false,
          home: const Scaffold(
            body: Center(
              child: ProgressIndicatorButton(
                startAngle: -120,
                endAngle: 150,
              ),
            ),
          ),
        );
      }
    }
    
    class ProgressIndicatorButton extends StatelessWidget {
      const ProgressIndicatorButton({
        Key? key,
        required this.startAngle,
        required this.endAngle,
      }) : super(key: key);
    
      final int startAngle;
      final int endAngle;
    
      @override
      Widget build(BuildContext context) {
        const buttonSize = 80.0;
        const borderWidth = 2.0;
    
        return Stack(
          children: [
            Container(
              width: buttonSize,
              height: buttonSize,
              decoration: BoxDecoration(
                shape: BoxShape.circle,
                border: Border.all(
                  color: Colors.white,
                  width: borderWidth,
                ),
              ),
            ),
            SizedBox(
              width: buttonSize,
              height: buttonSize,
              child: CustomPaint(
                painter: ProgressIndicatorPainter(
                  width: borderWidth,
                  startAngle: startAngle,
                  sweepAngle: endAngle,
                ),
                child: Center(
                  child: Container(
                    width: buttonSize - 20.0,
                    height: buttonSize - 20.0,
                    decoration: const BoxDecoration(
                      color: Colors.blue,
                      shape: BoxShape.circle,
                    ),
                    child: const Center(
                      child: Icon(
                        Icons.done,
                        size: 30.0,
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ],
        );
      }
    }
    
    class ProgressIndicatorPainter extends CustomPainter {
      const ProgressIndicatorPainter({
        required this.width,
        required this.startAngle,
        required this.sweepAngle,
      }) : super();
    
      final double width;
      final int startAngle;
      final int sweepAngle;
    
      @override
      void paint(Canvas canvas, Size size) {
        final paint = Paint()
          ..color = Colors.blue
          ..strokeCap = StrokeCap.butt
          ..style = PaintingStyle.stroke
          ..strokeWidth = width;
        final startAngleRad = startAngle * (math.pi / 180.0);
        final sweepAngleRad = sweepAngle * (math.pi / 180.0);
        final center = Offset(size.width / 2, size.height / 2);
        final radius = (size.width / 2) - (width / 2);
        canvas.drawArc(
          Rect.fromCircle(center: center, radius: radius),
          startAngleRad,
          sweepAngleRad,
          false,
          paint,
        );
      }
    
      @override
      bool shouldRepaint(CustomPainter oldDelegate) => false;
    }
    

    ボーナス!もう2つの例



    同じプロセス.我々は、0のstartangleを持っています.sweepangleについては、象限IVの4象限と象限IIIで1π/6スライスの完全象限を持っていますので、π/6 +π/2は4π/6です.

    少しねじれのしかし、まだ同じ概念.はい、我々は
    −5π/4のstartanglesweepangleのために、我々は象限IIで完全象限を持ちます、そして、象限IIIで、我々はπ/6スライスとπ/12スライスを持ちます.π/2+π/6+π/12=3π/4これがsweepangleです.

    結論


    私がstartangleとsweepangleを計算する方法はおそらく最も理想的ではないが、それは動作します.とにかく、あなたが何かを学んだことを願っていますし、質問があれば直接私に連絡したり、コメントを残してください.