Android加阴影 -(一键无脑引入,无兼容问题)

老规矩,无图言 × ,先上图
在这里插入图片描述
好像看不清,换个颜色再来一张吧,再加宽点
在这里插入图片描述
这颜色好丑哈哈哈哈哈哈,这是从黑到白色,你们用的时候一定要用有透明度的色值啊哈哈哈

接下来上代码!

	
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

导致就这样吧,好累了上了一天班,关于注释里提到的 “可能会导致某些父布局里的元素超出容器” 的问题,如果各位有什么解决方案的话,请务必留言交流!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

俺不理解

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值