Radio 單選框在平常應用中很常見,Flutter 提供的單選框與 Android 提供的略有不一樣,和尚簡單瞭解一下並對其進行部分擴展;
web
Radio
Radio 單選框是在一組選項中,互斥的選擇單個選項;canvas
源碼分析
class Radio<T> extends StatefulWidget {
const Radio({
Key key,
@required this.value, // 當前單選框設置的值
@required this.groupValue, // 當前單選框選定狀態的值
@required this.onChanged, // 選中回調
this.activeColor, // 選中狀態顏色
this.focusColor, // 獲取焦點時顏色
this.hoverColor, // 高亮時顏色
this.materialTapTargetSize, // 點擊範圍最小大小
this.focusNode,
this.autofocus = false,
})
}
簡單分析源碼可得,Radio 是一個有狀態的 StatefulWidget 小組件;Radio 單選框自己不保持任何狀態,經過 onChanged 回調,來判斷當前 value 是否與 groupValue 選項組中對應的 item 是否一致,來判斷選中狀態;通常經過調用 State.setState() 更新單選按鈕的 groupValue 從而響應 onChanged 回調;微信
案例嘗試
onChanged
Radio 單選框通常分爲三個狀態,分別爲未選中狀態、選中狀態和不可選中狀態;onChanged 爲單選框選中的回調,根據 value 和 groupValue 匹配是否爲選中狀態;當 onChanged 爲 null 時,單選框爲不可選中狀態;源碼分析
return Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Row(children: <Widget>[
Radio(value: GenderType.MALE, groupValue: _groupValue,
onChanged: (val) { print('---onChanged---$val');
setState(() => _groupValue = val);
}),
Text('男')
]),
Row(children: <Widget>[
Radio(value: GenderType.FEMALE, groupValue: _groupValue,
onChanged: (val) { print('---onChanged---$val');
setState(() => _groupValue = val);
}),
Text('女')
]),
Row(children: <Widget>[
Radio(value: GenderType.FEMALE, groupValue: _groupValue, onChanged: null),
Text('不可選中')
])
]);
materialTapTargetSize
materialTapTargetSize 爲默認 Radio 可選中點擊的最小範圍;主要分爲 padded 和 shrinkWrap 兩種狀態,分析源碼能夠看到二者尺寸相差 8.0,所以 Radio 所在的範圍是不可變動的,這也是和尚準備自定義 ACERadio 擴展方向之一;ui
switch (widget.materialTapTargetSize ?? themeData.materialTapTargetSize) {
case MaterialTapTargetSize.padded:
size = const Size(2 * kRadialReactionRadius + 8.0, 2 * kRadialReactionRadius + 8.0);
break;
case MaterialTapTargetSize.shrinkWrap:
size = const Size(2 * kRadialReactionRadius, 2 * kRadialReactionRadius);
break;
}
return Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Row(children: <Widget>[
Text('padded'),
Container( color: Colors.grey.withOpacity(0.4),
child: Radio( value: GenderType.MALE, groupValue: _groupValue,
materialTapTargetSize: MaterialTapTargetSize.padded,
onChanged: (val) { print('---onChanged---$val');
setState(() => _groupValue = val);
})),
]),
SizedBox(width: 10),
Row(children: <Widget>[
Container( color: Colors.grey.withOpacity(0.4),
child: Radio( value: GenderType.FEMALE, groupValue: _groupValue,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
onChanged: (val) { print('---onChanged---$val');
setState(() => _groupValue = val);
})),
Text('shrinkWrap')
])
]);
activeColor
activeColor 爲單選框選中狀態時繪製的顏色;若未設置,默認爲 ThemeData.toggleableActiveColor 對應顏色;this
return Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Row(children: <Widget>[
Radio( value: GenderType.MALE, groupValue: _groupValue,
activeColor: Colors.green,
onChanged: (val) { print('---onChanged---$val');
setState(() => _groupValue = val);
}),
Text('男', style: TextStyle( color: _groupValue == GenderType.MALE ? Colors.green : Colors.black))
]),
Row(children: <Widget>[
Radio( value: GenderType.FEMALE, groupValue: _groupValue,
activeColor: Colors.red,
onChanged: (val) { print('---onChanged---$val');
setState(() => _groupValue = val);
}),
Text('女', style: TextStyle( color: _groupValue == GenderType.FEMALE ? Colors.red : Colors.black))
])
]);
focusColor & hoverColor
focusColor / hoverColor 分別對應獲取焦點時的顏色與點擊高亮顏色;但和尚嘗試了屢次效果並不明顯,因需求場景較少,暫不作處理;spa
未選中顏色 & 不可選顏色
Radio 並未提供未選中狀態和不可選中狀態按鈕顏色;和尚分析源碼,發現 未選中狀態 與 ThemeData.unselectedWidgetColor 顏色對應,不可選中狀態 與 ThemeData.disabledColor 對應;若須要動態修改這兩種顏色狀態,能夠在對應的 Radio 外設置 ThemeData 對應的顏色狀態便可;.net
return Theme(
data: ThemeData(unselectedWidgetColor: Colors.deepPurple, disabledColor: Colors.brown),
child:
Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Row(children: <Widget>[
Radio( value: GenderType.MALE, groupValue: _groupValue,
activeColor: Colors.green,
onChanged: (val) { print('---onChanged---$val');
setState(() => _groupValue = val);
}),
Text('男', style: TextStyle( color: _groupValue == GenderType.MALE ? Colors.green : Colors.black))
]),
Row(children: <Widget>[
Radio( value: GenderType.FEMALE, groupValue: _groupValue,
activeColor: Colors.red,
onChanged: (val) { print('---onChanged---$val');
setState(() => _groupValue = val);
}),
Text('女', style: TextStyle( color: _groupValue == GenderType.FEMALE ? Colors.red : Colors.black))
]),
Row(children: <Widget>[
Radio( value: GenderType.FEMALE, groupValue: _groupValue, onChanged: null),
Text('不可選中')
])
]));
ACERadio
爲了更靈活的應用 Radio 單選框,和尚準備在此基礎上擴展以下幾個方面:3d
動態設置 未選中狀態顏色;code
動態設置 不可選中狀態顏色;
動態設置 選中框按鈕尺寸;
添加狀態 取消按鈕外邊距;
源碼擴展
和尚自定義了三種 ACEMaterialTapTargetSize 尺寸,增長了 zero 類型取消按鈕外邊距;
enum ACEMaterialTapTargetSize { padded, shrinkWrap, zero }
double radius = widget.radioSize ?? kRadialReactionRadius;
switch (widget.materialTapTargetSize ?? ACEMaterialTapTargetSize.padded) {
case ACEMaterialTapTargetSize.padded:
size = Size(2 * radius + 8.0, 2 * radius + 8.0);
break;
case ACEMaterialTapTargetSize.shrinkWrap:
size = Size(2 * radius, 2 * radius);
break;
case ACEMaterialTapTargetSize.zero:
size = Size(radius, radius);
break;
}
和尚優先判斷添加的未選中狀態顏色和不可選中狀態顏色;若未設置以 ThemeData 爲準;
Color _getInactiveColor(ThemeData themeData) {
return enabled ? widget.unCheckedColor ?? themeData.unselectedWidgetColor
: widget.disabledColor ?? themeData.disabledColor;
}
和尚添加一個 radioSize 屬性,在繪製按鈕時,按比例動態繪製按鈕尺寸;
// Outer circle
final Paint paint = Paint()
..color = Color.lerp(inactiveColor, radioColor, position.value)..style = PaintingStyle.stroke
..strokeWidth = radioSize / 4 ?? 2.0;
canvas.drawCircle(center, radioSize ?? _kOuterRadius, paint);
// Inner circle
if (!position.isDismissed) {
paint.style = PaintingStyle.fill;
canvas.drawCircle(center,
(radioSize != null ? radioSize * 4.5 / 8 : _kInnerRadius) * position.value, paint);
}
案例嘗試
取消按鈕外邊距
Radio 默認提供了兩種最小可點擊範圍,但和尚想取消按鈕總體外邊距,因而添加一種 ACEMaterialTapTargetSize.zero 方式來僅設置按鈕尺寸;
return Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Row(children: <Widget>[
Text('padded'),
Container(color: Colors.grey.withOpacity(0.4),
child: ACERadio(value: GenderType.MALE, groupValue: _groupValue,
materialTapTargetSize: ACEMaterialTapTargetSize.padded,
onChanged: (val) => setState(() => _groupValue = val))),
]),
Row(children: <Widget>[
Container(color: Colors.grey.withOpacity(0.4),
child: ACERadio(value: GenderType.FEMALE, groupValue: _groupValue,
materialTapTargetSize: ACEMaterialTapTargetSize.shrinkWrap,
onChanged: (val) => setState(() => _groupValue = val))),
Text('shrinkWrap')
]),
Row(children: <Widget>[
Container(color: Colors.grey.withOpacity(0.4),
child: ACERadio(value: GenderType.FEMALE, groupValue: _groupValue,
materialTapTargetSize: ACEMaterialTapTargetSize.zero,
onChanged: null)),
Text('zero')
])
]);
未選中狀態 & 不可選中狀態
未選中狀態 & 不可選中狀態 能夠經過 ThemeData 來動態修改,和尚爲了方便,添加了 unCheckedColor & disabledColor 可直接進行設置;
return Row(mainAxisAlignment: MainAxisAlignment.center, children: <Widget>[
Row(children: <Widget>[
ACERadio(
value: GenderType.MALE, groupValue: _groupValue,
activeColor: Colors.green, unCheckedColor: Colors.deepPurple,
onChanged: (val) { print('---onChanged---$val');
setState(() => _groupValue = val);
}),
Text('男', style: TextStyle( color: _groupValue == GenderType.MALE ? Colors.green : Colors.black))
]),
Row(children: <Widget>[
ACERadio(
value: GenderType.FEMALE, groupValue: _groupValue,
activeColor: Colors.red, unCheckedColor: Colors.deepPurple,
onChanged: (val) { print('---onChanged---$val');
setState(() => _groupValue = val);
}),
Text('女', style: TextStyle( color: _groupValue == GenderType.FEMALE ? Colors.red : Colors.black))
]),
Row(children: <Widget>[
ACERadio(
value: GenderType.FEMALE, groupValue: _groupValue,
disabledColor: Colors.brown, unCheckedColor: Colors.deepPurple,
onChanged: null),
Text('不可選中')
])
]);
選中框按鈕尺寸
Radio 單選框尺寸是固定的,和尚爲了更方便的修改,添加了 radioSize 尺寸來動態修改按鈕尺寸,且在動態設置按鈕尺寸以後依舊支持最小點擊範圍的三種樣式;
return Column(mainAxisSize: MainAxisSize.min, children: <Widget>[
Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
ACERadio(radioSize: 6.0, value: SizeType.SIZE_6, groupValue: _groupSizeValue, onChanged: (val) => setState(() => _groupSizeValue = val)),
Text('Size:6.0*6.0')
]),
Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
ACERadio(radioSize: 8.0, value: SizeType.SIZE_8, groupValue: _groupSizeValue, onChanged: (val) => setState(() => _groupSizeValue = val)),
Text('Size:8.0*8.0')
]),
Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
ACERadio(radioSize: 10.0, value: SizeType.SIZE_10, groupValue: _groupSizeValue, onChanged: (val) => setState(() => _groupSizeValue = val)),
Text('Size:10.0*10.0')
]),
Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
ACERadio(radioSize: 14.0, value: SizeType.SIZE_14, groupValue: _groupSizeValue, onChanged: (val) => setState(() => _groupSizeValue = val)),
Text('Size:14.0*14.0')
]),
Row(mainAxisSize: MainAxisSize.min, children: <Widget>[
ACERadio(radioSize: 18.0, value: SizeType.SIZE_18, groupValue: _groupSizeValue, onChanged: (val) => setState(() => _groupSizeValue = val)),
Text('Size:18.0*18.0')
]),
]);
ACERadio 案例源碼
和尚對底層源碼還不夠深刻,只是對 Radio 單選框的一點小擴展;若有錯誤,請多多指導!
來源:阿策小和尚
本文分享自微信公衆號 - 阿策小和尚(gh_8297e718c166)。
若有侵權,請聯繫 support@oschina.cn 刪除。
本文參與「OSC源創計劃」,歡迎正在閱讀的你也加入,一塊兒分享。