自定义View初探-onLayout详解_viewgroup onlayout参数-程序员宅基地

技术标签: --------2.2.1 自定义view系列  Android基础知识  

通过本篇博客你将学到

①自定义控件中onLayout的源码分析

一个例子来理解自定义控件的onLayout的过程 

getMeasureWidth和getWidth的区别


1.简单回顾

    在上一篇我们详细讲解了onMeasure方法,我们首先来回顾一下上一篇的内容,上一篇我们说到onMeasure的过程,在onMeasure方法中最终调用setMeasuredDimension方法来确定控件的大小,假如是自定义一个View的话,测量一下其大小就行了,如果是ViewGroup呢,则需要遍历其所有的子View来,并为每个子View测量它的大小。在测量子View时需要两个参数,measureWidth和measureHeight这两个值是根布局传过来的,也就是说是父View和子View本身共同决定子View的大小。


2.源码分析

今天呢,就和大家一起来探讨自定义控件的第二步onLayout即确定控件的位置,上篇文章我们说到performTraversals方法中会调用host.measure方法,在调用完host.measure方法后,就会调用host.layout对View进行定位,这也是今天我们要讨论的内容。

首先我们来看看layout的源码

[java]  view plain  copy
  1. public final void layout(int l, int t, int r, int b) {  
  2.         boolean changed = setFrame(l, t, r, b);  
  3.         if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {  
  4.             if (ViewDebug.TRACE_HIERARCHY) {  
  5.                 ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);  
  6.             }  
  7.             onLayout(changed, l, t, r, b);  
  8.             mPrivateFlags &= ~LAYOUT_REQUIRED;  
  9.         }  
  10.         mPrivateFlags &= ~FORCE_LAYOUT;  
  11.     }  

在其中调用了setFrame方法的源码如下

[java]  view plain  copy
  1. protected boolean setFrame(int left, int top, int right, int bottom) {  
  2.         boolean changed = false;  
  3.         if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {  
  4.             changed = true;  
  5.               
  6.             。。。省略部分代码。。。  
  7.   
  8.             mLeft = left;  
  9.             mTop = top;  
  10.             mRight = right;  
  11.             mBottom = bottom;  
  12.               
  13.             。。。省略部分代码。。。  
  14.               
  15.         }  
  16.         return changed;  
  17.     }  

在setFrame方法中将left,top,right,bottom这四个值保存下来。也就是说在layout中的setFrame方法中会将子View相对于父View的左,上,右,下这四个值保存下来,这四个值就会确定子View在父View中的位置。仔细的看layout方法的源码你会发现和measure方法一样在layout中调用了onLayout方法,赶紧先去看看View的onLayout的逻辑

View——onLayout

[java]  view plain  copy
  1. protected void onLayout(boolean changed, int left, int top, int right, int bottom) {  
  2.     }  

 啊?啥都没有,你这是弄啥嘞?其实仔细想想你就会理解,因为onLayout的目的是确定子View在父View中的位置,那么这个步骤肯定是由父View来决定的,因此在View中onLayout是一个空的实现,既然如此我们就去看看ViewGroup的onLayout的源码呗。

ViewGroup——onLayout

[java]  view plain  copy
  1. @Override  
  2.     protected abstract void onLayout(boolean changed,  
  3.             int l, int t, int r, int b);  

我们看到它是一个抽象的方法,我们都知道当继承一个类时必须实现其中的抽象方法,这也就是说在自定义ViewGroup时我们必须实现onLayout方法。在上一篇我们就说过onLayout的作用就是确定子View的位置,那么它是怎样确定子View的位置的呢?其实它是通过四个参数 l,t,r,b即代表距离父View的左上右下的距离,看张图你就会明白它的含义


mLeft,mTop,mRight,mBottom的讲解

mLeft——View.getLeft():子View的左边界到父View的左边界的距离

[java]  view plain  copy
  1.      // 获取子View的左边界到父View的左边界的距离  
  2. ublic final int getLeft() {  
  3. return mLeft;  

mTop——View.getTop():子View的顶部到父View顶部的距离

mRight——View.getRight():子View的右边界到父View的左边界的距离

mBottom——View.getBottom():子View的底部到父View的顶部的距离

它们在源码中的表现如下

[java]  view plain  copy
  1.        /** 
  2.  * Top position of this view relative to its parent. 
  3.  *  
  4.  * @return The top of this view, in pixels. 
  5.  */  
  6. // 获取子View的顶部到父View顶部的距离  
  7. public final int getTop() {  
  8.     return mTop;  
  9. }  
  10.   
  11. // 获取子View的底部到父View的顶部的距离  
  12. public final int getBottom() {  
  13.     return mBottom;  
  14. }  
  15.   
  16. // 获取子View的左边界到父View的左边界的距离  
  17. public final int getLeft() {  
  18.     return mLeft;  
  19. }  
  20.   
  21. // 获取子View的右边界到父View的左边界的距离  
  22. public final int getRight() {  
  23.     return mRight;  
  24. }  
  25.   
  26. public final int getWidth() {  
  27.     return mRight - mLeft;  
  28. }  
  29.   
  30. /** 
  31.  * Return the height of your view. 
  32.  *  
  33.  * @return The height of your view, in pixels. 
  34.  */  
  35. public final int getHeight() {  
  36.     return mBottom - mTop;  
  37. }  
  38.   
  39. /** 
  40.  * The height of this view as measured in the most recent call to measure(). 
  41.  * This should be used during measurement and layout calculations only. Use 
  42.  * {@link #getHeight()} to see how tall a view is after layout. 
  43.  *  
  44.  * @return The measured height of this view. 
  45.  */  
  46. // 获取测量的宽度  
  47. public final int getMeasuredWidth() {  
  48.     return mMeasuredWidth;  
  49. }  
  50.   
  51. /** 
  52.  * The width of this view as measured in the most recent call to measure(). 
  53.  * This should be used during measurement and layout calculations only. Use 
  54.  * {@link #getWidth()} to see how wide a view is after layout. 
  55.  *  
  56.  * @return The measured width of this view. 
  57.  */  
  58. // 获取测量的高度  
  59. public final int getMeasuredHeight() {  
  60.     return mMeasuredHeight;  
  61. }  
    如果你仔细看上面的源码你会看到在上面的距离中还有两对方法,getMeasureWidth,getMeasureHeight和getWidth,getHeight它们有什么区别呢?对于这个问题可能有很多人不是特别理解,在这里我们以getMeasureWidth和getWidth这两个方法为例来进行说明,

①getMeasureWidth()方法在measure()过程结束后就可以获得到它的值,而getWidth()方法要在layout()过程结束后才能获取到。这么说有什么依据?首先看看getMeasureWidth()方法的返回值,它是mMeasureWidth,对于它你熟悉吗?这就是在上篇博客中通过setMeasureDimension()方法设置的值,而getWidth()的返回值是mRight-mLeft,这两个值是在layout()过程中的setFrame方法中才设置的值,也就是说在layout结束后才确定的。

②getMeasureWidth()方法中的值是通过setMeasuredDimension()方法来进行设置的,而getWidth()方法中的值则是通过视图右边的坐标减去左边的坐标计算出来的。关于这两个方法的区别,现在有网上的很多说法是不正确的,我将通过一篇博客来给大家详细的说说这两个值的区别。


 3.小例子

   接下来通过一个简单的例子来了解下latyout的过程,这个例子是这样的,自定义一个ViewGroup让其子View在屏幕中间位置横向排列,首先我们来谈谈它的思想,我觉得当你在做一件事情的时候一定认真的去分析它的实现过程,按照自己的思想去设计自己的东西,不管可不可行,去尝试即使错了对自己也是一个很好的历练,这样时间长了你会发现你的技术提升了很多

我的思路是这样的

让自定义的ViewGroup的子View在屏幕中间横向排列思路:

①获得屏幕的高度

②获得子View的宽度和高度,通过屏幕的高度和子View的高度来计算layout时子View到父View顶端的距离

③因为子View是横向排列的,所以需要设定一个变量表示子View到父View左边界的距离,每次循环加上子View的宽度来设定下一个子View到父View左侧的距离

先上效果图


就是这么一个效果,接下来看看其代码吧,按照上面我们分析的思路,跟着代码看看吧

[java]  view plain  copy
  1. package com.example.customviewpractice;  
  2.   
  3. import android.app.Activity;  
  4. import android.content.Context;  
  5. import android.util.AttributeSet;  
  6. import android.util.DisplayMetrics;  
  7. import android.view.View;  
  8. import android.view.ViewGroup;  
  9.   
  10. public class MyViewGroup extends ViewGroup {  
  11.   
  12.     private Context mContext;  
  13.     private int sreenH;  
  14.   
  15.     public MyViewGroup(Context context, AttributeSet attrs) {  
  16.         super(context, attrs);  
  17.         mContext = context;  
  18.         // 获取屏幕的高度  
  19.         sreenH = getScreenSize(((Activity) mContext))[1];  
  20.     }  
  21.   
  22.     @Override  
  23.     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {  
  24.   
  25.         super.onMeasure(widthMeasureSpec, widthMeasureSpec);  
  26.         // 测量子View  
  27.         measureChildren(widthMeasureSpec, heightMeasureSpec);  
  28.     }  
  29.   
  30.     @Override  
  31.     protected void onLayout(boolean changed, int l, int t, int r, int b) {  
  32.         // 获得子View个数  
  33.         int childCount = getChildCount();  
  34.         // 设置一个变量保存到父View左侧的距离  
  35.         int mLeft = 0;  
  36.         // 遍历子View  
  37.         for (int i = 0; i < childCount; i++) {  
  38.   
  39.             View childView = getChildAt(i);  
  40.             // 获得子View的高度  
  41.             int childViewHeight = childView.getMeasuredHeight();  
  42.             // 获得子View的宽度  
  43.             int childViewWidth = childView.getMeasuredWidth();  
  44.             // 让子View在竖直方向上显示在屏幕的中间位置  
  45.             int height = sreenH / 2 - childViewHeight / 2;  
  46.             // 调用layout给每一个子View设定位置mLeft,mTop,mRight,mBottom.左上右下  
  47.             childView.layout(mLeft, height, mLeft + childViewWidth, height  
  48.                     + childViewHeight);  
  49.             // 改变下一个子View到父View左侧的距离  
  50.             mLeft += childViewWidth;  
  51.         }  
  52.     }  
  53.   
  54.     /** 
  55.      * 获取屏幕尺寸 
  56.      */  
  57.     public static int[] getScreenSize(Activity activity) {  
  58.         DisplayMetrics metrics = new DisplayMetrics();  
  59.         activity.getWindowManager().getDefaultDisplay().getMetrics(metrics);  
  60.         return new int[] { metrics.widthPixels, metrics.heightPixels };  
  61.     }  
  62. }  
布局文件
[html]  view plain  copy
  1. <com.example.customviewpractice.MyViewGroup xmlns:android="http://schemas.android.com/apk/res/android"  
  2.     xmlns:tools="http://schemas.android.com/tools"  
  3.     android:layout_width="match_parent"  
  4.     android:layout_height="match_parent" >  
  5.   
  6.     <Button  
  7.         android:id="@+id/btn1"  
  8.         android:layout_width="wrap_content"  
  9.         android:layout_height="wrap_content"  
  10.         android:text="测试一" />  
  11.   
  12.     <Button  
  13.         android:id="@+id/btn2"  
  14.         android:layout_width="wrap_content"  
  15.         android:layout_height="wrap_content"  
  16.         android:text="测试二" />  
  17.   
  18.     <Button  
  19.         android:id="@+id/btn3"  
  20.         android:layout_width="wrap_content"  
  21.         android:layout_height="wrap_content"  
  22.         android:text="测试三" />  
  23.   
  24.     <Button  
  25.         android:id="@+id/btn4"  
  26.         android:layout_width="wrap_content"  
  27.         android:layout_height="wrap_content"  
  28.         android:text="测试四" />  
  29.   
  30. </com.example.customviewpractice.MyViewGroup>  

    运行后就会看到上面的效果图,这里需要注意的是,在这个小例子中我们并没有进行换行处理,这里只是学习下layout的用法,如果需要你掌握了自定义控件的过程,完全可以去自定义一个FlowLayout。


4.总结

     到这里关于自定义控件的基础知识onMeasure和onLayout就讲完了,其实刚开始的时候很惧怕这部分的内容,感觉很难,但是沉下心来认真的去学习,认真去屡清思路,你会发现其实并不是特别难,有了上两篇的基础我们就可以去实现一些小的自定义控件了,在进行自定义控件时思路很重要,只有把基础掌握好了,才能按照我们的设计去实现所要实现的效果,学习编程就是这样它需要我们有思想这个思想是靠平时我们不断的积累而产生的。


版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/wu371894545/article/details/52909116

智能推荐

c# 调用c++ lib静态库_c#调用lib-程序员宅基地

文章浏览阅读2w次,点赞7次,收藏51次。四个步骤1.创建C++ Win32项目动态库dll 2.在Win32项目动态库中添加 外部依赖项 lib头文件和lib库3.导出C接口4.c#调用c++动态库开始你的表演...①创建一个空白的解决方案,在解决方案中添加 Visual C++ , Win32 项目空白解决方案的创建:添加Visual C++ , Win32 项目这......_c#调用lib

deepin/ubuntu安装苹方字体-程序员宅基地

文章浏览阅读4.6k次。苹方字体是苹果系统上的黑体,挺好看的。注重颜值的网站都会使用,例如知乎:font-family: -apple-system, BlinkMacSystemFont, Helvetica Neue, PingFang SC, Microsoft YaHei, Source Han Sans SC, Noto Sans CJK SC, W..._ubuntu pingfang

html表单常见操作汇总_html表单的处理程序有那些-程序员宅基地

文章浏览阅读159次。表单表单概述表单标签表单域按钮控件demo表单标签表单标签基本语法结构<form action="处理数据程序的url地址“ method=”get|post“ name="表单名称”></form><!--action,当提交表单时,向何处发送表单中的数据,地址可以是相对地址也可以是绝对地址--><!--method将表单中的数据传送给服务器处理,get方式直接显示在url地址中,数据可以被缓存,且长度有限制;而post方式数据隐藏传输,_html表单的处理程序有那些

PHP设置谷歌验证器(Google Authenticator)实现操作二步验证_php otp 验证器-程序员宅基地

文章浏览阅读1.2k次。使用说明:开启Google的登陆二步验证(即Google Authenticator服务)后用户登陆时需要输入额外由手机客户端生成的一次性密码。实现Google Authenticator功能需要服务器端和客户端的支持。服务器端负责密钥的生成、验证一次性密码是否正确。客户端记录密钥后生成一次性密码。下载谷歌验证类库文件放到项目合适位置(我这边放在项目Vender下面)https://github.com/PHPGangsta/GoogleAuthenticatorPHP代码示例://引入谷_php otp 验证器

【Python】matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距-程序员宅基地

文章浏览阅读4.3k次,点赞5次,收藏11次。matplotlib.plot画图横坐标混乱及间隔处理_matplotlib更改横轴间距

docker — 容器存储_docker 保存容器-程序员宅基地

文章浏览阅读2.2k次。①Storage driver 处理各镜像层及容器层的处理细节,实现了多层数据的堆叠,为用户 提供了多层数据合并后的统一视图②所有 Storage driver 都使用可堆叠图像层和写时复制(CoW)策略③docker info 命令可查看当系统上的 storage driver主要用于测试目的,不建议用于生成环境。_docker 保存容器

随便推点

网络拓扑结构_网络拓扑csdn-程序员宅基地

文章浏览阅读834次,点赞27次,收藏13次。网络拓扑结构是指计算机网络中各组件(如计算机、服务器、打印机、路由器、交换机等设备)及其连接线路在物理布局或逻辑构型上的排列形式。这种布局不仅描述了设备间的实际物理连接方式,也决定了数据在网络中流动的路径和方式。不同的网络拓扑结构影响着网络的性能、可靠性、可扩展性及管理维护的难易程度。_网络拓扑csdn

JS重写Date函数,兼容IOS系统_date.prototype 将所有 ios-程序员宅基地

文章浏览阅读1.8k次,点赞5次,收藏8次。IOS系统Date的坑要创建一个指定时间的new Date对象时,通常的做法是:new Date("2020-09-21 11:11:00")这行代码在 PC 端和安卓端都是正常的,而在 iOS 端则会提示 Invalid Date 无效日期。在IOS年月日中间的横岗许换成斜杠,也就是new Date("2020/09/21 11:11:00")通常为了兼容IOS的这个坑,需要做一些额外的特殊处理,笔者在开发的时候经常会忘了兼容IOS系统。所以就想试着重写Date函数,一劳永逸,避免每次ne_date.prototype 将所有 ios

如何将EXCEL表导入plsql数据库中-程序员宅基地

文章浏览阅读5.3k次。方法一:用PLSQL Developer工具。 1 在PLSQL Developer的sql window里输入select * from test for update; 2 按F8执行 3 打开锁, 再按一下加号. 鼠标点到第一列的列头,使全列成选中状态,然后粘贴,最后commit提交即可。(前提..._excel导入pl/sql

Git常用命令速查手册-程序员宅基地

文章浏览阅读83次。Git常用命令速查手册1、初始化仓库git init2、将文件添加到仓库git add 文件名 # 将工作区的某个文件添加到暂存区 git add -u # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,不处理untracked的文件git add -A # 添加所有被tracked文件中被修改或删除的文件信息到暂存区,包括untracked的文件...

分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120-程序员宅基地

文章浏览阅读202次。分享119个ASP.NET源码总有一个是你想要的_千博二手车源码v2023 build 1120

【C++缺省函数】 空类默认产生的6个类成员函数_空类默认产生哪些类成员函数-程序员宅基地

文章浏览阅读1.8k次。版权声明:转载请注明出处 http://blog.csdn.net/irean_lau。目录(?)[+]1、缺省构造函数。2、缺省拷贝构造函数。3、 缺省析构函数。4、缺省赋值运算符。5、缺省取址运算符。6、 缺省取址运算符 const。[cpp] view plain copy_空类默认产生哪些类成员函数

推荐文章

热门文章

相关标签