Android自定义view从入门到高级

news2024/12/23 5:29:36

简介

        什么是自定义view?我认为只要不是编译器直接提供可以使用的view,都可以认为是自定义view。自定义view主要分为两大类,第一类自定义view可以通过系统提供的各种view组合,样式变化实现的view。第二类是通过继承view或者ViewGroup类,通过ondraw方法绘制的view,onMeasure来定义视图的测量逻辑,onLayout来定义视图的布局逻辑,以及处理用户交互的方法。

入门-学会通过drawable和组合view实现

       1、通过在drawable目录下创建一个xml文件

        使用shape来实现各种矩形、圆角矩形、椭圆形、圆形、线条等。通过shape定义图形的边框、填充颜色、渐变、圆角半径等属性来创建各种视觉效果。这些shape可以用作背景、边框或者作为图形元素来装饰UI组件。

        2、通过view的组合来实现想要的效果

        1. layout_width:指定视图的宽度。
        2. layout_height:指定视图的高度。
        3. layout_margin:指定视图与其父布局或相邻视图之间的外边距。
        4. layout_gravity:指定视图在其父布局中的对齐方式。
        5. layout_weight:指定视图在线性布局中的权重,用于实现权重分配。
        6. layout_alignParentTop、layout_alignParentBottom、layout_alignParentLeft、                    layout_alignParentRight:用于相对布局,指定视图相对于父布局的对齐方式。

        。。。

初级-通过ondraw绘制简单图形

        1、了解canvas的一些常见操作

操作类型相关API备注
绘制颜色drawColor, drawRGB, drawARGB使用单一颜色填充整个画布
绘制基本形状drawPoint, drawPoints, drawLine, drawLines, drawRect, drawRoundRect, drawOval, drawCircle, drawArc依次为 点、线、矩形、圆角矩形、椭圆、圆、圆弧
绘制图片drawBitmap, drawPicture绘制位图和图片
绘制文本drawText, drawPosText, drawTextOnPath依次为 绘制文字、绘制文字时指定每一个文字位置、依据路径绘制文字
绘制路径drawPath绘制路径。绘制贝塞尔曲线时也须要用到该函数
顶点操作drawVertices, drawBitmapMesh通过对顶点操作能够使图像形变,drawVertices直接对画布作用、 drawBitmapMesh仅仅对绘制的Bitmap作用
画布剪裁clipPath, clipRect设置画布的显示区域
画布快照save, restore, saveLayerXxx, restoreToCount, getSaveCount依次为 保存当前状态、 回滚到上一次保存的状态、 保存图层状态、 回滚到指定状态、 获取保存次数
画布变换translate, scale, rotate, skew依次为 位移、缩放、 旋转、错切
Matrix(矩阵)getMatrix, setMatrix, concat实际画布的位移。缩放等操作的都是图像矩阵Matrix,仅仅只是Matrix比較难以理解和使用。故封装了一些经常使用的方法。

        2、了解自定义view的一些常见方法

class CustomView:View {
    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    constructor(
        context: Context?,
        attrs: AttributeSet?,
        defStyleAttr: Int,
        defStyleRes: Int
    ) : super(context, attrs, defStyleAttr, defStyleRes)

    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
    }

    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
        super.onLayout(changed, left, top, right, bottom)
    }

    override fun onTouchEvent(event: MotionEvent?): Boolean {
        return super.onTouchEvent(event)
    }
}

        这些构造方法允许你以不同的方式在代码中或者XML布局中创建自定义视图实例,并且提供了不同的参数组合来满足不同的需求。不写对应的构造方法,就不能使用这个方法去创建view。

1. constructor(context: Context?) : super(context):这个构造方法接受一个Context参数,用于在代码中动态创建视图实例。它调用了父类View的对应构造方法。 

2. constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs):这个构造方法接受Context和AttributeSet参数,用于在XML布局文件中使用自定义视图时创建实例。它也调用了父类View的对应构造方法。

3. constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr):这个构造方法接受Context、AttributeSet和defStyleAttr参数,用于在XML布局文件中使用自定义视图时创建实例,并且指定了默认的样式。它同样调用了父类View的对应构造方法。

4. constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes):这个构造方法接受Context、AttributeSet、defStyleAttr和defStyleRes参数,用于在XML布局文件中使用自定义视图时创建实例,并且指定了默认的样式和主题。同样,它调用了父类View的对应构造方法。

        下面的一些方法,是完成一个自定义view的核心方法。

1. onDraw(canvas: Canvas?):这个方法用于定义视图的绘制逻辑。你可以在这里使用Canvas对象来绘制你所需的图形、文本或者其他视觉元素。

2. onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int):这个方法用于定义视图的测量逻辑。在这里,你可以根据测量规格(MeasureSpec)来计算视图的宽度和高度,并通过setMeasuredDimension方法来设置测量结果。

        在这个阶段,系统会通过调用View的measure()方法来测量View的大小。在测量过程中,View会确定自己的宽度和高度,并为其子View提供测量规格。 - View的测量规格通过MeasureSpec来表示,包括三种模式:EXACTLY、AT_MOST和UNSPECIFIED。  EXACTLY模式表示View的大小已经确定,如设置了具体的数值或match_parent属性。 AT_MOST模式表示View的大小不能超过某个边界,如设置了wrap_content属性。  UNSPECIFIED模式表示View的大小没有限制,如在ScrollView中的子View。  在measure()方法中,View会根据测量规格计算自己的测量宽度和高度,并通过setMeasuredDimension()方法设置测量结果。


3. onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int):这个方法用于定义视图的布局逻辑。在这里,你可以根据视图的尺寸和位置来安排视图的子视图的位置。

        在这个阶段,系统会通过调用View的layout()方法来确定View在父容器中的位置。每个View都有自己的布局参数(LayoutParams),父容器会根据这些参数来摆放子View。  在layout()方法中,View会根据父容器传递的布局参数,计算自己的左上角坐标和右下角坐标,然后通过setFrame()方法设置自己的位置。

4. onTouchEvent(event: MotionEvent?):在这个方法中,你可以处理触摸事件,包括按下、移动、抬起等操作。根据需要,你可以返回true表示消费了该事件,或者返回false将事件传递给父视图或其他视图处理。

        如果使用的是一个viewgroup,那么它还有一些额外的常用方法

class CustomViewGroup:ViewGroup {
    constructor(context: Context?) : super(context)
    constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
    constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    )

    override fun onLayout(p0: Boolean, p1: Int, p2: Int, p3: Int, p4: Int) {
        
    }

    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        return super.onInterceptTouchEvent(ev)
    }

    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        return super.dispatchTouchEvent(ev)
    }
}

 1. onLayout方法:这个方法用于定义子视图在ViewGroup中的布局位置。当ViewGroup需要摆放子视图时,系统会调用这个方法来指定子视图的位置。

2. onInterceptTouchEvent方法:这个方法用于拦截触摸事件。当自身需要拦截触摸事件时,可以重写这个方法来返回true,从而拦截事件的传递。

3. dispatchTouchEvent方法:这个方法用于分发触摸事件。当触摸事件到达ViewGroup时,系统会调用这个方法来分发事件给子视图或者自身进行处理。

通过重写这些方法,你可以实现自定义的布局逻辑、触摸事件处理逻辑,以及事件拦截逻辑,从而实现定制化的ViewGroup行为。

3、实践应用

绘制几个简单常用的图形

绘制扇形,可以通过代码看到,绘制起始位置是3点钟方向,而不是0点方向

class SectorView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    /**扇形的画笔*/
    private var sectorPaint: Paint = Paint()

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        initPaint()
        drawSector(canvas)
    }

    /**
     * 绘制扇形
     * */
    private fun drawSector(canvas: Canvas) {
        val rect = RectF(
            10f,
            10f,
            150f,
            150f
        )
        canvas.drawArc(rect,0f,100f,true,sectorPaint)
    }

    /**
     * 初始化画笔
     * */
    private fun initPaint() {
        //当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式, 如圆形样Cap.ROUND,或方形样式Cap.SQUARE
        sectorPaint.strokeCap = Paint.Cap.ROUND
        //设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。
        sectorPaint.isAntiAlias = true
    }
}

绘制饼状图就是绘制多个扇形,这里是写死的数据作为演示,需要动态修改扇形区域就要通过数据来计算每一个颜色区域的开始位置,所占的比例。


class SectorView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    /**扇形的画笔*/
    private var sectorPaint: Paint = Paint()

    override fun onDraw(canvas: Canvas) {
        super.onDraw(canvas)
        initPaint()
        drawSector(canvas)
    }

    /**
     * 绘制扇形
     * */
    private fun drawSector(canvas: Canvas) {
        val rect = RectF(
            10f,
            10f,
            150f,
            150f
        )
        canvas.drawArc(rect,0f,100f,true,sectorPaint)
        sectorPaint.color = Color.parseColor("#FFBB86FC")
        canvas.drawArc(rect,100f,50f,true,sectorPaint)
        sectorPaint.color = Color.parseColor("#FF6200EE")
        canvas.drawArc(rect,150f,60f,true,sectorPaint)
        sectorPaint.color = Color.parseColor("#FF03DAC5")
        canvas.drawArc(rect,210f,150f,true,sectorPaint)
    }

    /**
     * 初始化画笔
     * */
    private fun initPaint() {
        //当画笔样式为STROKE或FILL_OR_STROKE时,设置笔刷的图形样式, 如圆形样Cap.ROUND,或方形样式Cap.SQUARE
        sectorPaint.strokeCap = Paint.Cap.ROUND
        //设置是否使用抗锯齿功能,会消耗较大资源,绘制图形速度会变慢。
        sectorPaint.isAntiAlias = true
        sectorPaint.color = Color.parseColor("#FF000000")
    }
}

        仪表盘主要是先绘制一个圆,然后绘制刻度,刻度采用for循环,从第一个刻度开始,每次xy坐标按规律增加,特殊刻度就单独定制。这里的仪表盘,我们就通过系统提供的onMeasure方法获取到这个控件宽高,通过宽高来绘制仪表盘,就不会导致绘制的过大,超过显示范围,或者绘制的过小,也可以适配不同的屏幕分辨率。

class ClockView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
    private var mWidth = 0f
    private var mHeight = 0f
    override fun onDraw(canvas: Canvas) {
        //画外圆
        val paint = Paint()
        paint.style = Paint.Style.STROKE
        paint.strokeWidth = 2f
        canvas.drawCircle(mWidth / 2, mHeight / 2, mWidth / 2, paint)
//        canvas.drawLine(mWidth / 2, mHeight / 2, mWidth, mHeight, paint)
        //画刻度线
        val paint1 = Paint()
        paint.strokeWidth = 3f
        for (i in 0..23) {
            if (i == 0 || i == 6 || i == 12 || i == 18) {
                paint1.strokeWidth = 5f
                paint1.textSize = 30f
                canvas.drawLine(
                    mWidth / 2,
                    mHeight / 2 - mWidth / 2,
                    mWidth / 2,
                    mHeight / 2 - mWidth / 2 + 60,
                    paint1
                )
                val degree = i.toString()
                canvas.drawText(
                    degree,
                    mWidth / 2 - paint1.measureText(degree) / 2,
                    mHeight / 2 - mWidth / 2 + 90,
                    paint1
                )
            } else {
                paint1.strokeWidth = 3f
                paint1.textSize = 15f
                canvas.drawLine(
                    mWidth / 2,
                    mHeight / 2 - mWidth / 2,
                    mWidth / 2,
                    mHeight / 2 - mWidth / 2 + 30,
                    paint1
                )
                val degree = i.toString()
                canvas.drawText(
                    degree,
                    mWidth / 2 - paint1.measureText(degree) / 2,
                    mHeight / 2 - mWidth / 2 + 60,
                    paint1
                )
            }
            canvas.rotate(15f, mWidth / 2, mHeight / 2)
        }
        //画指针
        val paintHour = Paint()
        paintHour.strokeWidth = 20f
        val paintMinute = Paint()
        paintMinute.strokeWidth = 10f
        canvas.save()
        canvas.translate(mWidth / 2, mHeight / 2)
        canvas.drawLine(0f, 0f, 100f, 100f, paintHour)
        canvas.drawLine(0f, 0f, 100f, 200f, paintMinute)
        canvas.restore()
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        mWidth = measuredWidth.toFloat() - 20
        mHeight = measuredHeight.toFloat() - 20
    }
}

中级-掌握点击事件的传递分发

     1、了解view的事件传递分发机制

         初级的时候,我们只需要掌握绘制一些静态图形技巧,到了中级,我们就需要让这些view动起来,这个动起来效果,我们经常是设置不同的值,再通知view重新绘制,达到视觉上的一种动起来,实际上就是播放PPT。这里我们经常使用invalidate()与postInvalidate(),都用于刷新View,主要区别是invalidate()在主线程中调用,若在子线程中使用需要配合handler;而postInvalidate()可在子线程中直接调用。postInvalidate它是向主线程发送个Message,然后handleMessage时,调用了invalidate()函数。还需要掌握基本view的点击事件传递流程,会解决一些滑动冲突,判断滑动,点击事件的传递等。

        通常情况下,我们写一个viewgroup不会去修改dispatchTouchEvent,因为我们如果直接在dispatchTouchEvent去把点击事件拦截了,其他人在你的viewgroup里面写的子view,就再也没办法获取到点击事件。所以我们一般都是在viewgroup的onInterceptTouchEvent去,viewgroup可以通过判断自己需要拦截的事件去处理并且拦截。同时子view可以通过调用requestDisallowInterceptTouchEvent(true)去阻止父布局拦截。

2、实践应用

        竖直方向两个ScrollView,上下滑动时子ScrollView是无法响应竖直方向的滑动事件,我们需要子ScrollView可以上下滑动。

        解决办法:1、重写子ScrollView的onInterceptTouchEvent方法,在这里添加parent.requestDisallowInterceptTouchEvent(true)方法,就可以让父控件不拦截事件。如果想要优化一下,让子ScrollView滑动到底部的时候父控件继续滑动,那就使用parent.requestDisallowInterceptTouchEvent(false),父控件会根据自身的判断来决定是否拦截。

        如何区别不同的手势,点击,轻滑,长按,拖拽等。

        解决办法:当然,系统提供的方法还是挺香的,Android sdk给我们提供了GestureDetector类

        

private class gesturelistener implements GestureDetector.OnGestureListener{
 
	public boolean onDown(MotionEvent e) {
		// TODO Auto-generated method stub
		return false;
	}
 
	public void onShowPress(MotionEvent e) {
		// TODO Auto-generated method stub
		
	}
 
	public boolean onSingleTapUp(MotionEvent e) {
		// TODO Auto-generated method stub
		return false;
	}
 
	public boolean onScroll(MotionEvent e1, MotionEvent e2,
			float distanceX, float distanceY) {
		// TODO Auto-generated method stub
		return false;
	}
 
	public void onLongPress(MotionEvent e) {
		// TODO Auto-generated method stub
		
	}
 
	public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
			float velocityY) {
		// TODO Auto-generated method stub
		return false;
	}
	
}

这些函数的触发时机:

------GestureDetector.OnGestureListener事件执行顺序------

快速点击屏幕:onDown→onSingleTapUp

稍微慢速的点击屏幕:onDown→onShowPress→onSingleTapUp

长按屏幕:onDown→onShowPress→onLongPress

快速点击屏幕后滑动无惯性:onDown→onScroll→onScroll→onScroll...........

慢速点击屏幕后滑动无惯性:onDown→onShowPress→onScroll→onScroll→onScroll...........

快速点击屏幕后滑动有惯性:onDown→onScroll→onScroll→onScroll...........→onFling

慢速点击屏幕后滑动有惯性:onDown→onShowPress→onScroll→onScroll→onScroll...........→onFling

class MyDoubleTapListener implements GestureDetector.OnDoubleTapListener{

        @Override
        public boolean onSingleTapConfirmed(MotionEvent motionEvent) {
            System.out.println("OnDoubleTapListener:" + "onSingleTapConfirmed");
            return true;
        }

        @Override
        public boolean onDoubleTap(MotionEvent motionEvent) {
            System.out.println("OnDoubleTapListener:" + "onDoubleTap");
            return true;
        }

        @Override
        public boolean onDoubleTapEvent(MotionEvent motionEvent) {
            System.out.println("OnDoubleTapListener:" + "onDoubleTapEvent");
            return true;
        }
    }

onSingleTapConfirmed:单击事件,用来判定该次点击是单纯的SingleTap而不是DoubleTap。

onDoubleTap:双击事件

onDoubleTapEvent:双击间隔中发生的动作

这玩意的用法也很简单,在ontouch方法里面,把event传进去就行了mGestureDetector.onTouchEvent(event);或者需要缩放判断的时候采用ScaleGestureDetector。

系统方法虽然用起来简单,但是总会出现不满足业务需求的时候,那就只有靠自己手撸了。

我们可以在down的时候记录按下的时间,这个时候就可以在up 的时候通过时间去判断是长按还是短按,或者自己设置不同的点击时长去做不同的处理。在按下到抬起这个时间段,我们可以在move的监听里面去做一些滑动的判断,比如滑动距离很短并且时间也短的时候就抬起了,认为是轻扫,滑动距离长,时间长,认为是滑动拖拽等。主要是通过滑动时间 + 滑动距离去做自己的手势判断。

 

高级-融合贯通

        何谓高级?高级其实就是更懂初级中级的一些操作,更能深入理解里面的逻辑和实现,能够自己通过不同的方式方法去灵活的实现自己的业务需求。列出一些常见的问题或者解决方式,学会自己去构造属于自己的自定义view的世界。

        1、自定义view的时候,适配问题?

        自己写一个控件的时候,宽高不要写死,从onMeasure获取到这个控件的宽高,有些控件需要计算某个刻度或者线条的长度啥的,尽量按照控件宽高的比例去获取,不要写死(采用不同分辨率获取不同的数值的适配方案还是可以写死的)。

        2、点击事件的处理

        自己不需要处理的事件就不要去拦截,只拦截自己需要处理的时候,避免其他人使用你的控件的时候,出现了滑动冲突,他不能修改自己的控件逻辑去解决,必须要改你的控件。

        3、考虑边界问题

        有些时候做一些图表的时候,有些点的位置很高或者很低的时候,再去根据这个点去绘制其它文字,就可能造成文字显示不全等问题。

        4、数据来源

        可以通过xml里面设置自定义属性的声明文件,自定义view的时候通过obtainStyledAttributes方法获取自定义属性的值,并在View的初始化中使用这些值。

        通过自定义view暴露的方法设置数据,通常建议在调用前组装好数据,直接传递给view使用,而不是在view里面去组装数据。

        。。。。

        


                

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

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

相关文章

​高山 MPV 四驱旗舰版:新能源时代的豪华出行新选择

随着新能源技术的不断进步和消费者对高端出行体验的追求,MPV(Multi-Purpose Vehicle,多用途车)市场迎来了新的发展机遇。 长城汽车旗下的魏牌,凭借其在新能源领域的深厚技术积累,推出了全新的高山MPV四驱旗…

用通俗易懂的方式讲解:大模型 Rerank 模型部署及使用技巧总结

Rerank 在 RAG(Retrieval-Augmented Generation)过程中扮演了一个非常重要的角色,普通的 RAG 可能会检索到大量的文档,但这些文档可能并不是所有的都跟问题相关,而 Rerank 可以对文档进行重新排序和筛选,让…

【Python 5】----Pytest接口自动化(实现基础的测试框架)

安装准备 安装好pytest的环境及allure环境 1. 安装pytest pip insatll pytest2.安装allure (需要确保安装了jdk环境)安装allure命令行: 访问allure官网,下载allure2.13.5的安装包,将其bin路径 添加进环境变量path中在cmd里面…

flutter选择国家或地区的电话号码区号

1.国家区号列表(带字母索引侧边栏) import package:generated/l10n.dart; import package:widget/login/area_index_bar_widget.dart; import package:flutter/material.dart; import package:flutter_screenutil/flutter_screenutil.dart;class LoginA…

位图、布隆过滤器

普通的哈希表增删查改的效率的确达到了令人满意的O(1),但是本质还是以空间换时间来实现的。并且哈希表中是直接存储数据的,应对一些海量数据处理的问题可能就会造成空间不足的问题。 加入现有40亿个无符号整形数字,设…

RPC基础知识回顾

RPC基础知识回顾 1、先认识一下大家熟悉的HTTP 大家都了解HTTP吧。相信项目中也用过一些。 比如: JDK自带的老旧的HttpURLConnection,封装写的很累,java8之前基于HTTP1.0。在java9开始支持Http2.0Spring的其中RestTemplate都是基于HTTP/1.1的请求。最新的还有Sp…

SpringCloudEureka理论与入门

文章目录 1. 前置工作1.1 搭建 user-server1.1.1 pom1.1.2 po,mapper,controller1.1.3 yml1.1.4 启动类1.1.5 启动并访问 1.2 搭建 order-server1.2.1 pom1.2.2 po mapper controller1.2.3 yml1.2.4 启动类1.2.5 启动并访问 1.3 两个服务通信 2. Eureka2…

当HR问你:“对于你申请的这个岗位,你觉得你欠缺什么?”【文章底部添加进大学生就业交流群】

当HR问这个问题时,你可以展示自我认识和诚实,同时展现你对自己的积极态度和学习能力。以下是一个可能的回答示例: "对于我申请的这个岗位,我认为我可能欠缺一些行业特定的经验。虽然我在相关领域有一定的工作经验和技能&…

H5 网课宣传引导跳转微信单页源码

源码名称:网课宣传引导跳转微信单页 源码介绍:一款网课销售宣传单页源码,源码支持一键复制微信号并跳转打开微信功能。 需求环境:H5 提示:源码仅支持复制微信和跳转打开微信,客户需自行贴贴搜索添加好友…

基于YOLOv8/YOLOv7/YOLOv6/YOLOv5的石头剪刀布手势识别系统详解(深度学习模型+UI界面代码+训练数据集)

摘要:本篇博客深入探讨了使用深度学习技术开发石头剪刀布手势识别系统的过程,并分享了完整代码。该系统利用先进的YOLOv8、YOLOv7、YOLOv6、YOLOv5算法,并对这几个版本进行性能对比,如mAP、F1 Score等关键指标。文章详细阐述了YOL…

【漏洞复现】网康科技 NS-ASG 应用安全网关 SQL注入漏洞(CVE-2024-2330)

免责声明:文章来源互联网收集整理,请勿利用文章内的相关技术从事非法测试,由于传播、利用此文所提供的信息或者工具而造成的任何直接或者间接的后果及损失,均由使用者本人负责,所产生的一切不良后果与文章作者无关。该…

碳实践 | 基于“界、源、算、质、查”五步法,实现企业组织碳核算

碳排放核算是夯实碳排放统计的基础,提高碳排放数据质量的关键,同时,将推动能耗“双控”向碳排放“双控”转变。总体来看,碳核算分为区域层面、组织层面和产品层面的碳核算,这三个层面的意义和计算方法完全不同。本文将…

SingleSpa微前端基本使用以及原理

先说说singleSpa的缺点 不够灵活 不能动态加载css文件css不隔离没有js沙箱的机制 ( 没有全局对象 每次切换的应用 都是同一个window ) 但是刚刚接触微前端 可以了解一下微前端的基础使用 qiankun微前端框架已经很成熟 也是基于singleSpa来实现的 点击跳转qiankun的基础使用 大…

6.同步异步、正则表达式

JS执行机制 js的特点:单线程,同一时间只能做一件事 可以通过多核CPU解决这个问题,允许js脚本创建多个线程,于是js出现了同步和异步 同步 程序执行的时候按照顺序依次执行 异步 程序执行的时候,会跳过某个步骤继续…

装饰者模式-C#实现

可以使用装饰者模式来动态地给一个对象添加额外的职责。 装饰者模式以对客户透明的方式动态地给一个对象附加上更多的责任,装饰者模式相比生成子类可以更灵活地增加功能。 在装饰者模式中各个角色有: 抽象构件(Phone)角色&#…

【NR 定位】3GPP NR Positioning 5G定位标准解读(十三)-DL-AoD定位

前言 3GPP NR Positioning 5G定位标准:3GPP TS 38.305 V18 3GPP 标准网址:Directory Listing /ftp/ 【NR 定位】3GPP NR Positioning 5G定位标准解读(一)-CSDN博客 【NR 定位】3GPP NR Positioning 5G定位标准解读(…

STM32点亮LED灯与蜂鸣器发声

STM32之GPIO GPIO在输出模式时可以控制端口输出高低电平,用以驱动Led蜂鸣器等外设,以及模拟通信协议输出时序等。 输入模式时可以读取端口的高低电平或电压,用于读取按键输入,外接模块电平信号输入,ADC电压采集灯 GP…

EM算法详解

EM(Expectation-Maximum)算法也称期望最大化算法,曾入选“数据挖掘十大算法”中,可见EM算法在机器学习、数据挖掘中的影响力。EM算法是最常见的隐变量估计方法,在机器学习中有极为广泛的用途,例如常被用来学习高斯混合模型(Gaussian mixture model,简称GMM)的参数;隐…

3D产品配置器帮助您更快进入市场的 5 种方式

如果您的电子商务商店可以从产品配置器的使用中受益(而且大多数都可以),您还应该知道,此功能还可以帮助您的公司采用更具创新性的产品开发姿态。 更简单地说:它可以帮助您更快地构建更好的产品。 继续阅读以了解3D 产…

FPGA Vivado环境下实现D触发器

题目要求:使用Verilog HDL语言设计一个D触发器。请提交程序源代码和Word格式的作业文档,作业文档中应给出程序源代码及RTL分析原理图。 D触发器的工作原理: 初始状态下,触发器处于复位状态,输出为复位信号的稳定状态…