Android自定义控件:一款多特效的智能loadingView

先上效果图(如果感兴趣请看后面讲解):

1、登录效果展示

img

2、关注效果展示

img

1、【画圆角矩形】

画图首先是onDraw方法(我会把圆代码写上,一步一步剖析): 首先在view中定义个属性:private RectF rectf = new RectF();//可以理解为,装载控件按钮的区域

rectf.left = current_left;
rectf.top = 0;      //(这2点确定空间区域左上角,current_left,是为了后面动画矩形变成等边矩形准备的,这里你可以看成0)  
rectf.right = width - current_left; 
rectf.bottom = height;       //(通过改变current_left大小,更新绘制,就会实现了动画效果)
//画圆角矩形 
//参数1:区域
//参数2,3:圆角矩形的圆角,其实就是矩形圆角的半径
//参数4:画笔
canvas.drawRoundRect(rectf, circleAngle, circleAngle, paint)

2、【确定控件的大小】

上面是画圆角,那width和height怎么来呢当然是通过onMeasure;

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    height = measuredHeight(heightMeasureSpec);  //这里是测量控件大小
    width = measureWidth(widthMeasureSpec);  //我们经常可以看到我们设置控件wrap_content,match_content或者固定值
    setMeasuredDimension(width, height);
}

下面以measureWidth为例:

private int measureWidth(int widthMeasureSpec) {
        int result;
        int specMode = MeasureSpec.getMode(widthMeasureSpec);
        int specSize = MeasureSpec.getSize(widthMeasureSpec);
        //这里是精准模式,比如match_content,或者是你控件里写明了控件大小
        if (specMode == MeasureSpec.EXACTLY) {
            result = specSize;
        } else {
            //这里是wrap_content模式,其实这里就是给一个默认值
            //下面这段注销代码是最开始如果用户不设置大小,给他一个默认固定值。这里以字体长度来决定更合理
            //result = (int) getContext().getResources().getDimension(R.dimen.dp_150);
            //这里是我设置的长度,当然你写自定义控件可以设置你想要的逻辑,根据你的实际情况
            result = buttonString.length() * textSize + height * 5 / 3;
            if (specMode == MeasureSpec.AT_MOST) {
                result = Math.min(result, specSize);
            }
        }
        return result;
    }

3、【绘制文字text】

这里我是用自己的方式实现:当文字长度超过控件长度时,文字需要来回滚动。所以自定义控件因为你需要什么样的功能可以自己去实现(当然这个方法也是在onDraw里,为什么这么个顺序讲,目的希望我希望你能循序渐进的理解,如果你觉得onDraw方代码太杂,你可以用个方法独立出去,你可以跟作者一样用private void drawText(Canvas canvas) {}), //绘制文字的路径(文字过长时,文字来回滚动需要用到)

private Path textPath = new Path():

textRect.left = 0;
textRect.top = 0;
textRect.right = width;
textRect.bottom = height; //这里确定文字绘制区域,其实就是控件区域
Paint.FontMetricsInt fontMetrics = textPaint.getFontMetricsInt();
//这里是获取文字绘制的y轴位置,可以理解上下居中
int baseline = (textRect.bottom + textRect.top - fontMetrics.bottom - fontMetrics.top) / 2;
//这里判断文字长度是否大于控件长度,当然我控件2边需要留文字的间距,所以不是大于width,这么说只是更好的理解
//这里是当文字内容大于控件长度,启动回滚效果。建议先看下面else里的正常情况
if ((buttonString.length() * textSize) > (width - height * 5 / 3)) {
    textPath.reset()//因为要留2遍间距,以heigh/3为间距
    textPath.moveTo(height / 3, baseline);
    textPath.lineTo(width - height / 3, baseline);
    //这里的意思是文字从哪里开始写,可以是居中,这里是右边
    textPaint.setTextAlign(Paint.Align.RIGHT);
    //这里是以路径绘制文字,scrollSize可以理解为文字在x轴上的便宜量,同时,我的混动效果就是通过改变scrollSize
    //刷新绘制来实现
    canvas.drawTextOnPath(buttonString, textPath, scrollSize, 0, textPaint);
    if (isShowLongText) {
        //这里是绘制遮挡物,因为绘制路径没有间距这方法,所以绘制遮挡物类似于间距方式
        canvas.drawRect(new Rect(width - height / 2 - textSize / 3, 0, width - height / 2, height),paintOval);
        canvas.drawRect(new Rect(height / 2, 0, height / 2 + textSize / 3, height), paintOval);
        //这里有个bug 有个小点-5  因画笔粗细产生
        canvas.drawArc(new RectF(width - height, 0, width - 5, height), -90, 180, true, paintOval);
        canvas.drawArc(new RectF(0, 0, height, height), 90, 180, true, paintOval);
    }

    if (animator_text_scroll == null) { 
        //这里是计算混到最右边和最左边的距离范围
        animator_text_scroll = ValueAnimator.ofInt(buttonString.length() * textSize - width + height * 2 / 3,-textSize);
        //这里是动画的时间,scrollSpeed可以理解为每个文字滚动控件外所需的时间,可以做成控件属性提供出去 
        animator_text_scroll.setDuration(buttonString.length() * scrollSpeed);
        //设置动画的模式,这里是来回滚动
        animator_text_scroll.setRepeatMode(ValueAnimator.REVERSE);
        //设置插值器,让整个动画流畅
        animator_text_scroll.setInterpolator(new LinearInterpolator());
        //这里是滚动次数,-1无限滚动
        animator_text_scroll.setRepeatCount(-1);
        animator_text_scroll.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //改变文字路径x轴的偏移量
                scrollSize = (int) animation.getAnimatedValue();
                postInvalidate();
            }
        });
        animator_text_scroll.start();
    }
} else {
    //这里是正常情况,isShowLongText,是我在启动控件动画的时候,是否启动 文字有渐变效果的标识,
    //如果是长文字,启动渐变效果的话,如果控件变小,文字内容在当前控件外,会显得很难看,所以根据这个标识,关闭,这里你可以先忽略(同时因为根据路径绘制text不能有间距效果,这个标识还是判断是否在控件2遍绘制遮挡物,这是作者的解决方式,如果你有更好的方式可以在下方留言)
    isShowLongText = false;
    /**
     * 简单的绘制文字,没有考虑文字长度超过控件长度
     * */
    //这里是居中显示
    textPaint.setTextAlign(Paint.Align.CENTER);
    //参数1:文字
    //参数2,3:绘制文字的中心点
    //参数4:画笔
    canvas.drawText(buttonString, textRect.centerX(), baseline, textPaint);
}

4、【自定义控件属性】

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="SmartLoadingView">
        <attr name="textStr" format="string" />
        <attr name="errorStr" format="string" />
        <attr name="cannotclickBg" format="color" />
        <attr name="errorBg" format="color" />
        <attr name="normalBg" format="color" />
        <attr name="cornerRaius" format="dimension" />
        <attr name="textColor" format="color" />
        <attr name="textSize" format="dimension" />
        <attr name="scrollSpeed" format="integer" />
    </declare-styleable>
</resources>

这里以,文案为例, textStr。比如你再布局种用到app:txtStr=“文案内容”。在自定义控件里获取如下:

public SmartLoadingView(Context context, AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    //自定义控件的3参方法的attrs就是我们设置自定义属性的关键
    //比如我们再attrs.xml里自定义了我们的属性,
    TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.SmartLoadingView);
    //这里是获取用户有没有设置整个属性
    //这里是从用户那里获取有没有设置文案
    String title = typedArray.getString(R.styleable.SmartLoadingView_textStr);
    if (TextUtils.isEmpty(title)){
       //如果获取来的属性是空,那么可以默认一个属性
       //(作者忘记设置了!因为已经发布后期优化,老尴尬了)
       buttonString ="默认文案";
    }else{
       //如果有设置文案
       buttonString = title;
    }

}

5、【设置点击事件,启动动画】

为了点击事件的直观,也可以把处理防止重复点击事件封装在里面

//这是我自定义登录点击的接口
public interface LoginClickListener {
    void click();
}

public void setLoginClickListener(final LoginClickListener loginClickListener) {
    this.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            if (loginClickListener != null) {
                //防止重复点击
                if (!isAnimRuning) {
                    start();
                    loginClickListener.click();
                }

            }
        }
    });
}

6、【动画讲解】

6.1、第一个动画,矩形到正方形,以及矩形到圆角矩形(这里是2个动画,只是同时进行)

矩形到正方形(为了简化,我把源码一些其他属性去掉了,这样方便理解)

//其中  default_all_distance = (w - h) / 2;除以2是因为2遍都往中间缩短
private void set_rect_to_circle_animation() {
    //这是一个属性动画,current_left 会在duration时间内,从0到default_all_distance匀速变化
    //想添加多样化的话  还可以加入插值器。
    animator_rect_to_square = ValueAnimator.ofInt(0, default_all_distance);
    animator_rect_to_square.setDuration(duration);
    animator_rect_to_square.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            //这里的current_left跟onDraw相关,还记得吗
            //onDraw里的控件区域 
            //控件左边区域 rectf.left = current_left;
            //控件右边区域 rectf.right = width - current_left;
            current_left = (int) animation.getAnimatedValue();
            //刷新绘制
            invalidate();
        }
    });

矩形到圆角矩形。就是从一个没有圆角的变成完全圆角的矩形,当然我展示的时候只有第三个图,最后一个按钮才明显了。

其他的我直接设置成了圆角按钮,因为我把圆角做成了一个属性。

还记得onDraw里的canvas.drawRoundRect(rectf, circleAngle, circleAngle, paint);circleAngle就是圆角的半径

可以想象一下如果全是圆角,那么circleAngle会是多少,当然是height/2;没错吧,所以

因为我把圆角做成了属性obtainCircleAngle是从xml文件获取的属性,如果不设置,则为0,就没有任何圆角效果

animator_rect_to_angle = ValueAnimator.ofInt(obtainCircleAngle, height / 2);
animator_rect_to_angle.setDuration(duration);
animator_rect_to_angle.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        //这里试想下如果是一个正方形,刚好是圆形的圆角,那就是一个圆
        circleAngle = (int) animation.getAnimatedValue();
        //刷新绘画
        invalidate();
    }
});

2个属性动画做好后,用 private AnimatorSet animatorSet = new AnimatorSet();把属性动画加进去,可以设置2个动画同时进行,还是先后顺序 这里是同时进行所用用with

animatorSet
        .play(animator_rect_to_square).with(animator_rect_to_angle);
6.2、变成圆形后,有一个loading加载动画

这里就是画圆弧,只是不断改变,圆弧的起始点和终点,最终呈现loading状态,也是在onDraw里

//绘制加载进度
if (isLoading) {
    //参数1:绘制圆弧区域
    //参数2,3:绘制圆弧起始点和终点
    canvas.drawArc(new RectF(width / 2 - height / 2 + height / 4, height / 4, width / 2 + height / 2 - height / 4, height / 2 + height / 2 - height / 4), startAngle, progAngle, false, okPaint);

    //这里是我通过实践,实现最佳loading动画
    //当然这里有很多方式,因为我自定义这个view想把所有东西都放在这个类里面,你也可以有你的方式
    //如果有更好的方式,欢迎留言,告知我一下
    startAngle += 6;
    if (progAngle >= 270) {
        progAngle -= 2;
        isAdd = false;
    } else if (progAngle <= 45) {
        progAngle += 6;
        isAdd = true;
    } else {
        if (isAdd) {
            progAngle += 6;
        } else {
            progAngle -= 2;
        }
    }
    //刷新绘制,这里不用担心有那么多刷新绘制,会不会影响性能
    //
    postInvalidate();
}
6.3、loading状态,到打勾动画

那么这里首先要把loading动画取消,那么直接改变isLoading=false;不会只它同时启动打勾动画;打勾动画的动画,这里比较麻烦,也是我在别人自定义动画里学习的,通过PathMeasure,实现路径动画

/**
 * 路径--用来获取对勾的路径
 */
private Path path = new Path();
/**
 * 取路径的长度
 */
private PathMeasure pathMeasure;
//初始化打勾动画路径;
private void initOk() {
    //对勾的路径
    path.moveTo(default_all_distance + height / 8 * 3, height / 2);
    path.lineTo(default_all_distance + height / 2, height / 5 * 3);
    path.lineTo(default_all_distance + height / 3 * 2, height / 5 * 2);
    pathMeasure = new PathMeasure(path, true);
}
//初始化打勾动画
private void set_draw_ok_animation() {
    animator_draw_ok = ValueAnimator.ofFloat(1, 0);
    animator_draw_ok.setDuration(duration);
    animator_draw_ok.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            startDrawOk = true;
            isLoading = false;
            float value = (Float) animation.getAnimatedValue();
            effect = new DashPathEffect(new float[]{pathMeasure.getLength(), pathMeasure.getLength()}, value * pathMeasure.getLength());
            okPaint.setPathEffect(effect);
            invalidate();

        }
    });
}

//启动打勾动画只需要调用
animator_draw_ok.start();

onDraw里绘制打勾动画

//绘制打勾,这是onDraw的,startDrawOk是判断是否开启打勾动画的标识
if (startDrawOk) {
    canvas.drawPath(path, okPaint);
}
6.4、loading状态下回到失败样子(有点类似联网失败了)

之前6.1提到了矩形到圆角矩形和矩形到正方形的动画,

那么这里只是前面2个动画反过来,再加上联网失败的文案,和联网失败的背景图即刻

6.5、loading状态下启动扩散全屏动画(重点)

这里我通过loginSuccess里参数的类型启动不同效果:

1、启动扩散全屏动画
public void loginSuccess(Animator.AnimatorListener endListener) {}

2、启动打勾动画
public void loginSuccess(AnimationOKListener animationOKListener) {}

启动扩散全屏是本文的重点,里面还涉及到了一个自定义view

CirclBigView,这个控件是全屏的,而且是从一个小圆不断改变半径变成大圆的动画,那么有人会问,全屏肯定不好啊,会影响布局,
但是这里,我把它放在了activity的视图层:
ViewGroup activityDecorView = (ViewGroup) ((Activity) getContext()).getWindow().getDecorView();
ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
activityDecorView.addView(circlBigView, layoutParams);

这个灵感也是前不久在学习微信,拖拽退出的思路里发现的。全部代码如下:

public void toBigCircle(Animator.AnimatorListener endListener) {
    //把缩小到圆的半径,告诉circlBigView
    circlBigView.setRadius(this.getMeasuredHeight() / 2);
    //把当前背景颜色告诉circlBigView
    circlBigView.setColorBg(normal_color);
    int[] location = new int[2];
    //测量当前控件所在的屏幕坐标x,y
    this.getLocationOnScreen(location);
    //把当前坐标告诉circlBigView,同时circlBigView会计算当前点,到屏幕4个点的最大距离,即是当前控件要扩散到的半径
    //具体建议读者看完本博客后,去下载玩耍下。
    circlBigView.setXY(location[0] + this.getMeasuredWidth() / 2, location[1]);
    if (circlBigView.getParent() == null) {
        ViewGroup activityDecorView = (ViewGroup) ((Activity) getContext()).getWindow().getDecorView();
        ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
        activityDecorView.addView(circlBigView, layoutParams);
    }
    circlBigView.startShowAni(endListener);
    isAnimRuning = false;
}

结束语

因为项目是把之前的功能写成了控件,所以有很多地方不完善。希望有建议的大牛和小伙伴,提示提示我,让我完善的更好。谢谢

更多Android进阶指南 可以扫码 解锁 《Android十大板块文档》

1.Android车载应用开发系统学习指南(附项目实战)

2.Android Framework学习指南,助力成为系统级开发高手

3.2023最新Android中高级面试题汇总+解析,告别零offer

4.企业级Android音视频开发学习路线+项目实战(附源码)

5.Android Jetpack从入门到精通,构建高质量UI界面

6.Flutter技术解析与实战,跨平台首要之选

7.Kotlin从入门到实战,全方面提升架构基础

8.高级Android插件化与组件化(含实战教程和源码)

9.Android 性能优化实战+360°全方面性能调优

10.Android零基础入门到精通,高手进阶之路

敲代码不易,关注一下吧。ღ( ´・ᴗ・` ) 🤔

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

虹科示波器 | 汽车免拆检修 | 2010款奥迪A5车怠速时发动机偶尔自动熄火

一、故障现象 一辆2010款奥迪A5车&#xff0c;搭载CDN发动机&#xff0c;累计行驶里程约为16.3万km。车主进厂反映&#xff0c;发动机怠速偶尔出现抖动&#xff0c;紧接着自动熄火&#xff1b;重新起动&#xff0c;发动机又能正常工作&#xff1b;故障频率较低&#xff0c;有时…

Elastcsearch入门案例之 —— 搜索聚合

前言 在前面的Mall项目脚手架整合中涉及到的Elasticsearch的内容仅仅只是在表面给出了一个在SpringBoot中的使用示例&#xff0c;但其实对于Elasticsearch的一些基础概念和底层的原理并没有过多的涉及&#xff0c;这种学习方式是浮躁的&#xff0c;所以这篇文章荔枝会对其中欠缺…

NSSCTF题库——web

[SWPUCTF 2021 新生赛]gift_F12 f12后ctrlf找到flag [SWPUCTF 2021 新生赛]jicao——json_decode() 加密后的格式 $json {"a":"php","b":"mysql","c":3}; json必须双引号传输 构造&#xff1a;GET里json{"x"…

蓝桥杯 冒泡排序

冒泡排序的思想 冒泡排序的思想是每次将最大的一下一下移动到最右边&#xff0c;然后将最右边这个确定下来。 再来确定第二大的&#xff0c;再确定第三大的… 对于数组a[n]&#xff0c;具体来说&#xff0c;每次确定操作就是从左往右扫描&#xff0c;如果a[i]>a[i1],我们将…

Linux-AWK(应用最广泛的文本处理程序)

目录 一、awk基础 二、awk工作原理 三、OFS输出分隔符 四、awk的格式化输出 五、awk模式pattern 一、awk基础 使用案例&#xff1a; 1.准备工作 请在Linux中执行以下指令 cat -n /etc/passwd > ./passwd 练习&#xff1a; 1.从文件 passwd 中提取并打印出第五行的内…

基于混合蛙跳算法优化概率神经网络PNN的分类预测 - 附代码

基于混合蛙跳算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于混合蛙跳算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于混合蛙跳优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神…

《深入浅出进阶篇》洛谷P4147 玉蟾宫——悬线法dp

上链接&#xff1a;P4147 玉蟾宫 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn)https://www.luogu.com.cn/problem/P4147 上题干&#xff1a; 有一个NxM的矩阵&#xff0c;每个格子里写着R或者F。R代表障碍格子&#xff0c;F代表无障碍格子请找出其中的一个子矩阵&#xff0c…

金蝶云星空设置单据体行高

文章目录 金蝶云星空设置单据体行高表单插件Python脚本 金蝶云星空设置单据体行高 表单插件 新建类继承AbstractBillPlugIn&#xff0c;重写OnInitialize方法进行设置 public override void OnInitialize(InitializeEventArgs e){base.OnInitialize(e);this.View.GetControl&…

卡尔曼滤波器第 1 部分 - 简介

一、说明 这是卡尔曼滤波器系列的第一部分。但这并不是另一本定义繁重的读物&#xff0c;它会给你带来一堆行话和方程式&#xff01;在本文中&#xff0c;我们首先关注需要解决方案的问题&#xff08;当然是卡尔曼滤波器&#xff09;&#xff0c;然后直观地了解卡尔曼滤波器。只…

CDN加速技术:节点部署的专业指南

随着互联网的迅猛发展&#xff0c;网站的访问量也在不断增加。为了提供更快、更稳定的用户体验&#xff0c;许多网站都采用了剑盾上云CDN&#xff08;内容分发网络&#xff09;技术。在CDN加速中&#xff0c;节点的合理部署是关键一环&#xff0c;决定了加速效果的优劣。本文将…

美国通胀预期高企,现货黄金价格继续承压下滑

上周五现货黄金持续振荡下滑&#xff0c;金价失守1940美元关口&#xff0c;最低至1933.17美元/盎司&#xff0c;最终收跌1.09%&#xff0c;报1936.51美元/盎司&#xff0c;创10月17日以来新低&#xff1b;今日&#xff08;周一&#xff09;截止汉声集团分析师发稿前&#xff0c…

python趣味编程-使用 Tkinter 的数字到单词转换器

数字到单词转换器应用程序是用Python编程语言编写的。该项目包含演示数字到单词转换的编码脚本。在 Python 中使用 Tkinter 的数字到单词转换器应用程序是一个应用程序,其主要目的是将您输入的数字转换为单词。数字到单词转换器应用程序提供学习资源,帮助您更好地了解Python编…

【python自动化】Playwright基础教程(五)事件操作②悬停输入清除精讲

【python自动化】Playwright基础教程(五)事件操作②悬停&输入&清除精讲 本章目录 文章目录 【python自动化】Playwright基础教程(五)事件操作②悬停&输入&清除精讲鼠标悬停 - hover鼠标悬停实战 输入内容 - fill输入内容实战清空内容实战 输入内容 - type模拟…

CSS省略号n行公式

记得改图中的n&#xff0c;这是你需要的几行省略号&#xff01;复制中间的5行就行了。 .text {overflow: hidden;text-overflow: ellipsis;display: -webkit-box;-webkit-line-clamp: n; //n为你想省略的行数&#xff0c;需要改-webkit-box-orient: vertical; } 这是…

单链表(8)

单链表的特点 可以发现&#xff0c;在单链表的for循环里&#xff0c;初始化就总结为这两种情况 上图中 用第一条&#xff08;要改变链表的结构&#xff0c;增加&#xff0c;减少结点个数等&#xff09;的有&#xff1a;尾插&#xff0c;插入&#xff0c;删除pos位置值&#x…

应用开发平台集成表单设计器系列之2——深入了解与技术验证

背景 上一篇&#xff0c;对表单设计器进行了技术预研&#xff0c;在三款组件form-generator、FormMaking、form-create-designer中初步选择了form-create-designer&#xff0c;接下来的工作&#xff0c;是需要深入了解&#xff0c;进行技术验证&#xff0c;确保该组件功能基本…

安防视频监控EasyCVR平台使用海康ehome接入,配置信息不对是什么原因?该如何将解决?

安防视频监控/视频集中存储/云存储/磁盘阵列EasyCVR平台可拓展性强、视频能力灵活、部署轻快&#xff0c;可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等&#xff0c;以及支持厂家私有协议与SDK接入&#xff0c;包括海康Ehome、海大宇等设备的SDK等。平台既具备传统安…

VMware ubuntu 新虚拟机的创建

根据自己指定的路径安装好vm后。 创建新的虚拟机。 记录一下&#xff0c;下次用到别再忘记了。 如需转载&#xff0c;注明出处&#xff01; 点赞收藏关注我 以资鼓励 打开vm 软件&#xff0c;点击创建新的虚拟机 选择典型&#xff0c;点击下一步 选择你的ubuntu镜像iso文件 …

【算法训练-链表 零】链表高频算法题看这一篇就够了

一轮的算法训练完成后&#xff0c;对相关的题目有了一个初步理解了&#xff0c;接下来进行专题训练&#xff0c;以下这些题目就是汇总的高频题目 题目题干直接给出对应博客链接&#xff0c;这里只给出简单思路、代码实现、复杂度分析 反转链表 依据难度等级分别为反转链表、…

Window安装MongoDB

三种NOSQL的一种,Redis MongoDB ES 应用场景: 1.社交场景:使用Mongodb存储用户信息,以及用户发表的朋友圈信息,通过地理位置索引实现附近的人,地点等功能 2.游戏场景:使用Mongodb存储游戏用户信息,用户的装备,积分等直接以内嵌文档的形式存储,方便查询,高效率存储和访问…