实现一个定制的3DListview——第二部分
2014-03-15 13:07
253 查看
原文在这里->Making your own 3D list – Part 2
左右的padding很容易通过减小项的宽度来产生,就是在我们measure子view的时候,然后在onLayout的时候让他居中。测量的部分像这样:
现在我们这样替换
ITEM_WIDTH定义了项的宽占总宽的比值
这样项的宽就会限制在列表宽的85%,这会在onLayout的时候产生一个很漂亮的左右间距,但项之间却没有。
为项之间添加间隙的最直接的方式是在layout的时候加一个偏移,这在大多数情况下可以,但是不是我想要的,我想做的是间距可以随着项的高度变化,所以我们这样定义padding:
这样较大的view就会有更宽的间隙,由于每个列表项会占用更大的空间,所以我们需要修改使用getTop(),getBottom(),getMeasureHeight()来得到子view数据的方法,例如,fillListDown()要依赖getMeasuredHeight()返回的值。在这样的定义下,一个列表项占的空间将是ITEM_VERTICAL_SPACE倍,首先我们来实现这些工具方法
现在替换所有child.getTop()、child.getBottom()和child.getMeasuredHeight(),使用上面的工具方法。这里着重强调一下positionItems()方法,
之后再运行它看起来是这样的
![](http://img.blog.csdn.net/20140311152254265?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQveHVfZnU=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
当你觉得一个view会有不同的缩放大小时,通过关联来定义变量,像view的宽和高,是一种很好的做法。这样可以方便地支持不同的屏幕大小。
然而在使用这些之前,我们需要覆写draw()方法来获取关联的canvas。通常你需要覆写onDraw(),然后在上面绘制内容,但是列表控件本身是空的,最终绘制的其实是列表项的内容。要覆写子view相关的绘制我们可以用dispatchDraw()或者drawChild(),这里我们使用drawChild()。
我们从使用常用的canvas操作开始来改变控件,下面的代码会将距离中心渐远的项进行缩放和旋转。
![](http://img.blog.csdn.net/20140311163029375)
通常你可以使用Paint对象来绘制图形,paint可以设置anti-aliasing和filtering,这两个都会降低性能,但是在大多数情况下需要这样做。为了修复锯齿我们可以获取子view的绘制缓存(确保在调用之前已使能该功能),然后用带有filtering和anti-aliasing的Paint的Canvas来绘制这个图像,将super的调用替换为下面的部分:
这样结果看上去好多了
![](http://img.blog.csdn.net/20140315103208328)
我们现在只是改变了子view绘制的地方而没有改变layout,子view实际的区域还没有变,也就是说显示出来的view不在它实际会响应的区域。结果就是我们点在一个view上,发生响应的却是另一个view。当然这取决于你移动view的距离,如果产生问题了,你就需要修改代码来找到它实际的位置,我们这里跳过这部分,只是将我们的listview设计的尽量避免这个问题。
另一个问题是现在我们的list有点丑,像我们这样旋转没什么意思,为了更有趣点,我们给它加点3D效果
Android的Camera类可以用来创建3D变换矩阵,有了它你可以绕x、y、z轴做旋转和平移。它的缺点是这个类的说明文档太简单了,在SDK里有一个关于它如何使用的例子。
下面的代码会将view绕Y轴旋转:
我们来看下这段代码,首先,创建camera,然后做Y-轴旋转,现在我们需要从camera中将旋转后的矩阵取出来,目前这个矩阵只包含了y轴的旋转动作,使用它会让view绕着(0,0)坐标点旋转,也就是view的左上角,要让它以view的中心为原点,我们需要先将原点平移到这个中心位置,然后我们加一些缩放和平移让它绘制在正确的位置上。
![](http://img.blog.csdn.net/20140315110640343)
这样每当用户滚动一个屏幕的距离后,列表项就会旋转DEGREES_PER_SCREEN的角度。
下面是drawChild()现在的样子
为了画一个面我们要先移动camera来将绘制的图像向我们拉近,然后绕x轴旋转后再移回去,就像这样
drawFace其余的代码和之前的类似,从camera获取matrix,移动然后绘制
![](http://img.blog.csdn.net/20140315121745500)
在Paint对象上我们可以设置color filter来影响绘制时候的色值,setColorFilter()方法需要传入一个ColorFilter,ColortFilter有一个子类LightColorFilter正是我们想要的。LightColorFilter使用两个色值,第一个与要绘制的颜色相乘,另一个会相加,乘法运算会使颜色变暗而加法则会使颜色更明亮一些,所以我们可以使用这个类来作阴影和高光效果。
实际计算光线时我们使用冯氏着色法的一个简化版本,先来定义一些光照常量,
然后实现一个计算光照的方法,以此创建LightColorFilter对象
最后就会看到这样的结果
![](http://img.blog.csdn.net/20140315125537875)
代码在这里->http://download.csdn.net/detail/xu_fu/7046515
Add some padding
首先我们要做的是增加一些padding,就是在列表项之间添加一些间隙。这个List本身可以实现但我们这里不用它。左右的padding很容易通过减小项的宽度来产生,就是在我们measure子view的时候,然后在onLayout的时候让他居中。测量的部分像这样:
int itemWidth = getWidth(); child.measure(MeasureSpec.EXACTLY | itemWidth, MeasureSpec.UNSPECIFIED);
现在我们这样替换
int itemWidth = (int) (getWidth() * ITEM_WIDTH); child.measure(MeasureSpec.EXACTLY | itemWidth, MeasureSpec.UNSPECIFIED);
ITEM_WIDTH定义了项的宽占总宽的比值
private static final float ITEM_WIDTH = 0.85f;
这样项的宽就会限制在列表宽的85%,这会在onLayout的时候产生一个很漂亮的左右间距,但项之间却没有。
为项之间添加间隙的最直接的方式是在layout的时候加一个偏移,这在大多数情况下可以,但是不是我想要的,我想做的是间距可以随着项的高度变化,所以我们这样定义padding:
private static final float ITEM_VERTICAL_SPACE = 1.45f;
这样较大的view就会有更宽的间隙,由于每个列表项会占用更大的空间,所以我们需要修改使用getTop(),getBottom(),getMeasureHeight()来得到子view数据的方法,例如,fillListDown()要依赖getMeasuredHeight()返回的值。在这样的定义下,一个列表项占的空间将是ITEM_VERTICAL_SPACE倍,首先我们来实现这些工具方法
private int getChildMargin(View view) { int margin = (int) (view.getMeasuredHeight() * (ITEM_VERTICAL_SPACE - 1) / 2); return margin; } private int getChildTop(View view) { return view.getTop() - getChildMargin(view); } private int getChildBottom(View view) { return view.getBottom() + getChildMargin(view); } private int getChildHeight(View view) { return view.getMeasuredHeight() + getChildMargin(view) * 2; }你可能想知道为什么getChildHeight()这样实现,为什么不直接返回child.getMeasuredHeight() +2*ITEM_VERTICAL_SPACE。原因是有时候我们只需要计算一边的padding,有时候两边都需要,如果我们不使用相同的规则计算,最后会发现getChildHeight()和getChildBottom() - getChildTop()的结果不一致而产生错误。
现在替换所有child.getTop()、child.getBottom()和child.getMeasuredHeight(),使用上面的工具方法。这里着重强调一下positionItems()方法,
private void positionItems() { int top = mListTop + mListTopOffset; for (int index = 0; index < getChildCount(); ++index) { View childView = getChildAt(index); int width = childView.getMeasuredWidth(); int height = childView.getMeasuredHeight(); int left = (getWidth() - width) / 2; int margin = getChildMargin(childView); int childTop = margin + top; childView.layout(left, childTop, left + width, childTop + height); top += height + 2 * margin; } }
之后再运行它看起来是这样的
当你觉得一个view会有不同的缩放大小时,通过关联来定义变量,像view的宽和高,是一种很好的做法。这样可以方便地支持不同的屏幕大小。
Changeing appearance
当在canvas上作图时,图形会受到canvas的变换矩阵的影响,这个矩阵可以缩放、移动、旋转或者改变内容,canvas类提供了简便的方法来做这些变换,像scale()、rotate()和translate()。然而在使用这些之前,我们需要覆写draw()方法来获取关联的canvas。通常你需要覆写onDraw(),然后在上面绘制内容,但是列表控件本身是空的,最终绘制的其实是列表项的内容。要覆写子view相关的绘制我们可以用dispatchDraw()或者drawChild(),这里我们使用drawChild()。
我们从使用常用的canvas操作开始来改变控件,下面的代码会将距离中心渐远的项进行缩放和旋转。
@Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { int left = child.getLeft(); int top = child.getTop(); int centerX = child.getWidth() / 2; int centerY = child.getHeight() / 2; float pivotX = left + centerX; float pivotY = top + centerY; float centerScreen = getHeight() / 2; float distFromCenter = (pivotY - centerScreen) / centerScreen; 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; }有趣的部分是从canvas调用save方法开始的,这样可以让我们在restore时恢复到现在的状态,以免在按步骤绘制时造成混乱。然后我们旋转并缩放子view。你可以看到下面的效果,但是有严重的锯齿。
通常你可以使用Paint对象来绘制图形,paint可以设置anti-aliasing和filtering,这两个都会降低性能,但是在大多数情况下需要这样做。为了修复锯齿我们可以获取子view的绘制缓存(确保在调用之前已使能该功能),然后用带有filtering和anti-aliasing的Paint的Canvas来绘制这个图像,将super的调用替换为下面的部分:
Bitmap bitmap = child.getDrawingCache(); canvas.drawBitmap(bitmap, left, top, mPaint);
这样结果看上去好多了
我们现在只是改变了子view绘制的地方而没有改变layout,子view实际的区域还没有变,也就是说显示出来的view不在它实际会响应的区域。结果就是我们点在一个view上,发生响应的却是另一个view。当然这取决于你移动view的距离,如果产生问题了,你就需要修改代码来找到它实际的位置,我们这里跳过这部分,只是将我们的listview设计的尽量避免这个问题。
另一个问题是现在我们的list有点丑,像我们这样旋转没什么意思,为了更有趣点,我们给它加点3D效果
Getting to know the Camera
Canvas的变换矩阵可以实现3D变换。但是canvas提供的通用方法不能达到我们的效果,我们需要创建自己的矩阵并在绘制的时候使用它。Android的Camera类可以用来创建3D变换矩阵,有了它你可以绕x、y、z轴做旋转和平移。它的缺点是这个类的说明文档太简单了,在SDK里有一个关于它如何使用的例子。
下面的代码会将view绕Y轴旋转:
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);
我们来看下这段代码,首先,创建camera,然后做Y-轴旋转,现在我们需要从camera中将旋转后的矩阵取出来,目前这个矩阵只包含了y轴的旋转动作,使用它会让view绕着(0,0)坐标点旋转,也就是view的左上角,要让它以view的中心为原点,我们需要先将原点平移到这个中心位置,然后我们加一些缩放和平移让它绘制在正确的位置上。
Blockifying the list
我们还要将列表项做成一个立方体,滚动的时候会看到它发生翻转。为了绘制立方体我们需要画两次表面(一般正面看一个盒子就是两个面),我们还需要几个旋转的变量来追踪the main rotation。我们不再使用到列表中心的距离来计算旋转角度,而是采用当前list top的位置:这样每当用户滚动一个屏幕的距离后,列表项就会旋转DEGREES_PER_SCREEN的角度。
下面是drawChild()现在的样子
@Override protected boolean drawChild(Canvas canvas, View child, long drawingTime) { final Bitmap bitmap = child.getDrawingCache(); if (bitmap == null) { return super.drawChild(canvas, child, drawingTime); } int left = child.getLeft(); int top = child.getTop(); int centerX = child.getWidth() / 2; int centerY = child.getHeight() / 2; float pivotX = left + centerX; float pivotY = top + centerY; float centerScreen = getHeight() / 2; float distFromCenter = (pivotY - centerScreen) / centerScreen; float scale = (float) (1 - SCALE_DOWN_FACTOR * (1 - Math.cos(distFromCenter))); float childRotation = mListRotation - 20 * distFromCenter; childRotation %= 90; if (childRotation < 0) { childRotation += 90; } if (childRotation < 45) { drawFace(canvas, bitmap, left, top, centerX, centerY, scale, childRotation - 90); drawFace(canvas, bitmap, left, top, centerX, centerY, scale, childRotation); } else { drawFace(canvas, bitmap, left, top, centerX, centerY, scale, childRotation); drawFace(canvas, bitmap, left, top, centerX, centerY, scale, childRotation - 90); } canvas.drawBitmap(bitmap, mMatrix, mPaint); return false; }大部分代码和之前的一样,主要修改在立方体的绘制上,所以把它提到一个单独的方法中drawFace();
为了画一个面我们要先移动camera来将绘制的图像向我们拉近,然后绕x轴旋转后再移回去,就像这样
mCamera.translate(0, 0, centerY); mCamera.rotateX(rotateDegree); mCamera.translate(0, 0, -centerY);
drawFace其余的代码和之前的类似,从camera获取matrix,移动然后绘制
private void drawFace(Canvas canvas, Bitmap bitmap, int left, int top,现在看起来已经有3d的效果了
int centerX, int centerY, float scale, float rotateDegree) {
if (mCamera == null) {
mCamera = new Camera();
}
mCamera.save();
mCamera.translate(0, 0, centerY); mCamera.rotateX(rotateDegree); mCamera.translate(0, 0, -centerY);
if (mMatrix == null) {
mMatrix = new Matrix();
}
mCamera.getMatrix(mMatrix);
mCamera.restore();
mMatrix.preTranslate(-centerX, -centerY);
mMatrix.postScale(scale, scale);
mMatrix.postTranslate(left + centerX, top + centerY);
canvas.drawBitmap(bitmap, mMatrix, mPaint);
}
Let there be light
为了让List看着更有3D的效果,我们需要加一些光照,没有光照的话看起来还是平面的。一种方法是调整alpha,优点是相当简单,缺点是只在黑色的背景上比较明显,而且这种方式也不支持高光(反射光)。为了效果更加逼真,我们需要想其他办法。在Paint对象上我们可以设置color filter来影响绘制时候的色值,setColorFilter()方法需要传入一个ColorFilter,ColortFilter有一个子类LightColorFilter正是我们想要的。LightColorFilter使用两个色值,第一个与要绘制的颜色相乘,另一个会相加,乘法运算会使颜色变暗而加法则会使颜色更明亮一些,所以我们可以使用这个类来作阴影和高光效果。
实际计算光线时我们使用冯氏着色法的一个简化版本,先来定义一些光照常量,
private static final int AMBIENT_LIGHT = 55; private static final int DIFFUSE_LIGHT = 200; private static final float SPECULAR_LIGHT = 70; private static final float SHININESS = 200; private static final int MAX_INTENSITY = 0xff;
然后实现一个计算光照的方法,以此创建LightColorFilter对象
private LightingColorFilter calculateColorFilter(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); }这个方法的输入是面的旋转角度,我们使用这个角度来计算两种亮度,第一个是我们的主要的光亮,他用来计算我们相乘时候的值,第二个是反射光,计算完之后我们要确保最大色值为255。在这种情况下我们的光源是白色的,但是你也可以使用其他的有色光源。最后创建LightColorFilter并返回。
最后就会看到这样的结果
To be contimued
这个部分主要是改变外观,最后一部分将会实现滑动和回弹的物理效果。代码在这里->http://download.csdn.net/detail/xu_fu/7046515
相关文章推荐
- 实现一个定制的3D ListView——第一部分
- 实现一个定制的3DListView——第三部分(final)
- 循环神经网络教程第二部分-用python,numpy,theano实现一个RNN
- ListView.OnScrollListener监听listview滚到最底部,实现分页加载(本文第一部分转载,第二部分原创)
- android listview嵌套gridview,并实现grid元素部分显示以及点击展开与折叠
- SpringAOP拦截Controller,Service实现日志管理(自定义注解的方式)里面的坑之 同一个controller部分方法可以拦截,部分方法拦截不了
- 想给自己制作一个简单的相册吗?快来看看 怎样实现3D图片相册效果
- 用cocos2d3.0写一个srpg游戏-移动部分的实现
- 黑马程序员-关于Collections类中fill方法的一个拓展练习(实现将list中部分元素替换)
- 怎样实现一个3D模型漂浮,上下浮动的效果
- (译)如何使用cocos2d和box2d来制作一个Breakout游戏:第二部分(完)
- 一个实现抽屉滑动删除效果的listView
- 如何通过创建一个Fraction类(分数)来实现分数的加减乘除,比较大小、约分等方法(方法的实现部分)
- 输入一个整型数组,实现一个函数,来调整数组中的数字顺序是的数组中所有的奇数位于数组前半部分,所有偶数位于数组的后半部分。
- android edittext+listview进阶 实现搜索listview中的内容 定制自己的过滤器
- 自己实现一个右滑删除的ListView
- Android高级控件(六)——自定义ListView高仿一个QQ可拖拽列表的实现
- 如何创建一个类似 Instagram 的使用 Web Service 作后台的应用 第二部分
- Cocos2d-x简单游戏<植物大战僵尸>代码实现|第二部分:菜单场景<后续会提供源码下载链接>
- dySE:一个 Java 搜索引擎的实现,第 2 部分: 网页预处理