Android绘图阴影渐变和位图运算处理

Posted mChenys

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android绘图阴影渐变和位图运算处理相关的知识,希望对你有一定的参考价值。

目录

本章将向您介绍阴影、渐变和位图运算等技术。阴影只是一个狭义的说法,实际上也包括发光等效果;android 也供了强大的渐变功能,渐变能为物体带来更真实的质感,比如可以用渐变绘制一颗五子棋或一根金属圆棒;位图运算就更有趣了,Android 为 Bitmap 的运算供了多达16 种运算方法,获得的结果也不尽相同。不过,主要还是在于灵活应用。

一、阴影

可以为文字和图形指定阴影(Shader)。在绘图中,有一个叫 layer(层)的概念,默认情况下,我们的文字和图形绘制在主层(main layer)上,其实也可以将内容绘制在新建的 layer 上。实际上阴影就是在 main layer 的下面添加了一个阴影层(shader layer),可以为阴影指定模糊度、偏移量和阴影颜色。
Paint 类定义了一个名为 setShadowLayer 的方法:

/**
 * 设置阴影
 * @param radius 阴影半径
 * @param dx  x方向阴影的偏移量
 * @param dy  y方向阴影的偏移量
 * @param shadowColor 阴影的颜色
 */
public void setShadowLayer(float radius, float dx, float dy, int shadowColor);

阴影layer显示阴影时,shader layer 有两种类型:View.LAYER_TYPE_SOFTWARE 和View.LAYER_TYPE_HARDWARE,layer的默认类型为 LAYER_TYPE_HARDWARE,但阴影只能在View.LAYER_TYPE_SOFTWARE 环境下工作, 所以我们需要调用View 类的如下方法为Paint对象指定层的类型为View.LAYER_TYPE_SOFTWARE

public void setLayerType(int layerType, Paint paint)

1.1 案例-为文字添加阴影和发光效果

// 自定义View,后面的案例都是使用此View进行修改
public class MyView extends View 
    public MyView(Context context) 
        this(context, null);
    

    public MyView(Context context, AttributeSet attrs) 
        this(context, attrs, 0);
    

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) 
        super(context, attrs, defStyleAttr);
        init();
    

    private Paint paint;

    private void init() 
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setAntiAlias(true);
        // 启用阴影
        this.setLayerType(View.LAYER_TYPE_SOFTWARE, paint); // 参数2可以传null, 否则使用同一个paint的地方都会关联LAYER_TYPE_SOFTWARE
        paint.setTextSize(80);
        // 指定阴影的半径为10px,x和y的偏移量为1px,阴影颜色为红色
        paint.setShadowLayer(10, 1, 1, Color.RED);
    

    @Override
    public void onDraw(Canvas canvas) 
        // 绘制文本
        canvas.drawText("Android 开发", 50, 100, paint);
        // 修改阴影效果
        paint.setShadowLayer(10, 5, 5, Color.BLUE);
        // 再次绘制文本
        canvas.drawText("Android 绘图技术", 50, 220, paint);
    

效果图:

上面代码中 , 我们绘制了两行文字, 第一 行 “Android 开 发 ”为红色发光效果,setShadowLayer(10, 1, 1, Color.RED)语句定义了一个模糊半径为 10、x 方向和 y 方向偏移量都为 1的红色阴影,当偏移量足够小时,我们看到的其实是发光效果。
paint.setShadowLayer(10, 5, 5,Color.BLUE)语句定义了一个模糊半径为 10、x 方向和 y 方向偏移量为 5 的蓝色阴影。
注意阴影必须在 LAYER_TYPE_SOFTWARE 模式下才能工作。

最后要强调的是,一旦定义了阴影层,接下来的所有绘制都会带阴影效果了(假设使用了同一个paint),如果想取消阴影,请将 setShadowLayer()方法的 radius 参数设置为 0,并且在调用View的setLayerType方法的第二个参数Paint传入null而不是全局的Paint; 或者调用paint的clearShadowLayer方法也行。

二、 渐变

渐变(Gradient)是绘图过程中颜色或位图以特定规律进行变化,能增强物体的质感和审美情趣。生活中的渐变非常多,例如公路两边的电线杆、树木、建筑物的阳台、铁轨的枕木延伸到远方等等,很多的自然理象都充满了渐变的形式特点。Android 同样对渐变进行了完善支持,通过渐变,可以绘制出更加逼真的效果。
Graphics2D 渐变种类有:

  1. 线性渐变:LinearGradient
  2. 径向渐变:RadialGradient
  3. 扫描渐变:SweepGradient
  4. 位图渐变:BitmapShader
  5. 混合渐变:ComposeShader

其中,线性渐变、径向渐变和扫描渐变属于颜色渐变,指定 2 种或 2 种以上的颜色,根据颜色过渡算法自动计算出中间的过渡颜色,从而形成渐变效果,对于开发人员来说,无需关注中间的渐变颜色。位图渐变则不再是简单的颜色渐变,而是以图片做为贴片有规律的变化,类似于壁纸平铺。混合渐变则能将多种渐变进行组合,实现更加复杂的渐变效果。

3种渐变模式
如果 A、B 分别代表 2 种不同的颜色,我们将渐变分为三种:
AABB 型:A、B 两种颜色只出现一次,通过 TileMode 类的 CLAMP 常量来表示, 效果就是:如果超出规定的区域就重复边缘的效果;
ABBA 型:A、B 两种颜色镜像变化,通过 TileMode 类的 MIRROR 常量来表示,效果就是:以镜像的方式显示;
ABAB 型:A、B 两种颜色重复变化,通过 TileMode 类的 REPEAT 常量来表示,效果就是:在竖直和水平方向上重复。
这三种不同的TileMode的渐变模式的效果如下图所示:

定义渐变时,必须指定一个渐变区域,根据定义的渐变内容和渐变模式填满该区域。每一种渐变都被定义成了一个类,他们都继承自同一个父类—Shader。绘图时,调用 Paint 类的setShader(Shader shader)方法指定一种渐变类型,绘制出来的绘图填充区域都将使用指定的渐变颜色或位图进行填充。

2.1 线性渐变(LinearGradient)

线性渐变(LinearGradient)根据指定的角度、颜色和模式使用渐变颜色填充绘图区域。我们必须定义两个点(x0,y0)和(x1,y1),渐变方向就是从起点指向终点,如下图所示:

如何通过坐标设置渐变方向?
通过坐标可以轻松实现,渐变方向的控制:

(0,0)->(0,400) // 从上到下
(0,400)->(0,0) //  从下到上
(0,0->(getMeasuredWidth(),0) // 表示从左到右
(getMeasuredWidth(),0)->(0,0) // 表示从右到左
(0,0-> (getMeasuredWidth(),getMeasuredHeight()) // 斜角,从左上角到右下角
(0,getMeasuredHeight()-> (getMeasuredWidth(),0) // 斜角,从左下角到右上角

LinearGradient 的构造方法如下:

/**
 * 构造线型渐变
 * @param x0 用于决定线性方向的第一个点的坐标(x0,y0)
 * @param y0
 * @param x1 用于决定线性方向的第二个点的坐标(x1,y1)
 * @param y1
 * @param color0 第一种颜色
 * @param color1 第二种颜色
 * @param tile 渐变模式
 */
public LinearGradient(float x0, float y0, float x1, float y1, int color0, int color1, TileMode tile) 


假设我们绘制了三个矩形,第一个矩形的渐变区域与矩形恰好一致,第二个矩形的渐变区域大于矩形区域,第三个矩形的渐变区域小于矩形区域,均采用 TileMode 的 CLAMP 模式,渐变方向都是左上角->右下角, 代码如下:

private Paint paint;
private static final int OFFSET = 100;
private  Rect rect;
// 三种不同的渐变效果
private LinearGradient lg1;
private LinearGradient lg2;
private LinearGradient lg3;

private void init() 
    paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setAntiAlias(true);

    // 定义矩形区域
    rect = new Rect(100, 100, 400, 300);
    // 定义和矩形区域大小相等的渐变
    lg1 = new LinearGradient(rect.left, rect.top, rect.right, rect.bottom,
            Color.RED, Color.BLUE, Shader.TileMode.CLAMP);

    // 放大渐变矩形
    Rect rect2 = new Rect(rect);
    // 如果 dx 和 dy 是负的,则两边向外移动,使矩形更宽
    rect2.inset(-100, -100);
    lg2 = new LinearGradient(
            rect2.left, rect2.top, rect2.right, rect2.bottom,
            Color.RED, Color.BLUE, Shader.TileMode.CLAMP);

    // 缩小渐变矩形
    Rect rect3 = new Rect(rect);
    // 如果 dx 和 dy 是正的,则两边向内移动,使矩形更窄
    rect3.inset(100, 100);
    lg3 = new LinearGradient(
            rect2.left, rect2.top, rect2.right, rect2.bottom,
            Color.RED, Color.BLUE, Shader.TileMode.CLAMP);


@Override
public void onDraw(Canvas canvas) 
    // 绘制第一个矩型,渐变区域和矩型区域一致
    paint.setShader(lg1);
    canvas.drawRect(rect, paint);

    //坐标往下移动
    canvas.translate(0, rect.height() + OFFSET);
    paint.setShader(lg2);
    // 绘制第二个矩形,渐变区域大于矩形区域
    canvas.drawRect(rect, paint);

    //坐标往下移动
    canvas.translate(0, rect.height() + OFFSET);
    paint.setShader(lg3);
    // 绘制第三个矩形,渐变区域小于矩形区域
    canvas.drawRect(rect, paint);


效果图:

改成边框模式的效果:

如果两种颜色无法满足绘图需求,LinearGradient 支持三种或者三种以上颜色的渐变,对应的构造方法如下:

/**
 * 多颜色的线性渐变
 * @param x0 起始点的坐标
 * @param y0
 * @param x1 终止点的坐标
 * @param y1
 * @param colors 多种颜色
 * @param positions 颜色的位置(比例)
 * @param tile 渐变模式
 */
public LinearGradient(float x0, float y0, float x1, float y1, int colors[], float positions[], TileMode tile) 


参数 colors 和 positions 都是数组,前者用于指定多种颜色,后者用于指定每种颜色的起始比例位置。positions 数组中的元素个数与 colors 要相同,且是 0 至 1 的数值,[0,1]是临界区,如 果小于 0 则当 0 处理,如果大于 1 则当 1 处理。假如在绘图区域和渐变区域大小相同的情况下,colors 包含了三种颜色:red、yellow、green,在渐变区域中这三种颜色的起始比例位置为 0、0.3、 1,则颜色渐变如下图所示:

如果在比例位置为 0.2、0.5、0.8,则颜色渐变如下图所示:

2.1.1 案例- 实现圆角矩形环形渐变

效果图如下:

可以看到渐变颜色有4种, 并且渐变方向是左下角->右上角等分比例, 这里实现圆角矩形环用到了path的图形运算来实现,具体是通过大的圆角矩形减去小的圆角矩形, 代码如下:

class GradientCornerView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    def: Int = 0,
) : View(
    context, attrs, def
) 
    // 外圆角
    private var outRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20f, resources.displayMetrics)

    // 内圆角
    private var innerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 18f, resources.displayMetrics)
    private var borderWidth = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4f, resources.displayMetrics)
    private val outPath = Path()

    // 是否立即显示
    private var showBorder = true
        set(value) 
            field = value
            invalidate()
        
    // 设置画笔
    private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply 
        style = Paint.Style.FILL
        strokeJoin = Paint.Join.ROUND
        strokeCap = Paint.Cap.ROUND
        strokeJoin = Paint.Join.ROUND
        strokeCap = Paint.Cap.ROUND
    

    private val rect = RectF()


    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) 
        super.onSizeChanged(w, h, oldw, oldh)
        // 设置区域区域
        rect.set(0f, 0f, w.toFloat(), h.toFloat())
        // 添加渐变从左下角->右上角渐变
        paint.shader = LinearGradient(
            rect.left, rect.bottom, rect.right, rect.top,
            // 支持多个颜色值
            intArrayOf(
                Color.parseColor("#00A6FF"),
                Color.parseColor("#4BC2C2"),
                Color.parseColor("#18D19B"),
                Color.parseColor("#AFE646")
            ), floatArrayOf(
                // 设置等分比例
                0f, 0.25f, 0.75f, 1f
            ), Shader.TileMode.CLAMP
        )

        val innerPath = Path()
        // 添加外圆角矩形
        outPath.addRoundRect(rect, outRadius, outRadius, Path.Direction.CCW)
        // 缩小矩形区域
        rect.inset(borderWidth, borderWidth)
        // 添加内圆角矩形
        innerPath.addRoundRect(rect, innerRadius, innerRadius, Path.Direction.CCW)
        // 通过path的op操作, 用外圆角矩形区域-内圆角矩形区域 = 圆角矩形环区域
        outPath.op(innerPath, Path.Op.DIFFERENCE)
    

    override fun onDraw(canvas: Canvas) 
        if (showBorder) 
            canvas.drawPath(outPath, paint)
        
    

2.2 径向渐变(RadialGradient)

径向渐变是以指定的点为中心,向四周以渐变颜色进行圆周扩散,和线性渐变一样,支持两种或多种颜色。径向渐变的示意图如图:

径向渐变的主要构造方法如下:

/**
 * 径向渐变
 * @param x 中心点x坐标
 * @param y 中心点y坐标
 * @param radius 渐变半径
 * @param color0 起始颜色
 * @param color1 结束颜色
 * @param tile 渐变模式
 */
public RadialGradient(float x, float y, float radius, int color0, int color1, TileMode tile) 


支持 3 种或 3 种以上颜色的渐变的构造方法如下:

/**
 *
 * @param x 中心点x坐标
 * @param y 中心点y坐标
 * @param radius 渐变半径
 * @param colors 多种颜色
 * @param positions 颜色的位置(比例)
 * @param tile 渐变模式
 */
public RadialGradient(float x, float y, float radius, int colors[], float positions[], TileMode tile) 


接下来我们在 View 上绘制相同大小的正方形和圆,使用一致的径向渐变,模式为TileMode.MIRROR,在大部分时候,镜像模式的渐变效果看起来会更舒服更讨人喜欢。

private Paint paint;
private Rect rect;
private RectF oval;

private void init() 
    paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setAntiAlias(true);

     // 定义矩形区域
    rect = new Rect(100, 100, 500, 500);
    // 定义圆的区域
    oval = new RectF(rect);
    RadialGradient rg = new RadialGradient(300, 300, 200, Color.RED, Color.GREEN, Shader.TileMode.MIRROR);
    paint.setShader(rg);


@Override
public void onDraw(Canvas canvas) 
    // 绘制矩形
    canvas.drawRect(rect, paint);
    // 平移
    canvas.translate(0, 500);
    // 绘制圆,虽然用的是drawOval,但是传入的矩形是正方型,所以绘制出来的就是正圆
    canvas.drawOval(oval, paint);


上面代码中,我们定义了一个 RadialGradient 对象,中心点坐标为(300, 300),正好是正方形和圆的中心,渐变半径为 200,意味着渐变区域与正方形和圆的大小相同。效果图:

2.2.1 案例-使用径向渐变绘制棋盘的棋子

利用径向渐变,我们可以画出五子棋的棋子,五子棋分为黑色和白色两种不同的棋子,为了画出更逼真的效果,需要考虑棋子的反光效果,光点不能是正中心,而应该向右下角偏移;同时,为棋子加上阴影,棋子似乎跃然纸上。黑色棋子使用黑白两色绘制,白色棋子则使用灰白两色绘制,效果如下图所示。

有了棋子,就应该有棋盘,棋盘是一个 m*n 的矩阵,按照一定的规律画好水平线和垂直线就可以了


private Paint paint;
private float chessSize = 100; // 棋子的大小

private void init() 
    paint = new Paint(Paint.ANTI_ALIAS_FLAG);
    paint.setStyle(Paint.Style.FILL);
    paint.setColor(Color.GRAY);


enum ChessColor 
    WHITE, BLACK


@Override
protected void onDraw(Canvas canvas) 
    int row = (int) (getHeight() / chessSize);
    int column = (int) (getWidth() / chessSize);
    // 棋盘居中
    int offsetX = (int) ((getWidth() - column * chessSize) * 0.5f);
    int offsetY = (int) ((getHeight() - row * chessSize) * 0.5f);
    canvas.translate(offsetX, offsetY);
    // 绘制棋盘
    drawChessBox(canvas, row, column);

    // 绘制棋子
    drawChess(canvas, 3, 5, ChessColor.BLACK);
    drawChess(canvas, 4, 6, ChessColor.WHITE);
    drawChess(canvas, 5, 5, ChessColor.BLACK);
    drawChess(canvas, 5, 6, ChessColor.WHITE);



// 绘制棋盘
private void iOS 2D绘图 (Quartz2D)之阴影和渐变(shadow,Gradient)

一起Talk Android吧(第四百二十一回:绘图中添加阴影)

一起Talk Android吧(第四百二十二回:绘图中添加发光效果)

Android UIPaint Gradient 渐变渲染 ③ ( RadialGradient 环形渐变渲染 | 在给定中心和半径的情况下绘制径向渐变的着色器 | 水波纹效果 )

如何在android中使用渐变阴影按钮制作渐变恒定发光?

duilib窗体阴影偏移情况