首页 > 代码库 > 【自定义控件】自定义ViewGroup 在ViewGroup中显示TextView

【自定义控件】自定义ViewGroup 在ViewGroup中显示TextView

需求:在ViewGroup中显示一个TextView

1、继承ViewGroup

必须要实现其构造方法和一个onLayout方法

构造函数的处理

public CusViewGroup(Context context) {
    this(context, null);
}

public CusViewGroup(Context context, AttributeSet attrs) {
    this(context, attrs, 0);
}

public CusViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr); 
}

注意构造函数中的this。自定义ViewGroup也能够有自己的属性,对于属性的操作和自定义View一致。(在Style中添加自定义属性,在构造函数中获取到layout中设置的自定义属性的值)

onLayout():
疑问:为什么必须要重写onLayout方法?
自定义ViewGroup 相当于是一个容器,里面能够放置很多View,这些View的位置由onLayout来确定。必须指定位置,才能显示到容器的对应位置上。

疑问:onLayout的几个参数是什么意思
l、t、r、b 是自定义ViewGroup父控件中设置的Padding 值。

2、自定义ViewGroup的执行过程

构造函数—–获取自定义属性
onMeasure—控件宽、高以及measure子控件
onLayout —–控件的位置,子View的位置
onDraw —— 画画咯

onMeasure
自定义ViewGroup onMeasure代码

/**
 * 确定ViewGroup的宽高
 *
 * @param widthMeasureSpec  宽参数
 * @param heightMeasureSpec 高参数
 */
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //ViewGroup主要是一个容器,当ViewGroup的宽高是确切的值的时候,控件的宽高就是它本身设置的值
    //主要是考虑ViewGroup Wrap_content的时,需要计算控件的宽高,控件的宽高根据子View的布局来计算
    int width;
    int height;
    int mWidthMeasureMode = MeasureSpec.getMode(widthMeasureSpec);
    measureChildren(widthMeasureSpec, heightMeasureSpec);//初始化所有子View的宽高

    if (mWidthMeasureMode == MeasureSpec.AT_MOST) {//Wrap_content的情况
        //测量子View的宽  怎么测量子View的宽
        //由于这里只有一个控件,暂时从这个一个控件开始学习
        View childView = getChildAt(0);//获取到这个控件
        width = childView.getMeasuredWidth();
    } else {
        width = MeasureSpec.getSize(widthMeasureSpec);
    }

    int mHeightMeasureMode = MeasureSpec.getMode(heightMeasureSpec);
    if (mHeightMeasureMode == MeasureSpec.AT_MOST) {
        View childView = getChildAt(0);
        height = childView.getMeasuredHeight();

    } else {
        height = MeasureSpec.getSize(heightMeasureSpec);
    }
    setMeasuredDimension(width, height);
}

测试布局文件:

<com.tjstudy.cusviewgroupdemo.customerview.CusViewGroup
    android:layout_width="wrap_content"
    android:layout_height="100dp"
    android:background="#ccc">

    <TextView
        android:layout_width="100dp"
        android:layout_height="50dp"
        android:background="#FF4444"
        android:gravity="center"
        android:text="0"
        android:textColor="#FFFFFF"
        android:textStyle="bold" />

</com.tjstudy.cusviewgroupdemo.customerview.CusViewGroup>

这里只设置了自定义ViewGroup 的宽度wrap_content
效果

技术分享

宽度此时就为TextView的宽度 高度还是自定义控件的高度
这个时候,你发现TextView根本没有显示到界面上,进入下一步

onLayout
疑问:onLayout():控件摆放到什么位置?
—onLayout的几个参数是父控件的padding值

疑问:是否能够改变padding值?
—不能,子控件怎么能够设置父控件的属性呢!!

疑问:怎么将TextView显示到界面上?

/**
 * 必须实现的方法,自定义ViewGroup的child位置布局
 *
 * @param changed
 * @param l       ViewGroup父类 paddingLeft
 * @param t       paddingTop
 * @param r       paddingRight
 * @param b       paddingBottom
 */
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    Log.e(TAG,"paddingLeft="+l+";paddingTop="+t+";paddingRight="+r+";paddingBottom="+b);
    //是否能对自定义ViewGroup再进行位置的变换?不能!!!!!
    //将TextView显示出来
    View childView = getChildAt(0);
    childView.layout(0,0,childView.getMeasuredWidth(),childView.getMeasuredHeight());
}

显示结果:

技术分享

到此就基本完成了ViewGroup的简单示例

Demo下载
http://download.csdn.net/detail/u012391876/9677845


练习:在ViewGroup中显示2个TextView 并实现指定布局

效果图:

技术分享

自定义ViewGroup代码:

ViewGroup:
package com.tjstudy.cusviewgroupdemo.customerview;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;

/**
 * 自定义ViewGroup 按效果显示两个TextView
 */
public class CusViewGroup extends ViewGroup {
    public static final String TAG = "CusViewGroup";
    private int childCount;

    public CusViewGroup(Context context) {
        this(context, null);
    }

    public CusViewGroup(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public CusViewGroup(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    /**
     * 必须实现的方法,自定义ViewGroup的child位置布局
     *
     * @param changed
     * @param l       ViewGroup父类 paddingLeft
     * @param t       paddingTop
     * @param r       paddingRight
     * @param b       paddingBottom
     */
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        int cl = 0;
        int ct = 0;
        int cr = 0;
        int cb = 0;

        int h = 0;//由于两个控件垂直放置,需要记录高度
        //为每一个子View指定位置  所需效果:查看mipmap里面的效果图
        for(int i=0;i<childCount;i++){
            View childView = getChildAt(i);
            if(i==0){//第一个放到上面的中间
                cl = (getMeasuredWidth()-childView.getMeasuredWidth())/2;
                ct = 0;
                cr = cl+childView.getMeasuredWidth();
                cb = ct+childView.getMeasuredHeight();
                h = ct+childView.getMeasuredHeight();
            }
            if(i==1){
                cl=0;
                ct=h;
                cr=cl+childView.getMeasuredWidth();
                cb=cb+childView.getMeasuredHeight();
            }
            childView.layout(cl,ct,cr,cb);
        }
    }

    /**
     * 确定ViewGroup的宽高
     *
     * @param widthMeasureSpec  宽参数
     * @param heightMeasureSpec 高参数
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //ViewGroup主要是一个容器,当ViewGroup的宽高是确切的值的时候,控件的宽高就是它本身设置的值
        //主要是考虑ViewGroup Wrap_content的时候,此时就需要计算控件的宽高了,控件的宽高就是子View的总的宽高
        int width = 0;
        int height = 0;
        int mWidthMeasureMode = MeasureSpec.getMode(widthMeasureSpec);
        measureChildren(widthMeasureSpec, heightMeasureSpec);//初始化所有子View的宽高
        childCount = getChildCount();

        if (mWidthMeasureMode == MeasureSpec.AT_MOST) {//Wrap_content的情况
            //测量子View的宽  怎么测量子View的宽
            //由于是垂直放置  这里需要最大的那个宽度
            int maxWidth = 0;
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                width = childView.getMeasuredWidth();
                maxWidth = (width>maxWidth)?width:maxWidth;
            }
            width = maxWidth;
        } else {
            width = MeasureSpec.getSize(widthMeasureSpec);
        }

        int mHeightMeasureMode = MeasureSpec.getMode(heightMeasureSpec);
        if (mHeightMeasureMode == MeasureSpec.AT_MOST) {
            for (int i = 0; i < childCount; i++) {
                View childView = getChildAt(i);
                height += childView.getMeasuredHeight();
            }
        } else {
            height = MeasureSpec.getSize(heightMeasureSpec);
        }
        setMeasuredDimension(width, height);
    }
}

layout:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.tjstudy.cusviewgroupdemo.MainActivity">

    <com.tjstudy.cusviewgroupdemo.customerview.CusViewGroup
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#ccc">

        <TextView
            android:layout_width="50dp"
            android:layout_height="50dp"
            android:background="#ED1C24"
            android:gravity="center"
            android:text="0"
            android:textColor="#FFFFFF"
            android:textStyle="bold" />
        <TextView
            android:layout_width="100dp"
            android:layout_height="50dp"
            android:background="#00A2E8"
            android:gravity="center"
            android:text="1"
            android:textColor="#FFFFFF"
            android:textStyle="bold" />

    </com.tjstudy.cusviewgroupdemo.customerview.CusViewGroup>

</LinearLayout>

运行结果:

技术分享

疑问:对于自定义ViewGroup ,应该还会有margin、padding等的设置,这些设置是否会对ViewGroup或者子控件的位置有影响?
原代码+打印信息打印结果:

技术分享

设置margin+padding

android:padding="10dp"
android:layout_margin="60dp"

打印信息:

技术分享

效果图:

技术分享

效果有变化,但是在自定义ViewGroup中打印的log信息确是相同的

理解:在自定义ViewGroup中,onLayout中默认情况下已经计算了padding的值,margin是自定义控件本身到自定义控件父控件的位置,不会对子View有影响。

Demo下载
http://download.csdn.net/detail/u012391876/9677964

<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    【自定义控件】自定义ViewGroup 在ViewGroup中显示TextView