[TOC]
# 1. 前言
很多时候都需要图片的缩放效果。在ImageView中就支持这个属性,比如有下面这个图像:
![](https://img.kancloud.cn/0b/e0/0be0387aadb4cc933d2327812a501b3e_536x402.png)
其详细信息为:
![](https://img.kancloud.cn/b7/37/b73729fb40f5cc09b68462e5521a15eb_244x59.png)
但是,我们可以在ImageView中很容易将其载入到一个正方形的ImageView容器中,比如:
~~~
<ImageView
android:layout_gravity="center_horizontal"
android:layout_marginTop="4dp"
android:src="@drawable/a"
android:layout_width="200dp"
android:layout_height="200dp"
android:scaleType="fitCenter"
android:background="@drawable/border"
/>
~~~
为了观察方便,这里为ImageView设置了一个边框border.xml:
~~~
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<!--设置边框颜色-->
<stroke android:width="4dp" android:color="@color/teal_700"/>
<!--设置填充颜色-->
<solid android:color="@android:color/transparent"/>
<padding android:left="4dp" android:top="4dp" android:right="4dp" android:bottom="4dp"/>
</shape>
~~~
效果:
![](https://img.kancloud.cn/ee/eb/eeeb4fb533ca5c2fc4cd9ec9e90c63dc_251x214.png)
可见其便捷性。所以这里大致将所遇到的图片缩放方法做一个总结。
# 2. 方法案例
## 2.1 使用Bitmap的createScaledBitmap
~~~
class ScaleBitmapDemo: View {
constructor(context: Context?) : super(context) {init()}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs){init()}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
){init()}
private lateinit var targetBitmap: Bitmap
private lateinit var mPaint: Paint
private lateinit var rect: Rect
private var startX = 0
private var targetWidth = 0
private var targetHeight = 0
private var paddingSize = 0
private fun init(){
// 关闭硬件加速
setLayerType(LAYER_TYPE_SOFTWARE, null)
// 载入图像
val decodeResource = BitmapFactory.decodeResource(resources, R.drawable.a)
// 因为这里没有裁剪,所以计算一下宽度的缩放值即可
targetWidth = dp2px(200)
val scale = targetWidth * 1f / decodeResource.width
targetHeight = (decodeResource.height * 1f * scale).toInt()
mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
mPaint.color = resources.getColor(R.color.teal_700, null)
mPaint.style = Paint.Style.FILL
// 左边X坐标
startX = (resources.displayMetrics.widthPixels - targetWidth) / 2
rect = Rect(startX, 300, startX + targetWidth, 300 + targetHeight)
paddingSize = dp2px(4)
// 从当前存在的位图,按一定的比例创建一个新的位图。
targetBitmap = Bitmap.createScaledBitmap(decodeResource,
targetWidth - paddingSize * 2,
(targetHeight - scale * paddingSize * 5).toInt(), false)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.apply {
drawRect(rect, mPaint)
drawBitmap(targetBitmap, startX.toFloat() + paddingSize, 300f + paddingSize, mPaint)
}
}
private fun dp2px(value: Int) : Int{
return (resources.displayMetrics.density * value).toInt()
}
}
~~~
关键部分也就是:
~~~
// 从当前存在的位图,按一定的比例创建一个新的位图。
targetBitmap = Bitmap.createScaledBitmap
~~~
结果:
![](https://img.kancloud.cn/8b/09/8b098ad8d8e75a99eea961b4a6a8eff4_247x190.png)
因为这里没有裁剪,所以这里保持图片长方形形状进行缩放。
## 2.2 缩放图片(createScaledBitmap),裁剪画布
大致思路:首先判断下图像的大小和容器的大小,然后进行缩放图像,需要确保图像的宽高大于容器的宽高。然后将这个图像所在的画布进行裁剪,从中心裁剪出容器大小的图像即可。代码如下:
~~~
class ShaderScaleBitmapDemo : View {
constructor(context: Context?) : super(context) {
init()
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init()
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init()
}
private lateinit var mPaint: Paint
private lateinit var mBitmap: Bitmap
private lateinit var mRect: Rect
private var translateCanvasX = 0f
private fun init() {
// 关闭硬件加速
setLayerType(LAYER_TYPE_SOFTWARE, null)
// 载入图像
mBitmap = BitmapFactory.decodeResource(resources, R.drawable.a)
mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
mPaint.color = resources.getColor(R.color.teal_700, null)
mPaint.style = Paint.Style.FILL_AND_STROKE
// 判断图像是否大于容器大小
val imagewidth = dp2px(200 - 4)
// 1 表示图像的宽或者高比容器小,需要放大
// 2 表示图像大小合适,直接裁剪即可
// 3 表示图像大小过大,需要缩小
var flag = 1
if (mBitmap.width > imagewidth && mBitmap.height > imagewidth) {
if( mBitmap.height - imagewidth < 500 ) flag = 2
else flag = 3
}
// 缩放图像
when(flag){
1, 3 -> {
mBitmap = scaleBitmapImage(mBitmap, imagewidth)
}
}
// 计算裁剪画布的坐标
val clipRectLeft = (mBitmap.width - imagewidth) / 2
val clipRectTop = (mBitmap.width - imagewidth) / 2
val clipRectRight = left + imagewidth
val clipRectBottom = top + imagewidth
translateCanvasX = (resources.displayMetrics.widthPixels - imagewidth) * 1f/ 2
// 裁剪画布的矩形
mRect = Rect(clipRectLeft, clipRectTop, clipRectRight, clipRectBottom)
}
private fun scaleBitmapImage(bitmap: Bitmap, w: Int): Bitmap {
var scale = 1f
scale = Math.min(scale, w * 1f / bitmap.width)
scale = Math.min(scale, w * 1f / bitmap.height)
return createBitmapByOriginAndScaleValue(bitmap, scale)
}
private fun createBitmapByOriginAndScaleValue(bitmap: Bitmap, scale:Float): Bitmap{
return Bitmap.createScaledBitmap(
bitmap,
(scale * bitmap.width + 0.5f).toInt(),
(scale * bitmap.height + 0.5f).toInt(),
false
)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.apply {
val saveLayer = saveLayer(0f, 0f,
translateCanvasX + mBitmap.width.toFloat(),
mBitmap.height.toFloat(),
mPaint
)
translate(translateCanvasX, 0f)
clipRect(mRect)
drawBitmap(mBitmap, dp2px(2).toFloat(), dp2px(2).toFloat(), mPaint)
restoreToCount(saveLayer)
}
}
private fun dp2px(value: Int): Int {
return (resources.displayMetrics.density * value).toInt()
}
}
~~~
结果也就是下面的第二个图像,第一个是上面直接使用ImageView的效果:
![](https://img.kancloud.cn/32/7d/327dc09426168d8a5e60c362ad545bf5_251x383.png)
看着有点像是缩放的大小差不多,但是因为在这个自定义View中没有处理onMeasure方法,所以这里的容器的宽高是存在问题的,当然,这里不再处理。比如如果我们在使用的时候也指定border.xml:
~~~
<com.weizu.ShaderScaleBitmapDemo
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/border"
/>
~~~
结果:
![](https://img.kancloud.cn/59/c5/59c5a3662cbab35277f5d63f6a3798b8_422x621.png)
很明显,达不到预期效果。但是这里如果仅仅是缩放和裁剪画布已经达到的练习效果。之后会就如何自定义View来实现ImageView的效果进行实践,所以这里不再介绍如何修复这里的bug。
## 2.3 另一种裁剪方式(createBitmap)
在上面的案例中继续使用另一种裁剪效果,这里使用:
```
createBitmap(Bitmap source, int x, int y, int width, int height)
```
而不是裁剪画布。也就是直接裁剪图像,source是待裁剪的源图像,x,y是裁剪位置的坐标,width和height是裁剪的高度。比如在《Android自定义控件开发入门与实战》一文中使用:
```
Bitmap.createBitmap(src,src.getWidth()/3,src.getHeight()/3, src.getWidth()/3,src.getHeight()/3);
```
源图像,其中红色为辅助线:
![](https://img.kancloud.cn/0d/b5/0db52703351a72d9ed390c7b1330a030_610x505.png)
最后得到的:
![](https://img.kancloud.cn/ba/ba/baba487d4995847d4a54bf079be24c45_294x226.png)
所以这里也可以不裁剪画布,直接裁剪图像即可。如下面的代码:
~~~
private fun init() {
// 关闭硬件加速
setLayerType(LAYER_TYPE_SOFTWARE, null)
// 载入图像
mBitmap = BitmapFactory.decodeResource(resources, R.drawable.a)
mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
mPaint.color = resources.getColor(R.color.teal_700, null)
mPaint.style = Paint.Style.FILL_AND_STROKE
// 判断图像是否大于容器大小
val imagewidth = dp2px(200 - 4)
// 1 表示图像的宽或者高比容器小,需要放大
// 2 表示图像大小合适,直接裁剪即可
// 3 表示图像大小过大,需要缩小
var flag = 1
if (mBitmap.width > imagewidth && mBitmap.height > imagewidth) {
if( mBitmap.height - imagewidth < 500 ) flag = 2
else flag = 3
}
// 缩放图像
when(flag){
1, 3 -> {
mBitmap = scaleBitmapImage(mBitmap, imagewidth)
}
}
// 计算裁剪画布的坐标
val clipRectLeft = (mBitmap.width - imagewidth) / 2
val clipRectTop = (mBitmap.width - imagewidth) / 2
val clipRectRight = left + imagewidth
val clipRectBottom = top + imagewidth
translateCanvasX = (resources.displayMetrics.widthPixels - imagewidth) * 1f/ 2
// 裁剪画布的矩形
mRect = Rect(clipRectLeft, clipRectTop, clipRectRight, clipRectBottom)
}
private fun scaleBitmapImage(bitmap: Bitmap, w: Int): Bitmap {
var scale = 1f
scale = Math.min(scale, w * 1f / bitmap.width)
scale = Math.min(scale, w * 1f / bitmap.height)
return createBitmapByOriginAndScaleValue(bitmap, scale)
}
private fun createBitmapByOriginAndScaleValue(bitmap: Bitmap, scale:Float): Bitmap{
return Bitmap.createScaledBitmap(
bitmap,
(scale * bitmap.width + 0.5f).toInt(),
(scale * bitmap.height + 0.5f).toInt(),
false
)
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.apply {
val saveLayer = saveLayer(0f, 0f,
translateCanvasX + mBitmap.width.toFloat(),
mBitmap.height.toFloat(),
mPaint
)
clipRect(mRect)
drawBitmap(mBitmap, dp2px(2).toFloat(), dp2px(2).toFloat(), mPaint)
restoreToCount(saveLayer)
}
}
~~~
然后在xml中指定大小和居中:
~~~
<com.weizu.ShaderScaleBitmapDemo
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"
/>
~~~
效果:
![](https://img.kancloud.cn/7a/42/7a42cbbeef4fa73ae979c38d18b3c9b8_249x376.png)
主要方法也就是:
~~~
private fun createBitmapByOriginAndScaleValue(bitmap: Bitmap, scale:Float): Bitmap{
return Bitmap.createScaledBitmap(
bitmap, // src
(scale * bitmap.width + 0.5f).toInt(), // dstWidth
(scale * bitmap.height + 0.5f).toInt(), // dstHeight
false // filter
)
}
~~~
值得注意的是,在上面的Boolean类型变量filter,这里直接设置为了false。该参数的意义是即是否给图像添加滤波效果。如果设 置为 true,则能够减少图像中由于噪声引起的突兀的孤立像素点或像素块。
## 2.4 使用density来进行图像缩放
Density 用于表示该Bitmap合适的屏幕dpi,可以分为inDensity和inTargetDensity两个值,当着两个值不等的时候,它会缩放图像。比如:先获取 Bitmap 的原始 Density,然后将 Density 放大两倍,这样在 显示屏幕分辨率不变的情况下,显示出来的图片就应该缩小一半。对应代码:
~~~
class Scale2BitmapDemo : View {
constructor(context: Context?) : super(context) {
init()
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init()
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
) {
init()
}
private lateinit var mPaint: Paint
private lateinit var mBitmap: Bitmap
private fun init() {
// 关闭硬件加速
setLayerType(LAYER_TYPE_SOFTWARE, null)
// 加载图片
mBitmap = BitmapFactory.decodeResource(resources, R.drawable.lf)
mPaint = Paint(Paint.ANTI_ALIAS_FLAG)
mPaint.color = resources.getColor(R.color.teal_700, null)
mPaint.style = Paint.Style.FILL_AND_STROKE
// 缩小图片,这里使用inDensity
// 获取 Bitmap 的原始 Density,然后将 Density 放大两倍,这样在
// 显示屏幕分辨率不变的情况下,显示出来的图片就应该缩小一半。
mBitmap.density *= 2
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas?.apply {
drawBitmap(mBitmap,0f, 0f, mPaint)
}
}
}
~~~
效果:
![](https://img.kancloud.cn/43/67/43671b8d45f68347b9d9b1f2af18bcc8_225x375.png)
上面为原图,下面为缩放后的图像。
需要注意的是:Bitmap 在内存中的尺寸是没有变化的,这种设置 Bitmap Density 的方式只会影响显示缩放,而不会改变 Bitmap 本身在内存中的大。
- 介绍
- UI
- MaterialButton
- MaterialButtonToggleGroup
- 字体相关设置
- Material Design
- Toolbar
- 下拉刷新
- 可折叠式标题栏
- 悬浮按钮
- 滑动菜单DrawerLayout
- NavigationView
- 可交互提示
- CoordinatorLayout
- 卡片式布局
- 搜索框SearchView
- 自定义View
- 简单封装单选
- RecyclerView
- xml设置点击样式
- adb
- 连接真机
- 小技巧
- 通过字符串ID获取资源
- 自定义View组件
- 使用系统控件重新组合
- 旋转菜单
- 轮播图
- 下拉输入框
- 自定义VIew
- 图片组合的开关按钮
- 自定义ViewPager
- 联系人快速索引案例
- 使用ListView定义侧滑菜单
- 下拉粘黏效果
- 滑动冲突
- 滑动冲突之非同向冲突
- onMeasure
- 绘制字体
- 设置画笔Paint
- 贝赛尔曲线
- Invalidate和PostInvalidate
- super.onTouchEvent(event)?
- setShadowLayer与阴影效果
- Shader
- ImageView的scaleType属性
- 渐变
- LinearGradient
- 图像混合模式
- PorterDuffXfermode
- 橡皮擦效果
- Matrix
- 离屏绘制
- Canvas和图层
- Canvas简介
- Canvas中常用操作总结
- Shape
- 圆角属性
- Android常见动画
- Android动画简介
- View动画
- 自定义View动画
- View动画的特殊使用场景
- LayoutAnimation
- Activity的切换转场效果
- 属性动画
- 帧动画
- 属性动画监听
- 插值器和估值器
- 工具
- dp和px的转换
- 获取屏幕宽高
- JNI
- javah命令
- C和Java相互调用
- WebView
- Android Studio快捷键
- Bitmap和Drawable图像
- Bitmap简要介绍
- 图片缩放和裁剪效果
- 创建指定颜色的Bitmap图像
- Gradle本地仓库
- Gradle小技巧
- RxJava+Okhttp+Retrofit构建网络模块
- 服务器相关配置
- node环境配置
- 3D特效