动态制作带有文本和按钮的卡片 [英] Make cards with texts and buttons dynamically
问题描述
我正在制作Notes应用程序.我用文本和按钮动态制作了卡片(单击按钮创建).但是我在更改当前卡上的文本时遇到问题.例如,我有3张卡片,它们都有自己的文本和按钮,我想更改第二张卡片上的文本,但文本在第三张卡片上正在更改.我该如何解决这个问题?
I'm making Notes app. I made cards with text and buttons dynamically (Create by clicking the button). But I have problem with Changing Text on CURRENT card. For example, I have 3 cards with own texts and buttons and I want to change text on 2nd card but text is changing on the 3rd card. How can I solve this problem?
过去,我曾尝试制作列表来收集文字,但我不知道如何识别当前卡.
In the past, I've tried making list to collect texts, but i dont know how to identify current card.
完整的main.dart
full 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
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,
),
],
)
);
}
}
推荐答案
好的,所以您碰巧会犯一些常见的错误,其中之一很关键.
Okay, so you happen to make some common mistakes, one of which is critical.
- 最重要的是不要使用全局变量!与
count
,titlecard
和textcard
一样. - 有一种做法是使用PascalCase和相应的状态来命名有状态的小部件,就像该小部件一样,但以下划线(
_
)开头以使其私有并以State
单词.
- most importantly don't use global variables! As you do with
count
,titlecard
andtextcard
. - there is a practice to name stateful widgets with PascalCase and corresponding states just like the widget but prefixed with an underscore (
_
) to make it private and suffixed by theState
word.
对此(或其中之一)的正确方法是拥有一个小部件,该小部件将是您的屏幕,带有用于编辑内容的表单,并且会在提交时弹出一些带有用户值的结构:
The correct approach for this (or one of them) would be to have a widget that would be your screen with a form to edit stuff and it would pop some struct with user values on submit:
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之类的.
You should also have a place where you store your notes in some kind of a list. Your main screen looks like a good place for it. Once your app will be bigger, think about using scoped_model
or Redux or something.
因此,让我们在主屏幕上添加一个 Note
类和一个带有注释的列表:
So let's add a Note
class and a list with your notes to your main screen:
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
小部件(下次尝试使用更好的名称)应包含有关其内容的某种信息,最好的方法之一就是将该信息传递给其构造函数,就像那样:
Your MyCard
widget (try to use better names next time) should contain some kind of information about its content, one of the best approaches would be to pass this info to its constructor, just like that:
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
重命名)中使用这些参数:
And use those parameters in your _MyCardState
class (I renamed it from _myCard
):
// (...)
children: <Widget>[
ListTile(
leading: Icon(Icons.album),
title: Text(widget.title),
subtitle: Text(widget.text),
),
// (...)
返回打开 ChangeTextScreen
的那一刻,应将 Navigation.push()
的结果分配给变量.这是您的结果,您可以对其进行处理(一旦我们检查了 null
,用户可能已经从此屏幕返回,那么结果将是 null
)./p>
Returning to the moment where you open your ChangeTextScreen
, you should assign the result of Navigation.push()
to a variable. This is your result, you can deal with it (once we check it for null
, the user could have returned from this screen and then the result would be null
).
void click() {
setState(() {
final ChangeTextResult result = Navigator.push(context, MaterialPageRoute(
builder: (context) => ChangeTextScreen())
);
if (result != null) {
widget.onEdit(result.title, result.text);
}
});
}
您还记得 onEdit
参数(我在上面的代码的注释中提到过)吗?我们在这里称该参数.
Do you remember that onEdit
parameter (I mentioned it in a comment in the code above)? We call that parameter here.
就是这样.我本可以将您的应用程序的一些概念混合在一起,但我认为您还是可以设法阐明我的观点的.
That's it I think. I could have mixed some concepts of your app, but I think you'll manage to get my point anyways.
我完全重写了您的所有代码.我认为重新开始并记住这些提示会更容易.另外,尝试使用Google进行类似的操作(例如简单的Todo应用程序),或执行从颤动开始.io 与第二部分!有关如何解决Flutter中的常见问题,这应该给您一个很好的主意.
I quite rewrote all of your code. I think it will be easier for you to start again from scratch and have those tips in mind. Also, try to Google some similar things (like a simple Todo application) or do Getting started from flutter.io with part two! That should give you a nice idea on how to resolve that common problem in Flutter.
此外,请阅读有关Flutter和Dart的良好做法.正确格式化代码之类的事情非常重要.
And also, read about good practises in Flutter and Dart. Things like correctly formatting your code are really important.
顺便说一句,这是到目前为止我对Stack Overflow的最长回答.希望您会对此表示感谢.
BTW that's my longest answer on Stack Overflow so far. I hope you'll appreciate that.
这篇关于动态制作带有文本和按钮的卡片的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!