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

??????CheckBox 復(fù)選框?qū)τ谒械拈_發(fā)朋友并不陌生枚抵,Flutter 提供了簡單便捷的使用方法线欲,但針對不同的業(yè)務(wù)場景,可能會有些許的不同汽摹,例如圓角矩形替換為圓形李丰,復(fù)選框尺寸調(diào)整等;
??????小菜今天通過對 CheckBox 進行研究擴展實現(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)對號顏色
    this.materialTapTargetSize, // 點擊范圍
})

??????分析源碼可知嗜憔,tristatetrue 時復(fù)選框有三種狀態(tài);為 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 源碼吉捶,其中復(fù)選框未選中顏色通過 ThemeData.unselectedWidgetColor 設(shè)置,修改顏色成本較大皆尔,小菜添加了 unCheckColor 屬性呐舔,可自由設(shè)置未選中狀態(tài)顏色,未設(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;
          }
        });
      });

擴展二:添加圓形樣式

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

案例嘗試
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揭斧,無法調(diào)整尺寸,小菜添加一個 width 參數(shù)峻堰,默認(rèn)為 18.0 允許用戶按需調(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 源碼


??????小菜在擴展過程中捐名,學(xué)習(xí) CheckBox 源碼旦万,還有很多有意思的地方,包括對 true/false/null 三態(tài)的處理方式镶蹋,以及 .lerp 動畫效果的應(yīng)用成艘,在實際應(yīng)用中都很有幫助;
??????小菜自定義 ACECheckBox 的擴展還不夠完善贺归,目前暫未添加圖片或 Icon 的樣式淆两,以后有機會一同擴展;如有錯誤請多多指導(dǎo)拂酣!

來源: 阿策小和尚

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末秋冰,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子婶熬,更是在濱河造成了極大的恐慌剑勾,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件赵颅,死亡現(xiàn)場離奇詭異虽另,居然都是意外死亡,警方通過查閱死者的電腦和手機饺谬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門捂刺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人募寨,你說我怎么就攤上這事叠萍。” “怎么了绪商?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵苛谷,是天一觀的道長。 經(jīng)常有香客問我格郁,道長腹殿,這世上最難降的妖魔是什么独悴? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮锣尉,結(jié)果婚禮上刻炒,老公的妹妹穿的比我還像新娘。我一直安慰自己自沧,他們只是感情好坟奥,可當(dāng)我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著拇厢,像睡著了一般爱谁。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上孝偎,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天访敌,我揣著相機與錄音,去河邊找鬼衣盾。 笑死寺旺,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的势决。 我是一名探鬼主播阻塑,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼果复!你這毒婦竟也來了叮姑?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤据悔,失蹤者是張志新(化名)和其女友劉穎传透,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體极颓,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡朱盐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了菠隆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片兵琳。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖骇径,靈堂內(nèi)的尸體忽然破棺而出躯肌,到底是詐尸還是另有隱情,我是刑警寧澤破衔,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布清女,位于F島的核電站,受9級特大地震影響晰筛,放射性物質(zhì)發(fā)生泄漏嫡丙。R本人自食惡果不足惜拴袭,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望曙博。 院中可真熱鬧拥刻,春花似錦、人聲如沸父泳。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惠窄。三九已至蒸眠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間睬捶,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工近刘, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留擒贸,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓觉渴,卻偏偏與公主長得像介劫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子案淋,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容