聊天气泡图片的动态拉伸、适配与镜像

news2024/11/17 7:42:49

聊天气泡图片的动态拉伸、适配与镜像

  • 前情提要
    • 创建.9.png格式的图片
    • 从资源文件夹加载.9.png图片
    • 从本地文件加载“.9.png”图片
    • 项目痛点
  • 进阶探索
    • iOS中的方式
    • Android中的探索
      • 构造chunk数据
      • 构造padding数据
      • 镜像翻转功能
      • 屏幕的适配
      • 简单封装
      • 演示示例
        • 一条线段控制的拉伸
        • 两条线段控制的拉伸
        • padding的示例
        • 屏幕适配的示例
  • 效果一览
  • 参考文章

前情提要

春节又到了,作为一款丰富的社交类应用,免不了要上线几款和新年主题相关的聊天气泡背景。这不,可爱的兔兔和财神爷等等都安排上了,可是Android的气泡图上线流程等我了解后着实感觉有些许复杂,对比隔壁的iOS真是被吊打,且听我从头到尾细细详解一遍。

创建.9.png格式的图片

新建项目.png
在开发上图所示的功能中,我们一般都会使用 .9.png 图片,那么一张普通png格式的图片怎么处理成 .9.png 格式呢,一起来简单回顾下。

在Android Studio中,对一张普通png图片右键,然后点击 “Create 9-Patch file…”,选择新图片保存的位置后,双击新图就会显示图片编辑器,图片左侧的黑色线段可以控制图片的竖向拉伸区域,上侧的黑色线段可以控制图片的横向拉伸区域,下侧和右侧的黑色线段则可以控制内容的填充区域,编辑后如下图所示:
Snipaste_2023-01-11_15-04-08.png

上图呢是居中拉伸的情况,但是如果中间有不可拉伸元素的话如何处理呢(一般情况下我们也不会有这样的聊天气泡,这里是拜托UI小姐姐专门修改图片做的示例),如下图所示,这时候拉伸的话左侧和上侧就需要使用两条(多条)线段来控制拉伸的区域了,从而避免中间的财神爷被拉伸:
Snipaste_2023-01-11_16-10-53.png

OK,.9.png格式图片的处理就是这样了。

从资源文件夹加载.9.png图片

比如加载drawable或者mipmap资源文件夹中的图片,这种加载方式的话很简单,直接给文字设置背景就可以了,刚刚处理过的小兔子图片放在drawable-xxhdpi文件夹下,命名为rabbit.9.png,示例代码如下所示:

textView.background = ContextCompat.getDrawable(this, R.drawable.rabbit)

从本地文件加载“.9.png”图片

如果我们将上述rabbit.9.png图片直接放到应用缓存文件夹中,然后通过bitmap进行加载,伪代码如下:

textView.text = "直接加载本地.9.png图片"
textView.background =
            BitmapDrawable.createFromPath(cacheDir.absolutePath + File.separator + "rabbit.9.png")

则显示效果如下:
Screenshot_2023-01-11-17-13-54-60.jpg
可以看到,这样是达不到我们想要的效果的,整张图片被直接进行拉伸了,完全没有我们上文设计的拉伸效果。

其实要想达到上文设计的居中拉伸效果,我们需要使用aapt工具对.9.png图片再进行下处理(在Windows系统上aapt工具所在位置为:你SDK目录\build-tools\版本号\aapt.exe),Windows下的命令如下所示:

.\aapt.exe s -i .\rabbit.9.png -o rabbit9.png

将处理过后新生成的rabbit9.png图片放入到应用缓存文件夹中,然后通过bitmap直接进行加载,代码如下:

textView.text = "加载经aapt处理过的本地图片"
textView.background =
            BitmapDrawable.createFromPath(cacheDir.absolutePath + File.separator + "rabbit9.png")

则显示效果正常,如下所示:
Screenshot_2023-01-11-17-32-33-91_24cef02ef5c5f1a3ed9b56e4c5956272.jpg
也就是说如果我们需要从本地或者assets文件夹中加载可拉伸图片的话,那么整个处理的流程就是:根据源rabit.png图片创建rabbit.9.png图片 -> 使用aapt处理生成新的rabbit9.png图片。

项目痛点

所以,以上就是目前项目中的痛点,每次增加一个聊天气泡背景,Android组都需要从UI小姐姐那里拿两张图片,一左一右,然后分别处理成 .9.png 图,然后还需要用aapt工具处理,然后再上传到服务器。后台还需要针对Android和iOS平台下发不同的图片,这也太复杂了。
所以我们的目标就是只需要一张通用的气泡背景图,直接上传服务器,移动端下载下来后,在本地做 拉伸镜像、缩放等 功能的处理,那么一起来探索下吧。

进阶探索

我们来先对比看下iOS的处理方式,然后升级我们的项目。

iOS中的方式

只需要一个原始的png的图片即可,人家有专门的resizableImage函数来处理拉伸,大致的示例代码如下所示:

let image : UIImage = UIImage(named: "rabbit.png")
image.resizableImage(withCapInsets: .init(top: 20, left: 20, right:20, bottom:20))

注意:这里的withCapInsets参数的含义应该是等同与Android中的padding。padding的区域就是被保护不会拉伸的区域,而剩下的区域则会被拉伸来填充。
可以看到这里其实是有一定的约束规范的,UI小姐姐是按照此规范来进行气泡图的设计的,所以我们也可以遵循大致的约束,和iOS使用同一张气泡背景图片即可。

Android中的探索

那么在Android中有没有可能也直接通过代码来处理图片的拉伸呢?也可以有!!!
原理请参考《Android动态布局入门及NinePatchChunk解密》,各种思想的碰撞请参考《Create a NinePatch/NinePatchDrawable in runtime》。
站在前面巨人的肩膀上看,最终我们需要自定义创建的就是一个NinePatchDrawable对象,这样可以直接设置给TextView的background属性或者其他drawable属性。那么先来看下创建该对象所需的参数吧:

/**
* Create drawable from raw nine-patch data, setting initial target density
* based on the display metrics of the resources.
*/
public NinePatchDrawable(
    Resources res,
    Bitmap bitmap,
    byte[] chunk,
    Rect padding,
    String srcName
)

主要就是其中的两个参数:

  • byte[] chunk:构造chunk数据,是构造可拉伸图片的数据结构
  • Rect padding:padding数据,同xml中的padding含义,不要被Rect所迷惑

构造chunk数据

这里构造数据可是有说法的,我们先以上文兔子图片的拉伸做示例,在该示例中,横向和竖向都分别有一条线段来控制拉伸,那么我们定义如下:
横向线段的起点位置的百分比为patchHorizontalStart,终点位置的百分比为patchHorizontalEnd;
竖向线段的起点位置的百分比为patchVerticalStart,终点位置的百分比为patchVerticalEnd;
width和height分别为传入进来的bitmap的宽度和高度,示例代码如下:

private fun buildChunk(): ByteArray {

    // 横向和竖向都只有一条线段,一条线段有两个端点
    val horizontalEndpointsSize = 2
    val verticalEndpointsSize = 2

    val NO_COLOR = 0x00000001
    val COLOR_SIZE = 9 //could change, may be 2 or 6 or 15 - but has no effect on output

    val arraySize = 1 + 2 + 4 + 1 + horizontalEndpointsSize + verticalEndpointsSize + COLOR_SIZE
    val byteBuffer = ByteBuffer.allocate(arraySize * 4).order(ByteOrder.nativeOrder())

    byteBuffer.put(1.toByte()) //was translated
    byteBuffer.put(horizontalEndpointsSize.toByte()) //divisions x
    byteBuffer.put(verticalEndpointsSize.toByte()) //divisions y
    byteBuffer.put(COLOR_SIZE.toByte()) //color size

    // skip
    byteBuffer.putInt(0)
    byteBuffer.putInt(0)

    // padding 设为0,即使设置了数据,padding依旧可能不生效
    byteBuffer.putInt(0)
    byteBuffer.putInt(0)
    byteBuffer.putInt(0)
    byteBuffer.putInt(0)

    // skip
    byteBuffer.putInt(0)

    // regions 控制横向拉伸的线段数据
    val patchLeft = (width * patchHorizontalStart).toInt()
    val patchRight = (width * patchHorizontalEnd).toInt()
    byteBuffer.putInt(patchLeft)
    byteBuffer.putInt(patchRight)

    // regions 控制竖向拉伸的线段数据
    val patchTop = (height * patchVerticalStart).toInt()
    val patchBottom = (height * patchVerticalEnd).toInt()
    byteBuffer.putInt(patchTop)
    byteBuffer.putInt(patchBottom)

    for (i in 0 until COLOR_SIZE) {
        byteBuffer.putInt(NO_COLOR)
    }

    return byteBuffer.array()
}

OK,上面是横向竖向都有一条线段来控制图片拉伸的情况,再看上文财神爷图片的拉伸示例,就分别都是两条线段控制了,也有可能需要更多条线段来控制,所以我们需要稍微改造下我们的代码,首先定义一个PatchRegionBean的实体类,该类定义了一条线段的起点和终点(都是百分比):

data class PatchRegionBean(
    val start: Float,
    val end: Float
)

在类中定义横向和竖向竖向线段的列表,用来存储这些数据,然后改造buildChunk()方法如下:

private var patchRegionHorizontal = mutableListOf<PatchRegionBean>()
private var patchRegionVertical = mutableListOf<PatchRegionBean>()

private fun buildChunk(): ByteArray {

    // 横向和竖向端点的数量 = 线段数量 * 2
    val horizontalEndpointsSize = patchRegionHorizontal.size * 2
    val verticalEndpointsSize = patchRegionVertical.size * 2

    val NO_COLOR = 0x00000001
    val COLOR_SIZE = 9 //could change, may be 2 or 6 or 15 - but has no effect on output

    val arraySize = 1 + 2 + 4 + 1 + horizontalEndpointsSize + verticalEndpointsSize + COLOR_SIZE
    val byteBuffer = ByteBuffer.allocate(arraySize * 4).order(ByteOrder.nativeOrder())

    byteBuffer.put(1.toByte()) //was translated
    byteBuffer.put(horizontalEndpointsSize.toByte()) //divisions x
    byteBuffer.put(verticalEndpointsSize.toByte()) //divisions y
    byteBuffer.put(COLOR_SIZE.toByte()) //color size

    // skip
    byteBuffer.putInt(0)
    byteBuffer.putInt(0)

    // padding 设为0,即使设置了数据,padding依旧可能不生效
    byteBuffer.putInt(0)
    byteBuffer.putInt(0)
    byteBuffer.putInt(0)
    byteBuffer.putInt(0)

    // skip
    byteBuffer.putInt(0)

    // regions 控制横向拉伸的线段数据
    patchRegionHorizontal.forEach {
        byteBuffer.putInt((width * it.start).toInt())
        byteBuffer.putInt((width * it.end).toInt())
    }

    // regions 控制竖向拉伸的线段数据
    patchRegionVertical.forEach {
        byteBuffer.putInt((height * it.start).toInt())
        byteBuffer.putInt((height * it.end).toInt())
    }

    for (i in 0 until COLOR_SIZE) {
        byteBuffer.putInt(NO_COLOR)
    }

    return byteBuffer.array()
}

构造padding数据

对比刚刚的chunk数据,padding就显得尤其简单了,注意这里传递来的值依旧是百分比,而且需要注意别和Rect的含义搞混了即可:

fun setPadding(
    paddingLeft: Float,
    paddingRight: Float,
    paddingTop: Float,
    paddingBottom: Float,
): NinePatchDrawableBuilder {
    this.paddingLeft = paddingLeft
    this.paddingRight = paddingRight
    this.paddingTop = paddingTop
    this.paddingBottom = paddingBottom
    return this
}

/**
 * 控制内容填充的区域
 * (注意:这里的left,top,right,bottom同xml文件中的padding意思一致,只不过这里是百分比形式)
 */
private fun buildPadding(): Rect {
    val rect = Rect()

    rect.left = (width * paddingLeft).toInt()
    rect.right = (width * paddingRight).toInt()

    rect.top = (height * paddingTop).toInt()
    rect.bottom = (height * paddingBottom).toInt()

    return rect
}

镜像翻转功能

因为是聊天气泡背景,所以一般都会有左右两个位置的展示,而这俩文件一般情况下都是横向镜像显示的,在Android中好像也没有直接的图片镜像功能,但好在之前做海外项目LTR以及RTL时候了解到一个投机取巧的方式,通过设置scale属性为-1来实现。这里我们同样可以这么做,因为最终处理的都是bitmap图片,示例代码如下:

/**
 * 构造bitmap信息
 * 注意:需要判断是否需要做横向的镜像处理
 */
private fun buildBitmap(): Bitmap? {
    return if (!horizontalMirror) {
        bitmap
    } else {
        bitmap?.let {
            val matrix = Matrix()
            matrix.setScale(-1f, 1f)
            val newBitmap = Bitmap.createBitmap(
                it,
                0, 0, it.width, it.height,
                matrix, true
            )
            it.recycle()
            newBitmap
        }
    }
}

如果需要镜像处理我们就通过设置Matrix的scaleX的属性为-1f,这就可以做到横向镜像的效果,竖向则保持不变,然后通过Bitmap类创建新的bitmap即可。
图像镜像反转的情况下,还需要注意的两点是:

  • chunk的数据中横向内容需要重新处理
  • padding的数据中横向内容需要重新处理
/**
 * chunk数据的修改
 */
if (horizontalMirror) {
    patchRegionHorizontal.forEach {
        byteBuffer.putInt((width * (1f - it.end)).toInt())
        byteBuffer.putInt((width * (1f - it.start)).toInt())
    }
} else {
    patchRegionHorizontal.forEach {
        byteBuffer.putInt((width * it.start).toInt())
        byteBuffer.putInt((width * it.end).toInt())
    }
}

/**
 * padding数据的修改
 */
if (horizontalMirror) {
    rect.left = (width * paddingRight).toInt()
    rect.right = (width * paddingLeft).toInt()
} else {
    rect.left = (width * paddingLeft).toInt()
    rect.right = (width * paddingRight).toInt()
}

屏幕的适配

屏幕适配的话其实就是利用Bitmap的density属性,如果UI给定的图是按照480dpi设计的,那么就设置为480dpi或者相近的dpi即可:

// 注意:是densityDpi的值,320、480、640等
bitmap.density = 480

简单封装

通过上述两步重要的过程我们已经知道如何构造所需的chunk和padding数据了,那么简单封装一个类来处理吧,加载的图片我们可以通过资源文件夹(drawable、mipmap),asstes文件夹,手机本地文件夹来获取,所以对上述三种类型都做下支持:

/**
 * 设置资源文件夹中的图片
 */
fun setResourceData(
    resources: Resources,
    resId: Int,
    horizontalMirror: Boolean = false
): NinePatchDrawableBuilder {
    val bitmap: Bitmap? = try {
        BitmapFactory.decodeResource(resources, resId)
    } catch (e: Throwable) {
        e.printStackTrace()
        null
    }

    return setBitmapData(
        bitmap = bitmap,
        resources = resources,
        horizontalMirror = horizontalMirror
    )
}

/**
 * 设置本地文件夹中的图片
 */
fun setFileData(
    resources: Resources,
    file: File,
    horizontalMirror: Boolean = false
): NinePatchDrawableBuilder {
    val bitmap: Bitmap? = try {
        BitmapFactory.decodeFile(file.absolutePath)
    } catch (e: Throwable) {
        e.printStackTrace()
        null
    }

    return setBitmapData(
        bitmap = bitmap,
        resources = resources,
        horizontalMirror = horizontalMirror
    )
}

/**
 * 设置assets文件夹中的图片
 */
fun setAssetsData(
    resources: Resources,
    assetFilePath: String,
    horizontalMirror: Boolean = false
): NinePatchDrawableBuilder {
    var bitmap: Bitmap?

    try {
        val inputStream = resources.assets.open(assetFilePath)
        bitmap = BitmapFactory.decodeStream(inputStream)
        inputStream.close()
    } catch (e: Throwable) {
        e.printStackTrace()
        bitmap = null
    }

    return setBitmapData(
        bitmap = bitmap,
        resources = resources,
        horizontalMirror = horizontalMirror
    )
}

/**
 * 直接处理bitmap数据
 */
fun setBitmapData(
    bitmap: Bitmap?,
    resources: Resources,
    horizontalMirror: Boolean = false
): NinePatchDrawableBuilder {
    this.bitmap = bitmap
    this.width = bitmap?.width ?: 0
    this.height = bitmap?.height ?: 0

    this.resources = resources
    this.horizontalMirror = horizontalMirror
    return this
}

横向和竖向的线段需要支持多段,所以分别使用两个列表来进行管理:

fun setPatchHorizontal(vararg patchRegion: PatchRegionBean): NinePatchDrawableBuilder {
    patchRegion.forEach {
        patchRegionHorizontal.add(it)
    }
    return this
}

fun setPatchVertical(vararg patchRegion: PatchRegionBean): NinePatchDrawableBuilder {
    patchRegion.forEach {
        patchRegionVertical.add(it)
    }
    return this
}

演示示例

我们使用一个5x5的25宫格图片来进行演示,这样我们可以很方便的看出来拉伸或者边距的设置到底有没有生效,将该图片放入资源文件夹中,页面上创建一个展示该图片用的ImageView,假设图片大小是200x200,然后创建一个TextView,通过我们自己的可拉伸功能设置文字的背景。
(注:演示所用的图片是请UI小哥哥帮忙处理的,听完说完我的需求后,UI小哥哥二话没说当着我的面直接出了十来种颜色风格的图片让我选,相当给力!!!)

一条线段控制的拉伸

示例代码如下:

textView.width = 800
textView.background = NinePatchDrawableBuilder()
            .setResourceData(
                resources = resources,
                resId = R.drawable.sample_1,
                horizontalMirror = false
            )
            .setPatchHorizontal(
                PatchRegionBean(start = 0.4f, end = 0.6f),
            )
            .build()

显示效果如下:
Screenshot_2023-01-13-17-52-29-22_24cef02ef5c5f1a3ed9b56e4c5956272.jpg
可以看到竖向上没有拉伸,横向上图片 0.4-0.6 的区域全部被拉伸,然后填充了800的宽度。

两条线段控制的拉伸

接下来再看这段代码示例,这里我们横向上添加了两条线段,分别是从0.2-0.4,0.6-0.8:

textView.width = 800
textView.background = NinePatchDrawableBuilder()
            .setResourceData(
                resources = resources,
                resId = R.drawable.sample_1,
                horizontalMirror = false
            )
            .setPatchHorizontal(
                PatchRegionBean(start = 0.2f, end = 0.4f),
                PatchRegionBean(start = 0.6f, end = 0.8f),
            )
            .build()

显示效果如下:
Screenshot_2023-01-13-17-35-49-40_24cef02ef5c5f1a3ed9b56e4c5956272.jpg
可以看到横向上中间的(0.4-0.6)的部分没有被拉伸,(0.2-0.4)以及(0.6-0.8)的部分被分别拉伸,然后填充了800的宽度。

padding的示例

我们添加上文字,并且结合padding来进行演示下,这里先设置padding距离边界都为0.2的百分比,示例代码如下:

textView.background = NinePatchDrawableBuilder()
            .setResourceData(
                resources = resources,
                resId = R.drawable.sample_2,
                horizontalMirror = false
            )
            .setPatchHorizontal(
                PatchRegionBean(start = 0.4f, end = 0.6f),
            )
            .setPatchVertical(
                PatchRegionBean(start = 0.4f, end = 0.6f),
            )
            .setPadding(
                paddingLeft = 0.2f,
                paddingRight = 0.2f,
                paddingTop = 0.2f,
                paddingBottom = 0.2f
            )
            .build()

显示效果如下:
Screenshot_2023-01-13-18-05-27-82_24cef02ef5c5f1a3ed9b56e4c5956272.jpg
然后将padding的边距都改为0.4的百分比,显示效果如下:
Screenshot_2023-01-13-18-05-49-15_24cef02ef5c5f1a3ed9b56e4c5956272.jpg

屏幕适配的示例

上述的图片都是在480dpi下显示的,这里我们将densityDpi设置为960,按道理来说拉伸图展示会小一倍,如下图所示:

textView.background = NinePatchDrawableBuilder()
            ......
            .setDensityDpi(densityDpi = 960)
            .build()

Screenshot_2023-01-14-19-18-35-82_24cef02ef5c5f1a3ed9b56e4c5956272.jpg

效果一览

整个工具类实现完毕后,又简单写了两个页面通过设置各种参数来实时预览图片拉伸和镜像以及padding的情况,效果展示如下:
zonghe.png

整体的探索过程到此基本就结束了,效果是实现了,然而性能和兼容性还无法保证,接下来需要进一步做下测试才能上线。可能有大佬很早就接触过这些功能,如果能指点下,鄙人则不胜感激。

文中若有纰漏之处还请大家多多指教。

参考文章

  1. Android 点九图机制讲解及在聊天气泡中的应用
  2. Android动态布局入门及NinePatchChunk解密
  3. Android点九图总结以及在聊天气泡中的使用

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/164311.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Pandas 安装与教程

前言Pandas 是 Python 语言的一个扩展程序库&#xff0c;用于数据分析。Pandas 是一个开放源码、BSD 许可的库&#xff0c;提供高性能、易于使用的数据结构和数据分析工具。Pandas 名字衍生自术语 "panel data"&#xff08;面板数据&#xff09;和 "Python data…

[apidoc]Apidoc-文档生成工具

Apidoc主要是用于生成API文档的工具&#xff0c;可以用于多种语言&#xff0c;包括java、javascript、php等 这里主要是为了写前端的APIDOC&#xff0c;方便交互是双方的使用; 工具的安装 工具包的安装 npm i apidoc [-g|-D]可以-g全局安装&#xff0c;或者-D局部安装,因为…

网盘系统|基于SpringBoot的网盘系统的设计与实现

作者主页&#xff1a;编程指南针 作者简介&#xff1a;Java领域优质创作者、CSDN博客专家 、掘金特邀作者、多年架构师设计经验、腾讯课堂常驻讲师 主要内容&#xff1a;Java项目、毕业设计、简历模板、学习资料、面试题库、技术互助 收藏点赞不迷路 关注作者有好处 文末获取源…

【无功优化】考虑泄流效应的光伏并网点电压系统侧无功优化(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

软考中级,【软件评测师】经验分享

&#xff0c;以下是我的考试成绩&#xff0c;一次通过很是幸运&#xff0c;希望把我的好运传递给大家&#xff0c;大家都能一次通过谈经验之前&#xff0c;先和大家说说考试的题型以及考试的内容&#xff0c;根据往年的考试题目我们可以很容易得知&#xff0c;软件评测师考试分…

Cisco(62)——PBR策略路由案例

场景1-单下一跳: 拓扑: 需求: R1和R2均连接100.100.100.100,R4看做一台PC,当PC访问100.100.100.100的时候优先走左边,当左边down掉之后切换到右边链路,使用PBR操作。 实现: 1.IP地址等基本配置 R4: R4(config)#no ip routingR4(config)#int e0/0 R4(config-if)#ip add…

Typora自动上传文章图片太难折腾?十三行JavaScript代码足矣

前言 Typora是我用过最爽的markdown文本编辑器了。但是有一点很让人难受&#xff0c;就是在写文章的时候&#xff0c;粘贴上的图片是本地路径。这就导致在复制文章到各大博客平台时发表&#xff0c;图片无法显示。然后需要各种办法去处理文章中的图片&#xff0c;不仅要手动上传…

【学习笔记】【Pytorch】十、线性层

【学习笔记】【Pytorch】九、线性层学习地址主要内容一、前言二、Pytorch的线性层三、Linear类的使用1.使用说明2.代码实现学习地址 PyTorch深度学习快速入门教程【小土堆】. 主要内容 一、前言 在神经网络中&#xff0c;我们通常用线性层来完成两层神经元间的线性变换。 …

【C++】面向对象---继承(万字详解)

目录前言一、继承的定义及概念二、继承方式三、基类和派生类之间的转换四、切片五、继承中的作用域六、派生类中的默认成员函数七、继承中的友元与静态成员继承与友元继承中的静态成员八、棱形继承和虚继承棱形继承虚继承总结前言 继承是面向对象的一个重点&#xff0c;而继承…

活动星投票医疗保障案例推介网络评选微信的投票方式线上免费投票

“医疗保障案例推介”网络评选投票_线上免费投票系统_功能齐全的微信投票_在线免费投票用户在使用微信投票的时候&#xff0c;需要功能齐全&#xff0c;又快捷方便的投票小程序。而“活动星投票”这款软件使用非常的方便&#xff0c;用户可以随时使用手机微信小程序获得线上投票…

图形编辑器:标尺功能的实现

大家好&#xff0c;我是前端西瓜哥。今天我们来实现图形编辑器的标尺功能。 项目地址&#xff1a; https://github.com/F-star/suika 线上体验&#xff1a; https://blog.fstars.wang/app/suika/ 标尺指的是画布上边和左边的两个有刻度的尺子&#xff0c;作用让用户知道他正在编…

java 探花交友day2 项目简介,环境搭建 登录验证码

技术方案&#xff1a; 项目结构&#xff1a; 项目概述 通过接口文档&#xff08;API文档&#xff09;定义规范 开发工具安装与配置 Linux虚拟机 YAPI 账号 tanhuaitcast.cn 密码123456 安装个安卓模拟器&#xff0c;然后安装APK 开发环境说明 初始工程搭建 阿里云短…

Leetcode:235. 二叉搜索树的最近公共祖先(C++)

目录 问题描述&#xff1a; 实现代码与解析&#xff1a; 递归&#xff1a; 原理思路&#xff1a; 精简版&#xff1a; 迭代&#xff1a; 原理思路&#xff1a; 问题描述&#xff1a; 给定一个二叉搜索树, 找到该树中两个指定节点的最近公共祖先。 百度百科中最近公共祖先…

1589_AURIX_TC275_PMU_Flash的基本特性以及操作

全部学习汇总&#xff1a; GreyZhang/g_TC275: happy hacking for TC275! (github.com) 关于这部分&#xff0c;感觉能够看到的比较有实践指导价值的信息不多。这里关于是否支持cache的信息&#xff0c;之前在内核手册等地方其实也看过了。 DFlash不支持buffer命中的功能&#…

21.Isaac教程--GEMS 导航堆栈简介

Isaac GEMS 导航堆栈简介 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 导航堆栈必须执行以下高级功能&#xff1a; Mapping 映射用于自动创建操作环境的地图。 该地图既用于定位&#xff0c;又用于路径规划。 它可以由具有附加功能的人进行注…

deap遗传算法 tirads代码解读

deap遗传算法 tirads代码解读写在最前面Overview 程序概览参考deap框架介绍creator模块创建适应度类Types定义适应度策略创建个体类Toolbox类创建种群&#xff08;个体、策略以及粒子&#xff09;Initialization1. 创建 attr_int 运算符2. 创建 individual_guess() 运算3.创建新…

学会python后:收集每天热点内容信息,并发送到自己的邮箱

嗨害大家好鸭&#xff01;我是小熊猫~ 实现目的 本篇文章内容主要为如何用代码&#xff0c;把你想要的内容&#xff0c;以邮件的形式发送出去 内容可以自己完善&#xff0c;还可以设置一个定时发送&#xff0c;或者开机启动自动运行代码 代理注册与使用 注册账号并登录 生成ap…

使用TDengine时序数据库的介绍以及系统整合

目录 一、 如何使用 安装目录介绍 数据文件查看和备份 客户端连接 sql使用 二、 系统整合 Java连接配置 Demo示例 三、 对采集点、超级表、子表和设备等关系进行维护 一、 如何使用 安装目录介绍 目录/文件 说明 /usr/local/taos/bin TDengine 可执行文件目录…

css笔记2

目录 选择器进阶 1、复合选择器 1.1后代选择器&#xff1a;空格 1.2 子代选择器&#xff1a; > 2、并集选择器&#xff1a;&#xff0c; 3、交集选择器 4、hover伪类选择器 Emmet语法 背景相关属性 1.1背景颜色 2.1背景图片 3.1背景平铺 4.1背景位置 5.1背景相关属…

linux中断机制

目录 1.中断机制 1.1.中断流程图 1.2.代码结构图 2.中断代码 2.1.硬件中断 2.2.asm.s 2.3.trap.c 2.3.1.trap_init函数 2.3.2.die函数 2.4 .sys_call.s 2.4.1._system_call.s 3.总结 1.中断机制 何为中断&#xff0c;中断里面各种名词的区分&#xff0c;请看下面这几篇…