现在好多设计都喜欢用阴影,各种颜色、各种图形的阴影做起来费时费力,而官方的cardview不支持设置阴影颜色,阴影位置也是更拟物化的z轴阴影,不能符合设计的要求,因此专门写了一个工具。
GitHub地址:https://github.com/maxiaoyi/FpShadowLayout
用法:implementation 'com.mxy.fpshadowlayout:fpshadowlayout:0.0.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);
具体效果:
接下来变一下,x从n到0,y不变:
可以看到颜色反转了,变成从右往左平铺。接下来x保持都是0,y从0到n:
此时颜色变成了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)
可以看出渐变的区域之外,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));
}
上面代码只是只有一条边有阴影时的情况,还可能有两条、三条和四条边有阴影,所以需要做出各种判断组合,比如:上下,上左,左右下等等组合,其中关键一点是画出两条边相交时角落处的阴影怎么去画:
从上图可以看出,角落处是一个从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画出这段圆弧。
接下来看一下如果是圆角矩形的话怎么去画:
从上图可以看出,画圆角处的阴影时我们的半径为:阴影长度+圆角的半径,但此时画这个圆弧时,阴影的渐变是从圆心向外扩散,也就是说从圆心到矩形圆角的这段(上图左上角圆心到黑色圆弧区域)区域会被遮挡。解决办法就是这部分区域用无色去填充,原来的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*圆角半径)所示:
将上图中的左边阴影换到下边,即上下边有阴影,再来看左下角,下图中紫色的区域就是半圆角的区域:
综上所看,以左下角为例,一个圆角有四种画法:圆角朝上(阴影在左边),圆角朝下(阴影在下边),90度的圆角(圆角所连的两条边都有阴影),顶角为直角的圆角(两条边的夹角没有圆角)
至此,四个角的画法和单边、多边和多角组合到一起,差不多200多种组合,才能完工。。。