Table of Contents Introduction
- 01. What are the ways to implement shadow effects?
- 02. Implement the shadow effect Api
- 03. What should you pay attention to when setting shadows
- 04. Common Shapes achieve shadow effects
- 05. Custom shadow effect control
- 06.How to use this shadow control
- 07. Notes on using recyclerView
01. What are the ways to implement shadow effects?
What are the ways to implement shadow effects
- First: Use CardView, but cannot set the shadow color
- The second type: shape superposition is used, which makes it difficult to optimize the later UI effect
- The third type: UI cut
- Fourth type: Custom View
Analysis of the reasons for denying the above two solutions?
- The CardView gradient color and shadow effect of the first solution are difficult to control, and can only support linear or ring-mounted gradients, which does not meet the needs, because the shadow itself is surrounded by a light color, and the colors are roughly the same at the level of a rectangular box. Moreover, this CardView has many limitations, such as the color of the shadow cannot be modified, and the depth of the shadow cannot be modified. So this idea cannot achieve this requirement.
- The second one uses shape overlay, which can achieve shadow effects, but affects the UI, and the shadow part occupies pixels and is inflexible.
- The third plan asked about ui. The result they gave is that if you use cut-out, it is difficult to mark it. As an excellent designer, most of them are sensitive to pixels, and if there is a little inconsistency in the pixels on the interface, it is unwilling tolerate it.
- In the following open source case code, I will show the shadow effects implemented by these different solutions one by one.
Some online introduction of shadow effect solutions
- All the deep technology is also prepared for needs. That is, it requires practice and can be used in actual development. This article no longer introduces the principle of shadow effect in abstractly, and understands how to deal with offset light in three-dimensional space to achieve shadow disparity, etc. I have read some articles online but I have not read or understood it. This blog directly achieves the expected effect by calling the API.
Does the shadow occupy a place
- Use CardView shadows to occupy space, and shadow colors and effects cannot be set.
- Use shape shadow to set the shadow color, but it is a placeholder
02. Implement the shadow effect Api
Think about how to achieve the View shadow effect?
- First of all, we must clarify what the implementation idea is, which is actually the visual illusion caused by color. To put it bluntly, draw a gradient color around your card that reflects three-dimensionality. Based on the above idea, we can draw a rectangular figure on a view, so that there are gradient shades around it. So we think of several APIs:
- Class: Paint is a class used to draw pictures on Android, equivalent to a brush
- Class: Canvas is equivalent to canvas, and the drawing of view on Android is related to it
- Method: You can add shadows to the drawn graphics, and you can also set the color of the shadows.
(float radius, float dx, float dy, int shadowColor);
- This method can achieve such an effect, and when drawing with canvas, it adds a layer of shadow effect to the view.
Let me briefly introduce these parameters:
- radius: Shadow radius, which can mainly control the blur effect of the shadow and the size of the shadow spreading out.
- dx: The offset of the shadow in the X-axis direction
- dy: The offset of the shadow in the Y-axis direction
- shadowColor: Shadow Color.
Finally found the color setting, and control the shadow color of the view by setting shadowColor.
03. What should you pay attention to when setting shadows
There are several properties involved, the width of the shadow, the distance from the view to the Viewgroup. If the view is as large as the parent layout, the shadow will be difficult to display. If you want to be able to display it, you must set clipChildren=false.
There is also the rounded corners that come with the view. Most of the backgrounds have rounded corners. For example, the rounded corners in the picture above need to achieve a high degree of restoration of the shadow, so that the rounded corners of the shadow are consistent with the background.
04. Common Shapes achieve shadow effects
Multiple drawables are superimposed
- Use layer-list to stack multiple drawables together in order. By default, the drawables in all items will automatically scale according to the size of the attached view. The items in layer-list are superimposed from bottom to top in order, that is, the items defined first are below, and the ones that are subsequently stacked on top in sequence.
The shadow effect code is as follows
- There are multiple layers here, so some are omitted. Then it can be achieved directly by setting the background property of the control.
<?xml version="1.0" encoding="utf-8"?> <layer-list xmlns:andro> <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
First customize the properties
<declare-styleable name="ShadowLayout"> <!--The rounded corner size of the shadow--> <attr name="yc_cornerRadius" format="dimension" /> <!--The spread range of shadows(It can also be understood as the degree of diffusion)--> <attr name="yc_shadowLimit" format="dimension" /> <!--Shadow color--> <attr name="yc_shadowColor" format="color" /> <!--xThe offset of the axis--> <attr name="yc_dx" format="dimension" /> <!--yThe offset of the axis--> <attr name="yc_dy" format="dimension" /> <!--Is there a shadow on the left?--> <attr name="yc_leftShow" format="boolean" /> <!--Is there a shadow on the right?--> <attr name="yc_rightShow" format="boolean" /> <!--Is there a shadow displayed above--> <attr name="yc_topShow" format="boolean" /> <!--Is there a shadow displayed below--> <attr name="yc_bottomShow" format="boolean" /> </declare-styleable>
The code is as follows
/** * <pre> * @author yangchong * blog : /yangchong211 * time: 2018/7/20 * desc : Custom shadow * revise: */ public class ShadowLayout extends FrameLayout { /** * Shadow color */ private int mShadowColor; /** * The diffusion range of the shadow (can also be understood as the diffusion degree) */ private float mShadowLimit; /** * Shadow rounded corner size */ private float mCornerRadius; /** * The offset of the x-axis */ private float mDx; /** * The offset of the y-axis */ private float mDy; /** * Is there a shadow on the left? */ private boolean leftShow; /** * Is there a shadow on the right? */ private boolean rightShow; /** * Is there a shadow displayed above */ private boolean topShow; /** * Is there a shadow displayed 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) { (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) { (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 + (mDx)); int yPadding = (int) (mShadowLimit + (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, ); BitmapDrawable drawable = new BitmapDrawable(getResources(), bitmap); if (.SDK_INT <= Build.VERSION_CODES.JELLY_BEAN) { setBackgroundDrawable(drawable); } else { setBackground(drawable); } } private void initAttributes(Context context, AttributeSet attrs) { TypedArray attr = getTypedArray(context, attrs, ); if (attr == null) { return; } try { //Default is to display leftShow = (.ShadowLayout_yc_leftShow, true); rightShow = (.ShadowLayout_yc_rightShow, true); bottomShow = (.ShadowLayout_yc_bottomShow, true); topShow = (.ShadowLayout_yc_topShow, true); mCornerRadius = (.ShadowLayout_yc_cornerRadius, 0); mShadowLimit = (.ShadowLayout_yc_shadowLimit, 0); mDx = (.ShadowLayout_yc_dx, 0); mDy = (.ShadowLayout_yc_dy, 0); mShadowColor = (.ShadowLayout_yc_shadowColor, getResources().getColor(.default_shadow_color)); } finally { (); } } private TypedArray getTypedArray(Context context, AttributeSet attributeSet, int[] attr) { return (attributeSet, attr, 0, 0); } private Bitmap createShadowBitmap(int shadowWidth, int shadowHeight, float cornerRadius, float shadowRadius, float dx, float dy, int shadowColor, int fillColor) { //Create a bitmap background based on width and height Bitmap output = (shadowWidth, shadowHeight, .ARGB_8888); //Draw with artboard canvas Canvas canvas = new Canvas(output); RectF shadowRect = new RectF(shadowRadius, shadowRadius, shadowWidth - shadowRadius, shadowHeight - shadowRadius); if (dy > 0) { += dy; -= dy; } else if (dy < 0) { += (dy); -= (dy); } if (dx > 0) { += dx; -= dx; } else if (dx < 0) { += (dx); -= (dx); } Paint shadowPaint = new Paint(); (true); (fillColor); (); if (!isInEditMode()) { (shadowRadius, dx, dy, shadowColor); } (shadowRect, cornerRadius, cornerRadius, shadowPaint); return output; } } ```
06.How to use this shadow control
Very simple, as shown below
< 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="Full round corners" android:textColor="#000" /> </>
07. Notes on using recyclerView
In the createShadowBitmap method, you can actually see that you need to create a bitmap object. Everyone knows that bitmap is more likely to cause too much memory. If you set a shadow effect on the item in the recyclerView, then how to avoid repeated creation? You can use cache at this time. So you can optimize the code based on the above.
Create a key first, mainly the key used for map collection. Why use the object key as the map key here? This is the idea of glide cache images. You can pass in the bitmap name and width and height attributes when creating a Key object, and you need to rewrite the 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) { = name; = width; = 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() != ()) { return false; } Key key = (Key) o; if (width != ) { return false; } if (height != ) { return false; } return name != null ? () : == null; } @Override public int hashCode() { int result = name != null ? () : 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 needs to meet three conditions (height, width, and name) at the same time to be considered as the same Bitmap.
Key key = new Key("bitmap", shadowWidth, shadowHeight); Bitmap output = (key); if(output == null){ //Create a bitmap background based on width and height output = (shadowWidth, shadowHeight, .ARGB_8888); (key, output); ("bitmap object-----","----Create the object directly and then save it in the cache---"); } else { ("bitmap object-----","----Fetch the object from the cache----"); }
Summarize
The above is the entire content of this article. I hope that the content of this article has certain reference value for your study or work. Thank you for your support.