View 是如何显示到屏幕上的

  • 基于 android 29

在上一篇 View 绘制流程解析中我们知道了在 Activity 进行 onResume 后 View 显示到屏幕上前需要经过的流程,接下来这篇我们重点来看看 View 在显示前 在 ViewRootImpl 的 performTraversals 方法中调用的三大方法。

文章目录

  • View 是如何显示到屏幕上的
    • 测量
      • 测量总结
    • 布局
      • 布局总结
    • 绘制
      • 绘制总结

测量

首先在 View 绘制前到屏幕前的第一步,也是最复杂的一步,测量。

这里承接上一篇从 ViewRootImpl#performTraversals 中调用 performMeasure 方法开始讲起。

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {if (mView == null) {return;}Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");try {mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}
}

首先可以知道这里的 mView 就是 DecorView,那 childWidthMeasureSpec 和 childHeightMeasureSpec 又是什么呢?想了解它,我们就必须先来看看 View 中的一个重要的内部类 MeasureSpec 了。

MeasureSpec 中有几个非常重要的常量,这里我用 2 进制先写在开头,以便于后面的分析。

  • MODE_MASK:11000000000000000000000000000000

  • UNSPECIFIED:00000000000000000000000000000000

  • EXACTLY:01000000000000000000000000000000

  • AT_MOST:10000000000000000000000000000000

首先我们通过如下方法看 MeasureSpec 的数值是如何构造出来的

public static int makeMeasureSpec(int size, int mode) {// sUseBrokenMakeMeasureSpec 其实是判断安卓版本如果是小于等于 17 时才为 true,否则为 falseif (sUseBrokenMakeMeasureSpec) {return size + mode;} else {// 在安卓 17 以上时这样写其实出于安全考虑,作用和加法一致return (size & ~MODE_MASK) | (mode & MODE_MASK);}
}

通过个方法其实就能知道 MeasureSpec 其实通过巧妙的位运算同事包含了大小和测量模式。在一个 int 值的中的前 2 位其实就是测量模式后 30 位是大小。

下面举个例子,若传入 size = 3,mode = EXACTLY:

(size)00000000000000000000000000000111 & (~MODE_MASK)00111111111111111111111111111111

= 00000000000000000000000000000111

(mode)01000000000000000000000000000000 & (MODE_MASK)00111111111111111111111111111111

= 01000000000000000000000000000000

(size & ~MODE_MASK) | (mode & MODE_MASK)

00000000000000000000000000000111 | 01000000000000000000000000000000

= 01000000000000000000000000000111

在理解了 makeMeasureSpec 的原理后后面的两个方法也很容易理解啦。

获取测量模式:

public static int getMode(int measureSpec) {//noinspection ResourceTypereturn (measureSpec & MODE_MASK);
}

获取测量的大小:

public static int getSize(int measureSpec) {return (measureSpec & ~MODE_MASK);
}

调整测量大小:

static int adjust(int measureSpec, int delta) {final int mode = getMode(measureSpec);int size = getSize(measureSpec);if (mode == UNSPECIFIED) {// No need to adjust size for UNSPECIFIED mode.return makeMeasureSpec(size, UNSPECIFIED);}size += delta;if (size < 0) {Log.e(VIEW_LOG_TAG, "MeasureSpec.adjust: new size would be negative! (" + size +") spec: " + toString(measureSpec) + " delta: " + delta);size = 0;}return makeMeasureSpec(size, mode);
}

在了解了 MeasureSpec 的原理后我们回到 mView.measure 方法,来看看这里的 childWidthMeasureSpec 和 childHeightMeasureSpec 是怎么得来的,因此我们回到 ViewRootImpl#performTraversals 方法中看到了这样一句。

int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

这里的 mWidth 和 mHeight 分别是屏幕的宽高,lp.width 和 lp.height 分别是 DecorView 的宽高的 LayoutParams 的属性,接下来我们进入 getRootMeasureSpec 方法具体看下做了什么。

private static int getRootMeasureSpec(int windowSize, int rootDimension) {int measureSpec;switch (rootDimension) {case ViewGroup.LayoutParams.MATCH_PARENT:measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);break;case ViewGroup.LayoutParams.WRAP_CONTENT:measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);break;default:measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);break;}return measureSpec;
}

从这里我可以知道当布局属性为 MATCH_PARENT 时,测量模式为 EXACTLY,大小为窗口大小,当布局属性为 WRAP_CONTENT 时,测量模式为 AT_MOST,大小为窗口大小,当布局属性为精确值时,测量模式为 EXACTLY,大小为 DecorView 的大小

在知道了 childWidthMeasureSpec 和 childHeightMeasureSpec 是怎么得来的后我们继续跟踪进入 View 的 measure。

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {boolean optical = isLayoutModeOptical(this);if (optical != isLayoutModeOptical(mParent)) {Insets insets = getOpticalInsets();int oWidth  = insets.left + insets.right;int oHeight = insets.top  + insets.bottom;widthMeasureSpec  = MeasureSpec.adjust(widthMeasureSpec,  optical ? -oWidth  : oWidth);heightMeasureSpec = MeasureSpec.adjust(heightMeasureSpec, optical ? -oHeight : oHeight);}// 这里将 widthMeasureSpec 和 heightMeasureSpec 拼接成功了一个 long 作为测量缓存的 keylong key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);// 下面这段判断了是否需要布局final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec|| heightMeasureSpec != mOldHeightMeasureSpec;final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY&& MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)&& getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);final boolean needsLayout = specChanged&& (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);if (forceLayout || needsLayout) {mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;resolveRtlPropertiesIfNeeded();// 判断之前缓存的测量值有没有,或是否强制布局int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);if (cacheIndex < 0 || sIgnoreMeasureCache) {onMeasure(widthMeasureSpec, heightMeasureSpec);mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;} else {// 有的话直接取long value = mMeasureCache.valueAt(cacheIndex);setMeasuredDimensionRaw((int) (value >> 32), (int) value);mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {throw new IllegalStateException("View with id " + getId() + ": "+ getClass().getName() + "#onMeasure() did not set the"+ " measured dimension by calling"+ " setMeasuredDimension()");}mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;}mOldWidthMeasureSpec = widthMeasureSpec;mOldHeightMeasureSpec = heightMeasureSpec;// 存入宽高的 MeasureSpecmMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |(long) mMeasuredHeight & 0xffffffffL);
}

这里有个小细节 getMeasuredHeight 和 getMeasuredWidth。

public final int getMeasuredWidth() {return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getMeasuredHeight() {return mMeasuredHeight & MEASURED_SIZE_MASK;
}

由于 MEASURED_SIZE_MASK = 0x00ffffff 这两个方法返回的宽和高只取了一个 int 的后 24 位。

在了解 View 的 measure 做了什么后,我们继续跟踪进入 onMeasure 方法。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

我们发现 setMeasuredDimension 方法就是设置 View 的宽高的大小,因此我们重点看 getDefaultSize 和 getSuggestedMinimumWidth,getSuggestedMinimumHeight 这几个方法。

protected int getSuggestedMinimumWidth() {return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}
protected int getSuggestedMinimumHeight() {return (mBackground == null) ? mMinHeight : max(mMinHeight, mBackground.getMinimumHeight());
}
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;
}

可以看到 getSuggestedMinimumWidth,getSuggestedMinimumHeight 这两个方法就是取背景的大小和最小宽高的最大值,而 getDefaultSize 会判断若测量模式是 UNSPECIFIED 时返回的就是 getSuggestedMinimumWidth,getSuggestedMinimumHeight 的大小,而 AT_MOST 和 EXACTLY 时,返回的是实际测量的大小。

由于我们这里的 View 是 DecorView ,而 DecorView 继承自 FrameLayout,因此我们还需要进入 FrameLayout 的 onMeasure 中查看。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {int count = getChildCount();// 宽和高任意一个不是 EXACTLY 测量模式就为 truefinal boolean measureMatchParentChildren =MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;// 先清空 MATCH_PARENT 的子组件集合mMatchParentChildren.clear();int maxHeight = 0;int maxWidth = 0;int childState = 0;// 遍历和测量子组件的并获取最大宽高for (int i = 0; i < count; i++) {final View child = getChildAt(i);if (mMeasureAllChildren || child.getVisibility() != GONE) {// 测量子 View 的边界,也就是子 View 的大小// 会先调用 getChildMeasureSpec,再调用子 View 的 measure 方法measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);final LayoutParams lp = (LayoutParams) child.getLayoutParams();maxWidth = Math.max(maxWidth,child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);maxHeight = Math.max(maxHeight,child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);childState = combineMeasuredStates(childState, child.getMeasuredState());if (measureMatchParentChildren) {// 这里判断宽和高有一个是 MATCH_PARENT 的就添加到 mMatchParentChildren 集合中if (lp.width == LayoutParams.MATCH_PARENT ||lp.height == LayoutParams.MATCH_PARENT) {mMatchParentChildren.add(child);}}}}// 将 padding 加到最大宽高上maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();// 背景的最小宽高和当前最大宽高,取大的maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());// 前景图的最小宽高和当前最大宽高,取大的final Drawable drawable = getForeground();if (drawable != null) {maxHeight = Math.max(maxHeight, drawable.getMinimumHeight());maxWidth = Math.max(maxWidth, drawable.getMinimumWidth());}// 先保存自己的宽高和测量值setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),resolveSizeAndState(maxHeight, heightMeasureSpec,childState << MEASURED_HEIGHT_STATE_SHIFT));count = mMatchParentChildren.size();if (count > 1) {// 接下来这里会再次测量 MATCH_PARENT 的子 Viewfor (int i = 0; i < count; i++) {final View child = mMatchParentChildren.get(i);final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();final int childWidthMeasureSpec;if (lp.width == LayoutParams.MATCH_PARENT) {final int width = Math.max(0, getMeasuredWidth()- getPaddingLeftWithForeground() - getPaddingRightWithForeground()- lp.leftMargin - lp.rightMargin);childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);} else {childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,getPaddingLeftWithForeground() + getPaddingRightWithForeground() +lp.leftMargin + lp.rightMargin,lp.width);}final int childHeightMeasureSpec;if (lp.height == LayoutParams.MATCH_PARENT) {final int height = Math.max(0, getMeasuredHeight()- getPaddingTopWithForeground() - getPaddingBottomWithForeground()- lp.topMargin - lp.bottomMargin);childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);} else {childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,getPaddingTopWithForeground() + getPaddingBottomWithForeground() +lp.topMargin + lp.bottomMargin,lp.height);}child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}}
}

通过这个方法我们知道了在 ViewGroup 的 onMeasure 中,会先遍历子 View 进行测量,以此来确定自身的大小。而测量子 View 的核心就是取出子 View 的 LayoutParams 和 ViewGroup 自身的 MeasureSpec,然后传入 getChildMeasureSpec 方法中来创建出子 View 的 MeasureSpec。

至此 View 的测量就结束了。

测量总结
  • MeasureSpec 前两位是测量模式,后 30 位是大小

  • getMeasuredWidth 和 getMeasuredHeight 方法返回的宽和高只取了一个 int 的后 24 位

  • FrameLayout 的宽和高任意一个不是 EXACTLY 测量模式,且子 View 中 MATCH_PARENT 属性有两个时,MATCH_PARENT 的子 View 会测量 2 次

  • View 的 MeasureSpec 在 ViewGroup#getChildMeasureSpec 中完成的测量,其是根据父容器的 MeasureSpec 和自己的 LayoutParams 决定的

    \ 父容器的测量模式 EXACTILY 父容器的测量模式AT_MOST 父容器的测量模式UNSPECIFIED
    子 view 的 LayoutParams:直接输入值 mode:EXACTILY
    size:childSize
    mode:EXACTILY
    size:childSize
    mode:EXACTILY
    size:childSize
    子 view 的 LayoutParams:match_parent mode:EXACTILY
    size:parentSize
    mdoe:AT_MOST
    size:parentSize
    mdoe:UNSPECIFIED
    size:0
    子 view 的 LayoutParams:wrap_content mode:AT_MOST
    size:parentSize
    mdoe:AT_MOST
    size:parentSize
    mdoe:UNSPECIFIED
    size:0

布局

这是 View 显示到屏幕上的第二步,布局。

这里承接上一篇从 ViewRootImpl#performTraversals 中调用 performLayout 方法开始讲起。

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,int desiredWindowHeight) {// ... ...final View host = mView;if (host == null) {return;}// ... ...try {host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());// ... ...} finally {Trace.traceEnd(Trace.TRACE_TAG_VIEW);}mInLayout = false;
}

这里我们只看重点的方法 host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()),因此我们跟踪进入 layout 方法中,在 layout 方法中有个 setFrame 方法该方法需要传入 4 个值来确定 View 的位置,也就是 left,top,right,bottom。而该方法调用完后会继续调用 onLayout 方法,若该 View 是 ViewGroup 时则需要重写该方法。这里我们来看 FrameLayout 的 onLayout 的实现。

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {final int count = getChildCount();// 获取自己 paddingfinal int parentLeft = getPaddingLeftWithForeground();final int parentRight = right - left - getPaddingRightWithForeground();final int parentTop = getPaddingTopWithForeground();final int parentBottom = bottom - top - getPaddingBottomWithForeground();// 遍历子 View for (int i = 0; i < count; i++) {final View child = getChildAt(i);// 只对可见性不为 GONE 的 View 布局if (child.getVisibility() != GONE) {final LayoutParams lp = (LayoutParams) child.getLayoutParams();// 获取子 View 的宽高final int width = child.getMeasuredWidth();final int height = child.getMeasuredHeight();int childLeft;int childTop;int gravity = lp.gravity;if (gravity == -1) {gravity = DEFAULT_CHILD_GRAVITY;}final int layoutDirection = getLayoutDirection();final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;// 下面这段是通过自己的 padding 和子 View 的 Gravity 和 margin 来确定子 View 的位置switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {case Gravity.CENTER_HORIZONTAL:childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +lp.leftMargin - lp.rightMargin;break;case Gravity.RIGHT:if (!forceLeftGravity) {childLeft = parentRight - width - lp.rightMargin;break;}case Gravity.LEFT:default:childLeft = parentLeft + lp.leftMargin;}switch (verticalGravity) {case Gravity.TOP:childTop = parentTop + lp.topMargin;break;case Gravity.CENTER_VERTICAL:childTop = parentTop + (parentBottom - parentTop - height) / 2 +lp.topMargin - lp.bottomMargin;break;case Gravity.BOTTOM:childTop = parentBottom - height - lp.bottomMargin;break;default:childTop = parentTop + lp.topMargin;}// 确定完位置后通知子 View 布局child.layout(childLeft, childTop, childLeft + width, childTop + height);}}
}

FrameLayout 会先获取自己的 padding,然后遍历取出子 View,判断子 View 的可见性不为 GONE 时,会通过过自己的 padding 和子 View 的 Gravity 和 margin 来确定子 View 的位置。

至此 View 的布局过程就结束了。

布局总结
  • View 的布局会调用 layout 方法,而 layout 中会通过 setFrame 先确定自身的位置然后再调用 onLayout 方法
  • ViewGroup 需要重写 onLayout 方法来为子 View 确定位置

绘制

这是 View 显示到屏幕上的最后一步,绘制。

这里承接上一篇从 ViewRootImpl#performTraversals 中调用 performDraw 在该方法中有会先调用 draw 方法,在 draw 方法中又会调用 drawSoftware,在 drawSoftware 中我们便能看到熟悉的 mView.draw(canvas) 啦!

public void draw(Canvas canvas) {final int privateFlags = mPrivateFlags;mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;/** Draw traversal performs several drawing steps which must be executed* in the appropriate order:**      1. Draw the background*      2. If necessary, save the canvas' layers to prepare for fading*      3. Draw view's content*      4. Draw children*      5. If necessary, draw the fading edges and restore layers*      6. Draw decorations (scrollbars for instance)*/// Step 1, draw the background, if neededint saveCount;drawBackground(canvas);// skip step 2 & 5 if possible (common case)final int viewFlags = mViewFlags;boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;if (!verticalEdges && !horizontalEdges) {// Step 3, draw the contentonDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);drawAutofilledHighlight(canvas);// Overlay is part of the content and draws beneath Foregroundif (mOverlay != null && !mOverlay.isEmpty()) {mOverlay.getOverlayView().dispatchDraw(canvas);}// Step 6, draw decorations (foreground, scrollbars)onDrawForeground(canvas);// Step 7, draw the default focus highlightdrawDefaultFocusHighlight(canvas);if (debugDraw()) {debugDrawFocus(canvas);}// we're done...return;}// ... ...
}
绘制总结

绘制有 4 步

  1. 绘制背景(drawBackground —> background.draw(canvas))
  2. 绘制自己(onDraw)
  3. 绘制子 View(dispathDraw)
  4. 绘制装饰,如前景和滚动条(onDrawForeground)

View 是如何显示到屏幕上的相关推荐

  1. Activity到底是什么时候显示到屏幕上的呢?

    From : http://blog.desmondyao.com/android-show-time/ http://www.open-open.com/lib/view/open148420764 ...

  2. 「Android渲染」图像是怎样显示到屏幕上的?

    我们每天花很多时间盯着手机屏幕,不知道你有没有好奇过: 手机屏幕上的这些东西是怎么显示出来的? 这时候来了一位Android程序员(当然也可以是iOS或者是前端程序员)说: 这里显示的其实是一个Vie ...

  3. 如何在python中显示电脑中的图片-python如何实现多个图片显示在屏幕上?

    pygame的三个方法(屏幕上显示需要的图片) yuhu102319582018-12-16 python将多幅图片显示在一张图片上 wugui1111116282018-06-15 iOS 图片显示 ...

  4. 编写一个Java程序将当100,101,102,103,104,105个数以数组的形式写入到Dest.txt文件中,并以相反的顺序读出显示在屏幕上。

    编写一个Java程序将当100,101,102,103,104,105个数以数组的形式写入到Dest.txt文件中,并以相反的顺序读出显示在屏幕上. package p1;import java.io ...

  5. 应用FileInputStream类,编写应用程序,从磁盘上读取一个Java程序,并将源程序代码显示在屏幕上

    应用FileInputStream类,编写应用程序,从磁盘上读取一个Java程序,并将源程序代码显示在屏幕上. package p1;import java.io.*; public class FI ...

  6. 从键盘输入一个英文字母,如果它是大写英文字母,则将其转换为小写英文字母,如果它是小写英文字母,则将其转换为大写英文字母,然后将它及其ASCII码值显示到屏幕上,如果不是英文字母,则不转换直接输出到屏幕

    从键盘输入一个英文字母,如果它是大写英文字母,则将其转换为小写英文字母,如果它是小写英文字母,则将其转换为大写英文字母,然后将它及其ASCII码值显示到屏幕上,如果不是英文字母,则不转换直接输出到屏幕 ...

  7. 终于懂了汇编代码为什么从键盘上输入字符,将该字符的ASCII显示在屏幕上必须要加30或37(附汇编代码)

    规则:二进制转换成十六进制且输出,四位四位判断其值范围,如果在0000-1001范围加30H,如果在1010-1111范围加37H 例如:从键盘上输入A,系统存的是二进制数01000001B,先把8b ...

  8. 80x86汇编语言 循环结构 找出最小的偶数并在屏幕上显示 求出数组的平均值显示在屏幕上

    题目1 写一个完整的80X86汇编语言程序:键盘输入15个数据(转换成数值,存储到一维数组中,数值的长度为字),找出最小的偶数并在屏幕上显示,若没有偶数则显示"没有偶数!". .d ...

  9. 将十六进制数的ASCII码转换为十进制数。十六进制数的值域为0~65535,最大转换为五位十进制数。要求将缓冲区的000CH的ASCII码转换为十进制,并将结果显示在屏幕上。

    将十六进制数的ASCII码转换为十进制数.十六进制数的值域为0~65535,最大转换为五位十进制数.要求将缓冲区的000CH的ASCII码转换为十进制,并将结果显示在屏幕上. 1.程序源码 DATAS ...

最新文章

  1. OpenCV YOLO DNN(yolo_object_detection)
  2. 线段 LibreOJ - 10007(贪心)
  3. 嵌套饼图_旭日图的效率,高到饼图都羡慕
  4. 前端之 JavaScript 基础
  5. Grunt 新手指南
  6. linux-选择输入法
  7. Markdown--行内公式编辑
  8. python重新安装_重新安装python
  9. vmware workstation虚拟机安装Ubuntu server 18.04
  10. 【PAT】2020年春季考试乙级题目、答案、摸鱼、游记、93分
  11. printf格式化字符串_Java printf()–将格式化的字符串打印到控制台
  12. TCP和UDP报文头格式(转)
  13. 计算机的基本数据结构与算法分析,数据结构与算法分析
  14. Intel主板芯片组发展历史(声卡驱动如何解决~)
  15. C#VS2019中ReportViewer控件和报表设计器 RDLC使用方法总结
  16. Matplotlib绘制自定义函数曲线
  17. 用Altium designer画PCB的一般心得
  18. Opus:IETF低延迟音频编解码器:API和操作手册
  19. 华为云服务器扩容挂盘失败fstab文件配置错误无法开机及进入单用户模式
  20. 通信协议整理之 SPI 通信

热门文章

  1. 制作zmap的dns探针
  2. 年轻人应该如何看待高薪
  3. Ghost ,博客系统代名词
  4. 详解pyqt5的UI中嵌入matplotlib图形并实时刷新(挖坑和填坑)
  5. c语言双向链表重组写法,一个C语言做的双向链表程序,请高手帮忙改错
  6. 2018年 CSDN博客背景皮肤设置
  7. 画出自己美好的人生。
  8. linux2019/8/1
  9. CSS描边动画,后端直呼哇塞
  10. Android图表库--MPChart(Piechart)