11

I want to extend LinearLayout so that when my layout is drawn a drop shadow is added underneath it. I've played around overriding the onDraw method but I'm a bit lost. Any help with this or even library suggestions would be greatly appreciated!

Here's an example of the drop shadow view I am trying to achieve. I don't believe I can use a nine patch here because I need the contents of the view to be within the white box. This would mean I would need to know the distance between the border and the end of the PNG. However I believe different screen densities mean that this distance will always be the same PX but not the same DP.

So to be clear I need a way to extend the View class so that a drop shadow is drawn under it when it is added to a layout. No XML or 9Patch solutions please.

enter image description here

Thanks

Jack

JackMahoney
  • 3,423
  • 7
  • 32
  • 50
  • I think you could draw a black rect which has size of your view, and then on top of it draw your view but translated a little bit to the down and right (or down and left, whatever you want). – Michał Z. Feb 18 '13 at 07:10
  • Could you please elaborate how one could do this? – JackMahoney Feb 19 '13 at 04:07
  • `Here's an example. I can't use a 9-Patch either` Why not? It's the best and easiest approach. – JanithaR Feb 20 '13 at 04:34
  • @JanithaR - i need to ensure the contents of the view doesn't flow out of the white boxes border. So I will need to add padding to the container. But how will I know where this border is if the 9-Patch is being stretched for different sizes? – JackMahoney Feb 20 '13 at 04:43
  • 2
    I didn't understand what you just said but trust me 9-patch is the way to go. Cause I have done it. [Here](http://adilsoomro.blogspot.com/2012/12/android-listview-with-speech-bubble.html) is an example. Just make sure you correctly define which parts needs to be not stretched and which area holds content. – JanithaR Feb 20 '13 at 05:14
  • @JanithaR - see this http://stackoverflow.com/questions/14972657/9-patch-drawable-dimensions-android – JackMahoney Feb 20 '13 at 05:21
  • Son, you've missed a part about 9-patch images on Android. You have an awesome tool called **draw9patch.bat** inside your Android SDK tools folder. For me it's at `"C:\Program Files (x86)\Android\android-sdk\tools\draw9patch.bat"` use that to create your 9-patch images, cause it makes your life easier you know. Check [this](http://developer.android.com/tools/help/draw9patch.html) for a second, see the bottom image with light blue areas inside the 9-patched versions? those mean you could limit your content to those areas **only**. I'll let you figure out how to do that. GLHF! – JanithaR Feb 20 '13 at 05:32
  • @JanithaR - I appreciate the fact you are trying to help but please don't patronise me. Of course I know about draw9patch - I stated that in the question I linked to above!! I still don't know what happens to the non-stretch areas however as the question asked! – JackMahoney Feb 20 '13 at 05:46
  • They just stay as they are, what else? Either everybody misunderstood you or you haven't communicated your question properly. For all I can understand you're not putting much effort to understand what's in the replies. You need it to be spoon fed. If I were you my 9-patch would be like [this](http://imgur.com/7loPH2m) – JanithaR Feb 20 '13 at 06:04
  • @JanithaR i agreed your point:). I think he need exact code snippet he don't want advice from our side. – AndroidEnthusiastic Feb 20 '13 at 10:01
  • @peter guys please relax. I appreciate your efforts but you aren't reading the question. I need to extend a View in Java. All the answers have been using Drawables or NinePatches. That is not a viable solution – JackMahoney Feb 20 '13 at 11:40
  • @jack i understand your situation. please refer this site http://stackoverflow.com/questions/3693234/custom-imageview-with-drop-shadow , i think positively try to resolve your problems. – AndroidEnthusiastic Feb 20 '13 at 13:02

3 Answers3

16

I agree with the comments on your question: programmatic dropshadow effect is a bad choice, and you could achieve the same effect with a simple 9patch (or a set of them) like stated here.

BTW I was too curious, and I ended with hacking a solution after work.

The code presented is a test, and should be intended as a simple proof-of-concept (so please don't downvote). Some of the operations shown are quite expensive, and may seriously impact on the performances (There are many examples around, look here, here to get an idea). It should be a last resort solution only for a component shown once-in-a-while.

public class BalloonView extends TextView {

  protected NinePatchDrawable bg;
  protected Paint paint;
  protected Rect padding = new Rect();
  protected Bitmap bmp;

  public BalloonView(Context context) {
    super(context);
init();
  }

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

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

  @SuppressLint("NewApi")
  protected void init() {
    // decode the 9patch drawable
    bg = (NinePatchDrawable) getResources().getDrawable(R.drawable.balloon);

    // get paddings from the 9patch and apply them to the View
    bg.getPadding(padding);
    setPadding(padding.left, padding.top, padding.right, padding.bottom);

    // prepare the Paint to use below
    paint = new Paint();
    paint.setAntiAlias(true);
    paint.setColor(Color.rgb(255,255,255));
    paint.setStyle(Style.FILL);

    // this check is needed in order to get this code
    // working if target SDK>=11
    if( Build.VERSION.SDK_INT >= 11 )
      setLayerType(View.LAYER_TYPE_SOFTWARE, paint);

    // set the shadowLayer
    paint.setShadowLayer(
      padding.left * .2f, // radius
      0f, // blurX
      padding.left * .1f, // blurY
      Color.argb(128, 0, 0, 0) // shadow color
    );
  }

  @Override
  protected void onDraw(Canvas canvas) {
    int w = getMeasuredWidth();
    int h = getMeasuredHeight();

    // set 9patch bounds according to view measurement
    // NOTE: if not set, the drawable will not be drawn
    bg.setBounds(0, 0, w, h);

    // this code looks expensive: let's do once
    if( bmp == null ) {

      // it seems like shadowLayer doesn't take into account
      // alpha channel in ARGB_8888 sources...
      bmp = Bitmap.createBitmap(w, h, Config.ARGB_8888);

      // draw the given 9patch on the brand new bitmap
      Canvas tmp = new Canvas(bmp);
      bg.draw(tmp);

      // extract only the alpha channel
      bmp = bmp.extractAlpha();
    }

    // this "alpha mask" has the same shape of the starting 9patch,
    // but filled in white and **with the dropshadow**!!!!
    canvas.drawBitmap(bmp, 0, 0, paint);

    // let's paint the 9patch over...
    bg.draw(canvas);

    super.onDraw(canvas);
  }
}

First of all in order to get programmatic drop shadow you have to deal with Paint.setShadowLayer(...) like stated here. Basically you should define a shadow layer for the Paint object used to draw on the Canvas of your custom view. Unfortunately you cannot use a Paint object to draw a NinePatchDrawable, so you need to convert it into a Bitmap (1st hack). Furthermore it seems like shadow layers can't work properly with ARGB_8888 images, so the only way I found in order to get a proper shadow has been to draw the alpha mask of the given NinePatchDrawable (2nd hack) just below itself.

Here's a couple of sshots (tested on Android 2.3.3@mdpi and 4.2.2@xhdpi) enter image description here enter image description here

Edit: just to be thorough, I attached the 9patch used in the test (placed in res/drawable/mdpi) enter image description here

Community
  • 1
  • 1
a.bertucci
  • 12,142
  • 2
  • 31
  • 32
  • Thanks for you answer I'll try it out. But I disagree about the 9Patch. The nonstretched areas of a 9Patch are not scaled for each screen density so they will appear smaller on high density screens - therefore the dropshadow area will be smaller and I won't know where to position the children of the container with padding – JackMahoney Feb 21 '13 at 00:58
  • 2
    @JackMahoney sorry but you're wrong. Just look at the screenshots above: there are 2 different **real devices**: 320x480|mdpi, 720x1280|xhdpi. In both cases the same 9patch is used. You can measure the round corner of the balloon in pixels in each sshot and you will notice that on xhdpi is 2x wide than on mdpi. Also paddings (defined in the 9patch) scale up accordingly. – a.bertucci Feb 21 '13 at 10:33
  • @a.bertucci can you please add a sample project for me ? – Michel Gokan Khan Aug 27 '13 at 17:45
4

This is the simplest way of dropping shadow to any thing either layout, button etc. background.xml

    <?xml version="1.0" encoding="utf-8"?>
    <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
        <item >
            <shape 
                android:shape="rectangle">
            <solid android:color="@android:color/darker_gray" />
            <corners android:radius="5dp"/>
            </shape>
        </item>
        <item android:right="1dp" android:left="1dp" android:bottom="2dp">
            <shape 
                android:shape="rectangle">
            <solid android:color="@android:color/white"/>
            <corners android:radius="5dp"/>
            </shape>
        </item>
    </layer-list>

in your main.xml

<LineaLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/background"
/>
dmon
  • 30,048
  • 8
  • 87
  • 96
Rohit
  • 490
  • 3
  • 15
3

Try this technique.

container_dropshadow.xml

`

 <!-- 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>

Then you can apply it to an XML layout as a background like

LinearLayout android:background="@drawable/container_dropshadow"

`

  • Hm this looks a bit fidgety. Can you explain what the idea behind this is. It looks like you've just stacked a series of boxed on top of one another. That won't create gradient. – JackMahoney Feb 19 '13 at 04:07
  • 1
    I think problem is your side, Michael also suggested one logic. But You told please elaborate. I am also showed a suggestion way. But you told same thing please explain. First you have search in google. Lot of stuff is there. if you can't find exact solution then come to stack overflow and then raise your question. We cant send complete snippet to do your work. Thanks for understanding. All the Best:) – AndroidEnthusiastic Feb 19 '13 at 06:16
  • The answer is wrong. The padding for each item is the same so it creates a solid color not a gradient – JackMahoney Feb 20 '13 at 09:19