??????CheckBox 復(fù)選框?qū)τ谒械拈_發(fā)朋友并不陌生枚抵,Flutter 提供了簡單便捷的使用方法线欲,但針對不同的業(yè)務(wù)場景,可能會有些許的不同汽摹,例如圓角矩形替換為圓形李丰,復(fù)選框尺寸調(diào)整等;
??????小菜今天通過對 CheckBox 進行研究擴展實現(xiàn)如下功能的 自定義 ACECheckBox 復(fù)選框逼泣;
- 復(fù)選框可變更未選中狀態(tài)顏色趴泌;
- 復(fù)選框支持圓形樣式;
- 復(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, // 點擊范圍
})
??????分析源碼可知嗜憔,tristate 為 true 時復(fù)選框有三種狀態(tài);為 false 時 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
擴展一:變更未選中顏色
源碼分析
// 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)拂酣!
來源: 阿策小和尚