在看了iOS上面的CoverFlow后,感觉效果真的不错,就想在android上面实现一个,这个程序在网上参考了一此核心的代码,当然我添加了一些其他的东西,废话不多话,先看效果,不然就是无图无真相了。
其实实现这个效果很简单,下面作一个简单的介绍
一,创建倒影效果
这个基本思路是:
1,创建一个源图一样的图,利用martrix将图片旋转180度。这个倒影图的高是源图的一半。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
Matrix matrix =
new
Matrix();
// 1表示放大比例,不放大也不缩小。
// -1表示在y轴上相反,即旋转180度。
matrix.preScale(
1
, -
1
);
Bitmap reflectionBitmap = Bitmap.createBitmap(
srcBitmap,
0
,
srcBitmap.getHeight() /
2
,
// top为源图的一半
srcBitmap.getWidth(),
// 宽度与源图一样
srcBitmap.getHeight() /
2
,
// 高度与源图的一半
matrix,
false
);
|
2,创建一个最终效果的图,即源图 + 间隙 + 倒影。
1
2
3
4
5
6
|
final
int
REFLECTION_GAP =
5
;
Bitmap bitmapWithReflection = Bitmap.createBitmap(
reflectionWidth,
srcHeight + reflectionHeight + REFLECTION_GAP,
Config.ARGB_8888);
|
3,依次将源图、倒影图绘制在最终的bitmap上面。
1
2
3
4
5
6
7
8
|
// Prepare the canvas to draw stuff.
Canvas canvas =
new
Canvas(bitmapWithReflection);
// Draw the original bitmap.
canvas.drawBitmap(srcBitmap,
0
,
0
,
null
);
// Draw the reflection bitmap.
canvas.drawBitmap(reflectionBitmap,
0
, srcHeight + REFLECTION_GAP,
null
);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
Paint paint =
new
Paint();
paint.setAntiAlias(
true
);
LinearGradient shader =
new
LinearGradient(
0
,
srcHeight,
0
,
bitmapWithReflection.getHeight() + REFLECTION_GAP,
0x70FFFFFF
,
0x00FFFFFF
,
TileMode.MIRROR);
paint.setShader(shader);
paint.setXfermode(
new
PorterDuffXfermode(android.graphics.PorterDuff.Mode.DST_IN));
// Draw the linear shader.
canvas.drawRect(
0
,
srcHeight,
srcWidth,
bitmapWithReflection.getHeight() + REFLECTION_GAP,
paint);
|
二,扩展Gallery
扩展系统的gallery,我们需要重写两个方法,getChildStaticTransformation()和getChildDrawingOrder(),同时,要使这两个方法能被调用,必须执行如下两行代码,文档上面是有说明的。
1
2
3
4
|
// Enable set transformation.
this
.setStaticTransformationsEnabled(
true
);
// Enable set the children drawing order.
this
.setChildrenDrawingOrderEnabled(
true
);
|
- getChildDrawingOrder的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
@Override
protected
int
getChildDrawingOrder(
int
childCount,
int
i)
{
// Current selected index.
int
selectedIndex = getSelectedItemPosition() - getFirstVisiblePosition();
if
(selectedIndex <
0
)
{
return
i;
}
if
(i < selectedIndex)
{
return
i;
}
else
if
(i >= selectedIndex)
{
return
childCount -
1
- i + selectedIndex;
}
else
{
return
i;
}
}
|
这里为什么要计算drawing order,因为从上图中看到,我们的效果是:中间左边的顺序是 0, 1, 2,右边的child覆盖左边的child,而在中间右边的顺序正好相反,左边的覆盖右边的,所以我们要重写这个方法,而gallery自身的实现,不是这种效果。
- getChildStaticTransformation的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
|
@Override
protected
boolean
getChildStaticTransformation(View child, Transformation t)
{
super
.getChildStaticTransformation(child, t);
final
int
childCenter = getCenterOfView(child);
final
int
childWidth = child.getWidth();
int
rotationAngle =
0
;
t.clear();
t.setTransformationType(Transformation.TYPE_MATRIX);
// If the child is in the center, we do not rotate it.
if
(childCenter == mCoveflowCenter)
{
transformImageBitmap(child, t,
0
);
}
else
{
// Calculate the rotation angle.
rotationAngle = (
int
)(((
float
)(mCoveflowCenter - childCenter) / childWidth) * mMaxRotationAngle);
// Make the angle is not bigger than maximum.
if
(Math.abs(rotationAngle) > mMaxRotationAngle)
{
rotationAngle = (rotationAngle <
0
) ? -mMaxRotationAngle : mMaxRotationAngle;
}
transformImageBitmap(child, t, rotationAngle);
}
return
true
;
}
|
这个方法就是根据child来计算它的transformation(变换),我们需要去修改它里面的matrix,从而达到旋转的效果。根据位置和角度来计算的matrix的方法写在另外一个方法transformImageBitmap中实现。
- transformImageBitmap()的实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
private
void
transformImageBitmap(View child, Transformation t,
int
rotationAngle)
{
mCamera.save();
final
Matrix imageMatrix = t.getMatrix();
final
int
imageHeight = child.getHeight();
final
int
imageWidth = child.getWidth();
final
int
rotation = Math.abs(rotationAngle);
// Zoom on Z axis.
mCamera.translate(
0
,
0
, mMaxZoom);
if
(rotation < mMaxRotationAngle)
{
float
zoomAmount = (
float
)(mMaxZoom + rotation *
1
.5f);
mCamera.translate(
0
,
0
, zoomAmount);
}
// Rotate the camera on Y axis.
mCamera.rotateY(rotationAngle);
// Get the matrix from the camera, in fact, the matrix is S (scale) transformation.
mCamera.getMatrix(imageMatrix);
// The matrix final is T2 * S * T1, first translate the center point to (0, 0),
// then scale, and then translate the center point to its original point.
// T * S * T
// S * T1
imageMatrix.postTranslate((imageWidth /
2
), (imageHeight /
2
));
// (T2 * S) * T1
imageMatrix.preTranslate(-(imageWidth /
2
), -(imageHeight /
2
));
mCamera.restore();
}
|
这里,简单说明一个,
第一,先在Z轴上平称,其实就是得到一个缩放矩阵变换,我这里简写为 S。
第二,是利用camera这个类来生成matrix,其实mCamera.rotateY就是围绕Y轴旋转。这里生成了一个旋转矩阵,记为 R 。经过这两步,此时调用mCamera.getMatrix(imageMatrix); 从Camera中得到matrix,此时这个矩阵中包含了S * R。
第三,最关键是下面两句
1
2
3
4
|
// S * T1
imageMatrix.postTranslate((imageWidth /
2
), (imageHeight /
2
));
// (T2 * S) * T1
imageMatrix.preTranslate(-(imageWidth /
2
), -(imageHeight /
2
));
|
M = T * (S * R) * T1 (这里在T1表示与T相反)。
三,完整代码
GalleryFlow.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
|
import
android.content.Context;
import
android.graphics.Camera;
import
android.graphics.Matrix;
import
android.util.AttributeSet;
import
android.view.View;
import
android.view.animation.Transformation;
import
android.widget.Gallery;
public
class
GalleryFlow
extends
Gallery
{
/**
* The camera class is used to 3D transformation matrix.
*/
private
Camera mCamera =
new
Camera();
/**
* The max rotation angle.
*/
private
int
mMaxRotationAngle =
60
;
/**
* The max zoom value (Z axis).
*/
private
int
mMaxZoom = -
120
;
/**
* The center of the gallery.
*/
private
int
mCoveflowCenter =
0
;
public
GalleryFlow(Context context)
{
this
(context,
null
);
}
public
GalleryFlow(Context context, AttributeSet attrs)
{
this
(context, attrs,
0
);
}
public
GalleryFlow(Context context, AttributeSet attrs,
int
defStyle)
{
super
(context, attrs, defStyle);
// Enable set transformation.
this
.setStaticTransformationsEnabled(
true
);
// Enable set the children drawing order.
this
.setChildrenDrawingOrderEnabled(
true
);
}
public
int
getMaxRotationAngle()
{
return
mMaxRotationAngle;
}
public
void
setMaxRotationAngle(
int
maxRotationAngle)
{
mMaxRotationAngle = maxRotationAngle;
}
public
int
getMaxZoom()
{
return
mMaxZoom;
}
public
void
setMaxZoom(
int
maxZoom)
{
mMaxZoom = maxZoom;
}
@Override
protected
int
getChildDrawingOrder(
int
childCount,
int
i)
{
// Current selected index.
int
selectedIndex = getSelectedItemPosition() - getFirstVisiblePosition();
if
(selectedIndex <
0
)
{
return
i;
}
if
(i < selectedIndex)
{
return
i;
}
else
if
(i >= selectedIndex)
{
return
childCount -
1
- i + selectedIndex;
}
else
{
return
i;
}
}
@Override
protected
void
onSizeChanged(
int
w,
int
h,
int
oldw,
int
oldh)
{
mCoveflowCenter = getCenterOfCoverflow();
super
.onSizeChanged(w, h, oldw, oldh);
}
private
int
getCenterOfView(View view)
{
return
view.getLeft() + view.getWidth() /
2
;
}
@Override
protected
boolean
getChildStaticTransformation(View child, Transformation t)
{
super
.getChildStaticTransformation(child, t);
final
int
childCenter = getCenterOfView(child);
final
int
childWidth = child.getWidth();
int
rotationAngle =
0
;
t.clear();
t.setTransformationType(Transformation.TYPE_MATRIX);
// If the child is in the center, we do not rotate it.
if
(childCenter == mCoveflowCenter)
{
transformImageBitmap(child, t,
0
);
}
else
{
// Calculate the rotation angle.
rotationAngle = (
int
)(((
float
)(mCoveflowCenter - childCenter) / childWidth) * mMaxRotationAngle);
// Make the angle is not bigger than maximum.
if
(Math.abs(rotationAngle) > mMaxRotationAngle)
{
rotationAngle = (rotationAngle <
0
) ? -mMaxRotationAngle : mMaxRotationAngle;
}
transformImageBitmap(child, t, rotationAngle);
}
return
true
;
}
private
int
getCenterOfCoverflow()
{
return
(getWidth() - getPaddingLeft() - getPaddingRight()) /
2
+ getPaddingLeft();
}
private
void
transformImageBitmap(View child, Transformation t,
int
rotationAngle)
{
mCamera.save();
final
Matrix imageMatrix = t.getMatrix();
final
int
imageHeight = child.getHeight();
final
int
imageWidth = child.getWidth();
final
int
rotation = Math.abs(rotationAngle);
// Zoom on Z axis.
mCamera.translate(
0
,
0
, mMaxZoom);
if
(rotation < mMaxRotationAngle)
{
float
zoomAmount = (
float
)(mMaxZoom + rotation *
1
.5f);
mCamera.translate(
0
,
0
, zoomAmount);
}
// Rotate the camera on Y axis.
mCamera.rotateY(rotationAngle);
// Get the matrix from the camera, in fact, the matrix is S (scale) transformation.
mCamera.getMatrix(imageMatrix);
// The matrix final is T2 * S * T1, first translate the center point to (0, 0),
// then scale, and then translate the center point to its original point.
// T * S * T
// S * T1
imageMatrix.postTranslate((imageWidth /
2
), (imageHeight /
2
));
// (T2 * S) * T1
imageMatrix.preTranslate(-(imageWidth /
2
), -(imageHeight /
2
));
mCamera.restore();
}
}
|
BitmapUtil.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
|
package
com.lee.gallery3d.utils;
import
android.graphics.Bitmap;
import
android.graphics.Bitmap.Config;
import
android.graphics.Canvas;
import
android.graphics.LinearGradient;
import
android.graphics.Matrix;
import
android.graphics.Paint;
import
android.graphics.PixelFormat;
import
android.graphics.PorterDuffXfermode;
import
android.graphics.Shader.TileMode;
import
android.graphics.drawable.Drawable;
public
class
BitmapUtil
{
public
static
Bitmap createReflectedBitmap(Bitmap srcBitmap)
{
if
(
null
== srcBitmap)
{
return
null
;
}
// The gap between the reflection bitmap and original bitmap.
final
int
REFLECTION_GAP =
4
;
int
srcWidth = srcBitmap.getWidth();
int
srcHeight = srcBitmap.getHeight();
int
reflectionWidth = srcBitmap.getWidth();
int
reflectionHeight = srcBitmap.getHeight() /
2
;
if
(
0
== srcWidth || srcHeight ==
0
)
{
return
null
;
}
// The matrix
Matrix matrix =
new
Matrix();
matrix.preScale(
1
, -
1
);
try
{
// The reflection bitmap, width is same with original's, height is half of original's.
Bitmap reflectionBitmap = Bitmap.createBitmap(
srcBitmap,
0
,
srcHeight /
2
,
srcWidth,
srcHeight /
2
,
matrix,
false
);
if
(
null
== reflectionBitmap)
{
return
null
;
}
// Create the bitmap which contains original and reflection bitmap.
Bitmap bitmapWithReflection = Bitmap.createBitmap(
reflectionWidth,
srcHeight + reflectionHeight + REFLECTION_GAP,
Config.ARGB_8888);
if
(
null
== bitmapWithReflection)
{
return
null
;
}
// Prepare the canvas to draw stuff.
Canvas canvas =
new
Canvas(bitmapWithReflection);
// Draw the original bitmap.
canvas.drawBitmap(srcBitmap,
0
,
0
,
null
);
// Draw the reflection bitmap.
canvas.drawBitmap(reflectionBitmap,
0
, srcHeight + REFLECTION_GAP,
null
);
Paint paint =
new
Paint();
paint.setAntiAlias(
true
);
LinearGradient shader =
new
LinearGradient(
0
,
srcHeight,
0
,
bitmapWithReflection.getHeight() + REFLECTION_GAP,
0x70FFFFFF
,
0x00FFFFFF
,
TileMode.MIRROR);
paint.setShader(shader);
paint.setXfermode(
new
PorterDuffXfermode(android.graphics.PorterDuff.Mode.DST_IN));
// Draw the linear shader.
canvas.drawRect(
0
,
srcHeight,
srcWidth,
bitmapWithReflection.getHeight() + REFLECTION_GAP,
paint);
return
bitmapWithReflection;
}
catch
(Exception e)
{
e.printStackTrace();
}
return
null
;
}
}
|