dart - 动态制作带有文本和按钮的卡片
问题描述
我正在制作笔记应用程序。我动态地制作了带有文本和按钮的卡片(通过单击按钮创建)。但是我在更改当前卡上的文本时遇到问题。例如,我有 3 张带有自己的文字和按钮的卡片,我想更改第二张卡片上的文字,但第三张卡片上的文字正在改变。我怎么解决这个问题?
过去,我尝试过制作列表来收集文本,但我不知道如何识别当前卡。
完整的 main.dart
import 'package:flutter/material.dart';
import './changeTextPage.dart';
int count = 0;
String titlecard = '';
String textcard = '';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Notes',
theme: ThemeData(
primarySwatch: Colors.deepPurple
),
home: HomePage(title: 'Notes',),
);
}
}
class HomePage extends StatefulWidget {
HomePage({Key key, this.title}) : super(key: key);
final title;
@override
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
List<Widget> cards = new List.generate(count, (int i) => new MyCard());
return Scaffold(
appBar: AppBar(
title: Text('Notes'),
),
body: LayoutBuilder(
builder: (context, constraint) {
return Column(
children: <Widget>[
Container(
height: 650.0,
child: new ListView(
children: cards,
scrollDirection: Axis.vertical,
),
),
],
);
}
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
Navigator.push(context, MaterialPageRoute(
builder: (context) => changeText())
);
});
},
),
);
}
}
class MyCard extends StatefulWidget {
@override
myCard createState() => myCard();
}
class myCard extends State<MyCard> {
int myCount = count;
void click() {
setState(() {
Navigator.push(context, MaterialPageRoute(
builder: (context) => setNewText())
);
});
}
@override
Widget build(BuildContext context) {
return Center(
child: Card(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text(titlecard),
subtitle: Text(textcard),
),
ButtonTheme.bar( // make buttons use the appropriate styles for cards
child: ButtonBar(
children: <Widget>[
FlatButton(
child: const Text('Change Text'),
onPressed: click,
),
FlatButton(
child: const Text('LISTEN'),
onPressed: () { /* ... */ },
),
],
),
),
],
),
),
);
}
}
class setNewText extends StatefulWidget {
@override
SetNewText createState() => SetNewText();
}
class SetNewText extends State<setNewText> {
final titleController = TextEditingController();
final textController = TextEditingController();
final formkey = GlobalKey<FormState>();
void _submit() {
setState(() {
if (formkey.currentState.validate()) {
formkey.currentState.save();
Navigator.pop(context);
titlecard = titleController.text;
textcard = textController.text;
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Change Title'),
),
body: Column(
children: <Widget>[
Card(
child: Padding(
padding: EdgeInsets.all(2.0),
child: Form(
key: formkey,
child: Column(
children: <Widget>[
TextFormField(
controller: titleController,
decoration: InputDecoration(
labelText: 'Title'
),
validator: (value) => value.length < 1 ? 'Invalid Title' : null,
onSaved: (value) => value = titleController.text,
),
TextFormField(
controller: textController,
decoration: InputDecoration(
labelText: 'Text'
),
validator: (text) => text.length < 1 ? 'Invalid Text' : null,
onSaved: (text) => text = textController.text,
)
],
),
),
),
),
FlatButton(
textColor: Colors.deepPurple,
child: Text('SUBMIT'),
onPressed: _submit,
),
],
)
);
}
}
changeTextPage.dart
import 'package:flutter/material.dart';
import './main.dart';
class changeText extends StatefulWidget {
@override
ChangeText createState() => ChangeText();
}
class ChangeText extends State<changeText> {
myCard s = myCard();
final titleController = TextEditingController();
final textController = TextEditingController();
final formkey = GlobalKey<FormState>();
void _submit() {
setState(() {
if (formkey.currentState.validate()) {
formkey.currentState.save();
Navigator.pop(context);
count++;
titlecard = titleController.text;
textcard = textController.text;
}
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Change Title'),
),
body: Column(
children: <Widget>[
Card(
child: Padding(
padding: EdgeInsets.all(2.0),
child: Form(
key: formkey,
child: Column(
children: <Widget>[
TextFormField(
controller: titleController,
decoration: InputDecoration(
labelText: 'Title'
),
validator: (value) => value.length < 1 ? 'Invalid Title' : null,
onSaved: (value) => value = titleController.text,
),
TextFormField(
controller: textController,
decoration: InputDecoration(
labelText: 'Text'
),
validator: (text) => text.length < 1 ? 'Invalid Text' : null,
onSaved: (text) => text = textController.text,
)
],
),
),
),
),
FlatButton(
textColor: Colors.deepPurple,
child: Text('SUBMIT'),
onPressed: _submit,
),
],
)
);
}
}
解决方案
好的,所以你碰巧犯了一些常见的错误,其中一个很关键。
- 最重要的是不要使用全局变量!正如您对
count
,titlecard
和所做的那样textcard
。 - 有一种做法是用 PascalCase 和相应的状态来命名有状态的小部件,就像小部件一样,但前缀为下划线 (
_
) 以使其私有并以State
单词为后缀。
这个(或其中一个)的正确方法是有一个小部件,它是你的屏幕,带有一个编辑内容的表单,它会在提交时弹出一些带有用户值的结构:
class ChangeTextScreen extends StatefulWidget {
_ChangeTextScreenState createState() => _ChangeTextScreenState();
}
class _ChangeTextScreenState extends State<ChangeTextScreen> {
void _submit() {
setState(() {
formkey.currentState.save();
Navigator.pop(ChangeTextResult(title: titleController.text, text: textController.text));
});
}
// Rest of your code...
}
class ChangeTextResult {
final String title;
final String text;
ChangeTextResult({@required this.title, @required this.text});
}
您还应该有一个将笔记存储在某种列表中的地方。你的主屏幕看起来是个不错的地方。一旦您的应用程序变得更大,请考虑使用scoped_model
或 Redux 之类的。
因此,让我们在主屏幕上添加一个Note
类和一个带有笔记的列表:
class Note {
String title;
String text;
Note(this.title, this.text);
}
class HomePageState extends State<HomePage> {
List<Note> _notes = [Note('Test', 'Some test note')];
@override
Widget build(BuildContext context) {
ListView cards = ListView.builder(
itemCount: _notes.length,
itemBuilder: (context, index) => MyCard(
title: _notes[index].title,
text: _notes[index].text,
onEdit: (title, text) => setState(() { // We'll get back to that later
_notes[index].title = title;
_notes[index].text = text;
})
));
// (...)
您的MyCard
小部件(下次尝试使用更好的名称)应该包含有关其内容的某种信息,最好的方法之一是将这些信息传递给它的构造函数,就像这样:
class MyCard extends StatefulWidget {
final String title;
final String text;
final Function onEdit;
MyCard({Key key, @required this.title, @required this.text, @required this.onEdit}) : super(key: key);
@override
_MyCardState createState() => _MyCardState();
}
拥有此
Key
参数是一个好习惯。
并在您的类中使用这些参数_MyCardState
(我将其重命名为_myCard
):
// (...)
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text(widget.title),
subtitle: Text(widget.text),
),
// (...)
回到你打开你的那一刻ChangeTextScreen
,你应该将结果分配Navigation.push()
给一个变量。这是你的结果,你可以处理它(一旦我们检查它null
,用户可能已经从这个屏幕返回,然后结果将是null
)。
void click() {
setState(() {
final ChangeTextResult result = Navigator.push(context, MaterialPageRoute(
builder: (context) => ChangeTextScreen())
);
if (result != null) {
widget.onEdit(result.title, result.text);
}
});
}
你还记得那个onEdit
参数吗(我在上面代码的注释中提到过)?我们在这里调用该参数。
我想就是这样。我本可以混合你的应用程序的一些概念,但我认为你无论如何都会设法理解我的观点。
我完全重写了你所有的代码。我认为您从头开始并记住这些提示会更容易。另外,尝试在 Google 上搜索一些类似的东西(比如一个简单的 Todo 应用程序)或从 Flutter.io 开始第二 部分!这应该让您对如何解决 Flutter 中的常见问题有了一个很好的了解。
此外,阅读有关 Flutter 和 Dart 的良好实践。诸如正确格式化代码之类的事情非常重要。
顺便说一句,这是迄今为止我对 Stack Overflow 最长的回答。我希望你会感激。
推荐阅读
- bash - 如何用命令行参数替换bash变量
- grafana - 用于 grafana 的 influxdb 中两个不同查询的单行查询百分比
- amazon-web-services - 配置输出 S3 存储桶以允许 AWS Transcribe 存储转录结果时出现问题
- java - EventQueue.invokeLater() 有一些错误
- makefile - 使用 Jobs (Multicore) 标志会导致 Makefile 编译失败
- scala - 交错多个流
- wordpress - 允许用户选择他们喜欢的类别并保存它们
- excel - 允许 Excel 宏在受保护的工作表上运行
- python - 模块“Crypto.PublicKey.RSA”没有属性“import_key”
- debian - 无法安装 mysql-Workbench (E: Unable to locate package mysql-workbench-community-8.0.17-src.tar.gz )