본문 바로가기

IT/Android

안드로이드 CoverFlow 예제 및 설명


Android Coverflow Widget V2


It's been a while now and I've been pretty busy with life, work, and other things, but at last here's a new version of the coverflow widget that I released a while back. There's not really much new functionality here, but I have cut down the amount of code needed by a lot. Gone are the CoverAbsSpinner and the CoverAdapterView classes and now we are left with just the CoverFlow class weighing in at less than 200 lines of code. Of course, you still have to instantiate it and populate it using an ImageAdapter so I'll also include the activity class for doing those tasks as well.


Background
The basic idea is that the Coverflow widget works very much like the standard Android Gallery widget, but with the addition of the rotation of the images. For more background information on the Coverflow widget see my original post here

How to use
In your Android project create a package called com.example.coverflow, and in this place the CoverFlow class. Now all you need is an activity to instantiate the Coverflow widget in this example the activity class is called CoverflowExampleCoverFlowExample simply extends Activity and instantiates a Coverflow class in it's onCreate method. It also has an internal Class, ImageAdapter which we use as the adapter for the Coverflow widget, much as we would for a Gallery widget. It is also possible to use the Coverflow widget when it is specified in an XML layout file.


The CoverFlow and CoverflowExample classes are here:


001./*
002. * Copyright (C) 2010 Neil Davies
003. *
004. * Licensed under the Apache License, Version 2.0 (the "License");
005. * you may not use this file except in compliance with the License.
006. * You may obtain a copy of the License at
007. *
009. *
010. * Unless required by applicable law or agreed to in writing, software
011. * distributed under the License is distributed on an "AS IS" BASIS,
012. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013. * See the License for the specific language governing permissions and
014. * limitations under the License.
015. 
016. * This code is base on the Android Gallery widget and was Created 
017. * by Neil Davies neild001 'at' gmail dot com to be a Coverflow widget
018. 
019. * @author Neil Davies
020. */
021.package com.example.coverflow;
022.  
023.  
024.import android.content.Context;
025.import android.graphics.Camera;
026.import android.graphics.Matrix;
027.import android.util.AttributeSet;
028.import android.view.View;
029.import android.view.animation.Transformation;
030.import android.widget.Gallery;
031.import android.widget.ImageView;
032.  
033.public class CoverFlow extends Gallery {
034.  
035.    /**
036.     * Graphics Camera used for transforming the matrix of ImageViews
037.     */
038.    private Camera mCamera = new Camera();
039.  
040.    /**
041.     * The maximum angle the Child ImageView will be rotated by
042.     */    
043.    private int mMaxRotationAngle = 60;
044.      
045.    /**
046.     * The maximum zoom on the centre Child
047.     */
048.    private int mMaxZoom = -120;
049.      
050.    /**
051.     * The Centre of the Coverflow 
052.     */   
053.    private int mCoveflowCenter;
054.     
055. public CoverFlow(Context context) {
056.  super(context);
057.  this.setStaticTransformationsEnabled(true);
058. }
059.  
060. public CoverFlow(Context context, AttributeSet attrs) {
061.  super(context, attrs);
062.        this.setStaticTransformationsEnabled(true);
063. }
064.   
065.  public CoverFlow(Context context, AttributeSet attrs, int defStyle) {
066.   super(context, attrs, defStyle);
067.   this.setStaticTransformationsEnabled(true);   
068.  }
069.    
070.    /**
071.     * Get the max rotational angle of the image
072.  * @return the mMaxRotationAngle
073.  */
074. public int getMaxRotationAngle() {
075.  return mMaxRotationAngle;
076. }
077.  
078. /**
079.  * Set the max rotational angle of each image
080.  * @param maxRotationAngle the mMaxRotationAngle to set
081.  */
082. public void setMaxRotationAngle(int maxRotationAngle) {
083.  mMaxRotationAngle = maxRotationAngle;
084. }
085.  
086. /**
087.  * Get the Max zoom of the centre image
088.  * @return the mMaxZoom
089.  */
090. public int getMaxZoom() {
091.  return mMaxZoom;
092. }
093.  
094. /**
095.  * Set the max zoom of the centre image
096.  * @param maxZoom the mMaxZoom to set
097.  */
098. public void setMaxZoom(int maxZoom) {
099.  mMaxZoom = maxZoom;
100. }
101.  
102. /**
103.     * Get the Centre of the Coverflow
104.     * @return The centre of this Coverflow.
105.     */
106.    private int getCenterOfCoverflow() {
107.        return (getWidth() - getPaddingLeft() - getPaddingRight()) / 2 + getPaddingLeft();
108.    }
109.      
110.    /**
111.     * Get the Centre of the View
112.     * @return The centre of the given view.
113.     */
114.    private static int getCenterOfView(View view) {
115.        return view.getLeft() + view.getWidth() / 2;
116.    }  
117.    /**
118.  * {@inheritDoc}
119.  *
120.  * @see #setStaticTransformationsEnabled(boolean) 
121.  */ 
122.    protected boolean getChildStaticTransformation(View child, Transformation t) {
123.    
124.  final int childCenter = getCenterOfView(child);
125.  final int childWidth = child.getWidth() ;
126.  int rotationAngle = 0;
127.    
128.  t.clear();
129.  t.setTransformationType(Transformation.TYPE_MATRIX);
130.    
131.        if (childCenter == mCoveflowCenter) {
132.            transformImageBitmap((ImageView) child, t, 0);
133.        } else {      
134.            rotationAngle = (int) (((float) (mCoveflowCenter - childCenter)/ childWidth) *  mMaxRotationAngle);
135.            if (Math.abs(rotationAngle) > mMaxRotationAngle) {
136.             rotationAngle = (rotationAngle < 0) ? -mMaxRotationAngle : mMaxRotationAngle;   
137.            }
138.            transformImageBitmap((ImageView) child, t, rotationAngle);         
139.        }    
140.               
141.  return true;
142. }
143.  
144. /**
145.  * This is called during layout when the size of this view has changed. If
146.  * you were just added to the view hierarchy, you're called with the old
147.  * values of 0.
148.  *
149.  * @param w Current width of this view.
150.  * @param h Current height of this view.
151.  * @param oldw Old width of this view.
152.  * @param oldh Old height of this view.
153.     */
154.     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
155.      mCoveflowCenter = getCenterOfCoverflow();
156.      super.onSizeChanged(w, h, oldw, oldh);
157.     }
158.    
159.     /**
160.      * Transform the Image Bitmap by the Angle passed 
161.      
162.      * @param imageView ImageView the ImageView whose bitmap we want to rotate
163.      * @param t transformation 
164.      * @param rotationAngle the Angle by which to rotate the Bitmap
165.      */
166.     private void transformImageBitmap(ImageView child, Transformation t, int rotationAngle) {            
167.      mCamera.save();
168.      final Matrix imageMatrix = t.getMatrix();;
169.      final int imageHeight = child.getLayoutParams().height;;
170.      final int imageWidth = child.getLayoutParams().width;
171.      final int rotation = Math.abs(rotationAngle);
172.                       
173.      mCamera.translate(0.0f, 0.0f, 100.0f);
174.           
175.      //As the angle of the view gets less, zoom in     
176.      if ( rotation < mMaxRotationAngle ) {
177.       float zoomAmount = (float) (mMaxZoom +  (rotation * 1.5));
178.       mCamera.translate(0.0f, 0.0f, zoomAmount);          
179.      
180.        
181.      mCamera.rotateY(rotationAngle);
182.      mCamera.getMatrix(imageMatrix);               
183.      imageMatrix.preTranslate(-(imageWidth/2), -(imageHeight/2)); 
184.      imageMatrix.postTranslate((imageWidth/2), (imageHeight/2));
185.      mCamera.restore();
186. }
187.}
001./*
002. * Copyright (C) 2010 Neil Davies
003. *
004. * Licensed under the Apache License, Version 2.0 (the "License");
005. * you may not use this file except in compliance with the License.
006. * You may obtain a copy of the License at
007. *
009. *
010. * Unless required by applicable law or agreed to in writing, software
011. * distributed under the License is distributed on an "AS IS" BASIS,
012. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013. * See the License for the specific language governing permissions and
014. * limitations under the License.
015. 
016. * This code is base on the Android Gallery widget and was Created 
017. * by Neil Davies neild001 'at' gmail dot com to be a Coverflow widget
018. 
019. * @author Neil Davies
020. */
021.package com.example.coverflow;
022.  
023.import java.io.FileInputStream;
024.  
025.import android.app.Activity;
026.import android.content.Context;
027.import android.graphics.Bitmap;
028.import android.graphics.BitmapFactory;
029.import android.graphics.Canvas;
030.import android.graphics.LinearGradient;
031.import android.graphics.Matrix;
032.import android.graphics.Paint;
033.import android.graphics.PorterDuffXfermode;
034.import android.graphics.Bitmap.Config;
035.import android.graphics.PorterDuff.Mode;
036.import android.graphics.Shader.TileMode;
037.import android.graphics.drawable.BitmapDrawable;
038.import android.os.Bundle;
039.import android.view.View;
040.import android.view.ViewGroup;
041.import android.widget.BaseAdapter;
042.import android.widget.ImageView;
043.import android.widget.ImageView.ScaleType;
044.  
045.public class CoverFlowExample extends Activity {
046.    /** Called when the activity is first created. */
047.    @Override
048.    public void onCreate(Bundle savedInstanceState) {
049.     super.onCreate(savedInstanceState);
050.       
051.     CoverFlow coverFlow;
052.     coverFlow = new CoverFlow(this);
053.       
054.     coverFlow.setAdapter(new ImageAdapter(this));
055.  
056.     ImageAdapter coverImageAdapter =  new ImageAdapter(this);
057.       
058.     //coverImageAdapter.createReflectedImages();
059.       
060.     coverFlow.setAdapter(coverImageAdapter);
061.       
062.     coverFlow.setSpacing(-25);
063.     coverFlow.setSelection(4, true);
064.     coverFlow.setAnimationDuration(1000);
065.       
066.       
067.     setContentView(coverFlow);
068.    }
069.      
070. public class ImageAdapter extends BaseAdapter {
071.     int mGalleryItemBackground;
072.     private Context mContext;
073.  
074.     private FileInputStream fis;
075.          
076.     private Integer[] mImageIds = {
077.       R.drawable.kasabian_kasabian,
078.             R.drawable.starssailor_silence_is_easy,
079.             R.drawable.killers_day_and_age,
080.             R.drawable.garbage_bleed_like_me,
081.             R.drawable.death_cub_for_cutie_the_photo_album,
082.             R.drawable.kasabian_kasabian,
083.             R.drawable.massive_attack_collected,
084.             R.drawable.muse_the_resistance,
085.             R.drawable.starssailor_silence_is_easy
086.     };
087.  
088.     private ImageView[] mImages;
089.       
090.     public ImageAdapter(Context c) {
091.      mContext = c;
092.      mImages = new ImageView[mImageIds.length];
093.     }
094.  public boolean createReflectedImages() {
095.          //The gap we want between the reflection and the original image
096.          final int reflectionGap = 4;
097.            
098.            
099.          int index = 0;
100.          for (int imageId : mImageIds) {
101.        Bitmap originalImage = BitmapFactory.decodeResource(getResources(), 
102.          imageId);
103.           int width = originalImage.getWidth();
104.           int height = originalImage.getHeight();
105.             
106.       
107.           //This will not scale but will flip on the Y axis
108.           Matrix matrix = new Matrix();
109.           matrix.preScale(1, -1);
110.             
111.           //Create a Bitmap with the flip matrix applied to it.
112.           //We only want the bottom half of the image
113.           Bitmap reflectionImage = Bitmap.createBitmap(originalImage, 0, height/2, width, height/2, matrix, false);
114.             
115.                 
116.           //Create a new bitmap with same width but taller to fit reflection
117.           Bitmap bitmapWithReflection = Bitmap.createBitmap(width 
118.             , (height + height/2), Config.ARGB_8888);
119.           
120.          //Create a new Canvas with the bitmap that's big enough for
121.          //the image plus gap plus reflection
122.          Canvas canvas = new Canvas(bitmapWithReflection);
123.          //Draw in the original image
124.          canvas.drawBitmap(originalImage, 0, 0, null);
125.          //Draw in the gap
126.          Paint deafaultPaint = new Paint();
127.          canvas.drawRect(0, height, width, height + reflectionGap, deafaultPaint);
128.          //Draw in the reflection
129.          canvas.drawBitmap(reflectionImage,0, height + reflectionGap, null);
130.            
131.          //Create a shader that is a linear gradient that covers the reflection
132.          Paint paint = new Paint(); 
133.          LinearGradient shader = new LinearGradient(0, originalImage.getHeight(), 0, 
134.            bitmapWithReflection.getHeight() + reflectionGap, 0x70ffffff, 0x00ffffff, 
135.            TileMode.CLAMP); 
136.          //Set the paint to use this shader (linear gradient)
137.          paint.setShader(shader); 
138.          //Set the Transfer mode to be porter duff and destination in
139.          paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN)); 
140.          //Draw a rectangle using the paint with our linear gradient
141.          canvas.drawRect(0, height, width, 
142.            bitmapWithReflection.getHeight() + reflectionGap, paint); 
143.            
144.          ImageView imageView = new ImageView(mContext);
145.          imageView.setImageBitmap(bitmapWithReflection);
146.          imageView.setLayoutParams(new CoverFlow.LayoutParams(120, 180));
147.          imageView.setScaleType(ScaleType.MATRIX);
148.          mImages[index++] = imageView;
149.            
150.          }
151.       return true;
152.  }
153.  
154.     public int getCount() {
155.         return mImageIds.length;
156.     }
157.  
158.     public Object getItem(int position) {
159.         return position;
160.     }
161.  
162.     public long getItemId(int position) {
163.         return position;
164.     }
165.  
166.     public View getView(int position, View convertView, ViewGroup parent) {
167.  
168.      //Use this code if you want to load from resources
169.         ImageView i = new ImageView(mContext);
170.         i.setImageResource(mImageIds[position]);
171.         i.setLayoutParams(new CoverFlow.LayoutParams(130, 130));
172.         i.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 
173.           
174.         //Make sure we set anti-aliasing otherwise we get jaggies
175.         BitmapDrawable drawable = (BitmapDrawable) i.getDrawable();
176.         drawable.setAntiAlias(true);
177.         return i;
178.        
179.      //return mImages[position];
180.     }
181.   /** Returns the size (0.0f to 1.0f) of the views 
182.      * depending on the 'offset' to the center. */ 
183.      public float getScale(boolean focused, int offset) { 
184.        /* Formula: 1 / (2 ^ offset) */ 
185.          return Math.max(0, 1.0f / (float)Math.pow(2, Math.abs(offset))); 
186.      
187.  
188. }
189.}

Code explanation
The CoverflowExample Code contains some extra code for creating reflections of the images, but apart from this is a standard activity class of the kind used with the Gallery widget.

The CoverFlow class extends the Gallery widget , but now we override a few methods to allow us to transform the images before they are displayed. The most important method that we override is the getChildStaticTransformation method. Here I have to say thank you to a user called Nerdrow on the Android developers group who first showed me an example of overriding this method.  By setting setStaticTransformationsEnabled in the constructors, we are telling the parent ViewGroup of the Coverflow class to invoke getChildStaticTransformation every time one of our images in drawn. In getChildStaticTransformation we simply calculate and apply a rotation and scale to the ImageView depending on it's position relative to the centre of the Coverflow widget. We also override the onSizeChanged method. This method is called every time the widget changes size e.g. when we change the orientation of the phone from portrait to landscape. In the onSizeChanged method we just get the centre position of the coverflow widget. There are also a few parameters that we can set, mZoomMax controls the maximum zoom of the central image and mMaxRotationAngle sets the maximum rotation angle of each image.


Thanks
Lastly, I just wanted to say thanks to everyone who has commented and given me feedback on the coverflow widget. It's be really positive and great to hear that people have got it working successfully on a range of devices such as the HTC magic , Motorolla droid and Nexus 1. There's still more to do for the coverflow widget and I'll release new version as and when I make updates. If anyone has anymore suggests for cool widgets or enhancements to this Coverflow widget then please do get in touch.