import 'dart:ui'; import 'package:flutter/material.dart'; import 'package:flutter/semantics.dart'; class UserLevel extends LeafRenderObjectWidget { const UserLevel( this.level, { super.key, this.height = 11, this.flash = false, }); final double height; final int level; final bool flash; @override RenderObject createRenderObject(BuildContext context) { return RenderLevel(height, level, flash); } @override void updateRenderObject( BuildContext context, RenderLevel renderObject, ) { renderObject ..height = height ..level = level ..flash = flash; } } class RenderLevel extends RenderBox { RenderLevel(this._height, this._level, this._flash); double _height; set height(double value) { if (_height == value) return; _height = value; markNeedsLayout(); } int _level; set level(int value) { if (_level == value) return; _level = value; markNeedsPaint(); markNeedsSemanticsUpdate(); } bool _flash; set flash(bool value) { if (_flash == value) return; _flash = value; markNeedsLayout(); } @override Size computeDryLayout(covariant BoxConstraints constraints) { return Size( (_flash ? LevelCanvas._extendR : LevelCanvas._totalR) * _height / LevelCanvas._totalB, _height, ); } @override void performLayout() { size = computeDryLayout(constraints); } @override void paint(PaintingContext context, Offset offset) { final paint = Paint()..color = lookupBackgroundColor(_level); LevelCanvas(context.canvas) ..save() ..translate(offset.dx, offset.dy) ..scale(_height / LevelCanvas._totalB) ..drawLevelBack(paint, bolt: _flash) ..drawLevelLv(paint..color = Colors.white) ..drawLEDigit(_level, paint) ..restore(); } @override void describeSemanticsConfiguration(SemanticsConfiguration config) { super.describeSemanticsConfiguration(config); config.label = '${_flash ? "硬核" : ""}$_level级'; } static Color lookupBackgroundColor(int level) { return switch (level) { 0 || 1 => const Color(0xFFC0C0C0), 2 => const Color(0xFF8BD29B), 3 => const Color(0xFF7BCDEF), 4 => const Color(0xFFFEBB8B), 5 => const Color(0xFFEE672A), _ => const Color(0xFFF04C49), }; } } extension type LevelCanvas(Canvas _) implements Canvas { // ========== 布局常量 ========== static const _r = Radius.circular(20); static const double _left = 629; static const double _right = 877; static const double _colW = 68; // 竖段宽度 static const double _lColR = _left + _colW; // 697 static const double _rColL = _right - _colW; // 810 // 三条横线的边界 static const double _rowH = 68; static const double _rowSp = 146; static const double _topY = 55; static const double _topYB = _topY + _rowH; // 123 static const double _midY = _topY + _rowSp; // 201 static const double _midYB = _midY + _rowH; // 269 static const double _botY = _midY + _rowSp; // 347 static const double _botYB = _botY + _rowH; // 415 // 竖段拼接用的中心线 static const double _midMid = (_midY + _midYB) / 2; // 235 static final _boltIcon = (ParagraphBuilder( ParagraphStyle( fontSize: 460, fontFamily: Icons.bolt_rounded.fontFamily, height: 1, fontWeight: FontWeight.w900, textDirection: TextDirection.ltr, ), )..addText(.fromCharCode(Icons.bolt_rounded.codePoint))).build() ..layout(const ParagraphConstraints(width: double.infinity)); void drawBolt() => drawParagraph(_boltIcon, const Offset(840, 5)); void _draw1(Paint paint) { final path = Path() ..addRRect(const .fromLTRBXY(673, _botY, 833, _botYB, 20, 20)) ..addRRect( .fromLTRBAndCorners( 673, _topY, 787, _topYB, topLeft: _r, bottomLeft: _r, topRight: _r, ), ) ..addRect(const .fromLTRB(719, _topYB, 787, _botY)); drawPath(path, paint); } void drawLEDigit(int digit, Paint paint) { if (digit == 1) return _draw1(paint); final bits = switch (digit) { 0 => 0x7E, 2 => 0x6D, 3 => 0x79, 4 => 0x33, 5 => 0x5B, 6 => 0x5F, 7 => 0x70, 8 => 0x7F, 9 => 0x7B, // _ => throw ArgumentError('Unsupported digit: $digit'), _ => 0x4F, // `E` }; _drawSegments( bits & 0x40 != 0, bits & 0x20 != 0, bits & 0x10 != 0, bits & 0x08 != 0, bits & 0x04 != 0, bits & 0x02 != 0, bits & 0x01 != 0, paint, ); } void _drawSegments( bool a, bool b, bool c, bool d, bool e, bool f, bool g, Paint paint, ) { // 横段 final path = Path(); if (a) { _addRRect( _left, _topY, _right, _topYB, _r, _r, f ? .zero : _r, b ? .zero : _r, path, ); } if (g) { _addRRect( _left, _midY, _right, _midYB, f ? .zero : _r, b ? .zero : _r, e ? .zero : _r, c ? .zero : _r, path, ); } if (d) { _addRRect( _left, _botY, _right, _botYB, e ? .zero : _r, c ? .zero : _r, _r, _r, path, ); } // 竖段 // 左上竖段 f if (f) { final top = a ? _topYB : _topY; // 有上横则齐底,否则到顶 final bottom = g ? _midY : (e ? _midMid : _midYB); final rTop = a ? Radius.zero : _r; final rBot = g || e ? Radius.zero : _r; _addRRect(_left, top, _lColR, bottom, rTop, rTop, rBot, rBot, path); } // 右上竖段 b if (b) { final top = a ? _topYB : _topY; final bottom = g ? _midY : (c ? _midMid : _midYB); final rTop = a ? Radius.zero : _r; final rBot = g || c ? Radius.zero : _r; _addRRect(_rColL, top, _right, bottom, rTop, rTop, rBot, rBot, path); } // 左下竖段 e if (e) { final top = g ? _midYB : (f ? _midMid : _midY); final bottom = d ? _botY : _botYB; final rTop = g || f ? Radius.zero : _r; final rBot = d ? Radius.zero : _r; _addRRect(_left, top, _lColR, bottom, rTop, rTop, rBot, rBot, path); } // 右下竖段 c if (c) { final top = g ? _midYB : (b ? _midMid : _midY); final bottom = d ? _botY : _botYB; final rTop = g || b ? Radius.zero : _r; final rBot = d ? Radius.zero : _r; _addRRect(_rColL, top, _right, bottom, rTop, rTop, rBot, rBot, path); } drawPath(path, paint); } /// 绘制圆角矩形,四角全零时退化为矩形 void _addRRect( double l, double t, double r, double b, Radius tl, Radius tr, Radius bl, Radius br, Path path, ) { if (tl == .zero && tr == .zero && bl == .zero && br == .zero) { path.addRect(.fromLTRB(l, t, r, b)); } else { path.addRRect( .fromLTRBAndCorners( l, t, r, b, topLeft: tl, topRight: tr, bottomLeft: bl, bottomRight: br, ), ); } } static const double _vLeft = 296; static final vV = Path() ..moveTo(_vLeft, 282) ..lineTo(_vLeft, 292) ..arcToPoint( const Offset(300, 313), radius: const .circular(50), clockwise: false, ) ..lineTo(395, 408) ..arcToPoint( const Offset(419.5, 415), radius: const .circular(50), clockwise: false, ) ..arcToPoint( const Offset(444, 408), radius: const .circular(50), clockwise: false, ) ..lineTo(539, 313) ..arcToPoint( const Offset(543, 292), radius: const .circular(50), clockwise: false, ) ..lineTo(543, 282) ..lineTo(476, 282) ..lineTo(419.5, 340) ..lineTo(363, 282) ..close(); void drawLevelLv(Paint paint) { const double lvTop = 106; drawPath( Path() ..addRRect( .fromLTRBAndCorners( 56, lvTop, 123, _botYB, topLeft: _r, topRight: _r, bottomLeft: _r, ), ) ..addRRect( .fromLTRBAndCorners( 123, _botY, 256, _botYB, topRight: _r, bottomRight: _r, ), ), paint, ); final path = Path() ..addRRect( RRect.fromLTRBAndCorners( _vLeft, lvTop, 363, 282, topLeft: _r, topRight: _r, ), ) ..addRRect( RRect.fromLTRBAndCorners( 476, lvTop, 543, 282, topLeft: _r, topRight: _r, ), ) ..addPath(vV, .zero); drawPath(path, paint); } static const double _totalR = 930; static const double _extendR = 1250; static const double _totalB = 466; void drawLevelBack(Paint paint, {bool bolt = false}) { const radius = Radius.circular(27); final double right = bolt ? _extendR : _totalR; final path = Path() ..addRRect( RRect.fromLTRBAndCorners( 0, 48, right, _totalB, topLeft: radius, bottomLeft: radius, bottomRight: radius, ), ) ..addRRect( RRect.fromLTRBAndCorners( 576, 0, right, 48, topLeft: radius, topRight: radius, ), ); drawPath(path, paint); if (bolt) drawBolt(); } }