Bootstrap

自定义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


;