PhotoView的使用方法

PhotoView的使用方法

PhotoView 是一款扩展自Android ImageView ,支持通过单点/多点触摸来进行图片缩放的智能控件。

项目地址:https://github.com/chrisbanes/PhotoView

特性

  1. 支持单点/多点触摸、双击,即时缩放图片;
  2. 支持平滑滚动;
  3. 在滑动父控件下能够运行良好;(例如:ViewPager)
  4. 当用户的触点改变是可以触发通知

Gradle中依赖

第1步:

在根build.gradle(不是module的 build.gradle)文件中添加以下内容:

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

第2步:

在module 的build.gradle文件中添加以下内容:

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

示例代码

示例代码来自 https://github.com/chrisbanes/PhotoView/tree/master/sample

有一点点修改的内容

基本使用

activity_simple_sample.xml布局文件:

	<?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的代码:

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必须是一样的 -->

转载于:https://my.oschina.net/neumeng/blog/761172

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值