自定义View之文字图形图片的阴影

Posted LQS_Android

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义View之文字图形图片的阴影相关的知识,希望对你有一定的参考价值。

一、setShadowLayer与阴影效果

Android 开发了一个添加阴影效果的函数 setShadowLayer(), 它可以为文字、图形、图片添加阴影效果。

setShadowLayer()函数能够实现如下效果:

  • 定制阴影模糊程度,通过模糊半径控制模糊与清晰的程度。
  • 定制影偏移距离,X轴方向可以向左偏移或者向右偏移,Y轴方向可以向上偏移或向下偏移。
  • 清除和显示阴影
/**
     * This draws a shadow layer below the main layer, with the specified
     * offset and color, and blur radius. If radius is 0, then the shadow
     * layer is removed.
     * <p>
     * Can be used to create a blurred shadow underneath text. Support for use
     * with other drawing operations is constrained to the software rendering
     * pipeline.
     * <p>
     * The alpha of the shadow will be the paint's alpha if the shadow color is
     * opaque, or the alpha from the shadow color if not.
     */
    public void setShadowLayer(float radius, float dx, float dy, @ColorInt int shadowColor) {
        setShadowLayer(radius, dx, dy, Color.pack(shadowColor));
    }

    /**
     * This draws a shadow layer below the main layer, with the specified
     * offset and color, and blur radius. If radius is 0, then the shadow
     * layer is removed.
     * <p>
     * Can be used to create a blurred shadow underneath text. Support for use
     * with other drawing operations is constrained to the software rendering
     * pipeline.
     * <p>
     * The alpha of the shadow will be the paint's alpha if the shadow color is
     * opaque, or the alpha from the shadow color if not.
     *
     * @throws IllegalArgumentException if the color space encoded in the
     *      {@code ColorLong} is invalid or unknown.
     */
    public void setShadowLayer(float radius, float dx, float dy, @ColorLong long shadowColor) {
        ColorSpace cs = Color.colorSpace(shadowColor);
        nSetShadowLayer(mNativePaint, radius, dx, dy, cs.getNativeInstance(), shadowColor);

        mShadowLayerRadius = radius;
        mShadowLayerDx = dx;
        mShadowLayerDy = dy;
        mShadowLayerColor = shadowColor;
    }

float radius:模糊半径,radius越大越模糊、越小越清晰。如果 radius设置为0 ,则阴影消失不可见。

float dx:阴影的横向偏移距离,正值向右偏移,负值向左偏移。

float dy:阴影的纵向偏移距离,正值向下偏移,负值向上偏移。

int color: 绘制阴 影的画笔颜色 FllJ 阴影的颜色 (对图片阴 影无效)。
 
 
setS hadowL ayer ()函数使用的是高斯模糊算法。高斯模糊的具体算法是:对于正在处理的每 一个像素,取周围若干个像素的 RGB 值并且平均,这个平均值就是模糊处理过的像素。如果对图片中的所有像素都这么处理,那么处理完成的图片就会变得模糊。其中,所取周围像素的半径就是模糊半径。所以,模糊半径越大,所得平均像素与原始像素相差就越大,也就 越模糊。
绘制阴影的画笔颜色为什么对图片无效?
从上面的效果图中可以看 出,使用 setSh adowLayer() 函数所 产生 的阴影,文字和绘制的图 形的阴影都是 使用 自定义的阴影画笔颜色来绘制的。 而图片的阴影则是直接产生一 张相同的图片,仅对阴影图 片的 边缘进行模糊。
 
之所以生成一 张相同的阴影图片,是因为如果统一使用某种颜色来绘制阴影,则可能 会与图片 的颜色相 差很大, 而且不协调,比如某张图片的色 彩非常丰富 ,而阴 影如果使用灰色来做,可能就会显得很突兀。
 
注意:setShadowLayer() 函数只有文字绘制阴影支持硬件加速,其他都不支持硬件加速。所以, 为了方便其他图形、图片阴影的绘制起见,需要在自定义控件中禁用硬件加速。

设置阴影的其他API

    /**
     * Clear the shadow layer.
     */
    public void clearShadowLayer() {
        setShadowLayer(0, 0, 0, 0);
    }

    /**
     * Checks if the paint has a shadow layer attached
     *
     * @return true if the paint has a shadow layer attached and false otherwise
     * @hide
     */
    public boolean hasShadowLayer() {
        return nHasShadowLayer(mNativePaint);
    }

    /**
     * Returns the blur radius of the shadow layer.
     * @see #setShadowLayer(float,float,float,int)
     * @see #setShadowLayer(float,float,float,long)
     */
    public float getShadowLayerRadius() {
        return mShadowLayerRadius;
    }

    /**
     * Returns the x offset of the shadow layer.
     * @see #setShadowLayer(float,float,float,int)
     * @see #setShadowLayer(float,float,float,long)
     */
    public float getShadowLayerDx() {
        return mShadowLayerDx;
    }

    /**
     * Returns the y offset of the shadow layer.
     * @see #setShadowLayer(float,float,float,int)
     * @see #setShadowLayer(float,float,float,long)
     */
    public float getShadowLayerDy() {
        return mShadowLayerDy;
    }

    /**
     * Returns the color of the shadow layer.
     * @see #setShadowLayer(float,float,float,int)
     * @see #setShadowLayer(float,float,float,long)
     */
    public @ColorInt int getShadowLayerColor() {
        return Color.toArgb(mShadowLayerColor);
    }

看函数名就知道这几个API的作用了,这里不再说明。

清除阴影

清除阴 影其实有两种方法,可以将 setShad owLay er () 函数 radius 参数值设为0。也可以使用专门的api,见上面其他API中第一个:
    /**
     * Clear the shadow layer.
     */
    public void clearShadowLayer() {
        setShadowLayer(0, 0, 0, 0);
    }

看代码,四个参数全设置为0了。自然阴影也就没有了。

代码实例:

package com.xw.shadowlayerview.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

import com.xw.shadowlayerview.R;

/**
 * Copyright (c)2021 网络科技有限公司
 *
 * @author: LQS
 * @date: 2021/6/15
 * @description:com.xw.shadowlayerview.view.ShadowLayerView
 */
public class ShadowLayerView extends View {
   private Paint mPaint = new Paint();
   private Bitmap mBitmap;
    public ShadowLayerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE,null);
        mPaint.setColor(Color.BLACK);
        mPaint.setTextSize(25);
        mPaint.setShadowLayer(1,10,10,Color.GRAY);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize=2;
        mBitmap= BitmapFactory.decodeResource(getResources(), R.drawable.avatar,options);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText("Good Morning!",100,100,mPaint);
        canvas.drawCircle(200,200,50,mPaint);
        canvas.drawBitmap(mBitmap,null,new RectF(50,300,200+mBitmap.getWidth(),300+mBitmap.getHeight()),mPaint);
    }
}

布局使用:

<?xml version="1.0" encoding="utf-8"?>
<com.xw.shadowlayerview.view.ShadowLayerView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


</com.xw.shadowlayerview.view.ShadowLayerView>

运行效果图:

二、BlurMaskFilter 发光效果与图片阴影

 

与阴影效果类似的还有一个发光效果,发光效果是无法指定发光颜色的,采用边缘部分的颜色取样来进行模糊发光。 所以,边缘是什么颜色的,发出的光就是什么颜色的。与 setShadowLayer() 函数一样, 发光效果使用的也是高斯模糊算法 ,并且只会影响边缘部分图像,内部图像是不受影响的。

setMaskFilter()函数中的MaskFilter是没有具体实现的, 是通过派生子类来实现具体的不同功能的。 MaskFilter有两个派生类:BlurMaskFilter和EmbossMaskFilter 。其中,BlurMaskFilter能够实现发光效果;而EmbossMaskFilter则可以用于实现浮雕效果。

setMaskFilter()函数源码定义如下:

    /**
     * Set or clear the maskfilter object.
     * <p />
     * Pass null to clear any previous maskfilter.
     * As a convenience, the parameter passed is also returned.
     *
     * @param maskfilter May be null. The maskfilter to be installed in the
     *                   paint
     * @return           maskfilter
     */
    public MaskFilter setMaskFilter(MaskFilter maskfilter) {
        long maskfilterNative = 0;
        if (maskfilter != null) {
            maskfilterNative = maskfilter.native_instance;
        }
        nSetMaskFilter(mNativePaint, maskfilterNative);
        mMaskFilter = maskfilter;
        return maskfilter;
    }

其中函数参数MaskFilter类的子类BlurMaskFilter的构造函数定义如下:

    /**
     * Create a blur maskfilter.
     *
     * @param radius The radius to extend the blur from the original mask. Must be > 0.
     * @param style  The Blur to use
     * @return       The new blur maskfilter
     */
    public BlurMaskFilter(float radius, Blur style) {
        native_instance = nativeConstructor(radius, style.native_int);
    }

参数

float radius 用来定义模糊半径,同样采用高斯模糊算法。
B l ur style :发光样式,B l ur.INNER(内发光)、B l ur.SOLID(外发光)、B l ur.NORMAL(内外发光) 、B l ur.OUTER( 仅显示发光效果) ;
/**
 * This takes a mask, and blurs its edge by the specified radius. Whether or
 * or not to include the original mask, and whether the blur goes outside,
 * inside, or straddles, the original mask's border, is controlled by the
 * Blur enum.
 */
public class BlurMaskFilter extends MaskFilter {

    public enum Blur {
        /**
         * Blur inside and outside the original border.
         */
        NORMAL(0),

        /**
         * Draw solid inside the border, blur outside.
         */
        SOLID(1),

        /**
         * Draw nothing inside the border, blur outside.
         */
        OUTER(2),

        /**
         * Blur inside the border, draw nothing outside.
         */
        INNER(3);
        
        Blur(int value) {
            native_int = value;
        }
        final int native_int;
    }

四种发光效果示意图:

使用实例代码,在上面的代码基础上添加三个发光的圆形:

package com.xw.shadowlayerview.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

import com.xw.shadowlayerview.R;

/**
 * Copyright (c)2021 网络科技有限公司
 *
 * @author: LQS
 * @date: 2021/6/15
 * @description:com.xw.shadowlayerview.view.ShadowLayerView
 */
public class ShadowLayerView extends View {
   private Paint mPaint = new Paint();
   //定义发光效果的画笔
   private Paint mMaskPaint = new Paint();
   private Bitmap mBitmap;
    public ShadowLayerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE,null);
        mPaint.setColor(Color.BLACK);
        mPaint.setTextSize(25);
        mMaskPaint.setColor(Color.BLUE);
        //设置画笔的发光效果为:内发光,模糊半径为50
        mMaskPaint.setMaskFilter(new BlurMaskFilter(50, BlurMaskFilter.Blur.INNER));
        //设置阴影效果
        mPaint.setShadowLayer(1,10,10,Color.GRAY);
        //设置图片的缩放效果参数
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize=2;
        mBitmap= BitmapFactory.decodeResource(getResources(), R.drawable.avatar,options);

    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawText("Good Morning!",100,100,mPaint);
        canvas.drawCircle(200,200,50,mPaint);
        //画一个内发光的圆形
        canvas.drawCircle(200,400,120,mMaskPaint);
        //改变画笔的发光模式为内外发光
        mMaskPaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.NORMAL));
        //画一个内外发光的圆形
        canvas.drawCircle(480,400,120,mMaskPaint);
        //改变画笔的发光模式为仅显示发光效果
        mMaskPaint.setMaskFilter(new BlurMaskFilter(20, BlurMaskFilter.Blur.OUTER));
        //画一个仅显示发光效果的圆形
        canvas.drawCircle(760,400,120,mMaskPaint);
        canvas.drawBitmap(mBitmap,null,new RectF(50,600,200+mBitmap.getWidth(),600+mBitmap.getHeight()),mPaint);
    }
}

效果图,说明都在代码注释中,相信很好理解就不多说:

给图片添加纯色阴影

大家是否可以 看出发光效果与s etShadowLayer () 函数所生成的阴影之 间有什么 联系吗?
 
   先来分析 setShadowLayer ()函数的阴影形成过程(假定阴 影画笔是灰 色的)。对于文 字和图形,首先产生一个跟原型一 样的灰色副本;然后对这个灰色副本应用 BlurMaskFilter 使其内外发光,最后偏移一 段距离,这样就形成了所谓的阴影。
 
1 .抽取灰色图像

首先来看如何绘制出一张位图所对应的灰色图像?

canvas.drawBitmap()中的画笔颜色对画出来的位图是没有任何影响的,所以如果我们需要画一张位图所对应的灰色图像,就需要新建一张一样大小的空白图片,而且,新图片的透明度要与原图片保持一致,这样一来,如何从原图片中抽出 Alpha值成为关键。也就是说,只需要创建一张与原图片一样大小并且Alpha值相同的图片即可。

其实Bitmap中己经存在抽取出只具有Alpha值图片的函数,其声明如下:

/**
     * Returns a new bitmap that captures the alpha values of the original.
     * This may be drawn with Canvas.drawBitmap(), where the color(s) will be
     * taken from the paint that is passed to the draw call.
     *
     * @return new bitmap containing the alpha channel of the original bitmap.
     */
    @CheckResult
    public Bitmap extractAlpha() {
        return extractAlpha(null, null);
    }

    /**
     * Returns a new bitmap that captures the alpha values of the original.
     * These values may be affected by the optional Paint parameter, which
     * can contain its own alpha, and may also contain a MaskFilter which
     * could change the actual dimensions of the resulting bitmap (e.g.
     * a blur maskfilter might enlarge the resulting bitmap). If offsetXY
     * is not null, it returns the amount to offset the returned bitmap so
     * that it will logically align with the original. For example, if the
     * paint contains a blur of radius 2, then offsetXY[] would contains
     * -2, -2, so that drawing the alpha bitmap offset by (-2, -2) and then
     * drawing the original would result in the blur visually aligning with
     * the original.
     *
     * <p>The initial density of the returned bitmap is the same as the original's.
     *
     * @param paint Optional paint used to modify the alpha values in the
     *              resulting bitmap. Pass null for default behavior.
     * @param offsetXY Optional array that returns the X (index 0) and Y
     *                 (index 1) offset needed to position the returned bitmap
     *                 so that it visually lines up with the original.
     * @return new bitmap containing the (optionally modified by paint) alpha
     *         channel of the original bitmap. This may be drawn with
     *         Canvas.drawBitmap(), where the color(s) will be taken from the
     *         paint that is passed to the draw call.
     */
    @CheckResult
    public Bitmap extractAlpha(Paint paint, int[] offsetXY) {
        checkRecycled("Can't extractAlpha on a recycled bitmap");
        long nativePaint = paint != null ? paint.getNativeInstance() : 0;
        noteHardwareBitmapSlowCall();
        Bitmap bm = nativeExtractAlpha(mNativePtr, nativePaint, offsetXY);
        if (bm == null) {
            throw new RuntimeException("Failed to extractAlpha on Bitmap");
        }
        bm.mDensity = mDensity;
        return bm;
    }

 

这个函数的功能是:新建一张空白图片,该图片具有与原图片一样的Alpha值,把这个新建的Bitmap作为结果返回。在这张空白图片中,每个像素都具有与原图片一样的 Alpha值。而且具体的颜色是在使用 canvas.drawBitmap()函数绘制时由传入的画笔颜色指定的。

总结 extractAlpha ()会新建一 幅仅具有 Alpha 值的 空白图像, 而且这幅图像的颜色是在 使用 canvas.drawBitmap() 函数绘制 时由 传入 的画 笔颜色指定的。

实例:先在手机屏幕左边绘制抽取出来仅具有源图像透明度的透明图像,这是对源图像Bitmap对象执行extractAlpha()函数后,得到的图像的效果,通过灰色画笔绘制可以看得出来效果,它用于模仿阴影效果,仅作为参考图。在右侧绘制一个一模一样的这个阴影效果,再在这个阴影效果上绘制彩色的源图像。就得到了为彩色图像绘制纯色阴影背景的完美效果了。 

package com.xw.shadowlayerview.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BlurMaskFilter;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;

import androidx.annotation.Nullable;

import com.xw.shadowlayerview.R;

/**
 * Copyright (c)2021 网络科技有限公司
 *
 * @author: LQS
 * @date: 2021/6/16
 * @description:
 */
public class ExtractAlphaView extends View {

    private final Paint mPaint;
    private final Bitmap mBitmap;
    private final Bitmap mAlphaBmp;

    public ExtractAlphaView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        setLayerType(LAYER_TYPE_SOFTWARE,null);
        mPaint = new Paint();
        mBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.cat_dog);
        //利用extractAlpha()函数来生成新的源图像的仅具有透明度的空白图像对象
        mAlphaBmp = mBitmap.extractAlpha();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = 500;
        int height = width*mAlphaBmp.getWidth()/mAlphaBmp.getHeight();
        mPaint.setColor(Color.GRAY);
        //设置发光效果
        mPaint.setMaskFilter(new BlurMaskFilter(10, BlurMaskFilter.Blur.NORMAL));
        //绘制仅具有透明度的空白图像,并用灰色画笔绘制填充这个图像
        canvas.drawBitmap(mAlphaBmp,null, new Rect(10,10,width,height),mPaint);

        //向右平移width宽度
        canvas.translate(width,0);
        mPaint.setColor(Color.GRAY);
        //绘制仅具有透明度的空白图像,并用灰色画笔绘制填充这个图像,形成纯色阴影的效果
        canvas.drawBitmap(mAlphaBmp,null,new Rect(10,10,width,height),mPaint);
        //在上面黑色图像上,绘制原来彩色的图片
        canvas.translate(-18,-18);
        mPaint.setMaskFilter(null);
        canvas.drawBitmap(mBitmap,null, new Rect(10,10,width,height),mPaint);
    }
}

效果图如下:

好了,关于文字、图形、图像简单的阴影绘制到此就完结了。欢迎留言评论!谢谢!

以上是关于自定义View之文字图形图片的阴影的主要内容,如果未能解决你的问题,请参考以下文章

安卓自定义View进阶-Canvas之图片文字

Android 自定义View之Draw过程(上)

Android自定义View学习五---图片文本绘制

iOS项目之自定义斜向文字标签

IOS用CGContextRef画各种图形(文字圆直线弧线矩形扇形椭圆三角形圆角矩形贝塞尔曲线图片)(转)

自定义View之绘图篇:baseLine和FontMetrics