Flutter 73: 圖解自定義 ACECheckBox 復(fù)選框

??????CheckBox 復(fù)選框?qū)τ谒械拈_(kāi)發(fā)朋友并不陌生,Flutter 提供了簡(jiǎn)單便捷的使用方法,但針對(duì)不同的業(yè)務(wù)場(chǎng)景,可能會(huì)有些許的不同,例如圓角矩形替換為圓形,復(fù)選框尺寸調(diào)整等;
??????小菜今天通過(guò)對(duì) CheckBox 進(jìn)行研究擴(kuò)展實(shí)現(xiàn)如下功能的 自定義 ACECheckBox 復(fù)選框;

  1. 復(fù)選框可變更未選中狀態(tài)顏色;
  2. 復(fù)選框支持圓形樣式;
  3. 復(fù)選框支持自定義尺寸;

CheckBox

源碼分析

const Checkbox({
    Key key,
    @required this.value,       // 復(fù)選框狀態(tài) true/false/null
    this.tristate = false,      // 是否為三態(tài)
    @required this.onChanged,   // 狀態(tài)變更回調(diào)
    this.activeColor,           // 選中狀態(tài)填充顏色
    this.checkColor,            // 選中狀態(tài)對(duì)號(hào)顏色
    this.materialTapTargetSize, // 點(diǎn)擊范圍
})

??????分析源碼可知,tristatetrue 時(shí)復(fù)選框有三種狀態(tài);為 false 時(shí) value 不可為 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

擴(kuò)展一:變更未選中顏色

源碼分析
// CheckBox
inactiveColor: widget.onChanged != null ? themeData.unselectedWidgetColor : themeData.disabledColor,
// ACECheckBox
inactiveColor: widget.onChanged != null
    ? widget.unCheckColor ?? themeData.unselectedWidgetColor
    : themeData.disabledColor,

??????分析 CheckBox 源碼,其中復(fù)選框未選中顏色通過(guò) ThemeData.unselectedWidgetColor 設(shè)置,修改顏色成本較大,小菜添加了 unCheckColor 屬性,可自由設(shè)置未選中狀態(tài)顏色,未設(shè)置時(shí)默認(rèn)為 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;
          }
        });
      });

擴(kuò)展二:添加圓形樣式

源碼分析
// 繪制邊框
_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 邊框和內(nèi)部填充以及對(duì)號(hào)全是通過(guò) Canvas 進(jìn)行繪制,其中繪制邊框時(shí),采用雙層圓角矩形方式 drawDRRect,默認(rèn)兩層圓角矩形之間是填充方式;小菜添加 ACECheckBoxType 屬性,允許用戶(hù)設(shè)置圓角樣式;
??????繪制邊框時(shí)畫(huà)筆屬性要與 drawDRRect 進(jìn)行區(qū)分;其中復(fù)選框邊框和內(nèi)部填充兩部分需要進(jìn)行樣式判斷;

案例嘗試
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;
          }
        });
      });

擴(kuò)展三:自定義尺寸

源碼分析
@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,無(wú)法調(diào)整尺寸,小菜添加一個(gè) width 參數(shù),默認(rèn)為 18.0 允許用戶(hù)按需調(diào)整尺寸;如上是繪制復(fù)選框的三態(tài)情況;

案例嘗試
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 源碼


??????小菜在擴(kuò)展過(guò)程中,學(xué)習(xí) CheckBox 源碼,還有很多有意思的地方,包括對(duì) true/false/null 三態(tài)的處理方式,以及 .lerp 動(dòng)畫(huà)效果的應(yīng)用,在實(shí)際應(yīng)用中都很有幫助;
??????小菜自定義 ACECheckBox 的擴(kuò)展還不夠完善,目前暫未添加圖片或 Icon 的樣式,以后有機(jī)會(huì)一同擴(kuò)展;如有錯(cuò)誤請(qǐng)多多指導(dǎo)!

來(lái)源: 阿策小和尚

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Checkbox復(fù)選框是一個(gè)可能每一個(gè)網(wǎng)站都在使用的HTML元素,但大多數(shù)人并不給它們?cè)O(shè)置樣式,所以在絕大多數(shù)網(wǎng)站...
    mhy_web閱讀 7,864評(píng)論 0 0
  • 小菜剛學(xué)習(xí)了 TextField 的基本用法,今天特意學(xué)習(xí)一下 TextField InputDecoration...
    阿策神奇閱讀 2,295評(píng)論 1 12
  • 小菜前幾天嘗試了 Flutter Stepper 簡(jiǎn)單實(shí)用,但樣式等方面也有局限性,Stepper 的使用小菜在上...
    阿策神奇閱讀 3,288評(píng)論 0 14
  • 概述 在網(wǎng)易云課堂學(xué)習(xí)李南江老師的《從零玩轉(zhuǎn)HTML5前端+跨平臺(tái)開(kāi)發(fā)》時(shí),所整理的筆記。筆記內(nèi)容為根據(jù)個(gè)人需求所...
    墨荀閱讀 2,475評(píng)論 0 7
  • 第一章 F12: element.style 內(nèi)聯(lián)樣式(可以直接在上面寫(xiě)代碼進(jìn)行簡(jiǎn)單調(diào)試) user agent...
    fastwe閱讀 1,652評(píng)論 0 0

友情鏈接更多精彩內(nèi)容