Android 自定义View 使用ShapeDrawable加BitmapShader实现图片局部放大效果

旧城冷巷雨未停 提交于 2019-12-02 05:27:53

 

一些说明:

 

> BitmapShader中放置的Bitmap是原图的缩放后的

> ShapeDrawable的setBounds() 仅仅是设置一块shape的外矩形区,如用英语表示:size-region

> ShapeDrawable中图片默认draw的原点为:相对于图片自身的坐标(0,0)

> ShapeDrawable如果要对图片进行矩阵变化,可以使用它的shader的setLocalMatrix()

> ShapeDrawable如要显示成图片某一点为原点,需要设置它的shader的matrix,该matrix进行平移

> 因shader中的图片进行了缩放,所以平移时的坐标点也要乘以缩放值

 

关于上面的效果图说明:

 

> 上面的不规则图片,是由canvas绘制三个图形而来的,paint设置了shader

> 下方绘制了一张原图,放大镜效果:在指定下方的原图区内,有放大效果

> 其它黑色背景区内所看到的放大镜效果:只是因shader的TileMode为REPEAT

 

package com.stone.shader.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.BitmapShader;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.Shader;
import android.graphics.drawable.ShapeDrawable;
import android.graphics.drawable.shapes.OvalShape;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

import com.stone.shader.R;

import java.io.InputStream;

/**
 * desc   : 通过canvas绘制图形,paint中设置了BitmapShader
 * author : stone
 * email  : aa86799@163.com
 * time   : 2016/11/28 16 44
 */
public class BitmapShaderView extends View {

    private Bitmap mSrcBitmap;
    private BitmapShader mShader, mShaderScale;
    private Paint mPaint;
    private int mBmW, mBmH, mMin;
    private ShapeDrawable mDrawable;
    private int mRadius;
    private Matrix matrix;
    private int mScale;

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

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

    public BitmapShaderView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    private void init() {
        InputStream is = getResources().openRawResource(R.drawable.mn);
        mSrcBitmap = BitmapFactory.decodeStream(is);
        //如果使用decodeResource,可能造成图片缩放。如图片放在hdpi,而设备是xhdpi,就会放大
//        mSrcBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.mn);
        mBmW = mSrcBitmap.getWidth();
        mBmH = mSrcBitmap.getHeight();
        mMin = Math.min(mBmW, mBmH);
        System.out.println("w" + mBmW + ",h" + mBmH);

        mShader = new BitmapShader(mSrcBitmap, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR);

        mPaint = new Paint();
        mPaint.setShader(mShader);

        mRadius = mMin / 3;

        mScale = 2;

        mDrawable = new ShapeDrawable(new OvalShape());
        Bitmap scaledBitmap = Bitmap.createScaledBitmap(mSrcBitmap, mBmW * mScale, mBmH * mScale, true);
        mShaderScale = new BitmapShader(scaledBitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);//缩放
//        mShaderScale = new BitmapShader(scaledBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP);//缩放
        mDrawable.getPaint().setShader(mShaderScale);

        matrix = new Matrix();


    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

//        canvas.drawBitmap(mSrcBitmap, 0, 0, null); //仅原图

//        canvas.drawBitmap(mSrcBitmap, 0, 0, mPaint); //使用带有shader的paint,这跟上面是一样的效果 一般不这么用

        /*
        绘制图形时,使用了带shader的paint,那么图形区中就有对应的shader效果
        默认:canvas的起始点,即对应 BitmapShader中图片的起始点
            在不同的坐标点绘制图形,即可绘制上对应坐标区域内BitmapShader中的图片
         */
//        canvas.translate(300, 300);
//        canvas.drawRect(new Rect(0, 0, mBmW/2, mBmH/2), mPaint);

//        canvas.drawOval(new RectF(mBmW/2 + 20, mBmW/2, mBmW, mBmH), mPaint);

//        canvas.drawCircle(mBmW/2, mBmH/2, mMin/2, mPaint);

//        canvas.drawRect(0, 0, mBmW, mBmH, mPaint); //原图

        //在下方绘制原图
//        canvas.drawBitmap(mSrcBitmap, 0, 100, null);
        canvas.drawBitmap(mSrcBitmap, 0, mMin + mBmH/2 + 10, null);
        /*
        绘制使用了BitmapShader的 ShapeDrawable
        绘制到canvas上的表现:以bounds为区域进行绘制,主要是显示这一块区域在canvas上
            但是内容,不是对应坐标区的图片上的内容;而是默认从图片(0,0)开始,相同大小形状的一块区域
            所以如果要显示对应这区域的,就要使用shader有setLocalMatrix(matrix)进行平移
         */
        mDrawable.draw(canvas);

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        /*
        当shader的图片,相对于自身,平移到(-a,-b)时,这时的原点显示shader中原图的(a,b)位置内容
        测试时,配合  canvas.drawRect(0, 0, mBmW, mBmH, mPaint);
         */
//        matrix.setTranslate(-mBmW/2, -mBmH/2);

        /*
        测试时使mScale = 1, 配合 canvas.drawRect(0, 0, mBmW, mBmH, mPaint);
        没有缩放, 进行平移后,即drawable-bounds的起始点变化到平移点,再以下方的范围来绘制
        为什么要用半径减去x、y:
            若直接使用(-x, -y),即平移后,显示触摸点及之下的内容(当然,如果触摸点在图片之外时,显示的内容受Shader.TileMode影响)
            (-x, -y)现在是原点,要使其在显示内容的中心点,即要使其x方向、y方向都加上一个半径值:(-x+r, -y+r)
         */
//        matrix.setTranslate(mRadius - x, mRadius - y);


        /*
        当: mScale = 1, 且 要使原图在(0, mMin + mBmH/2 + 10)位置开始显示
            即要平移到:(r - x,  -y + r + (mMin + mBmH/2 + 10);
         */
//        matrix.setTranslate(mRadius - x, mRadius - y + (mMin + mBmH/2 + 10));

        /*
        如果在上面的基础上再进行了缩放 mScale >= 2; 且canvas.drawBitmap(mSrcBitmap, 0, mMin + mBmH/2 + 10, null);
        同时配合TileMode.REPEAT来实现以下方式:
         */
//        int sy = 100 % mBmH;//测试下面的算法,配合canvas.drawBitmap(mSrcBitmap, 0, 100, null);
       int sy = (mMin + mBmH/2 + 10) % mBmH;
        /*
        同理,如果绘制原图时,有x值,要用 sx = x % mBmW;
         */
//        matrix.setTranslate(mRadius - x * mScale, mRadius - y  * mScale);//如果原图的绘制在x、y上都等于0的情况下
        matrix.setTranslate(mRadius - x * mScale, mRadius - y * mScale + sy * mScale);

        mDrawable.getPaint().getShader().setLocalMatrix(matrix);

        /*
         设置一个以(x,y)为中点的正方形范围-内容区,仅指大小;边长为mRadius
         */
        mDrawable.setBounds(x - mRadius, y - mRadius, x + mRadius, y + mRadius);

        invalidate();
        return true;
    }

}

关于平移原理,简单放置了一张草图:shader/bsview.png

完整示例:

本文地址:https://github.com/aa86799/MyCustomView/tree/master/shader 

 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!