1.问题
最近在做一个类似答题问卷的功能,利用RadioListTile来实现单选。这个需求和方案本身是没有什么问题的,本来想实现的效果如下所示。
好的,开始码代码。
Row(
children: <Widget>[
Flexible(
child: RadioListTile<String>(
title: Text('无'),
groupValue: this.value,
value: AnswerType.none.toString(),
onChanged: onChange),
),
Flexible(
child: RadioListTile<String>(
title: Text(
'有一点',
),
groupValue: this.value,
value: AnswerType.little.toString(),
onChanged: onChange),
),
Flexible(
child: RadioListTile<String>(
title: Text('多'),
groupValue: this.value,
value: AnswerType.more.toString(),
onChanged: onChange),
),
Flexible(
child: RadioListTile<String>(
title: Text('很多'),
groupValue: this.value,
value: AnswerType.lot.toString(),
onChanged: onChange),
),
],
)
走你。我尼玛!?
2.解决过程
我尝试了各种方法,比如把字体变小,寻找别的属性修改都不行。
从图上看我们需要解决的问题有两个,1是调整选中框的内边距;2是调整选中框和文本之间的间距。没办法只能从RadioListTile源码入手了。
const RadioListTile({
Key? key,
required this.value,
required this.groupValue,
required this.onChanged,
this.toggleable = false,
this.activeColor,
this.title,
this.subtitle,
this.isThreeLine = false,
this.dense,
this.secondary,
this.selected = false,
this.controlAffinity = ListTileControlAffinity.platform,
this.autofocus = false,
this.contentPadding,
this.shape,
this.tileColor,
this.selectedTileColor,
})
2.1 解决第一个问题
看了一圈只有contentPadding这个属性有用,他的作用是Defines the insets surrounding the contents of the tile.
意思大概是控制上图灰色背景也就是整个控件里面内容的内边距。默认是EdgeInsets.symmetric(horizontal: 16.0)
,我这里设置了contentPadding: EdgeInsets.all( 0.0)
之后解决了第一个问题,这个时候三个字的选项还是显示不开。
2.2 解决第二个问题
继续研究,发现RadioListTile
里面实际上的child是ListTile,而ListTile有个属性是horizontalTitleGap
。这个属性的意思指的是The horizontal gap between the titles and the leading/trailing widgets.
,大概意思就是说我们的文本和Radio之间的间隙,leading/trailing 指的是Radio在内容 前/后。
return MergeSemantics(
child: ListTileTheme.merge(
selectedColor: activeColor ?? Theme.of(context).accentColor,
child: ListTile(//子类是这个Widget
leading: leading,
title: title,
subtitle: subtitle,
trailing: trailing,
isThreeLine: isThreeLine,
dense: dense,
enabled: onChanged != null,
shape: shape,
tileColor: tileColor,
selectedTileColor: selectedTileColor,
onTap: onChanged != null ? () {
if (toggleable && checked) {
onChanged!(null);
return;
}
if (!checked) {
onChanged!(value);
}
} : null,
selected: selected,
autofocus: autofocus,
contentPadding: contentPadding,
),
),
);
但是RadioListTile属性里没有horizontalTitleGap
,不能直接对它进行修改,于是需要重写RadioListTile源码。我们把RadioListTile的源码复制出来改名为RadioListTileSuper,然后将修改为0,就搞定了。
3.总结
这里把源码完整的放出来。这里需要注意的是如果你的environment: sdk: '>=2.7.0 <3.0.0'
这里没有>2.12.0
你需要把源码里的?和!都删除掉。
3.1 修改源码
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class RadioListTileSuper<T> extends StatelessWidget {
const RadioListTileSuper({
Key key,
this.value,
this.groupValue,
this.onChanged,
this.toggleable = false,
this.activeColor,
this.title,
this.subtitle,
this.isThreeLine = false,
this.dense,
this.secondary,
this.selected = false,
this.controlAffinity = ListTileControlAffinity.platform,
this.autofocus = false,
this.contentPadding,
this.shape,
this.tileColor,
this.selectedTileColor,
}) : assert(toggleable != null),
assert(isThreeLine != null),
assert(!isThreeLine || subtitle != null),
assert(selected != null),
assert(controlAffinity != null),
assert(autofocus != null),
super(key: key);
final T value;
final T groupValue;
final ValueChanged<T> onChanged;
final bool toggleable;
final Color activeColor;
final Widget title;
final Widget subtitle;
final Widget secondary;
final bool isThreeLine;
final bool dense;
final bool selected;
final ListTileControlAffinity controlAffinity;
final bool autofocus;
final EdgeInsetsGeometry contentPadding;
bool get checked => value == groupValue;
final ShapeBorder shape;
final Color tileColor;
final Color selectedTileColor;
@override
Widget build(BuildContext context) {
final Widget control = Radio<T>(
value: value,
groupValue: groupValue,
onChanged: onChanged,
toggleable: toggleable,
activeColor: activeColor,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
autofocus: autofocus,
);
Widget leading, trailing;
switch (controlAffinity) {
case ListTileControlAffinity.leading:
case ListTileControlAffinity.platform:
leading = control;
trailing = secondary;
break;
case ListTileControlAffinity.trailing:
leading = secondary;
trailing = control;
break;
}
return MergeSemantics(
child: ListTileTheme.merge(
selectedColor: activeColor ?? Theme.of(context).accentColor,
child: ListTile(
leading: leading,
title: title,
subtitle: subtitle,
trailing: trailing,
horizontalTitleGap:0,//和源码的区别就是加了一个这个
isThreeLine: isThreeLine,
dense: dense,
enabled: onChanged != null,
shape: shape,
tileColor: tileColor,
selectedTileColor: selectedTileColor,
onTap: onChanged != null ? () {
if (toggleable && checked) {
onChanged(null);
return;
}
if (!checked) {
onChanged(value);
}
} : null,
selected: selected,
autofocus: autofocus,
contentPadding: contentPadding,
),
),
);
}
}
3.2 替换RadioListTileSuper
Row(
children: <Widget>[
Flexible(
child: RadioListTileSuper<String>(
title: Text('无'),
groupValue: this.value,
contentPadding: EdgeInsets.symmetric(horizontal: 10.0),//这里根据文本的内容灵活处理文本距离左边的距离
//,因为有的字多有的字短显示长短不一致 不美观
value: AnswerType.none.toString(),
onChanged: onChange),
),
Flexible(
child: RadioListTileSuper<String>(
title: Text('有一点',),
groupValue: this.value,
contentPadding: EdgeInsets.symmetric(horizontal: 0.0),
value: AnswerType.little.toString(),
onChanged: onChange),
),
Flexible(
child: RadioListTileSuper<String>(
title: Text('多'),
groupValue: this.value,
contentPadding: EdgeInsets.symmetric(horizontal: 10.0),
value: AnswerType.more.toString(),
onChanged: onChange),
),
Flexible(
child: RadioListTileSuper<String>(
title: Text('很多'),
groupValue: this.value,
contentPadding: EdgeInsets.symmetric(horizontal: 0.0),
value: AnswerType.lot.toString(),
onChanged: onChange),
),
],
)