如何在Ffltter
中创建像这样的自定义单选按钮组
推荐答案
这是完整的代码
复制代码
class CustomRadio extends StatefulWidget {
@override
createState() {
return new CustomRadioState();
}
}
class CustomRadioState extends State<CustomRadio> {
List<RadioModel> sampleData = new List<RadioModel>();
@override
void initState() {
// TODO: implement initState
super.initState();
sampleData.add(new RadioModel(false, 'A', 'April 18'));
sampleData.add(new RadioModel(false, 'B', 'April 17'));
sampleData.add(new RadioModel(false, 'C', 'April 16'));
sampleData.add(new RadioModel(false, 'D', 'April 15'));
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("ListItem"),
),
body: new ListView.builder(
itemCount: sampleData.length,
itemBuilder: (BuildContext context, int index) {
return new InkWell(
//highlightColor: Colors.red,
splashColor: Colors.blueAccent,
onTap: () {
setState(() {
sampleData.forEach((element) => element.isSelected = false);
sampleData[index].isSelected = true;
});
},
child: new RadioItem(sampleData[index]),
);
},
),
);
}
}
class RadioItem extends StatelessWidget {
final RadioModel _item;
RadioItem(this._item);
@override
Widget build(BuildContext context) {
return new Container(
margin: new EdgeInsets.all(15.0),
child: new Row(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Container(
height: 50.0,
width: 50.0,
child: new Center(
child: new Text(_item.buttonText,
style: new TextStyle(
color:
_item.isSelected ? Colors.white : Colors.black,
//fontWeight: FontWeight.bold,
fontSize: 18.0)),
),
decoration: new BoxDecoration(
color: _item.isSelected
? Colors.blueAccent
: Colors.transparent,
border: new Border.all(
width: 1.0,
color: _item.isSelected
? Colors.blueAccent
: Colors.grey),
borderRadius: const BorderRadius.all(const Radius.circular(2.0)),
),
),
new Container(
margin: new EdgeInsets.only(left: 10.0),
child: new Text(_item.text),
)
],
),
);
}
}
class RadioModel {
bool isSelected;
final String buttonText;
final String text;
RadioModel(this.isSelected, this.buttonText, this.text);
}
要使用,请执行以下操作:
复制代码
void main() {
runApp(new MaterialApp(
home: new CustomRadio(),
));
}
屏幕截图:
Flutter自定义组件实现图片单选按钮
效果图
Flutter中的Radio自定义空间较小,为了实现图中的效果我们需要自定义,最终要实现的目标是多个组件之间只要设置同一个controller即可实现单选效果,点击组件后无需外层调用setState,并且可以提供回调函数。
1、先说用法:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_radio/image_radio.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var images = [
"https://file03.16sucai.com/2016/10/1100/16sucai_p20161017095_34f.JPG",
"https://file03.16sucai.com/2016/10/1100/16sucai_p20161017095_34f.JPG",
"https://file03.16sucai.com/2016/10/1100/16sucai_p20161017095_34f.JPG",
];
ImageRadioController controller;
@override
void initState() {
controller = new ImageRadioController();
super.initState();
}
@override
void dispose() {
controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ImageRadio(
images[0],
isSeleted: false,
controller: controller,
onChange: (v) => print("ImageRadio_1--->$v"),
),
SizedBox(width: 20,),
ImageRadio(
images[1],
isSeleted: true,
controller: controller,
onChange: (v) => print("ImageRadio_2--->$v"),
),
SizedBox(width: 20,),
ImageRadio(
images[2],
isSeleted: false,
controller: controller,
onChange: (v) => print("ImageRadio_3--->$v"),
),
],
),
),
);
}
}
1、new一个ImageRadioController;
2、直接使用ImageRadio,controller使用同一个ImageRadioController即可实现单选;
2、实现原理
将单选组件的刷新回调函数保存在controller中,监听点击事件,点击后选中当前,并且回调controller中保存的其它回调函数,以通知同一个controller下其它组件取消选中,代码如下:
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
class ImageRadio extends StatefulWidget {
ImageRadio(@required this.imageUrl, {
this.isSeleted: false,
this.controller,
this.onChange,
this.width: 60.0,
this.height: 60.0,
this.activeBorderColor: Colors.red,
this.inactiveBorderColor: Colors.transparent,
this.activeBorderWidth: 3.0,
this.inactiveBorderWidth: 3.0,
this.borderRadius: 2.0
});
bool isSeleted;
VoidCallback callMe;
final String imageUrl;
final ImageRadioController controller;
final ValueChanged<bool> onChange;
final double width;
final double height;
final Color activeBorderColor;
final Color inactiveBorderColor;
final double activeBorderWidth;
final double inactiveBorderWidth;
final double borderRadius;
@override
_ImageRadioState createState() => _ImageRadioState();
}
class _ImageRadioState extends State<ImageRadio> {
VoidCallback makeMeUnselect;
@override
void initState() {
// init
makeMeUnselect = () {
setState(() {
widget.isSeleted = false;
});
if (widget.onChange != null) {
widget.onChange(false);
}
};
// backup
widget.callMe = makeMeUnselect;
// add
if (widget.controller != null) {
print("initState() add callback--->$makeMeUnselect");
widget.controller.add(makeMeUnselect);
}
super.initState();
}
@override
void dispose() {
if (widget.controller != null) {
print("dispose() remove callback--->$makeMeUnselect");
widget.controller.remove(makeMeUnselect);
}
super.dispose();
}
@override
void didUpdateWidget(ImageRadio oldWidget) {
if (oldWidget != widget && oldWidget.callMe != makeMeUnselect) {
if (widget.controller != null) {
//print("old callback == new callback ? --->${oldWidget.callMe == makeMeUnselect}");
widget.controller.remove(oldWidget.callMe);
widget.controller.add(makeMeUnselect);
print("didUpdateWidget() remove--->$makeMeUnselect");
print("didUpdateWidget() add--->$makeMeUnselect");
}
}
super.didUpdateWidget(oldWidget);
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
setState(() {
widget.isSeleted = true;
});
if (widget.onChange != null) {
widget.onChange(true);
}
widget.controller.unselectOthers(makeMeUnselect);
},
child: Container(
width: widget.width,
height: widget.height,
alignment: Alignment.center,
decoration: new BoxDecoration(
border: new Border.all(
width: widget.isSeleted ? widget.activeBorderWidth : widget.inactiveBorderWidth,
color: widget.isSeleted ? widget.activeBorderColor : widget.inactiveBorderColor),
borderRadius: BorderRadius.all(Radius.circular(widget.borderRadius)),
),
child: Image.network(
widget.imageUrl,
fit: BoxFit.cover,
width: widget.width,
height: widget.height,
),
),
);
}
}
class ImageRadioController {
List<VoidCallback> _callbackList;
ImageRadioController() {
_callbackList = [];
}
void add(VoidCallback callback) {
if (_callbackList == null) _callbackList = [];
_callbackList.add(callback);
}
void remove(VoidCallback callback) {
if (_callbackList != null) _callbackList.remove(callback);
}
void dispose() {
if (_callbackList != null) {
_callbackList.clear();
_callbackList = null;
}
}
void unselectOthers(VoidCallback currentCallback) {
if (_callbackList != null && _callbackList.length > 0) {
for(int i = 0, len = _callbackList.length; i < len; i++) {
VoidCallback callback = _callbackList[i];
if (callback == currentCallback) continue;
callback();
}
}
}
}
有需要的可以直接下载:github地址
Flutter 自定义单选控件
在Flutter 应用开发中,经常会遇到各种单选效果,虽然官方提供了Radio组件,但是并不能满足我们实际的开发需求,所以往往还需要自定义控件才能满足平时的开发需求。下面就平时开发中用到的单选进行介绍:
自定义SegmentBar
对于分段组件大家肯定不会陌生,主要是实现多个分段,实现单选功能,效果如下图。
话不多说,直接上代码:
class SegmentBarView extends StatefulWidget {
List<String> datas;
Function(String) onSelected;
int defaultIndex=0;
SegmentBarView({@required this.datas, this.onSelected,this.defaultIndex});
@override
_SegmentBarViewState createState() => _SegmentBarViewState();
}
class _SegmentBarViewState extends State<SegmentBarView> {
List<String> sdkLists;
String selectItem;
@override
void initState() {
super.initState();
sdkLists = widget.datas;
selectItem=sdkLists[widget.defaultIndex];
}
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Container(
padding: EdgeInsets.only(left: 10, right: 10),
child: Row(
children: _buildSegments(sdkLists),
),
),
);
}
_buildSegments(List list) {
if(list == null) {
return Container();
}
List<Widget> items = List();
list.forEach((item){
if(item != null) {
items.add(Container(
padding: EdgeInsets.only(top: 8,bottom: 8),
child: _buildItem(item),
));
}
});
return items;
}
_buildItem(String item) {
if(selectItem == item) {
return Container(
height: 34,
child: RaisedButton(
shape: RoundedRectangleBorder(
// borderRadius: BorderRadius.circular(15)
),
color: Color(0xFF00A6DE),
onPressed: (){
},
child: Text(item,style: TextStyle(color: Colors.white),),
),
);
}else {
return Container(
height: 34,
child: OutlineButton(
borderSide: BorderSide(color: Color(0xFFcccccc),width: 0.5),
onPressed: (){
updateGroupValue(item);
},
child: Text(item),
),
);
}
}
updateGroupValue(String item) {
if(item == selectItem) {
return;
}else {
selectItem = item;
widget.onSelected(selectItem);
setState(() {
});
}
}
}
使用的时候,只需要按照构造函数传入对应的参数即可。
自定义Radio
当然,开发中还可以遇到下面这种带圆角的按钮,效果如下。
对于这种效果怎么做呢,最简单的就是硬编码,用两个按钮,然后点击的时候去切换,代码如下:
//只能支持两个按钮单选
class RadioGroupWidget extends StatefulWidget {
List<String> datas ;
Function(String) onSelected;
double radioWidth=80;
double radioHeight=28;
RadioGroupWidget({@required this.datas,@required this.onSelected,this.radioWidth, this.radioHeight,});
@override
State<StatefulWidget> createState() {
return RadioGroupState();
}
}
class RadioGroupState extends State<RadioGroupWidget> {
var chooseStr;
int choosed=1;
Color choosedBgColor=Colors.blue;
Color choosedCornerColor=Colors.blue;
Color choosedTxtColor=Colors.white;
Color defaultBgColor=Colors.white;
Color defaultCornerColor=Colors.grey;
Color defaultTxtColor=Colors.grey;
@override
void initState() {
super.initState();
chooseStr=widget.datas[0];
}
@override
Widget build(BuildContext context) {
return Row(
children: [
GestureDetector(
onTap: (){
choosed=1;
chooseStr=widget.datas[0];
setState(() {});
widget.onSelected(chooseStr);
},
child: Container(
height: widget.radioHeight,
width: widget.radioWidth,
decoration: BoxDecoration(
color: choosed==1?choosedBgColor:defaultBgColor,
borderRadius: BorderRadius.only(topLeft: Radius.circular(15),bottomLeft: Radius.circular(15)),
border: Border.all(width: 1, color: choosed==1?choosedCornerColor:defaultCornerColor),
),
child: Center(child: Text(widget.datas[0],style: TextStyle(color: choosed==1?choosedTxtColor:defaultTxtColor,fontSize: 12))),
)
),
GestureDetector(
onTap: (){
choosed=2;
chooseStr=widget.datas[1];
setState(() {});
widget.onSelected(chooseStr);
},
child: Container(
height: widget.radioHeight,
width: widget.radioWidth,
decoration: BoxDecoration(
color: choosed==2?choosedBgColor:defaultBgColor,
borderRadius: BorderRadius.only(topRight: Radius.circular(15),bottomRight: Radius.circular(15)),
border: Border.all(width: 1, color: choosed==2?choosedCornerColor:defaultCornerColor),
),
child: Center(child: Text(widget.datas[1],style: TextStyle(color: choosed==2?choosedTxtColor:defaultTxtColor,fontSize: 12))),
)
)
],
);
}
}
实际使用时,传入参数即可。
List<String> lineRadios = ['实时流水', '累计流水'];
RadioGroupWidget(radioWidth:80,radioHeight:28,datas: lineRadios, onSelected: (value){
print('_buildChartTitle value: '+value.toString());
},)