自定义View第一篇(view生命周期的简介)
Posted pszh
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自定义View第一篇(view生命周期的简介)相关的知识,希望对你有一定的参考价值。
我们都知道activity的生命周期 ,但是没有很好的去理解view的“生命周期” ,就是自定义一个view正常的流程是什么样子的呢,下面请看一张图,我们这篇就只是围绕着这个图来说一.ConStructor:
自定义一个view不用说首先做的就是构造放法了,构造方法一般会有1-3个参数不等(这里用View作为例子) a.一个参数构造的 public View(Context context) 通过java代码创建视图的时候被调用, b.两个参数构造的public View(Context context, AttributeSet attrs) 通过xml填充视图的时候会被调用, c.三个参数构造的public View( Context context,AttributeSet attrs, int defStyle) 给我们的view提供一个基本的style,如果我们没有设置属性,就使用这个style中的属性,一般我们的都是不动这三个参数的方法 ⚠️ 在API21之后,引入了4个参数的构造参数 public View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) 然后这里要提的一点是自定义的属性 大家对这个应该也是不陌生的,在xml中经常会看到app:***="",这里的***就是自定义的属性了,1.首先是在res的values文件夹下创建一个attrs的文件,然后对自定义的控件添加属性
2.然后在两个参数的构造方法中获取这个xml中的属性值,
publicLowCoustView(Context context,AttributeSet attrs)
super(context, attrs);
TypedArray array = getContext().obtainStyledAttributes(attrs,R.styleable.LowCoustView);
String text = array.getString(R.styleable.LowCoustView_lowcoustview_text);
//记得回收
array.recycle();
二onAttachedToWindow():
Parentview调用addView的之后,这个自定义的view 会被依附到一个窗口上,这个时候自定义view 会知道他的周围的view是什么样子,如果周围的view 也有同样的自定义的view时候,你可以通过id找到它们,并作为全局变量的应用。 (很绕口,其实就是在一个layout.xml中有两个或两个以上的自定义view 的时候,通过这个方法让id去区分它们)。三 measure() onMeasure
找到了对应的 view之后,我们需要做的就是计算实际的大小 实际的高对应属性:mMeasuredHeight)和宽(对应属性: mMeasureWidth),每个View的控件的实际宽高都是由父视图和本身视图决定的,这个时候就需要用到onMeasure()这个处理自己大小的方法了,这个方法很重要,因为很多时候,你的view需要特定的大小去适应布局,也就是适配了。 ⚠️注意的是 ,如果你重写了这方法的话,一定要调用setMeasuredDimension(int width,int hight),把新测量的值设置到view 中。 这里也说下如何做设置吧:(1)计算你的view 的内容所需的大小(宽度和高度)
(2)获取你的View MeasureSpec大小和模式(宽度和高度,比如具体的值,wrap_content,match_parent,fill_parent这些)
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
int widthMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSize = MeasureSpec.getSize(widthMeasureSpec);
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
(3)检查MeasureSpec设置和调整View(宽和高)的尺寸模式
// desiredWidth 控件xml或者是动态设置的高度
// MeasureSpec的三种Mode:
// 1.UNSPECIFIED
// 父不没有对子施加任何约束,子可以是任意大小(也就是未指定)
// (UNSPECIFIED在源码中的处理和EXACTLY一样。
// 当View的宽高值设置为0的时候或者没有设置宽高时,模式为UNSPECIFIED
// 2.EXACTLY
// 父决定子的确切大小,子被限定在给定的边界里,忽略本身想要的大小。
// (当设置width或height为match_parent或者是确定值时,模式为EXACTLY,
// 因为子view会占据剩余容器的空间,所以它大小是确定的)
// 3.AT_MOST
// 子最大可以达到的指定大小
// (当设置为wrap_content时,模式为AT_MOST,
// 表示子view的大小最多是多少,这样子view会根据这个上限来设置自己的尺寸)
int width;
if (widthMode == MeasureSpec.EXACTLY)
width = widthSize;
else if (widthMode == AT_MOST)
width = Math.min(desiredWidth, widthSize);
else
width = desiredWidth;
如果想了解更多可以查看: http://blog.csdn.net/a396901990/article/details/36475213 (介绍了自定义view 和viewGroup的不同用法,这类代码的话基本是通用的)
四 layout()onLayout
自己的大小处理好了之后,就得想到给他的孩子 也就是给它的子view位置,当然还有它自己的位置,这个时候就需要我们用到 Layout()这个放方法了,如果view控件位置移动了,也是要调用这个方法的onLayout(),这里来简单介绍下方法的参数和使用 layout(int left, int top, int rgith, int bottom) : 四个参数表示最新的上下左右的距离(相对于父控件的),这里要介绍一个setFrame(left, top, right, bottom),他会返回一个boolean值changed表示view的位置是否有发生了变化,然后遍历子 view 调用onlayout()方法,修改子view的位置了onLayout( boolean changed, int left, int top, int right, int bottom) changed :view的大小和位置是否有变化,后面的是view相对父view的左,上,右,下的距离,
⚠️ 1.view.getWidth() = right-left; view.getHeight() = bottom - top;这个view.getWidth()和measure中的mMeasuredWidth,也就是 view.getMeasuredWidth()是有区别的, view.getWidth()是整个屏幕中显示的宽,而view.getMeasuredWidth()是指view整个宽度,包块屏幕没显示到的 2.view的话改变位置可以直接通过onlayout(),调用就是onlayout(boolean,left,top,left+width,top+height),一般不是继承view的话是不能重写layout()这个方法的
想要了解更多查看: http://blog.csdn.net/a396901990/article/details/38129669
五 dispatchDraw() . draw()和onDraw()
draw():先来画背景, 画内容 (通过调用onDraw()方法),然后画子视图dispatchDraw(),画装饰(比如滚动条),逻辑是完成的一般不要重写 onDraw():绘制视图中的内容,通过,canvas(画布),以及paint (画笔),path(画路径) 来画 dispatchDraw():调用它来绘制子视图用,(如果该View类型不为ViewGroup,就不包含子视图,不要重载它),一般情况ViewGroup已经实现了它的功能(通过遍历子视图,调用drawChild()去重新回调 “需要重绘”子view的draw()方法),通常我们不用处理这个类了; 所以大部分时候我们都是通过重写 onDraw()方法的就ok了, ⚠️ onDraw会比较耗时,在布局变化时候(点击,滑动等)都需要重绘,所以我们不要在onDraw中进行对象分配操作,比如new paint();最后我们会处理一些动画,然后导致view会重新绘制,这个时候的话就需要下面这些方法
(1)invalidate()方法
请求重绘View树,即draw()过程,假如视图发生大小没有变化就不会调用layout()过程,并且只绘制那些“需要重绘的”视图,即谁(View的话,只绘制该View ;ViewGroup,则绘制整个ViewGroup)请求invalidate()方法,就绘制该视图。
一般引起invalidate()操作的函数如下:
1、直接调用invalidate()方法,请求重新draw(),但只会绘制调用者本身。
2、setSelection()方法 :请求重新draw(),但只会绘制调用者本身。
3、setVisibility()方法 : 当View可视状态在INVISIBLE转换VISIBLE时,会间接调用invalidate()方法, 继而绘制该View。
4 、setEnabled()方法 : 请求重新draw(),但不会重新绘制任何视图包括该调用者本身。
(2)requestLayout()方法
请求重新布局layout,他会调用measure()和layout()过程,他不会调用draw()过程,也就是不会重新绘制任何视图包括自己。
一般引起requestLayout的操作函数:
1、setVisibility()方法:当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方 法。
一般引起requestLayout()操作的函数如下:
1、setVisibility()方法:
当View的可视状态在INVISIBLE/ VISIBLE 转换为GONE状态时,会间接调用requestLayout() 和invalidate方法。
同时,由于整个个View树大小发生了变化,会请求measure()过程以及draw()过程,同样地,只绘制需要“重新绘制”的视图。
题外话 引入动画的话
在自定义View中,动画是一帧一帧的过程。这意味着,如果你想使一个圆半径从小变大,你将需要逐步增加半径并调用invalidate()来重绘它。
在自定义View动画中,ValueAnimator是你的好朋友。下面这个类将帮助你从任何值开始执行动画到最后,甚至支持Interpolator(如果需要)。
ValueAnimator animator = ValueAnimator.ofInt( 0, 100); animator.setDuration( 1000); animator.setInterpolator( new DecelerateInterpolator()); animator.addUpdateListener( new ValueAnimator.AnimatorUpdateListener() public void onAnimationUpdate(ValueAnimator animation) int newRadius = ( int) animation.getAnimatedValue(); postInvalidate(); ); animator.start();
参考文章
https://hackernoon.com/android-draw-a-custom-view-ef79fe2ff54b
以上是关于自定义View第一篇(view生命周期的简介)的主要内容,如果未能解决你的问题,请参考以下文章