Android View系统分析之二View与ViewGroup
目录
ViewGroup与View的关系
我们在定义一个布局时,在它的顶层通常都是使用LinearLayout或者RelativeLayout等组件来包装一些子控件,例如TextView, Button, ImageView等。例如:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:gravity="center" > <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hello_world" /> </RelativeLayout>
为什么是这样呢?如果不用RelativeLayout包裹可不可以呢?
在Android View系统分析之从setContentView说开来(一)中的ViewGroup章节中我们说了,Android中的视图树是按照如下结构来组织的,即对顶层的是一个ViewGroup,然后其子节点可以是ViewGroup、View,只有ViewGroup下能够包含子节点,View则是叶子节点。如图 :
View是屏幕上所有可见元素的根类,其中ViewGroup也是View的子类。View的官方说明如下 :
This class represents the basic building block for user interface components. A View occupies a rectangular area on the screen and is responsible for drawing and event handling. View is the base class for widgets, which are used to create interactive UI components (buttons, text fields, etc.). The ViewGroup subclass is the base class for layouts, which are invisible containers that hold other Views (or other ViewGroups) and define their layout properties.
即一个View类的对象是一个包含了一定区域的可以在屏幕上绘制、并且处理事件的UI组件,所有可以放在屏幕上显示的都是View的子类。
而ViewGroup的子类则负责这些View类型对象的布局,即规定这些View放在屏幕的哪些地方。
因此,通常情况下我们都需要使用RelativeLayout,LinearLayout等ViewGroup来组织它的子节点,以约束子View的大小、显示位置等。当然也可以不用RelativeLayout来包裹,但是这样的话布局中只可以放置一个View或者ViewGroup元素,因为没有ViewGroup的组织,布局xml中只能有一个元素。例如 :
<TextView xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="@string/hello_world" />
View与ViewGroup的显示过程
View的丈量 ( measure )
View的丈量规格 ( MeasureSpec )
public static class MeasureSpec {private static final int MODE_SHIFT = 30;private static final int MODE_MASK = 0x3 << MODE_SHIFT;// 模式遮罩,将0x3 ( 二进制为 0011 )左移30位,这样高两位就为模式/*** Measure specification mode: The parent has not imposed any constraint* on the child. It can be whatever size it wants.*/public static final int UNSPECIFIED = 0 << MODE_SHIFT;// 模式被左移30位,因此模式在高2位上/*** Measure specification mode: The parent has determined an exact size* for the child. The child is going to be given those bounds regardless* of how big it wants to be.*/public static final int EXACTLY = 1 << MODE_SHIFT;/*** Measure specification mode: The child can be as large as it wants up* to the specified size.*/public static final int AT_MOST = 2 << MODE_SHIFT;/*** Extracts the mode from the supplied measure specification. 获取规格中的丈量模式** @param measureSpec the measure specification to extract the mode from* @return {@link android.view.View.MeasureSpec#UNSPECIFIED},* {@link android.view.View.MeasureSpec#AT_MOST} or* {@link android.view.View.MeasureSpec#EXACTLY}*/public static int getMode(int measureSpec) {return (measureSpec & MODE_MASK);}/*** Extracts the size from the supplied measure specification. 获取规格中的大小** @param measureSpec the measure specification to extract the size from* @return the size in pixels defined in the supplied measure specification*/public static int getSize(int measureSpec) {return (measureSpec & ~MODE_MASK);}// 代码省略
}
EXACTLY
AT_MOST
UNSPECIFIED
View的绘制
View的绘制相对就容易了,就是用Canvas绘制各种图形呗。drawText、drawBitmap、drawArc、drawRect等。View的draw()函数是一个模板方法,在其中会依次绘制背景、自身内容( onDraw函数中实现 ) 、以及绘制子View( ViewGroup 类型的才有,在dispatchDraw中实现 ) 、绘制边界、绘制滚动条,代码如下 :
/*** Manually render this view (and all of its children) to the given Canvas.* The view must have already done a full layout before this function is* called. When implementing a view, do not override this method; instead,* you should implement {@link #onDraw}.** @param canvas The Canvas to which the View is rendered.*/public void draw(Canvas canvas) {if (ViewDebug.TRACE_HIERARCHY) {ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);}final int privateFlags = mPrivateFlags;final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&(mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;/* 这里就是绘制的步骤说明* Draw traversal performs several drawing steps which must be executed* in the appropriate order:** 1. Draw the background* 2. If necessary, save the canvas' layers to prepare for fading* 3. Draw view's content* 4. Draw children* 5. If necessary, draw the fading edges and restore layers* 6. Draw decorations (scrollbars for instance)*/// Step 1, draw the background, if neededint saveCount;if (!dirtyOpaque) {final Drawable background = mBGDrawable;if (background != null) {final int scrollX = mScrollX;final int scrollY = mScrollY;if (mBackgroundSizeChanged) {background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);mBackgroundSizeChanged = false;}if ((scrollX | scrollY) == 0) {background.draw(canvas);} else {canvas.translate(scrollX, scrollY);background.draw(canvas);canvas.translate(-scrollX, -scrollY);}}}// skip step 2 & 5 if possible (common case)final int viewFlags = mViewFlags;boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;if (!verticalEdges && !horizontalEdges) {// Step 3, draw the contentif (!dirtyOpaque) onDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);// Step 6, draw decorations (scrollbars)onDrawScrollBars(canvas);// we're done...return;}
关于Canvas的资料,可以参考Android--使用Canvas绘图、利用Canvas绘制各种图形。
ViewGroup对子View的管理
先看看ViewGroup的官方说明 :
A ViewGroup is a special view that can contain other views (called children.) The view group is the base class for layouts and views containers. This class also defines the ViewGroup.LayoutParams class which serves as the base class for layouts parameters.
ViewGroup组织和管理它的子View,其实主要就是对子View进行布局(规定它们放在在哪里)以及迭代所有子元素让它们绘制自身,并且使用LayoutParams对象对子元素的大小、边距等进行约束。我们在xml中定义一个View或者ViewGroup时使用的layout_width、layout_height就是LayoutParams对象的属性。
ViewGroup丈量 ( measure )
public abstract class ViewGroup extends View implements ViewParent, ViewManager {// 代码省略/*** Ask all of the children of this view to measure themselves, taking into* account both the MeasureSpec requirements for this view and its padding.* We skip children that are in the GONE state The heavy lifting is done in* getChildMeasureSpec.** @param widthMeasureSpec The width requirements for this view* @param heightMeasureSpec The height requirements for this view* 遍历所有子元素,并且对每个子元素进行丈量*/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进行丈量操作measureChild(child, widthMeasureSpec, heightMeasureSpec);}}}/*** Ask one of the children of this view to measure itself, taking into* account both the MeasureSpec requirements for this view and its padding.* The heavy lifting is done in getChildMeasureSpec.** 对单个元素进行丈量* @param child The child to measure* @param parentWidthMeasureSpec The width requirements for this view* @param parentHeightMeasureSpec The height requirements for this view*/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 进行丈量操作child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}/*** Ask one of the children of this view to measure itself, taking into* account both the MeasureSpec requirements for this view and its padding* and margins. The child must have MarginLayoutParams The heavy lifting is* done in getChildMeasureSpec.* 对Child View进行丈量操作,并且算上margin。注意如果调用这个方法, 那你的LayoutParams必须是MarginLayoutParams的子类,* 否则会抛出强制转换的异常。** @param child The child to measure* @param parentWidthMeasureSpec The width requirements for this view* @param widthUsed Extra space that has been used up by the parent* horizontally (possibly by other children of the parent)* @param parentHeightMeasureSpec The height requirements for this view* @param heightUsed Extra space that has been used up by the parent* vertically (possibly by other children of the parent)*/protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin+ widthUsed, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin+ heightUsed, lp.height);child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}// 代码省略}
ViewGroup的布局( layout )
// 布局 @Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {if (mOrientation == VERTICAL) {// 竖直方向的布局layoutVertical();} else {layoutHorizontal();}}/*** Position the children during a layout pass if the orientation of this* LinearLayout is set to {@link #VERTICAL}.* 竖直方向上的布局操作* @see #getOrientation()* @see #setOrientation(int)* @see #onLayout(boolean, int, int, int, int)*/void layoutVertical() {final int paddingLeft = mPaddingLeft;int childTop;int childLeft;// Where right end of child should gofinal int width = mRight - mLeft;int childRight = width - mPaddingRight;// Space available for childint childSpace = width - paddingLeft - mPaddingRight;final int count = getVirtualChildCount();final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;// 根据gravity设置child的top起始位置switch (majorGravity) {case Gravity.BOTTOM:// mTotalLength contains the padding alreadychildTop = mPaddingTop + mBottom - mTop - mTotalLength;break;// mTotalLength contains the padding alreadycase Gravity.CENTER_VERTICAL:childTop = mPaddingTop + (mBottom - mTop - mTotalLength) / 2;break;case Gravity.TOP:default:childTop = mPaddingTop;break;}// 遍历所有子Viewfor (int i = 0; i < count; i++) {final View child = getVirtualChildAt(i);if (child == null) {childTop += measureNullChild(i);} else if (child.getVisibility() != GONE) {// 如果child不是隐藏的,那么获取child的宽度和高度final int childWidth = child.getMeasuredWidth();final int childHeight = child.getMeasuredHeight();// 获取child的布局参数final LinearLayout.LayoutParams lp =(LinearLayout.LayoutParams) child.getLayoutParams();// child的gravityint gravity = lp.gravity;if (gravity < 0) {gravity = minorGravity;}final int layoutDirection = getResolvedLayoutDirection();final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);// 根据child的gravity调整child的水平、左边界、右边界的位置switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {case Gravity.CENTER_HORIZONTAL:childLeft = paddingLeft + ((childSpace - childWidth) / 2)+ lp.leftMargin - lp.rightMargin;break;case Gravity.RIGHT:childLeft = childRight - childWidth - lp.rightMargin;break;case Gravity.LEFT:default:childLeft = paddingLeft + lp.leftMargin;break;}if (hasDividerBeforeChildAt(i)) {childTop += mDividerHeight;}childTop += lp.topMargin;// 最后调用setChildFrame函数来设置child的范围和坐标setChildFrame(child, childLeft, childTop + getLocationOffset(child),childWidth, childHeight);// 更新childTop,下个childTop为当期childTop + 当前child的高度 + 当前child的bottomMargin + 偏移量// 这样下一个child就会被放在当前child的下面,即实现了竖直方向的排列childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);i += getChildrenSkipCount(child, i);}}}// 调用child的layout进行布局,即设定child的坐标与范围private void setChildFrame(View child, int left, int top, int width, int height) { // 左边界为left, 上边界为top, 右边界是left + child的宽度,下边界是top + child的高度 child.layout(left, top, left + width, top + height);}
ViewGroup的绘制 ( draw )
/*** {@inheritDoc}*/@Overrideprotected void dispatchDraw(Canvas canvas) {final int count = mChildrenCount;final View[] children = mChildren;int flags = mGroupFlags;// 代码省略final long drawingTime = getDrawingTime();// 迭代所有子View, 并且绘制if ((flags & FLAG_USE_CHILD_DRAWING_ORDER) == 0) {for (int i = 0; i < count; i++) {final View child = children[i];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {// 绘制子Viewmore |= drawChild(canvas, child, drawingTime);}}} else {for (int i = 0; i < count; i++) {final View child = children[getChildDrawingOrder(count, i)];if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {// 绘制子Viewmore |= drawChild(canvas, child, drawingTime);}}}// 代码省略}}
其中drawChild就是绘制子view的函数了。
自定义View
public class MyTextView extends View {/*** 要绘制的文本内容*/private String mText = "";/*** 文字大小*/private int mTextSize = 60;/*** 画笔*/private Paint mPaint = new Paint();/*** 文本的区域,主要是宽度和高度*/Rect mTextRect = new Rect();/*** @param context*/public MyTextView(Context context) {this(context, null, 0);}public MyTextView(Context context, AttributeSet attrs) {this(context, attrs, 0);}public MyTextView(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);
// 获取属性集,即你在xml中设置的属性initAttrs(attrs);// 反锯齿mPaint.setAntiAlias(true);// 设置画笔的文字大小mPaint.setTextSize(mTextSize);}/*** @param text*/public void setText(String text) {mText = text;}/*** @param attrs*/private void initAttrs(AttributeSet attrs) {// 获取自定义的属性TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.MyTextView);// 获得要绘制的文本内容mText = typedArray.getString(R.styleable.MyTextView_text);// 设置字体的大小mTextSize = typedArray.getDimensionPixelSize(R.styleable.MyTextView_textSize, 60);//typedArray.recycle();}/** @see android.view.View#onDraw(android.graphics.Canvas)*/@Overrideprotected void onDraw(Canvas canvas) {int width = getWidth();int height = getHeight();// 获取要绘制文本的宽度和高度,存储在mTextRect中mPaint.getTextBounds(mText, 0, mText.length(), mTextRect);// 在view的中心绘制文本canvas.drawText(mText, width / 2 - mTextRect.width() / 2, height / 2 + mTextRect.height()/ 2, mPaint);}
}
自定义的MyTextView属性, res/values/attrs.xml中定义。
<?xml version="1.0" encoding="utf-8"?>
<resources><declare-styleable name="MyTextView"><attr name="text" format="string" /><attr name="textSize" format="dimension" /></declare-styleable></resources>
这个自定义属性的名字即为我们的自定义View的类名MyTextView, 这里只定义了两个属性,即text和textSize。在使用自定义View的属性时,我们需要引入该自定义View所在的包名,在xml中引入包名是这样的:
xmlns:别名="http://schemas.android.com/apk/res/这里写上你的自定义View的应用所在的工程的包名"
例如, android的包名是android,那么它的命名空间就是,
xmlns:android="http://schemas.android.com/apk/res/android"
呵,这个我们可见多了,每个布局用的xml中全都用这个。其实原理是这样的,res/values/attrs.xml中内容会被编译成R类,而R的完整路径就是工程的包名.R,注意,是工程的包名,而不是自定义View所在的包,因此你引入了工程的包名,Android系统就可以找到对应的R类,从而找到你的自定义属性,而你的自定义属性名又与你的自定义View类名一致,这样也就对应上了。
例如MyTextView所在的包为com.example.touch_event.viewsystem,但是我们的工程的包却是com.example.touch,因此R所在的路径就是com.example.touch.R,所以我在引入自定义属性的命名空间是需要引入的是com.example.touch,而不是com.example.touch_event.viewsystem。我们来试用一下吧:
main.xml :
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"xmlns:mytv="http://schemas.android.com/apk/res/com.example.touch"android:id="@+id/container"android:layout_width="match_parent"android:layout_height="match_parent"android:layout_gravity="center"tools:context="com.example.touch_event.MainActivity"tools:ignore="MergeRootFrame" ><com.example.touch_event.viewsystem.MyTextViewandroid:id="@+id/my_button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:background="#aa0000"mytv:text="这是我的TextView"mytv:textSize="30sp" /></FrameLayout>
我们引入了命名空间mytv, 用法为 xmlns:mytv="http://schemas.android.com/apk/res/com.example.touch"。并且需要注意,MyTextView的layout_width和layout_height我们都是设置为wrap_content的。然后使用mytv:text设置文本内容,使用mytv:textSize来设置文本大小。
运行以后效果如下图:
可以看到,我们的View正常运行起来了。但是我们设置的layout_width和layout_height都是设置为wrap_content的,怎么就变成了match_parent了呢?其实上文已经多次提到,View的默认onMeasure都只是支持match_parent,如果需要wrap_content,你需要覆写onMeasure来实现。那我们给MyTextView加上自己的onMeasure吧。
// 覆写onMeasure,最后必须调用setMeasuredDimension来设置该View的高度和高度@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {// 获得文本的宽度和高度mPaint.getTextBounds(mText, 0, mText.length(), mTextRect);// 默认支持的是match_parentsuper.onMeasure(widthMeasureSpec, heightMeasureSpec);// 获取由widthMeasureSpec提供的模式和宽度int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);// 获取由heightMeasureSpec提供的模式和高度int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);// 如果都是宽高设置为wrap_content,那么都使用文本的宽高if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode == MeasureSpec.AT_MOST) {setMeasuredDimension(mTextRect.width(), mTextRect.height());} else if (widthSpecMode == MeasureSpec.AT_MOST) {// 如果只是宽度是wrap_content,那么宽度使用文本的宽度,高度使用widthMeasureSpec中的高度setMeasuredDimension(mTextRect.width(), heightSpecSize);} else if (heightSpecMode == MeasureSpec.AT_MOST) {// 如果只是高度是wrap_content,那么高度使用文本的高度,宽度使用heightMeasureSpec中的宽度setMeasuredDimension(widthSpecSize, mTextRect.height());}}
最后运行看效果 :
效果不怎样,但总归是支持wrap_content了,细节就先不管了。
自定义ViewGroup
/*** @author mrsimple*/
public class VerticalLinearLayout extends ViewGroup {int mScreenWidth;int mScreenHeight;/*** @param context*/public VerticalLinearLayout(Context context) {this(context, null, 0);}/*** @param context* @param attrs*/public VerticalLinearLayout(Context context, AttributeSet attrs) {this(context, attrs, 0);}/*** @param context* @param attrs* @param defStyle*/public VerticalLinearLayout(Context context, AttributeSet attrs, int defStyle) {super(context, attrs, defStyle);mScreenWidth = context.getResources().getDisplayMetrics().widthPixels;mScreenHeight = context.getResources().getDisplayMetrics().heightPixels;}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {//super.onMeasure(widthMeasureSpec, heightMeasureSpec);//int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);int width = 0;int height = 0;int childCount = getChildCount();for (int i = 0; i < childCount; i++) {View child = getChildAt(i);if (child.getVisibility() != View.GONE) {// 丈量child的尺寸measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);// 这里的LayoutParams就是我们自定义的LayoutParamsLayoutParams layoutParams = (LayoutParams) child.getLayoutParams();if (widthSpecMode == MeasureSpec.AT_MOST) {width = Math.max(width, child.getMeasuredWidth() + layoutParams.leftMargin+ layoutParams.rightMargin);}//height += child.getMeasuredHeight() + layoutParams.topMargin+ layoutParams.bottomMargin;}}width += getPaddingLeft() + getPaddingRight();height += getPaddingBottom() + getPaddingTop();width = Math.min(width, mScreenWidth);height = Math.min(height, mScreenHeight);// 如果都是宽高设置为wrap_content,那么都使用文本的宽高if (widthSpecMode == MeasureSpec.AT_MOST && heightSpecMode ==MeasureSpec.AT_MOST) {setMeasuredDimension(width, height);} else if (widthSpecMode == MeasureSpec.AT_MOST) {// 如果只是宽度是wrap_content,那么宽度使用文本的宽度,高度使用widthMeasureSpec中的高度setMeasuredDimension(width, heightSpecSize);} else if (heightSpecMode == MeasureSpec.AT_MOST) {// 如果只是高度是wrap_content,那么高度使用文本的高度,宽度使用heightMeasureSpec中的宽度setMeasuredDimension(widthSpecSize, height);}Log.d(VIEW_LOG_TAG, "### vertical, width = " + getMeasuredWidth() + ", height = "+ getMeasuredHeight());}@Overrideprotected void onLayout(boolean changed, int l, int t, int r, int b) {//int left = getPaddingLeft();int top = getPaddingTop();//int childCount = getChildCount();//for (int i = 0; i < childCount; i++) {View childView = getChildAt(i);if (childView.getVisibility() != View.GONE) {// 对child进行布局childView.layout(left, top, left + childView.getMeasuredWidth(),top + childView.getMeasuredHeight());Log.d(VIEW_LOG_TAG, "### left = " + left + ", top = " + top + ", right = " + (left+ childView.getMeasuredWidth()));// 下一次的top为这次的top 加上 当前childView的高度, 因此下一个child会放到这个child的下面.top += childView.getMeasuredHeight();}}}/*** 注意覆写这几个方法,否则在进行measureChildWithMargins或者将child的LayoutParams进行转换时会出现转换异常*/@Overrideprotected android.view.ViewGroup.LayoutParams generateDefaultLayoutParams() {return new VerticalLinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,ViewGroup.LayoutParams.WRAP_CONTENT);}@Overridepublic android.view.ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) {return new VerticalLinearLayout.LayoutParams(getContext(), attrs);}@Overrideprotected android.view.ViewGroup.LayoutParams generateLayoutParams(android.view.ViewGroup.LayoutParams p) {return new VerticalLinearLayout.LayoutParams(p);}/*** 自定义LayoutParams,从而使你的Layout支持Margin等* * @author mrsimple*/public static class LayoutParams extends ViewGroup.MarginLayoutParams {public LayoutParams(Context c, AttributeSet attrs) {super(c, attrs);}public LayoutParams(int width, int height) {super(width, height);}/*** Copy constructor. Clones the width, height and margin values of the* source.** @param source The layout params to copy from.*/public LayoutParams(MarginLayoutParams source) {super(source);}public LayoutParams(ViewGroup.LayoutParams p) {super(p);}}}
main.xml的定义 :
<com.example.touch_event.viewsystem.VerticalLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"xmlns:mytv="http://schemas.android.com/apk/res/com.example.touch"android:id="@+id/container"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:background="#00dd00"><com.example.touch_event.viewsystem.MyTextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:background="#aa0000"mytv:text="这是我的TextView"mytv:textSize="30sp" /><com.example.touch_event.viewsystem.MyTextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:background="#aa0000"mytv:text="很简单的TextView"mytv:textSize="30sp" /><com.example.touch_event.viewsystem.MyTextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="center"android:background="#aa0000"mytv:text="自定义的TextView"mytv:textSize="30sp" /></com.example.touch_event.viewsystem.VerticalLinearLayout>
Android View系统分析之二View与ViewGroup相关推荐
- Android View系统分析之三Activity的启动与显示
前言 在Android View系统分析之从setContentView说开来(一)与Android View系统分析之二View与ViewGroup中我们已经简单介绍了一个Activity的UI内容 ...
- 【Android 应用开发】自定义View 和 ViewGroup
一. 自定义View介绍 自定义View时, 继承View基类, 并实现其中的一些方法. (1) ~ (2) 方法与构造相关 (3) ~ (5) 方法与组件大小位置相关 (6) ~ (9) 方法与触摸 ...
- android 动画多次使用,Android动画使用(二)——补间(View)动画
1.Android动画简介 1.1 分类 Android系统提供了很多丰富的API去实现UI的2D与3D动画,最主要的划分可以分为如下几类: Drawable Animation: 这种动画(也叫Fr ...
- Android 控件架构及View、ViewGroup的测量
附录:示例代码地址 控件在Android开发的过程中是必不可少的,无论是我们在使用系统控件还是自定义的控件.下面我们将讲解一下Android的控件架构,以及如何实现自定义控件. 1.Android控件 ...
- Android View系列(二):事件分发机制源码解析
概述 在介绍点击事件规则之前,我们需要知道我们分析的是MotionEvent,即点击事件,所谓的事件分发就是对MotionEvent事件的分发过程,即当一个MotionEvent生成以后,系统需要把这 ...
- Android绘图机制(二)——自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解
Android绘图机制(二)--自定义View绘制形, 圆形, 三角形, 扇形, 椭圆, 曲线,文字和图片的坐标讲解 我们要想画好一些炫酷的View,首先我们得知道怎么去画一些基础的图案,比如矩形,圆 ...
- [Android学习笔记二] View转化Bitmap
在View类中的onDraw方法的参数Canvas是View绘制的背景,要将View转换为Bitmap实际上就是让Canvas上的绘制操作绘制到Bitmap上. View转化为Bitmap也称为截屏, ...
- Android开发-自定义View-AndroidStudio(二十八)缩放的View
转载请注明出处: http://blog.csdn.net/iwanghang/ 觉得博文有用,请点赞,请评论,请关注,谢谢!~ 继续上一篇博文,移动的View,我们来一下自定义View如何缩放: 老 ...
- Android面试收集录12 View测量、布局及绘制原理
一.View绘制的流程框架 View的绘制是从上往下一层层迭代下来的.DecorView-->ViewGroup(--->ViewGroup)-->View ,按照这个流程从上往下, ...
最新文章
- matlab循环求 近似for,Matlab for 多个变量循环能不能这样啊 ,求教高手!!!!...
- Quartz.net官方开发指南 第二课:Jobs And Triggers
- mysql-atlas安装及使用教程
- mvc mysql linq_MVC3+Linq to sql 显示数据库中数据表的数据
- 下载的字幕php是什么格式的,mkv是什么文件格式
- 13.python中的字典
- python清理浏览器文件_URL可以在浏览器或wget中正常工作,但是从Python或cURL中清空...
- 计算机桌面锁屏设置,如何设置电脑锁屏壁纸详细方法
- 传真SIP会话发起协议(SIP)
- spring data jpa 动态查询Specification(包括各个In、like、Between等等各种工具类,及完整(分页查询)用法步骤(到返回给前端的结果))
- this的理解 转https://www.cnblogs.com/pssp/p/5216085.html#!comments
- 《炬丰科技-半导体工艺》 高频磷化铟芯片晶圆级背面加工
- Android 关于TextView 默认显示英文字母大写问题
- Shell脚本学习指南(三)——文本处理工具
- Xilinx UltraScale
- 破解背后的黑客,第1部分:如何绕过软件注册
- matlab电流可逆斩波电路,电流可逆斩波电路.PPT
- SQLite 表达式索引的概念和作用
- day05 【异常、线程】
- matlab (k),MATLAB的sgolay(k,f)的Python等价物是什么?