自定义ViewGroup 从零单排(一)

本文详细解析了ViewGroup的工作原理,包括其职责、绘制过程及测量模式等,并提供了两个具体的自定义ViewGroup实例,帮助读者深入理解。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、概述

1.ViewGroup的职责是什么?

(1)放置View的容器

(2)给childView计算出建议的宽和高和测量模式(为什么只是建议的宽和高,而不是直接确定呢,别忘了childView宽和高可以设置为wrap_content,这样只有childView才能计算出自己的宽和高

(3)决定childView摆放的位置(通过调用子View的layout(l,t,r,b)方法,让孩子view在此区域绘制自身)

(4)计算出自身的宽和高

2.View职责是什么?

(1)根据自己的测量模式和ViewGroup建议的宽和高,计算出自己的宽和高

(2)在ViewGroup为其指定的区域内绘制自己的形态

二、ViewGroup大体绘制过程

ViewGroup绘制包括两个步骤:1.measure 2.layout

在两个步骤中分别调用回调函数:1.onMeasure()   2.onLayout() 


1.onMeasure() 在这个函数中,ViewGroup会接受childView的请求的大小,

然后通过childView的measure(newWidthMeasureSpec, heightMeasureSpec)函数存储具体宽高到childView中,

以便childView的getMeasuredWidth() 和getMeasuredHeight() 的值可以被后续工作得到。 


2.在ViewGroup 的onMeasure的最后要调用setMeasuredDimension()这个方法存储本身的宽高,这个方法决定了

当前ViewGroup的大小。如果不调用次方法,则会抛出IllegalStateException异常。


3.onLayout() 在这个函数中,ViewGroup会拿到childView的getMeasuredWidth() 和getMeasuredHeight(),

用来布局所有的childView。

其中onMeasure中的两个参数,是从父控件传递下来的布局要求。这两个要求是按照View.MeasureSpec编码的,具体可以查看View.MeasureSpec源码。

      其实一句话可以总结:这个类包装了从parent传递下来的布局要求,通过int值传递给这个childView。每个MeasureSpec由一个size和mode组成。

三、.View的三种测量模式

EXACTLY:精确值或者match_parent

AT_MOST:wrap_content

UNSPECIFIED:父元素不对子元素宽高进行束缚,AdapterView 中父控件传入

四、自定义样例(一)

  • GridView实现分割线的可能方法

因为GridView不像ListView一般有自己的divider,而实际应用过程中,对于GridView分隔线的需求又比较大,所以此次选取了这个样例。

大家可以一起整理下思路,看看有哪些方法或许会实现GridView分割线。

(1)通过设置android:horizontalSpacing和android:verticalSpacing两个属性值以及背景色

这种方式设置背景色填充GridView之间的Spacing,

(2)给每项设置shape,shape用stroke描线


(3)自定义ViewGroup


ViewGroup的绘制严格来说是在dispatchDraw中完成,作为一个容器,绘制自己的孩子该通过dispatchDraw(canvas)实现。但当你的ViewGroup本身没有可绘制的内容时,onDraw方法不会被调用


因此,一般直接重写dispatchDraw来绘制viewGroup。


  • GridView实现分割线思想

1、只有第一行,绘制Cell上端的横线

2、只有第一列,绘制Cell左边的竖线

3、如果Cell不是最后一个,绘制右边的竖线以及下边的横线

  • 代码实现
<span style="font-size:14px;">@Override
    protected void dispatchDraw(Canvas canvas) {
        super.dispatchDraw(canvas);
        View localView1 = getChildAt(0);
        int column;
        column = COLUMNNUM;
        int childCount = getChildCount();
        Paint localPaint;
        localPaint = new Paint();
        localPaint.setStyle(Paint.Style.STROKE);
        localPaint.setColor(getContext().getResources().getColor(R.color.line_gray));
        for (int i = 0; i < childCount; i++) {
            View cellView = getChildAt(i);
            if (i < 3) {//第一行
                canvas.drawLine(cellView.getLeft(), cellView.getTop(), cellView.getRight(), cellView.getTop(), localPaint);
            }
            if (i % column == 0) {//第一列
                canvas.drawLine(cellView.getLeft(), cellView.getTop(), cellView.getLeft(), cellView.getBottom(), localPaint);
            }
            if ((i + 1) % column == 0) {//第三列
                //画子view底部横线
                canvas.drawLine(cellView.getLeft(), cellView.getBottom(), cellView.getRight(), cellView.getBottom(), localPaint);
                canvas.drawLine(cellView.getRight(), cellView.getTop(), cellView.getRight(), cellView.getBottom(), localPaint);
            } else if ((i + 1) > (childCount - (childCount % column))) {//如果view是最后一行
                //画子view的右边竖线
                canvas.drawLine(cellView.getRight(), cellView.getTop(), cellView.getRight(), cellView.getBottom(), localPaint);
                canvas.drawLine(cellView.getLeft(), cellView.getBottom(), cellView.getRight(), cellView.getBottom(), localPaint);
            } else {//如果view不是最后一行
                //画子view的右边竖线
                canvas.drawLine(cellView.getRight(), cellView.getTop(), cellView.getRight(), cellView.getBottom(), localPaint);
                //画子view的底部横线
                canvas.drawLine(cellView.getLeft(), cellView.getBottom(), cellView.getRight(), cellView.getBottom(), localPaint);
            }
        }
    }</span>

其实以上方法有一定弊端,每次绘制横线或者竖线的时候需要占据子View1像素的空间,因为1像素不是太大,所以可以忽略。

四、自定义样例(二)

           

这种自定义ViewGroup的原理其实就是封装View成一个控件,并实现一定功能。

下面是XML布局文件

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" >

    <TextView
        android:id="@+id/phone_type"
        android:layout_width="@dimen/verify_button_width"
        android:layout_height="@dimen/common_et_height"
        android:layout_alignParentLeft="true"
        android:background="@drawable/corners_left_login_bg"
        android:gravity="center"
        android:text="+86"
        android:textColor="@color/white"
        android:textSize="16sp" />

    <EditText
        android:id="@+id/et_phone"
        android:layout_width="match_parent"
        android:layout_height="@dimen/common_et_height"
        android:layout_alignBottom="@id/phone_type"
        android:layout_alignTop="@id/phone_type"
        android:layout_toRightOf="@id/phone_type"
        android:background="@drawable/corners_right_login_bg"
        android:hint="@string/input_phone"
        android:numeric="integer"
        android:paddingLeft="10dp"
        android:singleLine="true"
        android:textColor="@color/black"
        android:textSize="@dimen/text_size_small" />

    <RelativeLayout
        android:id="@+id/tmp_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/et_phone"
        android:layout_marginTop="15dp"
        android:background="@drawable/shape_edit_tv_rec_cor_bor" >

        <Button
            android:id="@+id/tv_confirm"
            style="@style/OrangeDialogButtonStyle"
            android:layout_width="108dp"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:layout_marginRight="5dp"
            android:gravity="center"
            android:paddingBottom="3dp"
            android:paddingLeft="10dp"
            android:paddingRight="10dp"
            android:paddingTop="3dp"
            android:text="@string/confirm" />

        <EditText
            android:id="@+id/et_confirm_code"
            android:layout_width="match_parent"
            android:layout_height="@dimen/common_et_height"
            android:layout_alignParentLeft="true"
            android:layout_toLeftOf="@id/tv_confirm"
            android:background="@drawable/shape_edit_tv_rec_cor_bor"
            android:hint="@string/input_confirmation_code"
            android:numeric="integer"
            android:paddingLeft="10dp"
            android:singleLine="true"
            android:textSize="@dimen/text_size_small"
            android:windowSoftInputMode="stateHidden|adjustPan" />
    </RelativeLayout>

    <TextView
        android:id="@+id/tv_get_verify_again"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@id/tmp_layout"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="18dp"
        android:text="@string/get_verify_agin"
        android:textColor="@color/edit_hint_gray"
        android:textSize="@dimen/text_size_minimal" />

    <Button
        android:id="@+id/tv_confirm_login"
        android:layout_width="match_parent"
        android:layout_height="@dimen/common_et_height"
        android:layout_below="@id/tv_get_verify_again"
        android:layout_marginTop="@dimen/phone_confirm_2_top"
        android:background="@drawable/logi_btn_gray_bg"
        android:minHeight="40dp"
        android:text="@string/confirm_login"
        android:textColor="@color/white"
        android:textSize="18sp" />

</RelativeLayout>

然后开发者根据以上布局,实现一定功能,并对两个Button定义点击回调接口,一个自定义控件便实现了。


以上两个实例其实并没有涉及到之前说的测量、布局以及事件分发的过程,属于自定义ViewGroup的展示类初级实例。下周会稍微深入的介绍下。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值