颤振 - 从画廊/相机中选择图像不会更新包含所选择图像预览的容器

Posted

技术标签:

【中文标题】颤振 - 从画廊/相机中选择图像不会更新包含所选择图像预览的容器【英文标题】:Flutter - picking an image from gallery/camera doesn't update the container that holds the picked image preview 【发布时间】:2021-09-20 23:11:42 【问题描述】:

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

使用的依赖项:

sliding_up_panel:^1.0.2

image_picker: ^0.6.6+5

这是我的完整代码:

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),
            )),
      ),
    ),
  );

【问题讨论】:

【参考方案1】:

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

我将您的 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 变量和我添加的方法。我让 cmets 进一步解释。

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 类,我从 [_imgFromCamera] 和 [_imgFromGallery] 方法中删除了 setState 调用,而是使用了在类构造函数中声明的 [triggerSelectedImage] 函数,该函数将在AddPostBody 类并导致显示用户的图像选择

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)保持不变。 解决方案是使用回调方法。

【讨论】:

以上是关于颤振 - 从画廊/相机中选择图像不会更新包含所选择图像预览的容器的主要内容,如果未能解决你的问题,请参考以下文章

图片来自相机或画廊?

ImageView 未显示在 android 中使用相机捕获的图像

从android中的意图选择器中选择选项(相机或画廊)后请求权限

无法替换图像视图,用于来自相机或画廊的图像(它不起作用)

将相机图像上传到 Firebase

从图库或相机中选择图像的对话框