flutter 图片查看浏览,保存到本地

这里介绍的是图片查看库photo_view的使用以及达到我们的效果我们使用了一下插件,效果还是很棒的
图片浏览的插件是不支持保存到本地的,所以我们这边结合image_gallery_saver保存到相册。写入相册是需要申请权限的所以用到 permission_handler。


Untitled.gif

1。配置pubspec.yaml

  #图片浏览查看
  photo_view: ^0.9.2
  #图片保存到本地相册
  image_gallery_saver: ^1.2.2
  #权限申请
  permission_handler: ^5.0.0+hotfix.3
  #弹框提醒
  fluttertoast: ^4.0.1
  #dio 网络请求
  dio: 3.0.9

这里是路由跳转的动画可以试下在一个页面打开没有push效果

//路由跳转
class FadeRoute extends PageRouteBuilder {
  final Widget page;
  FadeRoute({this.page}): super(
    pageBuilder: (
        BuildContext context,
        Animation<double> animation,
        Animation<double> secondaryAnimation,
        ) =>page,transitionsBuilder: (
      BuildContext context,
      Animation<double> animation,
      Animation<double> secondaryAnimation,
      Widget child,
      ) =>FadeTransition(
    opacity: animation,
    child: child,
  ),
  );
}

//图片浏览以及保存图片跟权限申请

import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
import 'package:image_gallery_saver/image_gallery_saver.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:dio/dio.dart';
import 'dart:typed_data';
import 'package:fluttertoast/fluttertoast.dart';
import 'dart:io';
//自己写的弹框 
import 'package:flutter_app_demo/app/CoustomWidget/alert_widget.dart';
class GalleryPhotoViewWrapper extends StatefulWidget {
  GalleryPhotoViewWrapper({
    this.loadingBuilder,
    this.backgroundDecoration,
    this.minScale,
    this.maxScale,
    this.initialIndex,
    @required this.galleryItems,
    this.scrollDirection = Axis.horizontal,
  }) : pageController = PageController(initialPage: initialIndex);

  final LoadingBuilder loadingBuilder;
  final Decoration backgroundDecoration;
  final dynamic minScale;
  final dynamic maxScale;
  final int initialIndex;
  final PageController pageController;
  final List galleryItems;
  final Axis scrollDirection;

  @override
  State<StatefulWidget> createState() {
    return _GalleryPhotoViewWrapperState();
  }
}

class _GalleryPhotoViewWrapperState extends State<GalleryPhotoViewWrapper> {
  int currentIndex;

  @override
  void initState() {
    currentIndex = widget.initialIndex;
    super.initState();
  }

  void onPageChanged(int index) {
    setState(() {
      currentIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        decoration: widget.backgroundDecoration,
        constraints: BoxConstraints.expand(
          height: MediaQuery.of(context).size.height,
        ),
        child: Stack(
          alignment: Alignment.bottomRight,
          children: <Widget>[
            PhotoViewGallery.builder(
              scrollPhysics: const BouncingScrollPhysics(),
              builder: _buildItem,
              itemCount: widget.galleryItems.length,
              loadingBuilder: widget.loadingBuilder,
              backgroundDecoration: widget.backgroundDecoration,
              pageController: widget.pageController,
              onPageChanged: onPageChanged,
              scrollDirection: widget.scrollDirection,
            ),
            Positioned(//图片index显示
              top: MediaQuery.of(context).padding.top+15,
              width: MediaQuery.of(context).size.width,
              child: Center(
                child: Text("${currentIndex+1}/${widget.galleryItems.length}",style: TextStyle(color: Colors.white,fontSize: 16)),
              ),
            ),
            Positioned(//右上角关闭按钮
              right: 10,
              top: MediaQuery.of(context).padding.top,
              child: IconButton(
                icon: Icon(Icons.close,size: 30,color: Colors.white,),
                onPressed: (){
                  Navigator.of(context).pop();
                },
              ),

            ),
            Positioned(
              bottom: MediaQuery.of(context).padding.bottom +16,
              child: FlatButton(
                child: Text('保存图片',style: TextStyle(color: Colors.white,fontSize: 16),),
                onPressed: (){
                  if (Platform.isIOS){
                    return   _savenNetworkImage();
                  }
                  requestPermission().then((bool){
                    if (bool){
                      _savenNetworkImage();
                    }
                  });

                },
              ),
            )
          ],
        ),
      ),
    );
  }
//动态申请权限,ios 要在info.plist 上面添加
  Future<bool> requestPermission() async {
    var status = await Permission.photos.status;
    if (status.isUndetermined) {
      Map<Permission, PermissionStatus> statuses = await [
        Permission.photos,
      ].request();
    }
    return status.isGranted;
  }

  //保存网络图片到本地
  _savenNetworkImage() async {

    var status = await Permission.photos.status;
    if (status.isDenied) {
      // We didn't ask for permission yet.
      print('暂无相册权限');
      showDialog(context: context,builder: (context){
        return AlertWidget(title: '微信提示',message: '您当前没有开启相册权限',confirm: '去开启',
        confirmCallback: (){
        //打开ios的设置
          openAppSettings();
        },);
      });


      return;
    }
    var response = await Dio().get(widget.galleryItems[currentIndex].resource, options: Options(responseType: ResponseType.bytes));
    final result = await ImageGallerySaver.saveImage(Uint8List.fromList(response.data));
    print(result);
    if (Platform.isIOS){
      if(result){
        Fluttertoast.showToast(msg: '保存成功',gravity:ToastGravity.CENTER);

      }else{
        Fluttertoast.showToast(msg: '保存失败',gravity:ToastGravity.CENTER);
      }
    }else{
      if(result !=null){
        Fluttertoast.showToast(msg: '保存成功',gravity:ToastGravity.CENTER);

      }else{
        Fluttertoast.showToast(msg: '保存失败',gravity:ToastGravity.CENTER);
      }
    }

  }

  PhotoViewGalleryPageOptions _buildItem(BuildContext context, int index) {
    final GalleryExampleItem item = widget.galleryItems[index];
    return PhotoViewGalleryPageOptions(
        onTapUp: (BuildContext context, TapUpDetails details, PhotoViewControllerValue controllerValue){
          Navigator.of(context).pop();
        },
      imageProvider:NetworkImage(item.resource),
//      initialScale: PhotoViewComputedScale.contained,
//      minScale: PhotoViewComputedScale.contained * (0.5 + index / 10),
//      maxScale: PhotoViewComputedScale.covered * 1.1,

      heroAttributes: PhotoViewHeroAttributes(tag: item.id),
    );
  }
}

//Hero 动画组件
class GalleryExampleItemThumbnail extends StatelessWidget {
  const GalleryExampleItemThumbnail(
      {Key key, this.galleryExampleItem, this.onTap})
      : super(key: key);

  final GalleryExampleItem galleryExampleItem;

  final GestureTapCallback onTap;

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
        borderRadius: BorderRadius.circular(4),
      ),
      child: GestureDetector(
        onTap: onTap,
        child: Hero(
          tag: galleryExampleItem.id,
          child:Image.network(galleryExampleItem.resource,fit: BoxFit.cover,),
        ),
      ),
    );
  }
}
//model
class GalleryExampleItem {
  GalleryExampleItem({this.id, this.resource, this.isSvg = false});
  //hero的id 不能重复
   String id;
   String resource;
   bool isSvg;
}



使用

model.imageUrlArray 路面存放的是 服务器上拉去的 多个图片的地址 ,我们需要把url转换成 GalleryExampleItem model,里面,有个字段 id就是Hero的id 要唯一 不能重复

GridView.builder(
                    shrinkWrap: true,
                    physics: NeverScrollableScrollPhysics(),
                    itemCount: model.imageUrlArray ==null?0 :model.imageUrlArray.length,
                    itemBuilder: (context,index){
                      return AspectRatio(
                        aspectRatio: 1,
                        child: GalleryExampleItemThumbnail(
                          galleryExampleItem: imageModel()[index],
                          onTap: (){
                            Navigator.of(context).push(
                                new FadeRoute(page: GalleryPhotoViewWrapper(
                                  galleryItems: imageModel(),
                                  backgroundDecoration: const BoxDecoration(
                                    color: Colors.black,
                                  ),
                                  initialIndex: index,

                                )));

                          },
                        ),
                      );
                    },
                    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                      crossAxisCount: model.imageUrlArray.length ==1?2: 3,
                      mainAxisSpacing: 8,
                      crossAxisSpacing: 8,
                    ),
                  )

我们这里使用到的是自定义的一个 id,通过所以跟服务器获取列表每一行的id来拼接

 List imageModel(){
    List imgList = List();
    for (int x = 0;x<model.imageUrlArray.length;x ++)
      {
        GalleryExampleItem item = GalleryExampleItem();
        item.id = 'tag${x}+${model.id}';
        item.resource =model.imageUrlArray[x];
        imgList.add(item);
      }
    print(imgList.first.id);

    return imgList;

  }
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容