前言:

  • 本文翻译于 sonymobile 的一系列教程,指导 Android 开发人员如何用一个手指控制图片的缩放,我们一般都是用两个手指控制图片的缩放。一个手指缩放图片的操作比较少见,但我还是想了解一下他们的思路和实现过程,所以边学习边翻译。这系列课程共四部分。这是第一部分,原文在已经官网找不到了,但我之前保存了html文档,所以这里无法给出原文链接。我会一段一段地给出原文,并在下面给出翻译。这些课程都比较老了,所以项目是基于Eclipse构建的,现在大家都基于AndroidStudio开发,所以不能直接导入,好在这些课程代码都不多,就几个文件,所以还是可以很方便地移植到AndroidStudio。
    由于这个项目用到了onCreateOptionsMenu,所以在新的Android系统上直接继承Activity会有问题,因为没有Menu按键,所以需要小小改动一下,把Activity改成AppCompatActivity。
    文中的单词Pan, 不知道怎么翻译才算得体,我先翻译成平放了,这个状态表示把图片拖到一个位置,同样,Pan value翻译成平放值了。
    下面是一张效果图


    panzoom1.gif

正文:

原文:

Android one finger zoom tutorial – Part 1

Welcome to this first Android tutorial on how to make your own one finger zoom control like the one found in the Camera and Album applications in the Sony Ericsson X10 Mini. The Tutorial is divided into four parts, each part adding new features. Below is a link to download the source code for part 1 of the tutorial, prepared for you to set up your own project in e.g. Eclipse.

译文:

Android单手指缩放-第一部分

欢迎来到这个Android课程第一节,这个课程是关于如何制作你自己的单手指缩放控制的,就像在索尼爱立信X10 Mini手机上的相机(Camera )和相册(Album )应用看到的一样。这个课程被分为4部分,每一部分增加一些新特性,下面是下载第一部分源码的链接,为你在Eclipse中创建工程准备的(译注:没有下载链接了,我会在结尾把代码上传到网盘里)。

原文:

Don’t miss to download the Sony Ericsson Tutorials app on Android Market where all applications in this and other Sony Ericsson tutorials are available. With the SonyEricsson tutorials app you can easily try out the different parts of the tutorial and see what the end result will be.

译文:

不要错过在Android市场下载“Sony Ericsson Tutorials”应用,这个课程的所有应用和其他索尼爱立信课程的应用都在哪儿。通过这个索尼爱立信课程应用,你可以很容易提炼出课程的不同部分,并看到它最终是什么样的。

原文:

Below is a video showing what you will be able to do after seeing all steps of the tutorial. There following parts of the turoital will be added over the next few weeks.

译文:

下面这个视频展示了当你看完这个课的所有步骤以后,你将能够做什么。这节课后面的部分将会在未来几周添加到哪儿(译注:找不到视频了)。

原文:

There will be some math in this tutorial, but there is no need to get discouraged if it is too much for you. Please feel free to skip the math part and accept that they work.

译文:

这个课程会涉及一些数学知识,但如果这对你来说太难的话,没有必要感到气馁,放轻松跳过数学的部分并接受它们的行为。

原文:

In this first part of the tutorial we are going to make a basic, interactive, zoom control that allows us to zoom into an image. This will be achieved by implementing three classes representing the following: 1) our own View class for drawing images with zoom applied to it, 2) a class holding the state of the zoom, and 3) a class implementing the OnTouchListener interface allowing the user to interact with the zoom. As we progress in the tutorial we will see the benefits of dividing our application into different classes this way, as it makes it easy to change specific parts of the behavior.

译文:

在课程的第一部分我们准备做一个基本的,可交互的,缩放控制,它允许我们放大(zoom into )一个图片。这将会通过实现三个类完成,这些类代表下面的: 1)我们自己的View类,用来画image并把缩放应用到它上面。2)一个保存缩放状态的类。3)一个实现OnTouchListener接口的类,使用户和缩放交互成为可能。随着我们在课程中前进,我们将会看见把我们的应用以这种方式划分成不同类的好处,它使得修改行为的特定部分变得容易。

原文:

Basic zoom listener

译文:

基础的缩放监听(译注:这段着实突兀,就这么三个单词)

原文:

Defining a zoom state

The first thing we need to do is to define how the zoom state should work. The goal for our definition should be to make it easy to understand, simple to interact with and do math on. There is probably other decent ways to do this, but I’ll show you one that worked for me.

译文:

定义一个缩放状态

我们需要做的第一件事情是定义缩放状态应该如何工作。我们定义的目标应该是使它容易理解,交互和数学运算简单,也许有其他得体的方法来做这个,但我将会给你展示一种对我来说可以工作的方式。

原文:

We’ll define the zoom state using three parameters.

  • The amount of zoom, that is how much the content has been scaled. We let a zoom value of 1 define the state where the content fits the view perfectly in at least one dimension.
  • The amount of pan in dimension-x (pan-x), that is how much the content is moved in respect to the width of the screen. We let the pan value define where the center position of the zoom-view (the view defining which part of the content we see) is in relation to the content. For example, a pan-x value of 0.5 would mean the center of the content would be visible in the center of the view, while a value of 0 would mean the left hand side of the content would be visible in the center of the screen.
  • The amount of pan in dimension-y (pan-y), like the pan-x except how the content is moved in relation to the height of the screen.

译文:

我们将会用三个参数定义缩放状态。

  • 缩放数量,它表示当前内容已经缩放了多少,我们用一个缩放值1定义这样的状态:内容至少在一个纬度上正好完美适应View。
  • X纬度的平放数量(pan-x),它表示内容相对于屏幕宽度移动了多少。我们用平放值定义缩放view(view决定我们能看见的内容的哪一部分)相对于内容的中心位置(译注:view就是控件,content就是控件中的一张图片)。例如,pan-x 值是0.5意味着内容的中心应该出现在view的中心;值是0意味着内容的左边出现在屏幕的中心。
  • Y纬度的平放数量(pan-y),和pan-x相似,除了内容如何相对于屏幕高度移动。

原文:

Images illustrating how the zoom state works, the dashed gray area represents what is shown in the view and the patterned area represents the content. On the left: Zoom is 1, pan-x and pan-y are both 0.5, in this state the image fits the screen perfectly. In the middle: Zoom is 2, pan-x and pan-y are still both 0.5, less content is now shown on the screen but will be scaled up. To the right: Zoom is 3, pan-x is 0.7 and pan-y is 0.833, we now see less of the image, only the top right corner, scaled up.

译文:

图片(译注:抱歉我当时只保存了html,没有保存图片,所以现在无法提供图片了)阐述了缩放状态(zoom state)是如何工作的,灰色的虚线区域代表view中显示了什么,图案区域代表content,在左边,Zoom是1,pan-x和pan-y都是0.5,在这种状态下图片完美适应屏幕。在中间,Zoom是2,pan-x和pan-y仍然是0.5,现在屏幕上显示较少的内容但是会按比例放大。在右边,Zoom是3,pan-x是0.7,pan-y是0.883,我们现在看到更少的图片,只有放大了的右上角。(译注:我实际测试的时候,第三种情况只看到放大了的右下角,显示在屏幕的左上角,不知道原文这里发生了什么。但又没保存他的图,需要脑补。第一张图片显示在屏幕的中央。中间在图一的基础上放大content,所以只能看见内容的中间一部分,因为左右已经到屏幕外边了,右图在中间图的基础上,继续放大,并向右,向下移动了。)
(译注:为了有更好的效果,我还是通过修改代码,截了上面三中情况下的图)

left.jpg middle.jpg right.jpg

原文:

Implementation

Now that we’ve got the basics laid down, let’s move on to coding and let’s start by implementing the state. Since we know the view will want to determine when the state has been modified we can implement the state as an instance of Observable, allowing us to easily notify other objects of state changes.

译文:

实现

现在我们有了基本的主张,让我们移步代码并以状态的实现开始,既然我们知道当状态被修改时view想要确定下来,我们可以将状态实现为一个Observable实例,使得我们容易把状态的变化通知给其他对象。

public class ZoomState extends Observable {    private float mZoom;    private float mPanX;    private float mPanY;    public float getPanX() {        return mPanX;    }    public float getPanY() {        return mPanY;    }    public float getZoom() {        return mZoom;    }    public void setPanX(float panX) {        if (panX != mPanX) {            mPanX = panX;            setChanged();        }    }    public void setPanY(float panY) {        if (panY != mPanY) {            mPanY = panY;            setChanged();        }    }    public void setZoom(float zoom) {        if (zoom != mZoom) {            mZoom = zoom;            setChanged();        }    }}

原文:

As simple as that, get/set methods for the available parameters and a call to setChanged() every time a parameter is changed. The client changing the values is responsible for calling notifyObservers() after modifying the state. Now that we have a state class, let’s move on to the view, we will extend the basic View class and implement the Observer interface in order to be able to observe and get callbacks when the state changes.

译文:

就是那么简单,可用参数的get/set方法,每一次参数改变时的一个setChanged()调用,客户端值改变,在修改状态以后负责调用notifyObservers()。现在我们有一个state 类,让我们移步到view,我们将会扩展基本的View类,并且为了能观察状态变化,并在状态变化时得到回调,还要实现Observer 接口。

public class ImageZoomView extends View implements Observer {    private Bitmap mBitmap;    private ZoomState mState;    public ImageZoomView(Context context, AttributeSet attrs) {        super(context, attrs);    }    public void setImage(Bitmap bitmap) {        mBitmap = bitmap;        invalidate();    }    public void setZoomState(ZoomState state) {        if (mState != null) {            mState.deleteObserver(this);        }        mState = state;        mState.addObserver(this);        invalidate();    }    @Override    protected void onDraw(Canvas canvas) {    }    public void update(Observable observable, Object data) {        invalidate();    }}

原文:

Now we have created the basic structure of the view, and as this will be a zoom view for images we will be using a Bitmap object as the zoom content. We can set a bitmap to zoom in, we can set a zoom state to observe and we call invalidate when the content or the state changes. Next step we want to draw the bitmap, for this we need a Paint object and two Rect objects representing rectangles; one for the part of the content we wish to draw and one for the area of the view we wish to draw on.

译文:

现在我们已经创建了view的基本结构,由于它将会是缩放图片的view,我将会用一个Bitmap对象作为缩放内容,我们可以设置一个缩放状态来观察,我们可以设置一个待缩放的bitmap进来,我们可以设置一个缩放状态来观察,当内容或者状态改变时,我们可以调用invalidate。下一步我们想画bitmap,我们需要一个Paint对象和两个Rect对象代表矩形,一个表示我们想绘制的内容的部分(译注:绘整张图或者部分图),另一个是我们想在上面绘图的view的区域(译注:这里有两个矩形,一个表示图片上的区域,这个区域的图像会被绘制到View中。另一个表示View上的区域,图像会在这个区域进行绘制)。

    private final Paint mPaint = new Paint(Paint.FILTER_BITMAP_FLAG);    private final Rect mRectSrc = new Rect();    private final Rect mRectDst = new Rect();    @Override    protected void onDraw(Canvas canvas) {        if (mBitmap != null && mState != null) {            canvas.drawBitmap(mBitmap, mRectSrc, mRectDst, mPaint);        }    }

原文:

As you can see there is a drawBitmap method on the Canvas object that takes two rectangle objects as input parameters just the way we need it to, and since we will be scaling the bitmap we should enable the bitmap filtering on the paint object. Finally we need to set up the rectangles correctly, and then we’re done.

译文:

你可以看见Canvas对象有一个drawBitmap 方法,它接受两个rectangle 对象作为形式参数,就像我需要的那样,既然我们将要缩放bitmap,我们应该在paint 对象上启用bitmap filtering(译注:咋翻译呢?图像过滤器?反正画图片这个标记基本是必选项)。最后我们需要正确地创建矩形,然后就做完了。

原文:

To set up the rectangles so that the visible part of the source image is cropped correctly and drawn at the correct location on the view we need to introduce a new variable. The reason for this is that if the content does not have the same aspect ratio (width / height) as the view, then a zoom value of 1 will mean that only one of the dimensions fit the view. In the other dimension the content will only partially cover the view, which means it will correspond to a zoom value lower than 1 in that particular dimension. The solution for this is to introduce a variable we will call the aspect quotient, that is the quotient between the aspect ratio of the content and the aspect ratio of the view. By using this value we can separate the zoom value on the x-axis and on the y-axis, and to do so we will add help methods to the ZoomState class to calculate them.

译文:

创建矩形是为了正确地裁剪原图像的可见部分,为了在view的正确位置绘制,我们需要引入一个新变量,这样做的原因是如果content和view没有相同的宽高比(width / height),那么Zoom值是1意味着content只在一个维度上适应view。在另一个维度content只是部分覆盖view,这意味着它在特定的维度上对应着一个小于1的Zoom值。解决这个问题的方式是引入一个变量,我们叫它横纵系数(aspect quotient)(译注:我不知道怎么翻译,先这样子,后面有好的见解再修改,反正就是一个变量),这是content的宽高比例与view的宽高比例的一个商,通过使用这个值我们可以分开x轴的zoom值和y轴的zoom值,为了做这个我们在ZoomState 类中添加辅助方法来计算它。

    public float getZoomX(float aspectQuotient) {        return Math.min(mZoom, mZoom * aspectQuotient);    }    public float getZoomY(float aspectQuotient) {        return Math.min(mZoom, mZoom / aspectQuotient);    }

原文:

As the aspect quotient is defined by the view characteristics and content we will store this value in the ZoomView, and update its value whenever changes are made to the view or content (the content in our case being a bitmap). Such changes are for example if the view changes in size due to the user changing the phone orientation, or if the content changes because of the user switching between zoomable images in an album application.

译文:

由于横纵系数(aspect quotient)是通过view和content的特征定义的,我们在ZoomView中保存这个值,并且在改变view或者content的任何时候更新这个值(content在我们的实例中是一个bitmap)。这些改变有:例如由于用户旋转手机的方向引起的view大小的变化,或者用户在相册(album)应用中切换可缩放的图片。

 private float mAspectQuotient;    private void calculateAspectQuotient() {        if (mBitmap != null) {            mAspectQuotient =                (((float)mBitmap.getWidth()) / mBitmap.getHeight()) /                (((float)getWidth()) / getHeight());        }    }    public void setImage(Bitmap bitmap) {        mBitmap = bitmap;        calculateAspectQuotient();        invalidate();    }    @Override    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {        super.onLayout(changed, left, top, right, bottom);        calculateAspectQuotient();    }

原文:

With the help of these methods we can now implement the onDraw method correctly. We’ll do this by first calculating how much scaling to apply in relation to the full size of the content (zoomX, zoomY). Then we calculate the source rectangle to crop the portion of the content that should be visible, allowing the calculations to crop outside of the content boundaries. Finally we need to recalculate the source rectangle as the drawBitmap function does not support cropping outside of our bitmap content, and then we recalculate the destination rectangle to compensate for this.

译文:

在这几个方法的帮助下,我们现在能够正确地实现onDraw 方法。我们首先计算相对于内容完整尺寸的缩放比例(zoomX, zoomY)是多少,然后我们计算源矩形来裁剪内容应该可见区域的一部分,允许计算可以裁剪到内容边界以外,最后我们需要重新计算源矩形,是因为drawBitmap 方法不支持裁剪掉我们内容图像以外的部分,然后我们重新计算目标矩形来弥补这部分。(译注:这部分比较晦涩,可以理解成第一次裁剪的图越过了bitmap的边界,所以需要重新裁剪)

原文:

Images showing how the zooming is done by setting up source and destination rectangles. The leftmost image shows the content with the crop area shown with a dashed square, this is how the source rectangle is initially calculated. To its right is the result shown in the zoom view, the dashed square here represents the destination rectangle that now covers the entire zoom view.

译文:

图像展示了如何通过设置源矩形和目标矩形完成的缩放。最左边的图展示了带有用灰色正方形表示裁剪区域的内容,这是源矩形初始的计算方式。它右边的图是它在zoom view(译注:缩放View,即前面提到的控件)中的结果,这儿的灰色正方形代表现在覆盖整个zoom view的目标矩形(译注:没有图了)。

原文:

The next step is to recalculate the rectangles so that the source rectangle fits inside of the content without changing the result shown on the screen. The left most image now shows the content with the zoom window completely inside the content. To its right is the result shown in the zoom view, the dashed square represents the destination rectangle that has now been recalculated so that it doesn’t cover the parts of the view where nothing should be drawn.

译文:

下一步是重新计算矩形,这样把源矩形调整到内容区域的内部,也不改变在屏幕上显示的结果(译注:因为第一次裁剪得到的矩形会扩展到内容之外,而扩展到内容之外这一部分是没有可显示的图像的,所以再做一次计算,把这些区域裁剪掉)。最左边的图现在展示了带有缩放窗口的内容完全置于内容区域的内部,它右边的是显示在缩放view中的结果,灰色正方形代表目标矩形,现在已经被重新计算,所以它不会覆盖住view中不绘制任何东西的部分。
(译注:这两个地方的图找不见了,非常可惜, 但看代码就会了解,首先取得图像的宽高和view的宽高,然后根据比例和pan-x,pan-y计算源矩形,就是图像的矩形,但图像的矩形有可能有一部分已经在view的外部了,所以重新计算了一下它的宽高,当然这一部分计算需要理解图像的换算过程,需要几何知识,不想了解的话,知道它的工作方式就可以了,反正我也看了个大概,只知道它把content可见的一部分绘制到canvas的一个区域了。)

    @Override    protected void onDraw(Canvas canvas) {        if (mBitmap != null && mState != null) {            final int viewWidth = getWidth();            final int viewHeight = getHeight();            final int bitmapWidth = mBitmap.getWidth();            final int bitmapHeight = mBitmap.getHeight();            final float panX = mState.getPanX();            final float panY = mState.getPanY();            final float zoomX = mState.getZoomX(mAspectQuotient) * viewWidth / bitmapWidth;            final float zoomY = mState.getZoomY(mAspectQuotient) * viewHeight / bitmapHeight;            // Setup source and destination rectangles            mRectSrc.left = (int)(panX * bitmapWidth - viewWidth / (zoomX * 2));            mRectSrc.top = (int)(panY * bitmapHeight - viewHeight / (zoomY * 2));            mRectSrc.right = (int)(mRectSrc.left + viewWidth / zoomX);            mRectSrc.bottom = (int)(mRectSrc.top + viewHeight / zoomY);            mRectDst.left = getLeft();            mRectDst.top = getTop();            mRectDst.right = getRight();            mRectDst.bottom = getBottom();            // Adjust source rectangle so that it fits within the source image.            if (mRectSrc.left < 0) {                                 mRectDst.left += -mRectSrc.left * zoomX;                                 mRectSrc.left = 0;                         }                         if (mRectSrc.right > bitmapWidth) {                mRectDst.right -= (mRectSrc.right - bitmapWidth) * zoomX;                mRectSrc.right = bitmapWidth;            }            if (mRectSrc.top < 0) {                                 mRectDst.top += -mRectSrc.top * zoomY;                                 mRectSrc.top = 0;                         }                         if (mRectSrc.bottom > bitmapHeight) {                mRectDst.bottom -= (mRectSrc.bottom - bitmapHeight) * zoomY;                mRectSrc.bottom = bitmapHeight;            }            canvas.drawBitmap(mBitmap, mRectSrc, mRectDst, mPaint);        }    }

原文:

So great, we have our view and state classes working, now let’s make a simple OnTouchListener so we can get to play around with the zoom as soon as possible! The listener will change the zoom or pan values of the state when the user touches the screen. For simplicity we’ll either zoom or pan when touch starts, which to do will be determined by an attribute we’ll call control type. Other than that we need to store the coordinates of the previous touch event so we know how much the touch has moved between each move event we receive.

译文:

非常好,我们的view类和state类可以工作了,现在让我们制作一个简单的OnTouchListener ,这样我们就能很快和缩放玩耍了。listener 将会在用户触摸屏幕的时候改变state的zoom值或者pan值,简单起见触摸开始时我们要么缩放要么平放,具体做哪一件由一个我们叫做控制类型(control type)的属性决定。不同的是我们需要保存前一个touch事件的坐标,所以我们知道在每一个收到的移动事件中touch已经移动了多远。

原文:

For the zoom value we use the distance moved in the y-dimension, using this as the exponent when calculating the exponentiation of of a well chosen number, and then multiply the result with the current zoom level. Multiplying with an exponentiation is practical since if we where to for example move 10 pixels in the y-dimension, doing so gives the same result if it’s obtained by one or several move events. This is because, where we get the same result when moving a + b pixels as we get if we first move a pixels and then b pixels

译文:

我们使用y纬度的移动距离作为zoom值,当我们计算一个选好数字的幂值时,使用这个作为指数,然后用当前的缩放等级乘以这个幂值。乘以一个幂值是可行的,例如我们在y纬度移动10像素,这样做,它被一个或者多个移动事件获取时会给出相同的结果。这是因为,当移动 a + b 像素,和我们先移动a像素再移动b像素得到相同的结果(译注:看下面的代码就会发现,假如我们在屏幕上向下滑动了dy个像素,就用Math.pow(20, -dy)计算得出一个值,这个20就是选好的数字,也是一个提前设置好 数字,然后用这个值乘以mState.getZoom(),得到一个新的zoom值)。

原文:

For the pan we will simply add the distance moved in to their respective pan values, making the pan move relative to the touch movement. Finally we call notifyObservers() on the state in order to make the view redraw it’s content.

译文:

对于平放,我们需要简单地把移动距离增加给它们各自的pan值,使得pan移动和触摸移动关联起来,最后我们在state 上调用notifyObservers(),让view重新绘制它的内容。

public class SimpleZoomListener implements View.OnTouchListener {    public enum ControlType {        PAN, ZOOM    }    private ControlType mControlType = ControlType.ZOOM;    private ZoomState mState;    private float mX;    private float mY;    public void setZoomState(ZoomState state) {        mState = state;    }    public void setControlType(ControlType controlType) {        mControlType = controlType;    }    public boolean onTouch(View v, MotionEvent event) {        final int action = event.getAction();        final float x = event.getX();        final float y = event.getY();        switch (action) {            case MotionEvent.ACTION_DOWN:                mX = x;                mY = y;                break;            case MotionEvent.ACTION_MOVE: {                final float dx = (x - mX) / v.getWidth();                final float dy = (y - mY) / v.getHeight();                if (mControlType == ControlType.ZOOM) {                    mState.setZoom(mState.getZoom() * (float)Math.pow(20, -dy));                    mState.notifyObservers();                } else {                    mState.setPanX(mState.getPanX() - dx);                    mState.setPanY(mState.getPanY() - dy);                    mState.notifyObservers();                }                mX = x;                mY = y;                break;            }        }        return true;    }}

原文:

Alright, so we’re almost done. Now we just need an Activity and a layout to go with this and we’re done, the layout is quite straight forward, we simply add our new ZoomView and tell it to fill it’s parent.

译文:

好了,我们几乎完成了,现在仅仅需要一个Activity和一个layout,我们就完成了。layout非常直截了当,我们简单地new一个ZoomView 对象,把它添加到并让它填满它的父view。

 

原文:

In the activity we set up our zoom components in the onCreate() method, creating a new zoom state, decoding the bitmap we want to zoom in, creating the listener and connecting it to the state, and finally setting up the view. We also implement the onDestroy method and make sure to recycle bitmaps and remove listeners.

译文:

在Activity的onCreate()方法中我们创建zoom组件,创建一个新的zoom state, decoding一个我们想要缩放的bitmaps ,创建一个listener 并和这个状态连接起来,最后创建view,我们还实现了onDestroy 方法确保回收bitmaps 并移除了listeners。

原文:

Finally we implement onCreateOptionsMenu() and onOptionsItemSelected() respectively enabling the user to change input method using the options menu and to reset the zoom to its original state.

译文:

最后我们实现onCreateOptionsMenu()和onOptionsItemSelected(),分别允许用户通过选项菜单( options menu)修改输入方法(译注:新版本的Android已经没有菜单按键了,所以需要继承AppCompatActivity),把zoom恢复到原始状态。

public class TutorialZoomActivity1 extends Activity {    private static final int MENU_ID_ZOOM = 0;    private static final int MENU_ID_PAN = 1;    private static final int MENU_ID_RESET = 2;    private ImageZoomView mZoomView;    private ZoomState mZoomState;    private Bitmap mBitmap;    private SimpleZoomListener mZoomListener;    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.main);        mZoomState = new ZoomState();        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.image800x600);        mZoomListener = new SimpleZoomListener();        mZoomListener.setZoomState(mZoomState);        mZoomView = (ImageZoomView)findViewById(R.id.zoomview);        mZoomView.setZoomState(mZoomState);        mZoomView.setImage(mBitmap);        mZoomView.setOnTouchListener(mZoomListener);        resetZoomState();    }    @Override    protected void onDestroy() {        super.onDestroy();        mBitmap.recycle();        mZoomView.setOnTouchListener(null);        mZoomState.deleteObservers();    }    @Override    public boolean onCreateOptionsMenu(Menu menu) {        menu.add(Menu.NONE, MENU_ID_ZOOM, 0, R.string.menu_zoom);        menu.add(Menu.NONE, MENU_ID_PAN, 1, R.string.menu_pan);        menu.add(Menu.NONE, MENU_ID_RESET, 2, R.string.menu_reset);        return super.onCreateOptionsMenu(menu);    }    @Override    public boolean onOptionsItemSelected(MenuItem item) {        switch (item.getItemId()) {            case MENU_ID_ZOOM:                mZoomListener.setControlType(ControlType.ZOOM);                break;            case MENU_ID_PAN:                mZoomListener.setControlType(ControlType.PAN);                break;            case MENU_ID_RESET:                resetZoomState();                break;        }        return super.onOptionsItemSelected(item);    }    private void resetZoomState() {        mZoomState.setPanX(0.5f);        mZoomState.setPanY(0.5f);        mZoomState.setZoom(1f);        mZoomState.notifyObservers();    }}

原文:

And voila, we’re done! You can now start to play around with the zoom control on your own. It’s basic but works perfectly well for zooming around in images. A few things will probably seem to be lacking at the moment: first panning around doesn’t follow the finger correctly, and there is no limits or snap to functionality to keep us within reasonable bounds. These things, and others, will be the topic of the next parts of this zoom tutorial.

译文:

瞧,我们完成了。你现在可以和你自己的缩放控制玩耍了,它很基本但是工作地非常完美。现在或许看起来有一些不足,首先是平放没有正确地跟随手指,没有限制或者回弹功能让我们在合理的边界。这些问题,和其他问题,将会是这个课程后面部分的话题。

原文:

Class diagram showing how the zoom components interact

译文:

类图显示了缩放组件之间的关联。(译注,没有类图了,不过就那么几个类。)

原文:

If you want to learn more about the zoom I suggest playing around with the code a bit, try changing how to zoom is controlled by the listener and try zooming into your own images.

译文:

如果你想学习更多关于缩放,我建议是和代码玩一会儿,尝试修改缩放是如何通过listener 控制的,并尝试缩放你自己的图片。

原文:

Good luck!

译文:

祝好!

代码下载

更多相关文章

  1. Android简易实战教程--第二十八话《加载大图片》
  2. Android中的网络应用之网页设置,检测、配置用户设备属性。
  3. 【Android】图片操作工具类(ImageUtil.java)
  4. THE 10-STEP GUIDE TO ANNOTATION PROCESSING IN ANDROID STUDIO
  5. 获取Bitmap和对Bitmap的操作工具类
  6. android双指平移、旋转、缩放控件完美版
  7. greendao 多表查询
  8. material-dialogs(material design)
  9. 拍照(连续拍照 焦距 压缩图像)

随机推荐

  1. android Manifest.xml选项
  2. Android(安卓)onSaveInstanceState和onRe
  3. Android下实现获取本机存储的联系人
  4. Android中的“再按一次返回键退出程序”
  5. Android(安卓)去掉title bar的3个方法
  6. android 拖拽图片&拖动浮动按钮到处跑
  7. 联系人头像 android
  8. Android文件图片上传的详细讲解(三)---模式
  9. Android(安卓)6.0后强制弹出权限
  10. Android(安卓)RingtoneManager铃声管理