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" />

效果如下图:

评论

您确定要删除吗?删除之后不可恢复