安卓实现简易的ImageView

引言

纸上谈来终觉浅,绝知此事要躬行。可能你了解很多的安卓领域,但是到你实践的时候,你未必能写的出来。还请各位沉下心,花时间检验自己认为和自己实际是不是一个水平。

本文中,我将一步一步实现具有居中显示Bitmap,并能根据设置的宽高来调整Bitmap显示大小的自定义简易版ImageView

分析

View的背景

开始自定义View以前,我们先复习一下Android视图层次结构

原生Activity的层次

再来复习一下自定义View的三个件

  • onMeasure() : 测量
  • onLayout() :摆放
  • onDraw() :绘制

View的绘制从ActivityThread类中Handler的处理RESUME_ACTIVITY事件开始,在执行performResumeActivity之后,创建PhoneWindow以及DecorView并调用WindowManager的addView方法添加到屏幕上,addView又调用ViewRootImpl的setView方法,最终执行performTraversals方法,依次执行performMeasure,performLayout,performDraw。

也就是view绘制的三大过程。

在onMeasure方法中,我们需要测量出当前View的宽高,然后调用setMeasuredDimension完成设置

在onLayout方法中,我们确定每个控件的摆放位置,严格来说,这个方法是给ViewGroup用的

在onDraw方法中,我们需要绘制当前的View

细说onMeasure

onMeasure(int widthMeasureSpec,int heightMeasureSpec)

widthMeasureSpec和heightMeasureSpec这两个值通常情况下都是由父视图经过计算后传递给子视图的,该参数表示控件可获得的空间以及关于这个空间描述的元数据

对这两个参数的操作,需要通过MeasureSpec实现,他有三个静态方法

MeasureSpec.makeMeasureSpec()    传入mode和size,返回一个新的int类型onMeasure参数。通常在ViewGroup中使用
MeasureSpec.getMode()    传入onMeasure方法提供的int类型参数,返回一个int类型,表示测量模式
MeasureSpec.getSize()    传入onMeasure方法提供的int类型参数,返回一个int类型,表示该View最大能获得的显示空间

测量模式如下图所示三种

三种模式和描述

分析ImageView的onMeasure

首先我们分析ImageView大小对Bitmap展示的影响。也就是onMeasure的流程。

分两大种,一种是有Bitmap的情况,一直是无Bitmap的情况。

没有Bitmap的情况下

ImageView的 宽/高 如果有一个设置了warp_content,且未设置Bitmap,则ImageView的 宽/高 为0;

有Bitmap的情况下

首先获取宽高的模式,和各自的最大长度

控件最大的宽高就是MeasureSpec.getSize获取的值。控件允许展示的宽高是这个值减去padding的值

宽高都是EXACTLY模式

即宽高大小已经固定,那我们只需要判断Bitmap的大小即可。

  1. 假如Bitmap的宽大于允许展示的宽 ,则缩小Bitmap,使得他的宽和控件的宽一致。同理Bitmap的高大于允许展示的高的情形。

  2. 假如Bitmap的宽高都大于允许展示的,则首先尝试根据宽缩小高,不起作用后再根据高缩小宽。

  3. 假如Bitmap的宽高均小于允许展示的,则直接展示Bitmap。

宽高都是AT_MOST模式

即宽高均不确定,我们需要把Bitmap的宽高和最大宽高比较

  1. 假如Bitmap的宽高都小于允许展示的,则直接展示Bitmap,控件大小为Bitmap加上padding的大小。

  2. 假如Bitmap的高小于,而宽不小于,则根据允许展示的宽,获得新的高,展示缩放后的Bitmap,控件大小为允许展示的宽和新的高。

  3. 同理Bitmap的宽小于,而高不小于的情形。

分析实现ImageView的显示

首先我们需要有两个Bitmap,一个是原始图像,一个是缩放后的图像。

因为我们是居中显示,所以我们需要确定图片绘制的左上角。可以在设置Bitmap后的第一次onDraw过程中,计算出这个坐标。

在更新图片时,重置第一次绘制的标记即可。

实战

public class SelectPointImageView extends android.view.View {

    private Bitmap bitmap;//实际的bitmap
    private Bitmap copy;//绘制需要的bitmap
    private volatile boolean isFirst = true;//是否第一次绘制当前Bitmap

    private final Point tl;//初始时,位图的左上角坐标
    private final Paint paint;//画笔

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

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

    public SelectPointImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, 0);
    }

    public SelectPointImageView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        tl = new Point();
        paint = new Paint();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);//算上padding的宽高
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int width = widthSize - paddingLeft - paddingRight;//真实能显示的宽高
        int height = heightSize - paddingTop - paddingBottom;
        if (bitmap == null) {//如果位图未设置,则宽高中哪个使用wrap,哪个就设置为0
            if (widthMode == MeasureSpec.AT_MOST)
                widthSize = 0;
            if (heightMode == MeasureSpec.AT_MOST)
                heightSize = 0;
            setMeasuredDimension(widthSize, heightSize);
            return;
        }
        int bitmapW = bitmap.getWidth();
        int bitmapH = bitmap.getHeight();
        int adjustW = adjustWidthByHeight(height, bitmapW, bitmapH);
        int adjustH = adjustHeightByWidth(width, bitmapW, bitmapH);
        Log.e("simon", "width" + width + "height" + height + "bitmapW" + bitmapW + "bitmapH" + bitmapH + "adjustW" + adjustW + "adjustH" + adjustH);
        if (widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY) {//宽高已经固定
            setMeasuredDimension(widthSize, heightSize);
            if (bitmapW > width && bitmapH > height) {//宽高都需要缩小
                if (adjustH < height) {//尝试根据宽缩小高
                    copy = Util.changeBitmapWH(bitmap, width, adjustH);
                    Log.e("simon", "adjustH success");
                } else if (adjustW < width) {//尝试根据高缩小宽
                    copy = Util.changeBitmapWH(bitmap, adjustW, height);
                    Log.e("simon", "adjustW success");
                } else {//两种缩放都失败了,摆烂开始
                    Log.e("simon", "adjust failed");
                    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
                }
            } else if (bitmapH > height) {
                Log.e("simon", "高大了,需要得到新的宽");
                copy = Util.changeBitmapWH(bitmap, adjustW, height);
            } else if (bitmapW > width) {
                Log.e("simon", "宽大了,需要得到新的高");
                copy = Util.changeBitmapWH(bitmap, width, adjustH);
            } else {//不需要缩小
                Log.e("simon", "宽高小于预设");
                copy = bitmap;
            }
            return;
        }
        if (widthMode == AT_MOST && heightMode == MeasureSpec.EXACTLY) {//宽不确定,高确定
            Log.e("simon", "宽不确定,高确定");
            if (adjustW > width) {//如果宽超过了能显示的宽
                copy = Util.changeBitmapWH(bitmap, width, adjustH);
                setMeasuredDimension(widthSize, heightSize);
            } else {
                copy = Util.changeBitmapWH(bitmap, adjustW, height);
                setMeasuredDimension(adjustW + paddingLeft + paddingRight, heightSize);
            }
            return;
        }
        if (heightMode == AT_MOST && widthMode == MeasureSpec.EXACTLY) {//高不确定,宽确定
            Log.e("simon", "高不确定,宽确定");
            if (adjustH > height) {
                copy = Util.changeBitmapWH(bitmap, adjustW, height);
                setMeasuredDimension(widthSize, heightSize);
            } else {
                copy = Util.changeBitmapWH(bitmap, width, adjustH);
                setMeasuredDimension(adjustW + paddingLeft + paddingRight, heightSize);
            }
            return;
        }
        if (heightMode == AT_MOST && widthMode == MeasureSpec.AT_MOST) {//宽高都不确定
            Log.e("simon", "宽高都不确定");
            if (bitmapW < width && bitmapH < height) {//宽高都小于预留空间
                copy = bitmap;
                setMeasuredDimension(bitmapW + paddingLeft + paddingRight, bitmapH + paddingBottom + paddingTop);
            } else if (bitmapW < width) {//宽够,高不够
                copy = Util.changeBitmapWH(bitmap, adjustW, height);
                setMeasuredDimension(adjustW + paddingLeft + paddingRight, heightSize);
            } else if (bitmapH < height) {//高够,宽不够
                copy = Util.changeBitmapWH(bitmap, width, adjustH);
                setMeasuredDimension(widthSize, adjustH + paddingBottom + paddingTop);
            }
            return;
        }
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    }

    //根据需要的高得到新的宽
    private int adjustWidthByHeight(int newHeight, int bmpW, int bmpH) {
        return bmpW * newHeight / bmpH;
    }

    //根据需要的宽得到新的高
    private int adjustHeightByWidth(int newWidth, int bmpW, int bmpH) {
        return bmpH * newWidth / bmpW;
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        if (copy == null) return;
        int paddingLeft = getPaddingLeft();
        int paddingRight = getPaddingRight();
        int paddingTop = getPaddingTop();
        int paddingBottom = getPaddingBottom();
        int width = getWidth() - paddingLeft - paddingRight;
        int height = getHeight() - paddingTop - paddingBottom;
        int centerX = width / 2 + paddingLeft;
        int centerY = height / 2 + paddingTop;
        int partBitmapW = copy.getWidth() / 2;
        int partBitmapH = copy.getHeight() / 2;
        if (isFirst) {
            tl.x = centerX - partBitmapW;
            tl.y = centerY - partBitmapH
            isFirst = false;
        }
        canvas.drawBitmap(copy, tl.x, tl.y, paint);
    }

    public void setImageBitmap(Bitmap bm) {
        bitmap = bm;
        copy = null;
        isFirst = true;
        requestLayout();
        invalidate();
    }

    public Bitmap getImageBitmap() {
        return bitmap;
    }
}

Util.java

public class Util {
    /**
     * 根据原图生成新宽高的图
     */
    public static Bitmap changeBitmapWH(Bitmap bitmap, int newWidth, int newHeight) {
        // 获得图片的宽高.
        int width = bitmap.getWidth();
        int height = bitmap.getHeight();
        // 计算缩放比例.
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        // 取得想要缩放的matrix参数.
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);
        // 得到新的图片.
        return Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, true);
    }
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

有头发的琦玉

打点钱,我会再努力的

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值