前言:
- 本文翻譯于 sonymobile 的一系列教程,指導 Android 開發人員如何制作3D風格的ListView,共三部分。這是第二部分。接上篇, 在上一節的基礎上,這一節主要實現的是一些視覺和感覺上的特效,代碼有好多數學相關的運算和圖像處理的細節,比較難懂,需要有相應的專業知識。不過通過閱讀,可以讓我們認識一些最基本的圖像處理知識,或許可以部分地采用到我們的項目中。還是采用一段一段的翻譯方式,文中多次提到效果圖片,我現在已經找不到圖片了,不過可以在文章結尾找到代碼下載鏈接,下載代碼運行就能看到完整的效果。
正文:
原文:
Welcome to the second tutorial out of three in the series of how to make your own 3D list view implementation for an Android application. In this tutorial we continue to develop the quite basic list created in part one of the tutorial into a list with 3D look and feel. At the end of this article we will have something that looks a bit more interesting than the standard list.
譯文:
歡迎來到本系列三節課程中的第二節,這系列課程是關于如何實現Android應用的3D ListView的。這節可我們繼續開發上節課創建的非常基礎的ListView,使它具有3D的樣子和感覺。在這篇文章的結尾,我們會擁有比標準ListV看起來更加有趣的東西。
原文
To see what list will look like, download the ’Sony Ericsson Tutorials‘ application from Android Market. In this app you will also see what the list will look like after the third part of this tutorial. Below is a link to the source code of part 2, prepared for you to set up your own project in e.g. Eclipse.
譯文:
想要看列表是什么樣子,在Android市場下載 “Sony Ericsson Tutorials” APP(譯注:沒有找到)。在這個應用中你還可以看見經過第三節課以后ListView的樣子。下面是第二節課程的代碼,是為你在Eclipse中創建工程準備的(譯注:在本文結尾查看代碼下載鏈接)。
原文:
Adding some padding
The first thing we are going to do is to add some padding. And by this I mean padding between the items. The list itself can have padding but we are currently ignoring that.
譯文:
添加一些間距
我們要做的第一件事是添加一些間距,這里的間距是指條目之間的間距,ListView可以自己有內邊距但我們當前忽略了它(譯注:忽略ListView的內邊距)。
原文:
Left and right padding can easily be handled by decreasing the width of the item when we measure it and then center it during the layouting. The measure part looked like this:
譯文:
在測量(measure) item的時候,通過減少item的寬度,并在布局(layout)的時候把它居中,可以輕松處理左邊距和右邊距。測量部分的代碼是這樣的:
int itemWidth = getWidth();
child.measure(MeasureSpec.EXACTLY | itemWidth, MeasureSpec.UNSPECIFIED);
原文:
If we replace that with
譯文:
如果我們把它替換成
int itemWidth = (int)(getWidth() * ITEM_WIDTH);
child.measure(MeasureSpec.EXACTLY | itemWidth, MeasureSpec.UNSPECIFIED);
原文:
where ITEM_WIDTH is defined as this
譯文:
ITEM_WIDTH 是這樣定義的
/** Width of the items compared to the width of the list */
private static final float ITEM_WIDTH = 0.85f;
原文:
the list items will be just 85% of the width of the list itself. This, together with our code in onLayout(), gives us some nice padding on the left and right, but not between list items. To get padding between list items we need to make some changes here and there but mainly in the methods that handle layout.
譯文:
ListView的Item只會有它寬度的85%,這些代碼和我們在onLayout()中的代碼一起,給我們一些非常好的左邊距和右邊距,但不會再item之間有間距。為了在item之間產生邊距,我需要在其他地方做一些改動,但主要是在處理layout的方法中。
原文:
The most straight forward way to get padding between list items is simply to layout them a fixed number pixels apart. This works fine in more or less all circumstances, but actually not for what I have in mind. What I would like to do instead is to let the padding be dependent on the height of the item. Let’s define the padding as follows:
譯文:
給列表條目之間設置間距的最直接方式是,把它們按一個固定像素值分離放置,這個在很多場景下工作正常。但實際上不是我想要的的。我想要的替代做法是,讓間距依賴item的高度,讓我們如下定義間距:
/** Space occupied by the item relative to the height of the item */
private static final float ITEM_VERTICAL_SPACE = 1.45f;
原文:
This way bigger items will get more padding than smaller items. Since each item will take more vertical space, we need to modify all the methods that use [getTop()], [getBottom()] or [getMessuredHight()] on the child views and rely on those values for layouting. For example, the method fillListDown() which we implemented last time relies on the fact that a view takes as much space as getMessuredHight() returns. With this padding definition a view take up that space times ITEM_VERTICAL_SPACE. First we implement some nice-to-have utility methods.
譯文:
這種方式下,大的條目將會比小的條目得到更大的間距。由于每一個條目將會占用更多的垂直空間,我們需要修改所有在子view上使用getTop(),getBottom(),getMessuredHight()的方法,并且依賴這些值進行布局。例如,我們上一次實現的方法fillListDown()依賴這樣的事實:一個view占據和 getMessuredHight()返回值一樣大的空間。用這種邊距定義,一個視圖占據ITEM_VERTICAL_SPACE倍的空間(譯注:一個視圖占據的高度是它自身高度的1.45倍)。首先我們實現一些有則更好的實用方法。
private int getChildMargin(View child) {
return (int)(child.getMeasuredHeight() * (ITEM_VERTICAL_SPACE - 1) / 2);
}
private int getChildTop(View child) {
return child.getTop() - getChildMargin(child);
}
private int getChildBottom(View child) {
return child.getBottom() + getChildMargin(child);
}
private int getChildHeight(View child) {
return child.getMeasuredHeight() + 2 * getChildMargin(child);
}
原文:
You might wonder why getChildHeight() is implemented the way it is. Why not just return child.getMeasuredHeight() * ITEM_VERTICAL_SPACE. The thing is that we sometimes need to calculate the padding on just one side and sometimes on both, and if we don’t use the same way of calculating it we might end up in a situation where getChildHeight() does not return the same thing as getChildBottom() – getChildTop() due to rounding errors.
譯文:
你或許好奇getChildHeight()為什么這樣實現,為什么不直接返回child.getMeasuredHeight() * ITEM_VERTICAL_SPACE。這是因為有時候我們只需要計算一端的間距,有時候要計算兩端的,如果我們不用相同的方式計算,我們最終會因為化整誤差,陷入getChildHeight()和getChildBottom() – getChildTop()返回不同值的處境。
原文:
Now we replace all occurrences of child.getTop() with getChildTop(child) and the same for getBottom() and getMeasuredHeight(). An exception is the part of the code that actually calls layout on the children, in this case the positionItems() method, which will look like this.
譯文:
現在我們用getChildTop(child)替換所有出現的child.getTop(),getBottom()和getMeasuredHeight()也是一樣的。實際在child上調用layout的部分代碼是一個例外,在positionItems方法這種情形中,看起來是這樣的:
int width = child.getMeasuredWidth();
int height = child.getMeasuredHeight();
int left = (getWidth() - width) / 2;
int margin = getChildMargin(child);
int childTop = top + margin;
child.layout(left, childTop, left + width, childTop + height);
top += height + 2 * margin;
原文:
Now our items are nicely padded and not side by side.
譯文:
現在我們的item被很好地填充,而不是一個挨一個的。
原文:
Defining things relative to something else, mostly the width and height of the view, is a good practice if you want a view that easily scales to different sizes. There are a lot of different android devices out there and it’s certainly a good thing to be able to support as many as possible. I’ve tested the view we do here on both the QVGA display of the X10 mini and the WVGA display of the X10 and, since almost all things are defined relative to the width and height of the view, it scales quite nicely.
譯文:
定義一些其他相關的東西,通常,如果你想簡單地把一個View縮放到不同的Size,改變View的寬和高(width and height)是一個好的做法。市場上有很多不同的android設備,盡可能地支持更多設備肯定是一件好事情。我已經在我們這兒做的QVGA分辨率X10 mini和WVGA分辨率X10兩款手機上測試了View,由于幾乎所有的東西都相對于view的width和hight定義,它縮放得非常漂亮。
原文:
Changing appearance
When drawing graphics on a [canvas], the graphics are always affected by the transformation matrix that the canvas has. This [matrix] can scale, translate, rotate or otherwise transform the content. The canvas class has some handy utility methods to do these normal transformations, for example [scale()], [rotate()] and [translate()].
譯文:
改變外表
當在canvas上繪制圖形時,圖形經常受到canvas的轉換矩陣的影響。這個矩陣可以縮放,平移,旋轉或者其他的內容轉換。canvas 類有一些實用的工具方法來做這些正常的轉換,例如scale(),rotate()和translate()。
原文:
However, before we get into using them, we need to override a draw method to get hold of the canvas. On a normal view you would override [onDraw()]and draw the content there. However, the list itself is empty and the content of the list is completely made up of what its child views draw. To override the drawing of the child views we could override [dispatchDraw()] or [drawChild()]. In this example we’re going to use drawChild().
譯文:
然而,當我們使用它之前,我們需要重寫一個draw 方法來持有canvas。在一個正常的View上你應該重寫onDraw()并在這個方法里繪制內容。然而ListView它本身是空的,它的內容完全是由它的子View繪制組成的。為了重寫子View的繪制,我們可以重寫dispatchDraw()或者drawChild(),這個例子中我們將使用drawChild()。
原文:
Let’s start by using the normal canvas operations to change the list. The code below will scale items further from the center down and rotate them.
譯文:
讓我們開始使用正常的canvas操作來改變列表,下面的代碼將會從中間往下縮放和旋轉條目。
@Override
protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
// get top left coordinates
int left = child.getLeft();
int top = child.getTop();
// get offset to center
int centerX = child.getWidth() / 2;
int centerY = child.getHeight() / 2;
// get absolute center of child
float pivotX = left + centerX;
float pivotY = top + centerY;
// calculate distance from center
float centerScreen = getHeight() / 2;
float distFromCenter = (pivotY - centerScreen) / centerScreen;
// calculate scale and rotation
float scale = (float)(1 - SCALE_DOWN_FACTOR * (1 - Math.cos(distFromCenter)));
float rotation = 30 * distFromCenter;
canvas.save();
canvas.rotate(rotation, pivotX, pivotY);
canvas.scale(scale, scale, pivotX, pivotY);
super.drawChild(canvas, child, drawingTime);
canvas.restore();
return false;
}
原文:
The interesting part begins with the [save] call to the canvas. This lets us [restore]the canvas later to this state, something that we have to do in order to not mess things up. Then we rotate the canvas around the center of the child view and then scale it down, all depending on the distance from the middle. Then we call the super method and restore the canvas. You can see the effect of this in the screen shot below (taken from the X10 mini). Note that while the views are scaled and rotated, they suffer from a horrible [aliasing] problem.
譯文:
開頭有趣的部分是調用了canvas的save方法,它能使我們在后面使用restore 把canvas恢復到save前的狀態,為了不使我們所做的事情搞糟了canvas。然后我們圍繞子View的中心,根據子View到屏幕中心的距離,旋轉畫布并把它(譯注:縮小canvas)縮小,然后我們調用父類的drawChild方法并將畫布還原。你可以在下面的截屏中看見這個效果(從X10 mini截的)(譯注,沒有圖了,可以運行代碼看效果)。注意當view被縮放和旋轉以后,它們遭受了巨大的走樣問題(譯注:旋轉和縮放以后,有明顯的鋸齒現象,現象為邊緣不平滑)。
原文:
Normally you use a [Paint] object to draw with and on the Paint object you can [enable anti-aliasing] and [filtering]. Filtering and anti-aliasing drags down performance but is, in most cases, a must have nonetheless. To be able to fix the aliasing problem we can ask the view for its [drawing cache] (making sure that we have [enabled] this before) and then drawing that bitmap to the canvas with a Paint object that has filtering and anti-aliasing enabled. Replacing the super call with
譯文:
通常你用一個Paint對象繪制,在Paint對象上你可以啟用anti-aliasing和filtering,這兩個屬性會拉低性能,但是,在大多數情況下必須如此。為了可以解決鋸齒問題我們可以尋找View自己的drawing cache(確保我們在前面已經啟用這個了),然后使用一個有filtering(譯注:濾波器)并且啟用anti-aliasing(譯注:抗鋸齒)的Paint對象把那個bitmap繪制到canvas上。用下面的代碼代替super調用
Bitmap bitmap = child.getDrawingCache();
canvas.drawBitmap(bitmap, left, top, mPaint);
原文:
gives a much better result
譯文:
得到一個更好的效果。
原文:
Remember that we are now changing where the views are drawn but we are not changing the layout. The actual positions of the views are still the same. In other words, the image of the view on the display will not correspond to the place where it actually is. A consequence of this is that when we click on a view on the display it might not be where that view really is, it might be another view or no view at all.
譯文:
記住我們現在改變的是View繪制的地方而不是布局的地方,View實際的位置還是不變的(譯注:修改子view的繪制不會改變子view的實際位置和大小,只是看起來縮放和旋轉了),換句話說,View顯示的圖像和它實際的位置將會不一致。導致一個結果是當我們點擊顯示在屏幕上的view時,它可能不是真正在那兒,它可能是另外一個View或者根本沒有View。
原文:
This of course depends on how much you have transformed or moved the view. If it becomes a problem you need to modify the code that finds what view the user clicked on, you can no longer rely on the [hit rect]. You also might need to override dispatchTouchEvent() and transform the coordinates of the motion event before you pass it on to the child view. For this tutorial though, we are going to skip that part and design our list to minimize problems like this.
譯文:
這當然取決于view已經被你轉換或者移動了多少,如果它成為一個需要你修改代碼來尋找用戶點擊在哪一個view上的問題,你就不能再依賴命中矩形 [hit rect]。你還可能需要重寫dispatchTouchEvent()并且在傳遞到子view之前,轉換手勢事件的坐標。然而這節課,我們將會跳過這部分并且設計我們的列表使得這樣的問題最小化。
原文:
Another noticeable thing about the list right now is that it looks quite lame. Rotating the views as we do here is not very interesting. To make it interesting, let’s make it a bit more 3D.
譯文:
另一個值得注意的事情是,列表現在看起來很蹩腳的。我們這兒做的旋轉view不是很有趣。為了讓它有意思,我做一點點3D。
原文:
Getting to know the Camera
The transformation matrix on the canvas is capable of 3D transformations, but we can’t modify it using the regular utility methods on the canvas. We need to make matrix ourselves and draw our views using that matrix.
譯文:
開始認識Camera(譯注:這個不是照相的相機)
Canvas上的矩陣變換是能勝任3D變換的。但我們不能用canvas上的普通方法,我們需要自己制作矩陣并且用那個矩陣畫我們的view。
原文:
Android has a [Camera] class that’s handy to use when creating 3D transformation matrices. With this class you can rotate and translate in X, Y and Z axes. A downside with this class is that it is completely undocumented. There is some [sample code] in the SDK that uses this class to do 3D transformations and I recommend looking at the sample code.
譯文:
Android有一個Camera類,用它來創建3D變換矩陣是很便利的。用這個類你可以沿著X,Y,Z軸旋轉和平移。這個類的一個缺點是完全沒有文檔。SDK里面有一些示例代碼用這個類做3D變換,我推薦查看這些示例代碼。
原文:
The below code rotates the views around the Y-axis using the camera class.
譯文:
下面的代碼使用camera類圍繞Y軸旋轉view。
if (mCamera == null) {
mCamera = new Camera();
}
mCamera.save();
mCamera.rotateY(rotation);
if (mMatrix == null) {
mMatrix = new Matrix();
}
mCamera.getMatrix(mMatrix);
mCamera.restore();
mMatrix.preTranslate(-centerX, -centerY);
mMatrix.postScale(scale, scale);
mMatrix.postTranslate(pivotX, pivotY);
Bitmap bitmap = child.getDrawingCache();
canvas.drawBitmap(bitmap, mMatrix, mPaint);
原文:
Let’s go through the code. First, we create a camera if we don’t have one. Then we save the camera state so that we can restore it to the default state later. Then we rotate the camera around the Y-axis. Now we need to get the matrix from the camera and then we are finished using the camera so we restore it. The matrix we have now has the rotation we did with the camera, but it’s a “raw” rotation matrix in the sense that it includes no translation so it will rotate everything around (0, 0). To rotate around the center of the child view we pre-translate the matrix so that (0, 0) will be at the center of the screen. Then we add some scaling and then we translate the matrix so it’s drawn at the correct position. Finally, we draw the bitmap using the matrix. The result can be seen below, though a static image does not convey the way it looks when scrolling the list.
譯文:
讓我們過一遍代碼。首先,如果還沒有camera對象我們就創建一個。然后我們保存camera的狀態,這樣我們能在后面把它還原到默認狀態。然后我們圍繞Y軸旋轉camera。現在我們需要從camera中得到矩陣,這時我們就完成了對camera的使用,所以我們還原它。我們現在拿到的矩陣已經具有我們使用camera所做的旋轉。但它在某種意義上是一個原始的旋轉矩陣,因為它不包含任何轉換,因此它會在圍繞(0, 0)旋轉所有東西。為了圍繞中心旋轉子view,我們pre-translate矩陣,(0, 0)位置會在屏幕的中心。然后我們添加一些縮放,然后我們變換矩陣,他就能被繪制到正確的位置上。最后我們用這個矩陣繪制bitmap,可以看到下面的結果(譯注:還是沒圖,下載代碼運行看效果),盡管一個靜態圖片不能傳達列表滾動是的樣子。
(譯注:這里主要是matrix和camera的變換,需要相應的專業知識,我并不了解這兩個類的數學細節)
原文:
There’s quite a lot of potential transformations you can play with here. I really suggest that you try out a few transformations to see the effect. Also, this code has some issues with the perspective. It’s quite easily corrected by translating the items using the camera instead of the matrix. I’ll touch upon this point later on. However, I will leave the task of correcting the translation as an exercise for the interested readers.
譯文:
在這里你可以在這里玩很多潛在的變換。我非常建議你試幾個變換看看效果。還有,這個代碼有一些視角問題,它很容易修復,通過使用camera變換代替matrix變換。我將在稍后談及這一點。然而,我將會修正的任務作為練習留給感興趣的讀者。
原文:
Blockifying the list
The X10 mini has quite amazing per-pixel performance for graphical stuff, so to show a bit of what it can do and at the same time show a bit of what you can do with canvas transformations and the camera class, we are going to do some more 3D effects. If you’ve seen the video you’ll know how it looks like. Each item will be a block (and not just a plane as before) that will rotate around its X-axis and look like it is rolling on the ground when the list stars to scroll. Each block will be as wide as the item normally is and the depth will be the same as the height. We’ll use the same bitmap for all the sides.
譯文:
塊狀化列表
X10 mini 手機有令人驚訝的逐像素繪制性能,因此可以在顯示一些它可以做的同時顯示一些你可以用canvas 變換和camera做的。我們繼續做更加3D的效果。如果你已經看過這個視頻,你就知道它是什么樣子。每一個item會是一個塊狀(而不僅僅是之前那樣的平面),當列表滑動的時候,它將會沿著它的X軸翻轉而看起來像是在滾動。每一個item將會和它正常時一樣寬,它的深度和它的高度相等。我們對所有的面使用相同的bitmap。
原文:
So what do we need to do to achieve this effect? In order to draw the blocks we need to draw the bitmap two times (since we will almost always see two sides of the block). We also need to have some kind of rotation variable to keep track of the main rotation. Since the blocks should rotate when the user scrolls the list and the blocks should have the same rotation (so that they all face up at the same time) we can no longer use the distance from the center to calculate the rotation for the block as we did before. A convenient way to calculate the rotation is to use the list top position. Let’s add the following line to scrollList().
譯文:
我們需要怎么做才能達到這種效果呢?為了畫塊狀我們需要畫兩次bitmap(因為我們大多數時候看見塊體的兩個面)。我們還需要一些類型的旋轉變量來追蹤主要的旋轉。由于當用戶滑動列表時塊體應該旋轉,所有的塊體應該有相同的旋轉(這樣他們在同一時間正面朝上)。我們不能再像之前一樣使用到中心的距離來計算塊的旋轉角度。一個計算旋轉的方便的辦法是用列表的top位置,讓我們在scrollList()添加下面的一行代碼。
mListRotation = -(DEGREES_PER_SCREEN * mListTop) / getHeight();
原文:
Doing like this will make the blocks rotate DEGREES_PER_SCREEN degrees when the user scrolls the list an entire screen no matter the pixel-height of the screen.
譯文:
像這樣做,當用戶滑動一整屏時塊體旋轉DEGREES_PER_SCREEN度,和屏幕高度像素數無關。(譯注:比如列表高度100,當前第一個可見的item頂部即mListTop是20,那么它就旋轉270 * 20 / 100 度)
原文:
That will take care of the rotation, now let’s think about the actual drawing. This is how the drawChild() method looks like right now.
譯文:
那樣會處理好旋轉。現在我們考慮實際繪制。這是drawChild() 方法現在的樣子。
@Override
protected boolean drawChild(final Canvas canvas, final View child, final long drawingTime) {
// get the bitmap
final Bitmap bitmap = child.getDrawingCache();
if (bitmap == null) {
// if the is null for some reason, default to the standard
// drawChild implementation
return super.drawChild(canvas, child, drawingTime);
}
// get top left coordinates
final int top = child.getTop();
final int left = child.getLeft();
// get centerX and centerY
final int childWidth = child.getWidth();
final int childHeight = child.getHeight();
final int centerX = childWidth / 2;
final int centerY = childHeight / 2;
// get scale
final float halfHeight = getHeight() / 2;
final float distFromCenter = (top + centerY - halfHeight) / halfHeight;
final float scale = (float)(1 - SCALE_DOWN_FACTOR * (1 - Math.cos(distFromCenter)));
// get rotation
float childRotation = mListRotation - 20 * distFromCenter;
childRotation %= 90;
if (childRotation < 0) {
childRotation += 90;
}
// draw the item
if (childRotation < 45) {
drawFace(canvas, bitmap, top, left, centerX, centerY, scale, childRotation - 90);
drawFace(canvas, bitmap, top, left, centerX, centerY, scale, childRotation);
} else {
drawFace(canvas, bitmap, top, left, centerX, centerY, scale, childRotation);
drawFace(canvas, bitmap, top, left, centerX, centerY, scale, childRotation - 90);
}
return false;
}
原文:
Most of this is quite similar to the code we did before to rotate and scale the items so I want go into details. Worth noting is that the code that will draw one face of the block is the same, it just depends on the rotation, so it’s extracted to a method. To draw a complete block we then simply draw two faces 90 degrees apart at the same place.
譯文:
這里的大多數和我們之前做的旋轉和縮放item的代碼非常相似,所以我想探究細節(譯注:作者不想探究細節,或許是筆誤)。值得注意的是那些代碼和繪制塊體的一面是相同的。它僅僅依賴旋轉。因此它被提取到一個方法里面。我們簡單通過在同一個地方畫兩個相隔90度的面來畫一個完整的塊體。
原文:
To draw a face we first translate the camera so that the face will be drawn closer to us. Then we rotate it and after that we translate it back so we don’t scale it. Keep in mind that the calls to the camera, just like the rotate, translate and scale methods on Canvas, needs to be written in reversed order, so to speak. In the code below, it is the last line that translates the face towards us, then we rotate it, and finally, with the first line, we translate it back.
譯文:
要畫一面,我們首先移動camera,這樣畫出來的面會離我們近一些。然后我們旋轉它,再把它移回去,這樣我們就不縮放它。記住對camera的調用,就像在canvas上的旋轉,平移,縮放方法,需要逆序寫,就是說,下面的代碼,最后一行把面向著我們移動,然后我們旋轉它,最后用第一行,我們把它向后移動。
mCamera.translate(0, 0, centerY);
mCamera.rotateX(rotation);
mCamera.translate(0, 0, -centerY);
原文:
The rest of drawFace is more or less the same as before. It gets the matrix from the camera, pre and post translates the matrix and then draws the bitmap with the matrix.
譯文:
drawFace剩下的和前面的代碼或多或少一樣,從相機中取得matrix,先后平移matrix,然后用bitmat繪制matrix。
原文:
This code will draw each item as if placed in the origin in 3D space and then we move the items to the correct place on the screen using pre and post translate on the matrix. This moves what we draw in 2D space without changing the perspective. We could apply the translation in X and Y on the camera instead, then the translation would be in 3D space and it would affect the perspective. We’re not doing that here because I want the appearance of a larger field of view than the fixed field of view of the camera. Instead, we fake it by slightly rotating and scaling the items depending on the distance from center of the screen. For this view it works quite well.
譯文:
這份代碼會把每一個item當成是一開始就放置在3D空間中一樣進行繪制,然后我們用matrix的先后平移,把item移動到屏幕的正確位置。這個移動我們在2D空間的繪制,沒有改變角度。我們應該使用在camera上進行X和Y軸的平移來替代。然后平移會在3D空間進行并且它會影響角度。我們這兒不這樣做因為相比camera中區域合適的View,我想要區域大一些的View出現。我們根據item相對屏幕中心的距離輕微地旋轉和縮放item假裝這樣,對這個view,它工作得挺好。
原文:
Anyway, this is how the list looks blockified.
譯文:
總之,這就是塊狀化的列表看起來的樣子(譯注:還是沒圖,運行代碼看效果)。
原文:
Let there be light
For the list to have a realistic 3D feel we need to add lighting. Without it, it will look flat. One way to do that is by modifying the alpha. It has the advantage of being very simple but the disadvantage is that it only works nicely on a black background. Also, it’s impossible to do highlights (specular lighting) in this way. We want our view to work on other backgrounds than black and we would like to have a highlight. Therefore, we will solve the lighting problem in another way.
譯文:
給一些光照
為了使列表有更逼真的3D效果,我們需要添加一些光照,沒有這個它會看起來是平的。一個實現辦法就是修改alpha(譯注:透明度)。這樣做的優勢是非常簡單,但缺點是它只有在黑色背景下才能表現得好。還有,這種方式不可能做高亮(光反射)效果。我們想要我們的View在其他背景下工作并且有高亮效果,因此我們將會用另一種辦法解決光照問題。
原文:
On a Paint object we can set color filters which will affect the color values of what we draw with that Paint object. setColorFilter() takes a ColorFilter and one of the subclasses of ColorFilter is LightingColorFilter which is almost exactly what we need. A LightingColorFilter takes two colors that are used to modify the colors that we are drawing. The first color will be multiplied with the colors we draw, while the second one will be added to the colors we draw. The multiplication will darken the color and adding will make it brighter so we can use this class to model both shadows and highlights. It would have been even better if instead of adding it would have implemented the screen blend mode, but add works OK.
譯文
在Paint對象上我們可以設置顏色過濾器,它會影響我們用Paint對象繪制的色值。setColorFilter()接受一個ColorFilter 并且ColorFilter 的一個子類是LightingColorFilter,這幾乎恰好是我們想要的。一個LightingColorFilter 接收兩個顏色參數,用來修改我們繪制的顏色,第一個顏色將會乘以(multiplied)我們繪制的顏色,這個乘法會是的顏色變暗并且(加法)adding會使得它變亮,因此我們可以用這個類來模擬陰影和高亮。用屏幕混合模式(screen blend mode)代替add效果更好,但是add工作也挺好。(譯注:圖像處理我不了解,照字面意思翻譯的。)
原文:
To actually calculate the light we’ll use a simplified version of Phong lighting. So let’s define some light constants.
譯文:
實際的光線計算我們用一個簡單版本的Phong著色法(Phong lighting),所以讓我們定義幾個光線常亮。
/** Ambient light intensity */
private static final int AMBIENT_LIGHT = 55;
/** Diffuse light intensity */
private static final int DIFFUSE_LIGHT = 200;
/** Specular light intensity */
private static final float SPECULAR_LIGHT = 70;
/** Shininess constant */
private static final float SHININESS = 200;
/** The max intensity of the light */
private static final int MAX_INTENSITY = 0xFF;
原文:
Now we implement a method that will calculate the light and create a LightingColorFilter that we can set to our Paint object.
譯文:
現在我們實現一個方法,它會計算光線并且創建LightingColorFilter 給我們的Paint 對象。
private LightingColorFilter calculateLight(final float rotation) {
final double cosRotation = Math.cos(Math.PI * rotation / 180);
int intensity = AMBIENT_LIGHT + (int)(DIFFUSE_LIGHT * cosRotation);
int highlightIntensity = (int)(SPECULAR_LIGHT * Math.pow(cosRotation, SHININESS));
if (intensity > MAX_INTENSITY) {
intensity = MAX_INTENSITY;
}
if (highlightIntensity > MAX_INTENSITY) {
highlightIntensity = MAX_INTENSITY;
}
final int light = Color.rgb(intensity, intensity, intensity);
final int highlight = Color.rgb(highlightIntensity, highlightIntensity, highlightIntensity);
return new LightingColorFilter(light, highlight);
}
原文:
The only input to this method is the rotation of the face. We use this rotation to compute two intensities. The first, “intensity”, will be our main light level. It’s used to create the color we multiply with so it will darken what we draw. The second intensity, “highlightIntensity”, is the specular light that we use to create the color we add so it will brighten what we draw. After we have calculated the intensities we make sure they are at most 255, and then we create the colors. In this case we have a white light source so our colors will be gray, but you can also use colored light. Finally we create a LightingColorFilter and return it. The result of drawing the list with lighting can be seen below.
譯文:
這個方法唯一的傳入參數是面的旋轉角度,我們用這個旋轉角度計算兩個強度,第一個強度是我們主要的光級,它被用來創建我們相乘的顏色,它會使我們的繪畫變暗。第二個強度是“高亮強度”,是我們用來創建相加顏色的反射光,它會使我們的繪制變亮。我們計算完強度以后,確保它們最大是255, 然后我們創建顏色。在這種情況下我們有一個白色光源因此我們的顏色會變灰,但你也可以用變亮的顏色。最后我們創建一個LightingColorFilter 并返回它。可以在下面看見最終用光照繪制的列表(譯注:還是沒圖,運行代碼看效果)。
原文:
To be continued…
This article talked about changing the appearance of the list. We haven’t added any real features, only changed the way the list is drawn so now it looks nice, but it’s still not very usable. The last part that’s missing is the behavior of the list and that is the subject for the next article where we will look into how to realize flinging and snapping of the list. That will be a large step towards a usable list.
譯文:
待續...
這篇文章討論了改變列表的外觀,我們沒有添加任何正真的功能。只改變了列表的繪制方式,所以它現在看起來好一些了。但它仍然不是可用的。缺少的最后一部分是列表的行為,這是下一篇的主題,哪里我們將會研究如何實現列表的速滑(flinging )和回彈(snapping )。那將會是向可用列表的一大進步。
- 評注:
這是第二部分,主要是實現了ListView中item的視覺特效,例如旋轉,3D,光照等。但文章中展示的代碼還不足以完成所述功能,可以在這里下載這一節的代碼。
https://pan.baidu.com/s/1CORB1bpQo61UEYE5_OVtfw