[안드로이드 프로그래밍] 안드로이드 커버플로우 만들기

     




안드로이드에서 커버 플로우기능이란 아이폰에서 음악을 들을때 앨범들이 그림으로 나와서 손으로 밀면 다음 앨범 그림이 나오는 기능입니다. 현재 선택한 그림은 반듯하게 보이지만 주변 그림은 가운데를 기준으로 약간씩 기울어져 있는 느낌입니다.





넥서스 7에서 샘플 예제를 실행했을때 다음과 같이 실행이 됩니다.

가운데는 반듯하게 (제가보기엔 약간 기울어져 보이지만...) 보이고 가운데를 기준으로 왼쪽과 오른쪽 그림들은 가운데를 향하도록 기울어져 있습니다.






손가락으로 화면을 밀면 가운대 그림들이 바뀌면서 각도도 다 바뀝니다. 마치 그림을 3D로 표현한 것처럼 보여주는데 실제로는 3D가 아닙니다. 2D표현에서 카메라라는 클래스를 사용하여 마치 3D공간에 있는 것처럼 표현 할 수 있습니다.


카메라란 특정한 그림을 바라보는 시점정도라고 생각하면 될 것 같습니다. 





위 그림처럼 하나의 직사각형 물체를 정면에서 볼 때 (A카메라) 좌측과 우측에서 볼 때 (B,C카메라) 각각 사람 눈에 보여지는 모습이 바뀌게 됩니다. 이런식으로 카메라의 방향만 바꿔주어서 물체를 달리 보이게 하는 클래스가 카메라 클래스 입니다.


위에 예제 프로젝트 코드를 보면 다음과 같습니다.



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
package org.androidtown.ui.coverflow;
 
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;
import android.widget.ImageView;
 
/**
 * 갤러리를 상속하여 커버플로우 뷰 정의
 * 
 * @author Mike
 *
 */
public class CoverFlow extends Gallery {
 
    private Camera camera = new Camera();
 
    /**
     * 회전 각도
     */
    public static int maxRotationAngle = 55;
 
    /**
     * 최대 확대 수준
     */
    public static int maxZoom = -60;
 
    private int centerPoint;
 
    /**
     * 생성자
     *
     * @param context
     */
    public CoverFlow(Context context) {
        super(context);
 
        init();
    }
 
    /**
     * 생성자
     *
     * @param context
     * @param attrs
     */
    public CoverFlow(Context context, AttributeSet attrs) {
        super(context, attrs);
 
        init();
    }
 
    /**
     * 생성자
     *
     * @param context
     * @param attrs
     * @param defStyle
     */
    public CoverFlow(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
 
        init();
    }
 
    /**
     * 초기화
     */
    private void init() {
        this.setStaticTransformationsEnabled(true);
    }
 
 
    public int getMaxRotationAngle() {
        return maxRotationAngle;
    }
 
    public void setMaxRotationAngle(int rotationAngle) {
        maxRotationAngle = rotationAngle;
    }
 
    public int getMaxZoom() {
        return maxZoom;
    }
 
    public void setMaxZoom(int zoom) {
        maxZoom = zoom;
    }
 
    private int getCenterOfCoverflow() {
        return (getWidth() - getPaddingLeft() - getPaddingRight()) / 2 + getPaddingLeft();
    }
 
    private static int getCenterOfView(View view) {
        return view.getLeft() + view.getWidth() / 2;
    }
 
    protected boolean getChildStaticTransformation(View child, Transformation t) {
 
        final int childCenter = getCenterOfView(child);
        final int childWidth = child.getWidth() ;
        int rotationAngle = 0;
        t.clear();
        t.setTransformationType(Transformation.TYPE_MATRIX);
 
        if (childCenter == centerPoint) {
            transformImageBitmap((ImageView) child, t, 0);
        } else {
            rotationAngle = (int) (((float) (centerPoint - childCenter)/ childWidth) *  maxRotationAngle);
            if (Math.abs(rotationAngle) > maxRotationAngle) {
                rotationAngle = (rotationAngle < 0) ? -maxRotationAngle : maxRotationAngle;
            }
            transformImageBitmap((ImageView) child, t, rotationAngle);
        }
 
        return true;
 
    }
 
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        centerPoint = getCenterOfCoverflow();
        super.onSizeChanged(w, h, oldw, oldh);
    }
 
    private void transformImageBitmap(ImageView child, Transformation t, int rotationAngle) {
        camera.save();
 
        final Matrix imageMatrix = t.getMatrix();;
        final int imageHeight = child.getLayoutParams().height;;
        final int imageWidth = child.getLayoutParams().width;
        final int rotation = Math.abs(rotationAngle);
 
        camera.translate(0.0f, 0.0f, 100.0f);
 
        if ( rotation < maxRotationAngle ) {
            float zoomAmount = (float) (maxZoom +  (rotation * 1.5));
            camera.translate(0.0f, 0.0f, zoomAmount);
        }
 
        camera.rotateY(rotationAngle);
        camera.getMatrix(imageMatrix);
 
        imageMatrix.preTranslate(-(imageWidth/2), -(imageHeight/2));
        imageMatrix.postTranslate((imageWidth/2), (imageHeight/2));
 
        camera.restore();
 
    }
 
}
 



전체적으로 상당히 복잡하지만 다 알필요는 없고 사용하는 부분만 설명하도록 하겠습니다. 일단 가장 중요한 부분은 102번째 줄에 getChildStaticTransformation() 메소드 입니다. 이 부분이 여러개의 자식 뷰들의 형태를 변환(transform)해주는 코드입니다. 이 메소드를 사용하기 위해서는 setStaticTransformationsEnabled()메소드를 true로 설정해줘야 합니다. 때문에 init() 메소드를 보면 setStaticTransformationsEnabled(true)라는 코드를 볼 수 있습니다.


getChildStaticTransformation() 메소드의 인자값을 보면 뷰 객체와 Transformation이라는 객체를 볼 수 있습니다. Transformation 객체는 뷰 객체를 Matrix형태로 받아들여서 실질적인 이미지 변환을 할 때 Matrix(행렬) 상태로 변환을 하게 됩니다. 


실질적으로 그림을 회전시키는 메소드는 transformImageBitmap()입니다. 이 메소드는 뷰와 Trnsformation, 회전시킬 각도를 인자값으로 받아들여서 카메라 클래스의 메소드인 translate와 rotate를 사용하여 뷰의 각도를 변화시키게 됩니다. 이 값들을 조정하면 다양한 각도에서 보이는 뷰 객체를 만들 수 있습니다.


카메라의 메소드인 save()와 restore()의 경우 그래픽 변환 작업을 할때 순차적으로 작업이 이루어지도록 스택구조를 사용함에 있어 스택에 넣을때는 save(), 뺄때는 restore()를 사용합니다. 작업처리가 엉키지 않도록 한다고 생각하시면 됩니다.


그 밖의 카메라의 메소드는 다음과 같은 것들이 있습니다.


setLocation(), translate(), rotate(), rotateX(), rotateY(), rotateZ()



SampleCoverFlow.zip




소스 출처 - do it! 안드로이드 앱 프로그래밍




반응형

댓글

Designed by JB FACTORY