【Android】自定义View组件,并实现在 Compose、Kotlin、Xml 中调用

news2024/11/17 16:28:32

从事 Android 开发以来,很少有过自定义 View 的相关开发需求,大部分 UI 都是可以集成某些官方组件,在组件的基础上完成能够大大缩短开发时间。但今天我要讲的是:如何使用 Android 开发一个Compose、Xml都可以调用的组件?接下来请跟随我的脚步一起去学习 View 的自定义组件开发吧。

目录

  • Android 屏幕坐标
  • 自定义 View 的方式
  • 自定义 View
    • 初始化
      • 重写构造函数
      • 自定义 XML 属性
    • 测量大小 onMeasure
    • 确定大小 onSizeChanged
    • 确定子布局位置 onLayout
    • 绘制 onDraw
  • 在Xml中引用
  • 在Activity中引用
  • 在Compose中引用
  • 最终效果

Android 屏幕坐标

自定义 View 之前,需要先了解 View 的坐标,知道哪里是起点,哪里是终点,才能更好的展开工作。

经历过九年义务教育的朋友们,相信大家都见过下面的这幅图 👇

平面直角坐标系
很眼熟吧?这张图片的东西被称之为 👉 平面直角坐标系。

在 Android 系统上,也是用的 平面直角坐标系 来确定 View 的方向、大小,只不过它是“倒”过来的平面直角坐标系,如下图👇

在这里插入图片描述
不明白?我们再把这个图片代入到 Android 屏幕上来

在这里插入图片描述
看懂了吧?在 Android 系统上,直角坐标系的原点就是屏幕的左上角,往右是 x 轴,往下是 y 轴,整个 Android 的屏幕就是处于 平面直角坐标系 的第四象限上,其坐标的单位则使用的是像素(px) 来表示。如果你的手机是 1080*1920 像素,则意味着,以原点为起点,至屏幕的右侧,共有 1080 像素 (px) ,以原点为起点,至屏幕的底部,共有 1920 个像素(px)。

自定义 View 的方式

在 Android 中,自定义 View 一般可分为两种方式:继承 ViewGroup 或 View 实现自定义。

  • ViewGroup
    自定义 ViewGroup 一般是利用现有的组件根据特定的布局方式来组成新的组件,大多继承自 ViewGroup 或各种 Layout ,包含子 View。
  • View
    在没有现成的View,需要自己实现的时候,就是用自定义 View,一般继承自 View、SurfaceView 或其它的 View。

本文只讲解通过继承 View 来实现自定义 View,通过继承 ViewGroup 实现自定义 View 的文章可参考往期文章👉【Android】实现自定义标题栏

自定义 View

View 完成自定义的过程需要经历:初始化 → onMeasure → onSizeChanged → onLayout → onDraw 五个阶段。

初始化

重写构造函数

View 的初始化方式可通过四种构造函数进行初始化,如下:

constructor(context: Context?) : this(context, null)

constructor(context: Context?, attrs: AttributeSet?) : this(context, attrs, 0)

constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : this(context, attrs, defStyleAttr, 0)

constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) {}
构造函数使用场景
public View(Context context)一般在 Activity、Fragment 中使用(本文使用该构造函数):
View view = new View(context);
public View(Context context, AttributeSet attrs)当从XML文件构造视图,提供XML文件中指定的属性时,会调用此函数(本文使用该构造函数):
< View android:layout_width=“wrap_content” android:layout_height=“wrap_content”/>
public View(Context context, AttributeSet attrs, int defStyleAttr)从XML执行膨胀,并从主题属性应用特定于类的基本样式。View的这个构造函数允许子类在膨胀时使用自己的基本样式。
public View(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes)从XML执行膨胀,并从主题属性或样式资源应用特定于类的基本样式。View的这个构造函数允许子类在膨胀时使用自己的基本样式。(本文使用该构造函数)

注意:重写构造方法时,拥有四个参数的那个构造函数必须使用super用于访问父类的构造方法,另外三个构造方法,则需要使用this指引下一个构造方法。这样,当调用第一、二、三个构造方法时,就会执行第四个构造方法,使用该方式才能使UI渲染上,否则会出现实例化 View 无效的情况出现。具体参考上方的四个构造方法。

自定义 XML 属性

如果自定义的 View 在 XML 布局上用的到,自定义属性这一步则少不了,届时需要在 res/values/arrts.xml 文件夹添加 XML 的属性。若没有 arrts.xml 文件则需手动创建。创建完成后在其中编写的代码如下:

<resources>
    <declare-styleable name="StepView">
        <attr name="type">
	        <!--      添加枚举,在布局样式使用type属性时可直接使用以下的选项      -->
            <enum name="start" value="0" />
            <enum name="middle" value="1" />
            <enum name="stop" value="2" />
        </attr>
        <attr name="text" format="string" localization="suggested" />
        <attr name="textSize" format="dimension" />
        <attr name="style" format="integer" >
	        <!--      添加枚举,在布局样式使用style属性时可直接使用以下的选项      -->
            <enum name="selected" value="10"/>
            <enum name="not_selected" value="11"/>
        </attr>
    </declare-styleable>
</resources>

编写了名为StepView的属性样式,需要在自定义 View 类里的构造函数中使用 Context.obtainStyledAttributes函数引用,通过循环遍历的方式找到属性赋值给对应的值,这时,就完成了 XML 布局属性的自定义。

constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int, ) : super(context, attrs, defStyleAttr, defStyleRes) {
    //获取定义的一些属性
    val styleAttrs = context!!.obtainStyledAttributes(attrs, R.styleable.StepView, defStyleAttr, 0)
    //数一数有多少个属性呢
    val indexCount = styleAttrs.indexCount
    // 循环遍历的方式,找到我们所定义的一些属性
    for (i in 0..indexCount) {
        //根据索引值给java代码中的成员变量赋值
        when (val index = styleAttrs.getIndex(i)) {
            R.styleable.StepView_text -> text = styleAttrs.getString(index).toString() 
            R.styleable.StepView_textSize -> textSize = styleAttrs.getDimension(index, 2f)
            R.styleable.StepView_type -> type = styleAttrs.getInt(index, type)
            R.styleable.StepView_style -> style = styleAttrs.getInteger(index, STYLE_NOT_SELECTED)
        }
    }
    //资源文件中的属性回收
    styleAttrs.recycle()
}

测量大小 onMeasure

为什么要测量 View 大小?

View 的大小不仅由自身大小所决定,同时也受到父控件的影响,为了我们的控件能更好的适应各种情况,一般需要自己进行测量。

测量 View 大小使用的是 View 的 onMeasure(int widthMeasureSpec, int heightMeasureSpec) 方法进行测量,为了更好的适配各种分辨率,自定义 View 的过程中,必须由重写该方法。

重写方法,还需要使用setMeasuredDimension(int measuredWidth, int measuredHeight)方法使测量好的宽高产生效果,如下:

override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    // 获取宽高的测量模式
    val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec)
    val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec)
    // 获取宽高的测量大小
    val widthSpecSize = MeasureSpec.getSize(widthMeasureSpec)
    val heithtSpecSize = MeasureSpec.getSize(heightMeasureSpec)
    if (layoutParams.width == ViewGroup.LayoutParams.WRAP_CONTENT && layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
        val fl = mWidthTotal.toFloat() / 1.5f
        setMeasuredDimension(fl.toInt() + indentAndBulge.toInt(), mHeight.toInt())
    } else if (layoutParams.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
        // 宽 / 高任意一个布局参数为= wrap_content时,都设置默认值
        val fl = mWidthTotal.toFloat() / 1.5f
        setMeasuredDimension(fl.toInt() + indentAndBulge.toInt(), heithtSpecSize)
    } else if (layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
        // 宽 / 高任意一个布局参数为= wrap_content时,都设置默认值
        setMeasuredDimension(widthSpecSize, mHeight.toInt())
    }
}

注意:若是没有重写onMeasure方法并完成测量,在给该组件定义宽高为wrap_content时,组件的宽、高度会默认跟随父类。具体原因请查看👉Android 自定义View:为什么你设置的wrap_content不起作用?

确定大小 onSizeChanged

在测量完 View 并使用 setMeasuredDimension(int measuredWidth, int measuredHeight) 函数之后,View 的大小基本上已经确定,那为什么还需要再次确定 View 的大小呢?

这是因为 View 的大小不仅由 View 本身控制,而且受父控件的影响,所以我们在确定 View 大小的时候最好使用系统提供的 onSizeChanged(int w, int h, int oldw, int oldh) 回调函数。

override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
    super.onSizeChanged(w, h, oldw, oldh)
    Log.e("onSizeChanged", "View的宽度:$w,高度$h")
}

onSizeChanged 方法的四个参数分别为:

  • w – Current width of this view.
  • h – Current height of this view.
  • oldw – Old width of this view.
  • oldh – Old height of this view.

此函数相对简单,我们只需要关注其宽度(w)、高度(h) 即可,这两个参数就是 View 的最终大小。该函数只会在 View 的大小发生改变时自动触发,例如:初始化View、界面横竖屏的切换等。

确定子布局位置 onLayout

确定子布局的函数是 onLayout(boolean changed, int left, int top, int right, int bottom) ,它用于确定子 View 在父 View 的位置。
当此视图应为其每个子级分配大小和位置时,从布局调用。具有子级的派生类应重写此方法,并在其每个子级上调用布局。

/**
 * 如果自定义的 View 有子组件时,必须重写该方法,用以确定子组件在 View 中的位置.
 * 如果有需要自定义有子 View 的组件时,应该继承 ViewGroup 而不是 View,具体看情况自己分析
 */
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
    super.onLayout(changed, left, top, right, bottom)
    Log.e("onLayout","$left,$top,$right,$bottom")
}

绘制 onDraw

扯了这么多,终于来到自定义 View 的最后一步了!

在上文我们说到 Android 的 平面直角坐标系 以及 Android 屏幕在 平面直角坐标系 中的位置是位于第四象限。那接下来我们如何在 Canvas 画布上绘制出自己想要的样式呢?

Android 中,提供了 Canvas 作为画画的载体,所绘制的东西最终呈现在 Canvas 上,因此也可以理解为 Canvas 是一张纸,Paint 则是五颜六色的笔。Canvas 作为画布(白纸),提供了多个在画布上画的函数给我们调用:

Canvas FunctionDescribe
drawARGB使用 srcover porterduff 模式,用指定的 ARGB 颜色填充整个画布的位图(仅限于当前剪辑)。
drawArc绘制指定的圆弧,该圆弧将缩放以适合指定的椭圆形。
drawBitmap使用指定的矩阵绘制位图。
drawBitmapMesh通过网格绘制位图,其中网格顶点均匀分布在位图上。
drawCircle使用指定的颜料绘制指定的圆。
drawColor使用指定的颜色和混合模式填充整个画布的位图(仅限于当前剪辑)。
drawDoubleRoundRect使用指定的 paint 绘制双圆角矩形。
drawGlyphs使用指定字体绘制字形数组。
drawLine使用指定的绘图绘制具有指定起点和终点 x,y 坐标的线段。
drawMesh将网格对象绘制到屏幕上。
drawOval使用指定的颜料绘制指定的椭圆形。椭圆形将根据绘画中的样式进行填充或加框。
drawPaint用指定的绘画填充整个画布的位图(仅限于当前剪辑)。
drawPatch将指定的位图绘制为 N 面片(最常见的是 9 面片)。
drawPath使用指定的油漆绘制指定的路径。路径将根据绘画中的样式进行填充或加框。
drawPicture绘制图片,拉伸以适合 dst 矩形。
drawPoint用于绘制单个点的 drawPoints() 的帮助程序。
drawRGB使用 srcover porterduff 模式,用指定的 RGB 颜色填充整个画布的位图(仅限于当前剪辑)。
drawRect使用指定的绘制绘制指定的矩形,矩形将根据绘画中的样式进行填充或加框。
drawRenderNode绘制给定的 RenderNode。
drawRoundRect使用指定的油漆绘制指定的圆角矩形,圆角矩形将根据绘画中的样式进行填充或加框。
drawText在指定的 Paint 中绘制指定范围的文本,由开始/结束指定,其原点位于 (x,y)。
drawTextOnPath使用指定的绘画,沿着指定的路径绘制文本,原点位于 (x,y)。
drawTextRun绘制一系列文本,全部在一个方向上,并带有用于复杂文本形状的可选上下文。
drawVertices绘制顶点数组,解释为三角形(基于模式)。

此处展示的函数并非全部,详情请参考👉Canvas

本文自定义 View 绘制的 UI 只需要使用到 drawTextdrawPath 两个 Canvas 函数进行绘制。drawPath 在这里用来绘制背景,drawText绘制文字。

接下来绘制的图形:

在这里插入图片描述
使用 drawPath 进行绘制前,需要先使用 Path 定义好 drawPathcanvas 经过的几个点,这里画个图,以便参考:

在这里插入图片描述
接着写代码实现:

override fun onDraw(canvas: Canvas) {
    super.onDraw(canvas)
    // 新建绘制的路径
    val path = Path()
    // 第一步,定原点
    path.moveTo(0f, 0f)
    // 第二步
    path.lineTo(totalWidth, 0f)
    // 第三步
    val rightBulge = totalWidth + indentAndBulge
    path.lineTo(rightBulge, halfHeight)
    // 第四步
    path.lineTo(totalWidth, mHeight)
    // 第五步
    path.lineTo(0f, mHeight)
    // 回到原点,闭合图形
    path.lineTo(0f, 0f)
    path.close()
    // 将图形绘制出来
    canvas.drawPath(path, mPaint)
}

完成了背景的绘制,接下来绘制 Text

mPaint.color = if (style == STYLE_NOT_SELECTED) context.getColor(R.color.font_black) else context.getColor(R.color.white)
canvas.drawText(text, startPointText, mHeight / 1.5f, mPaint)

至此,完成了自定义的全部过程。

在Xml中引用

在 xml 中引用和官方的 View 组件引用没有太大的差别,如下:

<com.miyue.stepdemo.StepView
    android:id="@+id/step1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

我们自定义开始时,在attrs.xml文件里自定义了一些组件的属性,添加后如下:

<com.miyue.stepdemo.StepView
    android:id="@+id/step1"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:style="selected"
    app:text="第一步"
    app:type="start"/>

注意:在 xml 中初始化的组件,是无法使用Debug断点调试的,但可通过Logcat查看日志信息。

在Activity中引用

val step = StepView(this)
step.setStyle(StepView.STYLE_SELECTED)
step.setType(StepView.TYPE_START)
step.setText("第一步")
step.setTextSize(30f)
val linearLayout = findViewById<LinearLayout>(R.id.ll_step2)
linearLayout.addView(step)

在Compose中引用

如果你的 Android 项目是 Java 项目,建议你创建一个 Kotlin 项目或者 Compose 项目。如果你的项目是 Kotlin 项目,则可以通过以下 的方式创建一个 Compose 界面:鼠标右键包名 → New → Compose → Empty Activity。

在这里插入图片描述
完成上述步骤即可获得一个 Compose 的 Activity,通过以下代码即可完成调用继承自 View 的组件调用。

@Composable
fun CustomView() {
    AndroidView(
        modifier = Modifier.fillMaxSize(), // Occupy the max size in the Compose UI tree
        factory = { context ->
            // Creates view
            StepView(context).apply {
                setText("第一步")
                setTextSize(30f)
                setStyle(StepView.STYLE_SELECTED)
                setType(StepView.TYPE_START)
            }
        },
        update = { view ->
            // 视图已膨胀或此块中读取的状态已更新
            // 如有必要,在此处添加逻辑
            // 由于selectedItem在此处阅读,AndroidView将重新组合
            // 每当状态发生变化时
            // 撰写示例->查看通信

            // 更新样式
            view.setText("第二步")
        }
    )
}

最终效果

在这里插入图片描述

点击前往下载代码👉StepDemo

总结

能找美工处理的就找美工处理,能不碰 View 自定义就不要碰 View 自定义,一旦开始自定义,意味着需要花很长的时间去处理自定义产生的各种适配问题,投入的时间用与产生的收益不成正比。

参考文档

1、【扔物线】UI-1 Drawing
2、HenCoder Android 开发进阶: 自定义 View 1-1 绘制基础
3、HenCoder Android 开发进阶: 自定义 View 1-2 Paint 详解
4、【Android Developer】在 Compose 中使用 View

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

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

相关文章

Vue中break关键字

Change() {//每次触发该事件&#xff0c;都要讲data重新赋值一次this.data JSON.parse(JSON.stringify(this.data1));// 根据选中的等级更新数据switch (this.selectedlevel) {case 1:// 更新数据为一级数据this.data this.data.filter(item > item.level "1"…

架构(1)

目录 1.如何理解架构的演进&#xff1f; 2.如何理解架构的服务化趋势&#xff1f; 3.架构中有哪些技术点&#xff1f; 4.谈谈架构中的缓存应用&#xff1f; 5.在开发中缓存具体如何实现&#xff1f; 1.如何理解架构的演进&#xff1f; 初始阶段的网站架构应用服务和数据服…

2024年1月6日 十二生肖 今日运势

小运播报&#xff1a;2024年1月6日&#xff0c;星期六&#xff0c;农历十一月廿五 &#xff08;癸卯年乙丑月己巳日&#xff09;&#xff0c;法定节假日。 红榜生肖&#xff1a;牛、猴、鸡 需要注意&#xff1a;鼠、虎、猪 喜神方位&#xff1a;东北方 财神方位&#xff1a…

【2024最新版】Win11基础配置操作(磁盘分区、修改各种默认存储位置、安装软件操作)【释放C盘空间】

文章目录 一、硬盘分区0. 磁盘管理1. 压缩卷2. 新建简单卷向导 二、修改默认存储位置1. 保持新内容的地方a. 位置b. 操作 2. 快速访问六件套a. 位置b. 操作 三、安装软件0. 应用商店设置a. 设置中心b. 修改下载设置 1. 微信电脑版设置a. 下载b. 安装c. 聊天记录迁移与备份d. 存…

MySQL-DCL

DCL是数据控制语言&#xff0c;用来管理数据库用户&#xff0c;控制数据库的访问权限。 管理用户&#xff1a;管理哪些用户可以访问哪些数据库 1.查询用户 USE mysql; SELECT * FROM user; 注意&#xff1a; MySQL中用户信息和用户的权限信息都是记录在mysql数据库的user表中的…

Vue入门一(前端发展史|Vue介绍|Vue插值语法|Vue指令|style与class使用|条件渲染)

文章目录 一、前端的发展史二、Vue介绍 和 基本使用1) Vue介绍2) Vue特点3) M-V-VM思想1.MVVM介绍2.MVVM的特性3.MVVM逻辑 4) 组件化开发、单页面开发组件化开发单页面开发 5) 引入方式6) 补充解释型的语言是需要解释器的 nodejs&#xff1a;一门后端语言7) 快速使用 三、Vue之…

力扣1944.队列中可以看到的人数--单调栈

思路&#xff1a; 由题知一个人能 看到 他右边另一个人的条件是这两人之间的所有人都比他们两人 矮 &#xff0c;也就是说&#xff0c;在自己右边第一个比自己高的人后面的人就肯定看不到了那么只需要找到右边第一个比自己高的人与自己之间的所有满足要求的人就行了&#xff0…

PyTorch|一次画一批图像

想想这样一个场景&#xff0c;我们训练了一个神经网络&#xff0c;输入一些信息&#xff0c;这个网络可以根据信息为我们生成相关图片。 这些图片并不是一张&#xff0c;而是多张&#xff0c;我们想把这些图片一次全部显示出来&#xff0c;而不是一张一张的显示&#xff08;这…

CSS 缩小旋转动画

<template><div class="container" @mouseenter="startAnimation" @mouseleave="stopAnimation"><!-- 旋方块 --><div class="box" :class="{ rotate-scale-down: isAnimating }"><!-- 元素内容…

Linux 进程(十) 进程替换

用fork创建子进程后执行的是和父进程相同的程序(但有可能执行不同的代码分支),子进程往往要调用一种exec*函数以执行另一个程序。当进程调用一种exec*函数时,该进程的用户空间代码和数据完全被新程序替换,从新程序的启动例程开始执行。调用exec*并不创建新进程,所以调用exec*前…

stable diffusion 人物高级提示词(四)朝向、画面范围、远近、焦距、机位、拍摄角度

一、朝向 英文中文front view正面Profile view / from side侧面half-front view半正面Back view背面(quarter front view:1.5)四分之一正面 prompt/英文中文翻译looking at the camera看向镜头facing the camera面对镜头turned towards the camera转向镜头looking away from …

c语言题目之统计二级制数中1的个数

文章目录 题目一、方法1二、方法2三&#xff0c;方法3总结 题目 统计二进制数中1的个数 输入一行&#xff0c;输出一行 输入&#xff1a; 输入一个整数 输出&#xff1a; 输出存储在内存中二进制的1的个数 一、方法1 之前的文章中&#xff0c;小编写了有关于内存在二进制中的存…

基于YOLOv7算法的高精度实时安全背心目标检测识别系统(PyTorch+Pyside6+YOLOv7)

摘要&#xff1a;基于YOLOv7算法的高精度实时安全背心目标检测系统可用于日常生活中检测与定位安全背心&#xff0c;此系统可完成对输入图片、视频、文件夹以及摄像头方式的目标检测与识别&#xff0c;同时本系统还支持检测结果可视化与导出。本系统采用YOLOv7目标检测算法来训…

flutter 五:MaterialApp

MaterialApp const MaterialApp({super.key,this.navigatorKey, //导航键this.scaffoldMessengerKey, //scaffold管理this.home, //首页Map<String, WidgetBuilder> this.routes const <String, WidgetBuilder>{}, //路由this.initialRoute, //初始路由th…

四种“栈溢出检测方法”实现分析(2种纯软件、一种纯硬件、一种软硬件结合)

1、两种纯软件的栈溢出检测方法 参考博客&#xff1a;《freeRTOS的栈溢出检测机制》&#xff1b; 2、纯硬件&#xff1a;使用栈限制寄存器 2.1、工作逻辑分析 前提条件&#xff1a;使用满减栈硬件上提供栈限制寄存器&#xff08;用SP_limit表示&#xff09;&#xff0c;可以…

互联网广告行业发展历程

在20年的历程中&#xff0c;广告主与媒体方持续面对着一些问题&#xff0c;一些核心问题推动了行业的迭代。 互联网广告经过了20年左右的高速发展&#xff0c;已愈发成熟&#xff0c;其历程是有趣的。 对互联网广告发展的理解&#xff0c;网上的文章并不多&#xff0c;已有的…

AIGC初探:提示工程 Prompt Engineering

简介 提升工程是什么 提示工程&#xff08;Prompt Engineering&#xff09;是人工智能领域中的一个概念&#xff0c;特别是在自然语言处理&#xff08;NLP&#xff09;领域中。它是一种通过设计和优化输入提示来提高AI模型表现的方法。 对于基于转换器的大型语言模型&#x…

无监督学习(K-Means)的认识

目录 一、无监督学习 二、无监督学习和有监督学习的区别 三、K-Means 3.1数据分析 3.2k-meas算法 3.3数据正态化后k-means 3.4找最佳k&#xff08;Elbow Plot&#xff09; 四、k-means算法的优缺点 一、无监督学习 无监督学习是一种机器学习的方法&#xff0c;…

机器学习--回归算法

&#x1f333;&#x1f333;&#x1f333;小谈&#xff1a;一直想整理机器学习的相关笔记&#xff0c;但是一直在推脱&#xff0c;今天发现知识快忘却了&#xff08;虽然学的也不是那么深&#xff09;&#xff0c;但还是浅浅整理一下吧&#xff0c;便于以后重新学习。 &#x1…

Eclipse设置不依赖系统环境变量,设置lombok

设置不依赖系统环境变量&#xff0c;如图首行添加 -vm. lombok配置在最后两行