前面学习了怎么自定义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 过程
@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 中来解析的 类必须实现这个构造函数
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了。