【Flutter 專題】73 圖解自定義 ACECheckBox 複選框|8月更文挑戰

語言: CN / TW / HK

      “這是我參與8月更文挑戰的第15天,活動詳情檢視: 8月更文挑戰” https://juejin.cn/post/6987962113788493831

      CheckBox 複選框對於所有的開發朋友並不陌生,Flutter 提供了簡單便捷的使用方法,但針對不同的業務場景,可能會有些許的不同,例如圓角矩形替換為圓形,複選框尺寸調整等;       小菜今天通過對 CheckBox 進行研究擴充套件實現如下功能的 自定義 ACECheckBox 複選框; 1. 複選框可變更未選中狀態顏色; 2. 複選框支援圓形樣式; 3. 複選框支援自定義尺寸;

CheckBox

原始碼分析

const Checkbox({ Key key, @required this.value, // 複選框狀態 true/false/null this.tristate = false, // 是否為三態 @required this.onChanged, // 狀態變更回撥 this.activeColor, // 選中狀態填充顏色 this.checkColor, // 選中狀態對號顏色 this.materialTapTargetSize, // 點選範圍 })       分析原始碼可知,tristatetrue 時複選框有三種狀態;為 falsevalue 不可為 null

案例嘗試

``` return Checkbox( value: state, onChanged: (value) => setState(() => state = value));

return Checkbox(value: state, checkColor: Colors.purpleAccent.withOpacity(0.7), onChanged: (value) => setState(() => state = value));

return Checkbox(value: state, activeColor: Colors.teal.withOpacity(0.3), checkColor: Colors.purpleAccent.withOpacity(0.7), onChanged: (value) => setState(() => state = value));

return Checkbox(tristate: true, value: _triState == null ? _triState : state, activeColor: Colors.teal.withOpacity(0.3), checkColor: Colors.purpleAccent.withOpacity(0.7), onChanged: (value) => setState(() { if (value == null) { _triState = value; } else { _triState = ''; state = value; } })); } ```

ACECheckBox

擴充套件一:變更未選中顏色

原始碼分析

// CheckBox inactiveColor: widget.onChanged != null ? themeData.unselectedWidgetColor : themeData.disabledColor, // ACECheckBox inactiveColor: widget.onChanged != null ? widget.unCheckColor ?? themeData.unselectedWidgetColor : themeData.disabledColor,       分析 CheckBox 原始碼,其中複選框未選中顏色通過 ThemeData.unselectedWidgetColor 設定,修改顏色成本較大,小菜添加了 unCheckColor 屬性,可自由設定未選中狀態顏色,未設定時預設為 ThemeData.unselectedWidgetColor

案例嘗試

``` return ACECheckbox(value: aceState, unCheckColor: Colors.amberAccent, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(value: aceState, checkColor: Colors.red.withOpacity(0.7), unCheckColor: Colors.amberAccent, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(value: aceState, activeColor: Colors.indigoAccent.withOpacity(0.3), checkColor: Colors.red.withOpacity(0.7), unCheckColor: Colors.amberAccent, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(tristate: true, value: _triAceState == null ? _triAceState : aceState, activeColor: Colors.indigoAccent.withOpacity(0.7), checkColor: Colors.red.withOpacity(0.4), unCheckColor: Colors.amberAccent, onChanged: (value) { setState(() { if (value == null) { _triAceState = value; } else { _triAceState = ''; aceState = value; } }); }); ```

擴充套件二:新增圓形樣式

原始碼分析

// 繪製邊框 _drawBorder(canvas, outer, t, offset, type, paint) { assert(t >= 0.0 && t <= 0.5); final double size = outer.width; if ((type ?? ACECheckBoxType.normal) == ACECheckBoxType.normal) { canvas.drawDRRect( outer, outer.deflate(math.min(size / 2.0, _kStrokeWidth + size * t)), paint..strokeWidth = _kStrokeWidth / 2.0..style = PaintingStyle.fill); } else { canvas.drawCircle( Offset(offset.dx + size / 2.0, offset.dy + size / 2.0), size / 2.0, paint..strokeWidth = _kStrokeWidth..style = PaintingStyle.stroke); } } // 繪製填充 _drawInner(canvas, outer, offset, type, paint) { if ((type ?? ACECheckBoxType.normal) == ACECheckBoxType.normal) { canvas.drawRRect(outer, paint); } else { canvas.drawCircle( Offset(offset.dx + outer.width / 2.0, offset.dy + outer.width / 2.0), outer.width / 2.0, paint); } }       分析原始碼可知,CheckBox 邊框和內部填充以及對號全是通過 Canvas 進行繪製,其中繪製邊框時,採用雙層圓角矩形方式 drawDRRect,預設兩層圓角矩形之間是填充方式;小菜新增 ACECheckBoxType 屬性,允許使用者設定圓角樣式;       繪製邊框時畫筆屬性要與 drawDRRect 進行區分;其中複選框邊框和內部填充兩部分需要進行樣式判斷;

案例嘗試

``` return ACECheckbox(value: aceState, unCheckColor: Colors.amberAccent, type: ACECheckBoxType.circle, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(value: aceState, checkColor: Colors.red.withOpacity(0.7), unCheckColor: Colors.amberAccent, type: ACECheckBoxType.circle, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox( value: aceState, activeColor: Colors.indigoAccent.withOpacity(0.3), checkColor: Colors.red.withOpacity(0.7), unCheckColor: Colors.amberAccent, type: ACECheckBoxType.circle, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(tristate: true, value: _triAceState == null ? _triAceState : aceState, activeColor: Colors.indigoAccent.withOpacity(0.7), checkColor: Colors.red.withOpacity(0.4), unCheckColor: Colors.amberAccent, type: ACECheckBoxType.circle, onChanged: (value) { setState(() { if (value == null) { _triAceState = value; } else { _triAceState = ''; aceState = value; } }); }); ```

擴充套件三:自定義尺寸

原始碼分析

``` @override void paint(PaintingContext context, Offset offset) { final Canvas canvas = context.canvas; paintRadialReaction(canvas, offset, size.center(Offset.zero));

final Paint strokePaint = _createStrokePaint(checkColor); final Offset origin = offset + (size / 2.0 - Size.square(width) / 2.0); final AnimationStatus status = position.status; final double tNormalized = status == AnimationStatus.forward || status == AnimationStatus.completed ? position.value : 1.0 - position.value; if (_oldValue == false || value == false) { final double t = value == false ? 1.0 - tNormalized : tNormalized; final RRect outer = _outerRectAt(origin, t); final Paint paint = Paint()..color = _colorAt(t); if (t <= 0.5) { _drawBorder(canvas, outer, t, origin, type, paint); } else { _drawInner(canvas, outer, origin, type, paint); final double tShrink = (t - 0.5) * 2.0; if (_oldValue == null || value == null) _drawDash(canvas, origin, tShrink, width, strokePaint); else _drawCheck(canvas, origin, tShrink, width, strokePaint); } } else { final RRect outer = _outerRectAt(origin, 1.0); final Paint paint = Paint()..color = _colorAt(1.0); _drawInner(canvas, outer, origin, type, paint); if (tNormalized <= 0.5) { final double tShrink = 1.0 - tNormalized * 2.0; if (_oldValue == true) _drawCheck(canvas, origin, tShrink, width, strokePaint); else _drawDash(canvas, origin, tShrink, width, strokePaint); } else { final double tExpand = (tNormalized - 0.5) * 2.0; if (value == true) _drawCheck(canvas, origin, tExpand, width, strokePaint); else _drawDash(canvas, origin, tExpand, width, strokePaint); } } } ```       分析原始碼 CheckBox 尺寸是固定的 Checkbox.width = 18.0,無法調整尺寸,小菜新增一個 width 引數,預設為 18.0 允許使用者按需調整尺寸;如上是繪製複選框的三態情況;

案例嘗試

``` return ACECheckbox(value: aceState, width: 10.0, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(value: aceState, checkColor: Colors.red.withOpacity(0.7), width: 18.0, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(value: aceState, activeColor: Colors.indigoAccent.withOpacity(0.3), checkColor: Colors.red.withOpacity(0.7), width: 28.0, onChanged: (value) => setState(() => aceState = value));

return ACECheckbox(tristate: true, value: _triAceState == null ? _triAceState : aceState, activeColor: Colors.indigoAccent.withOpacity(0.7), checkColor: Colors.red.withOpacity(0.4), type: ACECheckBoxType.normal, width: 38.0, onChanged: (value) { setState(() { if (value == null) { _triAceState = value; } else { _triAceState = ''; aceState = value; } }); }); ```


      ACECheckBox 原始碼


      小菜在擴充套件過程中,學習 CheckBox 原始碼,還有很多有意思的地方,包括對 true/false/null 三態的處理方式,以及 .lerp 動畫效果的應用,在實際應用中都很有幫助;       小菜自定義 ACECheckBox 的擴充套件還不夠完善,目前暫未新增圖片或 Icon 的樣式,以後有機會一同擴充套件;如有錯誤請多多指導!

來源: 阿策小和尚