Flutter - 从 gallery/camera 中选取图像不会更新保存选取的图像预览的容器

Flutter - picking an image from gallery/camera doesn't update the container that holds the picked image preview

我正在开发一个允许用户编写 post(类似于 facebook 的添加 post)的屏幕,用户可以在其中从图库中选择图片并可以在一个预览容器,问题是在我选择图像后预览不会立即更新,我必须离开屏幕并返回查看预览,我添加了屏幕截图以进一步解释,我还使用向上滑动面板作为底页,让用户能够选择媒体类型(图像、视频、音频...)。

使用的依赖项:

这是我的完整代码:

MediaFilesServices mediaFilesServices = MediaFilesServices();
PanelController _panelController = new PanelController();
String mediaFileType = "NONE";
PickedFile _image;
final ImagePicker imagePicker = ImagePicker();

class AddPostScreen extends StatefulWidget {
  @override
  _AddPostScreenState createState() => _AddPostScreenState();
}

class _AddPostScreenState extends State<AddPostScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      body: SafeArea(
        child: Container(
            margin: const EdgeInsets.only(top: 20), child: AddPostBody()),
      ),
    );
  }
}

class AddPostBody extends StatefulWidget {
  @override
  _AddPostBodyState createState() => _AddPostBodyState();
}

class _AddPostBodyState extends State<AddPostBody> {
  @override
  Widget build(BuildContext context) {
    return SlidingUpPanel(
      padding: EdgeInsets.only(top: 12, left: 12, right: 12),
      minHeight: 100,
      maxHeight: 310,
      backdropEnabled: true,
      slideDirection: SlideDirection.UP,
      isDraggable: true,
      controller: _panelController,
      borderRadius: BorderRadius.only(
        topLeft: Radius.circular(24.0),
        topRight: Radius.circular(24.0),
      ),
      panel: ExpandedPanelBody(),
      collapsed: collapsedPanelBody(),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.only(right: 14.0, left: 14, top: 20),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.start,
              children: [
                InkResponse(
                    onTap: () {
                      Navigator.pop(context);
                    },
                    child: Icon(CupertinoIcons.arrow_left)),
                SizedBox(
                  width: 14,
                ),
                Text('Add post'),
                Spacer(),
                ElevatedButton(
                  onPressed: () async {},
                  style: ButtonStyle(
                    shadowColor:
                        MaterialStateProperty.all<Color>(UIColors.primary_a),
                    backgroundColor:
                        MaterialStateProperty.all<Color>(UIColors.primary_a),
                  ),
                  child: Text('share'),
                )
              ],
            ),
          ),
          SizedBox(
            height: 18,
          ),
          TextField(
            maxLines: null,
            keyboardType: TextInputType.multiline,
            controller: _addPostController,
            cursorColor: UIColors.primary_a,
            decoration: InputDecoration(
              contentPadding:
                  EdgeInsets.symmetric(horizontal: 16.0, vertical: 15.0),
              border: InputBorder.none,
              hintText: 'Say something...',
            ),
          ),
          SizedBox(
            height: 20,
          ),
          Container(
            padding: EdgeInsets.symmetric(horizontal: 6, vertical: 3),
            decoration: BoxDecoration(
              color: UIColors.primaryTextFieldBackground.withAlpha(3),
            ),
            child: TextField(
              maxLines: 1,
              controller: _addPostLocationController,
              cursorColor: UIColors.primary_a,
              style: TextStyle(color: UIColors.primary_a),
              decoration: InputDecoration(
                contentPadding:
                    EdgeInsets.symmetric(horizontal: 16.0, vertical: 15.0),
                border: InputBorder.none,
                hintText: 'drop your location',
                prefixIcon: Icon(
                  CupertinoIcons.location_north_line_fill,
                  color: UIColors.primary_a,
                  size: 22,
                ),
              ),
            ),
          ),
          SizedBox(
            height: 14,
          ),
          buildImageMedia(_image)
        ],
      ),
    );
  }
}

class ExpandedPanelBody extends StatefulWidget {
  @override
  _ExpandedPanelBodyState createState() => _ExpandedPanelBodyState();
}

class _ExpandedPanelBodyState extends State<ExpandedPanelBody> {
  _imgFromCamera() async {
    PickedFile image = await imagePicker.getImage(
        source: ImageSource.camera, imageQuality: 100);
    setState(() {
      _image = image;
    });
    mediaFileType = "IMAGE";
  }

  _imgFromGallery() async {
    PickedFile image = await imagePicker.getImage(
        source: ImageSource.gallery, imageQuality: 100);
    setState(() {
      _image = image;
    });
    mediaFileType = "IMAGE";
  }

  @override
  Widget build(BuildContext context) {
void _showPicker(context) {
  showModalBottomSheet(
      context: context,
      builder: (context) {
        return SafeArea(
          child: Container(
            child: new Wrap(
              children: <Widget>[
                new ListTile(
                    leading: new Icon(Icons.photo_library),
                    title: new Text('Photo Library'),
                    onTap: () {
                      setState(() {
                        _imgFromGallery();
                      });

                      Navigator.of(context).pop();
                    }),
                new ListTile(
                  leading: new Icon(Icons.photo_camera),
                  title: new Text('Camera'),
                  onTap: () async {
                    await _imgFromCamera();
                    Navigator.of(context).pop();
                  },
                ),
              ],
            ),
          ),
        );
      });
}

return Container(
  margin: EdgeInsets.only(bottom: 13.0),
  child: Column(
    children: [
      Container(
        decoration: BoxDecoration(
          color: Colors.black12,
          borderRadius: BorderRadius.all(Radius.circular(64.0)),
        ),
        margin: EdgeInsets.only(left: 180.0, right: 180.0, bottom: 10.0),
        height: 6.0,
      ),
      ListTile(
        onTap: () {},
        leading: Icon(
          CupertinoIcons.plus_circle,
          color: UIColors.primary_a,
        ),
        title: Text(
          'upload media',
          style: TextStyle(
              color: UIColors.primary_a, fontWeight: FontWeight.w300),
        ),
      ),
      ListTile(
        onTap: () {
          _showPicker(context);
        },
        leading: Icon(
          FeatherIcons.image,
          color: Colors.black87,
        ),
        title: Text(
          'picture',
          style:
              TextStyle(color: Colors.black87, fontWeight: FontWeight.w300),
        ),
      ),
      ListTile(
        leading: Icon(
          FeatherIcons.film,
          color: Colors.black87,
        ),
        title: Text(
          'video',
          style:
              TextStyle(color: Colors.black87, fontWeight: FontWeight.w300),
        ),
      ),
      ListTile(
        leading: Icon(
          FeatherIcons.headphones,
          color: Colors.black87,
        ),
        title: Text(
          'audio',
          style:
              TextStyle(color: Colors.black87, fontWeight: FontWeight.w300),
        ),
      ),
      ListTile(
        leading: Icon(
          FeatherIcons.video,
          color: UIColors.primary_a,
        ),
        title: Text(
          'go Live',
          style: TextStyle(
              color: UIColors.primary_a, fontWeight: FontWeight.w300),
        ),
      ),
    ],
  ),
);
}
}

Widget collapsedPanelBody() {
  return Container(
    child: Column(
      children: [
        Container(
          decoration: BoxDecoration(
            color: Colors.black12,
            borderRadius: BorderRadius.all(Radius.circular(64.0)),
          ),
          margin: EdgeInsets.only(left: 180.0, right: 180.0, bottom: 10.0),
          height: 6.0,
        ),
        ListTile(
          onTap: () {
            _panelController.open();
          },
          leading: Icon(
            CupertinoIcons.plus_circle,
            color: UIColors.primary_a,
          ),
          title: Text(
            'upload media',
            style: TextStyle(
                color: UIColors.primary_a, fontWeight: FontWeight.w300),
          ),
        ),
      ],
    ),
  );
}

Widget buildImageMedia(PickedFile imgFilePreview) {
  return Container(
    margin: EdgeInsets.only(right: 14.0, left: 14),
    child: FractionallySizedBox(
      widthFactor: 1.0,
      child: ClipRRect(
        borderRadius: BorderRadius.circular(12.0),
        child: Container(
            height: 300,
            decoration: BoxDecoration(
              color: UIColors.notSelectedColor,
              image: DecorationImage(
                  image: imgFilePreview == null
                      ? Image.file(File("assets/images/empty.png")).image
                      : Image.file(File(imgFilePreview.path)).image,
                  fit: BoxFit.cover),
            )),
      ),
    ),
  );
}

我知道问题出在哪里了。希望你不介意我改变了一些东西,无状态小部件可以用来代替有状态小部件,因为你不需要管理它们中的任何状态。

我已将您的 AddPostScreen 更改为 StatelessWidget,但您可以根据需要将其更改回 StatefulWidget。

MediaFilesServices mediaFilesServices = MediaFilesServices();
PanelController _panelController = new PanelController();
String mediaFileType = "NONE";
class AddPostScreen extends StatelessWidget {
  const AddPostScreen({Key key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      resizeToAvoidBottomInset: false,
      body: SafeArea(
        child: Container(
            margin: const EdgeInsets.only(top: 20), child: AddPostBody()),
      ),
    );
  }
}

请注意我声明的新 PickedFile 变量和我添加的方法。我留下评论进一步解释。

class AddPostBody extends StatefulWidget {
  @override
  _AddPostBodyState createState() => _AddPostBodyState();
}

class _AddPostBodyState extends State<AddPostBody> {
  /// This is the variable that will hold the image the user has selected and is passed to the [buildImageMedia] Widget method
  PickedFile userSelectedImage;

  /// This is a callback method that will assign the value of [userSelectedImage]
  /// from the user's image choice in the ExpandedPanelBody class
  void selectedImageHandler(PickedFile selectedImage) {
    setState(() {
      userSelectedImage = selectedImage;
    });
  }

  @override
  Widget build(BuildContext context) {
    return SlidingUpPanel(
      padding: EdgeInsets.only(top: 12, left: 12, right: 12),
      minHeight: 100,
      maxHeight: 310,
      backdropEnabled: true,
      slideDirection: SlideDirection.UP,
      isDraggable: true,
      controller: _panelController,
      borderRadius: BorderRadius.only(
        topLeft: Radius.circular(24.0),
        topRight: Radius.circular(24.0),
      ),
      panel: ExpandedPanelBody(selectedImageHandler),
      collapsed: collapsedPanelBody(),
      body: Column(
        children: [
          Padding(
            padding: const EdgeInsets.only(right: 14.0, left: 14, top: 20),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.start,
              children: [
                InkResponse(
                    onTap: () {
                      Navigator.pop(context);
                    },
                    child: Icon(CupertinoIcons.arrow_left)),
                SizedBox(
                  width: 14,
                ),
                Text('Add post'),
                Spacer(),
                ElevatedButton(
                  onPressed: () async {},
                  style: ButtonStyle(
                    shadowColor:
                        MaterialStateProperty.all<Color>(Colors.orangeAccent),
                    backgroundColor:
                        MaterialStateProperty.all<Color>(Colors.orangeAccent),
                  ),
                  child: Text('share'),
                )
              ],
            ),
          ),
          SizedBox(
            height: 18,
          ),
          TextField(
            maxLines: null,
            keyboardType: TextInputType.multiline,
            decoration: InputDecoration(
              contentPadding:
                  EdgeInsets.symmetric(horizontal: 16.0, vertical: 15.0),
              border: InputBorder.none,
              hintText: 'Say something...',
            ),
          ),
          SizedBox(
            height: 20,
          ),
          Container(
            padding: EdgeInsets.symmetric(horizontal: 6, vertical: 3),
            decoration: BoxDecoration(
              color: Colors.blueAccent,
            ),
            child: TextField(
              maxLines: 1,
              style: TextStyle(color: Colors.black),
              decoration: InputDecoration(
                contentPadding:
                    EdgeInsets.symmetric(horizontal: 16.0, vertical: 15.0),
                border: InputBorder.none,
                hintText: 'drop your location',
                prefixIcon: Icon(
                  CupertinoIcons.location_north_line_fill,
                  color: Colors.red,
                  size: 22,
                ),
              ),
            ),
          ),
          SizedBox(
            height: 14,
          ),
          buildImageMedia(userSelectedImage)
        ],
      ),
    );
  }
}

对于 ExpandedPanelBody class,我从 [_imgFromCamera] 和 [_imgFromGallery] 方法中删除了 setState 调用,而是使用 class 构造函数中声明的 [triggerSelectedImage] 函数,它将在 AddPostBody class 中设置 [userSelectedImage] 的状态并显示用户的图像选择

class ExpandedPanelBody extends StatefulWidget {
  final Function(PickedFile) triggerSelectedImage;

  ExpandedPanelBody(this.triggerSelectedImage);
  @override
  _ExpandedPanelBodyState createState() => _ExpandedPanelBodyState();
}

class _ExpandedPanelBodyState extends State<ExpandedPanelBody> {
/// The instance of ImagePicker is declared here, where it is used
  final ImagePicker imagePicker = ImagePicker();
  _imgFromCamera() async {
    PickedFile image = await imagePicker.getImage(
        source: ImageSource.camera, imageQuality: 100);

    widget.triggerSelectedImage(image);
    mediaFileType = "IMAGE";
  }

  _imgFromGallery() async {
    PickedFile image = await imagePicker.getImage(
        source: ImageSource.gallery, imageQuality: 100);
    widget.triggerSelectedImage(image);
    mediaFileType = "IMAGE";
  }

  @override
  Widget build(BuildContext context) {
    void _showPicker(context) {
      showModalBottomSheet(
          context: context,
          builder: (context) {
            return SafeArea(
              child: Container(
                child: new Wrap(
                  children: <Widget>[
                    new ListTile(
                        leading: new Icon(Icons.photo_library),
                        title: new Text('Photo Library'),
                        onTap: () {
                          setState(() {
                            _imgFromGallery();
                          });

                          Navigator.of(context).pop();
                        }),
                    new ListTile(
                      leading: new Icon(Icons.photo_camera),
                      title: new Text('Camera'),
                      onTap: () async {
                        await _imgFromCamera();
                        Navigator.of(context).pop();
                      },
                    ),
                  ],
                ),
              ),
            );
          });
    }

    return Container(
      margin: EdgeInsets.only(bottom: 13.0),
      child: Column(
        children: [
          Container(
            decoration: BoxDecoration(
              color: Colors.black12,
              borderRadius: BorderRadius.all(Radius.circular(64.0)),
            ),
            margin: EdgeInsets.only(left: 180.0, right: 180.0, bottom: 10.0),
            height: 6.0,
          ),
          ListTile(
            onTap: () {},
            leading: Icon(
              CupertinoIcons.plus_circle,
              color: Colors.red,
            ),
            title: Text(
              'upload media',
              style:
                  TextStyle(color: Colors.black, fontWeight: FontWeight.w300),
            ),
          ),
          ListTile(
            onTap: () {
              _showPicker(context);
            },
            leading: Icon(
              Icons.account_balance,
              color: Colors.black87,
            ),
            title: Text(
              'picture',
              style:
                  TextStyle(color: Colors.black87, fontWeight: FontWeight.w300),
            ),
          ),
          ListTile(
            leading: Icon(
              Icons.account_balance,
              color: Colors.black87,
            ),
            title: Text(
              'video',
              style:
                  TextStyle(color: Colors.black87, fontWeight: FontWeight.w300),
            ),
          ),
          ListTile(
            leading: Icon(
              Icons.account_balance,
              color: Colors.black87,
            ),
            title: Text(
              'audio',
              style:
                  TextStyle(color: Colors.black87, fontWeight: FontWeight.w300),
            ),
          ),
          ListTile(
            leading: Icon(
              Icons.account_balance,
              color: Colors.orangeAccent,
            ),
            title: Text(
              'go Live',
              style:
                  TextStyle(color: Colors.black, fontWeight: FontWeight.w300),
            ),
          ),
        ],
      ),
    );
  }
}

其他Widget方法(collapsedPanelBody & buildImageMedia)保持不变。 解决方案是使用回调方法。