Created
January 19, 2012 16:04
-
-
Save tleach/1640793 to your computer and use it in GitHub Desktop.
An Android Layout which can be used to apply a "reflection" effect to all child Views it contains.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import android.content.Context; | |
import android.graphics.Bitmap; | |
import android.graphics.Canvas; | |
import android.graphics.LinearGradient; | |
import android.graphics.Matrix; | |
import android.graphics.Paint; | |
import android.graphics.PorterDuff.Mode; | |
import android.graphics.PorterDuffXfermode; | |
import android.graphics.Shader.TileMode; | |
import android.util.AttributeSet; | |
import android.widget.LinearLayout; | |
/** | |
* A general purpose Layout which simply renders a reflection of its contained | |
* child Views in the remaining space below them within the bounds of this control. | |
* For {@link ReflectingLayout} to work properly, it must be setup to provide | |
* sufficient empty space below its children. | |
* | |
* Copyright Tom Leach | |
*/ | |
public class ReflectingLayout extends LinearLayout { | |
/** The maximum ratio of the height of the reflection to the source image. */ | |
private static final float MAX_REFLECTION_RATIO = 0.9F; | |
/** The {@link Paint} object we'll use to create the reflection. */ | |
private Paint paint; | |
private Matrix vFlipMatrix; | |
/** | |
* Instantiates a new reflecting layout. | |
* | |
* @param context the context | |
* @param attrs the attrs | |
*/ | |
public ReflectingLayout(Context context, AttributeSet attrs) { | |
super(context, attrs); | |
init(); | |
} | |
/** | |
* Instantiates a new reflecting layout. | |
* | |
* @param context the context | |
*/ | |
public ReflectingLayout(Context context) { | |
super(context); | |
init(); | |
} | |
/** | |
* Initialises the layout. | |
*/ | |
private void init() { | |
// Ensures that we redraw when our children are redrawn. | |
setAddStatesFromChildren(true); | |
// Important to ensure onDraw gets called. | |
setWillNotDraw(false); | |
setDrawingCacheEnabled(true); | |
// Create the paint object which we'll use to create the reflection gradient | |
paint = new Paint(); | |
paint.setXfermode(new PorterDuffXfermode(Mode.DST_IN)); | |
// Create a matrix which can flip images vertically | |
vFlipMatrix = new Matrix(); | |
vFlipMatrix.preScale(1, -1); | |
} | |
/** | |
* {@inheritDoc} | |
* @see android.view.View#onDraw(android.graphics.Canvas) | |
*/ | |
@Override | |
protected void onDraw(Canvas canvas) { | |
super.onDraw(canvas); | |
// Only actually do anything if there is space to actually draw a reflection | |
if (getReflectionHeight() > 0) { | |
// Create a bitmap to hold the drawing of child views and pass this to a temp canvas | |
Bitmap sourceBitmap = Bitmap.createBitmap(getMeasuredWidth(), getMeasuredHeight(), | |
Bitmap.Config.ARGB_8888); | |
Canvas tempCanvas = new Canvas(sourceBitmap); | |
// Draw the content of this layout onto our temporary canvas. | |
super.dispatchDraw(tempCanvas); | |
// Calculate the height of the reflection and the bottom position of child views. | |
int reflectionHeight = getReflectionHeight(); | |
int childBottom = getMaxChildBottom(); | |
// Create a new bitmap from the source image which has been vertically flipped and | |
// only includes the region occupied child views. | |
Bitmap flippedBitmap = Bitmap.createBitmap( | |
sourceBitmap, | |
0, | |
0, | |
sourceBitmap.getWidth(), | |
childBottom, | |
vFlipMatrix, | |
false); | |
// Create a bitmap to hold just the reflection | |
Bitmap fadedBitmap = Bitmap.createBitmap( | |
getMeasuredWidth(), reflectionHeight, Bitmap.Config.ARGB_8888); | |
Canvas fadeCanvas = new Canvas(fadedBitmap); | |
fadeCanvas.drawBitmap(flippedBitmap, 0, 0, null); | |
LinearGradient gradient = new LinearGradient(0, 0, 0, reflectionHeight, | |
0x30FFFFFF, 0x00FFFFFF, TileMode.CLAMP); | |
paint.setShader(gradient); | |
// Now use some clever PorterDuff shading to get the fading effect. | |
fadeCanvas.drawRect(0, 0, getMeasuredWidth(), reflectionHeight, paint); | |
// Draw our image onto the canvas | |
canvas.drawBitmap(fadedBitmap, 0, childBottom, null); | |
} | |
} | |
/** | |
* Finds the bottom of the lowest view contained by this layout. | |
* | |
* @return the bottom of the lowest view | |
*/ | |
private int getMaxChildBottom() { | |
int maxBottom = 0; | |
for (int i = 0; i < getChildCount(); i++) { | |
int bottom = getChildAt(i).getBottom(); | |
if (bottom > maxBottom) | |
maxBottom = bottom; | |
} | |
return maxBottom; | |
} | |
/** | |
* Gets the highest top edge of all contained views. | |
* | |
* @return the min child top | |
*/ | |
private int getMinChildTop() { | |
int minTop = Integer.MAX_VALUE; | |
for (int i = 0; i < getChildCount(); i++) { | |
int top = getChildAt(i).getTop(); | |
if (top < minTop) | |
minTop = top; | |
} | |
return minTop; | |
} | |
/** | |
* Gets the height of the space covered by all children. | |
* | |
* @return the total child height | |
*/ | |
private int getTotalChildHeight() { | |
// The max value of any child's "bottom" minus the minimum of any "top" | |
return getMaxChildBottom() - getMinChildTop(); | |
} | |
/** | |
* Gets the height of the reflection to be drawn. | |
* | |
* <p>This is the minimum of either: | |
* <ul> | |
* <li>The remaining height between the bottom of this layout and the bottom-most | |
* child view</li> | |
* <li>The maximum reflection ratio</li> | |
* </p> | |
* | |
* @return the reflection height | |
*/ | |
private int getReflectionHeight() { | |
return (int) Math.min( | |
getMeasuredHeight() - getMaxChildBottom(), | |
getTotalChildHeight() * MAX_REFLECTION_RATIO); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment