Android 放大镜窥视效果

前言

放大镜效果是一种常用的局部图片观察效果,其本质原理依然是将原图片放大之后,经过范围裁剪然后会知道指定区域的一种效果。实际上放大效果有2种常见的效果,比如在一些购物网站,鼠标移动到的位置被放大,然后展示在侧边区域,这两者代码几乎一样,主要区别如下:

  • 侧边区域观测要移动Shader或者在指定位置裁剪图像
  • 本文效果是移动区域,但是为了保证图片能尽可能对齐,需要将放大的图片向左上角偏移。

本文和上一篇《手电筒照亮效果》一样,如果没看过的先看上一篇,方便你理解本篇,因为同样的原理不会在这篇重新提及或者过多提及,都是局部区域效果实现。

效果预览

滑动放大效果

窥视效果

方法镜滑动放大实现方法

使用Shader作为载体

首先要做的是将图片放大,放大之后,我们可以利用Path裁剪图片或者Shader向裁剪区域绘制,这里我们依然使用Shader,毕竟优点很多,这里我们主要要实现2个目的。

  • Shader载入Bitmap,放大1.2倍
  • Shader向左上角偏移,对齐图片中心
      if (shader == null) {
            float ratio  = 1.2f;
            scaledBitmap = Bitmap.createScaledBitmap(mBitmap, (int) (mBitmap.getWidth() * ratio), (int) (mBitmap.getHeight() * ratio), true);
            shader =  new BitmapShader(scaledBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
            // 做下偏移
            matrix.setTranslate(-(scaledBitmap.getWidth() - mBitmap.getWidth())/2f ,-(scaledBitmap.getHeight() - mBitmap.getHeight())/2f);
           shader.setLocalMatrix(matrix);
        }

事件处理

其实处理事件有很多简便的方法,但是首先得拦截事件,Android种拦截事件的方法很多,clickable就是其中之一

setClickable(true); //触发hotspot

拦截按压移动事件,这里我们使用 HotSpot 机制,其实就是触点,西方人命名习惯使用HotSpot,通过下面就能处理事件,连onTouchEvent我们都不用搭理。

  @Override
    public void dispatchDrawableHotspotChanged(float x, float y) {
        super.dispatchDrawableHotspotChanged(x, y);
        this.x = x;
        this.y = y;
        postInvalidate();
    }

    @Override
    protected void dispatchSetPressed(boolean pressed) {
        super.dispatchSetPressed(pressed);
        postInvalidate();
    }

裁剪Canvas区域为原图区域

为什么要裁剪Canvas区域内,主要是因为你的图片并不一定能完全填充整个View,但是你使用的TileMode肯定是CLAMP,这会使得放大镜中图像的边缘拉长,现象很奇怪,反正你可以去掉试试。另外说一下,Android中似乎新增加了一种TileMode,不过还没来得及试一下。

   int save = canvas.save();
  canvas.clipRect(0,0,mBitmap.getWidth(),mBitmap.getHeight());
  canvas.restoreToCount(save);

绘制核心逻辑

在核心逻辑中,我们有一步要绘制区域填充颜色,主要原因是非透明区域的绘制会导致出现透视效果。

    int save = canvas.save();
        canvas.clipRect(0,0,mBitmap.getWidth(),mBitmap.getHeight());
        //绘制原图
        canvas.drawBitmap(mBitmap, 0, 0, null);
        //区域用填充颜色,防止出现区域透视,上面的区域能看见下面的区域
        mCommonPaint.setColor(Color.WHITE);
        canvas.drawCircle( x , y,width/4f,mCommonPaint);
        //绘制放大效果
        mCommonPaint.setShader(shader);
        canvas.drawCircle( x , y,width/4f,mCommonPaint);
        mCommonPaint.setShader(null);

        canvas.restoreToCount(save);

放大镜窥视效果

其实两者代码没有多大区别,滑动放大效果主要是移动镜子,而窥视效果镜子不动,使用移动图片的方式实现。

位置计算 & 绘制

固定镜子中心在右下角

//放大平移时需要偏移的距离
float offsetX = -(scaledBitmap.getWidth() - mBitmap.getWidth()) / 2f;
float offsetY = -(scaledBitmap.getHeight() - mBitmap.getHeight())/2f;
//窥视镜圆心
float  mirrorCenterX = mBitmap.getWidth() - width / 4f;
float  mirrorCenterY =  mBitmap.getHeight() - width/4f;

图像平移距离

(mirrorCenterX - x) 
(mirrorCenterY - y) 

矩阵变换,平移事件点位置图像到右下角圆的中心

//(mirrorCenterX - x) ,(mirrorCenterY-y) 是把当前中心点的图像平移到圆心哪里
matrix.setTranslate( offsetX + (mirrorCenterX - x)  , offsetY + (mirrorCenterY-y));
shader.setLocalMatrix(matrix);

绘制镜子


int save = canvas.save();
canvas.clipRect(0,0,mBitmap.getWidth(),mBitmap.getHeight());
canvas.drawBitmap(mBitmap, 0, 0, null);
mCommonPaint.setColor(Color.DKGRAY);
canvas.drawCircle(mirrorCenterX , mirrorCenterY,width/4f,mCommonPaint);
mCommonPaint.setShader(shader);
canvas.drawCircle( mirrorCenterX , mirrorCenterY,width/4f,mCommonPaint);
mCommonPaint.setShader(null);

canvas.restoreToCount(save);

总结

本篇和之前的很多篇文章一样,都是实现Canvas图片绘制,很复杂的效果我们没有涉及到,但是在这些文章中,都会有各种各样的问题和思考。总之,我们要善于利用矩阵和设计思想,绘制我们的想象。

全部代码

按照惯例,提供全部代码

滑动放大代码

public class ScaleBigView extends View {
    private final DisplayMetrics mDM;
    private TextPaint mCommonPaint;
    private Bitmap mBitmap;
    private Shader shader = null;
    private Matrix matrix = new Matrix();
    private Bitmap scaledBitmap;

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

    public ScaleBigView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public ScaleBigView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDM = getResources().getDisplayMetrics();
        initPaint();
        setClickable(true); //触发hotspot
    }

    private void initPaint() {
        //否则提供给外部纹理绘制
        mCommonPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        mCommonPaint.setAntiAlias(true);
        mCommonPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mCommonPaint.setStrokeCap(Paint.Cap.ROUND);
        mCommonPaint.setFilterBitmap(true);
        mCommonPaint.setDither(true);
        mCommonPaint.setStrokeWidth(dp2px(20));
        mBitmap = decodeBitmap(R.mipmap.mm_012);

    }

    private Bitmap decodeBitmap(int resId) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inMutable = true;
        return BitmapFactory.decodeResource(getResources(), resId, options);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        if (widthMode != MeasureSpec.EXACTLY) {
            widthSize = mDM.widthPixels / 2;
        }
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (heightMode != MeasureSpec.EXACTLY) {
            heightSize = widthSize / 2;
        }
        setMeasuredDimension(widthSize, heightSize);

    }
    private float x;
    private float y;
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        if (width < 1 || height < 1) {
            return;
        }
        if (shader == null) {
            float ratio  = 1.2f;
            scaledBitmap = Bitmap.createScaledBitmap(mBitmap, (int) (mBitmap.getWidth() * ratio), (int) (mBitmap.getHeight() * ratio), true);
            shader =  new BitmapShader(scaledBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);
            matrix.setTranslate(-(scaledBitmap.getWidth() - mBitmap.getWidth())/2f ,-(scaledBitmap.getHeight() - mBitmap.getHeight())/2f);
           shader.setLocalMatrix(matrix);
        }


        int save = canvas.save();
        canvas.clipRect(0,0,mBitmap.getWidth(),mBitmap.getHeight());
        //绘制原图
        canvas.drawBitmap(mBitmap, 0, 0, null);
        //区域用填充颜色,防止出现区域透视,上面的区域能看见下面的区域
        mCommonPaint.setColor(Color.WHITE);
        canvas.drawCircle( x , y,width/4f,mCommonPaint);
        //绘制放大效果
        mCommonPaint.setShader(shader);
        canvas.drawCircle( x , y,width/4f,mCommonPaint);
        mCommonPaint.setShader(null);

        canvas.restoreToCount(save);
    }

    @Override
    public void dispatchDrawableHotspotChanged(float x, float y) {
        super.dispatchDrawableHotspotChanged(x, y);
        this.x = x;
        this.y = y;
        postInvalidate();
    }

    @Override
    protected void dispatchSetPressed(boolean pressed) {
        super.dispatchSetPressed(pressed);
        postInvalidate();
    }

    public float dp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDM);
    }

}

窥视镜效果

public class ScaleBigView extends View {
    private final DisplayMetrics mDM;
    private TextPaint mCommonPaint;
    private Bitmap mBitmap;
    private Shader shader = null;
    private Matrix matrix = new Matrix();
    private Bitmap scaledBitmap;

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

    public ScaleBigView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }
    public ScaleBigView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mDM = getResources().getDisplayMetrics();
        initPaint();
        setClickable(true); //触发hotspot
    }

    private void initPaint() {
        //否则提供给外部纹理绘制
        mCommonPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.FILTER_BITMAP_FLAG);
        mCommonPaint.setAntiAlias(true);
        mCommonPaint.setStyle(Paint.Style.FILL_AND_STROKE);
        mCommonPaint.setStrokeCap(Paint.Cap.ROUND);
        mCommonPaint.setFilterBitmap(true);
        mCommonPaint.setDither(true);
        mCommonPaint.setStrokeWidth(dp2px(20));
        mBitmap = decodeBitmap(R.mipmap.mm_012);

    }

    private Bitmap decodeBitmap(int resId) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inMutable = true;
        return BitmapFactory.decodeResource(getResources(), resId, options);
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);
        if (widthMode != MeasureSpec.EXACTLY) {
            widthSize = mDM.widthPixels / 2;
        }
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);

        if (heightMode != MeasureSpec.EXACTLY) {
            heightSize = widthSize / 2;
        }
        setMeasuredDimension(widthSize, heightSize);

    }
    private float x;
    private float y;
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        int width = getWidth();
        int height = getHeight();
        if (width < 1 || height < 1) {
            return;
        }
        if (shader == null) {
            float ratio  = 1.2f;
            scaledBitmap = Bitmap.createScaledBitmap(mBitmap, (int) (mBitmap.getWidth() * ratio), (int) (mBitmap.getHeight() * ratio), true);
            shader =  new BitmapShader(scaledBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);

        }
        //放大平移
        float offsetX = -(scaledBitmap.getWidth() - mBitmap.getWidth()) / 2f;
        float offsetY = -(scaledBitmap.getHeight() - mBitmap.getHeight())/2f;

        //窥视镜圆心
        float  mirrorCenterX = mBitmap.getWidth() - width / 4f;
        float  mirrorCenterY =  mBitmap.getHeight() - width/4f;

        //(mirrorCenterX - x) ,(mirrorCenterY-y) 是把当前中心点的图像平移到圆心哪里
        matrix.setTranslate( offsetX + (mirrorCenterX - x)  , offsetY + (mirrorCenterY-y));
        shader.setLocalMatrix(matrix);

        int save = canvas.save();
        canvas.clipRect(0,0,mBitmap.getWidth(),mBitmap.getHeight());
        canvas.drawBitmap(mBitmap, 0, 0, null);
        mCommonPaint.setColor(Color.DKGRAY);
        canvas.drawCircle(mirrorCenterX , mirrorCenterY,width/4f,mCommonPaint);
        mCommonPaint.setShader(shader);
        canvas.drawCircle( mirrorCenterX , mirrorCenterY,width/4f,mCommonPaint);
        mCommonPaint.setShader(null);

        canvas.restoreToCount(save);
    }

    @Override
    public void dispatchDrawableHotspotChanged(float x, float y) {
        super.dispatchDrawableHotspotChanged(x, y);
        this.x = x;
        this.y = y;
        postInvalidate();
    }

    @Override
    protected void dispatchSetPressed(boolean pressed) {
        super.dispatchSetPressed(pressed);
        postInvalidate();
    }

    public float dp2px(float dp) {
        return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dp, mDM);
    }

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值