Android 放大镜窥视效果

前言

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

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

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

效果预览

滑动放大效果

窥视效果

方法镜滑动放大实现方法

使用Shader作为载体

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

  • Shader载入Bitmap,放大1.2倍
  • Shader向左上角偏移,对齐图片中心
java 复制代码
      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就是其中之一

java 复制代码
setClickable(true); //触发hotspot

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

java 复制代码
  @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,不过还没来得及试一下。

java 复制代码
   int save = canvas.save();
  canvas.clipRect(0,0,mBitmap.getWidth(),mBitmap.getHeight());
  canvas.restoreToCount(save);

绘制核心逻辑

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

java 复制代码
    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);

放大镜窥视效果

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

位置计算 & 绘制

固定镜子中心在右下角

java 复制代码
//放大平移时需要偏移的距离
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;

图像平移距离

scss 复制代码
(mirrorCenterX - x) 
(mirrorCenterY - y) 

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

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

绘制镜子

ini 复制代码
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图片绘制,很复杂的效果我们没有涉及到,但是在这些文章中,都会有各种各样的问题和思考。总之,我们要善于利用矩阵和设计思想,绘制我们的想象。

全部代码

按照惯例,提供全部代码

滑动放大代码

java 复制代码
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);
    }

}

窥视镜效果

scss 复制代码
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);
    }

}
相关推荐
Angel_girl31922 分钟前
vue项目使用svg图标
前端·vue.js
嘉陵妹妹23 分钟前
深度优先算法学习
学习·算法·深度优先
難釋懷27 分钟前
vue 项目中常用的 2 个 Ajax 库
前端·vue.js·ajax
Qian Xiaoo29 分钟前
Ajax入门
前端·ajax·okhttp
爱生活的苏苏1 小时前
vue生成二维码图片+文字说明
前端·vue.js
GalaxyPokemon1 小时前
LeetCode - 53. 最大子数组和
算法·leetcode·职场和发展
拉不动的猪1 小时前
安卓和ios小程序开发中的兼容性问题举例
前端·javascript·面试
炫彩@之星1 小时前
Chrome书签的导出与导入:步骤图
前端·chrome
贩卖纯净水.1 小时前
浏览器兼容-polyfill-本地服务-优化
开发语言·前端·javascript
前端百草阁1 小时前
从npm库 Vue 组件到独立SDK:打包与 CDN 引入的最佳实践
前端·vue.js·npm