Custom ImageView with drop shadow

AndroidOverridingImageviewDropshadow

Android Problem Overview


Okay, I've been reading and searching around, and am now banging my head against the wall trying to figure this out. Here's what I have so far:

package com.pockdroid.sandbox;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.widget.ImageView;

public class ShadowImageView extends ImageView {

private Rect mRect;
private Paint mPaint;

public ShadowImageView(Context context)
{
	super(context);
	mRect = new Rect();
	mPaint = new Paint();
	mPaint.setAntiAlias(true);
	mPaint.setShadowLayer(2f, 1f, 1f, Color.BLACK);
}

@Override
protected void onDraw(Canvas canvas) 
{
	Rect r = mRect;
	Paint paint = mPaint;
	
	canvas.drawRect(r, paint);
	super.onDraw(canvas);
}

@Override
protected void onMeasure(int w, int h)
{
	super.onMeasure(w,h);
	int mH, mW;
	mW = getSuggestedMinimumWidth() < getMeasuredWidth()? getMeasuredWidth() : getSuggestedMinimumWidth();
	mH = getSuggestedMinimumHeight() < getMeasuredHeight()? getMeasuredHeight() : getSuggestedMinimumHeight();
	setMeasuredDimension(mW + 5, mH + 5);
}

}

The "+5" in the measurements are there as temporary; From what I understand I'll need to do some math to determine the size that the drop shadow adds to the canvas, right?

But when I use this:

public View getView(int position, View convertView, ViewGroup parent) {
    ShadowImageView sImageView;
    if (convertView == null) {
        sImageView = new ShadowImageView(mContext);
        GridView.LayoutParams lp = new GridView.LayoutParams(85, 85);
        sImageView.setLayoutParams(lp);
        
        sImageView.setScaleType(ImageView.ScaleType.CENTER);
        sImageView.setPadding(5,5,5,5);
    } else {
        sImageView = (ShadowImageView) convertView;
    }

    sImageView.setImageBitmap(bitmapList.get(position));
    return sImageView;
}

in my ImageView, I still get just a normal ImageView when I run the program.

Any thoughts? Thanks.

EDIT: So I spoke with RomainGuy some in the IRC channel, and I have it working now for plain rectangular images with the below code. It still won't draw the shadow directly to my bitmap's transparency though, so I'm still working on that.

@Override
protected void onDraw(Canvas canvas) 
{
	Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.omen);
	Paint paint = new Paint();
	paint.setAntiAlias(true);
	paint.setShadowLayer(5.5f, 6.0f, 6.0f, Color.BLACK);
	canvas.drawColor(Color.GRAY);
	canvas.drawRect(50, 50, 50 + bmp.getWidth(), 50 + bmp.getHeight(), paint);
	canvas.drawBitmap(bmp, 50, 50, null);		
}

Android Solutions


Solution 1 - Android

Okay, I don't foresee any more answers on this one, so what I ended up going with for now is just a solution for rectangular images. I've used the following NinePatch:

alt text

along with the appropriate padding in XML:

<ImageView
		android:id="@+id/image_test"
		android:background="@drawable/drop_shadow"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		android:paddingLeft="6px"
		android:paddingTop="4px"
		android:paddingRight="8px"
		android:paddingBottom="9px"
		android:src="@drawable/pic1"
		/>

to get a fairly good result:

alt text

Not ideal, but it'll do.

Solution 2 - Android

This is taken from Romain Guy's presentation at Devoxx, pdf found here.

Paint mShadow = new Paint(); 
// radius=10, y-offset=2, color=black 
mShadow.setShadowLayer(10.0f, 0.0f, 2.0f, 0xFF000000); 
// in onDraw(Canvas) 
canvas.drawBitmap(bitmap, 0.0f, 0.0f, mShadow);

Hope this helps.

NOTES

  1. Don't forget for Honeycomb and above you need to invoke setLayerType(LAYER_TYPE_SOFTWARE, mShadow), otherwise you will not see your shadow! (@Dmitriy_Boichenko)
  2. SetShadowLayer does not work with hardware acceleration unfortunately so it greatly reduces performances (@Matt Wear) [1] [2]

Solution 3 - Android

I believe this answer from UIFuel

<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">

     <!-- Drop Shadow Stack -->
     <item>
        <shape>
            <padding android:top="1dp" android:right="1dp" android:bottom="1dp" android:left="1dp" />
            <solid android:color="#00CCCCCC" />
        </shape>
    </item>
     <item>
        <shape>
            <padding android:top="1dp" android:right="1dp" android:bottom="1dp" android:left="1dp" />
            <solid android:color="#10CCCCCC" />
        </shape>
    </item>
     <item>
        <shape>
            <padding android:top="1dp" android:right="1dp" android:bottom="1dp" android:left="1dp" />
            <solid android:color="#20CCCCCC" />
        </shape>
    </item>
     <item>
        <shape>
            <padding android:top="1dp" android:right="1dp" android:bottom="1dp" android:left="1dp" />
            <solid android:color="#30CCCCCC" />
        </shape>
    </item>
    <item>
        <shape>
            <padding android:top="1dp" android:right="1dp" android:bottom="1dp" android:left="1dp" />
            <solid android:color="#50CCCCCC" />
        </shape>
    </item>

    <!-- Background -->
    <item>
	<shape>
            <solid android:color="@color/white" />
	    <corners android:radius="3dp" />
	</shape>
    </item>
</layer-list>

Solution 4 - Android

My dirty solution:

private static Bitmap getDropShadow3(Bitmap bitmap) {

    if (bitmap==null) return null;
    int think = 6;
    int w = bitmap.getWidth();
    int h = bitmap.getHeight();

    int newW = w - (think);
    int newH = h - (think);

    Bitmap.Config conf = Bitmap.Config.ARGB_8888;
    Bitmap bmp = Bitmap.createBitmap(w, h, conf);
    Bitmap sbmp = Bitmap.createScaledBitmap(bitmap, newW, newH, false);

    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    Canvas c = new Canvas(bmp);

    // Right
    Shader rshader = new LinearGradient(newW, 0, w, 0, Color.GRAY, Color.LTGRAY, Shader.TileMode.CLAMP);
    paint.setShader(rshader);
    c.drawRect(newW, think, w, newH, paint);

    // Bottom
    Shader bshader = new LinearGradient(0, newH, 0, h, Color.GRAY, Color.LTGRAY, Shader.TileMode.CLAMP);
    paint.setShader(bshader);
    c.drawRect(think, newH, newW  , h, paint);

    //Corner
    Shader cchader = new LinearGradient(0, newH, 0, h, Color.LTGRAY, Color.LTGRAY, Shader.TileMode.CLAMP);
    paint.setShader(cchader);
    c.drawRect(newW, newH, w  , h, paint);


    c.drawBitmap(sbmp, 0, 0, null);

    return bmp;
}

result: enter image description here

Solution 5 - Android

Here you are. Set source of ImageView statically in xml or dynamically in code.

Shadow is here white.

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content" android:layout_height="wrap_content">

    <View android:layout_width="wrap_content" android:layout_height="wrap_content"
	    android:background="@android:color/white" android:layout_alignLeft="@+id/image"
		android:layout_alignRight="@id/image" android:layout_alignTop="@id/image"
	    android:layout_alignBottom="@id/image" android:layout_marginLeft="10dp"
	    android:layout_marginBottom="10dp" />

	<ImageView android:id="@id/image" android:layout_width="wrap_content"
	    android:layout_height="wrap_content" android:src="..."
	    android:padding="5dp" />

</RelativeLayout>

Solution 6 - Android

I manage to apply gradient border using this code..

public static Bitmap drawShadow(Bitmap bitmap, int leftRightThk, int bottomThk, int padTop) {
	int w = bitmap.getWidth();
	int h = bitmap.getHeight();
	
	int newW = w - (leftRightThk * 2);
	int newH = h - (bottomThk + padTop);
	
	Bitmap.Config conf = Bitmap.Config.ARGB_8888;
	Bitmap bmp = Bitmap.createBitmap(w, h, conf);
	Bitmap sbmp = Bitmap.createScaledBitmap(bitmap, newW, newH, false);
	
	Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
	Canvas c = new Canvas(bmp);
			
	// Left
	int leftMargin = (leftRightThk + 7)/2;
	Shader lshader = new LinearGradient(0, 0, leftMargin, 0, Color.TRANSPARENT, Color.BLACK, TileMode.CLAMP);
	paint.setShader(lshader);
	c.drawRect(0, padTop, leftMargin, newH, paint);	
	
	// Right
	Shader rshader = new LinearGradient(w - leftMargin, 0, w, 0, Color.BLACK, Color.TRANSPARENT, TileMode.CLAMP);
	paint.setShader(rshader);
	c.drawRect(newW, padTop, w, newH, paint);
	
	// Bottom
	Shader bshader = new LinearGradient(0, newH, 0, bitmap.getHeight(), Color.BLACK, Color.TRANSPARENT, TileMode.CLAMP);
	paint.setShader(bshader);
	c.drawRect(leftMargin -3, newH, newW + leftMargin + 3, bitmap.getHeight(), paint);
	c.drawBitmap(sbmp, leftRightThk, 0, null);
	
	return bmp;
}

hope this helps !

Solution 7 - Android

This works for me ...

public class ShadowImage extends Drawable {

Bitmap bm;

@Override
public void draw(Canvas canvas) {

	Paint mShadow = new Paint();
	Rect rect = new Rect(0,0,bm.getWidth(), bm.getHeight());
	
	mShadow.setAntiAlias(true);
	mShadow.setShadowLayer(5.5f, 4.0f, 4.0f, Color.BLACK);

	canvas.drawRect(rect, mShadow);
	canvas.drawBitmap(bm, 0.0f, 0.0f, null);

}

public ShadowImage(Bitmap bitmap) {
	super();
	this.bm = bitmap;
} ... }

Solution 8 - Android

Here the Implementation of Paul Burkes answer:

public class ShadowImageView extends ImageView {

	public ShadowImageView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	public ShadowImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	public ShadowImageView(Context context) {
		super(context);
	}

	private Paint createShadow() {
		Paint mShadow = new Paint();

		float radius = 10.0f;
		float xOffset = 0.0f;
		float yOffset = 2.0f;

		// color=black
		int color = 0xFF000000;
		mShadow.setShadowLayer(radius, xOffset, yOffset, color);


		return mShadow;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		Paint mShadow = createShadow();
		Drawable d = getDrawable();
		if (d != null){
		    setLayerType(LAYER_TYPE_SOFTWARE, mShadow);
			Bitmap bitmap = ((BitmapDrawable) getDrawable()).getBitmap();
			canvas.drawBitmap(bitmap, 0.0f, 0.0f, mShadow);
		} else {
			super.onDraw(canvas);
		}

	};

}

TODO: execute setLayerType(LAYER_TYPE_SOFTWARE, mShadow); only if API Level is > 10

Solution 9 - Android

I've built upon the answer above - https://stackoverflow.com/a/11155031/2060486 - to create a shadow around ALL sides..

 private static final int GRAY_COLOR_FOR_SHADE = Color.argb(50, 79, 79, 79);

// this method takes a bitmap and draws around it 4 rectangles with gradient to create a
// shadow effect.
public static Bitmap addShadowToBitmap(Bitmap origBitmap) {
    int shadowThickness = 13; // can be adjusted as needed
    int bmpOriginalWidth = origBitmap.getWidth();
    int bmpOriginalHeight = origBitmap.getHeight();
    int bigW = bmpOriginalWidth + shadowThickness * 2; // getting dimensions for a bigger bitmap with margins
    int bigH = bmpOriginalHeight + shadowThickness * 2;
    Bitmap containerBitmap = Bitmap.createBitmap(bigW, bigH, Bitmap.Config.ARGB_8888);
    Bitmap copyOfOrigBitmap = Bitmap.createScaledBitmap(origBitmap, bmpOriginalWidth, bmpOriginalHeight, false);
    Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    Canvas canvas = new Canvas(containerBitmap); // drawing the shades on the bigger bitmap
    //right shade - direction of gradient is positive x (width)
    Shader rightShader = new LinearGradient(bmpOriginalWidth, 0, bigW, 0, GRAY_COLOR_FOR_SHADE,
            Color.TRANSPARENT, Shader.TileMode.CLAMP);
    paint.setShader(rightShader);
    canvas.drawRect(bigW - shadowThickness, shadowThickness, bigW, bigH - shadowThickness, paint);
    //bottom shade - direction is positive y (height)
    Shader bottomShader = new LinearGradient(0, bmpOriginalHeight, 0, bigH, GRAY_COLOR_FOR_SHADE,
            Color.TRANSPARENT, Shader.TileMode.CLAMP);
    paint.setShader(bottomShader);
    canvas.drawRect(shadowThickness, bigH - shadowThickness, bigW - shadowThickness, bigH, paint);
    //left shade - direction is negative x
    Shader leftShader = new LinearGradient(shadowThickness, 0, 0, 0, GRAY_COLOR_FOR_SHADE,
            Color.TRANSPARENT, Shader.TileMode.CLAMP);
    paint.setShader(leftShader);
    canvas.drawRect(0, shadowThickness, shadowThickness, bigH - shadowThickness, paint);
    //top shade - direction is negative y
    Shader topShader = new LinearGradient(0, shadowThickness, 0, 0, GRAY_COLOR_FOR_SHADE,
            Color.TRANSPARENT, Shader.TileMode.CLAMP);
    paint.setShader(topShader);
    canvas.drawRect(shadowThickness, 0, bigW - shadowThickness, shadowThickness, paint);
    // starting to draw bitmap not from 0,0 to get margins for shade rectangles
    canvas.drawBitmap(copyOfOrigBitmap, shadowThickness, shadowThickness, null);
    return containerBitmap;
}

Change the color in the const as you see fit.

Solution 10 - Android

Use this class to draw shadow on bitmaps

public class ShadowGenerator {

    // Percent of actual icon size
    private static final float HALF_DISTANCE = 0.5f;
    public static final float BLUR_FACTOR = 0.5f/48;

    // Percent of actual icon size
    private static final float KEY_SHADOW_DISTANCE = 1f/48;
    public static final int KEY_SHADOW_ALPHA = 61;

    public static final int AMBIENT_SHADOW_ALPHA = 30;

    private static final Object LOCK = new Object();
    // Singleton object guarded by {@link #LOCK}
    private static ShadowGenerator sShadowGenerator;

    private  int mIconSize;

    private final Canvas mCanvas;
    private final Paint mBlurPaint;
    private final Paint mDrawPaint;
    private final Context mContext;

    private ShadowGenerator(Context context) {
        mContext = context;
        mIconSize = Utils.convertDpToPixel(context,63);
        mCanvas = new Canvas();
        mBlurPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        mBlurPaint.setMaskFilter(new BlurMaskFilter(mIconSize * BLUR_FACTOR, Blur.NORMAL));
        mDrawPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
    }

    public synchronized Bitmap recreateIcon(Bitmap icon) {
        mIconSize = Utils.convertDpToPixel(mContext,3)+icon.getWidth();
        int[] offset = new int[2];
        Bitmap shadow = icon.extractAlpha(mBlurPaint, offset);
        Bitmap result = Bitmap.createBitmap(mIconSize, mIconSize, Config.ARGB_8888);
        mCanvas.setBitmap(result);

        // Draw ambient shadow
        mDrawPaint.setAlpha(AMBIENT_SHADOW_ALPHA);
        mCanvas.drawBitmap(shadow, offset[0], offset[1], mDrawPaint);

        // Draw key shadow
        mDrawPaint.setAlpha(KEY_SHADOW_ALPHA);
        mCanvas.drawBitmap(shadow, offset[0], offset[1] + KEY_SHADOW_DISTANCE * mIconSize, mDrawPaint);

        // Draw the icon
        mDrawPaint.setAlpha(255);
        mCanvas.drawBitmap(icon, 0, 0, mDrawPaint);

        mCanvas.setBitmap(null);
        return result;
    }



    public static ShadowGenerator getInstance(Context context) {

        synchronized (LOCK) {
            if (sShadowGenerator == null) {
                sShadowGenerator = new ShadowGenerator(context);
            }
        }
        return sShadowGenerator;
    }
    
}

Solution 11 - Android

If you want to use the custom imageView, I suggest you use this one

View look perfect and dont't use any nine path image

enter image description here

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionKevin CoppockView Question on Stackoverflow
Solution 1 - AndroidKevin CoppockView Answer on Stackoverflow
Solution 2 - AndroidPaul BurkeView Answer on Stackoverflow
Solution 3 - AndroidSoyoesView Answer on Stackoverflow
Solution 4 - AndroidrecoilmeView Answer on Stackoverflow
Solution 5 - AndroidplugmindView Answer on Stackoverflow
Solution 6 - AndroidRukmal DiasView Answer on Stackoverflow
Solution 7 - AndroidMiguel S. MendozaView Answer on Stackoverflow
Solution 8 - AndroidMurmelView Answer on Stackoverflow
Solution 9 - AndroidperesisUserView Answer on Stackoverflow
Solution 10 - AndroidAli ImranView Answer on Stackoverflow
Solution 11 - AndroidStevieView Answer on Stackoverflow