前面学习了怎么自定义View (Android学习小demo(1)自定义View), 这一个小demo, 我们接着来学习怎么自定义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 过程
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
....
}
layout 过程,就是将一个个子View 放到其应该展现的地方的一个过程。
Demo
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;
}
}
}
public CustomRotateViewGroup(Context context, AttributeSet attrs)
跟自定义View 一样,从xml 中来解析的 类必须实现这个构造函数
4) 进行Layout 过程,将子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的。
<?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了。