Android View measure (五) 支持margin属性,从一个异常说起

先来看下代码

一、查看夏目

1. 自定义控件

public class CustomViewGroup extends ViewGroup {

	......

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);

		// 遍历所有子视图,进行measure操作
        for (int i = 0; i < getChildCount(); i++) {
            View child = getChildAt(i);
            if (child != null && child.getVisibility() != View.GONE) {
                measureChild(child, widthMeasureSpec, heightMeasureSpec);

				// 支持子视图设置的android:layout_margin属性
                MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
                int marginLeft = layoutParams.leftMargin;
            }
        }

        setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        for (int i = 0; i < getChildCount(); i++) {
            View childView = getChildAt(i);
			// 只为最简单代码复现BUG,所有子视图都随便放
            childView.layout(left, top, left + childView.getMeasuredWidth(), top + childView.getMeasuredHeight());
        }
    }
}

继承自ViewGroup,主要是演示自定义视图中如何支持margin属性,重点在child.getLayoutParams()一行,接下来看下布局文件中如何使用

2. 布局文件

        <com.example.android.apis.CustomViewGroup
            android:id="@+id/custom_view_group"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" >

            <TextView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
				android:layout_margin="10dip"
                android:text="love_world_" />
        </com.example.android.apis.CustomViewGroup>

重头戏:android:layout_margin="10dip" margin属性

3. 异常

java.lang.ClassCastException: android.view.ViewGroup$LayoutParams cannot be cast to android.view.ViewGroup$MarginLayoutParams
	at com.example.android.apis.CustomViewGroup.onMeasure(CustomViewGroup.java:50)
	at android.view.View.measure(View.java:16831)
	at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5245)
	at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1410)
	at android.widget.LinearLayout.measureHorizontal(LinearLayout.java:1052)
	at android.widget.LinearLayout.onMeasure(LinearLayout.java:590)
	at android.view.View.measure(View.java:16831)
	at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5245)
	at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1410)
	at android.widget.LinearLayout.measureVertical(LinearLayout.java:695)
	at android.widget.LinearLayout.onMeasure(LinearLayout.java:588)
	at android.view.View.measure(View.java:16831)
	at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5245)
	at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
	at android.view.View.measure(View.java:16831)
	at android.widget.LinearLayout.measureVertical(LinearLayout.java:847)
	at android.widget.LinearLayout.onMeasure(LinearLayout.java:588)
	at android.view.View.measure(View.java:16831)
	at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:5245)
	at android.widget.FrameLayout.onMeasure(FrameLayout.java:310)
	at com.android.internal.policy.impl.PhoneWindow$DecorView.onMeasure(PhoneWindow.java:2586)
	at android.view.View.measure(View.java:16831)
	at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:2189)
	at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1352)
	at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1535)
	at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1249)
	at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6364)
	at android.view.Choreographer$CallbackRecord.run(Choreographer.java:791)
	at android.view.Choreographer.doCallbacks(Choreographer.java:591)
	at android.view.Choreographer.doFrame(Choreographer.java:561)
	at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:777)
	at android.os.Handler.handleCallback(Handler.java:730)
	at android.os.Handler.dispatchMessage(Handler.java:92)
	at android.os.Looper.loop(Looper.java:176)
	at android.app.ActivityThread.main(ActivityThread.java:5419)
	at java.lang.reflect.Method.invokeNative(Native Method)
	at java.lang.reflect.Method.invoke(Method.java:525)
	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1209)
	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1025)
	at dalvik.system.NativeStart.main(Native Method)

出现以上异常的原因,LayoutParams从哪里来的?

视图的加载有两种方式一种是代码addView 一种是inflate 。

1. inflate 方法加载并添加LayoutParams

Android中measure过程、WRAP_CONTENT详解以及xml布局文件解析流程浅析(上)

2. 以下演示addView方式添加LayoutParams

public class MainActivity extends Activity {

    @Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		CustomViewGroup customViewGroup = (CustomViewGroup) findViewById(R.id.custom_view_group);

		customViewGroup.addView(createTextView("Love_world_"));

	}

    private View createTextView(String value) {
        TextView textView = new TextView(this);
        textView.setText("a child view");
        textView.setText(value);
        textView.setLayoutParams(new ViewGroup.MarginLayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
        return textView;
    }

}	

2. ViewGroup.addView源码,查找何处添加LayoutParams

public abstract class ViewGroup extends View implements ViewParent, ViewManager {

    public void addView(View child) {
        addView(child, -1);
    }

    public void addView(View child, int index) {
        LayoutParams params = child.getLayoutParams();
		// 子视图LayoutParams为为空是处理方式
        if (params == null) {
            params = generateDefaultLayoutParams();
            if (params == null) {
                throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
            }
        }
        addView(child, index, params);
    }

    public void addView(View child, int width, int height) {
        final LayoutParams params = generateDefaultLayoutParams();
        params.width = width;
        params.height = height;
        addView(child, -1, params);
    }

    public void addView(View child, LayoutParams params) {
        addView(child, -1, params);
    }

    public void addView(View child, int index, LayoutParams params) {
        if (DBG) {
            System.out.println(this + " addView");
        }

        // addViewInner() will call child.requestLayout() when setting the new LayoutParams
        // therefore, we call requestLayout() on ourselves before, so that the child's request
        // will be blocked at our level
        requestLayout();
        invalidate();
        addViewInner(child, index, params, false);
    }

	// 子视图默认LayoutParams实例
	protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

	// 定义LayoutParams类
	public static class LayoutParams {
		public int width;
		public int height;

		public LayoutParams(int width, int height) {
            this.width = width;
            this.height = height;
        }
	}

}

以下是关键

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {

        if (child.getParent() != null) {
            throw new IllegalStateException("The specified child already has a parent. " +
                    "You must call removeView() on the child's parent first.");
        }

        if (!checkLayoutParams(params)) {
            params = generateLayoutParams(params);
        }

        if (preventRequestLayout) {
            child.mLayoutParams = params;
        } else {
            child.setLayoutParams(params);
        }

        ......
    }

	protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
        return  p != null;
    }
}

通过查看以上addView添加LayoutParams代码可以发现解决方案,复写这些函数,创建当前自定义视图的LayoutParams继承自MarginLayoutParams。

public class CustomViewGroup extends ViewGroup {

	@Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		......
	}

	@Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
		......
	}

    @Override
    protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
        return new LayoutParams(p);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    }    

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }    

    // 继承自margin,支持子视图android:layout_margin属性
    public static class LayoutParams extends MarginLayoutParams {

        public LayoutParams(Context c, AttributeSet attrs) {
            super(c, attrs);
        }

        public LayoutParams(int width, int height) {
            super(width, height);
        }

        public LayoutParams(ViewGroup.LayoutParams source) {
            super(source);
        }

        public LayoutParams(ViewGroup.MarginLayoutParams source) {
            super(source);
        }
    }
}	
时间: 2024-10-04 16:49:28

Android View measure (五) 支持margin属性,从一个异常说起的相关文章

Android View measure (二) 自定义UI控件measure相关

本篇模拟三个角色:Android 架构师-小福.Android  控件开发工程师-小黑. Android 开发工程师-小白,下面按照三个角色不同角度分析measure过程. 小福负责分享: measure的本质 measure代码流程 onMeasure方法与MeasureSpec 提出问题 小黑负责分享: 布局控件开发中覆写Measure例子 - ok 从遇到的一个异常说起 什么时候需要覆写onMeaure? - ok view.getWidth与view.getMeasureWidth区别

Android View measure流程详解

Android View measure流程详解 Android中View绘制的流程包括:measure(测量)->layout(布局)->draw(绘制). 因为Android中每个View都占据了一块矩形的空间,当我们要在屏幕上显示这个矩形的View的时候 首先我们需要知道这个矩形的大小(宽和高)这就对应了View的measure流程. 有了View的宽和高,我们还需要知道View左上角的起点在哪里,右下角的终点在哪里,这就对应了View的layout流程. 当矩形的区域在屏幕上确定之后,

Android View measure (三) 常用方法

ViewGroup.measureChildren() ViewGroup.measureChild() ViewGroup.measureChildWithMargins() /** * 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

Android View measure (一) 流程分析

本篇模拟三个角色:Android 架构师-小福.Android  控件开发工程师-小黑. Android 开发工程师-小白,下面按照三个角色不同角度分析measure过程. 小福负责分享: measure的本质 - ok measure代码流程 - 分析FrameLayout.onMeasure onMeasure方法与MeasureSpec - ok 提出问题 Android 架构师-小福的分享 一.Measure本质 小福:我今天分享是的measure架构设计相关的,先问一个问题,measu

Android View measure (三) 经常用法

ViewGroup.measureChildren() ViewGroup.measureChild() ViewGroup.measureChildWithMargins() /** * 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

Android View体系(七)从源码解析View的measure流程

相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源码解析Scroller Android View体系(五)从源码解析View的事件分发机制 Android View体系(六)从源码解析Activity的构成 前言 在上一篇我们了解了Activity的构成后,开始了解一下View的工作流程,就是measure.layout和draw.measure

Android View体系(五)从源码解析View的事件分发机制

相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源码解析Scroller 前言 三年前写过事件分发机制的文章但是写的不是很好,所以重新再写一篇,关于事件分发机制的文章已经有很多,但是希望我这篇是最简洁.最易懂的一篇. 1.处理点击事件的方法 View的层级 我们知道View的结构是树形的结构,View可以放在ViewGroup中,这个ViewGro

Android View体系(十一)自定义ViewGroup

相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源码解析Scroller Android View体系(五)从源码解析View的事件分发机制 Android View体系(六)从源码解析Activity的构成 Android View体系(七)从源码解析View的measure流程 Android View体系(八)从源码解析View的layout

Android View体系(八)从源码解析View的layout和draw流程

相关文章 Android View体系(一)视图坐标系 Android View体系(二)实现View滑动的六种方法 Android View体系(三)属性动画 Android View体系(四)从源码解析Scroller Android View体系(五)从源码解析View的事件分发机制 Android View体系(六)从源码解析Activity的构成 Android View体系(七)从源码解析View的measure流程 前言 上一篇文章我们讲了View的measure的流程,接下来我们