精华内容
下载资源
问答
  • Android自定义view绘制顺序漫谈

    千次阅读 2018-04-28 12:02:11
      今天就跟大家说说自定义view里面的绘制顺序问题,因为有时候避免不了在同一个地方绘制不同的view,那么就避免不了遮盖问题,这个时候就就必须考虑绘制顺序的问题了。   说到绘制顺序就不得不提onDraw和...

      今天就跟大家说说自定义view里面的绘制顺序问题,因为有时候避免不了在同一个地方绘制不同的view,那么就避免不了遮盖问题,这个时候就就必须考虑绘制顺序的问题了。
      说到绘制顺序就不得不提onDraw和dispatchDraw这两个方法,下面我们一一来看这两个方法。

    onDraw

    之前我们自定义view一般都继承了View这个类,其实Android里面所有的控件也都继承自这个类,无论是一个view还是viewGroup了,最终都是继承自view。好,如果我们自定义view直接继承自View类,那么我们重写了onDraw在里面做一些自己的绘制,我们也会重写super.onDraw(canvas),但是点击进去看会发现是空实现,只是用注释告诉我们“Implement this to do your drawing”,所以如果是继承自view,那么其实这个super是完全可以不写的,因为父类是空实现啊,但是如果是继承一个现有的view呢,那就不用点击过去看,肯定不是空实现,因为现有控件的绘制操作也是在onDraw里面写的啊,所以这个时候自己把代码写在super.onDraw(canvas)前面还是后面是有影响的

    • 把自己绘制的代码写在super.onDraw的后面,由于绘制代码会在原有内容绘制结束之后执行,所以绘制内容就会盖住控件原来的内容(这种应用场景就比较多,好比在原来的基础上面加一些东西)
    • 把自己绘制的代码写在super.onDraw的前面,由于绘制代码会在原有内容绘制之前执行,所以绘制的内容会被控件的super.onDraw的内容覆盖(这种应用场景比较少,但是也会有,例如给继承自textview的文本绘制一个背景色啦,当然绘制背景色有更简单的方法)

    dispatchDraw

    上面的onDraw是针对view自身的绘制,但是如果是把一个view放在viewGroup里面呢?那么父view和子view的绘制顺序又要怎么把握呢?就需要在这个dispatchDraw方法上面做文章了,先来看看这个方法的注释“Called by draw to draw the child views. This may be overridden by derived classes to gain control just before its children are drawn (but after its own view has been drawn).”翻译一下就是这个方法被draw方法调度然后绘制子view,这个方法可以被重写来获得控制权,以能够在它的子view们被绘制之前来做一些事情,但是此时它自身已经被绘制完毕了。好比有这么个例子,我们自定义了一个view继承自linearlayout,是的是一个viewgroup,然后我们想给linearlayout添加一些圆圈的幻影,然后我们将这个linearlayout写在xml中,如果它没有任何子view,那么这个幻影能够正常的展示出来,但是如果我们给这个linearlayout添加了子view,就会发现原来有的幻影现在没有了,这是因为幻影被子view遮盖了,那么要怎么处理呢?其实也很简单,就是在绘制完子view之后我们再绘制linearlayout的幻影就好了,这样就解决问题了,而上面的注释也说了,dispatchDraw方法就是用来调度绘制子view的方法,那么我们把绘制幻影的代码写在super.dispatchDraw(canvas);之后就好了,就这么简单

    关于绘制的顺序概述

    1. 背景background的绘制,这个是发生在一个drawBackground(Canvas canvas)的private的私有方法里面,所以我们无法重写这个方法,只能通过现有Android提供的API去设置它
    2. onDraw主体绘制,如果是对于父布局而言,也是先调用onDraw绘制自己,然后调用dispatchDraw去绘制子view
    3. dispatchDraw绘制子view
    4. 滑动边缘渐变和滑动条
    5. 前景(foreGround)(前景的支持是从Android6.0开始的,之前的只是支持framelayout),但是我们貌似一般都不用。这个是被放在一个onDrawForeground方法里面的, 该方法是可以被重写的,做一些自己的设置。当然也可以利用API自带的方法通过xml中的android:scrollbarXXX系列属性设置或者在java代码中调用对应的set方法来进行设置。如果重写了这个那么在super.onDrawForeground()方法的前后写代码则可以控制绘制内容和和滑动边缘以及前景的遮盖关系

    关于绘制方法的调度概述

    为什么上面的方法会按照那样的如下的顺序:背景—onDraw—dispatchDraw—滑动边缘渐变和滑动条—foreGround的顺序来绘制呢?这是因为有一个方法在进行总调度,它就是draw方法,所以如果想要在所有的绘制之前或者所有绘制方法都调用完成后做一些事情,那么就可以重写这个draw方法了,所以如果将自己的绘制代码写在了super.draw(canvas);前面,那么相当于自己绘制的代码会被所有后面的绘制覆盖,如果写在了super.draw(canvas);后面,那么相当于自己的绘制代码会覆盖所有后面的绘制,其实将代码写在super.draw(canvas)后面相当于将代码写在了super.onDrawForeground后面了,因为执行到onDrawForeground就已经执行到绘制的结束时候了

    关于绘制代码书写位置的概述

    • 出于效率考虑,viewgroup默认会绕过draw,onDraw方法,换而直接执行dispatchdraw,以此来简化绘制流程,所以如果你自定义了某个 ViewGroup 的子类(比如 LinearLayout)并且需要在它的除 dispatchDraw() 以外的任何一个绘制方法内绘制内容,你可能会需要调用 View.setWillNotDraw(false) 这行代码来切换到完整的绘制流程
    • 有时候代码写在ondraw里面可以,也可以写在dispatchDraw,但是推荐写在onDraw里面,因为对于这个方法android有优化,可以在不需要重绘时候自动跳过onDraw避免重复执行

      以上就是关于绘制顺序的全部内容,有问题欢迎批评指正,以上内容参考了扔物线大神的作品。

    展开全文
  • 继承View时,无论在super.onDraw(canvas)方法上面还是下面自定义绘制代码时,效果都是只会绘制你的自定义绘制代码。因为View中的onDraw方法是空实现。 public class MyView extends View { ... protected void ...

    一.继承View的绘制顺序

    继承View时,无论在super.onDraw(canvas)方法上面还是下面自定义绘制代码时,效果都是只会绘制你的自定义绘制代码。因为View中的onDraw方法是空实现。

    public class MyView extends View {
        ...
        protected void onDraw(Canvas canvas) {
        	//在super.onDraw(canvas)方法上面自定义绘制代码
            super.onDraw(canvas);
            //在下面自定义绘制代码
        }
        
        ...
    }
    

    二.继承其他已有的控件

    继承其他已有的控件(如TextView,EditView等),目的是对它们原有的功能进行扩展,加入自己想要的功能。但这些控件都重写了父类的onDraw方法,因此需要留意绘制顺序的问题。

    如果把绘制代码写在 super.onDraw() 的上面,由于绘制代码会执行在原有内容的绘制之前,所以绘制的内容会被控件的原内容盖住。简单来说就是先绘制的会被后面的盖住。

    public class MyTextView extends TextView {
        ...
    
        @Override
        protected void onDraw(Canvas canvas) {
        	//在onDraw方法上面绘制背景
            paint.setStyle(Paint.Style.FILL);
            paint.setColor(Color.YELLOW);
            canvas.drawRect(getLeft(), getTop(), getRight(), getBottom(), paint);
            super.onDraw(canvas);
        }
    }
    

    效果图如下:
    在这里插入图片描述
    如果将onDraw方法上面几行自定义绘制代码移到它的下面,就会出现下面的效果:
    在这里插入图片描述
    因此在继承其他控件时一定要注意上面的绘制顺序,以免出现上面的错误。

    三.主体和子View的绘制顺序

    在继承其他的布局,实现自定义布局时,如果按照上面的步骤给这个自定义布局中添加自定义绘制代码,并且在xml文件中给此布局添加子VIew,你会发现你的自定义绘制代码不起作用了。这时就要提到dispatchDraw(),它是绘制子 View 的方法。
    它和onDraw()的绘制顺序如下:
    在这里插入图片描述
    如果你想让自己的自定义图形在最上面显示,如下面的效果,就需要在重写父类dispatchDraw()方法,将自定义绘制代码移到super.dispatchDraw()方法下面.
    而如果在super.dispatchDraw()方法上面绘制,就会又出现自定义绘制图形被盖住的情况.

    效果图

    public class SpottedLinearLayout extends LinearLayout {
        ...
        protected void dispatchDraw(Canvas canvas) {
           super.dispatchDraw(canvas);
           ... // 自定义绘制代码
        }
    }
    

    参考文章:
    自定义View的绘制顺序

    展开全文
  • 自定义view

    2019-09-11 10:25:13
    自定义view优秀讲解 3.1.2 自定义 View 布局阶段 在 View 的布局阶段会执行两个方法(在布局阶段,View 的父 View 会通过调用 View 的 layout() 方法将 View 的实际尺寸(父 View 根据 View 的期望尺寸确定的 View ...

    自定义view优秀讲解

    3.1.2 自定义 View 布局阶段
    在 View 的布局阶段会执行两个方法(在布局阶段,View 的父 View 会通过调用 View 的 layout() 方法将 View 的实际尺寸(父 View 根据 View 的期望尺寸确定的 View 的实际尺寸)传给 View,View 需要在 layout() 方法中将自己的实际尺寸保存(通过调用 View 的 setFrame() 方法保存,在 setFrame() 方法中,又会通过调用 onSizeChanged() 方法告知开发者 View 的尺寸修改了)以便在绘制和触摸反馈阶段使用。保存 View 的实际尺寸之后,View 的 layout() 方法又会调用 View 的 onLayout() 方法,不过 View 的 onLayout() 方法是一个空实现,因为它没有子 View):

    layout() onLayout()

    layout() : 保存 View 的实际尺寸。调用 setFrame() 方法保存 View 的实际尺寸,调用
    onSizeChanged() 通知开发者 View 的尺寸更改了,并最终会调用 onLayout() 方法让子 View 布局(如果有子
    View 的话。因为自定义 View 中没有子 View,所以自定义 View 的 onLayout() 方法是一个空实现);
    onLayout() : 空实现,什么也不做,因为它没有子 View。如果是 ViewGroup 的话,在 onLayout()
    方法中需要调用子 View 的 layout() 方法,将子 View 的实际尺寸传给它们,让子 View
    保存自己的实际尺寸。因此,在自定义 View 中,不需重写此方法,在自定义 ViewGroup 中,需重写此方法。 注意: layout()
    & onLayout() 并不是「调度」与「实际做事」的关系,layout() 和 onLayout() 均做事,只不过职责不同。

    3.1.3 自定义 View 绘制阶段
    在 View 的绘制阶段会执行一个方法——draw(),draw() 是绘制阶段的总调度方法,在其中会调用绘制背景的方法 drawBackground()、绘制主体的方法 onDraw()、绘制子 View 的方法 dispatchDraw() 和 绘制前景的方法 onDrawForeground()。
    draw() : 绘制阶段的总调度方法,在其中会调用绘制背景的方法 drawBackground()、绘制主体的方法
    onDraw()、绘制子 View 的方法 dispatchDraw() 和 绘制前景的方法 onDrawForeground();
    drawBackground() : 绘制背景的方法,不能重写,只能通过 xml 布局文件或者 setBackground()
    来设置或修改背景;
    onDraw() : 绘制 View 主体内容的方法,通常情况下,在自定义 View 的时候,只用实现该方法即可;
    dispatchDraw() : 绘制子 View 的方法。同 onLayout() 方法一样,在自定义 View
    中它是空实现,什么也不做。但在自定义 ViewGroup 中,它会调用 ViewGroup.drawChild() 方法,在
    ViewGroup.drawChild() 方法中又会调用每一个子 View 的 View.draw() 让子 View 进行自我绘制;
    onDrawForeground() : 绘制 View 前景的方法,也就是说,想要在主体内容之上绘制东西的时候就可以在该方法中实现。
    注意: Android
    里面的绘制都是按顺序的,先绘制的内容会被后绘制的盖住。如,你在重叠的位置「先画圆再画方」和「先画方再画圆」所呈现出来的结果是不同的。

    view和ViewGroup的测量绘制过程我大致总结如下:
    measure()
    会被父view调用(应该是在onMeasure方法中调用),并传进父view对view的尺寸要求,完成一些测量前的优化和前置工作。measure接着会调用onMeasure方法,进行正式的测量,并最后通过setMeasureDimension告知父view自己的期望尺寸。(在ViewGroup中就是在onMeasure中调用子view的measure方法的)
    layout
    保存view的实际尺寸,通过调用setFrame方法。接着通过onSizeChange方法通知开发者view的尺寸发生了变化,最后调用onLayout方法(在onLayout中调用子view的layout方法)让子view进行布局(如果有子view的话)。
    draw
    依次执行绘制背景(不可重写,只能通过setBackground或xml进行设置)、绘制主体内容(onDraw)、绘制子view的方法(dispatchDraw()),会调ViewGroup.drawChild(),drawChild又会调用view的draw()方法依次类推、绘制前景。下面是重写draw()相关方法顺序不同的效果。
    在这里插入图片描述
    通过自定义ViewGroup和view来检验以上方法的执行顺序得到以下结果(以下结果均是在各方法体里的super代码前后打印log所得):

    1. VG-onMeasure:前
    2. V-onMeasure:前
    3. V-onMeasure:后
    4. VG-onMeasure:后
    5. VG-onLayout:前
    6. V-layout:前
    7. V-onLayout:前
    8. V-onLayout:后
    9. V-layout:后
    10. VG-onLayout:后
    11. VG-dispatchDraw:前
    12. V-draw:前
    13. V-onDraw:前
    14. V-onDraw:后
    15. V-dispatchDraw:前
    16. V-dispatchDraw:后
    17. V-draw:后
    18. VG-dispatchDraw:后

    其中VG代表ViewGroup,V代表View;而且measure方法View和ViewGroup中均不能重写;layout方法ViewGroup中写成了final,故不能重写,View中可以;ViewGroup一般情况下不会执行draw方法。

    展开全文
  • 自定义View 为什么要自定义View? 主要是Andorid系统内置的View 无法实现我们的 需求,我们需要针对我们的业务需求定制我们想要的 View.自定义View 我们大部分时候只需重写两个函数: onMeasure(),onDraw(). ...

    自定义View

    为什么要自定义View? 主要是Andorid系统内置的View 无法实现我们的 需求,我们需要针对我们的业务需求定制我们想要的 View.自定义View 我们大部分时候只需重写两个函数: onMeasure(),onDraw(). onMeasure()负责对当前View 的尺寸进行测量,onDraw负责把当前这个View绘制出来,当然了,还需要写构造函数。

    public Views(Context context) {
        super(context);
    }
    
    public Views(Context context,  AttributeSet attrs) {
        super(context, attrs);
    }
    

    onMeasure 概念

    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    

    主要用来测量布局,其参数 widthMeasureSpecheightMeasureSpec 包含宽和高的信息和测量模式。

    为什么一个 数里面能放两个信息呢?我们知道,我们在设置宽高时有三个选择,wrap_content,match_partent以及 指定固定尺寸,而测量模式也有三种:UNSPECIFIED,EXACTLY,AT_MOST,但它们并不是一一对应的关系。

    那么google 是如何做到把一个 int同时放测量模式 和尺寸信息呢?我们知道 int型数据占用 32个bit,而google实现的是,将 int数据的前面2个 bit用于区分不同的布局模式,后面 30个bit 存放的是尺寸的数据。

    如何提取测量模式与尺寸呢?

    int widthMode = MeasureSpec.getMode(widthMeasureSpec);
    int widthSize = MeasureSpec.getSize(widthMeasureSpec);
    
    测量模式表示意思
    UNSPECIFIED父容器没有对当前View有任何限制,当前View可以任意取尺寸
    EXACTLY当前的尺寸就是当前View应该取的尺寸
    AT_MOST当前尺寸是当前View能取的最大尺寸

    上面的测量模式和我们布局时的 warp_content,match_parent以及写成固定的尺寸有什么对应关系呢?

    match_parent--->EXACTLY。怎么理解呢?match_parent就是要利用父View给我们提供的所有剩余空间,而父View剩余空间是确定的,也就是这个测量模式的整数里面存放的尺寸。
    
    warp_parent---> AT_MOST
    我们想要将大小设置为包裹我们的View内容,那么尺寸大小就是父View给我作为参考的尺寸,至于不超过这个尺寸就可以啦。具体尺寸就根据我们的需求去设定。
    
    固定尺寸(100dp)--->EXACTLY.用户自己制定了尺寸大小,我们就不用再去干涉了,当然是以指定的大小为主。
    

    重写 onMeasure 函数Demo

    我们要实现的效果是一个小正方形。

    private int getMySize(int defaultSize,int measureSpec){
        int mySize=defaultSize;
        //取测量模式
        int mode=MeasureSpec.getMode(measureSpec);
        //取测量长度
        int size=MeasureSpec.getSize(measureSpec);
    
        switch (mode){
            //如果没有指定大小,就设置为默认大小
            case MeasureSpec.UNSPECIFIED:
                mySize=defaultSize;
                break;
                //如果测量模式是最大取值size
                //我们将大小取最大值,你也可以取其他值
            case MeasureSpec.AT_MOST:
                mySize=size;
                break;
    
                //如果是固定的大小,那就不要去改变它
            case MeasureSpec.EXACTLY:
                mySize=size;
                break;
        }
        return mySize;
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int width=getMySize(100,widthMeasureSpec);
        int heigth=getMySize(100,heightMeasureSpec);
        if (width<heigth){
            heigth=width;
        }else{
            width=heigth;
        }
        setMeasuredDimension(width,heigth);
    }
    

    xml

    <com.petterp.studybook.View.Views
        android:layout_width="match_parent"
        android:layout_height="100dp"
        android:background="#000"/>
    

    这样的效果就是一个正方形了。

    重写onDraw

    上面我们通过重写 onMeasure 实现了布局的测量与设定,接下来就是绘制了。绘制的话 我们直接在画板 Canvas 对象上绘制就好。

    我们以一个简单Demo来实现效果。

    @Override
    protected void onDraw(Canvas canvas) {
        //调用父View的onDraw函数,因为View这个类帮我们实现了一些
        //基本的绘制功能,拨入绘制背景颜色,背景图片等。
        super.onDraw(canvas);
        //也可以是 getMeasuredHieght()/2,因为这个例子中我们已将宽高设置相等了
        int r=getMeasuredWidth()/2;
        //圆心的横坐标为当前View的View左边起始位置+半径
        int centerx=getLeft()+r;
        //圆心的纵坐标表为当前的View的顶部起始位置+半径
        int centery =getTop()+r;
    
        //设置画笔
        @SuppressLint("DrawAllocation") Paint paint=new Paint();
        //设置画笔颜色
        paint.setColor(Color.GREEN);
        //开始绘制
        canvas.drawCircle(centerx,centery,r,paint);
    }
    

    效果出来就是一个小圆

    在这里插入图片描述

    自定义布局属性

    如果有些属性我们希望由用户指定,只有当用户不指定的时候采用我们硬编码的值,比如上面的默认尺寸,我们想要由用户自己在布局文件里面指定该怎么做呢?所以这个时候就需要我们自定属性,让用户用我们定义的属性。

    过程

    首先我们需要在 res/values/styles.xml 文件(如果没有就需要新建),里面声明一个我们自定义的属性:

    <!--name为声明的“属性集合”名,可以随便取,但是最好是设置为根我们的view一样的名称-->
    <declare-styleable name="Views">
        <!--声明我们的属性,名称为 default_size,取值类型为尺寸(dp,dx等)-->
        <attr name="default_size" format="dimension"/>
    </declare-styleable>
    

    然后在我们自定义View里面吧我们自定义的属性值取出来,在构造函数中,有个AttributeSet的属性,我们需要用它来帮我们把布局里面的属性取出来。

    private int defaultSize;
    public Views(Context context,  AttributeSet attrs) {
        super(context, attrs);
        //第二个参数就是我们在styles.xml文件中的<declare-styleable>标签
        //即属性集合的标签,在R 文件中名称为 R,styleable+name
        TypedArray a=context.obtainStyledAttributes(attrs, R.styleable.Views);
    
        //第一个参数为属性几个里面的属性,R文件名称:R。styleable+属性集合名称+下划线+属性名称
        //第二个参数为,如果没有设置这个属性,则设置的默认的值
        defaultSize=a.getDimensionPixelSize(R.styleable.Views_default_size,100);
    
        //最后记得将TypedArray对象回收
        a.recycle();
    }
    

    最后附上完整的代码

    package com.petterp.studybook.View;
    
    import android.annotation.SuppressLint;
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Canvas;
    import android.graphics.Color;
    import android.graphics.Paint;
    import android.os.Build;
    import android.support.annotation.RequiresApi;
    import android.util.AttributeSet;
    import android.view.View;
    
    import com.petterp.studybook.R;
    
    /**
     * @author Petterp on 2019/6/27
     * Summary:
     * 邮箱:1509492795@qq.com
     */
    public class Views extends View {
        public Views(Context context) {
            super(context);
        }
        private int defaultSize;
        public Views(Context context,  AttributeSet attrs) {
            super(context, attrs);
            //第二个参数就是我们在styles.xml文件中的<declare-styleable>标签
            //即属性集合的标签,在R 文件中名称为 R,styleable+name
            TypedArray a=context.obtainStyledAttributes(attrs, R.styleable.Views);
    
            //第一个参数为属性几个里面的属性,R文件名称:R。styleable+属性集合名称+下划线+属性名称
            //第二个参数为,如果没有设置这个属性,则设置的默认的值
            defaultSize=a.getDimensionPixelSize(R.styleable.Views_default_size,100);
    
            //最后记得将TypedArray对象回收
            a.recycle();
        }
    
        private int getMySize(int defaultSize,int measureSpec){
            int mySize=defaultSize;
            //取测量模式
            int mode=MeasureSpec.getMode(measureSpec);
            //取测量长度
            int size=MeasureSpec.getSize(measureSpec);
    
            switch (mode){
                //如果没有指定大小,就设置为默认大小
                case MeasureSpec.UNSPECIFIED:
                    mySize=defaultSize;
                    break;
                    //如果测量模式是最大取值size
                    //我们将大小取最大值,你也可以取其他值
                case MeasureSpec.AT_MOST:
                    mySize=size;
                    break;
    
                    //如果是固定的大小,那就不要去改变它
                case MeasureSpec.EXACTLY:
                    mySize=size;
                    break;
            }
            return mySize;
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int width=getMySize(defaultSize,widthMeasureSpec);
            int heigth=getMySize(defaultSize,heightMeasureSpec);
            if (width<heigth){
                heigth=width;
            }else{
                width=heigth;
            }
            setMeasuredDimension(width,heigth);
        }
    
        @Override
        protected void onDraw(Canvas canvas) {
            //调用父View的onDraw函数,因为View这个类帮我们实现了一些
            //基本的绘制功能,拨入绘制背景颜色,背景图片等。
            super.onDraw(canvas);
            //也可以是 getMeasuredHieght()/2,因为这个例子中我们已将宽高设置相等了
            int r=getMeasuredWidth()/2;
            //圆心的横坐标为当前View的View左边起始位置+半径
            int centerx=getLeft()+r;
            //圆心的纵坐标表为当前的View的顶部起始位置+半径
            int centery =getTop()+r;
    
            //设置画笔
            @SuppressLint("DrawAllocation") Paint paint=new Paint();
            //设置画笔颜色
            paint.setColor(Color.GREEN);
            //开始绘制
            canvas.drawCircle(centerx,centery,r,paint);
        }
    }
    

    自定义ViewGroup

    自定义View的过程简单,其实也就那几步,可自定义ViewGroup 可就比较麻烦了,因为不仅要管好自己,还要兼顾子View。因为 ViewGroup是一个容器,他装纳 子视图 并且负责把 子视图 放入指定的位置。

    一般自定义viewgroup我们从这几方面去思考:

    1. 首先,我们需要知道各个子 view 的大小,只有知道子 view 的大小,我们才知道当前的View Group该设置为多大去容纳它们。
    2. 根据子 View 的大小,以及我们的 ViewGroup 要实现的功能,决定出ViewGroup的大小。
    3. ViewGroup 和 子View 的大小算出来之后,接下来就需要去摆放,具体的排放规则,一般按照我们的需求去定制。
    4. 知道了怎么摆放之后,还需要决定每个子view对号入座,把他们放进它们该放的地方。

    实例Demo

    我们仿照LinearLayout的垂直布局,将子view按从上到下垂直顺序一个接一个摆放。

    public class MyViewGroup extends ViewGroup {
        public MyViewGroup(Context context) {
            super(context);
        }
    
        public MyViewGroup(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
    
        /**
         * 切记,这里都是相对于父view
         * @param changed
         * @param l //相对于父view left距离
         * @param t //相对于父view top距离
         * @param r
         * @param b
         */
        @Override
        protected void onLayout(boolean changed, int l, int t, int r, int b) {
            int count = getChildCount();
            //top重置为0
            int curHeight = 0;
            for (int i = 0; i < count; i++) {
                View child = getChildAt(i);
                int height = child.getMeasuredHeight();
                int width = child.getMeasuredWidth();
                //真正绘制的方法,其内部又有onlayout的调用,因为子view也是一个viewgroup
                child.layout(l, curHeight, l + width, curHeight + height);
                curHeight += height;
            }
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            //将所有的子view进行测量,这会触发每个子view的 onMeasure函数
            //注意要与 measureChild 区分,measureChild 是对单个view进行测量
            measureChildren(widthMeasureSpec,heightMeasureSpec);
    
            //测量模式
            int widthMode=MeasureSpec.getMode(widthMeasureSpec);
            //测量大小
            int widthSize=MeasureSpec.getSize(widthMeasureSpec);
            int heightMode=MeasureSpec.getMode(heightMeasureSpec);
            int heightSize=MeasureSpec.getSize(heightMeasureSpec);
    
            //获取子元素数量
            int childCount=getChildCount();
    
            //如果没有子view,当前viewGroup没有存在的意义,不用占用空间
            if (childCount==0){
                setMeasuredDimension(0,0);
            }else{
                //如果宽高都是包裹内容
                if (widthMode==MeasureSpec.AT_MOST&&heightMode==MeasureSpec.AT_MOST){
                    //我们将高度设置为所有子view的高度相加,宽度设置为子view中最大的宽度
                    int height=getTotleHeight();
                    int width=getMaxChildWidth();
                    setMeasuredDimension(width,height);
                }else if (heightMode==MeasureSpec.AT_MOST){
                    //如果只有高度是warp
                    //宽度设置为ViewGroup自己的测量宽度,高度设置为所有view的高度总和
                    setMeasuredDimension(widthSize,getTotleHeight());
                }else if (widthMode==MeasureSpec.AT_MOST){
                    //宽度设置为子View中宽度最大的值,高度设置为 ViewGroup自己测量的值
                    setMeasuredDimension(getMaxChildWidth(),heightSize);
                }
            }
        }
    
        /**
         * 获取子view中宽度最大的值
         * @return
         */
        private int getMaxChildWidth(){
            int childCount=getChildCount();
            int maxWidth=0;
            for (int i=0;i<childCount;i++){
                //获取相应的子view
                View  childView =getChildAt(i);
                //对比出最宽的子view -> 因为是垂直布局
                if (childView.getMeasuredWidth()>maxWidth){
                    maxWidth=childView.getMeasuredWidth();
                }
            }
            return maxWidth;
        }
    
        /**
         * 将子view的高度相加
         * @return
         */
        private int getTotleHeight(){
            int childCount=getChildCount();
            int height=0;
            for (int i=0;i<childCount;i++){
                View childView=getChildAt(i);
                height+=childView.getMeasuredHeight();
            }
            return height;
        }
    }
    
    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        xmlns:custom="http://schemas.android.com/apk/res-auto"
        android:orientation="vertical"
        android:layout_height="match_parent"
        tools:context=".View.ViewActivity">
        <com.petterp.studybook.View.MyViewGroup
            android:layout_width="match_parent"
            android:layout_height="match_parent">
            <Button
                android:text="1"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
            <Button
                android:text="2"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
            <Button
                android:text="3"
                android:layout_width="match_parent"
                android:layout_height="wrap_content" />
        </com.petterp.studybook.View.MyViewGroup>
    </LinearLayout>
    

    在这里插入图片描述
    更多Android开发知识请访问—— Android开发日常笔记,欢迎Star,你的小小点赞,是对我的莫大鼓励。

    参照博客:非常感谢https://www.jianshu.com/p/c84693096e41

    展开全文
  • 以一个小的案例打印记录下自定义View中的各个方法的执行顺序。 public class ProcessLinearLayout extends LinearLayout { public ProcessLinearLayout(Context context) { this(context,null); } public ...
  • Android系统中要自定义view,首先需要了解Android的view加载机制。主要有三个方法: 1、onMeasure() //计算出view自身大小 2、onLayout() //仅在ViewGroup中,用来为子view指定位置(left,top) 3、onDraw() //...
  • 记住一个原则:android自定义view绘制时,先绘制的内容会被后绘制的覆盖。 view的绘制顺序是: 背景–>view 主体–>子view–>滑动边缘渐变和滑动条–>前景见下图:注: draw()方法是总调度方法,所以如果把绘制...
  • 自定义View中测量流程

    2021-01-03 20:26:17
    自定义View View的测量是从子View开始的,具体代码顺序是:父容器的onMeasure方法会遍历到每一个子childView –>然后调用measureChild()方法 protected void measureChild(View child, int parentWidthMeasureSpec,...
  • 1、通过xib自定义view时,view中函数的大致执行过程 自定义view时如果使用xib的话在init相关函数中加载xib,xib加载起来之后自动调用awakeFromNib函数,此时可以在awakeFromNib函数中完成界面的初始化工作,在...
  • 《HenCoder Android 开发进阶:自定义 View 1-5 绘制顺序》 的练习项目
  • 自定义View,有这一篇就够了

    万次阅读 多人点赞 2016-06-03 15:08:21
    我的简书同步发布:自定义View,有这一篇就够了 为了扫除学习中的盲点,尽可能多的覆盖Android知识的边边角角,决定对自定义View做一个稍微全面一点的使用方法总结,在内容并没有什么独特,其他大神们的博客上面基本...
  • 为什么要自定义Viewandroid提供了很多控件供我们使用 但有些功能是系统所提供的实现不 了的 这时候我们就需要自定义一个View来实现我们所需要的效果. 在Android中所有的控件都直接或间接的继承自View,分View和...
  • 自定义View和ViewGroup

    2016-07-07 18:03:18
    转自huachao1001,原文地址 ... 1.自定义View ...首先我们要明白,为什么要自定义View?主要是Android系统内置的View无法实现我们的需求,我们需要针对我们的...自定义View我们大部分时候只需重写两个函数:onMeasur
  • Android系统中要自定义view,首先需要了解Android的view加载机制。主要有三个方法: 1、onMeasure() //计算出view自身大小 2、onLayout() //仅在ViewGroup中,用来为子view指定位置(left,top) 3、onDraw() //view...
  • 自定义View和自定义ViewGroup一步到位

    千次阅读 2018-09-29 18:15:30
    1.自定义View 首先我们要明白,为什么要自定义View?主要是Android系统内置的View无法实现我们的需求,我们需要针对我们的业务需求定制我们想要的View。自定义View我们大部分时候只需重写两个函数:onMeasure()、...
  • Android自定义View-自定义属性

    千次阅读 2017-09-26 11:18:48
    自定义View的自定义属性,为了能让自定义View在xml文件中编写时可以设置自己特有的属性。用代码写界面不需要自定义属性。
  • 自定义View系列教程02--onMeasure源码详尽分析

    万次阅读 多人点赞 2016-05-12 15:09:59
    大家知道,自定义View有三个重要的步骤:measure,layout,draw。而measure处于该链条的首端,占据着极其重要的地位;然而对于measure的理解却不是那么容易,许多问题都是一知半解,比如:为什么父View影响到了子...
  • 自定义View系列文章可以说是一大福利,如果更新的速度再快一些,就完美了,哈哈。最近有时间将其自定义View系列文章学习了一遍,收获颇多,通过思维导图的形式总结出来,备忘。 1、绘制基础 2、Paint详解 3...
  • Android自定义View之View的位置参数

    千次阅读 2017-02-21 11:10:29
    最近在学习自定义View,总是被View的显示的位置搞的一头雾水。对于一个View的位置,我比较迷惑: View在显示在哪个位置? View的宽和高的定义? 什么是ViewView是Android中所有控件的基类,不管是Button或者TextView,...
  • 参考转自郭霖博客带你一步步深入了解View系列 Android LayoutInflater原理分析 相信接触Android久一点的朋友对于LayoutInflater一定不会陌生,都会知道它主要是用于加载布局的。而刚接触Android的...
  • Android自定义View(八) – 硬件加速 前面学习的内容: Android自定义View(一) – 初识 Android自定义View(二) – Paint详解 ...Android自定义View(五) – 绘制顺序 Android自定义View(六) – 属性动画
  • Android自定义View构造函数详解

    万次阅读 多人点赞 2015-11-04 21:19:30
    生成Custom View自定义属性 在Custom View的构造函数中获取自定义属性 设置自定义属性值 在布局xml文件中为属性赋值 在style中为属性赋值 通过RstyledefStyle为属性赋值 在Custom View所在的Activity的Theme中指定...
  • 自定义View系列教程06--详解View的Touch事件处理

    万次阅读 多人点赞 2016-06-06 07:23:37
    在之前的几篇文章中结合Andorid源码还有示例分析完了自定义View的三个阶段:measure,layout,draw。 在自定义View的过程中我们还经常需要处理View的Touch事件,这就涉及到了大伙常说的Touch事件的分发。其实,这一...
  • 浅谈Android自定义View

    2016-05-26 21:17:56
    0. 前言本文将对自定义View的原理和方法进行简要讲解,通过此文,你将学到: 安卓的View架构 View的绘图机制 自定义View的方法步骤 1. View控件的架构1.1 View和ViewGroupAndroid中,控件大致可以分为两大类: View...
  • 自定义view增加动画效果

    千次阅读 2017-05-31 10:51:03
    通过这一周的学习总算对自定义view比较了解了。所以也想和大家分享一下自定义view的学习方法和我学习的一个过程。还是来看一下我们每篇比谈的我们的自定义view的大纲 1.自定义view单纯的用画笔绘制view(死view) ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 106,395
精华内容 42,558
关键字:

自定义view的顺序