利用 2D 图形和 PorterDuffXferMode 等实现被遮罩的图片

图片的遮罩就是将裁剪遮罩应用于图片或形状,定义应用中另一张图片的可见边界。

利用 2D 图形和 PorterDuffXferMode,可以将各种遮罩应用于某张位图。

第一张效果图:


其基本步骤:

1. 创建一个可变的空白 Bitmap 实例,以及在其中绘图的 Canvas。

2. 首先在 Canvas 上画好遮罩模式。

3. 将 PorterDuffXferMode 应用到 Paint 上。

4. 用传输模式将原图绘制到 Canvas 上。

其中的关键是 PorterDuffXferMode,它会考虑到 Canvas 中已有的数据的状态和应用到当前操作的图形数据的状态。



第一中方法实现遮罩,使用图片作为 BitmapShader 将内容绘制到另一个元素中。通过这种方式,就可以将图片像素视为用于绘制形状或者元素的“颜色”,这些形状或者元素将组成遮罩图片。


RoundedCornerImageView.java :

<span style="font-size:18px;">package com.scxh.imagecover;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.RectF;
import android.graphics.Shader;
import android.util.AttributeSet;
import android.view.View;

public class RoundedCornerImageView extends View{

    private Bitmap mImage;
    private Paint mBitmapPaint;
    private RectF mBounds;
    private float mRadius = 25.0f;

    public RoundedCornerImageView(Context context) {
        this(context, null);
    }

    public RoundedCornerImageView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public RoundedCornerImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        init();
    }

    private void init() {
        // 创建图片涂绘
        mBitmapPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        // 创建作为绘图边界的矩形
        mBounds = new RectF();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int height = 0;
        int width = 0;

        // 所请求大小是图片内容的大小
        int imageHeight, imageWidth;
        if (mImage == null) {
            imageHeight = imageWidth = 0;
        } else {
            imageHeight = mImage.getHeight();
            imageWidth = mImage.getWidth();
        }

        // 获得最佳测量值并在视图上设置该值
        width = getMeasurement(widthMeasureSpec, imageWidth);
        height = getMeasurement(heightMeasureSpec, imageHeight);

        setMeasuredDimension(width, height);
    }

    private int getMeasurement(int measureSpec, int contentSize) {
        int specSize = MeasureSpec.getSize(measureSpec);
        switch (MeasureSpec.getMode(measureSpec)) {
            case MeasureSpec.AT_MOST:
                return Math.min(specSize, contentSize);
            case MeasureSpec.UNSPECIFIED:
                return contentSize;
            case MeasureSpec.EXACTLY:
                return specSize;
            default:
                return 0;
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            // 我们要使图片居中,因此在视图改变大小时偏移值
            int imageWidth, imageHeight;
            if (mImage == null) {
                imageWidth = imageHeight = 0;
            } else {
                imageWidth = mImage.getWidth();
                imageHeight = mImage.getHeight();
            }
            int left = (w - imageWidth) / 2;
            int top = (h - imageHeight) / 2;

            // 设置边界以偏移圆角矩形(整个图形居中)
            mBounds.set(left, top, left+imageWidth, top+imageHeight);

            // 偏移着色器以在矩形内部绘制位图
            // 如果没有此步骤,位图将在视图中的(0, 0)处
            if (mBitmapPaint.getShader() != null) {
                Matrix m = new Matrix();
                m.setTranslate(left, top);
                mBitmapPaint.getShader().setLocalMatrix(m);
            }
        }
    }

    /**
     *  供使用者调用,并创建一个 BitmapShader 来封装图片像素,并在用于绘图
     *  的画笔上进行相应的设置。
     * @param bitmap 位图
     */
    public void setImage(Bitmap bitmap) {
        if (mImage != bitmap) {
            mImage = bitmap;
            if (mImage != null) {
                BitmapShader shader = new BitmapShader(mImage,
                        Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
                mBitmapPaint.setShader(shader);
            } else {
                mBitmapPaint.setShader(null);
            }
            // 绘制成 bitmap
            requestLayout();
        }
    }

    // 让视图绘制背景等对象
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        // 使用计算得出的值绘制图片
        if (mBitmapPaint != null) {
            canvas.drawRoundRect(mBounds, mRadius, mRadius, mBitmapPaint);
        }
    }

}
</span>

该类中关于自定义 view 时,用到的测量等,可以参见我以前的博客《 简单的完全自定义视图(同心圆) 》:http://blog.csdn.net/antimage08/article/details/50103433 点击打开链接




MainActivity.java  :
<span style="font-size:18px;">package com.scxh.imagecover;

import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;


public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        RoundedCornerImageView imageView = new RoundedCornerImageView(this);
        Bitmap source = BitmapFactory.decodeResource(getResources(), R.drawable.image01);

        imageView.setImage(source);

        setContentView(imageView);

    }

}
</span>






第二中方法实现遮罩:此处采用两张图片,一张如上图的效果所示;另一张采用一个黑色的倒三角形(从 300 * 300 像素上扣取)。效果如下:


首先在 Canvas 中绘制三角形图片,这就是图片的遮罩。然后,在同一个 Canvas 上绘制原图时应用 PorterDuff.Mode.SRC_IN 转换,得到的就是带圆角的原图。
这是因为 SRC_IN 转换模式就是告诉 Paint 对象,只在 Canvas 上原图和目标图(已经画好的三角形)重叠视为地方绘制像素点,像素点则来自原图。

在运行 Android 5.0 及更高版本的设备上,Android 框架支持通过动态阴影表明视图的提高(通过 elevation 和 translationZ 属性)。
在简单的示例中,可以在内部进行处理,但如果应用任意遮罩,则还必须使用匹配的 ViewOutlineProvider 指示在何处产生阴影。
ViewOutlineProvider 有一个必须的方法 getOutline(),如果由于大小或配置发生变化而需要更新轮廓,就会调用该方法。




MaskActivity.java :
<span style="font-size:18px;">package com.scxh.imagecover;

import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;


public class MaskActivity extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);


        ImageView imageView = new ImageView(this);
        imageView.setScaleType(ImageView.ScaleType.CENTER);

        // 创建并加载图片(通常是不可修改的)
        Bitmap source = BitmapFactory.decodeResource(getResources(), R.drawable.image01);
        Bitmap mask = BitmapFactory.decodeResource(getResources(), R.drawable.dsjx);

        // 创建一个可修改的位置以及一个在其中绘制的 Canvas
        final Bitmap result = Bitmap.createBitmap(source.getWidth(),
                source.getHeight(), Bitmap.Config.ARGB_8888);
        Canvas canvas = new Canvas(result);
        Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLACK);

        canvas.drawBitmap(mask, 0, 0, paint);
        // PorterDuff.Mode.SRC_IN 模式:会根据目标边界对原图进行裁剪
        paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
        canvas.drawBitmap(source, 0, 0, paint);
        paint.setXfermode(null);

        imageView.setImageBitmap(result);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            // 提高视图以建立可见阴影(数值越大阴影扩散的范围就越大)
            imageView.setElevation(30f);
            // 绘制匹配遮罩的轮廓,从而提供适当的阴影
            imageView.setOutlineProvider(new ViewOutlineProvider() {
                @Override
                public void getOutline(View view, Outline outline) {
                    int x = (view.getWidth() - result.getWidth()) / 2;
                    int y = (view.getHeight() - result.getHeight()) / 2;

                    Path path = new Path();

                    // 路径的起始位置(倒三角形的左上角)
                    path.moveTo(x, y);
                    // 沿路径绘制直线 (倒三角形的右上角)
                    path.lineTo(x + result.getWidth(), y);
                    // 沿路径绘制直线 (倒三角形的下顶点)
                    path.lineTo(x + result.getWidth() / 2, (float) (y + result.getHeight()/1.6));
                    // 沿路径绘制直线 (倒三角形的左上角)
                    path.lineTo(x, y);
                    // 绘制成封闭图形后,关闭路径
                    path.close();

                    outline.setConvexPath(path);
                }
            });
        }

        setContentView(imageView);

    }
}
</span>




如果轮廓足够简单,Android 还可以将其作为视图的剪切遮罩。只需要调用 setClipToOutline(true),即可表明视图应使用其轮廓作为剪切遮罩。
到Android5.0为止,仅支持通过矩形,圆形和圆角矩形轮廓进行剪切。上图的三角形就不能用作剪切。
圆形轮廓剪切的效果图:



OutlineActivity.java :
package com.scxh.imagecover;

import android.app.Activity;
import android.graphics.Outline;
import android.os.Bundle;
import android.view.View;
import android.view.ViewOutlineProvider;
import android.widget.ImageView;


public class OutlineActivity extends Activity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        final ImageView imageView = new ImageView(this);
        imageView.setScaleType(ImageView.ScaleType.CENTER);

        // 提高视图以建立可见阴影(数值越大阴影扩散的范围就越大)
        imageView.setElevation(30f);
        imageView.setImageResource(R.drawable.image02);

        // 告诉视图使用其轮廓作为剪切遮罩
        imageView.setClipToOutline(true);

        // 为剪切和阴影提供圆形视图轮廓
        imageView.setOutlineProvider(new ViewOutlineProvider() {
            @Override
            public void getOutline(View view, Outline outline) {

                ImageView mImageView = (ImageView)view;
                int radius = mImageView.getDrawable().getIntrinsicHeight()/2;
                int centerX = (view.getRight() - view.getLeft())/2;
                int centerY = (view.getBottom() - view.getTop())/2;

                outline.setOval(centerX - radius,
                        centerY - radius,
                        centerX + radius,
                        centerY + radius);
            }
        });

        setContentView(imageView);
    }
}



  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值