1. 程式人生 > >Android使用ViewPager、PhotoView實現類似QQ空間圖片瀏覽功能

Android使用ViewPager、PhotoView實現類似QQ空間圖片瀏覽功能

最近的專案中需要用到類似QQ空間那樣的圖片瀏覽功能,於是Google了一波,發現使用ViewPager與PhotoView即可實現。有了思路便開擼了。

首先,我們定義一個用於展示原圖的Activity。

public class ImageBrowseActivity extends Activity {

    // ViewPager物件
    private ViewPager mViewPager;
    // 原圖url路徑List
    private List<String> imagePath;
    // 當前顯示的位置
    private int position;

    @Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_images_view); // 獲取引數 this.position = getIntent().getIntExtra("position", 0); this.imagePath = getIntent().getStringArrayListExtra("imagePath"
); mViewPager = (ViewPager) findViewById(R.id.images_view); // 設定左右兩列快取的數目 mViewPager.setOffscreenPageLimit(2); // 新增Adapter PagerAdapter adapter = new ImageBrowseAdapter(this, imagePath); mViewPager.setAdapter(adapter); mViewPager.setCurrentItem(position); } }

佈局如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000"
    android:orientation="vertical">

    <android.support.v4.view.ViewPager
        android:id="@+id/images_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center" />

</FrameLayout>

Activity比較簡單,不做過多贅述。下面看看ImageBrowseAdapter怎麼實現的。
ImageBrowseAdapter程式碼如下:

public class ImageBrowseAdapter extends PagerAdapter {

    PhotoViewAttacher mAttacher;
    private Context context;
    private List<String> imagePath;

    public ImageBrowseAdapter(Context context, List<String> urls) {
        this.context = context;
        this.imagePath = urls;
    }

    @Override
    public int getCount() {
        return imagePath.size();
    }

    @Override
    public boolean isViewFromObject(View view, Object o) {
        return view == o;
    }

    @Override
    public void destroyItem(ViewGroup view, int position, Object object) {
        view.removeView((View) object);
    }

    @Override
    public Object instantiateItem(ViewGroup view, int position) {
        ImageView imageView = new ImageView(context);
        new DownloadImageTask(context, imageView).execute(imagePath.get(position));
        view.addView(imageView);
        return imageView;
    }

    private class DownloadImageTask extends BaseAsyncTask<String, Void, Bitmap> {
        ImageView bmImage;

        public DownloadImageTask(Context context, ImageView bmImage) {
            super(context);
            this.bmImage = bmImage;
        }

        @Override
        protected void onPreExecute() {

        }

        protected Bitmap doInBackground(String... urls) {
            String path = urls[0];
            Bitmap result = null;
            try {
                BitmapFactory.Options options = new BitmapFactory.Options();
                //先設定為true,獲取bitmap寬度、高度
                options.inJustDecodeBounds = true; 
                InputStream in = new java.net.URL(path).openStream();
                result = BitmapFactory.decodeStream(in, null, options);
                in.close();
                resetOptions(options);
                //後設置為false,載入進記憶體顯示
                options.inJustDecodeBounds = false; 
                // InputStream在讀取完之後就到結尾了,需要再次開啟才能重新讀取,否則下面的result將返回null
                in = new java.net.URL(path).openStream();
                result = BitmapFactory.decodeStream(in, null, options);
            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        }

        protected void onPostExecute(Bitmap result) {
            if (result != null) {
                bmImage.setImageBitmap(result);
                // PhotoViewAttacher繫結ImageView
                mAttacher = new PhotoViewAttacher(bmImage);
            }
        }
    }

    /**
     * 設定inSampleSize引數
     *
     * @param options
     * @return
     */
    public void resetOptions(BitmapFactory.Options options) {
        DisplayMetrics dm = context.getResources().getDisplayMetrics();
        int width = dm.widthPixels / 2;
        int height = dm.heightPixels / 2;
        options.inSampleSize = (options.outWidth / width > options.outHeight / height) ?
                options.outWidth / width : options.outHeight / height;
    }

}

在Adapter中,我為了求簡便,直接在instantiateItem()中新建ImageView例項,然後傳入到DownloadImageTask非同步任務中進行下載,下載完成後更新,然後將PhotoViewAttacher與ImageView例項繫結。示例中沒有使用PhoteView這個類,主要是用到了PhotoViewAttacher這個類。

我們會有一個顯示縮圖的列表,給列表的Item設定如下點選事件:

@Override
            public void ImageClicked(ArrayList<String> imagePath, int position) {
                Intent intent = new Intent(getActivity(), ImageBrowseActivity.class);
                intent.putExtra("position", position);
                intent.putStringArrayListExtra("imagePath", imagePath);
                startActivity(intent);
            }

imagePath是原圖的路徑陣列,position代表當前顯示第幾張。
點選事件設定好之後,便能實現網路圖片瀏覽功能了。下面上效果圖:

這裡寫圖片描述

小結:

問題:

  • 在使用PhotoView與ViewPager結合進行滑動顯示的時候,會打出ImageView no longer exists. You should not use this PhotoViewAttacher any more.的Log,算不上錯誤,是個Warning,網上查詢的解決辦法是修改cleapUp方法
  • 自己手賤,傳了一些手機拍攝的“高清大圖”,沒滑2張就OOM了。也算是個契機讓自己瞭解下Bitmap的記憶體優化。故在示例中會有resetOptions()方法以及這樣的程式碼片段:
...

BitmapFactory.Options options = new BitmapFactory.Options();
                //先設定為true,獲取bitmap寬度、高度
                options.inJustDecodeBounds = true; 
                InputStream in = new java.net.URL(path).openStream();
                result = BitmapFactory.decodeStream(in, null, options);
                in.close();
                resetOptions(options);
                //後設置為false,載入進記憶體顯示
                options.inJustDecodeBounds = false; 
                // InputStream在讀取完之後就到結尾了,需要再次開啟才能重新讀取,否則下面的result將返回null
                in = new java.net.URL(path).openStream();
                result = BitmapFactory.decodeStream(in, null, options);

...

Bitmap是OOM的一大凶器,所以碰到大圖我們需要壓縮。壓縮可以通過設定BitmapFactory.Options,來生成壓縮後的圖片,主要是inSampleSize引數的設定。resetOptions()方法便是進行設定inSampleSize引數的,方法內部的具體邏輯則需要根據專案業務的需求來制定了。

另外,在第二次BitmapFactory.decodeStream()時,若不進行其他處理,會返回null,後面查詢原因,是InputStream流在訪問後,內部指標會指到尾部,相當於是傳入的in是空的。所以需要新增一句in = new java.net.URL(path).openStream();,重新開啟in,然後再使用decodeStream方法,返回bitmap。