[翻譯]制作你自己的 3D ListView 第二部分(Making your own 3D list – Part 2)

前言:

  • 本文翻譯于 sonymobile 的一系列教程邑狸,指導(dǎo) Android 開發(fā)人員如何制作3D風(fēng)格的ListView巧骚,共三部分。這是第二部分侣监。接上篇, 在上一節(jié)的基礎(chǔ)上,這一節(jié)主要實(shí)現(xiàn)的是一些視覺和感覺上的特效臣淤,代碼有好多數(shù)學(xué)相關(guān)的運(yùn)算和圖像處理的細(xì)節(jié)橄霉,比較難懂,需要有相應(yīng)的專業(yè)知識(shí)邑蒋。不過通過閱讀姓蜂,可以讓我們認(rèn)識(shí)一些最基本的圖像處理知識(shí),或許可以部分地采用到我們的項(xiàng)目中医吊。還是采用一段一段的翻譯方式钱慢,文中多次提到效果圖片,我現(xiàn)在已經(jīng)找不到圖片了卿堂,不過可以在文章結(jié)尾找到代碼下載鏈接束莫,下載代碼運(yùn)行就能看到完整的效果懒棉。

正文:

原文:

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.

譯文:

歡迎來到本系列三節(jié)課程中的第二節(jié),這系列課程是關(guān)于如何實(shí)現(xiàn)Android應(yīng)用的3D ListView的览绿。這節(jié)可我們繼續(xù)開發(fā)上節(jié)課創(chuàng)建的非巢哐希基礎(chǔ)的ListView,使它具有3D的樣子和感覺饿敲。在這篇文章的結(jié)尾妻导,我們會(huì)擁有比標(biāo)準(zhǔn)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市場(chǎng)下載 “Sony Ericsson Tutorials” APP(譯注:沒有找到)倔韭。在這個(gè)應(yīng)用中你還可以看見經(jīng)過第三節(jié)課以后ListView的樣子。下面是第二節(jié)課程的代碼渠啤,是為你在Eclipse中創(chuàng)建工程準(zhǔn)備的(譯注:在本文結(jié)尾查看代碼下載鏈接)狐肢。

原文:

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可以自己有內(nèi)邊距但我們當(dāng)前忽略了它(譯注:忽略ListView的內(nèi)邊距)份名。

原文:

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:

譯文:

在測(cè)量(measure) item的時(shí)候,通過減少item的寬度妓美,并在布局(layout)的時(shí)候把它居中僵腺,可以輕松處理左邊距和右邊距。測(cè)量部分的代碼是這樣的:

    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只會(huì)有它寬度的85%壶栋,這些代碼和我們?cè)趏nLayout()中的代碼一起辰如,給我們一些非常好的左邊距和右邊距,但不會(huì)再item之間有間距贵试。為了在item之間產(chǎn)生邊距琉兜,我需要在其他地方做一些改動(dòng),但主要是在處理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:

譯文:

給列表?xiàng)l目之間設(shè)置間距的最直接方式是豌蟋,把它們按一個(gè)固定像素值分離放置,這個(gè)在很多場(chǎng)景下工作正常桑滩。但實(shí)際上不是我想要的的梧疲。我想要的替代做法是,讓間距依賴item的高度运准,讓我們?nèi)缦露x間距:

    /** 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.

譯文:

這種方式下幌氮,大的條目將會(huì)比小的條目得到更大的間距。由于每一個(gè)條目將會(huì)占用更多的垂直空間胁澳,我們需要修改所有在子view上使用getTop()该互,getBottom(),getMessuredHight()的方法听哭,并且依賴這些值進(jìn)行布局慢洋。例如塘雳,我們上一次實(shí)現(xiàn)的方法fillListDown()依賴這樣的事實(shí):一個(gè)view占據(jù)和 getMessuredHight()返回值一樣大的空間陆盘。用這種邊距定義普筹,一個(gè)視圖占據(jù)ITEM_VERTICAL_SPACE倍的空間(譯注:一個(gè)視圖占據(jù)的高度是它自身高度的1.45倍)。首先我們實(shí)現(xiàn)一些有則更好的實(shí)用方法隘马。

    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()為什么這樣實(shí)現(xiàn)太防,為什么不直接返回child.getMeasuredHeight() * ITEM_VERTICAL_SPACE。這是因?yàn)橛袝r(shí)候我們只需要計(jì)算一端的間距酸员,有時(shí)候要計(jì)算兩端的蜒车,如果我們不用相同的方式計(jì)算,我們最終會(huì)因?yàn)榛`差幔嗦,陷入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.

譯文:

現(xiàn)在我們用getChildTop(child)替換所有出現(xiàn)的child.getTop(),getBottom()和getMeasuredHeight()也是一樣的邀泉。實(shí)際在child上調(diào)用layout的部分代碼是一個(gè)例外嬉挡,在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.

譯文:

現(xiàn)在我們的item被很好地填充汇恤,而不是一個(gè)挨一個(gè)的庞钢。

原文:

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.

譯文:

定義一些其他相關(guān)的東西,通常因谎,如果你想簡(jiǎn)單地把一個(gè)View縮放到不同的Size基括,改變View的寬和高(width and height)是一個(gè)好的做法。市場(chǎng)上有很多不同的android設(shè)備财岔,盡可能地支持更多設(shè)備肯定是一件好事情风皿。我已經(jīng)在我們這兒做的QVGA分辨率X10 mini和WVGA分辨率X10兩款手機(jī)上測(cè)試了View,由于幾乎所有的東西都相對(duì)于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()].

譯文:

改變外表

當(dāng)在canvas上繪制圖形時(shí),圖形經(jīng)常受到canvas的轉(zhuǎn)換矩陣的影響患朱。這個(gè)矩陣可以縮放鲁僚,平移,旋轉(zhuǎn)或者其他的內(nèi)容轉(zhuǎn)換裁厅。canvas 類有一些實(shí)用的工具方法來做這些正常的轉(zhuǎn)換冰沙,例如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().

譯文:

然而拓挥,當(dāng)我們使用它之前,我們需要重寫一個(gè)draw 方法來持有canvas袋励。在一個(gè)正常的View上你應(yīng)該重寫onDraw()并在這個(gè)方法里繪制內(nèi)容侥啤。然而ListView它本身是空的当叭,它的內(nèi)容完全是由它的子View繪制組成的。為了重寫子View的繪制盖灸,我們可以重寫dispatchDraw()或者drawChild()蚁鳖,這個(gè)例子中我們將使用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操作來改變列表赁炎,下面的代碼將會(huì)從中間往下縮放和旋轉(zhuǎn)條目醉箕。

    @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.

譯文:

開頭有趣的部分是調(diào)用了canvas的save方法,它能使我們?cè)诤竺媸褂胷estore 把canvas恢復(fù)到save前的狀態(tài)徙垫,為了不使我們所做的事情搞糟了canvas讥裤。然后我們圍繞子View的中心,根據(jù)子View到屏幕中心的距離姻报,旋轉(zhuǎn)畫布并把它(譯注:縮小canvas)縮小,然后我們調(diào)用父類的drawChild方法并將畫布還原己英。你可以在下面的截屏中看見這個(gè)效果(從X10 mini截的)(譯注,沒有圖了吴旋,可以運(yùn)行代碼看效果)损肛。注意當(dāng)view被縮放和旋轉(zhuǎn)以后,它們?cè)馐芰司薮蟮淖邩訂栴}(譯注:旋轉(zhuǎn)和縮放以后邮府,有明顯的鋸齒現(xiàn)象荧关,現(xiàn)象為邊緣不平滑)。

原文:

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

譯文:

通常你用一個(gè)Paint對(duì)象繪制褂傀,在Paint對(duì)象上你可以啟用anti-aliasing和filtering忍啤,這兩個(gè)屬性會(huì)拉低性能,但是仙辟,在大多數(shù)情況下必須如此同波。為了可以解決鋸齒問題我們可以尋找View自己的drawing cache(確保我們?cè)谇懊嬉呀?jīng)啟用這個(gè)了),然后使用一個(gè)有filtering(譯注:濾波器)并且啟用anti-aliasing(譯注:抗鋸齒)的Paint對(duì)象把那個(gè)bitmap繪制到canvas上叠国。用下面的代碼代替super調(diào)用

Bitmap bitmap = child.getDrawingCache();
canvas.drawBitmap(bitmap, left, top, mPaint);

原文:

gives a much better result

譯文:

得到一個(gè)更好的效果未檩。

原文:

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.

譯文:

記住我們現(xiàn)在改變的是View繪制的地方而不是布局的地方,View實(shí)際的位置還是不變的(譯注:修改子view的繪制不會(huì)改變子view的實(shí)際位置和大小粟焊,只是看起來縮放和旋轉(zhuǎn)了)冤狡,換句話說,View顯示的圖像和它實(shí)際的位置將會(huì)不一致项棠。導(dǎo)致一個(gè)結(jié)果是當(dāng)我們點(diǎn)擊顯示在屏幕上的view時(shí)悲雳,它可能不是真正在那兒,它可能是另外一個(gè)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.

譯文:

這當(dāng)然取決于view已經(jīng)被你轉(zhuǎn)換或者移動(dòng)了多少合瓢,如果它成為一個(gè)需要你修改代碼來尋找用戶點(diǎn)擊在哪一個(gè)view上的問題,你就不能再依賴命中矩形 [hit rect]透典。你還可能需要重寫dispatchTouchEvent()并且在傳遞到子view之前晴楔,轉(zhuǎn)換手勢(shì)事件的坐標(biāo)顿苇。然而這節(jié)課,我們將會(huì)跳過這部分并且設(shè)計(jì)我們的列表使得這樣的問題最小化税弃。

原文:

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.

譯文:

另一個(gè)值得注意的事情是纪岁,列表現(xiàn)在看起來很蹩腳的。我們這兒做的旋轉(zhuǎn)view不是很有趣钙皮。為了讓它有意思蜂科,我做一點(diǎn)點(diǎn)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.

譯文:

開始認(rèn)識(shí)Camera(譯注:這個(gè)不是照相的相機(jī))

Canvas上的矩陣變換是能勝任3D變換的短条。但我們不能用canvas上的普通方法,我們需要自己制作矩陣并且用那個(gè)矩陣畫我們的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有一個(gè)Camera類茸时,用它來創(chuàng)建3D變換矩陣是很便利的。用這個(gè)類你可以沿著X赋访,Y可都,Z軸旋轉(zhuǎn)和平移。這個(gè)類的一個(gè)缺點(diǎn)是完全沒有文檔蚓耽。SDK里面有一些示例代碼用這個(gè)類做3D變換渠牲,我推薦查看這些示例代碼。

原文:

The below code rotates the views around the Y-axis using the camera class.

譯文:

下面的代碼使用camera類圍繞Y軸旋轉(zhuǎn)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對(duì)象我們就創(chuàng)建一個(gè)鼎兽。然后我們保存camera的狀態(tài)答姥,這樣我們能在后面把它還原到默認(rèn)狀態(tài)。然后我們圍繞Y軸旋轉(zhuǎn)camera⊙枰В現(xiàn)在我們需要從camera中得到矩陣鹦付,這時(shí)我們就完成了對(duì)camera的使用,所以我們還原它择卦。我們現(xiàn)在拿到的矩陣已經(jīng)具有我們使用camera所做的旋轉(zhuǎn)敲长。但它在某種意義上是一個(gè)原始的旋轉(zhuǎn)矩陣,因?yàn)樗话魏无D(zhuǎn)換秉继,因此它會(huì)在圍繞(0祈噪, 0)旋轉(zhuǎn)所有東西。為了圍繞中心旋轉(zhuǎn)子view秕噪,我們pre-translate矩陣钳降,(0, 0)位置會(huì)在屏幕的中心腌巾。然后我們添加一些縮放遂填,然后我們變換矩陣铲觉,他就能被繪制到正確的位置上。最后我們用這個(gè)矩陣?yán)L制bitmap吓坚,可以看到下面的結(jié)果(譯注:還是沒圖撵幽,下載代碼運(yùn)行看效果),盡管一個(gè)靜態(tài)圖片不能傳達(dá)列表滾動(dòng)是的樣子礁击。
(譯注:這里主要是matrix和camera的變換盐杂,需要相應(yīng)的專業(yè)知識(shí),我并不了解這兩個(gè)類的數(shù)學(xué)細(xì)節(jié))

原文:

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.

譯文:

在這里你可以在這里玩很多潛在的變換哆窿。我非常建議你試幾個(gè)變換看看效果链烈。還有,這個(gè)代碼有一些視角問題挚躯,它很容易修復(fù)强衡,通過使用camera變換代替matrix變換。我將在稍后談及這一點(diǎn)码荔。然而漩勤,我將會(huì)修正的任務(wù)作為練習(xí)留給感興趣的讀者。

原文:

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 手機(jī)有令人驚訝的逐像素繪制性能缩搅,因此可以在顯示一些它可以做的同時(shí)顯示一些你可以用canvas 變換和camera做的越败。我們繼續(xù)做更加3D的效果。如果你已經(jīng)看過這個(gè)視頻硼瓣,你就知道它是什么樣子究飞。每一個(gè)item會(huì)是一個(gè)塊狀(而不僅僅是之前那樣的平面),當(dāng)列表滑動(dòng)的時(shí)候巨双,它將會(huì)沿著它的X軸翻轉(zhuǎn)而看起來像是在滾動(dòng)噪猾。每一個(gè)item將會(huì)和它正常時(shí)一樣寬,它的深度和它的高度相等筑累。我們對(duì)所有的面使用相同的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().

譯文:

我們需要怎么做才能達(dá)到這種效果呢?為了畫塊狀我們需要畫兩次bitmap(因?yàn)槲覀兇蠖鄶?shù)時(shí)候看見塊體的兩個(gè)面)慢宗。我們還需要一些類型的旋轉(zhuǎn)變量來追蹤主要的旋轉(zhuǎn)坪蚁。由于當(dāng)用戶滑動(dòng)列表時(shí)塊體應(yīng)該旋轉(zhuǎn),所有的塊體應(yīng)該有相同的旋轉(zhuǎn)(這樣他們?cè)谕粫r(shí)間正面朝上)镜沽。我們不能再像之前一樣使用到中心的距離來計(jì)算塊的旋轉(zhuǎn)角度敏晤。一個(gè)計(jì)算旋轉(zhuǎn)的方便的辦法是用列表的top位置,讓我們?cè)趕crollList()添加下面的一行代碼缅茉。

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.

譯文:

像這樣做嘴脾,當(dāng)用戶滑動(dòng)一整屏?xí)r塊體旋轉(zhuǎn)DEGREES_PER_SCREEN度,和屏幕高度像素?cái)?shù)無關(guān)。(譯注:比如列表高度100译打,當(dāng)前第一個(gè)可見的item頂部即mListTop是20耗拓,那么它就旋轉(zhuǎn)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.

譯文:

那樣會(huì)處理好旋轉(zhuǎn)。現(xiàn)在我們考慮實(shí)際繪制奏司。這是drawChild() 方法現(xiàn)在的樣子乔询。

    @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.

譯文:

這里的大多數(shù)和我們之前做的旋轉(zhuǎn)和縮放item的代碼非常相似,所以我想探究細(xì)節(jié)(譯注:作者不想探究細(xì)節(jié)韵洋,或許是筆誤)竿刁。值得注意的是那些代碼和繪制塊體的一面是相同的。它僅僅依賴旋轉(zhuǎn)搪缨。因此它被提取到一個(gè)方法里面食拜。我們簡(jiǎn)單通過在同一個(gè)地方畫兩個(gè)相隔90度的面來畫一個(gè)完整的塊體。

原文:

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.

譯文:

要畫一面勉吻,我們首先移動(dòng)camera监婶,這樣畫出來的面會(huì)離我們近一些。然后我們旋轉(zhuǎn)它齿桃,再把它移回去,這樣我們就不縮放它煮盼。記住對(duì)camera的調(diào)用短纵,就像在canvas上的旋轉(zhuǎn),平移僵控,縮放方法香到,需要逆序?qū)懀褪钦f报破,下面的代碼悠就,最后一行把面向著我們移動(dòng),然后我們旋轉(zhuǎn)它充易,最后用第一行梗脾,我們把它向后移動(dòng)。

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剩下的和前面的代碼或多或少一樣盹靴,從相機(jī)中取得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.

譯文:

這份代碼會(huì)把每一個(gè)item當(dāng)成是一開始就放置在3D空間中一樣進(jìn)行繪制梭冠,然后我們用matrix的先后平移,把item移動(dòng)到屏幕的正確位置改备。這個(gè)移動(dòng)我們?cè)?D空間的繪制控漠,沒有改變角度。我們應(yīng)該使用在camera上進(jìn)行X和Y軸的平移來替代悬钳。然后平移會(huì)在3D空間進(jìn)行并且它會(huì)影響角度盐捷。我們這兒不這樣做因?yàn)橄啾萩amera中區(qū)域合適的View柬脸,我想要區(qū)域大一些的View出現(xiàn)。我們根據(jù)item相對(duì)屏幕中心的距離輕微地旋轉(zhuǎn)和縮放item假裝這樣毙驯,對(duì)這個(gè)view倒堕,它工作得挺好。

原文:

Anyway, this is how the list looks blockified.

譯文:

總之爆价,這就是塊狀化的列表看起來的樣子(譯注:還是沒圖垦巴,運(yùn)行代碼看效果)。

原文:

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效果铭段,我們需要添加一些光照骤宣,沒有這個(gè)它會(huì)看起來是平的。一個(gè)實(shí)現(xiàn)辦法就是修改alpha(譯注:透明度)序愚。這樣做的優(yōu)勢(shì)是非常簡(jiǎn)單憔披,但缺點(diǎn)是它只有在黑色背景下才能表現(xiàn)得好。還有芬膝,這種方式不可能做高亮(光反射)效果。我們想要我們的View在其他背景下工作并且有高亮效果形娇,因此我們將會(huì)用另一種辦法解決光照問題锰霜。

原文:

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對(duì)象上我們可以設(shè)置顏色過濾器,它會(huì)影響我們用Paint對(duì)象繪制的色值桐早。setColorFilter()接受一個(gè)ColorFilter 并且ColorFilter 的一個(gè)子類是LightingColorFilter癣缅,這幾乎恰好是我們想要的。一個(gè)LightingColorFilter 接收兩個(gè)顏色參數(shù)哄酝,用來修改我們繪制的顏色友存,第一個(gè)顏色將會(huì)乘以(multiplied)我們繪制的顏色,這個(gè)乘法會(huì)是的顏色變暗并且(加法)adding會(huì)使得它變亮陶衅,因此我們可以用這個(gè)類來模擬陰影和高亮屡立。用屏幕混合模式(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.

譯文:

實(shí)際的光線計(jì)算我們用一個(gè)簡(jiǎn)單版本的Phong著色法(Phong lighting),所以讓我們定義幾個(gè)光線常亮奕巍。

    /** 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.

譯文:

現(xiàn)在我們實(shí)現(xiàn)一個(gè)方法吟策,它會(huì)計(jì)算光線并且創(chuàng)建LightingColorFilter 給我們的Paint 對(duì)象。

    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.

譯文:

這個(gè)方法唯一的傳入?yún)?shù)是面的旋轉(zhuǎn)角度的止,我們用這個(gè)旋轉(zhuǎn)角度計(jì)算兩個(gè)強(qiáng)度檩坚,第一個(gè)強(qiáng)度是我們主要的光級(jí),它被用來創(chuàng)建我們相乘的顏色,它會(huì)使我們的繪畫變暗匾委。第二個(gè)強(qiáng)度是“高亮強(qiáng)度”拖叙,是我們用來創(chuàng)建相加顏色的反射光,它會(huì)使我們的繪制變亮赂乐。我們計(jì)算完強(qiáng)度以后薯鳍,確保它們最大是255, 然后我們創(chuàng)建顏色挨措。在這種情況下我們有一個(gè)白色光源因此我們的顏色會(huì)變灰挖滤,但你也可以用變亮的顏色。最后我們創(chuàng)建一個(gè)LightingColorFilter 并返回它浅役≌端桑可以在下面看見最終用光照繪制的列表(譯注:還是沒圖,運(yùn)行代碼看效果)觉既。

原文:

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.

譯文:

待續(xù)...
這篇文章討論了改變列表的外觀惧盹,我們沒有添加任何正真的功能。只改變了列表的繪制方式瞪讼,所以它現(xiàn)在看起來好一些了钧椰。但它仍然不是可用的。缺少的最后一部分是列表的行為尝艘,這是下一篇的主題演侯,哪里我們將會(huì)研究如何實(shí)現(xiàn)列表的速滑(flinging )和回彈(snapping )。那將會(huì)是向可用列表的一大進(jìn)步背亥。

  • 評(píng)注:
    這是第二部分,主要是實(shí)現(xiàn)了ListView中item的視覺特效悬赏,例如旋轉(zhuǎn)狡汉,3D,光照等闽颇。但文章中展示的代碼還不足以完成所述功能盾戴,可以在這里下載這一節(jié)的代碼。
    https://pan.baidu.com/s/1CORB1bpQo61UEYE5_OVtfw
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末兵多,一起剝皮案震驚了整個(gè)濱河市尖啡,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌剩膘,老刑警劉巖衅斩,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異怠褐,居然都是意外死亡畏梆,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奠涌,“玉大人宪巨,你說我怎么就攤上這事×锍” “怎么了捏卓?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)慈格。 經(jīng)常有香客問我怠晴,道長(zhǎng),這世上最難降的妖魔是什么峦椰? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任龄寞,我火速辦了婚禮,結(jié)果婚禮上汤功,老公的妹妹穿的比我還像新娘物邑。我一直安慰自己,他們只是感情好滔金,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布色解。 她就那樣靜靜地躺著,像睡著了一般餐茵。 火紅的嫁衣襯著肌膚如雪科阎。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天忿族,我揣著相機(jī)與錄音锣笨,去河邊找鬼。 笑死道批,一個(gè)胖子當(dāng)著我的面吹牛错英,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播隆豹,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼椭岩,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了璃赡?” 一聲冷哼從身側(cè)響起判哥,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎碉考,沒想到半個(gè)月后塌计,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡豆励,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年夺荒,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瞒渠。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡技扼,死狀恐怖伍玖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情剿吻,我是刑警寧澤窍箍,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站丽旅,受9級(jí)特大地震影響椰棘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜榄笙,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一邪狞、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茅撞,春花似錦帆卓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至拄查,卻和暖如春吁津,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背堕扶。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國(guó)打工碍脏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人稍算。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓潮酒,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親邪蛔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

推薦閱讀更多精彩內(nèi)容

  • rljs by sennchi Timeline of History Part One The Cognitiv...
    sennchi閱讀 7,346評(píng)論 0 10
  • 成長(zhǎng)的過程需要理解和尊重扎狱。 2017年9月18日 星期一 陰轉(zhuǎn)雨 進(jìn)入了九月似乎就進(jìn)入了雨季侧到,今年的雨...
    冰月月閱讀 309評(píng)論 12 11
  • 班展-在城南書院代表著大多數(shù)班級(jí)對(duì)城南兩年生活的一個(gè)提前告別。來曬曬我們的班展啦~ 這次班展花了大概1800的樣子...
    萌萌噠蓉閱讀 242評(píng)論 0 0
  • 窈窕淑女,君子好逑 蘇靜個(gè)子高挑污抬,文靜內(nèi)向汞贸,自第一天來公司面試绳军,便入了上司蘇然的法眼,在此之前矢腻,他已經(jīng)面試了2個(gè)月...
    少年長(zhǎng)志閱讀 633評(píng)論 3 3
  • 時(shí)如白駒過隙匆 如夢(mèng)醒時(shí)發(fā)斑白 唯持童心情未泯 笑憶攀樹逐花時(shí)
    凌峰峰行閱讀 249評(píng)論 1 4