美文网首页
自定义View

自定义View

作者: 安梦_4de8 | 来源:发表于2019-05-20 23:04 被阅读0次

自定义View

1、什么是自定义View?

    自定义View可分为三类:

    a、把系统内置的控件组合起来生成一个新的控件

    b、继承系统现有的控件,然后加入新的功能

    c、自己绘制控件,继承系统的View类,通过View中的回调方法实现绘制

2、为什么使用自定义View?

  自定义View可以实现系统View满足不了的需求,根据我们开发中不同的需求去实习我们自己的View.通过自定义View我们还可以实现一些炫酷的效果提升产品体验.

3、自定义View的三个方法:

  onMeasure()、onLayout()、onDraw().

4、View要是显示出来要经历测量、布局和绘制三个步骤,分别对应三个动作:measure、layout、draw

  测量:onMeasure()  决定View的大小

  布局:onLayout()  决定View在ViewGroup中的位置

  绘制:onDraw()  决定绘制这个View

***自定义控件又分为自定义View和自定义ViewGroup,自定义View只需要重写onMeasure()和onDraw(),而自定义ViewGroup需要重新onMeasure()和onLayout().

5、Measure

  作用:测量View的宽/高

注:

    a、在某些情况下,需要多次测量(measure)才能确定View最终的宽/高.

    b、在这种情况下measure过程后得到的宽/高可能是不准确的.

    c、建议在layout过程中onLayout()去获取最终的宽/高.

measure传递尺寸(宽/高测量值)的两个类:

    ViewGroup.LayoutParams (View自身的布局参数)

    MeasureSpecs类 (父视图对子视图的测量要求)

(1)、ViewGroup.LayoutParams

自定义View

  作用:指定视图的(height)和宽度(width)等布局参数.可通过以下参数指定:

  ViewGroup的子类有其对应的ViewGroup.LayoutParams子类:

    a、ViewGroup的子类包括RelativeLayout、LinearLayout等.

    b、RelativeLayout的ViewGroup.LayoutParams的子类是RelativeLayoutParams.

    构造函数:

// View的构造函数有四种重载

    public DIY_View(Context context){

        super(context);

    }

    public DIY_View(Context context,AttributeSet attrs){

        super(context, attrs);

    }

    public DIY_View(Context context,AttributeSet attrs,int defStyleAttr ){

        super(context, attrs,defStyleAttr);

// 第三个参数:默认Style

// 默认Style:指在当前Application或Activity所用的Theme中的默认Styl

(2)、MeasureSpec

  定义:测量规格(测量View的依据)(每个MeasureSpec代表了一组宽度和高度的测量规格)

  作用:决定了一个View的大小(宽/高)(即宽测量值(widthMeasureSpec)和高测量值(heightMeasureSpec)决定了View的大小)

  分类:widthMeasureSpec(宽测量规格)、heightMeasureSpec(高测量规格)

  组成:MeasureSpec              =    mode          +        size

          (测量规格,32位的int值)      (测量模式,高2位即31、32位+具体测量大小,底30位)

Mode模式共分为三类:

自定义View

总结:(以子View为标准,横向观察)

  (1)、当子View采用具体数值(dp\px)时

      无论父容器的测量模式是什么,子View的测量模式都是EXACTLY且大小等于设置的具体数值.

  (2)、当子View采用match_parent时

      子View的测量模式与父容器的测量模式一致.若测量模式为EXACTLY,则子View的大小为父容器的剩余空间;若测量模式为AT_MOST,则子View的大小不超过父容器的剩余空间.

    (3)、当子View采用wrap_parent时

      无论父容器的测量模式是什么,子View的测量模式都是AT_MOST且大小不超过父容器的剩余空间.

(3)、Measure过程:(如果需要重新Measure应该调用RequestLayout()方法)

  measure过程根据View的类型分两种:

    a、View类型 = 单一View时:只测量自身一个View.

    b、View类型 = ViewGroup时:对ViewGroup视图中所有的子View都进行测量(即遍历去调用所有子元素的measure方法,然后各子元素再递归去执行这个流程)

单一View的measure过程:

自定义View

  应用场景:在没有现成的View,需要自己实现的时候就使用自定义View,一般继承自View,surfaceView或其他的View,不包含子View.

  过程:

       

对于每个方法的总结:

自定义View

ViewGroup的measure过程:

  应用场景:自定义ViewGroup一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自ViewGroup或各种Layout(含有子View).

  原理:通过遍历所有的子View进行子View的测量,然后将所有子View的尺寸进行合并,最终得到ViewGroup父视图的测量值.

  过程:

自定义View

对于每个方法的总结:

自定义View

(4)、onMeasure()方法:

  作用:测量当前View的在屏幕上占用的尺寸.

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        //此方法是设置View的测量值   

        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),

                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

    }```

其中```setMeasuredDimension ```方法是设置View测量的值获取View值的方法是```getDefaultSize ``` ,我们再看一下它的源码:

```java

public static int getDefaultSize(int size, int measureSpec) {

        int result = size;

        //获取测量模式

        int specMode = MeasureSpec.getMode(measureSpec);

        //获取测量尺寸

        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {

            case MeasureSpec.UNSPECIFIED:

                result = size;

                break;

            //返回的大小等于测量尺寸

            case MeasureSpec.AT_MOST:

            case MeasureSpec.EXACTLY:

                result = specSize;

                break;

        }

        return result;

    }

    从getDefaultSize方法中可用看出View的宽高都由specSize决定,可得:在View中使用wrap_content就相当于是使用match_parent,所以直接继承系统View的类的控件需要重写onMeasure.

(5)、onDraw()方法:

  作用:把已经测量好的View画在屏幕上,开发者根据自己的需要绘制不同的功能.

  onDraw方法是通过view的draw方法调用,一般有以下几步:

    a、绘制背景  background.draw(canvas)

    b、绘制自己调用onDraw方法

    c、绘制children调用dispatchDrae方法

    d、绘制装饰 onDrawScrollBars方法

public void draw(Canvas canvas) {

        if (ViewDebug.TRACE_HIERARCHY) {

            ViewDebug.trace(this, ViewDebug.HierarchyTraceType.DRAW);

        }

        final int privateFlags = mPrivateFlags;

        final boolean dirtyOpaque = (privateFlags & DIRTY_MASK) == DIRTY_OPAQUE &&

                (mAttachInfo == null || !mAttachInfo.mIgnoreDirtyState);

        mPrivateFlags = (privateFlags & ~DIRTY_MASK) | DRAWN;

        // 第一步 绘制背景

        int saveCount;

        if (!dirtyOpaque) {

            final Drawable background = mBGDrawable;

            if (background != null) {

                final int scrollX = mScrollX;

                final int scrollY = mScrollY;

                if (mBackgroundSizeChanged) {

                    background.setBounds(0, 0, mRight - mLeft, mBottom - mTop);

                    mBackgroundSizeChanged = false;

                }

                if ((scrollX | scrollY) == 0) {

                    background.draw(canvas);

                } else {

                    canvas.translate(scrollX, scrollY);

                    background.draw(canvas);

                    canvas.translate(-scrollX, -scrollY);

                }

            }

        }

        final int viewFlags = mViewFlags;

        boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;

        boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;

        if (!verticalEdges && !horizontalEdges) {

            //第二步  绘制自己

            if (!dirtyOpaque) onDraw(canvas);

            //第三步 绘制childern

            dispatchDraw(canvas);

            //第四步 绘制装饰

            onDrawScrollBars(canvas);

            //以下省略...

            return;

        }

    }

相关文章

网友评论

      本文标题:自定义View

      本文链接:https://www.haomeiwen.com/subject/cnalzqtx.html