前言:
- 本文翻譯于 sonymobile 的一系列教程,指導(dǎo) Android 開發(fā)人員如何用一個(gè)手指控制圖片的縮放,接第一篇。這一篇在上一篇的基礎(chǔ)上,對(duì)縮放和平放增加了限制,這些限制主要是依據(jù)內(nèi)容的寬高比,控件的寬高比,縮放值,平放值,換算得來的,理解計(jì)算的過程需要讀代碼,但可以如第一篇所說:不關(guān)注計(jì)算的過程,只關(guān)注它的結(jié)果,粗略理解作者的意圖。
正文:
原文:
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單手指縮放第二部分
歡迎來到這個(gè)Android 課程的第二部分,如何制作你自己的縮放控制,像Sony Ericsson X10 Mini 相機(jī)和相冊(cè)應(yīng)用中使用的一樣。點(diǎn)擊這兒閱讀課程的第一部分。
原文:
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.
譯文:
不要忘了去安卓市場(chǎng)下載Sony Ericsson Tutorials,這個(gè)app收集了這個(gè)課程和其他Sony Ericsson課程的所有示例應(yīng)用。
原文:
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.
譯文:
這一部分課程我們將在第一部分啟動(dòng)的縮放應(yīng)用上改造。你或許還記得,在第一部分我們以一個(gè)沒有任何限制的縮放應(yīng)用收工的。我們能縮放或者平放成空的并回退。在這節(jié)課程我們將會(huì)引入限制,并且確保平放和期望的一樣經(jīng)常跟隨手指,在第一部分我們可以看見平放跟隨手指因當(dāng)前的縮放級(jí)別不同而不同。下面是第二步的源代碼鏈接,并且視頻顯示了你將會(huì)在單手指縮放系列課程中學(xué)到的知識(shí)。(譯注:本文結(jié)尾有源代碼下載鏈接,視頻沒有了。)
原文:
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.
譯文:
橫縱系數(shù)(aspect quotient)
還記得來自第一部分的這張圖嗎?(譯注:沒有圖了,解說可以參考第一部分的截圖)
圖片說明了縮放狀態(tài)(zoom state)是如何工作的,灰色虛線區(qū)域代表view中所要顯示的,圖案區(qū)域代表內(nèi)容。在左邊,Zoom是1,pan-x和pan-y是0.5,在這種狀態(tài)下圖片完美適應(yīng)屏幕。在中間,Zoom是2,pan-x和pan-y是0.5,現(xiàn)在屏幕上顯示較少但是被放大的內(nèi)容。在右邊,Zoom是3,pan-x是0.7并且pan-y是0.833,我們現(xiàn)在看見更少的圖像,只有放大了的右上角。(譯注:具體解釋看本課程第一節(jié)的圖片示例。)
原文:
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),這意味著當(dāng)縮放值(zoom value)是3的時(shí)候,x軸和y軸的最大平放值(pan value)分別是0.7和0.833,實(shí)際限制依賴具體維度上的縮放級(jí)別,具體緯度上的縮放級(jí)別依賴橫縱系數(shù)(aspect quotient )(content橫縱比例與view橫縱比例的商,更多解釋參見第一部分)。長(zhǎng)話短說,為了給平放采取限制,同樣使得平放正確地跟隨手指,我們需要橫縱系數(shù)(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.
譯文:
不過目前,從第一部分,我們的橫縱系數(shù)只能在ImageZoomView可用,所以首先讓我們修改這個(gè)并且創(chuàng)建一個(gè)對(duì)象來保存橫縱系數(shù),并且擴(kuò)展Observable 作為一個(gè)方便通知想要觀察它的人的手段(譯注:這里直譯有些別扭,但我還是這樣翻譯了,就是為了方便通知它的觀察者,但前提是它可被觀察,所以說是想觀察它)。
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.
譯文:
非常直接,計(jì)算和第一部分是一樣的,就像可觀察的ZoomState 一樣,我們讓客戶端決定修改橫縱系數(shù)來通知觀察者。下一步是修改ImageZoomView 來持有一個(gè)AspectQuotient 對(duì)象(對(duì)比第一部分僅僅是一個(gè)浮點(diǎn)數(shù))。讓其他人可以訪問它,當(dāng)然我們還需要更新當(dāng)前修改橫縱系數(shù)的代碼,但我會(huì)把像這樣的代碼留到課外,因?yàn)樗?jiǎn)單并且只是重構(gòu)現(xiàn)有的功能。請(qǐng)?jiān)谙旅娴捻?xiàng)目鏈接中查看代碼作為參考。
原文:
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?
譯文:
實(shí)施限制并且跟隨手指
好吧,現(xiàn)在我們能夠訪問我們需要的橫縱系數(shù),通過一個(gè)靈巧和整齊的Observable實(shí)現(xiàn)限制和跟隨手指。下一步是添加實(shí)際代碼來做這件事,但是在哪里呢?
原文:
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.
譯文:
現(xiàn)在是OnTouchListener負(fù)責(zé)修改ZoomState,同樣我們也可以在這兒添加功能,我非常不情愿,由于我想讓OnTouchListener 實(shí)現(xiàn)作為負(fù)責(zé)觸摸范例使用,而不是ZoomState 如何控制的細(xì)節(jié),例如,我想能容易地為多點(diǎn)觸摸實(shí)現(xiàn)一個(gè)OnTouchListener ,并且當(dāng)我不想修改或者復(fù)制使用縮放的功能,或者和我們后面部分看到的一樣,動(dòng)畫繪制一個(gè)狀態(tài)。
原文:
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.
譯文:
另一個(gè)辦法是給ZoomState添加功能,但我想讓ZoomState保持簡(jiǎn)單,它的責(zé)任是提供一個(gè)接口來訪問狀態(tài)并在它發(fā)生變化時(shí)得到回調(diào)。
原文:
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.
譯文:
所以,實(shí)現(xiàn)新功能的辦法是通過一個(gè)新的類,位于OnTouchListener 實(shí)現(xiàn)和ZoomState之間,讓我們叫這個(gè)新類BasicZoomControl,由于我們將要實(shí)現(xiàn)第一個(gè)非?;镜慕M件控制ZoomState,讓我們從創(chuàng)建這個(gè)類的骨架開始,我們知道我們需要使用橫縱比例(aspect quotient),并且在實(shí)施限制和橫縱比例發(fā)生變化的時(shí)候我們想要一個(gè)回調(diào),我們還知道我們將會(huì)控制一個(gè)縮放狀態(tài),所以讓我們添加這個(gè)縮放狀態(tài)和一個(gè)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.
譯文:
第二步,我們想讓應(yīng)用在縮放和平放上的方法以及應(yīng)用邏輯能夠保持在范圍之內(nèi),讓平放跟隨手指,我們還要在縮放過程中,使得內(nèi)容在按下事件不變量的坐標(biāo)中,意味著我們可以放大內(nèi)容的特定部分,不僅僅是當(dāng)前中心的部分。
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 方法接受一個(gè)縮放因子和按下事件的位置作為參數(shù),然后更新縮放等級(jí)和平放位置的狀態(tài)。通過保存前一個(gè)縮放等級(jí),能夠計(jì)算新的平放參數(shù),并且在縮放時(shí)滿足保持按下坐標(biāo)不變量。平放方法和第一部分實(shí)現(xiàn)的比較相似,但是現(xiàn)在dx和dy值在各自的緯度上除以了縮放等級(jí)(level of zoom),這樣做使得平放跟隨手指,因?yàn)槲覀儸F(xiàn)在考慮到了縮放等級(jí)。最后我們需要實(shí)現(xiàn)限制縮放和平放值的方法:
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(在屏幕范圍之內(nèi))和16(放大非常多,比滿足絕大多數(shù)屏幕上的絕大多數(shù)圖片還多)把數(shù)值簡(jiǎn)單地確定下來。對(duì)于平放,我們同樣處理,但問題是界限基于我們當(dāng)前的縮放等級(jí)而變化,例如,在縮放等級(jí)是1我們不想讓用戶平放(因?yàn)閮?nèi)容完美適應(yīng)控件)。
原文:
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!
譯文:
把它串聯(lián)起來
好吧,我們幾乎完成了縮放課程的第二部分,剩下的是適配Activity和OnTouchListener的實(shí)現(xiàn)的新方案。這意味著修改成這樣,OnTouchListener的實(shí)現(xiàn)不直接操作ZoomState ,而是用調(diào)用BasicZoomControl中的方法來替代,并且在Activity 的實(shí)現(xiàn)中把它正確地建立起來。如果你想了解具體的代碼,請(qǐng)簽出下面項(xiàng)目連接,或者你最好獨(dú)立地把玩一下代碼。
原文:
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.
譯文:
現(xiàn)在我們的縮放工作得很好,我們已經(jīng)修復(fù)了第一部分的瑕疵,但我們還是剩下一些工作使它真正實(shí)用。我意識(shí)中的第一件事是輸入方法,在選項(xiàng)菜單(options menu )中改變模式是一個(gè)不好的選擇,因?yàn)檫@個(gè),我們要在下一節(jié)課尋找一個(gè)新的OnTouchListener 實(shí)現(xiàn)和一個(gè)新的輸入范例。