💎一站式轻松地调用各大LLM模型接口,支持GPT4、智谱、星火、月之暗面及文生图 广告
[TOC] # 1. 前言 刚尝试使用Bitmap的尺寸压缩来进行图片大小的设置,但通过设置BitmapFactory.Options.inSampleSize来进行二次加载并没有达到预想的效果。在之前的[博客](https://blog.csdn.net/qq_26460841/article/details/119785601)中提到: > BitmapFactory.Options类中提供了inSampleSize属性,如果设置该值大于1,则请求解码器对原始图像进行二次采样,返回较小的图像以节省内存。样本大小是任一维度中对应于解码位图中单个像素的像素数。例如,inSampleSize=4返回的图像的宽度/高度为原始图像的1/4,像素数为1/16。任何小于等于1的值都被视为1。 但是这个尺寸只是像素尺寸,也就是下面的代码: ~~~ /** * 图片尺寸压缩 * @param bitmap 图片 * @param width 目标宽度 * @param height 目标高度 * @return 压缩后的图片 */ fun compressBitmap(bitmap: Bitmap, width: Int, height: Int): Bitmap{ // 装载Bitmap数据到字节数组 val byteArrayOutputStream = ByteArrayOutputStream() // Write a compressed version of the bitmap to the specified outputstream. bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream) val byteArray = byteArrayOutputStream.toByteArray() // byte[] // 获取BitmapFactory.Options val options = BitmapFactory.Options() BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options) // 设置采样率 options.inSampleSize = calculateInSampleSize(options, width, height) // 这里需要得到bitmap的实例,故而设置为false options.inJustDecodeBounds = false val tempBitmap = BitmapFactory.decodeByteArray(byteArray, 0, byteArray.size, options) return tempBitmap } /** * 计算采样率 */ private fun calculateInSampleSize(options: BitmapFactory.Options, width: Int, height: Int): Int{ val imgRealWidth = options.outWidth; val imgRealHeight = options.outHeight; var inSampleSize = 1; if(imgRealWidth > width || imgRealHeight > height){ if(imgRealWidth > width){ inSampleSize = Math.round(imgRealWidth.toFloat() / width.toFloat()); // 四舍五入 }else{ inSampleSize = Math.round(imgRealHeight.toFloat() / height.toFloat()); } } return inSampleSize; } ~~~ 上面代码的应用场景为图片的大小超过`16MB`,导致不能显示的问题。但实际上我这里的需求为让图片显示在固定大小的容器中,且随着容器大小的改变可以自适应。因为在ondraw中直接使用canvas来绘制bitmap图片就默认按照图片的原始大小来绘制的。 # 2. 分析 在ImageView中,我们常常可以指定scaleType值来让ImageView按照默认的缩放类型进行缩放,而这恰好就是我所需要的功能。故而考虑将这个功能copy到自己的自定义View中。 ## 2.1 源码分析 因为我们设置是在xml配置文件中通过如下的配置进行的: ~~~ <ImageView android:layout_width="match_parent" android:layout_height="match_parent" android:scaleType="centerCrop"> </ImageView> ~~~ 所以,我们需要在ImageView的构造函数中,找到含有AttributeSet的构造函数,可以看到其解析: ~~~ final int index = a.getInt(R.styleable.ImageView_scaleType, -1); if (index >= 0) { setScaleType(sScaleTypeArray[index]); } ~~~ 对于setScaleType函数: ~~~ public void setScaleType(ScaleType scaleType) { if (scaleType == null) { throw new NullPointerException(); } if (mScaleType != scaleType) { mScaleType = scaleType; requestLayout(); invalidate(); } } ~~~ 主要的部分也就是判断缩放类型是否和上一个一致,如果不一致就重新赋值。也就是我们这里需要关注mScaleType。进行简单的源码文件内搜索,可以看到在configureBounds中对其进行了缩放类型的判断以及处理,这里简要摘要: ~~~ private void configureBounds() { if (dwidth <= 0 || dheight <= 0 || ScaleType.FIT_XY == mScaleType) { /* If the drawable has no intrinsic size, or we're told to scaletofit, then we just fill our entire view. */ mDrawable.setBounds(0, 0, vwidth, vheight); mDrawMatrix = null; } else { // We need to do the scaling ourself, so have the drawable // use its native size. mDrawable.setBounds(0, 0, dwidth, dheight); if (ScaleType.MATRIX == mScaleType) { // Use the specified matrix as-is. if (mMatrix.isIdentity()) { mDrawMatrix = null; } else { mDrawMatrix = mMatrix; } } else if (fits) { // The bitmap fits exactly, no transform needed. mDrawMatrix = null; } else if (ScaleType.CENTER == mScaleType) { // Center bitmap in view, no scaling. mDrawMatrix = mMatrix; mDrawMatrix.setTranslate(Math.round((vwidth - dwidth) * 0.5f), Math.round((vheight - dheight) * 0.5f)); } else if (ScaleType.CENTER_CROP == mScaleType) { mDrawMatrix = mMatrix; float scale; float dx = 0, dy = 0; if (dwidth * vheight > vwidth * dheight) { scale = (float) vheight / (float) dheight; dx = (vwidth - dwidth * scale) * 0.5f; } else { scale = (float) vwidth / (float) dwidth; dy = (vheight - dheight * scale) * 0.5f; } mDrawMatrix.setScale(scale, scale); mDrawMatrix.postTranslate(Math.round(dx), Math.round(dy)); } else if (ScaleType.CENTER_INSIDE == mScaleType) { mDrawMatrix = mMatrix; float scale; float dx; float dy; if (dwidth <= vwidth && dheight <= vheight) { scale = 1.0f; } else { scale = Math.min((float) vwidth / (float) dwidth, (float) vheight / (float) dheight); } dx = Math.round((vwidth - dwidth * scale) * 0.5f); dy = Math.round((vheight - dheight * scale) * 0.5f); mDrawMatrix.setScale(scale, scale); mDrawMatrix.postTranslate(dx, dy); } else { // Generate the required transform. mTempSrc.set(0, 0, dwidth, dheight); mTempDst.set(0, 0, vwidth, vheight); mDrawMatrix = mMatrix; mDrawMatrix.setRectToRect(mTempSrc, mTempDst, scaleTypeToScaleToFit(mScaleType)); } } } ~~~ 可以发现其实也就是对mDrawMatrix的一系列变换,包括平移、缩放等。然后在onDraw方法中,也会对应的进行判断: ~~~ final int saveCount = canvas.getSaveCount(); canvas.save(); if (mCropToPadding) { final int scrollX = mScrollX; final int scrollY = mScrollY; canvas.clipRect(scrollX + mPaddingLeft, scrollY + mPaddingTop, scrollX + mRight - mLeft - mPaddingRight, scrollY + mBottom - mTop - mPaddingBottom); } canvas.translate(mPaddingLeft, mPaddingTop); if (mDrawMatrix != null) { // 对matrix的变换应用到canvas上的所有对象。 canvas.concat(mDrawMatrix); } mDrawable.draw(canvas); canvas.restoreToCount(saveCount); ~~~ 也就是变换其实是Matrix来施加的,最终通过canvas.concat来应用在画布之上。所以接下来继续了解Matrix。