导航:首页 > 操作系统 > android自定义view布局

android自定义view布局

发布时间:2022-12-10 04:17:23

android 自定义View之Layout过程

系列文章:

在上篇文章: Android 自定义View之Measure过程 ,我们分析了Measure过程,本次将会掀开承上启下的Layout过程神秘面纱,
通过本篇文章,你将了解到:

在上篇文章的比喻里,我们说过:

该ViewGroup 重写了onMeasure(xx)和onLayout(xx)方法:

同时,当layout 执行结束,清除PFLAG_FORCE_LAYOUT标记,该标记会影响Measure过程是否需要执行onMeasure。

该View 重写了onMeasure(xx)和onLayout(xx)方法:

MyViewGroup里添加了MyView、Button两个控件,最终运行的效果如下:

可以看出,MyViewGroup 里子布局的是横向摆放的。我们重点关注Layout过程。实际上,MyViewGroup里我们只重写了onLayout(xx)方法,MyView也是重写了onLayout(xx)方法。
接下来,分析View Layout过程。

与Measure过程类似,连接ViewGroup onLayout(xx)和View onLayout(xx)之间的桥梁是View layout(xx)。

可以看出,最终都调用了setFrame(xx)方法。

对于Measure过程在onMeasure(xx)里记录了尺寸的值,而对于Layout过程则在layout(xx)里记录了坐标值,具体来说是在setFrame(xx)里,该方法两个重点地方:

View.onLayout(xx)是空实现
从layout(xx)和onLayout(xx)声明可知,这两个方法都是可以被重写的,接下来看看ViewGroup是否重写了它们。

ViewGroup.layout(xx)虽然重写了layout(xx),但是仅仅做了简单判断,最后还是调用了View.layout(xx)。

这重写后将onLayout变为抽象方法,也就是说继承自ViewGroup的类必须重写onLayout(xx)方法。
我们以FrameLayout为例,分析其onLayout(xx)做了什么。

FrameLayout.onLayout(xx)为子布局Layout的时候,起始坐标都是以FrameLayout为基准,并没有记录上一个子布局占了哪块位置,因此子布局的摆放位置可能会重叠,这也是FrameLayout布局特性的由来。而我们之前的Demo在水平方向上记录了上一个子布局的摆放位置,下一个摆放时只能在它之后,因此就形成了水平摆放的功能。
由此类推,我们常说的某个子布局在父布局里的哪个位置,决定这个位置的即是ViewGroup.onLayout(xx)。

上边我们分析了View.layout(xx)、View.onLayout(xx)、ViewGroup.layout(xx)、ViewGroup.onLayout(xx),这四者什么关系呢?
View.layout(xx)

View.onLayout(xx)

ViewGroup.layout(xx)

ViewGroup.onLayout(xx)

View/ViewGroup 子类需要重写哪些方法:

用图表示:

通过上述的描述,我们发现Measure过程和Layout过程里定义的方法比较类似:

它俩的套路比较类似:measure(xx)、layout(xx)一般不需要我们重写,measure(xx)里调用onMeasure(xx),layout(xx)为调用者设置坐标值。
若是ViewGroup:onMeasure(xx)里遍历子布局,并测量每个子布局,最后将结果汇总,设置自己测量的尺寸;onLayout(xx)里遍历子布局,并设置每个子布局的坐标。
若是View:onMeasure(xx)则测量自身,并存储测量尺寸;onLayout(xx)不需要做什么。

Measure过程虽然比Layout过程复杂,但仔细分析后就会发现其本质就是为了设置两个成员变量:

而Layout过程虽然比较简单,其本质是为了设置坐标值

将Measure设置的变量和Layout设置的变量联系起来:

此外,Measure过程通过设置PFLAG_LAYOUT_REQUIRED 标记来告诉需要进行onLayout,而Layout过程通过清除 PFLAG_FORCE_LAYOUT来告诉Measure过程不需要执行onMeasure了。
这就是Layout的承上作用

我们知道View的绘制需要依靠Canvas绘制,而Canvas是有作用区域限制的。例如我们使用:

Cavas绘制的起点是哪呢?
对于硬件绘制加速来说:正是通过Layout过程中设置的RenderNode坐标。
而对于软件绘制来说:

关于硬件绘制加速/软件绘制 后续文章会分析。

这就是Layout的启下作用
以上即是Measure、Layout、Draw三者的内在联系。
当然Layout的"承上"还需要考虑margin、gravity等参数的影响。具体用法参见最开始的Demo。

getMeasuredWidth()/getMeasuredHeight 与 getWidth/getHeight区别
我们以获取width为例,分别来看看其方法:

getMeasuredWidth():获取测量的宽,属于"临时值"
getWidth():获取View真实的宽
在Layout过程之前,getWidth() 默认为0
何时可以获取真实的宽、高

下篇将分析Draw()过程,我们将分析"一切都是draw出来的"道理

本篇基于 Android 10.0

Ⅱ Android 自定义View:为什么你设置的wrap_content不起作用

在使用自定义View时,View宽 / 高的 wrap_content 属性不起自身应有的作用,而且是起到与 match_parent 相同作用。

其实这里有两个问题:

请分析 & 解决问题之前,请先看自定义View原理中 (2)自定义View Measure过程 - 最易懂的自定义View原理系列

问题出现在View的宽 / 高设置,那我们直接来看自定义View绘制中第一步对View宽 / 高设置的过程:measure过程中的 onMeasure() 方法

继续往下看 getDefaultSize()

从上面发现:

那么有人会问:wrap_content和match_parent具有相同的效果,为什么是填充父容器的效果呢?

我们知道,子View的MeasureSpec值是根据子View的布局参数(LayoutParams)和父容器的MeasureSpec值计算得来,具体计算逻辑封装在getChildMeasureSpec()里。

接下来,我们看生成子View MeasureSpec的方法: getChildMeasureSpec() 的源码分析:

getChildMeasureSpec()

从上面可以看出,当子View的布局参数使用 match_parent 或 wrap_content 时:

所以: wrap_content 起到了和 match_parent 相同的作用:等于父容器当前剩余空间大小

当自定义View的布局参数设置成wrap_content时时,指定一个默认大小(宽 / 高)。

这样,当你的自定义View的宽 / 高设置成wrap_content属性时就会生效了。

网上流传着这么一个解决方案:

答: 是,当父View为 AT_MOST 、View为 match_parent 时,该View的 match_parent 的效果就等于 wrap_content 。上述方法存在逻辑错误,但由于这种情况非常特殊的,所以导致最终的结果没有错误。具体分析请看下面例子:

从上面的效果可以看出,View大小 = 默认值

我再将子View的属性改为 wrap_content :

从上面的效果可以看出,View大小还是等于默认值。

相信看到这里你已经看懂了:

为了更好的表示判断逻辑,我建议你们用本文提供的解决方案,即根据布局参数判断默认值的设置

不定期分享关于 安卓开发 的干货,追求 短、平、快 ,但 却不缺深度

Ⅲ Android自定义View基础篇

在我们自定义View,尤其是制作一些复杂炫酷的效果的时候,实际上是将一些简单的东西通过数学上精密的计算组合到一起形成的效果。这其中可能会涉及到画布的相关操作(旋转),以及一些正余弦函数的计算等,这些内容就会用到一些角度、弧度相关的知识。

圆一周对应的角度为360度(角度),对应的弧度为2π弧度。

故得等价关系:360(角度) = 2π(弧度) ==> 180(角度) = π(弧度)

几种创建或使用颜色的方式

Android自定义属性可分为以下几步:

2.自定义View中获取属性

3.在布局中使用

4.属性值的类型归纳

Ⅳ Carson带你学Android:手把手教你写一个完整的自定义View

自定义View一共分为两大类,具体如下图:

对于自定义View的类型介绍及使用场景如下图:

在使用自定义View时有很多注意点(坑),希望大家要非常留意:

View的内部本身提供了post系列的方法,完全可以替代Handler的作用,使用起来更加方便、直接。

主要针对View中含有线程或动画的情况: 当View退出或不可见时,记得及时停止该View包含的线程和动画,否则会造成内存泄露问题

当View带有滑动嵌套情况时,必须要处理好滑动冲突,否则会严重影响View的显示效果。

接下来,我将用自定义View中最常用的 继承View 来说明自定义View的具体应用和需要注意的点

在下面的例子中,我将讲解:

下面我将逐个步骤进行说明:
步骤1:创建自定义View类(继承View类)

特别注意:

步骤2:在布局文件中添加自定义View类的组件及显示

至此,一个基本的自定义View已经实现了,运行效果如下图。

接下来继续看自定义View关于属性自定义的问题:

先来看wrap_content & match_parent属性的区别

如果不手动设置支持 wrap_content 属性,那么 wrap_content 属性是不会生效(显示效果同 match_parent )

padding 属性:用于设置控件内容相对控件边缘的边距;

如果不手动设置支持padding属性,那么padding属性在自定义View中是不会生效的。

绘制时考虑传入的padding属性值(四个方向)。

除了常见的以android:开头的系统属性(如下所示),很多场景下自定义View还需要系统所没有的属性,即自定义属性。

实现自定义属性的步骤如下:

下面我将对每个步骤进行具体介绍

对于自定义属性类型 & 格式如下:

至此,一个较为规范的自定义View已经完成了。

Carson_Ho的github: 自定义View的具体应用

不定期分享关于 安卓开发 的干货,追求 短、平、快 ,但 却不缺深度

Ⅳ Android自定义布局ViewGroup

2.onLayout()方法中,通过view.layout(left,right,top,bottom)。
layout方法会设置该View视图位于父视图的坐标轴
其中left:子布局的左侧到父布局左侧的距离,right:子布局的右侧到父布局左侧的距离,top:子布局的上侧到父布局上侧的距离,bottom:子布局的下侧到父布局上侧的距离。

Ⅵ Android开发 自定义View

Android自定义View实现很简单:
1、继承View,重写构造函数、onDraw,(onMeasure)等函数。
2、如果自定义的View需要有自定义的属性,需要在values下建立attrs.xml。在其中定义你的属性。
3、在使用到自定义View的xml布局文件中需要加入xmlns:前缀="http://schemas.android.com/apk/res/你的自定义View所在的包路径".
4、在使用自定义属性的时候,使用前缀:属性名,如my:textColor="#FFFFFFF"。
实例:
自定义TextView类:
复制代码
package com.zst.service.component;
import com.example.hello_wangle.R;
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.util.Log;
import android.view.View;
import android.widget.TextView;
public class MyTextView extends TextView {
//不能在布局文件中使用
public MyTextView(Context context) {
super(context);
}

//布局文件中用到此构造函数

Ⅶ android 自定义View 使用 DataBinding 笔记

在 build.gradle 文件中添加配置

系统会根据布局文件名称自动生成相应的 DataBinding 类,例如
布局文件 activity_main.xml 会生成 ActivityMainBinding 类 ,类名生成规则为驼峰命名: 文件名(首字母大写) + Binding

activity 中使用:

fragment 中使用:

这里 android:text="@{viewmodel.text}" 对text进行设置
在 Edittext 中可以使用 android:text="@={viewmodel.text}" 进行双向绑定,关键是这个 = 号;

若需要在xml布局文件中使用系统类的属性,则需要引入.例:

通过引入 <import type="android.view.View"/> 可以使用 View.VISIBLE 、 View.GONE 属性

有时候自定义view要用DataBinding时需要通过 @BindingAdapter 设置

单向绑定设置比较简单 @BindingAdapter("app:paramA") 方法名为 setParamA ,单向绑定嘛就是传入参数:

双向绑定 set 、 get 方法,最主要的是 setListeners 方法,名字可以随意,只是 @BindingAdapter 中是 "app:paramBAttrChanged" ,在属性后面跟上 AttrChanged , attrChange 为通知系统内容变更的回调,当在 MineView 内部 paramB 发生变更时,通过 attrChange 去通知对 paramB 进行绑定的对象更新自己缓存中的内容 如:

希望对您有帮助.

Ⅷ Android 自定义View之Draw过程(上)

Draw 过程系列文章

Android 展示之三部曲:

前边我们已经分析了:

这俩最主要的任务是: 确定View/ViewGroup可绘制的矩形区域。
接下来将会分析,如何在这给定的区域内绘制想要的图形。

通过本篇文章,你将了解到:

Android 提供了关于View最基础的两个类:

然而ViewGroup 并没有约定其内部的子View是如何布局的,是叠加在一起呢?还是横向摆放、纵向摆放等。同样的View 也没有约定其展示的内容是啥样,是矩形、圆形、三角形、一张图片、一段文字抑或是不规则的形状?这些都要我们自己去实现吗?
不尽然,值得高兴的是Android已经考虑到上述需求了,为了开发方便已经预制了一些常用的ViewGroup、View。
如:
继承自ViewGroup的子类

继承自View的子类

虽然以上衍生的View/ViewGroup子类已经大大为我们提供了便利,但也仅仅是通用场景下的通用控件,我们想实现一些较为复杂的效果,比如波浪形状进度条、会发光的球体等,这些系统控件就无能为力了,也没必要去预制千奇百怪的控件。想要达到此效果,我们需要自定义View/ViewGroup。
通常来说自定义View/ViewGroup有以下几种:

3 一般不怎么用,除非布局比较特殊。1、2、4 是我们常用的手段,对于我们常说的"自定义View" 一般指的是 4。
接下来我们来看看 4是怎么实现的。

在xml里引用MyView

效果如下:

黑色部分为其父布局背景。
红色矩形+黄色圆形即是MyView绘制的内容。
以上是最简单的自定义View的实现,我们提取重点归纳如下:

由上述Demo可知,我们只需要在重写的onDraw(xx)方法里绘制想要的图形即可。
来看看View 默认的onDraw(xx)方法:

发现是个空实现,因此继承自View的类必须重写onDraw(xx)方法才能实现绘制。该方法传入参数为:Canvas类型。
Canvas翻译过来一般叫做画布,在重写的onDraw(xx)里拿到Canvas对象后,有了画布我们还需要一支笔,这只笔即为Paint,翻译过来一般称作画笔。两者结合,就可以愉快的作画(绘制)了。
你可能发现了,在Demo里调用

并没有传入Paint啊,是不是Paint不是必须的?实际上调用该方法后,底层会自动生成Paint对象。

可以看到,底层初始化了Paint,并且给其设置的颜色为在java层设置的颜色。

onDraw(xx)比较简单,开局一个Canvas,效果全靠画。
试想,这个Canvas怎么来的呢,换句话说是谁调用了onDraw(xx)。发挥一下联想功能,在Measure、Layout 过程有提到过两者套路很像:

那么Draw过程是否也是如此套路呢?看见了onDraw(xx),那么draw(xx)还远吗?
没错,还真有draw(xx)方法:

可以看出,draw(xx)主要分为两个部分:

不管是A分支还是B分支,都进行了好几步的绘制。
通常来说,单一一个View的层次分为:

后面绘制的可能会遮挡前边绘制的。
对于一个ViewGroup来说,层次分为:

来看看A分支标注的4个点:
(1)
onDraw(canvas)
前面分析过,对于单一的View,onDraw(xx)是空实现,需要由我们自定义绘制。
而对于ViewGroup,也并没有具体实现,如果在自定义ViewGroup里重写onDraw(xx),它会执行吗?默认是不会执行的,相关分析请移步:
Android ViewGroup onDraw为什么没调用

(2)
dispatchDraw(canvas),来看看在View.java里的实现:

发现是个空实现,再看看ViewGroup.java里的实现:

也即是说,对于单一View,因为没有子布局,因此没必要再分发Draw,而对于ViewGroup来说,需要触发其子布局发起Draw过程(此过程后续分析),可以类比事件分发过程View、ViewGroup的处理。感兴趣的请移步:
Android 输入事件一撸到底之View接盘侠(3)

(3)
OverLay,顾名思义就是"盖在某个东西上面",此处是在绘制内容之后,绘制前景之前。怎么用呢?

以上是给一个ViewGroup设置overLay,效果如下:

你可能发现了,这和设置overLay差不多的嘛,实际还是有差别的。在onDrawForeground(xx)里会重新调整Drawable的尺寸,该尺寸与View大小一致,之前给Drawable设置的尺寸会失效。运行效果如下:

可以看出,ViewGroup都被前景盖住了。
再来看看B分支的重点:边缘渐变效果
先来看看TextView 边缘渐变效果:

加上这俩参数。
实际上系统自带的一些控件也使用了该效果,如NumberPicker、YearPickerView

以上是NumberPicker 的效果,可以看出是垂直方向渐变的。

对于View.java 里的onDraw(xx)、draw(xx),ViewGroup.java里并没有重写。
而对于dispatchDraw(xx),在View.java里是空实现。在ViewGroup.java里发起对子布局的绘制。

来看看标记的2点:
(1)
设置padding的目的是为了让子布局留出一定的空隙出来,因此当设置了padding后,子布局的canvas需要根据padding进行裁减。判断标记为:

FLAG_CLIP_TO_PADDING 默认设置为true
FLAG_PADDING_NOT_NULL 只要有padding不为0,该标记就会打上。
也就是说:只要设置了padding 不为0,子布局显示区域需要裁减。
能不能不让子布局裁减显示区域呢?
答案是可以的。
考虑到一种场景:使用RecyclerView的时候,我们需要设置paddingTop = 20px,效果是:RecyclerView Item展示时离顶部有20px,但是滚动的时候永远滚不到顶部,看起来不是那么友好。这就是上述的裁减起作用了,需要将此动作禁止。通过设置:

当然也可以在xml里设置:

(2)
drawChild(xx)

从方法名上看是调用子布局进行绘制。
child.draw(x1,x2,x3)里分两种情况:

这两者具体作用与区别会在下篇文章分析,不管是硬件加速绘制还是软件加速绘制,最终都会调用View.draw(xx)方法,该方法上面已经分析过。
注意,draw(x1,x2,x3)与draw(xx)并不一样,不要搞混了。

用图表示:

View/ViewGroup Draw过程的联系:

一般来说,我们通常会自定义View,并且重写其onDraw(xx)方法,有没有绘制内容的ViewGroup需求呢?
是有的,举个例子,大家可以去看看RecyclerView ItemDecoration 的绘制,其中运用到了ViewGroup draw(xx)、ViewGroup onDraw(xx) 、View onDraw(xx)绘制的先后顺序来实现分割线,分组头部悬停等功能的。

本篇文章基于 Android 10.0

Ⅸ Android自定义View

View的构造函数:共有4个

系统自带的View可以在xml中配置属性,对于写的好的自定义View同样可以在xml中配置属性,为了使自定义的View的属性可以在xml中配置,需要以下4个步骤:

一定要记住:无论是measure过程、layout过程还是draw过程,永远都是从View树的根节点开始测量或计算(即从树的顶端开始),一层一层、一个分支一个分支地进行(即树形递归),最终计算整个View树中各个View,最终确定整个View树的相关属性。

Android的坐标系定义为:

View的位置由4个顶点决定的 4个顶点的位置描述分别由4个值决定:

View的位置是通过view.getxxx()函数进行获取:(以Top为例)

与MotionEvent中 get()和getRaw()的区别

MarginLayoutParams是和外间距有关的。事实也确实如此,和LayoutParams相比,MarginLayoutParams只是增加了对上下左右外间距的支持。实际上大部分LayoutParams的实现类都是继承自MarginLayoutParams,因为基本所有的父容器都是支持子View设置外间距的。

1. 创建自定义属性

2. 继承MarginLayout

3. 重写ViewGroup中几个与LayoutParams相关的方法

在为View设置LayoutParams的时候需要根据它的父容器选择对应的LayoutParams,否则结果可能与预期不一致,这里简单罗列一些常见的LayoutParams子类:

测量规格,封装了父容器对 view 的布局上的限制,内部提供了宽高的信息( SpecMode 、 SpecSize ),SpecSize是指在某种SpecMode下的参考尺寸,其中SpecMode 有如下三种:

针对上表,这里再做一下具体的说明

一般getIntrinsicWidth/Height能获得内部宽/高 图片Drawable其内部宽高就是图
片的宽高 颜色Drawable没有内部宽高的概念 内部宽高不等同于它的大小,一般
Drawable没有大小概念(作为View背景时,会被拉伸至View的大小)

阅读全文

与android自定义view布局相关的资料

热点内容
dvd光盘存储汉子算法 浏览:757
苹果邮件无法连接服务器地址 浏览:962
phpffmpeg转码 浏览:671
长沙好玩的解压项目 浏览:142
专属学情分析报告是什么app 浏览:564
php工程部署 浏览:833
android全屏透明 浏览:732
阿里云服务器已开通怎么办 浏览:803
光遇为什么登录时服务器已满 浏览:302
PDF分析 浏览:484
h3c光纤全工半全工设置命令 浏览:141
公司法pdf下载 浏览:381
linuxmarkdown 浏览:350
华为手机怎么多选文件夹 浏览:683
如何取消命令方块指令 浏览:349
风翼app为什么进不去了 浏览:778
im4java压缩图片 浏览:362
数据查询网站源码 浏览:150
伊克塞尔文档怎么进行加密 浏览:892
app转账是什么 浏览:163