前言:
本文翻译于 sonymobile 的一系列教程,指导 Android 开发人员如何用一个手指控制图片的缩放,接第一篇。这一篇在上一篇的基础上,对缩放和平放增加了限制,这些限制主要是依据内容的宽高比,控件的宽高比,缩放值,平放值,换算得来的,理解计算的过程需要读代码,但可以如第一篇所说:不关注计算的过程,只关注它的结果,粗略理解作者的意图。
正文:
原文:
Android one finger zoom tutorial – Part 2
Welcome to the second part of the Android tutorial on how to make your own zoom control like the one used in Sony Ericsson X10 Mini in the Camera and Album applications. Click [here] to read the first part of the tutorial.
译文:
Android单手指缩放第二部分
欢迎来到这个Android 课程的第二部分,如何制作你自己的缩放控制,像Sony Ericsson X10 Mini 相机和相册应用中使用的一样。点击这儿阅读课程的第一部分。
原文:
Don’t forget to go to Android Market and download Sony Ericsson Tutorials, the app that collects all sample apps in this and other Sony Ericsson tutorials.
译文:
不要忘了去安卓市场下载Sony Ericsson Tutorials,这个app收集了这个课程和其他Sony Ericsson课程的所有示例应用。
原文:
In this part of the tutorial we will build on the zoom application we started in part 1. As you might remember, in part 1 we finished with a zoom application that didn’t have any limits, we could zoom and pan into the void and back. In this tutorial we will introduce limits and we will also make sure that the pan always follows the finger as one would expect, as we in part 1 could see panning following the finger differently depending on the current zoom level. Below is a link to the source code for step 2 and the video showing what you will learn in the one finger zoom tutorial series.
译文:
这一部分课程我们将在第一部分启动的缩放应用上改造。你或许还记得,在第一部分我们以一个没有任何限制的缩放应用收工的。我们能缩放或者平放成空的并回退。在这节课程我们将会引入限制,并且确保平放和期望的一样经常跟随手指,在第一部分我们可以看见平放跟随手指因当前的缩放级别不同而不同。下面是第二步的源代码链接,并且视频显示了你将会在单手指缩放系列课程中学到的知识。(译注:本文结尾有源代码下载链接,视频没有了。)
原文:
The aspect quotient
Remember this picture from part 1?
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.
译文:
横纵系数(aspect quotient)
还记得来自第一部分的这张图吗?(译注:没有图了,解说可以参考第一部分的截图)
图片说明了缩放状态(zoom state)是如何工作的,灰色虚线区域代表view中所要显示的,图案区域代表内容。在左边,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.833,我们现在看见更少的图像,只有放大了的右上角。(译注:具体解释看本课程第一节的图片示例。)
原文:
In the rightmost image we’re at the top right pan limit, this means the maximum pan values are 0.7 and 0.833 on the x- and y-axis respectively when the zoom level is 3. The actual limits are dependent on the level of zoom in that particular dimension, and the level of zoom in a particular dimension depends on the aspect quotient (quotient between content aspect ratio and view aspect ratio, refer to part 1 for more explanation). And to make a long story short, in order to apply limits to the pan, as well as making pan follow the finger correctly, we need the aspect quotient.
译文:
在最右边的图片,我们处于右上角的平放边界(pan limit),这意味着当缩放值(zoom value)是3的时候,x轴和y轴的最大平放值(pan value)分别是0.7和0.833,实际限制依赖具体维度上的缩放级别,具体纬度上的缩放级别依赖横纵系数(aspect quotient )(content横纵比例与view横纵比例的商,更多解释参见第一部分)。长话短说,为了给平放采取限制,同样使得平放正确地跟随手指,我们需要横纵系数(aspect quotient)。
原文:
Currently though, from part 1, our aspect quotient is only available in the ImageZoomView. So first let us change this and create an object that holds the aspect quotient and extends Observable as a means of easily notifying whom ever wishes to observe.
译文:
不过目前,从第一部分,我们的横纵系数只能在ImageZoomView可用,所以首先让我们修改这个并且创建一个对象来保存横纵系数,并且扩展Observable 作为一个方便通知想要观察它的人的手段(译注:这里直译有些别扭,但我还是这样翻译了,就是为了方便通知它的观察者,但前提是它可被观察,所以说是想观察它)。
public class AspectQuotient extends Observable {
private float mAspectQuotient;
public float get() {
return mAspectQuotient;
}
public void updateAspectQuotient(float viewWidth, float viewHeight, float contentWidth,
float contentHeight) {
final float aspectQuotient = (contentWidth / contentHeight) / (viewWidth / viewHeight);
if (aspectQuotient != mAspectQuotient) {
mAspectQuotient = aspectQuotient;
setChanged();
}
}
}
原文:
Pretty straight forward, the calculations are the same as in part 1 and just as the ZoomState observable we leave it up to the client updating the aspect quotient to notify observers. The next step is to update ImageZoomView to hold a AspectQuotient object (as opposed to just a float as in part 1), and give others access to it. Of course we also need to update the code that currently modifies the aspect quotient, but I’ll leave code like this out of the tutorial as it’s straightforward and just re-factors existing functionality. Please see the code in the project linked below for reference.
译文:
非常直接,计算和第一部分是一样的,就像可观察的ZoomState 一样,我们让客户端决定修改横纵系数来通知观察者。下一步是修改ImageZoomView 来持有一个AspectQuotient 对象(对比第一部分仅仅是一个浮点数)。让其他人可以访问它,当然我们还需要更新当前修改横纵系数的代码,但我会把像这样的代码留到课外,因为它简单并且只是重构现有的功能。请在下面的项目链接中查看代码作为参考。
原文:
Enforcing limits and following fingers
Alright, so now we’re able to access the aspect quotient that we need for implementing limits and follow finger through a neat and tidy Observable. The next step is to add the actual code that does these things, but where?
译文:
实施限制并且跟随手指
好吧,现在我们能够访问我们需要的横纵系数,通过一个灵巧和整齐的Observable实现限制和跟随手指。下一步是添加实际代码来做这件事,但是在哪里呢?
原文:
Currently the responsibility for modifying the ZoomState lies with the OnTouchListener, while we could add the functionality here I am reluctant too as I want the OnTouchListener implementation to be responsible for the touch paradigm used and not the specifics on how the ZoomState is controlled. For example, I want to easily be able to implement an OnTouchListener for multi-touch, and when I do I don’t want to update or even duplicate the functionality that applies limits, or as we’ll see in later parts, animates a state.
译文:
现在是OnTouchListener负责修改ZoomState,同样我们也可以在这儿添加功能,我非常不情愿,由于我想让OnTouchListener 实现作为负责触摸范例使用,而不是ZoomState 如何控制的细节,例如,我想能容易地为多点触摸实现一个OnTouchListener ,并且当我不想修改或者复制使用缩放的功能,或者和我们后面部分看到的一样,动画绘制一个状态。
原文:
Another idea would be to add the functionality to the ZoomState, but I want the ZoomState to be simple, it’s responsibility is to provide an interface for accessing the state and getting callbacks when it changes.
译文:
另一个办法是给ZoomState添加功能,但我想让ZoomState保持简单,它的责任是提供一个接口来访问状态并在它发生变化时得到回调。
原文:
So, the way to implement the new functionality is through a new class that sits in between the OnTouchListener implementation and the ZoomState. Let’s call this new class BasicZoomControl, as we’ll implement a first and quite basic component for controlling the ZoomState. And let’s start with creating the skeleton of this class, we know we need access to the aspect quotient and we want a callback when this changes where we enforce limits, we also know we will be controlling a zoom state so lets add that and a get method for access.
译文:
所以,实现新功能的办法是通过一个新的类,位于OnTouchListener 实现和ZoomState之间,让我们叫这个新类BasicZoomControl,由于我们将要实现第一个非常基本的组件控制ZoomState,让我们从创建这个类的骨架开始,我们知道我们需要使用横纵比例(aspect quotient),并且在实施限制和横纵比例发生变化的时候我们想要一个回调,我们还知道我们将会控制一个缩放状态,所以让我们添加这个缩放状态和一个get方法来访问。
public class BasicZoomControl implements Observer {
private AspectQuotient mAspectQuotient;
private final ZoomState mState = new ZoomState();
public ZoomState getZoomState() {
return mState;
}
public void setAspectQuotient(AspectQuotient aspectQuotient) {
if (mAspectQuotient != null) {
mAspectQuotient.deleteObserver(this);
}
mAspectQuotient = aspectQuotient;
mAspectQuotient.addObserver(this);
}
public void update(Observable observable, Object data) {
limitZoom();
limitPan();
}
}
原文:
Secondly we want methods for applying zoom and pan and apply the logics needed to keep within limits, have pan follow finger and also we’ll be making the content under the coordinate of the touch down event invariant during zooming, meaning we can zoom into specific parts of the content and not just the current center.
译文:
第二步,我们想让应用在缩放和平放上的方法以及应用逻辑能够保持在范围之内,让平放跟随手指,我们还要在缩放过程中,使得内容在按下事件不变量的坐标中,意味着我们可以放大内容的特定部分,不仅仅是当前中心的部分。
public void zoom(float f, float x, float y) {
final float aspectQuotient = mAspectQuotient.get();
final float prevZoomX = mState.getZoomX(aspectQuotient);
final float prevZoomY = mState.getZoomY(aspectQuotient);
mState.setZoom(mState.getZoom() * f);
limitZoom();
final float newZoomX = mState.getZoomX(aspectQuotient);
final float newZoomY = mState.getZoomY(aspectQuotient);
mState.setPanX(mState.getPanX() + (x - .5f) * (1f / prevZoomX - 1f / newZoomX));
mState.setPanY(mState.getPanY() + (y - .5f) * (1f / prevZoomY - 1f / newZoomY));
limitPan();
mState.notifyObservers();
}
public void pan(float dx, float dy) {
final float aspectQuotient = mAspectQuotient.get();
mState.setPanX(mState.getPanX() + dx / mState.getZoomX(aspectQuotient));
mState.setPanY(mState.getPanY() + dy / mState.getZoomY(aspectQuotient));
limitPan();
mState.notifyObservers();
}
原文:
The zoom method takes a zoom factor and the position of the touch down event as parameters and then updates the state of both the zoom level and the pan position. By storing the previous zoom levels it is possible to calculate new pan parameters that satisfies keeping the down coordinate invariant when zooming. The pan method is similar to how panning was done in part 1 but now the dx and dy values are divided by the level of zoom in the respective coordinate, doing this makes pan follow the finger as we now take the zoom level in consideration. Finally we need to implement the methods that limit the zoom and pan values:
译文:
zoom 方法接受一个缩放因子和按下事件的位置作为参数,然后更新缩放等级和平放位置的状态。通过保存前一个缩放等级,能够计算新的平放参数,并且在缩放时满足保持按下坐标不变量。平放方法和第一部分实现的比较相似,但是现在dx和dy值在各自的纬度上除以了缩放等级(level of zoom),这样做使得平放跟随手指,因为我们现在考虑到了缩放等级。最后我们需要实现限制缩放和平放值的方法:
private static final float MIN_ZOOM = 1;
private static final float MAX_ZOOM = 16;
private void limitZoom() {
if (mState.getZoom() < MIN_ZOOM) {
mState.setZoom(MIN_ZOOM);
} else if (mState.getZoom() > MAX_ZOOM) {
mState.setZoom(MAX_ZOOM);
}
}
private float getMaxPanDelta(float zoom) {
return Math.max(0f, .5f * ((zoom - 1) / zoom));
}
private void limitPan() {
final float aspectQuotient = mAspectQuotient.get();
final float zoomX = mState.getZoomX(aspectQuotient);
final float zoomY = mState.getZoomY(aspectQuotient);
final float panMinX = .5f - getMaxPanDelta(zoomX);
final float panMaxX = .5f + getMaxPanDelta(zoomX);
final float panMinY = .5f - getMaxPanDelta(zoomY);
final float panMaxY = .5f + getMaxPanDelta(zoomY);
if (mState.getPanX() < panMinX) {
mState.setPanX(panMinX);
}
if (mState.getPanX() > panMaxX) {
mState.setPanX(panMaxX);
}
if (mState.getPanY() < panMinY) {
mState.setPanY(panMinY);
}
if (mState.getPanY() > panMaxY) {
mState.setPanY(panMaxY);
}
}
原文:
Limiting the zoom is quite straightforward, we use 1 (within screen bounds) and 16 (quite a lot of zoom, more than enough for most images on most screens) and simply clamp the value. For the pan we do the same but the problem with pan is that the limits change based on the level of zoom we’re currently at. For example, at a zoom level of 1 we don’t want the user to be able to pan at all (as the content fits the view perfectly).
译文:
限制缩放非常直观,我们用1(在屏幕范围之内)和16(放大非常多,比满足绝大多数屏幕上的绝大多数图片还多)把数值简单地确定下来。对于平放,我们同样处理,但问题是界限基于我们当前的缩放等级而变化,例如,在缩放等级是1我们不想让用户平放(因为内容完美适应控件)。
原文:
Stringing it all together
Alright, we’re almost done with part 2 of the zoom tutorial. What’s left is adapting the Activity and the OnTouchListener implementations to the new solutions. This means changing so that the OnTouchListener implementation doesn’t manipulate the ZoomState directly but instead calls methods in BasicZoomControl, and setting it all up correctly in the Activity implementation. Please check out the project link below if you want to know the specifics of this code, or even better if you want to start playing around with the code on your own!
译文:
把它串联起来
好吧,我们几乎完成了缩放课程的第二部分,剩下的是适配Activity和OnTouchListener的实现的新方案。这意味着修改成这样,OnTouchListener的实现不直接操作ZoomState ,而是用调用BasicZoomControl中的方法来替代,并且在Activity 的实现中把它正确地建立起来。如果你想了解具体的代码,请签出下面项目连接,或者你最好独立地把玩一下代码。
原文:
Now we have a zoom that works good, we’ve fixed the blemishes from part 1 but we’ve got some work left to make it really useful. First thing that comes in my mind is the input method, changing mode in the options menu is a poor choice. Because of this we’ll look into implementing a new OnTouchListener and a new input paradigm in the next tutorial.
译文:
现在我们的缩放工作得很好,我们已经修复了第一部分的瑕疵,但我们还是剩下一些工作使它真正实用。我意识中的第一件事是输入方法,在选项菜单(options menu )中改变模式是一个不好的选择,因为这个,我们要在下一节课寻找一个新的OnTouchListener 实现和一个新的输入范例。