android单边阴影、多边阴影和圆角阴影

       现在好多设计都喜欢用阴影,各种颜色、各种图形的阴影做起来费时费力,而官方的cardview不支持设置阴影颜色,阴影位置也是更拟物化的z轴阴影,不能符合设计的要求,因此专门写了一个工具。

       GitHub地址:https://github.com/maxiaoyi/FpShadowLayout

      用法:implementation 'com.mxy.fpshadowlayout:fpshadowlayout:0.0.1'

       效果如图:

android单边阴影、多边阴影和圆角阴影_第1张图片

       核心实现是用LinearGradient,自定义viewGroup,设置阴影长度对应的padding,画出阴影。

       首先,来看一下LinearGradient的用法:

        int[] colors = {Color.parseColor("#ff0000"), Color.parseColor("#00ffffff")};

        float[] floats = new float[]{0f, 1.0f};

        LinearGradient gradient = new LinearGradient(0,0,100,0,colors, floats, Shader.TileMode.CLAMP);

        paint.setShader(gradient);

        RectF rectF = new RectF(0, 0, 100, 50);

        canvas.drawRect(rectF, paint);

colors是一组颜色数组,代表颜色从哪一种过渡到另外一种,可是两个或者三个颜色。floats是位置权重的数组,代表每一种颜色所占位置的权重,同样可以是两个或者三个,数量要和colors对应。

lineargradient的构造方法中,重点说一下前面四个参数,这四个参数实际上是定义了渐变的方向,x如果从0到n,y是从0到0的话,表示渐变在x轴上从左往右平铺。代码

new LinearGradient(0,0,100,0,colors, floats, Shader.TileMode.CLAMP);

具体效果:

android单边阴影、多边阴影和圆角阴影_第2张图片

接下来变一下,x从n到0,y不变:

android单边阴影、多边阴影和圆角阴影_第3张图片

可以看到颜色反转了,变成从右往左平铺。接下来x保持都是0,y从0到n:

android单边阴影、多边阴影和圆角阴影_第4张图片

此时颜色变成了y轴上从上往下平铺,同理,y如果从n到0的话,颜色也会出现反转,从下往上平铺。所以,改变x和y我们就可以实现渐变从不同方向开始。

在上面代码中RectF rectF = new RectF(0, 0, 100, 50),画了一个矩形区域,new LinearGradient(0,0,100,0,colors, floats, Shader.TileMode.CLAMP),这句代码表示颜色渐变的区域,此时我们把渐变x=100,变成x=50,为了区分明显,我加了一个矩形边框

        LinearGradient gradient = new LinearGradient(0,0,50,0,colors, floats, Shader.TileMode.CLAMP);

        paint.setShader(gradient);

        RectF rectF = new RectF(0, 0, 100, 50);

        canvas.drawRect(rectF, paint);

        Paint paint1 = new Paint();

        paint1.setColor(Color.GREEN);

        paint1.setStyle(Paint.Style.STROKE);

        paint1.setStrokeWidth(2);

        canvas.drawRect(rectF,paint1);

为了区别明显,我把colors中第二个颜色白色改成黑色,int[] colors = {Color.parseColor("#ff0000"), Color.parseColor("#00000000")};

效果对比如下(左边是x=100,右边是x=50)

android单边阴影、多边阴影和圆角阴影_第5张图片

可以看出渐变的区域之外,view还是保持原来的样子不变,至此我们可以看出view所在区域和渐变区域的关系。接下来就可以进入正题,怎么去画出view的阴影。

首先,关闭硬件加速,setLayerType(View.LAYER_TYPE_SOFTWARE, null)设置view的阴影所在边的padding:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        float effect = mShadowRadius;

        int paddingLeft = 0;

        int paddingTop = 0;

        int paddingRight = 0;

        int paddingBottom = 0;

        if ((mShadowSide & LEFT) == LEFT) {
            paddingLeft = (int) effect;
        }

        if ((mShadowSide & TOP) == TOP) {
            paddingTop = (int) effect;
        }

        if ((mShadowSide & RIGHT) == RIGHT) {
            paddingRight = (int) effect;
        }

        if ((mShadowSide & BOTTOM) == BOTTOM) {
            paddingBottom = (int) effect;
        }

        this.setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);

        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    }

获取属性值:

        TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.Fp_ShadowLayout);

        if (typedArray != null) {

            mShadowColor = typedArray.getColor(R.styleable.Fp_ShadowLayout_fp_shadowColor,
                    getContext().getResources().getColor(android.R.color.black));

            mShadowRadius = typedArray.getDimension(R.styleable.Fp_ShadowLayout_fp_shadowRadius, dip2px(0));

            mRoundCornerRadius = typedArray.getDimension(R.styleable.Fp_ShadowLayout_fp_shadowRoundRadius, dip2px(0));

            mShadowSide = typedArray.getInt(R.styleable.Fp_ShadowLayout_fp_shadowSide, ALL);

            mShadowShape = typedArray.getInt(R.styleable.Fp_ShadowLayout_fp_shadowShape, SHAPE_RECTANGLE);

            mCornerPosition = typedArray.getInt(R.styleable.Fp_ShadowLayout_fp_round_corner, CORNER_ALL);

            typedArray.recycle();

        }

其中有阴影的颜色,阴影的长度,圆角的大小,阴影所在的边,圆角所在的边,是矩形还是圆角矩形。接下来就要在ondraw里面画出阴影,如果是矩形的话部分代码如下:

if ((mShadowSide & LEFT) == LEFT && (mShadowSide & TOP) != TOP && (mShadowSide & RIGHT) != RIGHT && (mShadowSide & BOTTOM) != BOTTOM) {
                //画左边阴影
                drawRectLinearGradient(canvas, mShadowRadius, 0, 0, 0, colors, floats, Shader.TileMode.CLAMP, new RectF(0, 0, mShadowRadius, mViewHeight));
            } else if ((mShadowSide & LEFT) != LEFT && (mShadowSide & TOP) == TOP && (mShadowSide & RIGHT) != RIGHT && (mShadowSide & BOTTOM) != BOTTOM) {
                //画上边阴影
                drawRectLinearGradient(canvas, 0, mShadowRadius, 0, 0, colors, floats, Shader.TileMode.CLAMP, new RectF(0, 0, mViewWidth, mShadowRadius));
            } else if ((mShadowSide & LEFT) != LEFT && (mShadowSide & TOP) != TOP && (mShadowSide & RIGHT) == RIGHT && (mShadowSide & BOTTOM) != BOTTOM) {
                //画右边阴影
                drawRectLinearGradient(canvas, mViewWidth - mShadowRadius, 0, mViewWidth, 0, colors, floats, Shader.TileMode.CLAMP, new RectF(mViewWidth - mShadowRadius, 0, mViewWidth, mViewHeight));
            } else if ((mShadowSide & LEFT) != LEFT && (mShadowSide & TOP) != TOP && (mShadowSide & RIGHT) != RIGHT && (mShadowSide & BOTTOM) == BOTTOM) {
                //画下边阴影
                drawRectLinearGradient(canvas, 0, mViewHeight - mShadowRadius, 0, mViewHeight, colors, floats, Shader.TileMode.CLAMP, new RectF(0, mViewHeight - mShadowRadius, mViewWidth, mViewHeight));
            }

上面代码只是只有一条边有阴影时的情况,还可能有两条、三条和四条边有阴影,所以需要做出各种判断组合,比如:上下,上左,左右下等等组合,其中关键一点是画出两条边相交时角落处的阴影怎么去画:

android单边阴影、多边阴影和圆角阴影_第6张图片

从上图可以看出,角落处是一个从180度开始,扫过90度的四分之一弧度的半圆,此时就要用到RadialGradient了:

        RadialGradient linearGradient = new RadialGradient(centerX, centerY, radius, colors, positions, tile);

        mPaint.setShader(linearGradient);

        canvas.drawArc(rectF, startAngle, sweepAngle, true, mPaint);

可以看出RadialGradient和LinearGradient差不多,最主要的是圆心和半径,其他的参数和LinearGradient一样。圆心就是角落顶点的坐标,半径就是阴影的半径,然后用canvas的drawArc画出这段圆弧。

接下来看一下如果是圆角矩形的话怎么去画:

android单边阴影、多边阴影和圆角阴影_第7张图片

从上图可以看出,画圆角处的阴影时我们的半径为:阴影长度+圆角的半径,但此时画这个圆弧时,阴影的渐变是从圆心向外扩散,也就是说从圆心到矩形圆角的这段(上图左上角圆心到黑色圆弧区域)区域会被遮挡。解决办法就是这部分区域用无色去填充,原来的colors有两个颜色,现在变成三个,对应的positions也要改成三个值:

    private float[] creatTwoPositionFloat() {

        float[] floats = new float[]{0f, 1f};

        return floats;

    }

    private float[] creatThreePositionFloat() {

        float[] floats = new float[]{mRoundCornerRadius / (mShadowRadius + mRoundCornerRadius), mRoundCornerRadius / (mShadowRadius + mRoundCornerRadius), 1f};

        return floats;
    }

    private int[] creatTwoPositionColor() {

        int[] colors = new int[]{mShadowColor, 0x00ffffff};

        return colors;

    }

    private int[] creatThreePositionColor() {

        int[] colors = new int[]{0x00ffffff, mShadowColor, 0x00ffffff};

        return colors;

    }

同时要关注左下角,这里的圆弧和左上角的又不一样,这里需要一个以下图点A(圆角半径+阴影半径,viewHeight-圆角半径)为圆心,圆角半径+阴影半径为半径画一个圆,同时这个圆的区域限定如下图左下角的绿色矩形(长度=2*(圆角半径+阴影半径),高度=2*圆角半径)所示:

android单边阴影、多边阴影和圆角阴影_第8张图片

将上图中的左边阴影换到下边,即上下边有阴影,再来看左下角,下图中紫色的区域就是半圆角的区域:

android单边阴影、多边阴影和圆角阴影_第9张图片

综上所看,以左下角为例,一个圆角有四种画法:圆角朝上(阴影在左边),圆角朝下(阴影在下边),90度的圆角(圆角所连的两条边都有阴影),顶角为直角的圆角(两条边的夹角没有圆角)

android单边阴影、多边阴影和圆角阴影_第10张图片

至此,四个角的画法和单边、多边和多角组合到一起,差不多200多种组合,才能完工。。。

 

你可能感兴趣的:(android自定义view)