前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android自定义控件之局部图片放大镜--BiggerView

Android自定义控件之局部图片放大镜--BiggerView

作者头像
张风捷特烈
发布2018-11-21 18:37:36
2K0
发布2018-11-21 18:37:36
举报
零、前言:
本文的知识点一览

1.自定义控件及自定义属性的写法,你也将对onMesure有更深的认识 2.关于bitmap的简单处理,及canvas区域裁剪 3.本文会实现两个自定义控件:FitImageView(图片自适应)BiggerView(放大镜),前者为后者作为铺垫。 4.最后会介绍如何从guihub生成自己的依赖库,这样一个完整的自定义控件库便ok了。 5.本项目源码见文尾捷文规范第一条

实现效果一览:

1.放大镜效果1:

放大镜效果1.gif

2.放大镜效果2:(使用了clipOutPath需要API26)

放大镜效果2.gif

3.该控件已做成类库(欢迎star),使用:
代码语言:javascript
复制
    allprojects {
        repositories {
            ...
            maven { url 'https://jitpack.io' }
        }
    }
    
    dependencies {
            implementation 'com.github.toly1994328:BiggerView:v1.01'
    }

一、宽高等比例自适应的控件:FitImageView

一开始想做放大镜效果,没多想就继承ImageView了,后来越做越困难,bitmap的裁剪模式会影响视图中显示图片的大小。 而View自己的的大小不变,会导致图片显示宽高捕捉困难,和图片左上角捕捉困难。 这就会导致绘制放大图片时的定位适配困难,那么多裁剪模式,想想都崩溃。 于是我想到,自己定义图像显示的view算了,需求是宽高按比例适应,并且View的尺寸即图片的尺寸, 将蓝色作为背景,结果如下,你应该明白是什么意思了吧,就是既想要图片不变形,又想不要超出的背景区域:

宽大于高.png

高大于宽.png


1.自定义属性:
代码语言:javascript
复制
    <!--图片放大镜-->
    <declare-styleable name="FitImageView">
        <!--图片资源-->
        <attr name="z_fit_src" format="reference"/>
    </declare-styleable>
2.自定义控件初始代码
代码语言:javascript
复制
/**
 * 作者:张风捷特烈<br/>
 * 时间:2018/11/19 0019:0:14<br/>
 * 邮箱:1981462002@qq.com<br/>
 * 说明:宽高自适应图片视图
 */
public class FitImageView extends View {

    private Paint mPaint;//主画笔
    private Drawable mFitSrc;//自定义属性获取的Drawable
    private Bitmap mBitmapSrc;//源图片
    protected Bitmap mFitBitmap;//适应宽高的缩放图片

    protected float scaleRateW2fit = 1;//宽度缩放适应比率
    protected float scaleRateH2fit = 1;//高度缩放适应比率
    protected int mImageW, mImageH;//图片显示的宽高

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

    public FitImageView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs,0);
    }

    public FitImageView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.FitImageView);
        mFitSrc = a.getDrawable(R.styleable.FitImageView_z_fit_src);
        a.recycle();
        init();//初始化
    }

    private void init() {
        //初始化主画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mBitmapSrc = ((BitmapDrawable) mFitSrc).getBitmap();//获取图片
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        //TODO draw
    }
3.测量及摆放:(这是核心处理)
代码语言:javascript
复制
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    mImageW = dealWidth(widthMeasureSpec);//显示图片宽
    mImageH = dealHeight(heightMeasureSpec);//显示图片高
    float bitmapWHRate = mBitmapSrc.getHeight() * 1.f / mBitmapSrc.getWidth();//图片宽高比
    if (mImageH >= mImageW) {
        mImageH = (int) (mImageW * bitmapWHRate);//宽小,以宽为基准
    } else {
       mImageW = (int) (mImageH / bitmapWHRate);//高小,以高为基准
    }
    setMeasuredDimension(mImageW, mImageH);
}


/**
 * @param heightMeasureSpec
 * @return
 */
private int dealHeight(int heightMeasureSpec) {
    int result = 0;
    int mode = MeasureSpec.getMode(heightMeasureSpec);
    int size = MeasureSpec.getSize(heightMeasureSpec);
    if (mode == MeasureSpec.EXACTLY) {
        //控件尺寸已经确定:如:
        // android:layout_height="40dp"或"match_parent"
        scaleRateH2fit = size * 1.f / mBitmapSrc.getHeight() * 1.f;
        result = size;
    } else {
        result = mBitmapSrc.getHeight();
        if (mode == MeasureSpec.AT_MOST) {//最多不超过
            result = Math.min(result, size);

        }
    }
    return result;
}


/**
 * @param widthMeasureSpec
 */
private int dealWidth(int widthMeasureSpec) {
    int result = 0;
    int mode = MeasureSpec.getMode(widthMeasureSpec);
    int size = MeasureSpec.getSize(widthMeasureSpec);
    if (mode == MeasureSpec.EXACTLY) {
        //控件尺寸已经确定:如:
        // android:layout_XXX="40dp"或"match_parent"
        scaleRateW2fit = size * 1.f / mBitmapSrc.getWidth();
        result = size;

    } else {
        result = mBitmapSrc.getWidth();
        if (mode == MeasureSpec.AT_MOST) {//最多不超过
            result = Math.min(result, size);
        }
    }
    return result;
}
4.创建缩放后的bitmap及绘制

创建的时机选择在onLayout里,因为要先测量后才能知道缩放比

代码语言:javascript
复制
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    mFitBitmap = createBigBitmap(Math.min(scaleRateW2fit, scaleRateH2fit), mBitmapSrc);
    mBitmapSrc = null;//原图已无用将原图置空
}

/**
 * 创建一个rate倍的图片
 *
 * @param rate 缩放比率
 * @param src  图片源
 * @return 缩放后的图片
 */
protected Bitmap createBigBitmap(float rate, Bitmap src) {
    Matrix matrix = new Matrix();
    //设置变换矩阵:扩大3倍
    matrix.setValues(new float[]{
            rate, 0, 0,
            0, rate, 0,
            0, 0, 1
    });
    return Bitmap.createBitmap(src, 0, 0,
            src.getWidth(), src.getHeight(), matrix, true);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    canvas.drawBitmap(mFitBitmap, 0, 0, mPaint);
}

一、自定义控件:BiggerView
1.自定义属性:attrs.xml
代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!--图片放大镜-->
    <declare-styleable name="BiggerView">
        <!--半径-->
        <attr name="z_bv_radius" format="dimension"/>
        <!--边线宽-->
        <attr name="z_bv_outline_width" format="dimension"/>
        <!--进度色-->
        <attr name="z_bv_outline_color" format="color"/>
        <!--放大倍率-->
        <attr name="z_bv_rate" format="float"/>
    </declare-styleable>
</resources>
2.初始化自定义控件
代码语言:javascript
复制
public class BiggerView extends FitImageView {
    private int mBvRadius = dp(30);//半径
    private int mBvOutlineWidth = 2;//边线宽

    private float rate = 4;//默认放大的倍数
    private int mBvOutlineColor = 0xffCCDCE4;//边线颜色

    private Paint mPaint;//主画笔
    private Bitmap mBiggerBitmap;//放大的图片
    private Path mPath;//剪切路径

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

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

    public BiggerView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.BiggerView);
        mBvRadius = (int) a.getDimension(R.styleable.BiggerView_z_bv_radius, mBvRadius);
        mBvOutlineWidth = (int) a.getDimension(R.styleable.BiggerView_z_bv_outline_width, mBvOutlineWidth);
        mBvOutlineColor = a.getColor(R.styleable.BiggerView_z_bv_outline_color, mBvOutlineColor);
        rate = (int) a.getFloat(R.styleable.BiggerView_z_bv_rate, rate);
        a.recycle();
        init();
    }

    private void init() {
        //初始化主画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setColor(mBvOutlineColor);
        mPaint.setStrokeWidth(mBvOutlineWidth * 2);
        mPath = new Path();
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        }
    }
}
二、初级阶段

点击的时候生成一个圆球,并随着手指移动跟随移动,松开手时消失,如图: 这个小球就是将来展示局部放大效果的地方

初阶效果.gif

1.添加成员变量:
代码语言:javascript
复制
private int mBvRadius = dp(30);//半径
private Paint mPaint;//主画笔

private float mCurX;//当前触点X
private float mCurY;//当前触点Y
private boolean isDown;//是否触摸
2.触点的处理
代码语言:javascript
复制
@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
            isDown = true;
            mCurX = event.getX();
            mCurY = event.getY();
            break;
        case MotionEvent.ACTION_UP:
            isDown = false;
    }
    invalidate();//记得刷新
    return true;
}
3.绘制
代码语言:javascript
复制
@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (isDown) {
        canvas.drawCircle(mCurX, mCurY, mBvRadius, mPaint);
    }
}

三、中级阶段:(放大图片的处理)

放大镜效果1.gif

放大图平移到触点.png

1.在onLayout时创建一个rate倍大小的Bitmap
代码语言:javascript
复制
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    super.onLayout(changed, left, top, right, bottom);
    mBiggerBitmap = createBigBitmap(rate, mFitBitmap);
}
2.绘制比放大后的图

这里通过定位,将图片移至指定位置

代码语言:javascript
复制
    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (isDown) {
            canvas.drawBitmap(mBiggerBitmap, -mCurX * (rate - 1), -mCurY * (rate - 1), mPaint);
        }
    }

这样效果1就完成了


3.效果2的实现:

使用了clipOutPath的API,不须26及以上 一开始触点是在圆的中心,这里往上调了一下(理由很简单,手指太大,把要看的部位遮住了...) 但这有个问题,就是最上面的部分再往上就无法显示了,使用做了如下的优化:

优化.gif

代码语言:javascript
复制
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    mShowY = -mCurY * (rate - 1) - 2 * mBvRadius;
    canvas.drawBitmap(mBiggerBitmap,
            -mCurX * (rate - 1), mShowY, mPaint);
    float rY = mCurY > 2 * mBvRadius ? mCurY - 2 * mBvRadius : mCurY +  mBvRadius;
    mPath.addCircle(mCurX, rY, mBvRadius, Path.Direction.CCW);
    canvas.clipOutPath(mPath);
    super.onDraw(canvas);
    canvas.drawCircle(mCurX, rY, mBvRadius, mPaint);
}

四、高级阶段:优化点:
1.使用枚举切换放大镜类型:
代码语言:javascript
复制
enum Style {
    NO_CLIP,//无裁剪,直接放大
    CLIP_CIRCLE,//圆形裁剪
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (isDown) {
        switch (mStyle) {
            case NO_CLIP://无裁剪,直接放大
                float showY = -mCurY * (rate - 1);
                canvas.drawBitmap(mBiggerBitmap, -mCurX * (rate - 1), showY, mPaint);
                break;
            case CLIP_CIRCLE:
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    mPath.reset();
                    showY = -mCurY * (rate - 1) - 2 * mBvRadius;
                    canvas.drawBitmap(mBiggerBitmap, -mCurX * (rate - 1), showY, mPaint);
                    float rY = mCurY > 2 * mBvRadius ? mCurY - 2 * mBvRadius : mCurY + mBvRadius;
                    mPath.addCircle(mCurX, rY, mBvRadius, Path.Direction.CCW);
                    canvas.clipOutPath(mPath);
                    super.onDraw(canvas);
                    canvas.drawCircle(mCurX, rY, mBvRadius, mPaint);
                } else {
                    mStyle = Style.NO_CLIP;//如果版本过低,无裁剪,直接放大
                    invalidate();
                }
                //可拓展更多模式....
        }
    }
}
2.落点在图片边界区域处理:

矩形区域校验.png

代码语言:javascript
复制
@Override
public boolean onTouchEvent(MotionEvent event) {
    switch (event.getAction()) {
        case MotionEvent.ACTION_DOWN:
        case MotionEvent.ACTION_MOVE:
            mCurX = event.getX();
            mCurY = event.getY();
            //校验矩形区域
            isDown = judgeRectArea(mImageW / 2, mImageH / 2, mCurX, mCurY, mImageW, mImageH);
            break;
        case MotionEvent.ACTION_UP:
            isDown = false;
    }
    invalidate();//记得刷新
    return true;
}

/**
 * 判断落点是否在矩形区域
 */
public static boolean judgeRectArea(float srcX, float srcY, float dstX, float dstY, float w, float h) {
    return Math.abs(dstX - srcX) < w / 2 && Math.abs(dstY - srcY) < h / 2;
}

五、上传github并成库
0.变成库!!,变成库!!,变成库!!

变成库.png


1.上传github

上传github.png


2.发布:

1.png

2.png


3.查看:https://jitpack.io/

see1.png

4.测试使用:

使用.png

ok,本篇完结

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.11.19 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 零、前言:
    • 本文的知识点一览
      • 实现效果一览:
        • 3.该控件已做成类库(欢迎star),使用:
        • 一、宽高等比例自适应的控件:FitImageView
          • 1.自定义属性:
            • 2.自定义控件初始代码
              • 3.测量及摆放:(这是核心处理)
                • 4.创建缩放后的bitmap及绘制
                • 一、自定义控件:BiggerView
                  • 1.自定义属性:attrs.xml
                    • 2.初始化自定义控件
                    • 二、初级阶段
                      • 1.添加成员变量:
                        • 2.触点的处理
                          • 3.绘制
                          • 三、中级阶段:(放大图片的处理)
                            • 1.在onLayout时创建一个rate倍大小的Bitmap
                              • 2.绘制比放大后的图
                                • 3.效果2的实现:
                                • 四、高级阶段:优化点:
                                  • 1.使用枚举切换放大镜类型:
                                    • 2.落点在图片边界区域处理:
                                    • 五、上传github并成库
                                      • 0.变成库!!,变成库!!,变成库!!
                                        • 1.上传github
                                          • 2.发布:
                                            • 3.查看:https://jitpack.io/
                                              • 4.测试使用:
                                              领券
                                              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档