Extending Android View class to add a dropshadow

前端 未结 3 784
生来不讨喜
生来不讨喜 2020-12-13 22:16

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 metho

相关标签:
3条回答
  • 2020-12-13 22:19

    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"
    />
    
    0 讨论(0)
  • 2020-12-13 22:38

    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"

    `

    0 讨论(0)
  • 2020-12-13 22:41

    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

    0 讨论(0)
提交回复
热议问题