Android自定义view之View的测量过程全解析

2023-10-30

Android 应用层开发中绕不开自定义 View 这个话题,虽然现在 Github 上有形形色色的开源库供大家使用,

但是作为一名开发者而言,虽然不提倡重复造轮子,但是轮子都是造出来的。碰到一些新鲜的 UI 效果时,

如果现有的控件无法完成任务,那么我们就应该想到要自定义一个 View 了。 

我们知道,在 Android 中 View 绘制流程有测量、布局、绘制三个步骤,它们分别对应 3 个 API :onMeasure()、onLayout()、onDraw()。 

- 测量 onMeasure() :测量View的尺寸,决定View的大小

- 布局 onLayout() :通过设置l,t,r,b确定view在父容器中的位置

- 绘制 onDraw():通过canvas绘制我们想要展示的内容

没有办法说这三个阶段,哪个阶段最重要,只是相对而言,测量阶段对于开发者而言难度相对其它两个要大,处理的细节也要多得多,

自定义一个 View,正确的测量是第一步,正因为如此今天我将从源码的角度来学习View的测量过程.

View在本质上是一个Rect矩形的区域.

在Android中View的测量是从View树的根节点开始的,一步一步的往下测量而成的.

那么,首先我们来了解下View树的结构:

我们在Activity中一般通过setContentView()方法来设置我们的View视图:

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

在setContentView()方法中,我们调用了getWindow().setContentView()方法,而这里的

getWindow()就是PhoneWindow,因此我们转到PhoneWindow的setContentView()方法:

public void setContentView(int layoutResID) {
          //如果顶层容器FrameLayout为空的话,需要从xml加载顶层容器
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //将我们通过setContentView设置的布局资源加载到顶层容器mContentParent中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }

在PhoneWindow的setContentView()中会调用installDecor()方法(部分代码省略):

 private void installDecor() {
        mForceDecorInstall = false;
        if (mDecor == null) {
            //生成DecorView
            mDecor = generateDecor(-1);
        } else {
            //将DecorView关联到PhoneWindow上
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            //同时生成Activity的setContentView方法需要加载的View的顶层容器,并添加到DecorView中去
            mContentParent = generateLayout(mDecor);
            }
        }

分别调用generateDecor()方法生成DecorView:

protected DecorView generateDecor(int featureId) {
        //生成顶层的DecorView
        return new DecorView(context, featureId, this, getAttributes());
    }

同时我们会看到在installDecor()方法中,也会通过generateLayout方法生成mContentParent对象:

 protected ViewGroup generateLayout(DecorView decor) {
        
        int layoutResource;
        
        // Embedded, so no decoration is needed.
        layoutResource = R.layout.screen_simple;
        //在该方法中最终将mContentParent顶层view添加到DecorView中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        //mContentParent顶层容器View的布局id为com.android.internal.R.id.content
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        return contentParent;
    }

以上的流程中最终会将资源id为:R.layout.screen_simple的布局加载DecorView中:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
         android:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

可以看出我们的顶层布局DecorView中包含的是一个线性布局,包含两部分

ViewStub用于控制是否显示StatusBar或者TitleBar相关的显示

而id为content的FrameLayout则最终会加载我们的Activity通过setContentView()设置的布局资源

由此我们可以大致的画出Activity中view树的结构了:

在ActivityTherad中创建Activity,在Activity中创建PhoneWindow而PhoneWindow中包含了DecorView

因此Activity PhoneWindow  DecorView这三者相互关联实现Android view树的整体流程:

可以看到,每一个Activity都包含了PhoneWindow,PhoneWindow又包含了一个DecorView,

而DecorView中包含了TitleView和一个ContentView,TitleView就是我们开发过程中需要设置

的ActionBar和StatusBar相关,而界面的主体结构都在这个ContentView中设置了,所以我们

每次给Activity设置布局文件的时候必须调用setContentVIew(int layoutResID)方法来设置。
而ContentView中就包含了一个或多个ViewGroup和View,关于ViewGroup和View,

整个ContentView的视图结构:最顶层是ViewGroup,ViewGroup下可能有多个ViewGroup和View,

就好像布局文件来说,我们写布局文件的时候,最外层一般都是一个ViewGroup(LinearLayout或者是ConstraintLayout等),

之后这个ViewGroup内部可能还是一个ViewGroup(LinearLayout或者是RelativeLayout等)或者直接放置了一个或者

多个View(如TextView或者是Button等)。

了解了Activity的view树的组成结构,接下来我们就要了解View的measure测量流程了:

在View的测量流程中,主要对应的就是onMeasure()方法,在该方法中有两个参数,分别是

widthMeasureSpec heightMeasureSpec因此我们学习View的测量measure过程中首先就要学习,MeasureSpec这个类:

 public static class MeasureSpec {
        
        public static final int UNSPECIFIED = 0 << MODE_SHIFT;

        public static final int EXACTLY     = 1 << MODE_SHIFT;

        public static final int AT_MOST     = 2 << MODE_SHIFT;

       
        public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                          @MeasureSpecMode int mode) {
            if (sUseBrokenMakeMeasureSpec) {
                return size + mode;
            } else {
                return (size & ~MODE_MASK) | (mode & MODE_MASK);
            }
        }

        @MeasureSpecMode
        public static int getMode(int measureSpec) {
            //noinspection ResourceType
            return (measureSpec & MODE_MASK);
        }
        
        public static int getSize(int measureSpec) {
            return (measureSpec & ~MODE_MASK);
        }
    }

MeasureSpec是View的一个静态内部类代表着测量的规格(包含mode和size),该类主要用于封装父View对于子View的布局要求:

而它的手段主要是通过一个32位的int类型的数值来实现的,我们知道一个int类型的数值有32位组成,MeasureSpec将它的高2位,

用来代表测量模式mode,低30位用来代表在该测量模式下具体的数值大小size,如下图所示:

在MeasureSpec中分别有三个重要的变量和方法:

MeasureSpec.EXACTLY:

该模式表示,父容器已经检测出子view所需要的精确大小,在该模式下,子View的测量大小即为SpecSize

MeasureSpec.AT_MOST:

该模式表示,父容器未能检测出子view所需要的精确大小,但是指定了一个可用的大小即SpecSize,

在该模式下View的测量大小不能超过SpecSize

MeasureSpec.UNSPECIFIED:

该模式下父容器不对子view的大小做任何的限制,子view想要多大就多大

MeasureSpec.UNSPECIFIED这种模式一般用于我们的系统的内部,像我们的AdapterView,

ScrollView,ListView等,因此在以下的讨论中该模式不在我们的讨论范围内

而MeasureSpec的三个方法,则是获取和生成模式mode和size大小相关的:

getMode(measureSpec):根据测量规格获取测量的模式

getSize(measureSpec):根据测量规格获取测量的具体大小值

makeMeasureSpce(int mode,int size):根据指定的模式和大小生成一个指定的测量规格

在Android中由于View的树形结构,在测量时会从view树的顶端从上向下的依次进行遍历,完成对子view的测量

因此子View的测量是由父View发起的,并且子View的父view必须是一个容器,因为只有容器才有能力装载子

View因此我们可以判定,父view肯定是一个ViewGroup,由此我们可以从ViewGroup的源码来入手对view的测量流程

来一探究竟,由于ViewGroup继承自View并且ViewGroup并没有重写measure()和onMeasure()这两个方法,那么到底是怎么测量的呢?

我们可以从ViewGroup的子类中寻找答案,我们以我们不经常使用的AbsoluteLayout为例来进行讲解:

首先,我们应该知道绝对布局的布局特点是有以下的两个属性来决定的:

AbsoluteLayout_Layout_layout_x:控件相对于父容器左上角的left坐标
AbsoluteLayout_Layout_layout_y:控件相对于父容器左上角的top坐标

AbsoluteLayout中的子View通过设置自身的left top相对于父容器左上角的坐标位置来最终确定自身在容器中的位置

我们来看AbsoluteLayout的onMeasure()方法:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        //获取容器中的子view的数量
        int count = getChildCount();
        
        int maxHeight = 0;  //定义容器的高度
        int maxWidth = 0;  //定义容器的宽度

        //首先通过measureChildren()方法来测量容器中的所有子view的宽高
        measureChildren(widthMeasureSpec, heightMeasureSpec);

        //接着遍历所有的子view,由于子view已经通过measureChildren()方法测量过一遍了
        //即子view已经有确定的测量大小了
        for (int i = 0; i < count; i++) {
            //依次获取指定的子view
            View child = getChildAt(i);
            //如果子view为GONE的话就跳过
            if (child.getVisibility() != GONE) {
                int childRight;
                int childBottom;
                //获取每个子View的LayoutParams
                AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) child.getLayoutParams();
                //通过设置的绝对坐标x,y 分别获取view的右边 和底部的位置
                childRight = lp.x + child.getMeasuredWidth();
                childBottom = lp.y + child.getMeasuredHeight();
            
                //获取容器的宽和高,容器的宽和高就是所有子view中(子view的x坐标 +  子view的宽度,子view的y坐标  +  子view的高度)的最大值
                maxWidth = Math.max(maxWidth, childRight);
                maxHeight = Math.max(maxHeight, childBottom);
            }
        }

       //分别在水平和垂直方向上将padding考虑进去
        maxWidth += mPaddingLeft + mPaddingRight;
        maxHeight += mPaddingTop + mPaddingBottom;

        //考虑layout_minWidth 和 layout_minHeight的值和我们计算出容器的宽高值,取最大值
        maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
        maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());
        //通过setMeasuredDimension将我们的值设置给容器自身,才算完成了一次完整的测量
        setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, 0),
                resolveSizeAndState(maxHeight, heightMeasureSpec, 0));
    }

通过以上对AbsoluteLayout的测量流程分析我们可以得出:

ViewGroup即容器的测量流程是先测量容器中的所有的子View,然后通过

测量好的子View的大小来计算自身的大小,最后通过setMeasureDimension()

将计算好的容器的大小值设置给自身才算完成了一次完整的测量

由于ViewGroup是一个容器,因此其不仅要测量自己还要测量其包含的子view

因此在ViewGroup中提供了几个测量子view的方法,用于子view的测量:

在以上的三个方法中,measureChildren()是一次测量完所有的子view而measureChild()和measureChildWithMargins()则是只单独对某一个

子View进行测量,我们就挑选一个最复杂的measureChildWithMargins()来进行分析:

//注意方法的参数:
//child:父容器要进行测量的子View
//parentWidthMeasureSpec:父容器在width宽度方向上的测量规格MeasureSpce
//widthUsed:父容器在width方向上已经使用的宽度值  比如 其它子view已经使用的宽度 + 其它子view的left_marging + 其它子view的right_margin + 父容器的padding_left + 父容器的padding_right
//parentHeightMeasureSpec:父容器在height高度方向上的测量规格
//heightUsed:父容器在height高度方向上已经使用的高度空间 比如 其它子view已经使用的高度 + 其它子view的top_marging + 其它子view的bottom_margin + 父容器的padding_top + 父容器的padding_bottom
protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
    //获取要测量子View的LayoutParams
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        
    //通过getChildMeasureSpec()方法获取要测量子view在width水平方向的宽度的测量规格childWidthMeasureSpec
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);
    //通过getChildMeasureSpec()方法获取要测量子view在height垂直方向上的高度的测量规格childHeightMeasureSpec
        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);
        //通过以上计算出要测量的子View在水平和垂直方向上的测量规格并调用要测量子view的measure()方法来对子view 进行测量
        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

在以上调用measureChildWithMargins()方法测量子view的过程中主要有以下步骤:

1.获取要测量子view的LayoutParams

2.通过getChildMeasureSpec()方法传入父容器的测量宽高尺寸规格widthMeasureSpec / heightMeasureSpec 和子view的LayoutParams的width / height

生成要测量子view在width 和 height方向上的测量规格MeasureSpec

3.调用要测量子view的measure()方法并将计算出的子view的宽高测量规格传递给要测量子view的measure()方法

那么,子view的宽高测量规格到底具体是怎么生成的呢,我们需要对getChildMeasureSpec()方法一探究竟:

//注意方法参数:
//spec:要测量的子view的父容器在width 或者 height方向上的测量规格 即父容器的MeasureSpec
//padding:父容器在水平或者垂直方向上的已经占用的空间,比如mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin + widthUsed
//表示父容器的左右或者上下padding值 以及该控件的左右或者上下margin值,以及其它已经测量过的子view的宽高值,这些已经使用的空间不能纳入到要测量子view的MeasureSpec的计算中
//childDimension:通过子View的LayoutParams获取到的width height (即通过xml layout_width / layout_height 或者 代码设置的layoutparams.width layout.height设置的值)
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec); //获取父容器的测量模式 mode 
        int specSize = MeasureSpec.getSize(spec);  //获取父容器的测量大小 size
        
       //获取父容器的剩余可用空间大小
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;
        //通过switch语句判断父容器的mode ,来生成要测量的子view的mode 和 size
        switch (specMode) {
       //父容器的specMode为EXACTLY时子view的mode 和 size的生成情况  即父容器的layout_width = 具体值 比如 122dp   或者  layout_width = match_parent
        case MeasureSpec.EXACTLY:
               //表示子的layout_width 为一个具体的值 比如:100dp  50dp 等
            if (childDimension >= 0) {
                //子view的size就是 childDimension 即子view设置的值
                resultSize = childDimension;
                //子view的mode为 EXACTLY
                resultMode = MeasureSpec.EXACTLY;
                //子View的layout_width为 match_parent
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                //子view的size为 父容器在width 或者height方向上的可用的空间大小
                resultSize = size;
                //子view的mode为,EXACTLY
                resultMode = MeasureSpec.EXACTLY;
                //子view的layout_width 为 wrap_content
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //子view的size为 父容器在width 或者height 方向上的可用的空间大小
                resultSize = size;
                //子view的mode 为 AT_MOST
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

       //父容器的mode为AT_MOST的情况 即父容器的layout_width = wrap_content
        case MeasureSpec.AT_MOST:
                //子view的layout_width 为 具体值 比如 50dp  100dp ..等的情况
            if (childDimension >= 0) {
                //子view的size为子view自己设置的layout_width值
                resultSize = childDimension;
                //子view的mode为EXACTLY
                resultMode = MeasureSpec.EXACTLY;
                //子view的layout_width为match_parent的情况
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
               //子view的size为 父容器在width 或者 height 方向上可用的剩余空间 并且子view的size 不能超过父容器在width 或者 height 方向上可用的剩余空间
                resultSize = size;
                //子view的mode为AT_MOST
                resultMode = MeasureSpec.AT_MOST;
                //子view的layout_width = wrap_content
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //子view的size为 父容器在width 或者 height 方向上可用的剩余空间 并且子view的size 不能超过父容器在width 或者 height 方向上可用的剩余空间
                resultSize = size;
                //子view的mode为AT_MOST
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        //父容器的mode为UNSPECIFIED
        case MeasureSpec.UNSPECIFIED:
                //子view的layout_width 为具体值 如layout_width = 100dp
            if (childDimension >= 0) {
                //子view的size为子view自己设置的值
                resultSize = childDimension;
                //子view的mode为EXACTLY
                resultMode = MeasureSpec.EXACTLY;
                //子view的layout_width 为match_parent
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                 //子View的size 为 父容器的可用剩余空间或者为0
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                //子view的mode 为 UNSPECIFIED
                resultMode = MeasureSpec.UNSPECIFIED;
                //子view的layout_width = wrap_content
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                //子view的size为父容器的可用剩余空间或者为0
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                //子view的mode为 UPSPECIFIED
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //通过MeasureSpec将测量计算出的size 和 mode组合成一个完整的MeasureSpec 返回
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

通过以上的getChildMeasureSpec()计算出一个要测量的子view的宽度或者高度的MeasureSpec有以下几个步骤:

1.获取父容器测量规格的mode 和 size

2.获取父容器在width 或者 height方向上的可用空间

3.通过switch语句判断父容器的MeasureSpec的mode来决定要测量子view的mode和size

由此我们知道子view的MeasuerSpec的生成是由其父容器的MeasureSpec和子view自身的LayoutParams来决定的

而至于这个决定的规则,我总结出了一个一下的表格:

从以上总结的这张表中我们总结出了以下的这几个结论(从左往右看,同时UNSPECIFIED模式不在考虑范围内):

1.如果子View的width 或者height为具体值的时候比如 50dp那么此时不管父容器的specMode是什么子view的specMode一定是EXACTLY并且子view的width 或者height的specSize就是我们设置的具体值

2.当子View的size为MATCH_PARENT的时候,子view的测量模式specMode跟随父容器的specMoe,即父容器的specMode是什么子view的测量模式specMode和其相同,但是子view的测量大小分为以下两种情况:

        a.父容器的specMode为EXACTLY时,子view的size为父容器在width 或者height方向上可用的剩余空间

        b.父容器的specMode为AT_MOST时,子View的size不能超过父容器在width 或者height方向上可用剩余空间的大小

3.当子view的size为WRAP_CONTENT时,子View的测量模式specMode一定为AT_MOST,并且子View的测量大小size一定不能超过父容器在width或者height方向上的可用剩余空间大小.

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Android自定义view之View的测量过程全解析 的相关文章

随机推荐

  • matlab函数库-optimset,非线性优化-matlab函数库-optimset

    创建或编辑一个最优化参数选项 句法规则 options optimset param1 value1 param2 value2 设置所有参数及其值 未设置的为默认值 options optimset optimfun 设置与最优化函数有关
  • layui左侧三级菜单2.0

    之前在layui 1 0的时候写过一个三级菜单 那时候用三级菜单还要自己扩展 与后台交互也显得麻烦 现在新版本的layui自动就可以实现三级菜单了 很多朋友在问 我就帮大家整理了一下样式大家可以改改 直接上代码 ul class layui
  • Android Geocoder(位置解析)

    Android中提供GPS定位服务 同时开发者可以对获得的位置信息进行解析 可以获得位置的详细信息 1 gps定位 在Eclipse中建立android应用程序 android sdk中提供了locationmanager来获得系统提供的定
  • Python练习(三)

    目录 传感器日志光照统计 文本字符分布 白鹿原 词频统计 习题 传感器日志光照统计 本题目附件提供了一个传感器日志文件 为文本类型 共 1 千行 每行包含了日期 时间和 4 种传感器读数值 其中 4 种传感器读数值分别是 温度 湿度 光照和
  • Spring Boot 实现接口幂等性的 4 种方案

    一 什么是幂等性 幂等是一个数学与计算机学概念 在数学中某一元运算为幂等时 其作用在任一元素两次后会和其作用一次的结果相同 在计算机中编程中 一个幂等操作的特点是其任意多次执行所产生的影响均与一次执行的影响相同 幂等函数或幂等方法是指可以使
  • 广点通sdk接入 _橱窗广告

    广点通sdk接入 橱窗广告 1 导入相关架包 写入相关权限和配置 android query full 0 26 7 jar GDTUnionSDK 4 8 513 jar
  • 【Elasticsearch学习笔记-基础篇3】Elasticsearch 聚集(aggregation)与过滤器(filter)

    前言 这篇主要总结一下 es 的聚集 aggregation 与过滤器 filter 不会涉及到具体的 API 操作与示例 主要总结概念性与本人理解的内容 以下是主要内容地图 在写聚集之前 我们先来看一下过滤器 过滤器 Filter 首先
  • linux安装odoo10,Centos7部署Odoo10生产环境

    该篇文章是我参考网上教程 整理出适合自己使用的方法 是通过odoo10的rpm包进行安装 一 安装odoo10 1 安装相关依赖 yum update yum install wget yum install y epel release
  • Spring Data JPA教程:审计(二)

    公众号 欢迎关注 书接上文 本文解决前面两个问题中的第二个问题 我们将为实体加上创建者和修改者的信息 首先创建一个返回授权用户信息的组件 获取授权用户信息 Spring Data JPA使用AuditorAware
  • c++基础——区分引用和指针

    目录 前言 1 引用 1 2引用的概念 1 2引用的定义 1 3引用与const 1 4引用的使用场景 2 指针 2 1概念 2 2获取对象的地址 2 3利用指针访问对象 2 3空指针 2 4野指针 2 4 1概念 2 4 2野指针的产生
  • Vs2019+Qt

    一 下载vs2019和qt 关于vs2019的配置方法不在赘述 上一篇已经讲解了 点击传送门 1 下载vs2019 直接在官网点击下载即可 是免费的 2 下载qt 在官网站下载即可 关于vs和qt安装 vs2019安装到自定义的目录就行 根
  • javascript 中函数调用方法:apply() 和 call()

    每个函数都包含两根非继承而来的方法 apply 和call 这两个方法的用途都是在特定的作用域中调用函数 实际上等于设置函数体内this对象的值 首先 apply 方法接收两个参数 一个是在其中运行函数的作用域 另一个是参数数组 其中第二个
  • Nacos - nacos-mysql.sql源文件与application.properties配置文件

    目录标题 前言 内容 初始化 MySQL 数据库 application properties 配置 前言 Nacos设置外部数据源 需要初始化nacos mysql sql源文件 修改application properties配置文件
  • android游戏开发(OpenGL ES绘制矩形平面)

    接触android将近一年了 以前学的应用开发 现在自学android游戏开发 把自己学到的分享出来一下 这也是我的第一篇博客 不说废话了 开始正文 GLRender类用于图形的渲染工作 Util类用于glrender中的数据缓冲 GLRe
  • 信号与中断的区别

    信号与中断的相似点 1 采用了相同的异步通信方式 2 当检测出有信号或中断请求时 都暂停正在执行的程序而转去执行相应的处理程序 3 都在处理完毕后返回到原来的断点 4 对信号或中断都可进行屏蔽 信号与中断的区别 1 中断有优先级 而信号没有
  • R:增加或删除列表元素

    列表创建之后可以添加新的组件 gt z lt list a abc b 12 gt z c lt Add gt z a 1 abc b 1 12 c 1 Add 还可以直接使用索引添加组件 gt z lt list a abc b 12 c
  • 深入了解java.lang.ArrayIndexOutOfBoundsException异常

    异常介绍 什么是异常 在编程过程中 异常是指在程序执行期间发生的意外或异常情况 当程序遇到异常时 会中断正常的执行流程 并且根据异常类型采取相应的处理措施 异常的分类 异常可以分为两种类型 受检异常 Checked Exception 和非
  • 在职阿里6年,一个29岁女软件测试工程师的心声

    简单的先说一下 坐标杭州 14届本科毕业 算上年前在阿里巴巴的面试 一共有面试了有6家公司 因为不想请假 因此只是每个晚上去其他公司面试 所以面试的公司比较少 其中成功的有4家 另外2家失败的原因在于 1 对于系统知识的了解不够全面 在最后
  • 【华为OD机试真题 JAVA】数组连续和

    JS版 华为OD机试真题 JS 数组连续和 标题 数组连续和 时间限制 1秒 内存限制 65536K 语言限制 不限 给定一个含有N个正整数的数组 求出有多少个连续区间 包括单个正整数 它们的和大于等于x 输入描述 第一行两个整数N x 0
  • Android自定义view之View的测量过程全解析

    Android 应用层开发中绕不开自定义 View 这个话题 虽然现在 Github 上有形形色色的开源库供大家使用 但是作为一名开发者而言 虽然不提倡重复造轮子 但是轮子都是造出来的 碰到一些新鲜的 UI 效果时 如果现有的控件无法完成任