Android的三种动画详解(帧动画,View动画,属性动画)

Android的三种动画详解(帧动画、View动画、属性动画)_android动画效果大全-CSDN博客

1、帧动画

缺点是:占用内存较高,播放的是一帧一帧的图片,很少使用

顺序播放预先定义的图片,类似于播放视频。

步骤:

1).在drawable文件夹下创建一个animation_picture.xml文件,Root element选择为animation-list.

具体为:右键点击drawable文件夹->New→Drawable Resource File

2)配置自己需要播放的图片

<?xml version="1.0" encoding="utf-8"?>

<animation-list xmlns:android="http://schemas.android.com/apk/res/android"

    android:oneshot="false">

    <item android:drawable="@drawable/food1" android:duration="500"/>

    <item android:drawable="@drawable/food2" android:duration="500"/>

    <item android:drawable="@drawable/food3" android:duration="500"/>

    <item android:drawable="@drawable/laojunshan1" android:duration="500"/>

    <item android:drawable="@drawable/laojunshan2" android:duration="500"/>

</animation-list>

上述xml中,有些属性我们要了解到:

  • 1、android:oneshot=“false”: 表示是否重复播放动画,还是只播放一次;
  • 2、每个item都有Drawable和duration属性,Drawable表示我们要播放的图片;duration表示这张图播放的时间;
3).将animation_picture.xml设置为imageview的播放资源

package com.example.animationtest;

import androidx.appcompat.app.AppCompatActivity;

import android.graphics.drawable.AnimationDrawable;

import android.os.Bundle;

import android.util.Log;

import android.view.View;

import android.widget.Button;

import android.widget.ImageView;

public class MainActivity extends AppCompatActivity {

    private ImageView mImageView;

    private AnimationDrawable animationDrawable = null;

    private boolean flag = true;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        Button button = (Button) findViewById(R.id.animationButton);

        mImageView = (ImageView)findViewById(R.id.image_view);

        mImageView.setBackgroundResource(R.drawable.animation_picture);//设置资源文件

        animationDrawable = (AnimationDrawable) mImageView.getBackground();

        button.setOnClickListener(new View.OnClickListener() {

            @Override

            public void onClick(View v) {

                if (flag)

                {

                    animationDrawable.start();//开启动画

                    flag = false;

                }else{

                    animationDrawable.stop();

                    flag = true;

                }

            }

        });

    }

}

实际上,从名字也可以看出,AnimationDrawable是一个Drawable的子类,所以我们定义的xml文件也是放在res/rawable目录下的.

2、View动画

View动画是补间动画,设定起始和终止位置,中间会自动补齐,有平移、缩放、旋转、透明四种选择。对应的类为TranslateAnimation、ScaleAnimation、RotateAnimation、AlphaAnimation。

view动画也称为补间动画,因为我们只需要拿到一个view,设定它开始和结束的位置,中间的view会自动由系统补齐,而不需要帧动画每一幅图都是提前准备好的。

View动画是Android一开始就提供的比较原始的动画,主要支持四种效果:平移、缩放、旋转、透明度变化(渐变) 四种基本效果,我们可以再这四种基础效果的基础上,选择其中的几种进行组合。

优点:效率高,使用方便。

缺点:交互性差,当动画结束后会回到初始位置,对于交互性要求较高的使用属性动画。

        (1) 作用对象局限于View

        (2) 动画效果单一,仅能实现位移、旋转、缩放、透明度四种属性的改变

        (3) 没有改变View真实属性

可以使用xml配置资源文件实现,也可以用代码实现,这里用代码实现。其中包含按钮控制动画和默认显示组合动画。

Bar.java

package com.example.viewanimationtest;

import androidx.annotation.NonNull;

import androidx.appcompat.app.AppCompatActivity;

import android.annotation.SuppressLint;

import android.os.Bundle;

import android.os.Handler;

import android.os.Looper;

import android.os.Message;

import android.util.Log;

import android.view.View;

import android.view.animation.AlphaAnimation;

import android.view.animation.Animation;

import android.view.animation.AnimationSet;

import android.view.animation.RotateAnimation;

import android.view.animation.ScaleAnimation;

import android.view.animation.TranslateAnimation;

import android.widget.Button;

import android.widget.ImageView;

import android.widget.Toast;

/*

*Since the child thread cannot directly modify the main UI,

*it is implemented using the Handler mechanism.

* */

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

    private ImageView mImageView;

    public static final int TRANSLATE_ANI = 1;

    public static final int ROTATE_ANI = 2;

    public static final int SCALE_ANI = 3;

    public static final int ALPHA_ANI = 4;

    private Handler mHandler = new Handler(Looper.getMainLooper()){

        @Override

        public void handleMessage(@NonNull Message msg) {

            switch(msg.what)

            {

                case TRANSLATE_ANI:

                    mImageView.clearAnimation();//When setting a new animation, first clear the previous animation.

                    Animation translateAnimation = new TranslateAnimation(0,500,

                            0,500);//The animation moves from(0,0)to (500,500).

                    translateAnimation.setDuration(2000);

                    mImageView.setAnimation(translateAnimation);

                    break;

                case ROTATE_ANI:

                    mImageView.clearAnimation();

                    Animation rotateAnimation = new RotateAnimation(0,360,

                            0,0);//The animation rotates from 0° to 360° around(0,0).

                    rotateAnimation.setDuration(2000);

                    mImageView.setAnimation(rotateAnimation);

                    break;

                case SCALE_ANI:

                    mImageView.clearAnimation();

                    Animation scaleAnimation = new ScaleAnimation(0,1,0,1);

                    scaleAnimation.setDuration(2000);

                    mImageView.setAnimation(scaleAnimation);

                    break;

                case ALPHA_ANI:

                    mImageView.clearAnimation();

                    Animation alphaAnimation = new AlphaAnimation(0,1);

                    alphaAnimation.setDuration(2000);

                    mImageView.setAnimation(alphaAnimation);

                    break;

                default:

                    Log.d("1111","default");

                    break;

            }

        }

    };

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        mImageView =(ImageView)findViewById(R.id.image_view);

        Button translateButton = (Button) findViewById(R.id.translate_button);

        Button scaleButton = (Button) findViewById(R.id.scale_button);

        Button rotateButton = (Button) findViewById(R.id.rotate_button);

        Button alphaButton = (Button) findViewById(R.id.alpha_button);

        translateButton.setOnClickListener(this);

        scaleButton.setOnClickListener(this);

        rotateButton.setOnClickListener(this);

        alphaButton.setOnClickListener(this);

        mImageView.clearAnimation();//If some animations exist, clear them first.

        //set mix animation

        AnimationSet animationSet = new AnimationSet(true);

        //set default alpha animation

        Animation alphaAnimation = new AlphaAnimation(0,1);//The animation is from fully transparent to  fully opaque.

        alphaAnimation.setDuration(2000);//The animation lasts 2 seconds.

        //set default scale animation

        Animation scaleAnimation = new ScaleAnimation(0,1,0,1);//The animation is from 0% to 100%.

        scaleAnimation.setDuration(2000);

        //add sub-animations to combined animation

        animationSet.addAnimation(alphaAnimation);

        animationSet.addAnimation(scaleAnimation);

        //show the combined animation

        mImageView.setAnimation(animationSet);

    }

    @SuppressLint("NonConstantResourceId")

    @Override

    public void onClick(View v) {

        switch (v.getId())

        {

            case R.id.translate_button:

                new Thread(new Runnable() {

                    @Override

                    public void run() {

                        Message msg = new Message();

                        msg.what = TRANSLATE_ANI;

                        mHandler.sendMessage(msg);

                    }

                }).start();

                break;

            case R.id.rotate_button:

                new Thread(new Runnable() {

                    @Override

                    public void run() {

                        Message msg = new Message();

                        msg.what = ROTATE_ANI;

                        mHandler.sendMessage(msg);

                    }

                }).start();

                break;

            case R.id.scale_button:

                new Thread(new Runnable() {

                    @Override

                    public void run() {

                        Message msg = new Message();

                        msg.what = SCALE_ANI;

                        mHandler.sendMessage(msg);

                    }

                }).start();

                break;

            case R.id.alpha_button:

                new Thread(new Runnable() {

                    @Override

                    public void run() {

                        Message msg = new Message();

                        msg.what = ALPHA_ANI;

                        mHandler.sendMessage(msg);

                    }

                }).start();

                break;

            default:

                break;

        }

    }

}

具体效果

​编辑view.mp4

3.属性动画

跟补间动画类似。具体内容可以参考文章:

Android的三种动画详解(帧动画、View动画、属性动画)

在Android3.0以后引入了这种动画模式,用来弥补传统的补间动画和帧动画的不足。属性动画的核心类如下图所示:

其中Animator是属性动画的基类,提供了一些通用的方法。AnimatorSet相当于一组属性动画的容器,用来同时执行多个属性动画。app开发中常用的属性动画为ValueAnimator和ObjectAnimator,本文将基于Android O的版本详细介绍ValueAnimator在系统中的实现方式。

优点:交互性强,动画结束时的位置就是最终位置。详细使用可参考:Android进阶之光  书籍

代码实现

Bar.java

package com.example.propertyanimation;

import androidx.appcompat.app.AppCompatActivity;

import android.animation.Animator;

import android.animation.AnimatorSet;

import android.animation.ObjectAnimator;

import android.os.Bundle;

import android.view.animation.AnimationSet;

import android.widget.ImageView;

import java.util.Set;

public class MainActivity extends AppCompatActivity {

    private ImageView mImageview;

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_main);

        mImageview = (ImageView) findViewById(R.id.image_view);

        //use AnimatorSet to show the combined animation

        AnimatorSet animatorSet = new AnimatorSet();

        //Use the ofFloat function to construct an ObjectAnimator object and set the alpha parameter.

        ObjectAnimator animator1 = ObjectAnimator.ofFloat(mImageview,

                "alpha",0f,1.0f);

        //Set the translationY parameter to move to a certain position along the Y axis

        ObjectAnimator animator2 = ObjectAnimator.ofFloat(mImageview,

                "translationY"200);

        //Set the translationX parameter to move to a certain position along the X axis

        ObjectAnimator animator3 = ObjectAnimator.ofFloat(mImageview,

                "translationX"200);

        //Set the rotation parameter to rotate a certain angle

        ObjectAnimator animator4 = ObjectAnimator.ofFloat(mImageview,

                "rotation"180);

        //Magnify 0.5 times

        ObjectAnimator animator5 = ObjectAnimator.ofFloat(mImageview,

                "scaleX"0.5f);

        //The animation lasts 2 seconds.

        animatorSet.setDuration(2000);

        //set the combined animation

        animatorSet.playTogether(

                animator1,

                animator2,

                animator3,

                animator4,

                animator5

        );

        //start the animation

        animatorSet.start();

    }

}

1.使用方法

private void testValueAnimator() {

    ValueAnimator valueAnimator = ValueAnimator.ofInt(0100);

    valueAnimator.setDuration(300);

    valueAnimator.addListener(new Animator.AnimatorListener() {

        @Override

        public void onAnimationStart(Animator animation) {

            Log.d(TAG, "onAnimationStart");

        }

  

        @Override

        public void onAnimationEnd(Animator animation) {

            Log.d(TAG, "onAnimationEnd");

        }

  

        @Override

        public void onAnimationCancel(Animator animation) {

  

        }

  

        @Override

        public void onAnimationRepeat(Animator animation) {

  

        }

    });

  

    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

        @Override

        public void onAnimationUpdate(ValueAnimator animation) {

            Log.d(TAG, "fraction: " + animation.getAnimatedFraction() + " value: "

                    + animation.getAnimatedValue());

        }

    });

  

    Log.d(TAG, "testValueAnimator");

    valueAnimator.setStartDelay(100);

    valueAnimator.start();

}

        其主要分为以下几个过程:

        (1) 创建ValueAnimator对象 => 通过ofXXX的静态方法获取

        (2) 初始化参数 => 根据一系列setXXX/addXXX方法添加一些初始化参数和回调函数

        (3) 开始动画 => 调用start方法

        在使用属性动画时,客户端通过ValueAnimator.AnimatorUpdateListener接口的回调函数onAnimationUpdate来监听每一帧的变化。属性动画顾名思义,在动画过程中每一帧都会对预设的属性进行改变,客户端通过每一帧该属性的变化自定义一些操作。其预设的属性就是在获取ValueAnimator对象时,通过ofXXX方法设置的。上例中的ValueAnimator.ofInt(0, 100)则表示:在该属性动画执行的过程中,一个属性名为空字符串(默认属性名,后面会说到属性动画的属性名何时为默认,何时为开发者自定义),属性类型为整型的属性在0 ~ 100区间内递增的变化。在每一帧的回调中,开发者可以通过ValueAnimator#getAnimatedValue方法获取默认属性值在该帧的值为多少。上例中的另一个方法:ValueAnimator#getAnimatedFraction则表示该帧的动画进度(0 ~ 1浮点数)为多少。

2.动画初始化过程

        (1) ofXXX方法:设置动画属性类型及变化范围

  初始化提供的方法有:ofInt,ofFloat,ofArgb(描述颜色的ARGB值的变化),ofPropertyValuesHolder(自定义的一系列PropertyValuesHolder,使用该方法时,会在动画每一帧分别处理添加进来的PropertyValuesHolder的变化),ofObject(使用该方法时需要自定义类型估值器)。ofInt,ofFloat,ofObject,ofArgb的实质都是先包装出一个对应的PropertyValuesHolder对象,然后当动画驱动时,根据这个对象计算数当前动画的进度(fraction)以及对应类型值在这一帧的取值(animated value)。ofPropertyValuesHolder方法相当于开发者自己创建若干PropertyValuesHolder对象,在动画驱动时批处理这些对象。

        如果使用ofPropertyValuesHolder,需要开发者自己创建PropertyValuesHolder对象,需要传入对应改变的属性的名字。如果使用其它初始化方法,则属性名字默认为空字符串("")。客户端通过ValueAnimator#getAnimatedValue方法获取每一帧的属性值为多少:如果调用无参的该方法,则返回的是默认的属性值在当前帧的值,因为默认的初始化方法确定了其属性类型,并且只有一个。如果调用的是带String类型参数的该方法,则返回的是对应名字的属性值在当前帧的值,其名字是在创建PropertyValuesHolder对象时设置的。另外,开发者也可以通过ValueAnimator#setValues方法主动添加PropertyValuesHolder对象进来。

        综上,无论通过哪种ofXXX方法进行初始化,最终都会创建一个或多个PropertyValuesHolder对象,这个类是用来管理属性动画中开发者定义的“属性”的变化,每当动画进行时,ValueAnimator对象会计算所有持有的PropertyValuesHolder对象在此帧的属性值为多少,开发者可以通过getAnimatedValue获取。属性动画的核心也在于此——动画驱动过程中,开发者可以自定义任意类型的对象的变化,因此属性动画也打破了补间动画的局限性。

        (2) addUpdateListener:添加一个AnimatorUpdateListener的监听者
        这个监听者的作用在于:在动画驱动过程中,每一帧都会通过该回调通知给客户端进程,客户端可以在该回调中处理每一帧要做的事情。

        (3) addListener:添加一个AnimatorListener的监听者
        这个监听者用来监听动画的开始/结束/取消/重复四个行为的发生。

        (4) setDuration:添加动画执行时长

        (5) setCurrentFraction/setCurrentPlayTime
        设置动画开始的动画进度/开始的时间点(0 ~ Duration范围内)

3.动画启动过程:ValueAnimator#start()

        动画启动过程的关键流程:(step表示上图中的步骤序号)

        step4:向Choreographer中注册一个动画回调,用来驱动整个动画流程。

        step6 ~ step7:初始化PropertyValuesHolder对象中的类型估值器,默认只支持Int/Float两种类型的类型估值器,其他类型需要自定义。

        step8:通知客户端注册的监听者动画开始:AnimatorListener#onAnimationStart被回调。

        step9 ~ step10:设置动画开始时间以及开始时动画进度,如果客户端没有主动通过setCurrentFraction/setCurrentPlayTime设置,则默认会调用setCurrentPlayTime(0),表示动画从头开始。

        step13 ~ step15:入口为animateValue,每一帧的动画都会调用到此方法,该方法主要完成以下几件事:

        (1) 根据时间进度以及动画插值器计算出当前动画进度(fraction)

        (2) 根据当前动画进度以及类型估值器计算出当前PropertyValuesHolder在此帧中的取值是多少(animated value)

        (3) 回调AnimatorUpdateListener#onAnimationUpdate方法

        启动时会调用该方法,认为start方法触发的行为为第一帧(Choreographer驱动的为第二帧)

4.动画驱动过程:Choreographer#doFrame

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

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

相关文章

MySQL语法分类 DDL

DDL(操作数据库、表) 数据库操作(CRUD) C(Create):创建 //指定字符集创建 create database db_1 character set utf8;//避免重复创建数据库报错可以用一下命令 create database if not exists db_1 character set utf8;R(Retrieve):查询 //查询所有数据库的名称 show datab…

基于ElasticSearch存储海量AIS数据:时空立方体索引篇

文章目录 引言I 时间维切分II 空间范围切分引言 索引结构制约着查询请求的类型和处理方式,索引整体架构制约着查询请求的处理效率。随着时间推移,AIS数据在空间分布上具备局部聚集性,如 果简单地将所有AIS数据插入一个索引结构,随着数据量增长,索引的更新效率、查询效率及…

8508A福禄克(FLUKE)数字多用表

181/2461/8938产品概述&#xff1a; Fluke 8508A 万用表在广泛的测量范围内具有卓越的准确性和稳定性&#xff0c;旨在用作校准实验室的多功能精密测量工具&#xff0c;这些实验室必须满足日益严格的测量不确定性分析要求以及提高生产率的需要。 作为其复杂职责的一部分&…

前端项目,个人笔记(一)【Vue-cli - 定制化主题 + 路由设计】

目录 1、项目准备 1.1、项目初始化 1.2、elementPlus按需引入 注&#xff1a;使用cnpm安装elementplus及两个插件&#xff0c;会报错&#xff1a;vueelement-plus报错TypeError: Cannot read properties of null (reading isCE ) &#xff0c;修改&#xff1a; 测试&#…

SSO 单点登录

什么是JWT JWT&#xff08;JSON Web Token&#xff09;是一种开放标准&#xff08;RFC 7519&#xff09;&#xff0c;用于在网络应用间传输声明。它以一种紧凑且自包含的方式安全地在用户和服务之间传递信息&#xff0c;通常用于身份验证和信息交换 为什么要使用JWT 1.传统Se…

解密学习机制:线性回归与梯度下降之旅

摘要 在理解机器学习机制的过程中&#xff0c;我们探讨了在合成数据集上训练简单线性回归模型的过程。整个过程要解决的问题是算法如何通过迭代优化来学习输入和输出变量之间的基本关系。 我们的方法包括生成一个合成线性数据集&#xff0c;实施梯度下降进行参数估计&#xf…

Sonarqube中Java规则与CWE与OWASP的映射关系

很多企业使用Sonarqube社区版作为静态分析工具&#xff0c;在开发阶段检测代码中的缺陷或安全漏洞。但是如果是作为SAST工具厂商&#xff0c;集成该引擎&#xff0c;则需要把Sonarqube中的检测规则与其它引擎的规则进行整合&#xff0c;例如下图&#xff0c;把Sonarqube中的一些…

Spring Cloud Alibaba微服务从入门到进阶(三)(Spring Cloud Alibaba)

Spring Cloud Alibaba是spring Cloud的子项目 Spring Cloud Alibaba的主要组件&#xff08;红框内是开源的&#xff09; Spring Cloud是快速构建分布式系统的工具集&#xff0c; Spring Cloud提供了很多分布式功能 Spring Cloud常用子项目 项目整合 Spring Cloud Alibaba …

Java项目:56 ssm681基于Java的超市管理系统+jsp

作者主页&#xff1a;舒克日记 简介&#xff1a;Java领域优质创作者、Java项目、学习资料、技术互助 文中获取源码 项目介绍 功能包括:商品分类&#xff0c;供货商管理&#xff0c;库存管理&#xff0c;销售统计&#xff0c;用户及角色管理&#xff0c;等等功能。项目采用mave…

【考研数学】高等数学总结

文章目录 第一章 极限 函数 连续1.1 极限存在准则及两个重要极限1.1.1 夹逼定理1.1.1.1 数列夹逼定理1.1.1.2函数夹逼定理 1.1.2 两个重要极限1.1.2.1 极限公式11.1.2.1.1 证明1.1.2.1.2 数列的单调有界收敛准则1.1.2.1.2.1 二项式定理1.1.2.1.2.2 证明 1.1.2.2 极限公式21.1.2…

【Linux进程信号】信号的发送与保存

【Linux进程信号】信号的发送与保存 目录 【Linux进程信号】信号的发送与保存阻塞信号1. 信号其他相关常见概念2. 在内核中的表示3. sigset_t4. 信号集操作函数sigprocmasksigpendingsignal测试这几个系统调用接口 进程地址空间第三讲捕捉信号1. 内核如何实现信号的捕捉2. siga…

一个能够自我游戏的贪吃蛇(pygame与搜索算法)

贪吃蛇小游戏再经典不过了&#xff0c;作为编程爱好者&#xff0c;代码编译的贪吃蛇&#xff0c;又能有怎样的成绩呢&#xff1f; 带着好奇&#xff0c;开始&#xff01; 先做一个普通的贪吃蛇游戏 引入相关package import pygame 定义相关配置变量 # 定义字体 font pyg…

SQLiteC/C++接口详细介绍之sqlite3类(十六)

返回目录&#xff1a;SQLite—免费开源数据库系列文章目录 上一篇&#xff1a;SQLiteC/C接口详细介绍之sqlite3类&#xff08;十五&#xff09; 下一篇&#xff1a; SQLiteC/C接口详细介绍之sqlite3类&#xff08;十七&#xff09;&#xff08;未发表&#xff09; 50.sqlite…

STL:List从0到1

&#x1f389;个人名片&#xff1a; &#x1f43c;作者简介&#xff1a;一名乐于分享在学习道路上收获的大二在校生 &#x1f648;个人主页&#x1f389;&#xff1a;GOTXX &#x1f43c;个人WeChat&#xff1a;ILXOXVJE &#x1f43c;本文由GOTXX原创&#xff0c;首发CSDN&…

uniapp开发微信小程序调用打电话

在使用uniapp开发微信小程序的时候&#xff0c;经常需要调用打电话功能。 下面我们来讲解一下如何实现该功能&#xff0c;效果图请看下图&#xff1a; 代码部分&#xff1a; <!-- h5部分 --><button click"playphone()"></button><!-- JS部分 …

代码随想录day21(1)二叉树:平衡二叉树(leetcode110)

题目要求&#xff1a;判断一棵树是否为平衡二叉树 思路&#xff1a;递归地比较左右子树&#xff0c;只要有一棵子树不满足条件就说明这棵树不是平衡二叉树。本题采用迭代法较为复杂。 leetcode实战&#xff1a; 代码实现&#xff1a; 递归&#xff1a; 迭代&#xff1a;

【WebAssembly】WebAssembly概念介绍和在js中使用

简言 记录下WebAssembly的概念和在JavaScript中的使用方法。 WebAssembly官网 WebAssembly WebAssembly &#xff08;缩写为 Wasm&#xff09;是一种二进制指令格式&#xff0c;用于基于堆栈的虚拟机。Wasm 被设计为编程语言的可移植编译目标&#xff0c;可在网络上部署客户…

爬虫逆向sm3和sm4 加密 案例

注意&#xff01;&#xff01;&#xff01;&#xff01;某XX网站逆向实例仅作为学习案例&#xff0c;禁止其他个人以及团体做谋利用途&#xff01;&#xff01;&#xff01; 案例--aHR0cDovLzExMS41Ni4xNDIuMTM6MTgwODgvc3Vic2lkeU9wZW4 第一步&#xff1a;分析页面和请求方式 …

代码随想录算法训练营第39天 | 62.不同路径 , 63. 不同路径 II

动态规划章节理论基础&#xff1a; https://programmercarl.com/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 62.不同路径 题目链接&#xff1a;https://leetcode.cn/problems/unique-paths/ 思路&#xff1a; 动规五部曲&#xff1a…

实战Python Socket编程:开发多用户聊天应用

实战Python Socket编程&#xff1a;开发多用户聊天应用 Python Socket 编程概述什么是Socket编程&#xff1f;Socket编程的应用场景Socket编程的重要性基本概念 环境准备Python版本必要的库开发环境配置调试工具 基本Socket编程创建Socket绑定Socket到端口监听连接接受连接发送…