现在的位置: 首页 > 综合 > 正文

Android学习小demo(2)自定义ViewGroup

2014年04月05日 ⁄ 综合 ⁄ 共 8060字 ⁄ 字号 评论关闭

前面学习了怎么自定义View  (位面传送门:Android学习小demo(1)自定义View)。

这一篇文章我们来学习,怎么自定义ViewGroup。

ViewGroup, 本质上也是一个View, 不过它增加了一个属性,就是能够去包含其他的View, 甚至是其他的ViewGroup, 故名思义, Views' Group.

既然是众多的View 在一起展现,那么这些View 到底是被安置在哪里,如何安置,谁上谁下,谁前谁后,那就是我们需要去考虑的事情了。

前面说过,一个View 的绘制, 是需要经过三个步骤的,分别是Measure, Layout 和 Draw.  对于ViewGroup 来说,我们里面是容纳其他的View, View 本身 的内容是怎么样的,我

们是不用关心的,所以说,最后一个过程 Draw 不是我们ViewGroup 关心的,我们不理它。

那么我们接下来看看 Measure 和 Layout 过程。

Measure 过程:

一个自定义的View 有多大,可以通过重写OnMeasure 函数来实现。

@Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {		
	setMeasuredDimension(300, 300);
}

但是更多情况下,我们希望一个View的大小,可以根据其父视图的大小变化而变化,那么实现这个测量过程呢,就是我们要在ViewGroup中的测量过程中实现的,如下:

@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		int len = getChildCount();
		int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
		parentHeight -= padding * len;
		for(int i = 0; i < len; i++){
			View child = getChildAt(i);
			int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(parentHeight / len, MeasureSpec.AT_MOST);
			measureChild(child, widthMeasureSpec, childHeightMeasureSpec);
		}
	}

简单讲解一下:

1)首先,我们要先设置好ViewGroup 本身的宽高,因为它也是一个View

2)  我们根据自己的想法,给ViewGroup 中的每一个子View 设置其宽高,通过调用measureChild 或者  child.measure 方法去实现。

综合来说,测量ViewGroup本身及其子View的过程就是ViewGroup的OnMeasure 函数做的事情了。

Tips: 当然,如果所有View 的宽高都是已经定好的,那么这个过程也是可以省略的.

Layout 过程

不管有没有进行OnMeasure 过程,对于一个ViewGroup 来说, layout 的这个步骤是必不可少的。
当我们自定义的ViewGroup 继承 ViewGroup 的时候,下面这个函数是一定要实现的。
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    ....
}

layout 过程,就是将一个个子View 放到其应该展现的地方的一个过程。

所以在OnLayout 函数中,我们自己去实现这个逻辑。

Demo

下面是一个很简单的自定义的ViewGroup 的一个小Demo。
首先自定义了一个CustomRotateViewGroup, 继承于ViewGroup, 其完整代码如下:
package com.example.apidemostudy;

import android.content.Context;
import android.graphics.Color;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

public class CustomRotateViewGroup extends ViewGroup{

	private int padding = 10;
	
	public CustomRotateViewGroup(Context context, AttributeSet attrs) {
		super(context, attrs);
		initiazle();
	}
	
	public void initiazle(){
		CustomRotateView customRotateView1 = new CustomRotateView(getContext(), R.drawable.photo1, 60, Color.YELLOW);
		addView(customRotateView1);
		CustomRotateView customRotateView2 = new CustomRotateView(getContext(), R.drawable.photo2, 30, Color.CYAN);
		addView(customRotateView2);
		CustomRotateView customRotateView3 = new CustomRotateView(getContext(), R.drawable.photo3, -30, Color.MAGENTA);
		addView(customRotateView3);
		CustomRotateView customRotateView4 = new CustomRotateView(getContext(), R.drawable.photo1, 60, Color.YELLOW);
		addView(customRotateView4);
		CustomRotateView customRotateView5 = new CustomRotateView(getContext(), R.drawable.photo2, 30, Color.CYAN);
		addView(customRotateView5);
		CustomRotateView customRotateView6 = new CustomRotateView(getContext(), R.drawable.photo3, -30, Color.MAGENTA);
		addView(customRotateView6);
	}
	
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec){
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
		int len = getChildCount();
		int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
		parentHeight -= padding * len;
		for(int i = 0; i < len; i++){
			View child = getChildAt(i);
			int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(parentHeight / len, MeasureSpec.AT_MOST);
			measureChild(child, widthMeasureSpec, childHeightMeasureSpec);
		}
	}

	@Override
	protected void onLayout(boolean changed, int l, int t, int r, int b) {
		int startX = 0;
		int startY = 0;		
		int len = getChildCount();
		for(int i = 0; i <len; i++){
			View child = getChildAt(i);
			int childHeight = child.getMeasuredHeight();
			int childWidth = child.getMeasuredWidth();
			child.layout(startX, startY, startX + childWidth, startY + childHeight);
			startY += childHeight + padding;
		}
	}

}

这里面其实就只有几个函数:

1)自定义构造函数
public CustomRotateViewGroup(Context context, AttributeSet attrs)

跟自定义View 一样,从xml 中来解析的 类必须实现这个构造函数

2) 初始化这个View, 添加了几个自定义的CustomRotateView
3) 进行 Measure 过程,将ViewGroup 的高度 减掉一下padding, 然后平均分给其子View 
4) 进行Layout 过程,将子View 们按照从上到下的高度放在一下。
下面是CustomRotateView 的代码,跟在Android学习小demo(1)自定义View 有点不一样,增加了一个自定义的构造函数:
package com.example.apidemostudy;

import com.example.apidemostudy.helper.BitmapReader;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Paint.Style;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;

public class CustomRotateView extends View {

	private final static String tag = "com.example.apidemostudy.CustomRotateView";
	
	private int resId;

	private Bitmap mBitmap; // The bitmap to be drawn

	private int bgColor; // the BackgroundColor

	private float degree; // the angels to rotate

	private Matrix matrix; // the matrix to transform

	private int mWidth = 240, mHeight = 240; // The RotateImageView's height and
												// width
	private int mPivotX, mPivotY; // the pivot point to rotate by

	private int mTranslateX, mTranslateY; // the translation to center in
											// current view
	
	private Paint mPaint;

	public CustomRotateView(Context context, int pResId, float pDegree, int pBgColor){
		super(context);
		resId = pResId;
		degree = pDegree;
		bgColor = pBgColor;
		initialze();
	}
	
	/**
	 * Constructor, called when inflate the xml definition
	 * 
	 * @param context
	 * @param attrs
	 */
	public CustomRotateView(Context context, AttributeSet attrs) {
		super(context, attrs);
		Log.v(tag, "CustomRotateView Initializing");
		TypedArray typedArray = context.obtainStyledAttributes(attrs,R.styleable.CustomRotateView);
		resId = typedArray.getResourceId(R.styleable.CustomRotateView_drawable, R.drawable.empty_photo);		
		degree = typedArray.getFloat(R.styleable.CustomRotateView_degree, 0);
		bgColor = typedArray.getColor(R.styleable.CustomRotateView_bgcolor,Color.BLACK);
		typedArray.recycle();

		initialze();
	}
	
	private void initialze(){					 
		matrix = new Matrix();
		mPaint = new Paint();
		mPaint.setColor(Color.WHITE);
		mPaint.setAntiAlias(true);
		mPaint.setStyle(Style.STROKE);
		mPaint.setStrokeWidth(1);
	}

	/**
	 * Measure the current view set it to the fixed width and height. it's ok to
	 * use the widthMeasureSpec and heightMeasureSpec if its size is decided by
	 * its parent.
	 */
	@Override
	public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		int width = MeasureSpec.getSize(widthMeasureSpec);		
		int height = MeasureSpec.getSize(heightMeasureSpec);		
		// Change to the drawable to bitmap and zoom it until the diagnoal line
		// is shorter than the required width and height
		mBitmap = zoomBitmap(BitmapReader.readBitmapFromResource(getContext(), resId, width), width, height);
		mPivotX = mBitmap.getWidth() / 2;
		mPivotY = mBitmap.getHeight() / 2;
		// translation, translate the bitmap to the center of this view
		mTranslateX = width / 2 - mPivotX;
		mTranslateY = height / 2 - mPivotY;
		setMeasuredDimension(width, height);
	}

	/**
	 * Draw the current view, 1) draw background color by bgColor 2) rotate the
	 * bitmap 3) move the bitmap to center
	 */
	@Override
	public void onDraw(Canvas canvas) {
		if(bgColor != Color.BLACK){
			canvas.drawColor(bgColor);
		}
		matrix.reset();
		matrix.postRotate(degree, mPivotX, mPivotY);
		matrix.postTranslate(mTranslateX, mTranslateY);

		canvas.drawBitmap(mBitmap, matrix, null);
		
		Rect rect = canvas.getClipBounds();
		rect.bottom--;
		rect.right--;
		canvas.drawRect(rect, mPaint);

	}

	/**
	 * Zoom the bitmap to make sure it will be always displayed inside the view
	 * 
	 * @param oldBitmap
	 * @return
	 */
	private Bitmap zoomBitmap(Bitmap oldBitmap, int reqWidth, int reqHeight) {
		Matrix pMatrix = new Matrix();
		int oldWidth = oldBitmap.getWidth();
		int oldHeight = oldBitmap.getHeight();

		double diagnoal = Math.sqrt(oldWidth * oldWidth + oldHeight * oldHeight);
		float scaleX = (float) (reqWidth / diagnoal);
		float scaleY = (float) (reqHeight / diagnoal);

		float scale = scaleX < scaleY ? scaleX : scaleY;
		pMatrix.postScale(scale, scale);
		Bitmap bitmap = Bitmap.createBitmap(oldBitmap, 0, 0, oldWidth,
				oldHeight, pMatrix, true);

		return bitmap;
	}
}

里面有一个BitmapReader 是专门写的一个辅助类,用来decode 各种资源文件并转化成bitmap的。

下面,我们在布局文件中引用这个自定义的ViewGroup,如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >
    
	<com.example.apidemostudy.CustomRotateViewGroup
	    android:background="#000000"
	    android:layout_width="match_parent"
	    android:layout_height="match_parent" 
	    >	    
	</com.example.apidemostudy.CustomRotateViewGroup>
</LinearLayout>

其他的就是一个MainActivity 去设置这个layout了。

当我们添加3个View跟6个View的时候,可以见到效果是不一样的。

下面是添加6个View的样子,


其实我这个自定义的ViewGroup,是为了下一步,用scroller来实现滚动,而一步一步做过来的,^_^,好了,先睡觉了。

抱歉!评论已关闭.