前言:
- 本文翻译于 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