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);
    }

}
相关推荐
Dragon Wu几秒前
前端 Canvas 绘画 总结
前端
顾北川_野2 分钟前
Android CALL关于电话音频和紧急电话设置和获取
android·音视频
CodeToGym5 分钟前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化
~甲壳虫6 分钟前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
顶呱呱程序6 分钟前
2-143 基于matlab-GUI的脉冲响应不变法实现音频滤波功能
算法·matlab·音视频·matlab-gui·音频滤波·脉冲响应不变法
~甲壳虫10 分钟前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
Cwhat11 分钟前
前端性能优化2
前端
&岁月不待人&13 分钟前
Kotlin by lazy和lateinit的使用及区别
android·开发语言·kotlin
爱吃生蚝的于勒28 分钟前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~31 分钟前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio