老规矩,无图言 × ,先上图
好像看不清,换个颜色再来一张吧,再加宽点
这颜色好丑哈哈哈哈哈哈,这是从黑到白色,你们用的时候一定要用有透明度的色值啊哈哈哈
接下来上代码!
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 设置阴影
* <b>#Warning: 使用此方法后 viewTree的父容器 的 ClipChildren 属性会置true
* 这里可能会导致某些父布局里的元素超出容器,后期需要优化</b>
*
* @param view 需要设置阴影的 view
* <b>如果是 ViewGroup 需要是 relativeLayout 或 ConstrainLayout</b>
*
* @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 或 ConstrainLayout
ShadowUtil shadowUtil = ShadowUtil.setViewBoundShadow(shadowContainer);
View shadowView = shadowUtil.getShadowView();
shadowView.setVisiblity(显示或隐藏);
//endregion
//region 不是 RelativeLayout 或 ConstrainLayout
ShadowUtil.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
导致就这样吧,好累了上了一天班,关于注释里提到的 “可能会导致某些父布局里的元素超出容器” 的问题,如果各位有什么解决方案的话,请务必留言交流!!!