Расширение класса Android View для добавления dropshadow

Я хочу расширить LinearLayout, чтобы при рисовании моего макета под ним добавлялась тень. Я играл вокруг переопределения метода onDraw, но я немного потерялся. Любая помощь с этими или даже предложениями библиотеки будет принята с благодарностью!

Вот пример теневого представления, которого я пытаюсь достичь. Я не верю, что могу использовать девять патчей, потому что мне нужно, чтобы содержимое представления находилось в белом ящике. Это означало бы, что мне нужно знать расстояние между границей и концом PNG. Однако я считаю, что разные плотности экрана означают, что это расстояние всегда будет одним и тем же PX, но не тем же DP.

Поэтому, чтобы быть ясным, мне нужен способ расширить класс View, чтобы при его добавлении к макету была подкрашена тень. Нет решений XML или 9Patch.

enter image description here

Спасибо

Джек

Ответы

Ответ 1

Я согласен с комментариями по вашему вопросу: программный эффект dropshadow - плохой выбор, и вы можете добиться такого же эффекта с помощью простого 9patch (или набора из них), как указано здесь.

Мне было слишком любопытно, и я закончил с взломом решения после работы.

Представленный код - это тест, и его следует рассматривать как простое доказательство концепции (так что, пожалуйста, не используйте downvote). Некоторые из показанных операций довольно дорогие и могут серьезно влиять на производительность (примеров много, посмотрите здесь, здесь, чтобы получить представление). Он должен быть последним решением только для компонента, показанного один раз в секунду.

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 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 paint the 9patch over...
    bg.draw(canvas);

    super.onDraw(canvas);
  }
}

Прежде всего, чтобы получить программную тень, вам нужно иметь дело с Paint.setShadowLayer(...), как указано здесь. В основном вы должны определить теневой слой для объекта Paint, который используется для рисования на Canvas вашего пользовательского представления. К сожалению, вы не можете использовать объект Paint для рисования NinePatchDrawable, поэтому вам нужно преобразовать его в Bitmap (1-й взлом). Кроме того, кажется, что теневые слои не могут нормально работать с изображениями ARGB_8888, поэтому единственным способом, который я нашел для того, чтобы получить правильную тень, было рисование альфа-маски данного NinePatchDrawable (2-го взлома) чуть ниже самого себя.

Здесь пара sshots (протестирована на Android [email protected] и [email protected]) enter image description hereenter image description here

Изменить: только для того, чтобы быть тщательным, я прикрепил 9-патч, используемый в тесте (помещен в res/drawable/mdpi) enter image description here

Ответ 2

Попробуйте эту технику.

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>

Затем вы можете применить его к XML-макету в качестве фона, например

LinearLayout android: background = "@drawable/container_dropshadow"

`

Ответ 3

Это самый простой способ сбросить тень на любую вещь, будь то макет, кнопка и т.д. 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>

в вашем main.xml

<LineaLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/background"
/>