利用onMeasure测量来实现图片拉伸永不变形,解决屏幕适配问题

上一篇文章详细讲解了一下onMeasure/measure方法在Android自定义控件时的原理和作用,参看博文:Android自定义控件系列七:详解onMeasure()方法中如何测量一个控件尺寸(一),今天就来真正实践一下,让这两个方法大显神威来帮我们搞定图片的屏幕适配问题。

请尊重原创劳动成果,转载请注明出处:http://blog.csdn.net/cyp331203/article/details/45038329,非允许请勿用于商业或盈利用途,违者必究。

使用ImageView会遇到的问题

        在Android应用中,都少不了图片的显示,ImageView,轮播图,ViewPager等等,很多都是来显示图片的,比如一个广告条的轮播效果,参看博客:广告条效果实现----ViewPager加载大图片(LruCache)以及定时刷新,很多时候,我们都希望图片能够在宽度上填充父窗体,这样比较符合人的审美观点,但是问题就随之而来了,那就是高度如何定义??先来看一个普通的ImageView的
Xml布局文件的定义:

[html] view
plain
copy

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical" >  
  6.   
  7.     <ImageView  
  8.         android:layout_width="fill_parent"  
  9.         android:layout_height="wrap_content"  
  10.         android:src="@drawable/recommend_39" />  
  11.   
  12.     <TextView  
  13.         android:layout_width="wrap_content"  
  14.         android:layout_height="wrap_content"  
  15.         android:text="描述文字信息........................." />  
  16.   
  17. </LinearLayout>  

为了方便查看,我在ImageView下面又加上了一句描述的信息的TextView,这时,父控件都是填充父窗体,而ImageView则是:横向填充父窗体,纵向包裹内容;text都是包裹内容;那么来看看显示效果:

上面那个蓝色的小框就是ImageView的范围,这种效果一般都不会是我们想要的,那么如果想要ImageView中的图片能够填满ImageView的整个窗体怎么办?添加一个属性:scaleType,如下:

[html] view
plain
copy

  1. <ImageView  
  2.     android:layout_width="fill_parent"  
  3.     android:layout_height="wrap_content"  
  4.     android:scaleType="fitXY"  
  5.     android:src="@drawable/recommend_39" />  

效果如图:

可以看到,填是填满了,但是也由于纵向拉伸而使图片变形了。那要怎么做呢?

我们仔细观察一下,不难发现ImageView的纵向高度是包裹内容:wrapcontent,有些同学可能想到,能够直接在这里给ImageView一个特定的dip值,让这个ImageView符合图片的宽高比呢?这样做无疑是可以的,但是却不具有通用性。。。下面还是用上面的例子来讲解:

我们先来实现一下,制定ImageView的高度,来达到让这张图片在这个特定模拟器下显示比例正常的过程:

上面的图片实际像素的尺寸是:828*314,宽度和高度的比例大约是2.43,而我们这里使用的模拟器的尺寸是480*800(单位是px,也就是像素),也就是说宽度上的像素是480,那么我们要设置这个ImageView的高度为多少dip才能够让其正好符合2.43的比例呢。

Android设备上px(像素)、dpi(像素密度)、dip(密度无关像素)之间的关系:

这里又要牵扯出另外一块知识点,dip和dpi的联系:

分辨率(Resolution):表示设备屏幕上像素点的总数,比如上面的模拟器,屏幕像素尺寸是480(px)*800(px)

dpi(像素密度):是指每英寸的像素,所以同分辨率的两个设备,它们的dpi很可能不一样;如果一个手机分辨率5寸是1080*1920,而一个平板9.7寸分辨率也是1080*1920,那么 手机的dpi会比平板高出很多。

dp/dip:全称是Density-independent pixel ,中文名是 “密度无关像素”,也就是我们经常在xml文件中写的长度单位dp。为什么叫做密度无关像素呢,这其实是为了解决不同分辨率设备显示效果统一的一个解决方案,试想,如果一个两个手机屏幕都是一样大小,比如5寸,A手机的分辨率是 720*1280,而B手机的分辨率是1080*1920;那么如果我们想在上面显示一个图片分辨率为:200*200的图片,就会发现,在A手机上显示的图片,比B手机上显示的图片要小了很多;直观的来看,A手机的宽度是720,显示200*200的图片差不多要占据将近1/3的宽度,而反观B手机,宽度是1080,显示200*200的图片,则只需要占据1/5不到的宽度,而两个手机的尺寸又都是5寸,所以就会在显示同样分辨率的图片时,产生大小的差异。这种差异明显不是我们想要的。

所以dip/dp,密度无关像素就应运而生;它是这样规定的,dip与一个dpi(像素密度)为160dpi的设备的px(像素)值是相等的,而对于其他像素密度的设备,则依据转换公式来计算对应的dip值,这个公式是根据dpi(相当于比例),来转换px(像素)和dip(密度无关像素)的:

[java] view
plain
copy

  1. px = dip * (dpi / 160)  
  2.   
  3. dip = px / (dpi / 160)  

经过上面的转换之后,由于dip和px的转换是按照比例来的,而这个比例又是dpi/160,而dpi又是根据各个设备的分辨率和尺寸的比例得来的,所以使用相同的dip来设置的尺寸的控件,在相同尺寸大小的设备上,不论设备的分辨率是多少,它们显示的大小都会是一样的。

计算ImageView的合适高度的方法

有了上面的知识之后,我们就可以来转换一下我们的ImageView的大小了:

首先,图片的宽高比是2.43,而模拟器屏幕的宽度是480px,于是计算得到图片应该显示的高度是:480/2.43=197.53px(像素),但是一般时候,我们在xml文件中,设定的高度单位都是dip,所以这里要需要使用上面的转换公式:dip = px / (dpi / 160),而在这个模拟器的参数上可以查询到,它的dpi是240,所以计算得到高度应该是:

197.53/(240/160)=131.68(dip),约等于132dp,于是我们将上面的ImageView的高度设置成132dp:

[html] view
plain
copy

  1. <ImageView  
  2.     android:layout_width="fill_parent"  
  3.     android:layout_height="132dp"  
  4.     android:scaleType="fitXY"  
  5.     android:src="@drawable/recommend_39" />  

再来看一下显示效果:

这样显示比例就完全正常了。

但是这样问题就解决了吗?答案是没有,我们不妨来换一个模拟器来显示,这次选用Nexus7的模拟器,分辨率为1200*1920,dpi为320,ImageView的参数不变,再来看看效果:

会发现图片被拉长了,这是为什么 呢,我们可以简单的再算一下:

nexus7 宽度为1200px,而dpi为320,图片比例为2.43,那么应该设置ImageView的高度dp值是:1200/2.43/(320/160)=246.91dp,而我们现在的高度却还是之前的132dp,当然会发现被拉伸了。

那怎么办,有点让人抓狂!!!

重写onMeasure方法,重新测量控件高度,实现多种屏幕下自适应图片显示

其实办法是有的,思路就是让控件(ImageView)自己根据不同的设备帮我们来计算这个高度,而不需要我们自己去计算,那要怎么做呢?就得用到上一篇博文中说到的onMeasure方法了,对measure/onMeasure方法不熟悉的同学,可以再去看一下Android自定义控件系列七:详解onMeasure()方法中如何测量一个控件尺寸(一)Android自定义控件系列二:自定义开关按钮(一)等几篇文章,下面就开始:

首先要明确的一点就是,自定的view在调用view.measure()之前,是得不到控件的宽和高的,下面就一步步来写:

思路是首先写一个SmartImageView来继承自ImageView,并且添加相应的构造:

[java] view
plain
copy

  1. package com.example.imageviewdemo;  
  2.   
  3. import android.content.Context;  
  4. import android.util.AttributeSet;  
  5. import android.widget.ImageView;  
  6.   
  7. /** 
  8.  * @author : 苦咖啡 
  9.  *  
  10.  * @version : 1.0 
  11.  *  
  12.  * @date :2015年4月14日 
  13.  *  
  14.  * @blog : http://blog.csdn.net/cyp331203 
  15.  *  
  16.  * @desc : SmartImageView,能根据给定的图片比例,自动调整宽高,解决拉伸变形的屏幕适配问题 
  17.  */  
  18. public class SmartImageView extends ImageView {  
  19.   
  20.     public SmartImageView(Context context, AttributeSet attrs,  
  21.             int defStyleAttr, int defStyleRes) {  
  22.         super(context, attrs, defStyleAttr, defStyleRes);  
  23.     }  
  24.   
  25.     public SmartImageView(Context context, AttributeSet attrs, int defStyle) {  
  26.         super(context, attrs, defStyle);  
  27.     }  
  28.   
  29.     public SmartImageView(Context context, AttributeSet attrs) {  
  30.         super(context, attrs);  
  31.     }  
  32.   
  33.     public SmartImageView(Context context) {  
  34.         super(context);  
  35.     }  
  36.   
  37. }  

然后在SmartImageView中,添加一个float类型的成员变量ratio作为图片的比例值,并且给它暴露一个setter方法,以便于设置图片比例。

[java] view
plain
copy

  1. /** 图片宽和高的比例 */  
  2. private float ratio = 2.43f;  
  3.   
  4. public void setRatio(float ratio) {  
  5.     this.ratio = ratio;  
  6. }  

然后我们来重写onMeausre方法,如下:

[java] view
plain
copy

  1. @Override  
  2. protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  3.   
  4.     // 父容器传过来的宽度方向上的模式  
  5.     int widthMode = MeasureSpec.getMode(widthMeasureSpec);  
  6.     // 父容器传过来的高度方向上的模式  
  7.     int heightMode = MeasureSpec.getMode(heightMeasureSpec);  
  8.   
  9.     // 父容器传过来的宽度的值  
  10.     int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft()  
  11.             - getPaddingRight();  
  12.     // 父容器传过来的高度的值  
  13.     int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingLeft()  
  14.             - getPaddingRight();  
  15.   
  16.     if (widthMode == MeasureSpec.EXACTLY  
  17.             && heightMode != MeasureSpec.EXACTLY && ratio != 0.0f) {  
  18.         // 判断条件为,宽度模式为Exactly,也就是填充父窗体或者是指定宽度;  
  19.         // 且高度模式不是Exaclty,代表设置的既不是fill_parent也不是具体的值,于是需要具体测量  
  20.         // 且图片的宽高比已经赋值完毕,不再是0.0f  
  21.         // 表示宽度确定,要测量高度  
  22.         height = (int) (width / ratio + 0.5f);  
  23.         heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,  
  24.                 MeasureSpec.EXACTLY);  
  25.     } else if (widthMode != MeasureSpec.EXACTLY  
  26.             && heightMode == MeasureSpec.EXACTLY && ratio != 0.0f) {  
  27.         // 判断条件跟上面的相反,宽度方向和高度方向的条件互换  
  28.         // 表示高度确定,要测量宽度  
  29.         width = (int) (height * ratio + 0.5f);  
  30.   
  31.         widthMeasureSpec = MeasureSpec.makeMeasureSpec(width,  
  32.                 MeasureSpec.EXACTLY);  
  33.     }  
  34.   
  35.     super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
  36. }  

对于onMeasure方法,有几点需要注意的:

1、父容器传过来的两个参数widthMeasureSpec和heightMeasureSpec,通过MeasureSpec.getMode()来获取参数中的模式,与控件的填充方式都是有对应关系的,这在上一篇博文:Android自定义控件系列七:详解onMeasure()方法中如何测量一个控件尺寸(一)中也有提到过

        ①xml布局文件中的fill_parent或具体值,或者是直接设置控件的LayoutParams中的width和height的具体值或者LayoutParams.FILL_PARENT填充父容器方式,都会对应让上面通过getMode获取参数中的模式为:MeasureSpect.EXACTLY,代表精确取值,因为除了直接指定值之外,填充父容器,也是精确值

  

        ②xml布局文件中设置wrap_content方式或者是在代码中设置LayoutParams.WRAP_CONTENT方式,都会让getMode变成MeasureSpect.AT_MOST

2、对于父容器传过来的高度或者宽度的值,不一定就是控件想要的宽度或者高度的值,这是因为模式不一样,这个值代表的意义也不一样,所以才会需要通过测量来改变高度或者宽度的值来达到想要的效果。

其中,如果是模式是EXACTLY,那么传过来的值就是具体指,也可以说是父容器想要我们的控件变成这个具体的大小。

但是模式如果是AT_MOST,那么传过来的值,就不会是具体的值,一般会是一个最大值,因为AT_MOST代表,不超过多少,那么这个值就是不超过的上限。

3、可以看到我们通过拿到父容器传过来的高度,宽度的模式和值,然后经过两种if-else判断来重新测量值的大小,这两种判断的依据就是:

        ①当宽度确定时(宽度为EXACTLY),高度模式不是EXACTLY时(也即高度不确定时),高度按照ratio的比例来重新测量

        ②当高度确定时(高度为EXACTLY),高度模式不是EXACTLY时(也即高度不确定时),宽度按照ratio的比例来重新测量

4、在测量完毕之后,因为已经得到了想要的宽度或者高度的具体的精确的值,我们再通过MeasureSpec.makeMeasureSpec()方法来调用精确的值和精确的模式,来合成一个宽度/高度方向上的合成值,最后将合成好的值传递给super.onMeasure(widthMeasureSpec, heightMeasureSpec);设置控件为我们想要的大小。

然后我们就可以在XML布局文件中,将之前的ImageView改成:com.example.imageviewdemo.SmartImageView

然后在代码中将其通过findviewbyid拿到它的对象,然后通过setRatio来设定图片的比例,如下:

[html] view
plain
copy

  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent"  
  5.     android:orientation="vertical" >  
  6.   
  7.     <com.example.imageviewdemo.SmartImageView  
  8.         android:layout_width="fill_parent"  
  9.         android:layout_height="wrap_content"  
  10.         android:scaleType="fitXY"  
  11.         android:id="@+id/siv"  
  12.         android:src="@drawable/recommend_39" />  
  13.   
  14.     <TextView  
  15.         android:layout_width="wrap_content"  
  16.         android:layout_height="wrap_content"  
  17.         android:text="描述文字信息........................." />  
  18.   
  19. </LinearLayout>  

[java] view
plain
copy

  1. package com.example.imageviewdemo;  
  2.   
  3. import android.app.Activity;  
  4. import android.os.Bundle;  
  5.   
  6. public class MainActivity extends Activity {  
  7.   
  8.     @Override  
  9.     protected void onCreate(Bundle savedInstanceState) {  
  10.         super.onCreate(savedInstanceState);  
  11.         setContentView(R.layout.activity_main);  
  12.   
  13.         // 拿到SmartImageView对象  
  14.         SmartImageView siv = (SmartImageView) findViewById(R.id.siv);  
  15.         // 设置ratio的值  
  16.         siv.setRatio(2.43f);  
  17.     }  
  18. }  

经过上面之后,我们会发现,不论在什么屏幕下,不论在横屏还是竖屏,都能以正确的比例显示图片了,

效果图:

        

                       240*320/2.7寸设备                                                                  nexus7 1200*1920设备

                                     androidTV(1920*1080)横屏显示设备

最后再留一个小地方,就是要显示图片的ratio,这个可以有多种方式,一种是从服务器传过来时,服务器指定了,那么我们可以直接拿到,然后设置好即可;然后是自己通过测量BitMap的宽高来确定比例,也是可以的。

设置ratio的方式可以像上面的调用setRatio()方法,也可以使用自定义属性,在XML文件中直接确定,关于自定义属性,由于不是文本重点,不了解的同学可以去看看专栏的这篇文章:Android自定义控件系列四:自定义开关按钮(三)---
自定义属性
,就会明白了。

之后会带来自定义View的另一个方法:onLayout方法的研究,敬请期待,谢谢!

时间: 2024-10-04 17:15:25

利用onMeasure测量来实现图片拉伸永不变形,解决屏幕适配问题的相关文章

Android Zxing二维码扫描图片拉伸问题的解决方法

还是这个接手项目,二维码扫描集成的是zxing,扫描界面的图像有明显的拉伸变形. 这种问题,根据以往的经验,一般是x,y轴错位引起的,处理好x,y轴的问题,一般可以解决问题. 由于这个问题,之前有很多人遇到,并分享在网上了,所以,我这里也就不需要重复造轮子了. 这里看了一篇博客:Android Zxing二维码扫描图片拉伸,用了上面的办法, 成功的解决图片拉伸问题. 解决方法如下: 修改CameraConfigurationManager.Java里面的initFromCameraParamet

iOS图片实现可拉伸不变形的处理操作_IOS

在iOS的实际开发中,如果我们把一张有图片(有特别形状的,特别是类似有圆角的图片)放在UIButton中,当UIButton改变大小是,图片可能会被拉伸并且产生变形,我们可以通过-(UIImage *)resizableImageWithCapInsets:resizingMode:方法(通过UIImage对象调用该方法,并且传入要拉伸的图片的名字作为参数)实现返回一个可拉伸不变形的图片,这里我们把这个方法写到UIImage类的分类中把它封装起来,日后的iOS开发中我们可以直接拿来使用: UII

iOS图片拉伸小技巧_IOS

纵观移动市场,一款移动app,要想长期在移动市场立足,最起码要包含以下几个要素:实用的功能.极强的用户体验.华丽简洁的外观.华丽外观的背后,少不了美工的辛苦设计,但如果开发人员不懂得怎么合理展示这些设计好的图片,将会糟蹋了这些设计,功亏一篑. 比如下面张图片,本来是设计来做按钮背景的: button.png,尺寸为:24x60 现在我们把它用作为按钮背景,按钮尺寸是150x50: // 得到view的尺寸 CGSize viewSize = self.view.bounds.size; // 初

利用jQuery和CSS将背景图片拉伸_jquery

现在WEB页面设计比较流行使用大背景图,那么您知道如何使用一张大背景图进行拉伸效果呢?也就是说使用一张固定尺寸的背景图片,让它在页面中随着浏览器尺寸进行拉伸,就像我们的电脑桌面壁纸效果.本文将带您一起使用jQuery和CSS实现背景图片拉伸效果. 将背景图片拉伸,而不是平铺,注意平铺效果我们可以使用CSS的background-repeat来实行背景图片的平铺效果,本文讨论的是背景图片的拉伸效果.这种效果在一些前卫的页面设计中已经广泛应用,尤其在一些独立页面,像登录页面使用拉伸的背景图片效果比较

利用transitions类轻松创建图片过渡效果

创建 利用transitions类轻松创建图片过渡效果点击查看Flash:http://space.flash8.net/bbs/attachment.php?aid=311487 主场景第一帧上:myMovieClip.swapDepths(myMovieClip0); var i:Number = 0; function tween(obj) {         i++;         myMovieClip0.gotoAndStop(i-1);         obj.gotoAndSt

iphone图片拉伸的几种方法

系统至ios6之后,关于图片拉伸的方法已经扩展至3个函数: 1.ios4提供的方法: - (UIImage *)stretchableImageWithLeftCapWidth:(NSInteger)leftCapWidth topCapHeight:(NSInteger)topCapHeight 这个函数是UIImage的一个实例函数,它的功能是创建一个内容可拉伸,而边角不拉伸的图片,需要两个参数,第一个是不拉伸区域距离左边框的宽度,第二个参数是不拉伸区域距离上边框的宽度,其操作本质是对一个像

利用通道选区调出风景图片唯美的橙红色

利用通道选区调出风景图片唯美的橙红色 教程的调色方法虽然有点复杂,不过也不难理解.大致过程:先用计算等把图片中主色及绿色的选区计算出来,得到选区后用调色工具把颜色转化为我们想要的颜色即可. 原图 最终效果  1.打开原图素材,先将图片模式转为lab颜色(图像 > 模式 > Lab颜色),并新建通道,填充中性灰. 2.执行:图像 > 计算,得到绿色选区(如果不勾选反向那就是)即Alpha 2通道.  3.执行:图像 > 计算,得到黄色选区即Alpha 3通道. 4.再次计算,这次是蓝

css 如何让背景图片拉伸填充避免重复显示

如何让背景图片拉伸填充,这个问题听起来似乎很简单.但是很遗憾的告诉大家.不是我们想的那么简单. 比如一个容器(body,div,span)中设定一个背景.这个背景的长宽值在css2.1之前是不能被修改的. 所以实际的结果是只能重复显示,所以出现了repeat,repeat-x,repeat-y,no-repeat这些属性.就是用来控制背景图片的显示的.所以一般用作背景图片的有2类: 1.是一整张大图,尺寸和区域大小刚好吻合 2.一个很小的条状图,通过repeat后,形成一个很规则的大图背景. 但

利用JS简单制作的图片的切换

做网页时需要利用JS进行特效展示图片,下面是利用JS简单制作的图片的切换,需要的朋友可以了解下 在网页制作的时候,需要利用JS进行特效展示图片,下面是利用JS简单制作的图片的切换.  代码如下: <html>    <head>    <script type="JavaScript">    var srr = new Array('图片一', '图片二', '图片三', '图片四');//利用JS中内置的数组,进行数据的存储  var s = 0