Android自定义控件学习笔记四
概要
本篇主要针对上一篇中onMeasure方法应用做一个简单的介绍,自定义一个按一定比例缩放的ImageView以实现图片的等比例缩放,当然了在实际开发中可以自定义一个按一定比例显示的布局视图。
上一篇中Android自定义控件学习笔记三中已经对onMeasure相关知识有了一定的了解,那么这篇笔记就通过上一篇知识点来做一个简单的应用,虽说简单可是用处也很大,在实际开发中我们常常需要处理各种图片资源,而图片如何显示的更为好美观不拉伸,让用户体验更好一些,一个图片从网络下载之后再在本地如何等比例缩放显示,这些都是每一个开发者必须要处理的事情,通过这篇笔记我们就可以很容易的解决上述问题。
ImageView显示图片
ImageView显示图片一般我们不会将图片用作背景图片的,而是直接src中设置图片,先来看一个简单的ImageView的xml的布局:
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" > <ImageView android:id="@+id/imageView" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="#ccc"/> </LinearLayout>
上述布局文件ImageView加上灰色的背景色是为了对比图片显示区用的,为了方面我们将手机进行横竖屏切换来对比显示,下面我们看一下显示效果:
横屏下的显示效果
竖屏下显示效果
然而一般情况下我们希望图片可以填充父窗体显示,ImageView布局文件更改如下:
<ImageView android:id="@+id/imageView01" android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="fitXY" android:background="#ccc"/>
对ImageView添加了一个scaleType属性,属性值为fixXY。这里在竖屏下图片已经填充父窗体了,图片就不再截取了,我们看一下横屏下图片的显示效果:
图片明显已经被拉伸变形了,而在这里我们仅仅进行了横竖屏两种分辨率测试,事实上市场上Android手机的分辨率远远超过这里的两种,可想而知在不同手机上的显示更是千差万别。那么该如何结果这种问题呢,现在只是知道这种静态很直接的处理方式是不可行的,那么只有动态计算了,只要知道图片的宽或者高以及宽高比值就可以了,在每一张图片显示之前动态计算一下。
处理方式一
本文示例所采用的图片是546*360的,宽高比为1.5,我们以屏幕的宽度为已知变量,然后通过宽高比计算出高度h=w/1.5;
//获取屏幕宽度 float ratio=1.5; DisplayMetrics metric = new DisplayMetrics(); getWindowManager().getDefaultDisplay().getMetrics(metric); phoneWidth = metric.widthPixels; imageView=(ImageView) findViewById(R.id.imageView02); //设置图片的宽度 imageView.getLayoutParams().width=phoneWidth; //计算图片需要显示的高度 imageView.getLayoutParams().height=(int) (phoneWidth/ratio); imageView.setImageResource(R.drawable.image);
在实际开发中我们要显示的图片多数会在ListView或者GridView中,这种情况需要批量处理图片,如在GridView中一般会在适配器Adapter中暴露一个方法比如setItemWidth(int width),当知道列数以及列数之间间隔宽度时就很容易计算出每一列的宽度,这样我们在适配器Adapter中的getView(int position, View convertView, ViewGroup parent)方法中就可以动态设置宽高了。这种处理方式虽然复杂了一些,但是也不失为一种很好的解决方式,因为目前市场上有许多应用就是采用的这种处理方式,接下来再讲一种处理方式,该方式也是今天的重点,自定义等比例缩放控件。
处理方式二
这种处理方式可以说是达到了一劳永逸的效果,想要很好的掌握该种方式,必须对上一篇笔记Android自定义控件学习笔记三中的onMeasure以及MeasureSpec有比较深入的理解,因为measure方法是不可以被重写的,而如果想要得到一个控件的宽高,也就父控件所分配给自己的空间大小,必须得在onMeasure执行过后才可以,现在就应该有些眉目了,如果要让一个控件按照一定比例显示,只需要在onMeasure方法中做一些想要的处理就可以了。 接下来线简单分析一下:
通过上一篇笔记我们知道控件的宽高测量都想要一个MeasureSpec,每一个MeasureSpec代表了一个对宽度和高度的要求,它有三种模式,在这里我们只需要对一种模式进行处理就可以了EXACTLY,在布局文件中有两种方式都对应这该模式一种是MATCH_PARENT,另外一种就是具体的dimension。
- 当宽度确定时(宽度为EXACTLY),高度模式不是EXACTLY时(也即高度不确定时),高度按照ratio的比例来重新测量;
- 当高度确定时(高度为EXACTLY),宽度模式不是EXACTLY时(也即宽度不确定时),宽度按照ratio的比例来重新测量。
测量完毕之后,因为已经得到了想要的宽度或者高度的具体的精确的值,我们再通过MeasureSpec.makeMeasureSpec()方法来调用精确的值和精确的模式,来合成一个宽度/高度方向上的合成值,最后将合成好的值传递给super.onMeasure(widthMeasureSpec, heightMeasureSpec);设置控件为我们想要的大小。
首先看一下自定义属性,有关自定义属性更多内容可以参看Android自定义控件学习笔记一:
<resources> <declare-styleable name="RatioImageView"> <attr name="ratio" format="float" /> </declare-styleable> </resources>
RatioImageView类
public class RatioImageView extends ImageView { private float ratio = 0.0f; public RatioImageView(Context context) { this(context, null); } public RatioImageView(Context context, AttributeSet attrs) { this(context, attrs, 0); } public RatioImageView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.RatioImageView); ratio = a.getFloat(R.styleable.RatioImageView_ratio, 0); a.recycle(); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); int widthMode = MeasureSpec.getMode(widthMeasureSpec); int heightMode = MeasureSpec.getMode(heightMeasureSpec); int width = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(); int height = MeasureSpec.getSize(heightMeasureSpec) - getPaddingBottom() - getPaddingTop(); if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY && ratio != 0.0f) { // 判断条件为,宽度模式为Exactly,也就是填充父窗体或者是指定宽度; // 且高度模式不是Exaclty,代表设置的既不是match_parent也不是具体的值,于是需要具体测量 // 且图片的宽高比已经赋值完毕,不再是0.0f // 表示宽度确定,要测量高度 height = (int) (width / ratio + 0.5f); heightMeasureSpec = MeasureSpec.makeMeasureSpec(height+ getPaddingTop() + getPaddingBottom(), MeasureSpec.EXACTLY); } else if (widthMode != MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY && ratio != 0.0f) { // 判断条件跟上面的相反,宽度方向和高度方向的条件互换 // 表示高度确定,要测量宽度 width = (int) (height * ratio + 0.5f); widthMeasureSpec = MeasureSpec.makeMeasureSpec(width+ getPaddingLeft() + getPaddingRight(), MeasureSpec.EXACTLY); } super.onMeasure(widthMeasureSpec, heightMeasureSpec); } public void setRatio(float ratio) { this.ratio = ratio; } }
下面是三张图片对应的布局:
<com.sunny.demo.view.RatioImageView android:id="@+id/imageView01" android:layout_width="match_parent" android:layout_height="wrap_content" android:scaleType="fitXY" app:ratio="1.5" /> <com.sunny.demo.view.RatioImageView android:id="@+id/imageView02" android:layout_width="240dp" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:scaleType="fitXY" app:ratio="1.5" /> <com.sunny.demo.view.RatioImageView android:id="@+id/imageView03" android:layout_width="180dp" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:scaleType="fitXY" app:ratio="1.5" />
效果如下图: