Android加阴影 -(一键无脑引入,无兼容问题)
16lz
2021-01-26
老规矩,无图言 × ,先上图
好像看不清,换个颜色再来一张吧,再加宽点
这颜色好丑哈哈哈哈哈哈,这是从黑到白色,你们用的时候一定要用有透明度的色值啊哈哈哈
接下来上代码!
import android.content.Context;import android.graphics.Color;import android.graphics.drawable.GradientDrawable;import android.support.constraint.ConstraintLayout;import android.view.View;import android.view.ViewGroup;import android.view.ViewParent;import android.widget.RelativeLayout;import org.jetbrains.annotations.NotNull;import java.lang.ref.WeakReference;import javax.annotation.Nullable;/** * @author lzd * * 阴影工具类 */public class ShadowUtil { private WeakReference<View> needShadowView; private WeakReference<ViewGroup> shadowView; private int scaleUnitHor, scaleUnitVer; private int lastWidth, lastHeight; private static ShadowUtil Create(){ return Create(0, 0); } /** * 在需要设置阴影的 view 尺寸变小 时,自动缩小的速度 * Warning 注意:传入的数值不可大于 设置阴影的 view 尺寸,否则会出问题 */ public static ShadowUtil Create(int scaleUnitHor, int scaleUnitVer) { return new ShadowUtil(scaleUnitHor, scaleUnitVer); } private ShadowUtil (int scaleUnitHor, int scaleUnitVer) { this.scaleUnitHor = scaleUnitHor; this.scaleUnitVer = scaleUnitVer; } private void initListener(WeakReference<View> view, WeakReference<ViewGroup> shadowView) { if (view != null && view.get() != null) { view.get().getViewTreeObserver().addOnGlobalLayoutListener(() -> { if (shadowView == null || shadowView.get() == null) { return; } setViewFamilyClipChildren(shadowView.get()); updateParentViewSize(shadowView.get(), view.get()); }); } } /** * 获取当前 阴影 shadowView */ public View getShadowView() { return shadowView == null ? null : shadowView.get(); } /** * 获取当前 被添加阴影的 shadowView */ public View getNeedShadowView() { return needShadowView == null ? null : needShadowView.get(); } /** * 更新阴影尺寸信息 * @param parentView 阴影父布局 * @param view 需要设置阴影,正在监听尺寸 的 view */ private void updateParentViewSize(ViewGroup parentView, View view) { if (lastWidth != Math.max(view.getWidth() - scaleUnitHor, 0) || lastHeight != Math.max(view.getHeight() - scaleUnitVer, 0)) { lastWidth = Math.max(view.getWidth() - scaleUnitHor, 0); lastHeight = Math.max(view.getHeight() - scaleUnitVer, 0); if (view instanceof RelativeLayout) { parentView.setLayoutParams(new RelativeLayout.LayoutParams(lastWidth, lastHeight)); } else if (view instanceof ConstraintLayout) { parentView.setLayoutParams(new ConstraintLayout.LayoutParams(lastWidth, lastHeight)); } } } private void setViewFamilyClipChildren(ViewGroup view) { if (view == null) { return; } view.setClipChildren(false); view.setClipToPadding(false); ViewParent rooter = view.getParent(); while (rooter instanceof ViewGroup) { ((ViewGroup) rooter).setClipChildren(false); ((ViewGroup) rooter).setClipToPadding(false); rooter = rooter.getParent(); } } /** * 阴影 Group 数据结构 * 作为 {@link ShadowUtil#initShadowView(Context, View)} 的返回值生成 */ public static class ShadowGroup{ /** * {@link ShadowUtil#initShadowView(Context, View)} 后的新的 content View */ View contentView; /** * {@link ShadowUtil#initShadowView(Context, View)} 后的 阴影容器 */ View shadowContainer; View needShadow; private ShadowGroup(View contentView, View shadowContainer, View needShadow) { this.contentView = contentView; this.shadowContainer = shadowContainer; this.needShadow = needShadow; } public View getContentView() { return contentView; } public View getShadowContainer() { return shadowContainer; } public View getNeedShadow() { return needShadow; } } /** * 初始化需要添加阴影的 View * @param needShadow 需要添加阴影的View ,注意:这个 View 不能有 parent * @return 阴影的容器,需要再调用 {@link ShadowUtil#setViewBoundShadow(View)} * 方法对此返回值设置阴影 */ public static @Nullable ShadowGroup initShadowView(Context context, View needShadow) { if (needShadow.getParent() != null) { return null; } ConstraintLayout contentView = new ConstraintLayout(context); contentView.setLayoutParams(new ConstraintLayout.LayoutParams( ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)); // region 初始化需要添加阴影的 view 的相关参数 ConstraintLayout.LayoutParams needShadowViewParams; if (needShadow.getLayoutParams() instanceof ConstraintLayout.LayoutParams) { needShadowViewParams = (ConstraintLayout.LayoutParams) needShadow.getLayoutParams(); } else { needShadowViewParams = new ConstraintLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } needShadowViewParams.bottomToBottom = ConstraintLayout.LayoutParams.PARENT_ID; needShadowViewParams.topToTop = ConstraintLayout.LayoutParams.PARENT_ID; needShadowViewParams.startToStart = ConstraintLayout.LayoutParams.PARENT_ID; needShadowViewParams.endToEnd = ConstraintLayout.LayoutParams.PARENT_ID; needShadowViewParams.setMargins( DisplayUtils.dip2px(context, 8), DisplayUtils.dip2px(context, 8), DisplayUtils.dip2px(context, 8), DisplayUtils.dip2px(context, 8) ); needShadow.setLayoutParams(needShadowViewParams); if (needShadow.getId() == View.NO_ID) { needShadow.setId(View.generateViewId()); } contentView.addView(needShadow); // endregion // region 初始化 shadow container RelativeLayout shadowContainer = new RelativeLayout(context); ConstraintLayout.LayoutParams shadowParams = new ConstraintLayout.LayoutParams(0, 0); shadowParams.startToStart = needShadow.getId(); shadowParams.endToEnd = needShadow.getId(); shadowParams.topToTop = needShadow.getId(); shadowParams.bottomToBottom = needShadow.getId(); shadowContainer.setLayoutParams(shadowParams); contentView.addView(shadowContainer); // endregion return new ShadowGroup(contentView, shadowContainer, needShadow); } /** * 默认 设置阴影的方法 * @return 阴影相关结果集 */ public static ShadowUtil setViewBoundShadow(@NotNull View view) { return setViewBoundShadow(view, ShadowUtil.Create()); } /** * 默认 设置阴影的方法 * @param shadowUtilValues 可使用 {@link ShadowUtil#Create(int, int)} 传入 hor 和 ver * @return 阴影相关结果集 */ public static ShadowUtil setViewBoundShadow(@NotNull View view, @NotNull ShadowUtil shadowUtilValues) { //region 默认缩放速度 if (shadowUtilValues.scaleUnitVer == 0) { shadowUtilValues.scaleUnitVer = DisplayUtils.dip2px(view.getContext(), 2); } if (shadowUtilValues.scaleUnitHor == 0) { shadowUtilValues.scaleUnitHor = DisplayUtils.dip2px(view.getContext(), 2); } //endregion int shadowSize = DisplayUtils.dip2px(view.getContext(), 8); // 默认阴影颜色 int startColor = Color.parseColor("#00000000"); int endColor = Color.parseColor("#050F0F0F"); return setViewBoundShadow(view, shadowSize, startColor, endColor, shadowUtilValues); } /** * 为 view 设置阴影 * #Warning: 使用此方法后 viewTree的父容器 的 ClipChildren 属性会置true * 这里可能会导致某些父布局里的元素超出容器,后期需要优化 * * @param view 需要设置阴影的 view * 如果是 ViewGroup 需要是 relativeLayout 或 ConstrainLayout * * @param shadowSize 阴影宽度 * @param startColor 远离 view 一侧的色值 * @param endColor 贴近 view 一侧的色值 * @param shadowUtilValues 在需要设置阴影的 view 尺寸变小 时,自动缩小的速度 信息 * @return 失败 null ; 成功则返回阴影 所属的 parentView,可通过设置 visibility 控制是否显示 * 为 view 设置阴影 * * #Note 使用此方法后 若导致某一父容器下的子控件超出,可在适当的位置 将容器 ClipChildren 属性置 false */ public static ShadowUtil setViewBoundShadow(@NotNull View view, int shadowSize , int startColor, int endColor, @NotNull ShadowUtil shadowUtilValues) { if (shadowSize < 0) { return null; } if (shadowUtilValues.getShadowView() != null) { // 已有阴影,移除之前的阴影,并刷新数据 ((ViewGroup) shadowUtilValues.getShadowView().getParent()) .removeView(shadowUtilValues.getShadowView()); shadowUtilValues.shadowView = null; shadowUtilValues.needShadowView = null; shadowUtilValues.lastHeight = shadowUtilValues.lastWidth = 0; } ViewGroup parentView = new ConstraintLayout(view.getContext()); if (view instanceof RelativeLayout || view instanceof ConstraintLayout) { ((ViewGroup) view).addView(parentView); if (view instanceof ConstraintLayout) { ConstraintLayout.LayoutParams params = new ConstraintLayout.LayoutParams(0, 0); params.startToStart = ConstraintLayout.LayoutParams.PARENT_ID; params.topToTop = ConstraintLayout.LayoutParams.PARENT_ID; parentView.setLayoutParams(params); } } else if (!(view instanceof ViewGroup)){ parentView.addView(view); } else { return null; } class childValue { int width; int height; int horPos; int verPos; GradientDrawable.Orientation orientation; public childValue(int width, int height, int horPos, int verPos, GradientDrawable.Orientation orientation) { this.width = width; this.height = height; this.horPos = horPos; this.verPos = verPos; this.orientation = orientation; } } childValue[] childValues = new childValue[]{ new childValue(shadowSize, shadowSize, 1, 1, null), new childValue(shadowSize, shadowSize, -1, 1, null), new childValue(shadowSize, shadowSize, -1, -1, null), new childValue(shadowSize, shadowSize, 1, -1, null), new childValue(shadowSize, 0, 1, 0 , GradientDrawable.Orientation.RIGHT_LEFT), new childValue(shadowSize, 0, -1, 0 , GradientDrawable.Orientation.LEFT_RIGHT), new childValue(0, shadowSize, 0, -1 , GradientDrawable.Orientation.TOP_BOTTOM), new childValue(0, shadowSize, 0, 1 , GradientDrawable.Orientation.BOTTOM_TOP), }; int[] dirViewsId = new int[4]; for (int i = 0; i < childValues.length; i++) { // region 尺寸 ConstraintLayout.LayoutParams childParams = new ConstraintLayout.LayoutParams(childValues[i].width, childValues[i].height); View child = new View(view.getContext()); //endregion // region 位置 if (childValues[i].horPos == 1) { childParams.startToEnd = ConstraintLayout.LayoutParams.PARENT_ID; childParams.leftMargin = shadowUtilValues.scaleUnitHor; } else if (childValues[i].horPos == -1) { childParams.endToStart = ConstraintLayout.LayoutParams.PARENT_ID; } if (childValues[i].verPos == 1) { childParams.topToBottom = ConstraintLayout.LayoutParams.PARENT_ID; childParams.topMargin = shadowUtilValues.scaleUnitVer; } else if (childValues[i].verPos == -1) { childParams.bottomToTop = ConstraintLayout.LayoutParams.PARENT_ID; } if (i < dirViewsId.length) { // 前四个为四个角 child.setId(View.generateViewId()); dirViewsId[i] = child.getId(); } else { // 后四个以四个角为基准来显示 if (childValues[i].horPos == 1 && childValues[i].verPos == 0) { childParams.bottomToTop = dirViewsId[0]; childParams.topToBottom = dirViewsId[3]; } else if (childValues[i].horPos == -1 && childValues[i].verPos == 0) { childParams.topToBottom = dirViewsId[2]; childParams.bottomToTop = dirViewsId[1]; } else if (childValues[i].horPos == 0 && childValues[i].verPos == -1) { childParams.endToStart = dirViewsId[3]; childParams.startToEnd = dirViewsId[2]; } else if (childValues[i].horPos == 0 && childValues[i].verPos == 1) { childParams.endToStart = dirViewsId[0]; childParams.startToEnd = dirViewsId[1]; } } //endregion // region 背景图 GradientDrawable childBg = new GradientDrawable(); if (childValues[i].orientation != null) { childBg.setColors(new int[]{startColor, endColor}); childBg.setOrientation(childValues[i].orientation); } else { childBg.setColors(new int[]{endColor, startColor}); childBg.setGradientRadius(shadowSize); childBg.setGradientCenter(Math.max(0, -childValues[i].horPos) , Math.max(0, -childValues[i].verPos)); childBg.setGradientType(GradientDrawable.RADIAL_GRADIENT); childBg.setCornerRadii(new float[]{ childValues[i].horPos + childValues[i].verPos == -2 ? shadowSize : 0, childValues[i].horPos + childValues[i].verPos == -2 ? shadowSize : 0, childValues[i].horPos == 1 && childValues[i].verPos == -1 ? shadowSize : 0, childValues[i].horPos == 1 && childValues[i].verPos == -1 ? shadowSize : 0, childValues[i].horPos + childValues[i].verPos == 2 ? shadowSize : 0, childValues[i].horPos + childValues[i].verPos == 2 ? shadowSize : 0, childValues[i].horPos == -1 && childValues[i].verPos == 1 ? shadowSize : 0, childValues[i].horPos == -1 && childValues[i].verPos == 1 ? shadowSize : 0 }); } //endregion child.setBackground(childBg); child.setLayoutParams(childParams); parentView.addView(child); } shadowUtilValues.shadowView = new WeakReference<>(parentView); shadowUtilValues.needShadowView = new WeakReference<>(view); shadowUtilValues.initListener(new WeakReference<>(view) , new WeakReference<>(parentView)); return shadowUtilValues; } /** * 重新注册阴影的尺寸监听,用处: * 例如:在 popupWindow 被 dismiss 后,视图的 GlobalLayoutListener 会被移除, * 如果重新 show 后尺寸会发生改变,则无法监听到,建议在 show 时调用此方法重新监听; * 如果重新 show 后尺寸不再改变,则无需调用此方法。 * * @param shadowUtilValues 需要传入 {@link ShadowUtil#setViewBoundShadow(View)} 等方法的返回值 */ public static void reInit(ShadowUtil shadowUtilValues) { if (shadowUtilValues.needShadowView == null || shadowUtilValues.needShadowView.get() == null || shadowUtilValues.shadowView == null || shadowUtilValues.shadowView.get() == null) { return; } shadowUtilValues.lastHeight = shadowUtilValues.lastWidth = 0; shadowUtilValues.initListener(new WeakReference<>(shadowUtilValues.getNeedShadowView()) , new WeakReference<>((ViewGroup)shadowUtilValues.getShadowView())); shadowUtilValues.needShadowView.get().requestLayout(); }}
使用方法示例:
//region 已经是 RelativeLayout 或 ConstrainLayoutShadowUtil shadowUtil = ShadowUtil.setViewBoundShadow(shadowContainer);View shadowView = shadowUtil.getShadowView();shadowView.setVisiblity(显示或隐藏);//endregion//region 不是 RelativeLayout 或 ConstrainLayoutShadowUtil.ShadowGroup shadowGroup = ShadowUtil.initShadowView(context, textView);if (shadowGroup == null) { return;}ShadowUtil.setViewBoundShadow(shadowGroup.getShadowContainer());//endregion//region 使用自定义的收缩速度ShadowUtil shadowUtil = ShadowUtil.Create(DisplayUtils.dip2px(context, 2), DisplayUtils.dip2px(context, 24));ShadowUtil.setViewBoundShadow(needShowShadow, shadowUtil);//endregion//region 布局变化后,监听失效后ShadowUtil.reInit(shadowUtil);//endregion
导致就这样吧,好累了上了一天班,关于注释里提到的 “可能会导致某些父布局里的元素超出容器” 的问题,如果各位有什么解决方案的话,请务必留言交流!!!
更多相关文章
- android设置中的Preferencescreen使用方法介绍与分析
- android shape的使用 自定义 控件形状
- Android(安卓)CoordinatorLayout使用 标题由图片变纯色
- Android全屏 去除标题栏和状态栏
- Android(安卓)WebView使用经验总结
- XUI 一个简洁而优雅的Android原生UI框架,解放你的双手!
- Android各版本间API的差异 - ActionBar
- 【android之锚定视图】
- Android(安卓)Design Support 介绍