解决ImageView图片挤压问题
# 一、需求
在屏幕边缘显示一张图片,超出屏幕宽度时,只显示图片的左边部分,并且不被挤压,其余部分剪切。但我在实际开发中,踩了个坑,这里做个记录,下面通过图片直观了解一下情况:
理想 | 现实 |
---|---|
目前可以确定,这种情况会出现在使用RelativeLayout作为ImageView父控件的情况下,其他类型的ViewGroup效果如何,暂不确定。
# 二、分析
这部分是我对问题研究的记录,心急的朋友可以直接跳到第三部分,看源码实现。
# 1、使用 HorizontalScrollView
在网上也百度到了一个类似的问题: Android ImageView 超出屏幕边界 图片会被挤压 (opens new window)
这位朋友遇到的问题跟我是差不多的,当子控件ImageView超过父控件尺寸时,ImageView显示的图片就会挤压,热心的网友们给他支了一个招:把ImageVIew放在HorizontalScrollView控件里面。但是,我遇到的需求是不要滚动效果(不要跟我说可以屏蔽ScrollView滚动。。。),就单单显示图片的部分区域而已,使用HorizontalScrollView控件明显不适用,只能另寻出路了。
# 2、使用 LinearLayout
实践证明,使用LinearLayout作为ImageView的父控件,当ImageView超出父控件尺寸时,不会挤压图片。
这种方式是一种不错的解决方案,但是,局限性太大,大多数情况下,会希望使用RelativeLayout作为控件的父控件,所以,这种情况也不适用我目前的情况。
# 3、使用ImageView的ScaleType
因为前面的方案都要求将ImageView放置到特定父控件中,局限性太大,严重的,可能会造成UI多次绘制,降低性能,造成画面卡顿,所以,感觉还是从ImageView本身下手比较合理。
ImageView的ScaleType有如下几种:
源自ImageView (一) ——从源码的角度分析ScaleType (缩放模式) (opens new window)
缩放模式 | 裁剪 | 按比例 | 放大 | 缩小 | 描述 |
---|---|---|---|---|---|
MATIRX | 未知 | 未知 | 未知 | 未知 | 通过设定setImageMatrix函数相应的矩阵,来完成。 |
FIT_XY | 否 | 否 | 是 | 是 | 直接将图片铺满整个view |
CENTER | 是 | 是 | 否 | 否 | 1)如果图片较小,直接居中全部显示2) 如果图片较大,裁剪后居中显示 |
CENTER_CROP | 是 | 是 | 是 | 是 | 按比例缩放,按照缩放比例大的进行缩放,然后居中显示(除非图片和view的形状一样,否则一定存在裁剪,所以有的描述为图片的长宽≥View的长宽) |
CENTER_INSIDE | 否 | 是 | 否 | 是 | 1) 如果图片较小,直接放入view,不进行放大处理,居中显示2) 将图片按比例进行缩放,使得图片完全展示,并且长或者宽等于view的长和宽(所以有的描述为图片的长宽≤View的长宽) |
FIT_START | 否 | 是 | 是 | 是 | 对图片的处理和FIT_CENTER,区别为:1)view的横向有空,居左边2)view的纵向有空,居上边 |
FIT_CENTER(默认) | 否 | 是 | 是 | 是 | 将图片按比例进行缩放,使得图片完全展示,并且长或者宽等于view的长和宽(所以有的描述为图片的长宽≤View的长宽),最后居中显示 |
FIT_END | 否 | 是 | 是 | 是 | 对图片的处理和FIT_CENTER,区别为:1) view的横向有空,居右边2) view的纵向有空,居下边 |
通过上表描述,可以确定只有CENTER_CROP的效果比较符合,但还是有点区别,CENTER_CROP只会显示中间区域部分,而我要的是显示左边区域,所以,下面就进行ImageView源码分析。
在ImageView中搜索CENTER_CROP,定位到核心逻辑就在configureBounds()方法中,代码如下:
以下是 博客:ImageView的ScaleType原理及效果分析 (opens new window) 中对CENTER_CROP的解释。
该模式按比例扩大图片的尺寸并居中显示,使得图片长(宽)等于或大于View的长(宽)。
如果dwidth/dheight>vwidth/vheight(图片的宽高比大于ImageView的宽高比),即vheight/dheight>vwidth/dwidth,也就是说ImageView和图片高度比小于ImageView和图片的宽度比,这时候取vheight/dheight的比例进行图片缩放,这样就能保证图片宽度在进行同等比例缩放的时候,图片宽度大于或等于ImageView的宽度,因为(vheight/dheight)* dwidth>vwidth。
同理,如果vwidth/dwidth>vheight/dheight时,取vwidth/dwidth作为图片的缩放比例,可以保证缩放完成后图片宽度等于ImageView的宽度,图片的高度大于或等于ImageView的高度,因为(vwidth/dwidth)*dheight>vheight。图片在缩放之后再进行CENTER操作即可。
上图所示,vwidth/dwidth>vheight/dheight,图片先按照vwidth/dwidth进行缩放,缩放后的图片高度>=vheight,然后再进行向上移位。
上图所示,vheight/dheight>vwidth/dwidth,图片先按照vheight/dheight进行缩放,缩放后的图片宽度>=vwidth,然后再进行想左移位。
回到主线,根据上面的分析,明白了CENTER_CROP的工作原理(可能也不太明白,但没关系),我们只需要在CENTER_CROP的基础上控制dx或dy即可,同时,在分析完整个configureBounds()方法之后,可以确定,不管是哪种ScaleType,其原理都是操控图片矩阵mDrawMatrix来实现的,这时候ScaleType中的MATRIX就可以派上用场了,下面是我对MATRIX做的一些封装,你也可以根据自己的项目需求,编写自己的处理逻辑。
# 三、代码
用了在布局中使用方便,我将它做成一个自定义控件,命名为ImageViewExt。
# 1、自定义属性attrs_image_view_ext
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="ImageViewExt">
<attr name="ive_scale_type_matrix_ext" format="enum">
<enum name="left_crop" value="1"/>
<enum name="right_crop" value="2"/>
<enum name="center_crop" value="3"/>
</attr>
</declare-styleable>
</resources>
# 1、自定义控件ImageViewExt
/**
* @创建者 CSDN_LQR
* @时间 2018/9/4
* @描述 ImageView扩展控件
* <p>
* 1、对 ScaleType.MATRIX 进行封装拓展
*/
public class ImageViewExt extends ImageView {
public static final int SCALE_TYPE_MATRIX_LEFT_CROP = 1; // 等比例缩放,当图像超出控件尺寸时,保留左边,其余部分剪切掉。
public static final int SCALE_TYPE_MATRIX_RIGHT_CROP = 2; // 等比例缩放,当图像超出控件尺寸时,保留右边,其余部分剪切掉。
public static final int SCALE_TYPE_MATRIX_CENTER_CROP = 3; // 等比例缩放,当图像超出控件尺寸时,保留中间,其余部分剪切掉。
@IntDef({SCALE_TYPE_MATRIX_LEFT_CROP, SCALE_TYPE_MATRIX_RIGHT_CROP, SCALE_TYPE_MATRIX_CENTER_CROP})
@Retention(RetentionPolicy.RUNTIME)
public @interface ScaleTypeMatrixExt {
}
private int mScaleTypeMatrixExt;
public ImageViewExt(Context context) {
this(context, null);
}
public ImageViewExt(Context context, @Nullable AttributeSet attrs) {
this(context, attrs, 0);
}
public ImageViewExt(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
protected void init(Context context, @Nullable AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.ImageViewExt);
mScaleTypeMatrixExt = typedArray.getInt(R.styleable.ImageViewExt_ive_scale_type_matrix_ext, -1);
typedArray.recycle();
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
handScaleTypeMatrixExt();
}
@Override
public void requestLayout() {
super.requestLayout();
handScaleTypeMatrixExt();
}
private void handScaleTypeMatrixExt() {
if (this.getScaleType() == ScaleType.MATRIX && mScaleTypeMatrixExt != -1) {
// 图片实际尺寸
final int dwidth = getDrawable().getIntrinsicWidth();
final int dheight = getDrawable().getIntrinsicHeight();
// ImageView图片显示尺寸
final int vwidth = getWidth() - getPaddingLeft() - getPaddingRight();
final int vheight = getHeight() - getPaddingTop() - getPaddingBottom();
float scale;
float dx = 0, dy = 0;
if (dwidth * vheight > vwidth * dheight) { // 图片宽高比 > 控件宽高比
scale = (float) vheight / (float) dheight;
switch (mScaleTypeMatrixExt) {
case SCALE_TYPE_MATRIX_LEFT_CROP:
dx = 0; // 保留左边
break;
case SCALE_TYPE_MATRIX_RIGHT_CROP:
dx = (vwidth - dwidth * scale); // 保留右边
break;
case SCALE_TYPE_MATRIX_CENTER_CROP:
dx = (vwidth - dwidth * scale) * 0.5f; // 保留中间(效果与 CENTER_CROP 一样)
break;
default:
break;
}
} else {
scale = (float) vwidth / (float) dwidth;
// dy = (vheight - dheight * scale) * 0.5f; // 根据实际情况编写,默认为0,保留上边
}
Matrix matrix = new Matrix();
matrix.setScale(scale, scale);
matrix.postTranslate(Math.round(dx), Math.round(dy));
this.setImageMatrix(matrix);
}
}
public void setScaleTypeMatrixExt(@ScaleTypeMatrixExt int scaleTypeMatrixExt) {
this.mScaleTypeMatrixExt = scaleTypeMatrixExt;
requestLayout();
}
}
# 四、使用
需要使用matrix作为ImageView的ScaleType,再指定ive_scale_type_matrix_ext属性即可,也可以在代码中调用setScaleType()、setScaleTypeMatrixExt()来指定。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="382.5dp"
android:background="@android:color/white"
android:orientation="horizontal">
<com.lqr.widget.ImageViewExt
android:layout_width="275dp"
android:layout_height="135.5dp"
android:layout_marginLeft="450dp"
android:scaleType="matrix"
android:src="@mipmap/main_recommand_1_4_sample3"
app:ive_scale_type_matrix_ext="left_crop"/>
</LinearLayout>
效果如下:
- 01
- Flutter - 危!3.24版本苹果审核被拒!11-13
- 02
- Flutter - 轻松搞定炫酷视差(Parallax)效果09-21
- 03
- Flutter - 轻松实现PageView卡片偏移效果09-08