android图片查看(1)

图片查看是很常见的功能,点击图片之后跳转到另一个界面查看大图,看起来是非常简单,不过自己动手尝试了一下之后并没有想象中的那么顺利,其中还是有很多需要注意的地方。

好了,先看一下效果


这里写图片描述

对,就是这么简单!显示一张图片,图片可以保存到本地,当然,现在的app基本上都有手势缩放图片的功能,这里我们也要添加这个功能。

好了,功能基本就是这样,下面看代码,首先得界面布局

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#c0000000"
    tools:context="me.masteryi.gankio.PhotoActivity">

    <ImageView
        android:id="@+id/photo_iv"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="center"/>

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/transparent"
        android:theme="@style/AppTheme.AppBarOverlay">

        <android.support.v7.widget.Toolbar
            android:id="@+id/toolbar"
            android:layout_width="match_parent"
            android:layout_height="?attr/actionBarSize"
            android:background="?attr/colorPrimary"
            app:popupTheme="@style/AppTheme.PopupOverlay"/>

    </android.support.design.widget.AppBarLayout>

</FrameLayout>

布局很简单,一个FrameLayout,上面是Appbar,下面是一个ImageView。这里为什么用FrameLayout而不用RelativeLayout呢?主要是为了能够让图片能够全屏显示,可以参考网易新闻的图片界面,这里就不贴图了,你要是喜欢,改成别的布局也不影响。
接下来就是Activity了。这里先提一下用到的第三方库:

  1. 图片加载:Picasso
  2. 图片缩放:PhotoView
  3. 控件注入:ButterKnife
  4. 异步操作:RxJava/RxAndroid

其实Picasso和Butterknife是Android Studio自带的,Picasso是Square家的图片加载库,是主流的图片加载库之一,可以搭配同样是Square出品的OkHttp使用。Butterknife配合Butterknife Zelezny插件可以非常方便的完成控件的注入,妈妈再也不用担心我写findViewById了。RxJava/RxAndroid最近非常火,为了跟上时代潮流,我也在努力学习RxJava的用法。PhotoView是一个非常好的图片控制库,可以手势控制图片移动和缩放,并支持双击放大。其实图片缩放本来是想自己实现的,后来发现有这么一个牛逼的库,就偷个懒先用着,以后再补上。
首先是加载图片,这里我们用Picasso实现

 Picasso.with(this)
                .load(url)
                .into(new Target() {
                    @Override
                    public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {
                        photoImageView.setImageBitmap(bitmap);

                        if (attacher == null) {
                            attacher = new PhotoViewAttacher(photoImageView);
                        } else {
                            attacher.update();
                        }
                    }

                    @Override
                    public void onBitmapFailed(Drawable errorDrawable) {
                        Toast.makeText(PhotoActivity.this, "加载图片出错", Toast.LENGTH_SHORT).show();
                    }

                    @Override
                    public void onPrepareLoad(Drawable placeHolderDrawable) {

                    }
                });

我这里into方法中用的是一个Target而没有直接用imageView,因为通过Target我们可以得到Bitmap对象,至于为什么要得到这个bitmap对象,我们后面再说。
好了,就这样,一个简单的图片详情页面就做好了。当然,这是最基本的功能,这么可爱的妹纸,当然要保存起来了,so,一般的App都会有保存图片,收藏或者分享功能,这里我们只做下载功能,收藏跟分享以后再补上。
我们新建一个menu

<?xml version="1.0" encoding="utf-8"?>
<menu
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item
        android:id="@+id/menu_more"
        android:icon="@drawable/ic_more_vert_white_24dp"
        android:title="@string/more"
        app:showAsAction="always"
        >

        <menu>
            <item
                android:id="@+id/menu_download"
                android:icon="@drawable/ic_file_download_black_24dp"
                android:title="@string/download"
                />
        </menu>

    </item>
</menu>

菜单包括溢出菜单,溢出菜单中有一个下载菜单。好,就是这么简单。接下来我们做下载功能。
我们先准备一个保存图片的工具类FileUtil

/**
 * Created by Lee
 * Date 2016/2/20
 * Email jon_ly@163.com
 * Blog http://masteryi.me
 */
public class FileUtil {

    public static final String TAG = "FileUtil";

    public static final String IMAGE_PATH = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)
            .getPath() + File.separator + "Gank";


    public static boolean saveImage(String imageName, Bitmap image) {

       // Log.d(TAG, "image path:" + IMAGE_PATH);
        File file = new File(IMAGE_PATH);
        if (!file.exists()) {
            file.mkdirs();
        }

        File imageFile = new File(file, imageName);
        try {
            if (imageFile.createNewFile()) {

                FileOutputStream fos = new FileOutputStream(imageFile);
                image.compress(Bitmap.CompressFormat.JPEG, 100, fos);
                fos.close();
            }
            return true;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return false;
    }

    /**
     * 通过url获得文件名
     *
     * @param url 图片url
     * @return 图片文件名
     */
    public static String url2ImageName(String url) {

        String imageName = url.substring(url.lastIndexOf("/") + 1);
        return imageName;
    }
}

FileUtil很简单,只有两个方法,一个是通过url找到文件名XXX.jpg,一个是保存图片的方法,应该没什么难度。这里有一点要注意的是保存路径我这里用了Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)这个方法,这个其实就是android用来存放公共图片的文件夹,打开这个目录,我们可以看见还有知乎,网易等目录在下面,我们新建一个Gank的目录来存放图片。关于Environment.getExternalStoragePublicDirectory其实不止DIRECTORY_PICTURES一种,还有很多别的类型,具体可以看官方文档(自备梯子)。

接下来我们添加保存图片的方法

    private void downloadImage() {

        Target target = new Target() {
            @Override
            public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) {

                Observable.create((Observable.OnSubscribe<Boolean>) subscriber -> {
                   // Log.d(TAG, "thread1:" + Thread.currentThread().getName());
                    String imageName = FileUtil.url2ImageName(url);
                    subscriber.onNext(FileUtil.saveImage(imageName, bitmap));

                })
                        .subscribeOn(Schedulers.io())
                        .observeOn(AndroidSchedulers.mainThread())
                        .subscribe(aBoolean -> {

                           // Log.d(TAG, "thread2:" + Thread.currentThread().getName());
                            if (aBoolean) {
                                Toast.makeText(PhotoActivity.this, "保存图片成功", Toast.LENGTH_SHORT).show();
                            } else {
                                Toast.makeText(PhotoActivity.this, "保存图片失败", Toast.LENGTH_SHORT).show();
                            }

                        }, throwable -> {
                            Toast.makeText(PhotoActivity.this, "保存图片失败", Toast.LENGTH_SHORT).show();
                            Log.d(TAG, throwable.getMessage());
                            throwable.printStackTrace();
                        });
            }

            @Override
            public void onBitmapFailed(Drawable errorDrawable) {

                Toast.makeText(PhotoActivity.this, "保存图片失败", Toast.LENGTH_SHORT).show();
            }

            @Override
            public void onPrepareLoad(Drawable placeHolderDrawable) {

            }
        };

        Picasso.with(this)
                .load(url)
                .into(target);
    }

我们来看一下downloadImage方法
这里Picasso的into方法中也用了Target,具体原因刚才介绍过了,为了获得Bitmap对象。拿到bitmap之后接下来就是要保存到本地,考虑到图片可能很大,保存操作可能是个耗时操作,为了提升界面流畅度,我们需要在线程中进行。这里我们用RxJava来进行异步操作。RxJava教程可以参考这里。RxJava有一个很好的地方就是可以很方便的切换线程,这里我们让图片保存的操作在Schedulers.io()线程也就是IO线程中进行,回调显示在AndroidSchedulers.mainThread()也就是android的主线程中进行,这样,就可以很方便的进行耗时操作并显示结果了。我们可以把线程打印出来

02-21 14:12:44.976 29015-29074/me.masteryi.gankio D/PhotoActivity: thread1:RxCachedThreadScheduler-2
02-21 14:12:45.116 29015-29015/me.masteryi.gankio D/PhotoActivity: thread2:main

可以看出来保存图片的操作发生在RxCachedThreadScheduler这个线程,而显示结果的线程发生在main线程,也就是主线程。
关于subscribeOn和observeOn这两个方法我也看了很久,根据官方文档

observeOn
To specify on which Scheduler the Observable should invoke its observers’ onNext, onCompleted, and onError methods, use the observeOn operator, passing it the appropriate Scheduler.


subscribeOn
To specify on which Scheduler the Observable should operate, use the subscribeOn operator, passing it the appropriate Scheduler.

我的理解是subscribeOn指定Observable所运行的线程,也就是这里我们Observable.creat()运行的线程,obserOn指定onNext,onError等方法运行的线程,所以我们可以在create中运行耗时方法,在onNext中调用改变UI的方法。我不知道这样理解有没有问题,如果有错,希望大家能够指出来。

好了,最基本的功能都已经实现了,后面我们会加入一些扩展的功能。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值