android (实现左滑删除)自定义控件+事件分发

news2024/11/18 4:48:51

左滑删除

  • 背后的逻辑
    • 1布局的绘制
      • onMeasure
      • onLayout
    • 2 事件的分发
      • 都不处理
      • 爸爸拦截
        • 不吃
      • 事件分发的结论
  • 完整代码的实现
    • 效果图
    • 代码

背后的逻辑

想要实现左滑删除,在现有控件不满足的情况下,肯定是要自定义View。
然后考虑需要实现的效果,里面肯定具有两个子控件,一个是显示内容,一个是显示按钮,所以毫无疑问要自定义控件需要继承ViewGroup(布局控件)。

1布局的绘制

布局绘制中,最重要的就是onMeasure方法和onLayout方法。一个onMeasure是用来测量本控件的大小,onLayout方法则是排列孩子控件在本控件的位置。
左滑删除具体分析:
1.刚开始应该是这样(屏幕外侧的东西用户是看不到的)
在这里插入图片描述

2.左移动的时候
在这里插入图片描述

首先,我们自定义控件肯定是放在别的控件里面的,而且通常这种都是放在RecyclerView里面的。
所以我们就这样假定,这个是个item布局,item布局完整的样式如下所示。
很简单外面一个CardView,里面一个SwipeView也就是我们的自定义控件,然后SwipeView里面有两个直接的子控件,一个用来文本,一个用来放按钮。

<androidx.cardview.widget.CardView
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/card"
        android:layout_margin="3dp"
        app:cardCornerRadius="@dimen/cardCornerRadius"
        tools:ignore="MissingConstraints">

        <com.rengda.sigangapp.SwipeView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            >

        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/contentView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:paddingRight="@dimen/mainPadding"
            android:paddingVertical="@dimen/mainPadding"
            android:clickable="true"
            >
            <TextView
                android:id="@+id/No"
                android:layout_marginLeft="3dp"
                android:layout_marginRight="3dp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintLeft_toLeftOf="parent"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textSize="@dimen/headTitleFontSize"
                android:textColor="@color/black"
                android:text="序号">
            </TextView>
        </androidx.constraintlayout.widget.ConstraintLayout>
        <LinearLayout
                android:id="@+id/deleteButton"
                android:layout_width="100dp"
                android:layout_height="wrap_content"
                android:gravity="center"
                android:background="@color/redButton">
                <TextView
                    android:layout_width="match_parent"
                    android:textSize="@dimen/headFontSize"
                    android:layout_height="match_parent"
                    android:textColor="@color/white"
                    android:gravity="center"
                    android:background="@color/redButton"
                    android:text="删除">
                </TextView>
            </LinearLayout>
        </com.rengda.sigangapp.SwipeView>
    </androidx.cardview.widget.CardView>

那么对于自定义控件逻辑很清楚了
1.刚绘制的时候,应该是第一个子控件占满该控件,然后按钮排列在第一个控件的右边。
2.当手势有左滑的时候,整个控件的内容左移
3.当手势右滑的时候,整个控件的内容右移

onMeasure

onMeasure是用来确定本控件该有多大的。
onMeasure里面的两个参数是本控件的父控件给的,按照上面的布局,CardView是自定义控件的父控件。而且很明显,CardView知道自己多宽,但是不知道自己多高的。所以其实我们着重处理的是高度,宽度其实听老爸的就行了。

 override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        measureChildren(widthMeasureSpec, heightMeasureSpec)//测量孩子的高度和宽度
        var width = MeasureSpec.getSize(widthMeasureSpec)//父组件能够给的宽度
        val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec)//父组件的宽度的设置方式
        var height = MeasureSpec.getSize(heightMeasureSpec)//父组件能够给的高度
        val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec)//父组件的高度的设置方式
        var resultWidth=0;
        var resultHeight=0;

        //其实只有三种类型
        if(widthSpecMode==MeasureSpec.EXACTLY){//表示父控件给子view一个具体的值,子view要设置成这些值的大小
            resultWidth=width //听老爸的
        }else if (widthSpecMode==MeasureSpec.UNSPECIFIED){//父组件没告诉你限制 随你发挥
            resultWidth= getChildAt(0).measuredWidth //用第一个孩子宽度
        }else if(widthSpecMode==MeasureSpec.AT_MOST){//表示父控件个子view一个最大的特定值,而子view不能超过这个值的大小
            resultWidth=Math.min(getChildAt(0).measuredWidth,width) //看看谁最小听谁的
        }
        //其实只有三种类型
        if(heightSpecMode==MeasureSpec.EXACTLY){//表示父控件给子view一个具体的值,子view要设置成这些值的大小
            resultHeight=height//听老爸的
        }else if (heightSpecMode==MeasureSpec.UNSPECIFIED){//父组件没告诉你限制 随你发挥
            resultHeight= getChildAt(0).measuredHeight //用第一个孩子的高度
        }else if(heightSpecMode==MeasureSpec.AT_MOST){//表示父控件个子view一个最大的特定值,而子view不能超过这个值的大小
            resultHeight=Math.min(getChildAt(0).measuredHeight,height) //看看谁最小听谁的
        }
        //设置
        setMeasuredDimension(resultWidth, resultHeight)
    }

onLayout

设置第一个孩子在本控件的上下左右位置。
设置第二个孩子在第一个孩子的右边。

override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
         if (childCount==2){
            val view = getChildAt(1)
            sendViewWidth=view.measuredWidth;//记录按钮的宽度
            view.layout(r, t, r + view.measuredWidth, b) //删除按钮排布在第一个View之后
        }
        getChildAt(0).layout(l, t, r, b)//设置第一个控件的位置 上下左右的位置
    }

这一步结束,其实页面刚进来展示的样子就结束了,至于左滑右滑的展示需要去确定的事件里面进行。

2 事件的分发

1.完整的事件,指的是从手指按下down,到手指移动move,然后到手指抬起up,这一个完整的流程。当然在这个流程中,move可能一次都不被触发,也可能被触发多次。
2.事件
在处理事件的时候,有三个重要的概念。1.分发 2.拦截 3.消费
是否分发(dispatchTouchEvent):拿到事件必进dispatchTouchEvent,返回结果表示是不是还给上级处理。true不还给上级了,false返给上级。
是否拦截(onInterceptTouchEvent):当事件分发到自己了,布局文件具有拦截事件的功能,是否让这个事件给自己,不往下发了,如果true拦截,并且去消费,如果false继续下发。
是否消费(onTouchEvent):是否消费掉这个事件,为true是消费,为false是不消费。
很抽象没关系下面慢慢理解:
以下图为例一个嵌套的布局,A是B的父亲,B是C、D的父亲,C、D是兄弟。
在这里插入图片描述

都不处理

这几个布局对事件都不做任何处理,那么当我手指在红色区域的位置按下(down事件)。
那么其实有A、B、C都有这个红色区域,D根本没资格参与,他的区域和这个红色区域无关的。
为了好理解,我们把A、B、C替换成 爷爷,爸爸,孩子,把事件替换成苹果。

在这里插入图片描述对于最后一个节点进行的顺序是这样的:
拿到事件:进来 dispatchTouchEvent
思考拦截:进来 onInterceptTouchEvent
思考结果: onInterceptTouchEvent -> 返回值 拦截 true 不拦截 false
思考是否自己吃: 进来 onTouchEvent
结果自己吃不吃 :onTouchEvent -> 返回值 吃 true 不吃 false
还不还回去 :dispatchTouchEvent-> 返回值 不还给老爸 true 还回去给老爸 false

在这里插入图片描述

爸爸拦截

不吃

在这里插入图片描述

在这里插入图片描述

事件分发的结论

1.如果事件以down为例,发送到了某个view,那么必定先进dispatchTouchEvent。
2.某个view有了事件,会进onInterceptTouchEvent,让你思考拦不拦截。这个方法的返回值true 代表拦截,false代表不拦截。
3.不拦截就继续下发,拦截就直接走onTouchEvent方法。
4.onTouchEvent思考自己吃不吃,吃的话true(消费了),不吃false(不消费)。
5.自己不吃也就是没消费,那么自己的dispatchTouchEvent的返回值默认情况下就是false,代表还回去给爸爸处理。如果自己吃,那么默认情况下dispatchTouchEvent为true了,代表爸爸别处理了,我已经吃了。

所以onTouchEvent会有两种情况会进来,一种,我自己拦下来的,然后就到我自己去判断吃不吃,孩子连这个事件都接不到了。
另一种是,我一路发下去发给孩子了,孩子都不吃,还给我处理,那么我也要思考我自己吃不吃。

特别注意
1.在一个完整事件中也就是手指按下到起来的时候(down开始-到up结束),某个view拦截了完整事件中的一次事件之后,这个后续动作都直接交给这个view了,不会问拦不拦截,默认你直接拦,也不进入onInterceptTouchEvent了,直接到onTouchEvent。(eg:下一次的按下到起来还是重新的逻辑的)。
2.如果down事件,下来一直到还回去最外面都没被消耗,就不会有后续的move或up事件了。因为大家都不吃,所以都没必要问了。
3.某个view,如果接到过down事件,但是爸爸又把其他的事件给拦截了,那么孩子会接到一次cancel事件。eg:孩子接到了down,爸爸把后面的move拦截了,然后爸爸拦截了move之后,会发个cancel事件给孩子,告诉孩子别搞了,整个完整的事件爸爸接手了。

完整代码的实现

效果图

在这里插入图片描述

代码

package com.rengda.sigangapp

import android.content.Context
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.ViewGroup
import android.widget.Scroller
import kotlin.math.abs


class SwipeView(context:Context,attrs: AttributeSet):ViewGroup(context,attrs) {
    private val scroller=Scroller(context);
    private var sendViewWidth=0;
    private var firstX="0".toFloat();//第一个触点的位置
    private var isSendViewShow=false;
    private var newX="0".toFloat()


    private var lastX="0".toFloat();
    private var lastY="0".toFloat();
    override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
         if (childCount==2){
            val view = getChildAt(1)
            sendViewWidth=view.measuredWidth;//记录按钮的宽度
            view.layout(r, t, r + view.measuredWidth, b) //删除按钮排布在第一个View之后
        }
        getChildAt(0).layout(l, t, r, b)//设置第一个控件的位置 上下左右的位置
    }

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        measureChildren(widthMeasureSpec, heightMeasureSpec)//测量孩子的高度和宽度
        var width = MeasureSpec.getSize(widthMeasureSpec)//父组件能够给的宽度
        val widthSpecMode = MeasureSpec.getMode(widthMeasureSpec)//父组件的宽度的设置方式
        var height = MeasureSpec.getSize(heightMeasureSpec)//父组件能够给的高度
        val heightSpecMode = MeasureSpec.getMode(heightMeasureSpec)//父组件的高度的设置方式
        var resultWidth=0;
        var resultHeight=0;

        //其实只有三种类型
        if(widthSpecMode==MeasureSpec.EXACTLY){//表示父控件给子view一个具体的值,子view要设置成这些值的大小
            resultWidth=width //听老爸的
        }else if (widthSpecMode==MeasureSpec.UNSPECIFIED){//父组件没告诉你限制 随你发挥
            resultWidth= getChildAt(0).measuredWidth //用第一个孩子宽度
        }else if(widthSpecMode==MeasureSpec.AT_MOST){//表示父控件个子view一个最大的特定值,而子view不能超过这个值的大小
            resultWidth=Math.min(getChildAt(0).measuredWidth,width) //看看谁最小听谁的
        }
        //其实只有三种类型
        if(heightSpecMode==MeasureSpec.EXACTLY){//表示父控件给子view一个具体的值,子view要设置成这些值的大小
            resultHeight=height//听老爸的
        }else if (heightSpecMode==MeasureSpec.UNSPECIFIED){//父组件没告诉你限制 随你发挥
            resultHeight= getChildAt(0).measuredHeight //用第一个孩子的高度
        }else if(heightSpecMode==MeasureSpec.AT_MOST){//表示父控件个子view一个最大的特定值,而子view不能超过这个值的大小
            resultHeight=Math.min(getChildAt(0).measuredHeight,height) //看看谁最小听谁的
        }
        //设置
        setMeasuredDimension(resultWidth, resultHeight)
    }


    //拿到了这个事件    我是否消费这个事件  如果不消费的话就会还给父级 让父取处理
    override fun onTouchEvent(event: MotionEvent): Boolean {

        var consum=true

        Log.d("SwipeView", "onTouchEvent: "+event.action)
        var x=event.x//这次进来的x
        when (event.action) {
            MotionEvent.ACTION_DOWN -> {// 如果孩子不要这个事件 才会到这里来 如果孩子不要的话 自己要消耗掉否则后续的事件都没了
                consum=true
             }
            MotionEvent.ACTION_MOVE -> {//这个会进来多次
                var newX = firstX-x;//手指移动距离第一次触点的位置 其实就是现在内容需要在的位置 (左为正)
                Log.d("SwipeView", "ACTION_MOVE:isSendViewShow "+isSendViewShow)
                Log.d("SwipeView", "ACTION_MOVE:OFFSET "+newX)

                if (!isSendViewShow){//按钮还没显示的情况下  向左滑才是有效的  0<=newX<=sendViewWidth
                    if (newX>sendViewWidth){ //最远只能滑动第二个控件的宽度
                        newX=sendViewWidth.toFloat()
                    }
                    if (newX<0){//无效的
                        newX="0".toFloat()
                    }
                }


                if (isSendViewShow){//按钮已经显示了  向右滑才是有效的有效的距离   -sendViewWidth<=newX<=0
                    if (newX<-sendViewWidth){
                        newX=-sendViewWidth.toFloat()
                    }
                    if (newX>0){//无效的
                        newX="0".toFloat()
                    }

                    newX=newX+sendViewWidth
                }



                scrollTo(newX.toInt(), 0)//因为这个方法 是让内容距离原始(也就是第一次绘制)的位置 偏移的位置 所以上面newX算的是距离初始的偏移量
                Log.d("SwipeView", "ACTION_MOVE: newX"+newX)

                consum=true
            }

            MotionEvent.ACTION_UP,MotionEvent.ACTION_CANCEL-> {
                if (isSendViewShow){//原先是有按钮的
                    if (scrollX <((sendViewWidth/5)*4)) {//说明真的想关闭了
                        scrollTo(0, 0)
                        isSendViewShow=false
                    } else {
                        scrollTo(sendViewWidth, 0)
                        isSendViewShow=true
                    }

                }else{//没展示按钮的
                    if (scrollX >= sendViewWidth/5) {// 代表真的想打开
                        scrollTo(sendViewWidth, 0)
                        isSendViewShow=true
                    } else {//否则不显示
                        scrollTo(0, 0)
                        isSendViewShow=false
                    }
                }

                Log.d("SwipeView", "ACTION_UP: isSendViewShow "+isSendViewShow)

              }
            else -> { consum=false}
        }
        return consum
    }




    //做外部拦截----是否消拦截这个事件(true拦截,false 不拦截)  因为孩子设置了click事件,如果直接发下 所有的事件都会被孩子消费掉的,不会再回来用父亲的事件了。所以自己要先把用到的拦截一下 其他的再发给孩子
    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        when (ev?.action){
            MotionEvent.ACTION_DOWN -> {//因为孩子要触发click 起码要有个down,所以先下发。 当你拦截了一个之后,后续onInterceptTouchEvent不会再调用了
                //记录按下的时候的X
                firstX=ev.x;//记录

                newX="0".toFloat()
                return false;
            }
            MotionEvent.ACTION_MOVE -> {
                return true;
            }
            else->{return false}
        }

    }



    //做内部拦截  就是去控制老爸的
    override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
        when (ev?.action){
            MotionEvent.ACTION_DOWN -> {//让老爸不要拦截
                lastX=ev.x;
                lastY=ev.y;
                getParent().requestDisallowInterceptTouchEvent(true);
            }
            MotionEvent.ACTION_MOVE -> {
                if (abs( ev.x-lastX)> abs(ev.y-lastY)){//说明x滑动的距离大于Y的距离 说明是左右滑动 那么就不允许老爸拦截
                    getParent().requestDisallowInterceptTouchEvent(true);
                }else{//允许老爸去拦截
                    getParent().requestDisallowInterceptTouchEvent(false);
                }
            }
            MotionEvent.ACTION_UP ->{//让老爸不要拦截
                getParent().requestDisallowInterceptTouchEvent(true);
            }
            else->{return false}
        }

        return super.dispatchTouchEvent(ev)
    }
}

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

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

相关文章

nginx(CVE-2022-41741)漏洞修复

大家好&#xff0c;我是早九晚十二&#xff0c;目前是做运维相关的工作。写博客是为了积累&#xff0c;希望大家一起进步&#xff01; 我的主页&#xff1a;早九晚十二 最近&#xff0c;nginx曝出了最新漏洞CVE-2022-41741&#xff0c;这个影响还是比较大的&#xff0c;因为这个…

你真的了解低代码吗?

&#x1f431; 个人主页&#xff1a;不叫猫先生&#xff0c;公众号&#xff1a;前端舵手 &#x1f64b;‍♂️ 作者简介&#xff1a;2022年度博客之星前端领域TOP 2&#xff0c;前端领域优质作者、阿里云专家博主&#xff0c;专注于前端各领域技术&#xff0c;共同学习共同进步…

在vue中上传图片

大纲&#xff1a; &#x1f335; 1、avue中如何上传图片 Avue官网 : Avue 在Avue官网中找到 Upload附件上传。本案例为了满足项目需求&#xff0c;我只用了上传后的方法 :upload-after"uploadAfter" &#x1f346; Avue上传图片案例代码 <template><div…

【axios】vue中axios的请求配置

注意&#xff1a;本文实例化为TS版 1、axios概念 axios 是一个基于 promise 封装的网络请求库&#xff0c;它是基于 原生XHR 进行二次封装&#xff0c;可以说是 XHR 的一个子集&#xff0c;而 XHR 又是 Ajax 的一个子集 特点 从浏览器中创建 XMLHttpRequests从 node.js 创建…

Maven——Maven工程

1.Maven工程类型 【1】POM工程 【2】JAR工程 【3】WAR工程 2.Maven的目录结构 3.POM模式-Maven工程关系 在Maven中它把每个项目都看成一个对象 3.1依赖 【1】依赖关系 【2】如何注入依赖 【3】依赖的好处&#xff1a; 省去了程序员手动添加jar包的操作&#xff01; 可以帮…

类与对象(中)(一)

1.类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 空类中真的什么都没有吗&#xff1f;并不是&#xff0c;任何类在什么都不写时&#xff0c;编译器会自动生成以下6个默认成员 函数。 默认成员函数&#xff1a;用户没有显式实现&#xff0c;编译器…

【NoteExpress】解决缺少样式的问题

之前写过一篇关于在NoteExpress里面把参考文献输出样式改成Elsevier的教程 &#x1f449;【NoteExpress】统一Elsevier旗下期刊参考文献格式 今天打开 NoteExpress &#xff0c;准备换一个输出样式&#xff0c;发现样式数量变了&#xff0c;从原来几千多变成了7&#xff01; 我…

日撸 Java 三百行day51-53

文章目录 说明Day51-52 KNN 分类器1.KNN2.代码1.aff内容解读2.代码理解 Day53 knn补充1.加权思路2.加权代码3.leave-one-out 测试思路4.leave-one-out代码 说明 闵老师的文章链接&#xff1a; 日撸 Java 三百行&#xff08;总述&#xff09;_minfanphd的博客-CSDN博客 自己也把…

sonarqube主要功能概览

sonarqube质量标准 sonarqube通过可靠性、安全性、安全复审、可维护性、覆盖率、重复度等方面来评价代码质量。 分别使用bugs&#xff0c; 漏洞等指标。 如图&#xff0c;有项目状态为正常&#xff0c;有项目状态为错误。 点进项目可以看具体 可以对问题进行分配&#xff0c;…

7个既可学习又可玩游戏的CSS在线学习网站

学习编码并不容易&#xff0c;尤其是 CSS&#xff0c;所以&#xff0c;在本文中我将跟大家分享一些既能学习CSS知识技能有可以玩游戏的网站&#xff0c;以有趣好玩的方式来帮助你提高学习兴趣以及解决问题的能力。现在&#xff0c;就让我们进入一些在线学习CSS的游戏网站列表&a…

【JOSEF约瑟 JDZS-1202B 可调断电延时中间继电器 精度高、延时宽、】

品牌&#xff1a;JOSEF约瑟名称&#xff1a;可调断电延时中间继电器型号&#xff1a;JDZS-1202B系列额定电压&#xff1a;110、220VDC/AC触点容量&#xff1a;250V/5A功率消耗&#xff1a;2W返回系数&#xff1a;≥5%特点&#xff1a;高精度、延时宽、功耗低。 用途及特点 基本…

使用Rust构建一个kvm用户空间实例

最近在学习虚拟化相关的内容&#xff0c;想着使用Rust构建一个最小的kvm用户空间实例。也就是直接调用kvm的api&#xff0c;然后创建虚拟机。网络上关于kvm的内容大部分是使用libvirt的&#xff0c;然后kvm用户空间实例也是使用C编写的。因此想着使用Rust写一个简单的。 思路 …

Maven依赖管理

文章目录 1 依赖传递与冲突问题2 可选依赖和排除依赖方案一:可选依赖方案二:排除依赖 Masked5 / heima_maven_codes GitCode 我们现在已经能把项目拆分成一个个独立的模块&#xff0c;当在其他项目中想要使用独立出来的这些模块&#xff0c;只需要在其pom.xml使用<depende…

看干货,10个网络安全小知识

如今&#xff0c;大家的生活与互联网已密不可分&#xff0c;每天享受着网络带给我们的服务和便利&#xff0c;工作、娱乐、购物、刷热点……&#xff0c;但网络也是一把双刃利器网络风险无孔不入&#xff0c;信息泄露、网络诈骗、虚假信息满天飞……所以&#xff0c;网络安全不…

JavaWeb-JQuery的学习

1、JQuery快速入门 1.1、JQuery介绍 jQuery 是一个 JavaScript 库。所谓的库&#xff0c;就是一个 JS 文件&#xff0c;里面封装了很多预定义的函数&#xff0c;比如获取元素&#xff0c;执行隐藏、移动等&#xff0c;目的就是在使用时直接调用&#xff0c;不需要再重复定义&…

图解LeetCode——142. 环形链表 II

一、题目 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#xff0c;评测系统…

绚丽的流光心

快到520了&#xff0c;送大家一颗心吧。 代码如下&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title><style>body {background-color: #000;margin: 0;overflow…

国内免费版ChatGPT

目录 前言&#xff1a;网站大全 1. ChatGPT是什么 2. ChatGPT的发展历程 3. ChatGPT对程序员的影响 4. ChatGPT对普通人的影响 5. ChatGPT的不足之处 前言&#xff1a;网站大全 AI文本工具站 (laicj.cn) ——gpt-3.5 功能强大(推荐&#xff09; Chatgpt在线网页版-…

2024王道数据结构考研丨第四章:串

2024王道数据结构考研笔记专栏将持续更新&#xff0c;欢迎 点此 收藏&#xff0c;共同交流学习… 文章目录 第四章&#xff1a;串4.1串的定义和实现4.1.1串的定义4.1.2串的基本操作4.1.3串的存储结构 4.2串的模式匹配4.2.1朴素模式匹配算法4.2.2改进的模式匹配算法——KMP算法 …

【SQLServer】sqlserver数据库导入oracle

将sqlserver数据库导入到oracle 实用工具&#xff1a; SQL Server Management Studio 15.0.18424.0 SQL Server 管理对象 (SMO) 16.100.47021.07eef34a564af48c5b0cf0d617a65fd77f06c3eb1 Microsoft Analysis Services 客户端工具 15.0.19750.0 Microsoft 数据访问组件 (MDAC) …