美文网首页Android开发Android开发经验谈Android开发
Android进阶:最简单的方式实现自定义阴影效果

Android进阶:最简单的方式实现自定义阴影效果

作者: Android_until | 来源:发表于2019-03-21 17:34 被阅读13次

    网话说UI设计有三宝 :透明,阴影,加圆角。很多UI在做设计的时候都喜欢做卡片形式,然后添加阴影。卡片UI确实挺好看,但是对Android开发者来说,显示阴影却并不那么手到擒来,因为Android对阴影没有做出很好的支持。

    CardView

    谷歌也许早就注意到了UI的三宝之一阴影,于是开发了一个继承FrameLayout的CardView公开发这使用,这个控件虽然在v7包里,但是需要单独添加依赖才可以使用,就好像不是亲生的似的!

    CardView本质上继承FrameLayout,需要添加依赖才可以使用:

    compile 'com.android.support:cardview-v7:25.3.1'
    

    当你知道它继承FrameLayout的时候你就知道怎么使用了,但是这个CardView有很多局限性,比如不能修改阴影的颜色,不能修改阴影的深浅。这就很诡异了,根本无法满足UI设计潮流的内心。
    那为了产品蒸蒸日上,早日走上人生巅峰,实现财富自由,应该如何让你的APP支持修改阴影的颜色呢?

    有个很暴力的办法,就是吧CardView的代码自己抠出来,然后自己定制,网上已经有很多人这样做了。
    比如这篇CSDN博主就这样做了:https://blog.csdn.net/wangjie_de/article/details/82993017

    思路:修改谷歌原生的CardView代码:原生的CardView的阴影逻辑分为高版本21以上的和低版本21一下的两种方案处理,其中低版本使用了渐变色来初六阴影渐变的效果,而高版本使用了 Elevation来设置阴影,但是 Elevation又没提供修改颜色的方法,所以作者就把高版本的实现方案拿掉了,统一采用低版本的处理方法,就可以修改颜色了。具体过程请看其博客。

    但是现在我自己探索了一个新的较为简单的添加阴影的实现方案,仅供参考

    ShadowCardView

    思路:首先要明确阴影的实现思路是什么,其实就是颜色导致的视觉错觉。说白了就是在你的Card周围画一个渐变的体现立体感的颜色。

    基于上述思路,我们在一个在一个view上画一个矩形的图形,让他周围有渐变色的阴影即可。于是我们想起几个API:

    • 类:Paint 用于在Android上画图的类,相当于画笔
    • 类:Canvas 相当于画布,Android上的view的绘制都与他相关
    • 方法:paint.setShadowLayer可以给绘制的图形增加阴影,还可以设置阴影的颜色

    如图,红色部分是我们绘制的图形,边框以内,红色之外的是阴影的显示部分。


    我们知道谷歌开发的CardView的控件继承了FrameLayout,方便我们自由扩展。那么我们也需要继承FrameLayout。

    ShadowCardView继承FrameLayout之后,可以重写其一个方法:

    @Override
        protected void dispatchDraw(Canvas canvas) {
            super.dispatchDraw(canvas);
        }
    

    这个方法是ViewGroup在绘制子View的时候调用的,那么我们可以在这个时候进行阴影的绘制。

    首先,这个方法已经为我们提供了这个View的画布:Canvas,我们可以直接在上面进行阴影的绘制,代码如下:

        Paint shadowPaint = new Paint();
        shadowPaint.setColor(Color.RED);
        shadowPaint.setStyle(Paint.Style.FILL);
        shadowPaint.setAntiAlias(true);
        float left = 45;
        float top = 45;
        float right = getWidth() - 45;
        float bottom = getHeight() - 45;
    
        shadowPaint.setShadowLayer(45, 0, 0, getContext().getResources().getColor(R.color.color_000000));
        RectF rectF = new RectF(left, top, right, bottom);
        canvas.drawRoundRect(rectF, 0, 0, shadowPaint);
        canvas.save();
    
    • 创建画笔,设置画笔的颜色,风格
    • 获取绘画的范围:ShadowCard的范围减去需要的阴影的范围,假如阴影的宽度为45px,则在ShadowCard内部的45px内进行绘制
    • 给画笔设置阴影的颜色,阴影的模糊度,模糊度值越大越模糊,且不能为0
    • 创建RectF,最后开始绘画。

    这样阴影就可以成功绘制了,这个方法代码量很少,很简单,也很实用。


    为了更好的封装,我们可以为上面需要的参数进行定制,比如阴影的颜色,阴影的宽度,阴影的上下偏移,阴影的模糊度。

    代码如下:

    public class ShadowViewCard extends FrameLayout {
        private static final int DEFAULT_VALUE_SHADOW_COLOR = R.color.shadow_default_color;
        private static final int DEFAULT_VALUE_SHADOW_CARD_COLOR = R.color.shadow_card_default_color;
        private static final int DEFAULT_VALUE_SHADOW_ROUND = 0;
    
        private static final int DEFAULT_VALUE_SHADOW_RADIUS = 10;
        private static final int DEFAULT_VALUE_SHADOW_TOP_HEIGHT = 5;
        private static final int DEFAULT_VALUE_SHADOW_LEFT_HEIGHT = 5;
        private static final int DEFAULT_VALUE_SHADOW_RIGHT_HEIGHT = 5;
        private static final int DEFAULT_VALUE_SHADOW_BOTTOM_HEIGHT = 5;
        private static final int DEFAULT_VALUE_SHADOW_OFFSET_Y = 0;
        private static final int DEFAULT_VALUE_SHADOW_OFFSET_X = DEFAULT_VALUE_SHADOW_TOP_HEIGHT / 3;
    
        private int shadowRound;
        private int shadowColor;
        private int shadowCardColor;
        private int shadowRadius;
        private int shadowOffsetY;
        private int shadowOffsetX;
        private int shadowTopHeight;
        private int shadowLeftHeight;
        private int shadowRightHeight;
        private int shadowBottomHeight;
    
        public ShadowViewCard(Context context) {
            this(context, null);
        }
    
        public ShadowViewCard(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
        }
    
        public ShadowViewCard(Context context, AttributeSet attrs, int defStyleAttr) {
            super(context, attrs, defStyleAttr);
            initView(context, attrs);
        }
    
        private void initView(Context context, AttributeSet attrs) {
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ShadowViewCard);
            shadowRound = a.getDimensionPixelSize(R.styleable.ShadowViewCard_shadowRound, DEFAULT_VALUE_SHADOW_ROUND);
            shadowColor = a.getColor(R.styleable.ShadowViewCard_shadowColor, getResources().getColor(DEFAULT_VALUE_SHADOW_COLOR));
            shadowCardColor = a.getColor(R.styleable.ShadowViewCard_shadowCardColor, getResources().getColor(DEFAULT_VALUE_SHADOW_CARD_COLOR));
            shadowTopHeight = a.getDimensionPixelSize(R.styleable.ShadowViewCard_shadowTopHeight, dp2px(getContext(), DEFAULT_VALUE_SHADOW_TOP_HEIGHT));
            shadowRightHeight = a.getDimensionPixelSize(R.styleable.ShadowViewCard_shadowRightHeight, dp2px(getContext(), DEFAULT_VALUE_SHADOW_RIGHT_HEIGHT));
            shadowLeftHeight = a.getDimensionPixelSize(R.styleable.ShadowViewCard_shadowLeftHeight, dp2px(getContext(), DEFAULT_VALUE_SHADOW_LEFT_HEIGHT));
            shadowBottomHeight = a.getDimensionPixelSize(R.styleable.ShadowViewCard_shadowBottomHeight, dp2px(getContext(), DEFAULT_VALUE_SHADOW_BOTTOM_HEIGHT));
            shadowOffsetY = a.getDimensionPixelSize(R.styleable.ShadowViewCard_shadowOffsetY, dp2px(getContext(), DEFAULT_VALUE_SHADOW_OFFSET_Y));
            shadowOffsetX = a.getDimensionPixelSize(R.styleable.ShadowViewCard_shadowOffsetX, dp2px(getContext(), DEFAULT_VALUE_SHADOW_OFFSET_X));
            shadowRadius = a.getInteger(R.styleable.ShadowViewCard_shadowRadius, DEFAULT_VALUE_SHADOW_RADIUS);
            a.recycle();
            setPadding(shadowLeftHeight, shadowTopHeight, shadowRightHeight, shadowBottomHeight);
            setLayerType(LAYER_TYPE_SOFTWARE, null);
        }
    
        public static int dp2px(Context context, float dipValue) {
            final float scale = context.getResources().getDisplayMetrics().density;
            return (int) (dipValue * scale + 0.5f);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            super.onDraw(canvas);
        }
    
        @Override
        protected void dispatchDraw(Canvas canvas) {
            Paint shadowPaint = new Paint();
            shadowPaint.setColor(Color.WHITE);
            shadowPaint.setStyle(Paint.Style.FILL);
            shadowPaint.setAntiAlias(true);
            float left = shadowLeftHeight;
            float top = shadowTopHeight;
            float right = getWidth() - shadowRightHeight;
            float bottom = getHeight() - shadowBottomHeight;
            shadowPaint.setShadowLayer(shadowRadius, shadowOffsetX, shadowOffsetX, shadowColor);
            RectF rectF = new RectF(left, top, right, bottom);
            canvas.drawRoundRect(rectF, shadowRound, shadowRound, shadowPaint);
            canvas.save();
            super.dispatchDraw(canvas);
        }
    }
    

    attr.xml

       <declare-styleable name="ShadowViewCard">
            <!--圆角度-->
            <attr name="shadowRound" format="dimension" />
            <!--阴影的高度-->
            <attr name="shadowLeftHeight" format="dimension" />
            <attr name="shadowTopHeight" format="dimension" />
            <attr name="shadowRightHeight" format="dimension" />
            <attr name="shadowBottomHeight" format="dimension" />
            <!--上方阴影的偏离度-->
            <attr name="shadowOffsetY" format="dimension" />
            <attr name="shadowOffsetX" format="dimension" />
            <!--阴影颜色-->
            <attr name="shadowCardColor" format="color" />
            <attr name="shadowColor" format="color" />
            <!--阴影颜色模糊度,越大越模糊-->
            <attr name="shadowRadius" format="integer" />
        </declare-styleable>
    

    color.xml

     <color name="shadow_default_color">#1a000000</color>
     <color name="shadow_card_default_color">#ffffff</color>
    

    有更多其他想法的朋友,欢迎加群:979045005,一起交流技术。吃货还整理了一份Android高级工程师技术大纲以及一套系统全面而且非常深入的Android进阶资料,需要的可以进群,找管理领取。

    高级进阶技术大纲 Android系统进阶资料

    相关文章

      网友评论

        本文标题:Android进阶:最简单的方式实现自定义阴影效果

        本文链接:https://www.haomeiwen.com/subject/ljqcvqtx.html