Android Implementation Universal Custom Shadow Control Instance Code

  • 2021-11-10 10:53:59
  • OfStack

Directory introduction

01. What are the ways to achieve the shadow effect 02. Implement the shadow effect Api 03. What should I pay attention to when setting shadows 04. Common Shape realizes shadow effect 05. Custom Shadow Effect Control 06. How to Use the Shadow Control 07. Points to note when using recyclerView

01. What are the ways to achieve the shadow effect

What are the ways to realize the shadow effect

Type 1: Use CardView, but cannot set shadow color The second type: shape superposition is adopted, and the effect of UI in the later period is inconvenient to optimize Type 3: UI cut diagram Type 4: Custom View

An analysis of the reasons for denying the two schemes before?

The CardView gradient and shadow effect of the first scheme is difficult to control, and can only support linear or ring-mounted gradient, which does not meet the needs, because the shadow itself is surrounded by a very light color of 4 weeks and 1 layer, and the color is about 1 on the level of a rectangular box. Moreover, this CardView has many limitations, such as the color and depth of the shadow cannot be modified. Therefore, this idea cannot meet this requirement. The second one adopts shape superposition, which can achieve shadow effect, but affects UI, and the shadow part occupies pixels and is inflexible. The third scheme inquires about ui under 1. The result they give is that it is difficult to mark if you use cut pictures. As an excellent designer, most of them are sensitive to pixels, and one point of incongruity between pixels on the interface is intolerable. In the following open source case code, I will show the shadow effect implemented by these different schemes in 11.

Some on the Internet introduce the shadow effect scheme

All esoteric technologies are also prepared for needs. That is to say, it needs practice and can be used in actual development. This article no longer introduces the principle of shadow effect abstractly, and understands how to deal with offset light to achieve shadow parallax in 3D space. I didn't understand or understand it after reading some articles on the Internet. This blog achieves the desired effect directly by calling api.

Does the shadow occupy space

Shadow does not occupy space when using CardView, and shadow color and effect cannot be set Shadow colors can be set using shape shadows, but they are placeholders

02. Realize the shadow effect Api

Think about how to achieve View shadow effect under 1.

First of all, we should make clear what is the realization idea of shadow, which is actually the visual illusion caused by color. To put it bluntly, draw a gradient color around your Card. Based on the above ideas, we draw a rectangular figure on an view, so that there are gradient shadows around it. So we think of several API: Class: Paint is used to draw pictures on Android, which is equivalent to a brush Class: Canvas is equivalent to canvas, and the drawing of view on Android is related to it Method: paint. setShadowLayer can add shadows to the drawing, and can also set the color of the shadows

paint.setShadowLayer(float radius, float dx, float dy, int shadowColor);

This method achieves the effect of adding a shadow to the view when drawing with canvas.

Briefly introduce these parameters under 1:

radius: Shadow radius, which can mainly control the blurring effect of shadows and the size of shadows spreading out. dx: Shadow Offset in X Axis dy: Shadow Offset in Y Axis shadowColor: Shadow color.

Finally found the setting color, by setting shadowColor to control the shadow color of the view.

03. What should I pay attention to when setting shadows

There are several attributes involved, such as the width of shadow and the distance from view to Viewgroup. If the view is as big as the parent layout 1, the shadow is not easy to display. If you want to display it, you must set clipChildren=false.

There is also the rounded corners of the view. Most of the backgrounds have rounded corners, such as the rounded corners in the above picture. The effect of highly restoring shadows is to keep the rounded corners of shadows and the background 1.

04. Common Shape realizes shadow effect

Multiple drawable superposition

Using layer-list, multiple drawable can be stacked and displayed in one sequence. By default, drawable in all item will be automatically scaled according to the size of view attached to it. item in layer-list is stacked from bottom to top in sequence, that is, the first defined item is stacked below, and the later ones are stacked above in turn

The shadow effect code is as follows

There are many layers here, so one is omitted. It can then be achieved directly by setting the background property of the control.

<?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="@color/indexShadowColor_1" />
      <corners android:radius="5dip" />
      <padding
        android:bottom="1dp"
        android:left="1dp"
        android:right="1dp"
        android:top="1dp" />
    </shape>
  </item>
  <item>
    <shape android:shape="rectangle">
      <solid android:color="@color/indexShadowColor_2" />
      <corners android:radius="5dip" />
      <padding
        android:bottom="1dp"
        android:left="1dp"
        android:right="1dp"
        android:top="1dp" />
    </shape>
  </item>
  
   ... 

  <item>
    <shape android:shape="rectangle">
      <corners android:radius="5dip" />
      <solid android:color="@color/indexColor" />
    </shape>
  </item>
</layer-list>

05. Custom Shadow Effect Control

Customize properties first


<declare-styleable name="ShadowLayout">
  <!-- Fillet size of shadow -->
  <attr name="yc_cornerRadius" format="dimension" />
  <!-- Spread range of shadow ( It can also be understood as the degree of diffusion )-->
  <attr name="yc_shadowLimit" format="dimension" />
  <!-- Shadow color -->
  <attr name="yc_shadowColor" format="color" />
  <!--x Offset of axis -->
  <attr name="yc_dx" format="dimension" />
  <!--y Offset of axis -->
  <attr name="yc_dy" format="dimension" />
  <!-- Does the left show a shadow -->
  <attr name="yc_leftShow" format="boolean" />
  <!-- Does the right side show a shadow -->
  <attr name="yc_rightShow" format="boolean" />
  <!-- Shadow on top -->
  <attr name="yc_topShow" format="boolean" />
  <!-- Shadow below -->
  <attr name="yc_bottomShow" format="boolean" />
</declare-styleable>

The code is as follows


/**
 * <pre>
 *   @author yangchong
 *   blog : https://github.com/yangchong211
 *   time : 2018/7/20
 *   desc :  Custom Shadow 
 *   revise:
 */
public class ShadowLayout extends FrameLayout {

  /**
   *  Shadow color 
   */
  private int mShadowColor;
  /**
   *  Spread range of shadow ( It can also be understood as the degree of diffusion )
   */
  private float mShadowLimit;
  /**
   *  Fillet size of shadow 
   */
  private float mCornerRadius;
  /**
   * x Offset of axis 
   */
  private float mDx;
  /**
   * y Offset of axis 
   */
  private float mDy;
  /**
   *  Does the left show a shadow 
   */
  private boolean leftShow;
  /**
   *  Does the right side show a shadow 
   */
  private boolean rightShow;
  /**
   *  Shadow on top 
   */
  private boolean topShow;
  /**
   *  Shadow below 
   */
  private boolean bottomShow;


  private boolean mInvalidateShadowOnSizeChanged = true;
  private boolean mForceInvalidateShadow = false;

  public ShadowLayout(Context context) {
    super(context);
    initView(context, null);
  }

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

  public ShadowLayout(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initView(context, attrs);
  }

  @Override
  protected int getSuggestedMinimumWidth() {
    return 0;
  }

  @Override
  protected int getSuggestedMinimumHeight() {
    return 0;
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    if (w > 0 && h > 0 && (getBackground() == null || mInvalidateShadowOnSizeChanged
        || mForceInvalidateShadow)) {
      mForceInvalidateShadow = false;
      setBackgroundCompat(w, h);
    }
  }

  @Override
  protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    if (mForceInvalidateShadow) {
      mForceInvalidateShadow = false;
      setBackgroundCompat(right - left, bottom - top);
    }
  }

  public void setInvalidateShadowOnSizeChanged(boolean invalidateShadowOnSizeChanged) {
    mInvalidateShadowOnSizeChanged = invalidateShadowOnSizeChanged;
  }

  public void invalidateShadow() {
    mForceInvalidateShadow = true;
    requestLayout();
    invalidate();
  }

  private void initView(Context context, AttributeSet attrs) {
    initAttributes(context, attrs);

    int xPadding = (int) (mShadowLimit + Math.abs(mDx));
    int yPadding = (int) (mShadowLimit + Math.abs(mDy));
    int left;
    int right;
    int top;
    int bottom;
    if (leftShow) {
      left = xPadding;
    } else {
      left = 0;
    }

    if (topShow) {
      top = yPadding;
    } else {
      top = 0;
    }


    if (rightShow) {
      right = xPadding;
    } else {
      right = 0;
    }

    if (bottomShow) {
      bottom = yPadding;
    } else {
      bottom = 0;
    }

    setPadding(left, top, right, bottom);
  }

  @SuppressWarnings("deprecation")
  private void setBackgroundCompat(int w, int h) {
    Bitmap bitmap = createShadowBitmap(w, h, mCornerRadius, mShadowLimit, mDx,
        mDy, mShadowColor, Color.TRANSPARENT);
    BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap);
    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) {
      setBackgroundDrawable(drawable);
    } else {
      setBackground(drawable);
    }
  }


  private void initAttributes(Context context, AttributeSet attrs) {
    TypedArray attr = getTypedArray(context, attrs, R.styleable.ShadowLayout);
    if (attr == null) {
      return;
    }

    try {
      // The default is to display 
      leftShow = attr.getBoolean(R.styleable.ShadowLayout_yc_leftShow, true);
      rightShow = attr.getBoolean(R.styleable.ShadowLayout_yc_rightShow, true);
      bottomShow = attr.getBoolean(R.styleable.ShadowLayout_yc_bottomShow, true);
      topShow = attr.getBoolean(R.styleable.ShadowLayout_yc_topShow, true);

      mCornerRadius = attr.getDimension(R.styleable.ShadowLayout_yc_cornerRadius, 0);
      mShadowLimit = attr.getDimension(R.styleable.ShadowLayout_yc_shadowLimit, 0);
      mDx = attr.getDimension(R.styleable.ShadowLayout_yc_dx, 0);
      mDy = attr.getDimension(R.styleable.ShadowLayout_yc_dy, 0);
      mShadowColor = attr.getColor(R.styleable.ShadowLayout_yc_shadowColor,
          getResources().getColor(R.color.default_shadow_color));
    } finally {
      attr.recycle();
    }
  }

  private TypedArray getTypedArray(Context context, AttributeSet attributeSet, int[] attr) {
    return context.obtainStyledAttributes(attributeSet, attr, 0, 0);
  }

  private Bitmap createShadowBitmap(int shadowWidth, int shadowHeight, float cornerRadius,
                   float shadowRadius, float dx, float dy,
                   int shadowColor, int fillColor) {

    // Create based on width and height bitmap Background 
    Bitmap output = Bitmap.createBitmap(shadowWidth, shadowHeight, Bitmap.Config.ARGB_8888);
    // With a drawing board canvas Draw 
    Canvas canvas = new Canvas(output);
    RectF shadowRect = new RectF(shadowRadius, shadowRadius,
        shadowWidth - shadowRadius, shadowHeight - shadowRadius);

    if (dy > 0) {
      shadowRect.top += dy;
      shadowRect.bottom -= dy;
    } else if (dy < 0) {
      shadowRect.top += Math.abs(dy);
      shadowRect.bottom -= Math.abs(dy);
    }

    if (dx > 0) {
      shadowRect.left += dx;
      shadowRect.right -= dx;
    } else if (dx < 0) {
      shadowRect.left += Math.abs(dx);
      shadowRect.right -= Math.abs(dx);
    }

    Paint shadowPaint = new Paint();
    shadowPaint.setAntiAlias(true);
    shadowPaint.setColor(fillColor);
    shadowPaint.setStyle(Paint.Style.FILL);
    if (!isInEditMode()) {
      shadowPaint.setShadowLayer(shadowRadius, dx, dy, shadowColor);
    }
    canvas.drawRoundRect(shadowRect, cornerRadius, cornerRadius, shadowPaint);
    return output;
  }
}
```

06. How to Use the Shadow Control

10 points is simple, as shown below


<com.ns.yc.yccardviewlib.shadow.ShadowLayout
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_gravity="center_horizontal"
  android:layout_marginTop="10dp"
  app:yc_cornerRadius="18dp"
  app:yc_dx="0dp"
  app:yc_dy="0dp"
  app:yc_shadowColor="#2a000000"
  app:yc_shadowLimit="5dp">

  <TextView
    android:layout_width="wrap_content"
    android:layout_height="36dp"
    android:background="@drawable/shape_show_"
    android:gravity="center"
    android:paddingLeft="10dp"
    android:paddingRight="10dp"
    android:text=" Completely rounded fillet "
    android:textColor="#000" />

</com.ns.yc.yccardviewlib.shadow.ShadowLayout>

07. Points for attention in recyclerView

In the createShadowBitmap method, you can actually see that you need to create an bitmap object. As we all know, bitmap is easy to cause excessive memory. If the shadow effect is set for item in recyclerView, how to avoid repeated creation can be used at this time. So you can optimize the code under 1 on the basis of the above.

Create key first, mainly the key for map collection. Why use the object Key as the key of map here? This is a reference to the idea of glide caching pictures. When creating Key objects, you can pass in bitmap names and width and height attributes, and you need to rewrite hashCode and equals methods.


public class Key {

  private final String name;
  private final int width;
  private final int height;

  public Key(String name, int width, int height) {
    this.name = name;
    this.width = width;
    this.height = height;
  }

  public String getName() {
    return name;
  }

  public int getWidth() {
    return width;
  }

  public int getHeight() {
    return height;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) {
      return true;
    }
    if (o == null || getClass() != o.getClass()) {
      return false;
    }
    Key key = (Key) o;
    if (width != key.width) {
      return false;
    }
    if (height != key.height) {
      return false;
    }
    return name != null ? name.equals(key.name) : key.name == null;
  }

  @Override
  public int hashCode() {
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + width;
    result = 31 * result + height;
    return result;
  }
}

Then the access operation is as follows

When searching, search through Key. Note: Bitmap can be regarded as the same Bitmap only if it meets three conditions (height, width and name) at the same time.

Key key = new Key("bitmap", shadowWidth, shadowHeight);
Bitmap output = cache.get(key);
if(output == null){
  // Create based on width and height bitmap Background 
  output = Bitmap.createBitmap(shadowWidth, shadowHeight, Bitmap.Config.ARGB_8888);
  cache.put(key, output);
  LogUtil.v("bitmap Object -----","---- Create the object directly and store it in the cache ---");
} else {
  LogUtil.v("bitmap Object -----","---- Fetch an object from the cache ---");
}

Summarize


Related articles: