Android 自定义ViewGroup
import android.content.Context; import android.content.res.TypedArray; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.RemoteViews; /** * Example of writing a custom layout manager. This is a fairly full-featured * layout manager that is relatively general, handling all layout cases. You * can simplify it for more specific cases. */ @RemoteViews.RemoteView public class CustomLayout extends ViewGroup { /** The amount of space used by children in the left gutter. */ private int mLeftWidth; /** The amount of space used by children in the right gutter. */ private int mRightWidth; /** These are used for computing child frames based on their gravity. */ private final Rect mTmpContainerRect = new Rect(); private final Rect mTmpChildRect = new Rect(); public CustomLayout(Context context) { super(context); } public CustomLayout(Context context, AttributeSet attrs) { this(context, attrs, 0); } public CustomLayout(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } /** * Any layout manager that doesn‘t scroll will want this. */ @Override public boolean shouldDelayChildPressedState() { return false; } /** * Ask all children to measure themselves and compute the measurement of this * layout based on the children. */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int count = getChildCount(); // These keep track of the space we are using on the left and right for // views positioned there; we need member variables so we can also use // these for layout later. mLeftWidth = 0; mRightWidth = 0; // Measurement will ultimately be computing these values. int maxHeight = 0; int maxWidth = 0; int childState = 0; // Iterate through all children, measuring them and computing our dimensions // from their size. for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { // Measure the child. measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0); // Update our size information based on the layout params. Children // that asked to be positioned on the left or right go in those gutters. final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (lp.position == LayoutParams.POSITION_LEFT) { mLeftWidth += Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); } else if (lp.position == LayoutParams.POSITION_RIGHT) { mRightWidth += Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); } else { maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin); } maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin); childState = combineMeasuredStates(childState, child.getMeasuredState()); } } // Total width is the maximum width of all inner children plus the gutters. maxWidth += mLeftWidth + mRightWidth; // Check against our minimum height and width maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight()); maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth()); // Report our final dimensions. setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT)); } /** * Position all children within this layout. */ @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { final int count = getChildCount(); // These are the far left and right edges in which we are performing layout. int leftPos = getPaddingLeft(); int rightPos = right - left - getPaddingRight(); // This is the middle region inside of the gutter. final int middleLeft = leftPos + mLeftWidth; final int middleRight = rightPos - mRightWidth; // These are the top and bottom edges in which we are performing layout. final int parentTop = getPaddingTop(); final int parentBottom = bottom - top - getPaddingBottom(); for (int i = 0; i < count; i++) { final View child = getChildAt(i); if (child.getVisibility() != GONE) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final int width = child.getMeasuredWidth(); final int height = child.getMeasuredHeight(); // Compute the frame in which we are placing this child. if (lp.position == LayoutParams.POSITION_LEFT) { mTmpContainerRect.left = leftPos + lp.leftMargin; mTmpContainerRect.right = leftPos + width + lp.rightMargin; leftPos = mTmpContainerRect.right; } else if (lp.position == LayoutParams.POSITION_RIGHT) { mTmpContainerRect.right = rightPos - lp.rightMargin; mTmpContainerRect.left = rightPos - width - lp.leftMargin; rightPos = mTmpContainerRect.left; } else { mTmpContainerRect.left = middleLeft + lp.leftMargin; mTmpContainerRect.right = middleRight - lp.rightMargin; } mTmpContainerRect.top = parentTop + lp.topMargin; mTmpContainerRect.bottom = parentBottom - lp.bottomMargin; // Use the child‘s gravity and size to determine its final // frame within its container. Gravity.apply(lp.gravity, width, height, mTmpContainerRect, mTmpChildRect); // Place the child. child.layout(mTmpChildRect.left, mTmpChildRect.top, mTmpChildRect.right, mTmpChildRect.bottom); } } } // ---------------------------------------------------------------------- // The rest of the implementation is for custom per-child layout parameters. // If you do not need these (for example you are writing a layout manager // that does fixed positioning of its children), you can drop all of this. @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new CustomLayout.LayoutParams(getContext(), attrs); } @Override protected LayoutParams generateDefaultLayoutParams() { return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); } @Override protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { return new LayoutParams(p); } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LayoutParams; } /** * Custom per-child layout information. */ public static class LayoutParams extends MarginLayoutParams { /** * The gravity to apply with the View to which these layout parameters * are associated. */ public int gravity = Gravity.TOP | Gravity.START; public static int POSITION_MIDDLE = 0; public static int POSITION_LEFT = 1; public static int POSITION_RIGHT = 2; public int position = POSITION_MIDDLE; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); // Pull the layout param values from the layout XML during // inflation. This is not needed if you don‘t care about // changing the layout behavior in XML. TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.CustomLayoutLP); gravity = a.getInt(R.styleable.CustomLayoutLP_android_layout_gravity, gravity); position = a.getInt(R.styleable.CustomLayoutLP_layout_position, position); a.recycle(); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(ViewGroup.LayoutParams source) { super(source); } } }先不说它的实现逻辑,看一下里面都用到了哪些方法(参数先忽略):onMeasure()、getChildCount()、getChildAt()、measureChildWithMargins()、setMeasuredDimension()、onLayout()、getPaddingLeft()、getMeasuredWidth()、layout()等。从这些方法可以看出,有些是View类里面的,有些是ViewGroup类里面的。因此不是说自定义ViewGroup时候使用的方法都是ViewGroup里面的,因为ViewGroup里面也包含了View,子View类在设置自己的位置、大小等时候需要调用自己的方法。所以下面的重点将从View以及ViewGroup常用方法两个方面来展开。
- 一个确切的数量
- MATCH_PARENT,这意味着要与其父一样大(减去填充)
- WRAP_CONTENT,这意味着认为希望是大到足以将其内容(加上填充)。
- UNSPECIFIED:这是父母用来确定所需的子视图的尺寸。例如,LinearLayout可能调用测量()在其孩子的高度不确定,宽度240找出给定的子视图要多高的宽度为240像素。
- EXACTLY:这是使用的父母对孩子施加一个精确的尺寸。孩子必须使用这个尺寸,保证所有的后代会适合这个尺寸。
- AT_MOST:这是使用的父母对孩子施加一个最大尺寸。孩子必须保证它和它的所有后代会适合这个尺寸。
- xml文件中的wrap_content-----MeasureSpec.AT_MOST:Child可以是自己任意的大小,但是有个绝对尺寸的上限。
- xml文件中的match_parent-----MeasureSpec.EXACTLY:Parent为child决定了一个绝对尺寸,child将会被赋予这些边界限制,不管child自己想要多大。
- xml文件中的-----MeasureSpec.UNSPECIFIED:这说明parent没有对child强加任何限制,child可以是它想要的任何尺寸。
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); //获取ViewGroup宽度 int height = MeasureSpec.getSize(heightMeasureSpec); //获取ViewGroup高度 setMeasuredDimension(width, height); //设置ViewGroup的宽高 int childCount = getChildCount(); //获得子View的个数,下面遍历这些子View设置宽高 for (int i = 0; i < childCount; i++) { View child = getChildAt(i); child.measure(viewWidth, viewHeight); //设置子View宽高 } }
protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) { boolean optical = isLayoutModeOptical(this); if (optical != isLayoutModeOptical(mParent)) { Insets insets = getOpticalInsets(); int opticalWidth = insets.left + insets.right; int opticalHeight = insets.top + insets.bottom; measuredWidth += optical ? opticalWidth : -opticalWidth; measuredHeight += optical ? opticalHeight : -opticalHeight; } mMeasuredWidth = measuredWidth; //这就是保存到类变量 mMeasuredHeight = measuredHeight; mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET; }
public final void measure(int widthMeasureSpec, int heightMeasureSpec) { // measure ourselves, this should set the measured dimension flag back onMeasure(widthMeasureSpec, heightMeasureSpec); }
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec)); }
/** * @see android.view.View#measure(int, int) */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); } /** * Determines the width of this view * @param measureSpec A measureSpec packed into an int * @return The width of the view, honoring constraints from measureSpec */ private int measureWidth(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else { // Measure the text result = (int) mTextPaint.measureText(mText) + getPaddingLeft() + getPaddingRight(); if (specMode == MeasureSpec.AT_MOST) { // Respect AT_MOST value if that was what is called for by measureSpec result = Math.min(result, specSize); } } return result; } /** * Determines the height of this view * @param measureSpec A measureSpec packed into an int * @return The height of the view, honoring constraints from measureSpec */ private int measureHeight(int measureSpec) { int result = 0; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); mAscent = (int) mTextPaint.ascent(); if (specMode == MeasureSpec.EXACTLY) { // We were told how big to be result = specSize; } else { // Measure the text (beware: ascent is a negative number) result = (int) (-mAscent + mTextPaint.descent()) + getPaddingTop() + getPaddingBottom(); if (specMode == MeasureSpec.AT_MOST) { // Respect AT_MOST value if that was what is called for by measureSpec result = Math.min(result, specSize); } } return result; }
@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int mTotalHeight = 0; // 当然,也是遍历子View,每个都要告诉ViewGroup int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View childView = getChildAt(i); // 获取在onMeasure中计算的视图尺寸 int measureHeight = childView.getMeasuredHeight(); int measuredWidth = childView.getMeasuredWidth(); childView.layout(left, mTotalHeight, measuredWidth, mTotalHeight + measureHeight); mTotalHeight += measureHeight; } }
/** * {@inheritDoc} */ @Override protected void dispatchDraw(Canvas canvas) { final int count = mChildrenCount; final View[] children = mChildren; int flags = mGroupFlags; if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) { final boolean cache = (mGroupFlags & FLAG_ANIMATION_CACHE) == FLAG_ANIMATION_CACHE; final boolean buildCache = !isHardwareAccelerated(); for (int i = 0; i < count; i++) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { final LayoutParams params = child.getLayoutParams(); attachLayoutAnimationParameters(child, params, i, count); bindLayoutAnimation(child); if (cache) { child.setDrawingCacheEnabled(true); if (buildCache) { child.buildDrawingCache(true); } } } }
4、drawChild(Canvas canvas, View child, long drawingTime))
5、getChildCount()
6、getChildAt()
7、onSizeChanged(int, int, int, int)
测量视图的大小,并保存起来
9、layout(int l, int t, int r, int b)
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) { final int size = mChildrenCount; final View[] children = mChildren; for (int i = 0; i < size; ++i) { final View child = children[i]; if ((child.mViewFlags & VISIBILITY_MASK) != GONE) { measureChild(child, widthMeasureSpec, heightMeasureSpec); } } }
protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { final LayoutParams lp = child.getLayoutParams(); final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec, mPaddingLeft + mPaddingRight, lp.width); final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, mPaddingTop + mPaddingBottom, lp.height); child.measure(childWidthMeasureSpec, childHeightMeasureSpec); }
2、ViewGroup和View是一样的,不同的是ViewGroup中onLayout是一个必须被实现的抽象方法。同样的在layout()中调用onLayout(),在measure()中调用onMeasure()。
3、要实现自定义的ViewGroup,可以在其必须实现的onLayout()中调用子View的layout()。而子view的layout()又会去调用其onLayout()方法,那么这样就形成了所谓的“递归”布局过程了。
4、一个view的真正布局方法是setFrame(),这在layout()中有被调用到。即layout()中会先调用setFrame()设置组件的边界,再调用onLayout(),如果该view包含子view。则可以调用每个子view的layout()方法。
5、要指子view的大小可以在子view的onMeasure()中调用setMesauredDimension()来实现。
6、综上所述,android中组件绘制分为两个过程,测量(Measure)和布局(Layout)。测量过程通过measure()及onMeasure()方法实现,measure()方法中调用了onMeasure()方法,在onMeasure()中可以通过setMeasuredDimension()来设置组件的尺寸信息,这些尺寸信息在layout过程中会被使用到。布局过程是通过layout()及onLayout()方法实现的,layout()中调用了onLayout(),在layout()中可以使用getMeasuredHeight()或getMeasuredWidth()通过setFrame()来设置组件范围。所以在自定义的ViewGroup中往往只重写onLayout(),这是因为ViewGroup的layout()方法已经被系统调用了。
import android.content.Context;
import android.util.AttributeSet;
import android.view.ViewGroup;
public class CustomViewGroup extends ViewGroup {
public CustomViewGroup(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
public CustomViewGroup(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
}
public CustomViewGroup(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
}
@Override
protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) {
// TODO Auto-generated method stub
}
}
import android.os.Bundle;
import android.app.Activity;
import android.graphics.Color;
import android.widget.ImageView;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(mCustomViewGroup);
}
}
android:layout_width="match_parent"
android:layout_height="match_parent" >
<!-- put first view to left. -->
<TextView
android:layout_width="50dp"
android:layout_height="100dp"
android:background="@drawable/ic_launcher"
android:text="l1" />
<!-- stack second view to left. -->
<TextView
android:layout_width="50dp"
android:layout_height="100dp"
android:background="@drawable/ic_launcher"
android:text="l2" />
</com.example.viewgroupdemo.CustomViewGroup>
@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int width = MeasureSpec.getSize(widthMeasureSpec); Log.i("onMeasure--width", width + ""); int height = MeasureSpec.getSize(heightMeasureSpec); Log.i("onMeasure--height", height + ""); setMeasuredDimension(width, height); measureChildren(widthMeasureSpec, heightMeasureSpec); }
@Override protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) { // TODO Auto-generated method stub for (int i = 0; i < getChildCount(); i++) { View v=getChildAt(i); v.layout(arg1, arg2, arg3, arg4); } }
@Override protected void onLayout(boolean arg0, int arg1, int arg2, int arg3, int arg4) { // 屏幕方向改变时候会调用 Log.i("onLayout--arg0", arg0 + ""); Log.i("onLayout--arg1", arg1 + ""); Log.i("onLayout--arg2", arg2 + ""); Log.i("onLayout--arg3", arg3 + ""); Log.i("onLayout--arg4", arg4 + ""); int childCount = getChildCount(); Log.i("onLayout--childCount", childCount + ""); for (int i = 0; i < childCount; i++) { View view = getChildAt(i); int chiledWidth = view.getMeasuredWidth(); Log.i("onLayout--chiledWidth", chiledWidth + ""); int chiledHeight = view.getMeasuredHeight(); Log.i("onLayout--chiledHeight", chiledHeight + ""); view.layout(arg1, arg2, chiledWidth, chiledHeight); } }
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。