美文网首页Android资源收录
自定义ViewGroup-第二十一步:Measure与Layou

自定义ViewGroup-第二十一步:Measure与Layou

作者: crossroads | 来源:发表于2017-03-06 16:14 被阅读100次

    前言

    根据启舰大大 的博客所学习的自定义View。

    一、测量

    1. MeasureSpec

    MeasureSpec.getMode(int spec) //获取MODE  
    MeasureSpec.getSize(int spec) //获取数值
    
    • MODE:
    • MeasureSpec.AT_MOST(至多):子元素至多达到指定大小的值->wrap_content。
    • MeasureSpec.EXACTLY(完全):父元素决定自元素的确切大小,子元素将被限定在给定的边界里而忽略它本身大小->match_parent。
    • MeasureSpec.UNSPECIFIED(未指定):父元素不对子元素施加任何束缚,子元素可以得到任意想要的大小->具体值。

    2. onMeasure()

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)  
    

    一般用法举例:

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);  
        int measureWidth = MeasureSpec.getSize(widthMeasureSpec);  
        int measureHeight = MeasureSpec.getSize(heightMeasureSpec);  
        int measureWidthMode = MeasureSpec.getMode(widthMeasureSpec);  
        int measureHeightMode = MeasureSpec.getMode(heightMeasureSpec);  
          
        //经过计算,控件所占的宽和高分别对应width和height  
        …………  
          
        setMeasuredDimension((measureWidthMode == MeasureSpec.EXACTLY) ? measureWidth: width, (measureHeightMode == MeasureSpec.EXACTLY) ? measureHeight: height);  
    }  
    

    3. measureChild与measureChildren
    measureChild用来测量子View的宽高

    //
    protected void measureChild(View child, int parentWidthMeasureSpec,  
                int parentHeightMeasureSpec);
    

    measureChildren是用来测量所有子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(child, widthMeasureSpec, heightMeasureSpec);  
                }  
            }  
        } 
    

    二、onLayout():实现所有子控件布局的函数

     protected void onLayout(boolean changed, int left, int top, int right, int bottom) 
    

    自定义实现简易的纵向LinearLayout:

    public class MyLinear extends ViewGroup {
        public MyLinear(Context context) {
            this(context, null);
        }
    
        public MyLinear(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
            int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
            int widthMode = MeasureSpec.getMode(measureWidth);
            int heightMode = MeasureSpec.getMode(measureHeight);
            int height=0;
            int width=0;
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                measureChild(child,widthMeasureSpec,heightMeasureSpec);
                int childHeight=child.getMeasuredHeight();
                int childWidth=child.getMeasuredWidth();
                height+=childHeight;
                width=Math.max(width,childWidth);
            }
            setMeasuredDimension((widthMode==MeasureSpec.EXACTLY)?measureWidth:width,(heightMode==MeasureSpec.EXACTLY)?measureHeight:height);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            int childCount = getChildCount();
            int top = 0;
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                child.layout(l, top, l + child.getMeasuredWidth(), top + child.getMeasuredHeight());
                top += child.getMeasuredHeight();
            }
        }
    }
    
        <com.crossroads.demo.demo.MyLinear
            android:background="#00ff00"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            >
            <TextView
                android:background="@color/colorPrimary"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="自定义"
                />
            <TextView
                android:background="@color/colorPrimary"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="实现"
                />
            <TextView
                android:background="@color/colorPrimary"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="简易LinearLayout效果"
                />
        </com.crossroads.demo.demo.MyLinear>
    
    效果图

    getMeasuredWidth()与getWidth()区别

    • getMeasureWidth()方法在measure()过程结束后就可以获取到了,
      而getWidth()方法要在layout()过程结束后才能获取到
    • getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过layout(left,top,right,bottom)方法设置的。

    三、获取子控件Margin

    上边的例子中,发现设置margin无效,那么该怎么设置margin呢?
    只需要重写下边这三个方法就可以获取到Margin啦

       @Override
        protected LayoutParams generateLayoutParams(LayoutParams p) {
            return new MarginLayoutParams(p);
        }
    
        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new  MarginLayoutParams(getContext(),attrs);
        }
    
        @Override
        protected LayoutParams generateDefaultLayoutParams() {
            return new MarginLayoutParams(LayoutParams.MATCH_PARENT,LayoutParams.MATCH_PARENT);
        }
    

    然后,onLayout和onMeasure也需要将margin计算进去滴

      @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
            int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
            int widthMode = MeasureSpec.getMode(measureWidth);
            int heightMode = MeasureSpec.getMode(measureHeight);
            int height=0;
            int width=0;
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                measureChild(child,widthMeasureSpec,heightMeasureSpec);
                MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
    
                int childHeight=child.getMeasuredHeight()+layoutParams.bottomMargin+layoutParams.topMargin;
                int childWidth=child.getMeasuredWidth()+layoutParams.leftMargin+layoutParams.rightMargin;
                height+=childHeight;
                width=Math.max(width,childWidth);
            }
            setMeasuredDimension((widthMode==MeasureSpec.EXACTLY)?measureWidth:width,(heightMode==MeasureSpec.EXACTLY)?measureHeight:height);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            int childCount = getChildCount();
            int top = 0;
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
                child.layout(l+layoutParams.leftMargin, top+layoutParams.topMargin, l + child.getMeasuredWidth()+layoutParams.leftMargin, top + child.getMeasuredHeight()+layoutParams.topMargin);
                top += child.getMeasuredHeight()+layoutParams.topMargin+layoutParams.bottomMargin;
            }
        }
    
     <com.crossroads.demo.demo.MyLinear
            android:background="#00ff00"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            >
            <TextView
                android:layout_marginTop="5dp"
                android:background="@color/colorPrimary"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="自定义"
                />
            <TextView
                android:layout_margin="10dp"
                android:background="@color/colorPrimary"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="实现"
                />
            <TextView
                android:layout_marginBottom="10dp"
                android:background="@color/colorPrimary"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="简易LinearLayout效果"
                />
        </com.crossroads.demo.demo.MyLinear>
    
    flow

    四、接下来完成这个效果

    自适应

    代码如下:

    <?xml version="1.0" encoding="utf-8"?>
    <shape
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:shape="rectangle">
        <corners android:radius="180dp"/>
        <padding
            android:bottom="3dp"
            android:left="5dp"
            android:right="5dp"
            android:top="3dp"/>
        <solid android:color="#fff"/>
    </shape>
    
    public class FlowLayout extends ViewGroup {
        public FlowLayout(Context context) {
            super(context);
        }
    
        public FlowLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
            int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
            int heightMode = MeasureSpec.getMode(measureHeight);
            int height = 0;//viewGroup高度
            int lineWidth = 0;//累加当前行的行宽
            int lineHeight = 0;//当前行的行高
            int childCount = getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                measureChild(child, widthMeasureSpec, heightMeasureSpec);
                MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
                int childWidthWithMargin = child.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
                int childHeightWithMargin = child.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
                if (lineWidth + childWidthWithMargin > measureWidth) {
                    //换行
                    lineWidth = childWidthWithMargin;
                    height += lineHeight;
                    lineHeight = childHeightWithMargin;
                }
                else {
                    //不换行
                    lineHeight = Math.max(childHeightWithMargin, lineHeight);
                    lineWidth += childWidthWithMargin;
                }
                //最后一行还没有计算呢
                if (i == childCount - 1) {
                    height += lineHeight;
                }
            }
            setMeasuredDimension(measureWidth, (heightMode == MeasureSpec.EXACTLY) ? measureHeight : height);
        }
    
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            int childCount = getChildCount();
            int top = 0;
            int lineWidth = 0, lineHeight = 0;
            for (int i = 0; i < childCount; i++) {
                View child = getChildAt(i);
                MarginLayoutParams layoutParams = (MarginLayoutParams) child.getLayoutParams();
                int childWidthWithMargin = child.getMeasuredWidth() + layoutParams.leftMargin + layoutParams.rightMargin;
                int childHeightWithMargin = child.getMeasuredHeight() + layoutParams.topMargin + layoutParams.bottomMargin;
                if (lineWidth + childWidthWithMargin > getMeasuredWidth()) {
                    //换行
                    top += lineHeight;
                    child.layout(layoutParams.leftMargin, top + layoutParams.topMargin, layoutParams.leftMargin + child.getMeasuredWidth(), top + layoutParams.topMargin + child.getMeasuredHeight());
                    lineWidth = childWidthWithMargin;
                    lineHeight = childHeightWithMargin;
                }
                else {
                    //不换行
                    child.layout(lineWidth + layoutParams.leftMargin, top + layoutParams.topMargin, lineWidth + layoutParams.leftMargin + child.getMeasuredWidth(), top + layoutParams.topMargin + child.getMeasuredHeight());
                    lineHeight = Math.max(childHeightWithMargin, lineHeight);
                    lineWidth += childWidthWithMargin;
                }
            }
        }
    
        @Override
        protected LayoutParams generateLayoutParams(LayoutParams p) {
            return new MarginLayoutParams(p);
        }
    
        @Override
        public LayoutParams generateLayoutParams(AttributeSet attrs) {
            return new MarginLayoutParams(getContext(), attrs);
        }
    
        @Override
        protected LayoutParams generateDefaultLayoutParams() {
            return new MarginLayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
        }
    }
    
      <com.crossroads.demo.demo.FlowLayout
            android:id="@+id/flowLayout"
            android:background="#00ff00"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            />
    
      final FlowLayout flowLayout = (FlowLayout) findViewById(R.id.flowLayout);
            flowLayout.removeAllViews();
            for (int i = 0; i < 10; i++) {
                final TextView child = new TextView(this);
                if (i % 7 == 0 || i % 7 == 1)
                    child.setText("两只老虎" + i);
                else if (i % 7 == 2 || i % 7 == 3)
                    child.setText("跑得快" + i);
                else if (i % 7 == 4 )
                    child.setText("一只没有尾巴,一只没有眼睛" + i);
                else
                    child.setText("真奇怪" + i);
                child.setTextSize(20);
                child.setBackgroundResource(R.drawable.roundrect);
                LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(
                        LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT);
                params.setMargins(3, 4, 5, 7);
                child.setLayoutParams(params);
                child.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        flowLayout.removeView(child);
                    }
                });
                flowLayout.addView(child);
            }
        }
    

    参考网址

    自定义View中的onMeasure,measureChild与measureChildren

    相关文章

      网友评论

        本文标题:自定义ViewGroup-第二十一步:Measure与Layou

        本文链接:https://www.haomeiwen.com/subject/vlwygttx.html