Android自定义控件:图片比例适配,解决图片白边(详解View中onMeasure方法)

来源:互联网 发布:winxp网络共享设置 编辑:程序博客网 时间:2024/06/05 22:36

这里写图片描述

当App中涉及到布局需要展示大量图片时,你就应该考虑到“图片比例适配“的问题。当图片的宽高规格不同时,你设置展示的ImageView是否可以完好地展示填充满也就是说ImagView的比例和图片的比例不匹配,不然的话会导致图片旁会留有空白这样一系列的组图模块下拉,有的有白边,有的没有,非常影响美观。接下来的自定义控件将可以消除 展示图片有白边的问题,在不对图片进行任何裁剪、拉伸的前提下,最大限度呈现出图片样式!
这里写图片描述





一. 自定义 按比例展示高度 的控件

以上图的组图页面为例,为了达到更好地显示图片的效果,我们来自定义一个控件,这个控件的宽度填充屏幕,但是高度不确定,根据图片比例具体情况动态的设置高度值

这里写图片描述
比如说组图页面展现的图片样式都是这种:图片尺寸为 444 x 183 ,所以比例444/183 =2.43 (具体比例根据你APP中需要展示的图片类型而定!)有了比例之后,自定义控件中的 高度 就根据这个 比例 来动态设置。


这里写图片描述


1.自定义控件 继承帧布局

有了以上规划后,我们可以用一个 帧布局,以它来作为一个容器,让它的宽高严格按照图片的宽高来设置,使得图片填充这个帧布局,图片ImageView无需做任何改动,修改帧布局即可。(这里不需要考虑自定义控件去继承ImageView,太过复杂,帧布局更容易实现,而且在自定义控件和动态填充页面这方面使用非常广)

/** *   自定义控件, 按照比例来决定布局高度 * Created by gym on 2016/9/15. */public class RatioLayout extends FrameLayout {    private float ratio;    public RatioLayout(Context context) {        super(context);    }    public RatioLayout(Context context, AttributeSet attrs) {        super(context, attrs);    }    public RatioLayout(Context context, AttributeSet attrs, int defStyleAttr) {        super(context, attrs, defStyleAttr);    }}

2.自定义属性,布局编写

xml文件中部分代码        <com.gym.googlemarket.ui.view.RatioLayout            android:layout_width="match_parent"            android:layout_height="wrap_content"            app:ratio="2.43">        <ImageView            android:id="@+id/iv_pic"            android:layout_width="match_parent"            android:layout_height="match_parent"            android:src="@drawable/subject_default" />        </com.gym.googlemarket.ui.view.RatioLayout>attrs.xml  自定义属性<?xml version="1.0" encoding="utf-8"?><resources>    <declare-styleable name="RatioLayout">        <attr name="ratio" format="float"/>    </declare-styleable></resources>

如以上布局文件中代码所示,帧布局RatioLayout的宽度填充屏幕,高度随比例而定,也就是会让图片来填充满这个帧布局,所以原先设定的ImageView的属性要修改!
android:layout_height="wrap_content"
android:scaleType="fitXY"


3. 在构造方法中获取 属性值

 public RatioLayout(Context context, AttributeSet attrs) {        super(context, attrs);        // 获取属性值        // attrs.getAttributeFloatValue("", "ratio", -1);        // 当自定义属性时, 系统会自动生成属性相关id, 此id通过R.styleable来引用        TypedArray typedArray = context.obtainStyledAttributes(attrs,                R.styleable.RatioLayout);        // id = 属性名_具体属性字段名称 (此id系统自动生成)        ratio = typedArray.getFloat(R.styleable.RatioLayout_ratio, -1);        typedArray.recycle();// 回收typearray, 提高性能        System.out.println("ratio:" + ratio);    }

获取属性值的两种方法:
一.
一般谷歌最常用的是attrs.getAttributeFloatValue("", "ratio", -1);

二.
(1)首先使用 context 的方法获取属性的集合数组。
这里写图片描述
这里的RatioLayout是在自定义属性里定义的,底层已经将它编译成一个R文件了,就是一个int数组
这里写图片描述

(2)调用完方法后,它会返回一个TypedArray类的数组集合,再去get到自定义的属性字段即可。(注意:typedArray.getFloat(R.styleable.RatioLayout_ratio, -1); 中的RatioLayout_ratio id 是系统自动生成:”属性名称_具体字段“)
这里写图片描述

(3) typeArray 回收,提高性能。

分析完后,明显发现第一种取属性值的方法要简单,所以仅作了解即可。






二. 重写 onMeasure 方法(详解onMeasure )

经过以上步骤,我们已经拿到了 比例值,宽度也确定下拉,现在要根据比例值来设置 自定义控件高度。这就涉及到 尺寸的调整一个布局,它有三个核心的方法:Measure测量调整它的大小;layout设置它的位置;draw具体绘制。

而我们需要在测量—–Measure里重新修改它的宽高,重写onMeasure方法。方法中需要做的步骤:
a. 获取宽度
b. 根据宽度和比例ratio, 计算控件的高度
c.. 重新测量控件

    @Override    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {        // 1. 获取宽度        // 2. 根据宽度和比例ratio, 计算控件的高度        // 3. 重新测量控件        //1000000000000000000000111001110        System.out.println("widthMeasureSpec:" + widthMeasureSpec)        int width = MeasureSpec.getSize(widthMeasureSpec);// 获取宽度值        int widthMode = MeasureSpec.getMode(widthMeasureSpec);// 获取宽度模式        int height = MeasureSpec.getSize(heightMeasureSpec);// 获取高度值        int heightMode = MeasureSpec.getMode(heightMeasureSpec);// 获取高度模式        // 宽度确定, 高度不确定, ratio合法, 才计算高度值        if (widthMode == MeasureSpec.EXACTLY                && heightMode != MeasureSpec.EXACTLY && ratio > 0) {            // 图片宽度 = 控件宽度 - 左侧内边距 - 右侧内边距            int imageWidth = width - getPaddingLeft() - getPaddingRight();            // 图片高度 = 图片宽度/宽高比例            int imageHeight = (int) (imageWidth / ratio + 0.5f);            // 控件高度 = 图片高度 + 上侧内边距 + 下侧内边距            height = imageHeight + getPaddingTop() + getPaddingBottom();            // 根据最新的高度来重新生成heightMeasureSpec(高度模式是确定模式)            heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,                    MeasureSpec.EXACTLY);        }        // 按照最新的高度测量控件        super.onMeasure(widthMeasureSpec, heightMeasureSpec);    }

1. onMeasure 方法中的参数分析
void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
方法中两个参数widthMeasureSpecheightMeasureSpec并非是真正的宽高,而是带了一种模式的数值表示。

MeasureSpec类的两个方法,一个是getSize,一个是getMode。首先打印日志查看这个widthMeasureSpec具体值
System.out.println("widthMeasureSpec:" + widthMeasureSpec)
这里写图片描述

1073742286这么大的值,怎么可能是它的宽度呢?将它转为二进制
这里写图片描述

最前面的 ”1“代表的就是模式,后面的数值才是真正的宽度。将111001110 转换成 十进制,是462个像素(如下图蓝线所示)。我的模拟器是 480x800 ,所以这个值是合理的

这里写图片描述

所以这个widthMeasureSpec值表示的是:模式+宽度值



2. MeasureSpec中模式分析

而MeasureSpec的Mode 模式类型 有三种:
MeasureSpec.AT_MOST:至多模式, 控件有多大显示多大,类似于wrap_content
MeasureSpec.EXACTLY:确定模式, 类似宽高写死成dip,类似 match_parent
MeasureSpec.UNSPECIFIED:未指定模式,不确定宽高,动态计算测量。(举例scrollView,高度决定于它的孩子数量所占的高度)

这里写图片描述

所以之前转换成那么大的数,最前头的1代表模式 EXACTLY,而根据源码定义 1 << 30widthMeasureSpec数值之所以那么大,是因为它左移了30位。
所以真正的宽高值是 将widthMeasureSpec 转换为二进制后的后面几位数,再转换为十进制即可,当然方法中毋须那么复杂,MeasureSpec.getSize(widthMeasureSpec);即可。




3. 计算控件高度
详解完 onMeasure方法后,则可以开始重新测量布局,步骤a已完成,进行步骤b ,首先 if 判断 当宽度确定(这里的宽度一定要是一个准确值), 高度不确定, ratio合法, 才计算高度值。

 if (widthMode == MeasureSpec.EXACTLY                && heightMode != MeasureSpec.EXACTLY && ratio > 0)

注意:在计算imageView时,一定要考虑 Padding 值,图片宽度 = 控件宽度 - 左侧内边距 - 右侧内边距
根据图片真正的宽度,使用比例计算它的高度,这时我们自定义控件的高度也出来了
控件高度 = 图片高度 + 上侧内边距 + 下侧内边距,最后我们有了真正高度数值后,再获取当前Mode类型,封装好heightMeasureSpec,让父类执行测量的最新高度值即可。

        // 按照最新的高度测量控件        super.onMeasure(widthMeasureSpec, heightMeasureSpec);



三.展示效果

总结就是我们就是希望有一个布局,让它完全按照图片的比例来展现原样式,所以我们自定义一个帧布局FrameLayout,让图片去填充这个帧布局,帧布局有多宽多高,展示的图片就有多宽多高。

更改前
这里写图片描述


更改后
这里写图片描述


可以明显看出,前一张小白边左右多出来的,后者是完全无贴合的,而且并没有裁剪、拉伸图片,按照完美比例展现图片。以下动图就是最后成果,彻底消灭图片留有白边的情况

这里写图片描述





如果你还是认为这个 图片比例适配的自定义控件没起到什么作用,那我现在把 比例改成1 ,呈现如下,宽高值相同,但是图片的左右两边仍然是无缝衔接,未留有白边!

这里写图片描述






希望对你有帮助:)

1 0
原创粉丝点击