引言
纸上谈来终觉浅,绝知此事要躬行。可能你了解很多的安卓领域,但是到你实践的时候,你未必能写的出来。还请各位沉下心,花时间检验自己认为和自己实际是不是一个水平。
本文中,我将一步一步实现具有居中显示Bitmap,并能根据设置的宽高来调整Bitmap显示大小的自定义简易版ImageView
分析
View的背景
开始自定义View以前,我们先复习一下Android视图层次结构
再来复习一下自定义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的大小即可。
-
假如Bitmap的宽大于允许展示的宽 ,则缩小Bitmap,使得他的宽和控件的宽一致。同理Bitmap的高大于允许展示的高的情形。
-
假如Bitmap的宽高都大于允许展示的,则首先尝试根据宽缩小高,不起作用后再根据高缩小宽。
-
假如Bitmap的宽高均小于允许展示的,则直接展示Bitmap。
宽高都是AT_MOST模式
即宽高均不确定,我们需要把Bitmap的宽高和最大宽高比较
-
假如Bitmap的宽高都小于允许展示的,则直接展示Bitmap,控件大小为Bitmap加上padding的大小。
-
假如Bitmap的高小于,而宽不小于,则根据允许展示的宽,获得新的高,展示缩放后的Bitmap,控件大小为允许展示的宽和新的高。
-
同理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);
}
}