Android programming implementation of the super cool picture browser

  • 2020-11-18 06:27:07
  • OfStack

This article is an example of Android programming implementation of the super cool picture browser. To share for your reference, the details are as follows:

Anyone who has used Android's built-in gallery component knows that gallery drags through a set of images, which is a poor comparison to coverflow, which also drags through images. In fact, gallery can be extended and the effect of coverflow can be basically achieved through the pseudo-3ES10en transformation. This article through the source code analysis of the implementation of this 1 function. The concrete code function may refer to the comment.

The final effect is as follows:

To use gallery, we must first assign it an adapter. Here, we implemented a custom ImageAdapter to create a reflection effect for the image.

The parameters passed in are context and the ID array of images in drawable. The createReflectedImages() method is then called to create a reflection effect for each image, generating the corresponding ImageView array, which is returned in getView().


Copyright (C) 2010 Neil Davies 
* 
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
* 
* [url]http://www.apache.org/licenses/LICENSE-2.0[/url] 
* 
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
* 
* This code is base on the Android Gallery widget and was Created 
* by Neil Davies neild001 'at' gmail dot com to be a Coverflow widget 
* 
* @author Neil Davies 
*/ 
public class ImageAdapter extends BaseAdapter { 
int mGalleryItemBackground; 
private Context mContext; 
private Integer[] mImageIds ; 
private ImageView[] mImages; 
public ImageAdapter(Context c, int[] ImageIds) { 
mContext = c; 
mImageIds = ImageIds; 
mImages = new ImageView[mImageIds.length]; 
} 
public boolean createReflectedImages() { 
// The gap we want between the reflection and the original image 
final int reflectionGap = 4; 
int index = 0; 
for (int imageId : mImageIds) { 
Bitmap originalImage = BitmapFactory.decodeResource( 
mContext.getResources(), imageId); 
int width = originalImage.getWidth(); 
int height = originalImage.getHeight(); 
// This will not scale but will flip on the Y axis 
Matrix matrix = new Matrix(); 
matrix.preScale(1, -1); 
// Create a Bitmap with the flip matrix applied to it. 
// We only want the bottom half of the image 
Bitmap reflectionImage = Bitmap.createBitmap(originalImage, 0, 
height / 2, width, height / 2, matrix, false); 
// Create a new bitmap with same width but taller to fit 
// reflection 
Bitmap bitmapWithReflection = Bitmap.createBitmap(width, 
(height + height / 2), Config.ARGB_8888); 
// Create a new Canvas with the bitmap that's big enough for 
// the image plus gap plus reflection 
Canvas canvas = new Canvas(bitmapWithReflection); 
// Draw in the original image 
canvas.drawBitmap(originalImage, 0, 0, null); 
// Draw in the gap 
Paint deafaultPaint = new Paint(); 
canvas.drawRect(0, height, width, height + reflectionGap, 
deafaultPaint); 
// Draw in the reflection 
canvas.drawBitmap(reflectionImage, 0, height + reflectionGap, 
null); 
// Create a shader that is a linear gradient that covers the 
// reflection 
Paint paint = new Paint(); 
LinearGradient shader = new LinearGradient(0, 
originalImage.getHeight(), 0, 
bitmapWithReflection.getHeight() + reflectionGap, 
0x70ffffff, 0x00ffffff, TileMode.CLAMP); 
// Set the paint to use this shader (linear gradient) 
paint.setShader(shader); 
// Set the Transfer mode to be porter duff and destination in 
paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN)); 
// Draw a rectangle using the paint with our linear gradient 
canvas.drawRect(0, height, width, 
bitmapWithReflection.getHeight() + reflectionGap, paint); 
ImageView imageView = new ImageView(mContext); 
imageView.setImageBitmap(bitmapWithReflection); 
imageView 
.setLayoutParams(new GalleryFlow.LayoutParams(160, 240)); 
// imageView.setScaleType(ScaleType.MATRIX); 
mImages[index++] = imageView; 
} 
return true; 
} 
public int getCount() { 
return mImageIds.length; 
} 
public Object getItem(int position) { 
return position; 
} 
public long getItemId(int position) { 
return position; 
} 
public View getView(int position, View convertView, ViewGroup parent) { 
// Use this code if you want to load from resources 
/* 
* ImageView i = new ImageView(mContext); 
* i.setImageResource(mImageIds[position]); i.setLayoutParams(new 
* CoverFlow.LayoutParams(350,350)); 
* i.setScaleType(ImageView.ScaleType.CENTER_INSIDE); 
* 
* //Make sure we set anti-aliasing otherwise we get jaggies 
* BitmapDrawable drawable = (BitmapDrawable) i.getDrawable(); 
* drawable.setAntiAlias(true); return i; 
*/ 
return mImages[position]; 
} 
/** 
* Returns the size (0.0f to 1.0f) of the views depending on the 
* 'offset' to the center. 
*/ 
public float getScale(boolean focused, int offset) { 
/* Formula: 1 / (2 ^ offset) */ 
returnMath.max(0,1.0f / (float) Math.pow(2, Math.abs(offset))); 
} 
} 
}

Just reflecting the image is not enough, as the coverflow image toggle has rotation and zooming effects, while the native gallery does not. Therefore, we extended the built-in gallery to implement our own galleryflow. In the original gallery class, a method, getChildStaticTransformation(), is provided to transform images. By overriding this method and calling the custom transformImageBitmap(" Distance between each image and the center of gallery ") method, we can achieve the corresponding rotation and scaling of each image. camera and matrix are used for view transformation. Refer to code comments for details.


public class GalleryFlow extendsGallery { 
  /** 
   * Graphics Camera used for transforming the matrix of ImageViews 
   */ 
  privateCamera mCamera = newCamera(); 
  /** 
   * The maximum angle the Child ImageView will be rotated by 
   */ 
  privateint mMaxRotationAngle =60; 
  /** 
   * The maximum zoom on the centre Child 
   */ 
  privateint mMaxZoom = -120; 
  /** 
   * The Centre of the Coverflow 
   */ 
  privateint mCoveflowCenter; 
  publicGalleryFlow(Context context) { 
    super(context); 
    this.setStaticTransformationsEnabled(true); 
  } 
  publicGalleryFlow(Context context, AttributeSet attrs) { 
    super(context, attrs); 
    this.setStaticTransformationsEnabled(true); 
  } 
  publicGalleryFlow(Context context, AttributeSet attrs, int defStyle) { 
    super(context, attrs, defStyle); 
    this.setStaticTransformationsEnabled(true); 
  } 
  /** 
   * Get the max rotational angle of the image 
   * 
   * @return the mMaxRotationAngle 
   */ 
  publicint getMaxRotationAngle() { 
    returnmMaxRotationAngle; 
  } 
  /** 
   * Set the max rotational angle of each image 
   * 
   * @param maxRotationAngle 
   *   the mMaxRotationAngle to set 
   */ 
  publicvoid setMaxRotationAngle(intmaxRotationAngle) { 
    mMaxRotationAngle = maxRotationAngle; 
  } 
  /** 
   * Get the Max zoom of the centre image 
   * 
   * @return the mMaxZoom 
   */ 
  publicint getMaxZoom() { 
    returnmMaxZoom; 
  } 
  /** 
   * Set the max zoom of the centre image 
   * 
   * @param maxZoom 
   *   the mMaxZoom to set 
   */ 
  publicvoid setMaxZoom(intmaxZoom) { 
    mMaxZoom = maxZoom; 
  } 
  /** 
   * Get the Centre of the Coverflow 
   * 
   * @return The centre of this Coverflow. 
   */ 
  privateint getCenterOfCoverflow() { 
    return(getWidth() - getPaddingLeft() - getPaddingRight()) / 2 
        + getPaddingLeft(); 
  } 
  /** 
   * Get the Centre of the View 
   * 
   * @return The centre of the given view. 
   */ 
  privatestatic int getCenterOfView(View view) { 
    returnview.getLeft() + view.getWidth() / 2; 
  } 
  /** 
   * {@inheritDoc} 
   * 
   * @see #setStaticTransformationsEnabled(boolean) 
   */ 
  protectedboolean getChildStaticTransformation(View child, Transformation t) { 
    finalint childCenter = getCenterOfView(child); 
    finalint childWidth = child.getWidth(); 
    introtationAngle = 0; 
    t.clear(); 
    t.setTransformationType(Transformation.TYPE_MATRIX); 
    if(childCenter == mCoveflowCenter) { 
      transformImageBitmap((ImageView) child, t,0); 
    }else { 
      rotationAngle = (int) (((float) (mCoveflowCenter - childCenter) / childWidth) * mMaxRotationAngle); 
      if(Math.abs(rotationAngle) > mMaxRotationAngle) { 
        rotationAngle = (rotationAngle <0) ? -mMaxRotationAngle 
            : mMaxRotationAngle; 
      } 
      transformImageBitmap((ImageView) child, t, rotationAngle); 
    } 
    returntrue; 
  } 
  /** 
   * This is called during layout when the size of this view has changed. If 
   * you were just added to the view hierarchy, you're called with the old 
   * values of 0. 
   * 
   * @param w 
   *   Current width of this view. 
   * @param h 
   *   Current height of this view. 
   * @param oldw 
   *   Old width of this view. 
   * @param oldh 
   *   Old height of this view. 
   */ 
  protectedvoid onSizeChanged(intw, int h, int oldw, int oldh) { 
    mCoveflowCenter = getCenterOfCoverflow(); 
    super.onSizeChanged(w, h, oldw, oldh); 
  } 
  /** 
   * Transform the Image Bitmap by the Angle passed 
   * 
   * @param imageView 
   *   ImageView the ImageView whose bitmap we want to rotate 
   * @param t 
   *   transformation 
   * @param rotationAngle 
   *   the Angle by which to rotate the Bitmap 
   */ 
  privatevoid transformImageBitmap(ImageView child, Transformation t, 
      introtationAngle) { 
    mCamera.save(); 
    finalMatrix imageMatrix = t.getMatrix(); 
    finalint imageHeight = child.getLayoutParams().height; 
    finalint imageWidth = child.getLayoutParams().width; 
    finalint rotation = Math.abs(rotationAngle); 
    //  in Z It's moving forward camera The actual effect is to zoom in on the image.  
    //  If the Y When moving on the axis, the picture moves up and down; X Move the image left and right along the axis.  
    mCamera.translate(0.0f,0.0f, 100.0f); 
    // As the angle of the view gets less, zoom in 
    if(rotation < mMaxRotationAngle) { 
      floatzoomAmount = (float) (mMaxZoom + (rotation *1.5)); 
      mCamera.translate(0.0f,0.0f, zoomAmount); 
    } 
    //  in Y Rotate on the axis, and invert the corresponding picture vertically.  
    //  If the X Rotate on the axis and the corresponding picture will be flipped laterally inward.  
    mCamera.rotateY(rotationAngle); 
    mCamera.getMatrix(imageMatrix); 
    imageMatrix.preTranslate(-(imageWidth /2), -(imageHeight /2)); 
    imageMatrix.postTranslate((imageWidth /2), (imageHeight /2)); 
    mCamera.restore(); 
  } 
}

That's the end of the code. If you are interested, you can adjust the parameters to achieve more dazzling effects.
Here is an example of the call:


public void onCreate(Bundle savedInstanceState) { 
    super.onCreate(savedInstanceState); 
    setContentView(R.layout.layout_gallery); 
    Integer[] images = { R.drawable.img0001, R.drawable.img0030, 
      R.drawable.img0100, R.drawable.img0130, R.drawable.img0200, 
      R.drawable.img0230, R.drawable.img0300, R.drawable.img0330, 
      R.drawable.img0354 }; 
    ImageAdapter adapter =new ImageAdapter(this, images); 
    adapter.createReflectedImages(); 
    GalleryFlow galleryFlow = (GalleryFlow) findViewById(R.id.gallery_flow); 
    galleryFlow.setAdapter(adapter); 
}

PS1:

As you can see, the gallery implemented in this way is a serious problem. You can use the following code in createReflectedImages() :


BitmapDrawable bd = new BitmapDrawable(bitmapWithReflection);
bd.setAntiAlias(true);

Then use iv. setImageDrawable (bd);
Instead of iv. setImageBitmap (bitmapWithReflection);
Can basically eliminate the sawtooth.

PS2:

ImageAdapter to be determined MemoryLeak problem, it looks like Bitmap's decode method will cause ML, OOM will appear when using ImageAdapter after rotating the screen several times. Some improvements can be made by calling the recycle() method from the finished bimap and setting up null and calling system.gc () in a timely manner, but the problem is not obvious.
Celebrate highlights and recommendations, add 3

PS3 ON PS1:

Why is it not obvious after turning on the antialiasing? The answer is that you can't eliminate a sawtooth completely, but you can make a big difference when you turn anti-aliasing on.
Also on why android doesn't turn on serrations by default, here's my 1 thought:
Interpolation is what I now know as an anti-aliasing algorithm, which calculates the relevancy between pixels and inserts intermediate pixels between them to smooth out the edges of the image. But it certainly takes a lot of computation.
Although I haven't tested it, My guess is that graphics performance will decline by at least 30% with antialias.
Of course, there's no complex graphics involved here, so turning on anti-aliasing won't have a noticeable performance impact, but if you test it in an emulator or on a low-end model, you'll see a point.

PS4:

Someone asked what these two sentences in transformImageBitmap() meant:


imageMatrix.preTranslate(-(imageWidth / 2), -(imageHeight / 2));
imageMatrix.postTranslate((imageWidth / 2), (imageHeight / 2));

My understanding is as follows:

preTranslate is equivalent to performing preTranslate before any matrix transformation of the image. postTranslate is the opposite, performing postTranlate after all transformations.
Before any transformation is performed, the entire image is moved from the center of the image to the origin ((0,0)), and after the transformation is completed, the image is moved from the origin to the previous center.
If you don't add these two sentences, any transformation is going to be centered at the origin of the image, and if you add them, any transformation is going to be centered at the center of the image.
For example, to rotate an image, two parameters are required: one is the Angle of rotation, and the other is the coordinate of the center of rotation. The coordinates of the center of rotation affect the effect of the rotation. Does that make sense? It is not the same thing to take a stick and spin it with the end of the stick and spin it with the middle of the stick. After the execution of preTranslate and postTranslate, the image itself will not be affected, but the rotation axis when the image is transformed.
It's a little convoluted to say all this, but it's just matrix transformations.

PS5 ON PS2:

This problem has been well discussed under google group and seems to exist only in debug mode. Now I have no problems with OOM using this code

I hope this article has been helpful in Android programming.


Related articles: