PhotoView的使用方法

PhotoView的使用方法

PhotoView 是一款擴展自Android ImageView ,支持經過單點/多點觸摸來進行圖片縮放的智能控件。java

項目地址:https://github.com/chrisbanes/PhotoViewandroid

特性

  1. 支持單點/多點觸摸、雙擊,即時縮放圖片;
  2. 支持平滑滾動;
  3. 在滑動父控件下可以運行良好;(例如:ViewPager)
  4. 當用戶的觸點改變是能夠觸發通知

Gradle中依賴

第1步:git

在根build.gradle(不是module的 build.gradle)文件中添加如下內容:github

allprojects {
    repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}

第2步:api

在module 的build.gradle文件中添加如下內容:app

dependencies {
    compile 'com.github.chrisbanes:PhotoView:1.3.0'
}

示例代碼

示例代碼來自 https://github.com/chrisbanes/PhotoView/tree/master/sample框架

有一點點修改的內容dom

基本使用

activity_simple_sample.xml佈局文件:maven

<?xml version="1.0" encoding="utf-8"?>
	<merge xmlns:android="http://schemas.android.com/apk/res/android">
		<FrameLayout
			android:layout_width="match_parent"
			android:layout_height="match_parent">
			
			<uk.co.senab.photoview.PhotoView
				android:id="@+id/iv_photo"
				android:layout_width="fill_parent"
				android:layout_height="fill_parent" />

			<TextView
				android:id="@+id/tv_current_matrix"
				android:layout_width="fill_parent"
				android:layout_height="wrap_content"
				android:layout_gravity="bottom|center_horizontal"
				android:background="#60000000"
				android:gravity="center"
				android:textColor="@android:color/white" />
		</FrameLayout>
	</merge>

SimpleSampleActivity.java的代碼:ide

public class SimpleSampleActivity extends AppCompatActivity {
    static final String PHOTO_TAP_TOAST_STRING = "Photo Tap! X: %.2f %% Y:%.2f %% ID: %d";
    static final String SCALE_TOAST_STRING = "Scaled to: %.2ff";
    static final String FLING_LOG_STRING = "Fling velocityX: %.2f, velocityY: %.2f";

    private TextView mCurrMatrixTv;

    private PhotoViewAttacher mAttacher;

    private Toast mCurrentToast;

    private Matrix mCurrentDisplayMatrix = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_simple_sample);

        //PhotoVIew是ImageView的子類
        ImageView mImageView = (ImageView) findViewById(R.id.iv_photo);
        mCurrMatrixTv = (TextView) findViewById(R.id.tv_current_matrix);

        Drawable bitmap = ContextCompat.getDrawable(this, R.drawable.wallpaper);
        mImageView.setImageDrawable(bitmap);

        // The MAGIC happens here!
        //PhotoViewAttacher是整個庫裏比較重要的一個類,它控制它所附着的PhotoView
        mAttacher = new PhotoViewAttacher(mImageView);
        // Lets attach some listeners, not required though!
        //給mAttacher添加各類事件
        mAttacher.setOnMatrixChangeListener(new MatrixChangeListener());
        mAttacher.setOnPhotoTapListener(new PhotoTapListener());
        mAttacher.setOnSingleFlingListener(new SingleFlingListener());
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // Need to call clean-up
        //它所附着的PhotoView再也不使用的時候,進行清理
        mAttacher.cleanup();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main_menu, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        MenuItem zoomToggle = menu.findItem(R.id.menu_zoom_toggle);
        assert null != zoomToggle;
        zoomToggle.setTitle(mAttacher.canZoom() ? R.string.menu_zoom_disable : R.string.menu_zoom_enable);
        return super.onPrepareOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case R.id.menu_zoom_toggle:
                //設置是否能夠縮放
                mAttacher.setZoomable(!mAttacher.canZoom());
                return true;

            case R.id.menu_scale_fit_center:
                //設置ScaleType
                mAttacher.setScaleType(ImageView.ScaleType.FIT_CENTER);
                return true;

            case R.id.menu_scale_fit_start:
                mAttacher.setScaleType(ImageView.ScaleType.FIT_START);
                return true;

            case R.id.menu_scale_fit_end:
                mAttacher.setScaleType(ImageView.ScaleType.FIT_END);
                return true;

            case R.id.menu_scale_fit_xy:
                mAttacher.setScaleType(ImageView.ScaleType.FIT_XY);
                return true;

            case R.id.menu_scale_scale_center:
                mAttacher.setScaleType(ImageView.ScaleType.CENTER);
                return true;

            case R.id.menu_scale_scale_center_crop:
                mAttacher.setScaleType(ImageView.ScaleType.CENTER_CROP);
                return true;

            case R.id.menu_scale_scale_center_inside:
                mAttacher.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
                return true;

            case R.id.menu_scale_random_animate:
            case R.id.menu_scale_random:
                Random r = new Random();

                float minScale = mAttacher.getMinimumScale();
                float maxScale = mAttacher.getMaximumScale();
                float randomScale = minScale + (r.nextFloat() * (maxScale - minScale));

                //設置縮放與動畫
                mAttacher.setScale(randomScale, item.getItemId() == R.id.menu_scale_random_animate);

                showToast(String.format(SCALE_TOAST_STRING, randomScale));

                return true;
            case R.id.menu_matrix_restore:
                if (mCurrentDisplayMatrix == null)
                    showToast("You need to capture display matrix first");
                else
                    mAttacher.setDisplayMatrix(mCurrentDisplayMatrix);
                return true;
            case R.id.menu_matrix_capture:
                //這段代碼有問題
                if (mCurrentDisplayMatrix == null)
                    mCurrentDisplayMatrix = new Matrix();
                mAttacher.getDisplayMatrix(mCurrentDisplayMatrix);
                return true;
            case R.id.extract_visible_bitmap:
                try {
                    //經過mAttacher得到可視部分的Bitmap對象
                    Bitmap bmp = mAttacher.getVisibleRectangleBitmap();
                    File tmpFile = File.createTempFile("photoview", ".png",
                            Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS));
                    FileOutputStream out = new FileOutputStream(tmpFile);
                    bmp.compress(Bitmap.CompressFormat.PNG, 90, out);
                    out.close();
                    Intent share = new Intent(Intent.ACTION_SEND);
                    share.setType("image/png");
                    share.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(tmpFile));
                    startActivity(share);
                    Toast.makeText(this, String.format("Extracted into: %s", tmpFile.getAbsolutePath()), Toast.LENGTH_SHORT).show();
                } catch (Throwable t) {
                    t.printStackTrace();
                    Toast.makeText(this, "Error occured while extracting bitmap", Toast.LENGTH_SHORT).show();
                }
                return true;
        }

        return super.onOptionsItemSelected(item);
    }

    /**
     * 點擊一下的事件
     */
    private class PhotoTapListener implements PhotoViewAttacher.OnPhotoTapListener {

        @Override
        public void onPhotoTap(View view, float x, float y) {
            float xPercentage = x * 100f;
            float yPercentage = y * 100f;

            showToast(String.format(PHOTO_TAP_TOAST_STRING, 
                                                        xPercentage, 
                                                        yPercentage, 
                                                        view == null ? 0 : view.getId()));
        }

        @Override
        public void onOutsidePhotoTap() {
            showToast("You have a tap event on the place where out of the photo.");
        }
    }

    /**
     * Matrix改變監聽事件
     */
    private class MatrixChangeListener implements PhotoViewAttacher.OnMatrixChangedListener {

        @Override
        public void onMatrixChanged(RectF rect) {
            mCurrMatrixTv.setText(rect.toString());
        }
    }

    /**
     * 單個觸摸的滑動事件
     */
    private class SingleFlingListener implements PhotoViewAttacher.OnSingleFlingListener {

        @Override
        public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
            if (BuildConfig.DEBUG) {
                Log.d("PhotoView", String.format(FLING_LOG_STRING, velocityX, velocityY));
            }
            return true;
        }
    }

    private void showToast(CharSequence text) {
        if (null != mCurrentToast) {
            mCurrentToast.cancel();
        }

        mCurrentToast = Toast.makeText(SimpleSampleActivity.this, text, Toast.LENGTH_SHORT);
        mCurrentToast.show();
    }

}

從代碼中,能夠看到如下幾點基本內容:

  1. 能夠直接在佈局文件中使用PhotoView
  2. 在Activity中,能夠用ImageView來獲取佈局文件中的PhotoView
  3. 經過PhotoViewAttacher來對PhotoView進行控制:
    • 經過setScaleType方法設置ScaleType
    • 經過setScale方法進行帶有動畫的縮放
    • 經過getDisplayMatrixsetDisplayMatrix得到與設置顯示的Matrix
    • 經過getVisibleRectangleBitmap方法得到可視部分的Bitmap對象
  4. 經過PhotoViewAttacher來對PhotoView進行事件響應:
    • GestureDetector.OnDoubleTapListener 雙擊事件
    • OnScaleChangeListener scale改變事件
    • OnSingleFlingListener 單個觸摸的滑動事件
    • OnLongClickListener 長按事件
    • OnMatrixChangedListener Matrix改變監聽事件
    • OnPhotoTapListener 圖片點擊事件

在ViewGroup中使用

在ViewGroup(使用了onInterceptTouchEvent)中使用PhotoView會拋異常,最多見的是ViewPager與DrawerLayout。這個是框架自己的問題,尚未解決。爲了防止這個異常,就是簡單的catch住這個異常就能夠了。這是一個基本的代碼模板,ProblematicViewGroup表明會出現問題的ViewGroup(如ViewPager和DrawerLayout):

public class HackyProblematicViewGroup extends ProblematicViewGroup {

    public HackyProblematicViewGroup(Context context) {
        super(context);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        try {
            return super.onInterceptTouchEvent(ev);
        } catch (IllegalArgumentException e) {
            //uncomment if you really want to see these errors
            //e.printStackTrace();
            return false;
        }
    }
}

例如,若是使用的是ViewPager,那麼這樣:

/**
 * Hacky fix for Issue #4 and
 * http://code.google.com/p/android/issues/detail?id=18990
 * <p/>
 * ScaleGestureDetector seems to mess up the touch events, which means that
 * ViewGroups which make use of onInterceptTouchEvent throw a lot of
 * IllegalArgumentException: pointerIndex out of range.
 * <p/>
 * There's not much I can do in my code for now, but we can mask the result by
 * just catching the problem and ignoring it.
 *
 * @author Chris Banes
 */
public class HackyViewPager extends ViewPager {
	
    public HackyViewPager(Context context) {
        super(context);
    }

    public HackyViewPager(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
		try {
			return super.onInterceptTouchEvent(ev);
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
			return false;
		}
    }
}

若是使用DrawerLayout,那麼:

/**
 * Hacky fix for Issue #4 and
 * http://code.google.com/p/android/issues/detail?id=18990
 * <p/>
 * ScaleGestureDetector seems to mess up the touch events, which means that
 * ViewGroups which make use of onInterceptTouchEvent throw a lot of
 * IllegalArgumentException: pointerIndex out of range.
 * <p/>
 * There's not much I can do in my code for now, but we can mask the result by
 * just catching the problem and ignoring it.
 * Created by John on 10/1/15.
 */
public class HackyDrawerLayout extends DrawerLayout {

    public HackyDrawerLayout(Context context) {
        super(context);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        try {
            return super.onInterceptTouchEvent(ev);
        } catch (IllegalArgumentException e) {
            e.printStackTrace();
            return false;
        }
    }
}

旋轉圖片

RotationSampleActivity.java:

public class RotationSampleActivity extends AppCompatActivity {

    private PhotoView photo;
    private final Handler handler = new Handler();
    private boolean rotating = false;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        photo = new PhotoView(this);
        photo.setImageResource(R.drawable.wallpaper);
        //整個佈局就是一個PhotoView
        setContentView(photo);
    }

    @Override
    protected void onPause() {
        super.onPause();
        handler.removeCallbacksAndMessages(null);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        menu.add(Menu.NONE, 0, Menu.NONE, "Rotate 10° Right");
        menu.add(Menu.NONE, 1, Menu.NONE, "Rotate 10° Left");
        menu.add(Menu.NONE, 2, Menu.NONE, "Toggle automatic rotation");
        menu.add(Menu.NONE, 3, Menu.NONE, "Reset to 0");
        menu.add(Menu.NONE, 4, Menu.NONE, "Reset to 90");
        menu.add(Menu.NONE, 5, Menu.NONE, "Reset to 180");
        menu.add(Menu.NONE, 6, Menu.NONE, "Reset to 270");
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case 0:
                //相對旋轉Degree to rotate PhotoView to, should be in range 0 to 360
                photo.setRotationBy(10);
                return true;
            case 1:
                photo.setRotationBy(-10);
                return true;
            case 2:
                toggleRotation();
                return true;
            case 3:
                //絕對旋轉Degree to rotate PhotoView to, should be in range 0 to 360
                photo.setRotationTo(0);
                return true;
            case 4:
                photo.setRotationTo(90);
                return true;
            case 5:
                photo.setRotationTo(180);
                return true;
            case 6:
                photo.setRotationTo(270);
                return true;
        }

        return super.onOptionsItemSelected(item);
    }

    private void toggleRotation() {
        if (rotating) {
            handler.removeCallbacksAndMessages(null);//取消旋轉
        } else {
            rotateLoop();
        }
        rotating = !rotating;
    }

    private void rotateLoop() {
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                //每次轉一度
                photo.setRotationBy(1);
                //一直轉
                rotateLoop();
            }
        }, 15);
    }
}

從代碼中能夠看出:

  1. setRotationBy來設置相對旋轉
  2. setRotationTo來設置絕對旋轉

與Picasso結合使用

PicassoSampleActivity.java的代碼以下,值得注意的是將圖片內容顯示到PhotoView以後,須要更新PhotoViewAttacher 對象

public class PicassoSampleActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_picasso_sample);
        //獲取佈局文件中的PhotoView
        PhotoView photoView = (PhotoView) findViewById(R.id.iv_photo);
        //聲名 PhotoViewAttacher 對象
        final PhotoViewAttacher attacher = new PhotoViewAttacher(photoView);

        //使用Picasso加載圖片
        Picasso.with(this)
                .load("http://ww4.sinaimg.cn/large/610dc034jw1f8xz7ip2u5j20u011h78h.jpg")
                .into(photoView, new Callback() {
                    @Override
                    public void onSuccess() {
                        //將圖片內容顯示到PhotoView以後,須要更新PhotoViewAttacher 對象
                        attacher.update();
                    }

                    @Override
                    public void onError() {
                    }
                });
    }
}

在Activity間跳轉時的轉換效果

起始Activity,它的佈局裏只有一個RecyclerView,RecyclerView的item只有一個ImageView,點擊這個ImageView響應點擊事件,打開另外一個Activity。

起始ActivityTransitionActivity.java的代碼:

public class ActivityTransitionActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_transition);
        RecyclerView list = (RecyclerView) findViewById(R.id.list);
        list.setLayoutManager(new GridLayoutManager(this, 2));
        ImageAdapter imageAdapter = new ImageAdapter(new ImageAdapter.Listener() {
            @Override
            public void onImageClicked(View view) {
                transition(view);
            }
        });
        list.setAdapter(imageAdapter);
    }

    private void transition(View view) {
        if (Build.VERSION.SDK_INT < 21) {//只有api level>=21才能夠用這種效果
            Toast.makeText(ActivityTransitionActivity.this, "21+ only, keep out", Toast.LENGTH_SHORT).show();
        } else {//這種寫法不是最好的,只是爲了說明能夠有這種轉換效果
            Intent intent = new Intent(ActivityTransitionActivity.this, ActivityTransitionToActivity.class);
            ActivityOptionsCompat options = ActivityOptionsCompat.
                    makeSceneTransitionAnimation(
                            ActivityTransitionActivity.this,//起始activity
                            view, //轉換的起始view,這裏就是這個ImageView
                            getString(R.string.transition_test));//view 對應的名字
            startActivity(intent, options.toBundle());
        }
    }
}

在被啓動的activity中,只有一個PhotoView,它的佈局文件是:

<?xml version="1.0" encoding="utf-8"?>
<uk.co.senab.photoview.PhotoView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/iv_photo"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:src="@drawable/wallpaper"
    android:transitionName="@string/transition_test" />
<!-- 這裏的transittionName與啓動它的Activity裏使用的name必須是同樣的 -->
相關文章
相關標籤/搜索