美文网首页
笔记51 | Android自定义View(二)

笔记51 | Android自定义View(二)

作者: 项勇 | 来源:发表于2017-12-14 16:26 被阅读5次

    地址

    http://blog.csdn.net/xiangyong_1521/article/details/78804104
    http://www.jianshu.com/p/c84693096e41


    自定义ViewGroup

    自定义View的过程很简单,就那几步,可自定义ViewGroup可就没那么简单啦~,因为它不仅要管好自己的,还要兼顾它的子View。我们都知道ViewGroup是个View容器,它装纳child View并且负责把child View放入指定的位置。我们假象一下,如果是让你负责设计ViewGroup,你会怎么去设计呢?

    1. 首先,我们得知道各个子View的大小吧,只有先知道子View的大小,我们才知道当前的ViewGroup该设置为多大去容纳它们。

    2. 根据子View的大小,以及我们的ViewGroup要实现的功能,决定出ViewGroup的大小

    3. ViewGroup和子View的大小算出来了之后,接下来就是去摆放了吧,具体怎么去摆放呢?这得根据你定制的需求去摆放了,比如,你想让子View按照垂直顺序一个挨着一个放,或者是按照先后顺序一个叠一个去放,这是你自己决定的。

    4. 已经知道怎么去摆放还不行啊,决定了怎么摆放就是相当于把已有的空间"分割"成大大小小的空间,每个空间对应一个子View,我们接下来就是把子View对号入座了,把它们放进它们该放的地方去。

    现在就完成了ViewGroup的设计了,我们来个具体的案例:将子View按从上到下垂直顺序一个挨着一个摆放,即模仿实现LinearLayout的垂直布局。

    首先重写onMeasure,实现测量子View大小以及设定ViewGroup的大小:

    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //将所有的子View进行测量,这会触发每个子View的onMeasure函数
            //注意要与measureChild区分,measureChild是对单个view进行测量
            measureChildren(widthMeasureSpec, heightMeasureSpec);
    
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
    
            int childCount = getChildCount();
    
            if (childCount == 0) {//如果没有子View,当前ViewGroup没有存在的意义,不用占用空间
                setMeasuredDimension(0, 0);
            } else {
                //如果宽高都是包裹内容
                if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
                    //我们将高度设置为所有子View的高度相加,宽度设为子View中最大的宽度
                    int height = getTotleHeight();
                    int width = getMaxChildWidth();
                    setMeasuredDimension(width, height);
    
                } else if (heightMode == MeasureSpec.AT_MOST) {//如果只有高度是包裹内容
                    //宽度设置为ViewGroup自己的测量宽度,高度设置为所有子View的高度总和
                    setMeasuredDimension(widthSize, getTotleHeight());
                } else if (widthMode == MeasureSpec.AT_MOST) {//如果只有宽度是包裹内容
                    //宽度设置为子View中宽度最大的值,高度设置为ViewGroup自己的测量值
                    setMeasuredDimension(getMaxChildWidth(), heightSize);
    
                }
            }
        }
        /***
         * 获取子View中宽度最大的值
         */
        private int getMaxChildWidth() {
            int childCount = getChildCount();
            int maxWidth = 0;
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                if (childView.getMeasuredWidth() > maxWidth)
                    maxWidth = childView.getMeasuredWidth();
    
            }
    
            return maxWidth;
        }
    
        /***
         * 将所有子View的高度相加
         **/
        private int getTotleHeight() {
            int childCount = getChildCount();
            int height = 0;
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                height += childView.getMeasuredHeight();
    
            }
    
            return height;
        }
    
    

    代码中的注释我已经写得很详细,不再对每一行代码进行讲解。上面的onMeasure将子View测量好了,以及把自己的尺寸也设置好了,接下来我们去摆放子View吧~

     @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            int count = getChildCount();
            //记录当前的高度位置
            int curHeight = t;
            //将子View逐个摆放
            for (int i = 0; i < count; i++) {
                View child = getChildAt(i);
                int height = child.getMeasuredHeight();
                int width = child.getMeasuredWidth();
                //摆放子View,参数分别是子View矩形区域的左、上、右、下边
                child.layout(l, curHeight, l + width, curHeight + height);
                curHeight += height;
            }
        }
    
    

    我们测试一下,将我们自定义的ViewGroup里面放3个Button ,将这3个Button的宽度设置不一样,把我们的ViewGroup的宽高都设置为包裹内容wrap_content,为了看的效果明显,我们给ViewGroup加个背景:

    <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context="com.evan.view.MainActivity"
        tools:ignore="MergeRootFrame" >
    
        <com.evan.view.myView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@android:color/holo_blue_light" >
    
            <Button
                android:layout_width="100dp"
                android:layout_height="wrap_content"
                android:text="btn" />
    
            <Button
                android:layout_width="200dp"
                android:layout_height="wrap_content"
                android:text="btn" />
    
            <Button
                android:layout_width="50dp"
                android:layout_height="wrap_content"
                android:text="btn" />
    
            <Button
                android:layout_width="150dp"
                android:layout_height="wrap_content"
                android:text="btn" />
    
            <Button
                android:layout_width="250dp"
                android:layout_height="wrap_content"
                android:text="btn" />
        </com.evan.view.myView>
    
    </FrameLayout>
    
    

    看看最后的效果吧~


    device-2017-12-14-161459.png

    是不是很激动我们自己也可以实现LinearLayout的效果啦~~~

    最后附上MyViewGroup的完整源码:

    package com.evan.view;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.util.AttributeSet;
    import android.view.View;
    import android.view.ViewGroup;
    
    public class myView extends ViewGroup{
    
        private int defalutSize;
        public myView(Context context, AttributeSet attrs) {
            super(context, attrs);
            //第二个参数就是我们在styles.xml文件中的<declare-styleable>标签
            //即属性集合的标签,在R文件中名称为R.styleable+name
            TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MyView);
    
            //第一个参数为属性集合里面的属性,R文件名称:R.styleable+属性集合名称+下划线+属性名称
            //第二个参数为,如果没有设置这个属性,则设置的默认的值
            defalutSize = a.getDimensionPixelSize(R.styleable.MyView_default_size, 100);
    
            //最后记得将TypedArray对象回收
            a.recycle();
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            // TODO Auto-generated method stub
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            measureChildren(widthMeasureSpec, heightMeasureSpec);
            
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int widthSize = MeasureSpec.getSize(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            int heightSize = MeasureSpec.getSize(heightMeasureSpec);
            
            int childCount = getChildCount();
            if (childCount == 0) {//如果没有子View,当前ViewGroup没有存在的意义,不用占用空间
                setMeasuredDimension(0, 0);
            }else{
                //如果宽高都是包裹内容
                if (widthMode == MeasureSpec.AT_MOST && heightMode == MeasureSpec.AT_MOST) {
                    int height = getTotleHeight();
                    int width = getMaxChildWidth();
                    setMeasuredDimension(width, height);
                }else if (heightMode == MeasureSpec.AT_MOST) {
                    //宽度设置为ViewGroup自己的测量宽度,高度设置为所有子View的高度总和
                    setMeasuredDimension(widthSize, getTotleHeight());
                }else if (widthMode == MeasureSpec.AT_MOST) {
                    //宽度设置为子View中宽度最大的值,高度设置为ViewGroup自己的测量值
                    setMeasuredDimension(getMaxChildWidth(), heightSize);
                }
            }
        }
        
        /*
         * (non-Javadoc)
         * 获取子View中宽度的最大值
         */
        private int getMaxChildWidth(){
            int childCount = getChildCount();
            int maxWidth = 0;
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                if (childView.getMeasuredWidth() > maxWidth) {
                    maxWidth = childView.getMeasuredWidth()+5;
                }
            }
            return maxWidth;
        }
        
        /*
         * (non-Javadoc)
         * 将所有子View的高度相加
         * 
         */
        private int getTotleHeight(){
            int childCount = getChildCount();
            int height = 0;
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                height+=(childView.getMeasuredHeight());
            }
            return height;
        }
        
        /*
         * (non-Javadoc)
         * 摆放
         */
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            int count = getChildCount();
            //记录当前的高度位置
            int curHeight = t;
            //将子View逐个摆放
            for (int i = 0; i < count; i++) {
                View child = getChildAt(i);
                int height = child.getMeasuredHeight();
                int width = child.getMeasuredWidth();
                //摆放子View,参数分别是子View矩形区域的左、上、右、下边
                child.layout(l, curHeight, l+width, curHeight+height);
                curHeight += height;
            }
        }
    }
    
    

    好啦~自定义View的学习到此结束,是不是发现自定义View如此简单呢?


    我的Android征途

    相关文章

      网友评论

          本文标题:笔记51 | Android自定义View(二)

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