一緒に創造し、成長するために一緒に働きましょう!「ナゲッツデイリー新プラン・8月アップデートチャレンジ」参加5日目ですイベント詳細はこちら
序文
この記事の目的は、FlutterUnit Drawing Collection に含まれる演習を描くことです。また、「Flutter Grammar Basics - The Land of Dreams」には、このダイヤルを使用する必要があるが、描画の知識をあまり必要としない章があるため、ここで実装されています。効果は次のとおりで、外側のリングは線で囲まれたダイヤルで、内側の小さな円は現在位置を示し、中央に情報テキストが表示されます。
1.需要分析
ここに描かれているのは秒表表盘
、円が1 分种
であり、対応する 1 秒あたりの3
グリッド、つまり180
グリッドの総数があり、各グリッド間の角度が であるということです2°
。描画プロセスで必要なパラメーターを見てみましょう.まず、ストップウォッチの現在の時間を表すDuration
オブジェクトです。また、時間に応じて小円の角度を計算することができます。
半径、スケールの色、文字の色、スタイルなど、描画時に設定できるパラメータ。さらに、スケールの小円の長さ、厚さ、半径などのより詳細なパラメータは、半径に応じて計算できます。
2. 縮尺図
下図のような四角形が作画領域で、左目盛りの幅はscaleLineWidth
四角形の領域の幅と_kScaleWidthRate
縮尺決まります。
const double _kScaleWidthRate = 0.4/10;
final double scaleLineWidth = size.width*_kScaleWidthRate;
复制代码
左右の点の座標を決定する限り、スケールを描くときは直線を使用してください。tag1
スケールの座標は、以下のように座標の原点を領域の中心に移動することで簡単に計算できtag2
ます。
final Paint scalePainter = Paint();
@override
void paint(Canvas canvas, Size size) {
canvas.translate(size.width/2, size.height/2); // tag1
scalePainter..color = Colors.red..style=PaintingStyle.stroke;
final double scaleLineWidth = size.width*_kScaleWidthRate;
canvas.drawLine(
Offset(size.width/2, 0), // tag2
Offset(size.width/2 - scaleLineWidth, 0),
scalePainter
);
}
复制代码
下の文字盤を描いてみましょう. トラバースと回転によって描画できる目盛りの合計180
を. 次のように、描画180
時間と、各描画が完了するたびにキャンバスが回転し2°
、描画180
時間にキャンバスが360°
元の位置に戻ります。インターフェイスに描画されたコンテンツは次のように表示されます。
for(int i = 0; i < 180 ; i++){
canvas.drawLine(
Offset(size.width/2, 0),
Offset(size.width/2 - scaleLineWidth, 0),
scalePainter
);
canvas.rotate(pi/180*2);
}
复制代码
3.テキスト描画
可以看出这里的文字有两种样式,毫秒数颜色跟随主题色。在 Canvas
文字绘制时可以通过 TextPainter
对象完成。使用该对象必须指定 textDirection
,表示文字的排布方向。
TextPainter textPainter = TextPainter(
textAlign: TextAlign.center,
textDirection: TextDirection.ltr,
);
复制代码
textPainter
绘制内容通过 text
成员指定,该成员类型是 InlineSpan
。所以可以展示富文本,如下代码展示 commonStyle
和 highlightStyle
两种样式的文字。
void drawText(Canvas canvas){
textPainter.text = TextSpan(
text: '00:04',
style: commonStyle,
children: [TextSpan(text: ".65", style: highlightStyle)]);
textPainter.layout(); // 进行布局
final double width = textPainter.size.width;
final double height = textPainter.size.height;
textPainter.paint(canvas, Offset(-width / 2, -height / 2));
}
复制代码
然后,可以根据一个 duration
对象来得到需要展示的文字:
final Duration duration = Duration(minutes: 0, seconds: 4, milliseconds: 650);
int minus = duration.inMinutes % 60;
int second = duration.inSeconds % 60;
int milliseconds = duration.inMilliseconds % 1000;
String commonStr = '${minus.toString().padLeft(2, "0")}:${second.toString().padLeft(2, "0")}';
String highlightStr = ".${(milliseconds ~/ 10).toString().padLeft(2, "0")}";
复制代码
4.绘制指示器
圆的指示器的半径也是根据大圆半径计算的,然后根据时长计算出偏转角度即可。
final double scaleLineWidth = size.width * _kScaleWidthRate;
final double indicatorRadius = size.width * _kIndicatorRadiusRate;
canvas.drawCircle(
Offset(0,-size.width/2+scaleLineWidth+indicatorRadius,),
indicatorRadius/2,
indicatorPainter
);
复制代码
下面来算个简单的数学题,已知当前时长,如何求得该时长在表盘的旋转角度?
只要算出当前分钟内毫秒数
对 一分钟毫秒数(60 * 1000)
占比即可。在绘制指示器时,将画布进行旋转 radians
弧度,不过要注意,为了避免这个旋转变换对其他绘制的影响,需要通过 save
和 restore
方法进行处理。
int second = duration.inSeconds % 60;
int milliseconds = duration.inMilliseconds % 1000;
double radians = (second * 1000 + milliseconds) / (60 * 1000) * 2 * pi;
canvas.save();
canvas.rotate(radians);
// 绘制...
canvas.restore();
复制代码
这样,给出一个 Duration
对象,就能线数处正确的文字及指示器位置:
5. 组件的封装
组件的封装是为了更简洁的使用,如下通过为 StopWatchWidget
组件提供配置即可呈现出对应的绘制效果。就可以将绘制的细节封装起来使用者不需要了解具体是怎么画出来的,只要用 StopWatchWidget
组件即可。
StopWatchWidget(
duration: Duration(minutes: 0, seconds: 24, milliseconds: 850),
radius: 100,
),
复制代码
如下在 StopWatchPainter
中封装了4
个可配置的参数,在 shouldRepaint
方法中,当这四者其中之一发生变化时都允许进行重绘。
class StopWatchPainter extends CustomPainter {
final Duration duration; // 时长
final Color themeColor; // 主题色
final Color scaleColor; // 刻度色
final TextStyle textStyle; // 文本样式
StopWatchPainter({
required this.duration,
required this.themeColor,
required this.scaleColor,
required this.textStyle,
})
// 绘制略...
@override
bool shouldRepaint(covariant StopWatchPainter oldDelegate) {
return oldDelegate.duration != duration ||
oldDelegate.textStyle != textStyle ||
oldDelegate.themeColor != themeColor||
oldDelegate.scaleColor != scaleColor;
}
}
复制代码
StopWatchWidget
から継承されStatelessWidget
、それ自体は状態を変更する機能を引き受けません。つまり、そのプレゼンテーション コンテンツは、ユーザーによって渡された構成情報に関連するだけであり、プレゼンテーション効果を積極的に変更するものではありません。CustomPaint
描画されたアートボードはコンポーネントを通して表示されますStopWatchPainter
。
さらに、いくつかの細かい点に注意してください。構築组件
パラメータは と似ていますが、構成要素は と に直接接触しています。使いやすさを考慮すると、いくつかのデフォルト パラメータを提供するか、または に従って情報を取得する必要があります。現在のテーマ。アートボード オブジェクトは作成を担当し、直面する役割が異なり、カプセル化の考慮事項も異なります。画板
使用者
创造者
class StopWatchWidget extends StatelessWidget {
final double radius;
final Duration duration;
final Color? themeColor;
final TextStyle? textStyle;
final Color scaleColor;
const StopWatchWidget({
Key? key,
required this.radius,
required this.duration,
this.scaleColor = const Color(0xffDADADA),
this.textStyle,
this.themeColor
}) : super(key: key);
TextStyle get commonStyle => TextStyle(
fontSize: radius/3,
fontWeight: FontWeight.w200,
color: const Color(0xff343434),
);
@override
Widget build(BuildContext context) {
TextStyle style = textStyle??commonStyle;
Color themeColor = this.themeColor??Theme.of(context).primaryColor;
return CustomPaint(
painter: StopWatchPainter(
duration: duration,
themeColor: themeColor,
scaleColor: scaleColor,
textStyle: style),
size: Size(radius * 2, radius * 2),
);
}
}
复制代码
今回はここまでで、次の記事ではこの描画コンポーネントを元に、2 番目のダイヤルの開始と一時停止の機能を実現します。見てくれてありがとう〜