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

来源:互联网 发布:ios8.4.1完美越狱mac 编辑:程序博客网 时间:2024/06/05 03:16

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

利用 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 0
原创粉丝点击