android图片查看(1)

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

好了,先看一下效果



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

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


<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


<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 [email protected]
 * 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) 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这两个方法我也看了很久,根据官方文档

android图片查看(1)_第1张图片
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.


android图片查看(1)_第2张图片
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的方法。我不知道这样理解有没有问题,如果有错,希望大家能够指出来。

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

你可能感兴趣的:(android,android)