Android Jetpack学习系列——Navigation

news2024/11/28 14:53:33

写在前面

Google在2018年就推出了Jetpack组件库,但是直到今天我才给重视起来,这真的不得不说是一件让人遗憾的事。过去几年的空闲时间里,我一直在尝试做一套自己的组件库,帮助自己快速开发,虽然也听说过Jetpack,但是压根儿也没去了解,但是其实自己已经无形之中用到过很多Jetpack中的库了,只是自己不知道,比如说databinding、viewmodel、camerax等等

所以我打算推出一个Jetpack的学习记录,今天是第一个组件:Navigation

老规矩,文末有demo的源码(永久0积分)

demo效果

正文

关于Navigation

据通义千问的说法:

Android Jetpack Navigation组件是Google推出的一个用于简化Android应用导航的库。旨在提供一种结构化和统一的方式来管理应用程序中的屏幕切换和导航流程,特别是对于基于Fragment的应用。

关于Navigation,我觉得可能大家在生活里对于它的功能并不会陌生,拿微信来说,底部有四个按钮,分别是“微信”、“通讯录”、“发现”、“我”,如下图

当我们分别点击四个按钮的时候,界面区域也会随之切换到对应的页面。这就是一种比较常见的底部导航功能。当然这种结合底部导航栏的fragment切换只是Navigation能够实现的其中一种,还有其他的并不需要底部导航栏的,比如说登录模块,登录模块可能包含着登录、注册和重置密码这三个子模块,那么按照UI的设计,就需要三张页面去实现,我们知道可以使用一个Activity去嵌套三个Fragment去实现,那么显然这种纯fragment切换,这也是Navigation可以实现的范畴

整体设计思路

今天展示Navigation的使用的demo的整体设计思路为:LoginActivity+MainActivity

其中LoginActivity包含着LoginFragment、RegisterFragment、ResetPasswordFragment

其中MainActivity包含HomeFragment、ContactFragment、FindFragment、MeFragment

编码开始

我的环境:AndroidStudio 4.2.2

(1)引用

项目级build.gradle

dependencies {
        ...
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.0"
        ...
    }

模块级build.gradle

plugins {
    ....
    id 'androidx.navigation.safeargs.kotlin'
}

dependencies {
    ...
    //Navigation
    implementation "androidx.navigation:navigation-fragment-ktx:2.4.2"
    implementation "androidx.navigation:navigation-ui-ktx:2.4.2"
}
(2)创建导航文件

步骤如下:

按照这样的步骤,即可创建login_nav_graph.xml和main_nav_graph.xml两个导航文件,如图:

此时,两份文件里内容还是空的,具体内容还需要自行添加

AndroidStudio支持可视化添加fragment以及相互之间的导航关系,这点真的是非常方便

1. 点击加号,可以添加fragment到导航文件

我们首先把使用到的fragment全部添加到导航文件中,如下图:

这里先看一下AndroidStudio自动为我们生成的代码

<fragment
        android:id="@+id/loginFragment"
        android:name="com.swy.navigationdemo.login.LoginFragment"
        android:label="fragment_login"
        tools:layout="@layout/fragment_login" />

 这个fragment标签,与我们拖进来的三个fragment是一一对应的,它包含了四个参数

  • id:比唯一标识符,可供本文件其他地方调用
  • name:是对应Fragment的路径
  • label:是对应Fragment的一个标签
  • tools:layout:对应Fragment的布局文件

还有另一个属性也是值得关注的,就是最外层navigation标签的

app:startDestination="@id/loginFragment"

这表明了,在LoginActivity中,默认优先显示的是LoginFragment,

说明:我们首次添加的Fragment会被默认为优先显示的Fragment,即如果我这里优先添加LoginFragment到导航图,navigation自动生成app:startDestination="@id/loginFragment",那么如果我首先把RegisterFragment添加进导航图,那么这个属性就会是app:startDestination="@id/registerFragment"

2.增加Fragment间的导航关系 

点LoginFragment,框体右边中部会出现一个圆环

点击该圆环,并拖动,就会出现一条蓝线,将该蓝线指向RegisterFragment后松手

此时就会看到,LoginFragment到RegisterFragment的导航关系被建立了,观察新增的action代码

<action
            android:id="@+id/action_loginFragment_to_registerFragment"
            app:destination="@id/registerFragment" />
  •  id:唯一标识符,可供Activity调用
  • destination:用来表示跳转的目的地,可见此时这个action表示的是跳转到registerFragment。需要说明的是,这个跳转每次都会新建实例,也就是我可以从LoginFragment跳转到LoginFragment,但是这两个LoginFragment是不同的实例,也就是栈内会同时存在两个不同的LoginFragment。

其他的属性:

  • app:launchSingleTop:类似于Android活动中singleTop启动模式,当该属性为true时,如果目标Fragment已经在回退栈的顶部(即用户最近访问的Fragment),那么Navigation组件将不会创建新的Fragment实例,而是重用已经存在的那个Fragment实例。如果目标Fragment已经在回退栈中,但不在栈顶,那么app:launchSingleTop属性将不会起作用。在这种情况下,即使app:launchSingleTop设置为true,Navigation组件也会创建一个新的目标Fragment实例并将其推送到回退栈的顶部。
  • app:popUpTo:出栈直到某个元素。比如目前栈内有fragment1 - fragment2 - fragment3,当我在fragment4中定义了app:popUpTo:"@id/fragment1"时,那么fragment2和fragment3会被出栈,最终栈内情况为fragment1 - fragment4。
  • app:popUpToInclusive:这个属性是配合上面的app:popUpTo使用的,用来判断到达指定元素时是否把指定元素也出栈。还是以上面的例子来说明,如果该值为true,那么作为指定元素,fragment1也会被出栈,最终栈内只剩下fragment4.
  • app:enterAnim、app:exitAnim、app:popEnterAnim、app:popExitAnim:这四个属性都是跳转动画相关的,前两个用来配置移动到目的地的动画,后两个配置离开目的地的动画。
    举例说明fragment1跳转到fragment2:
    (1)enterAnim和exitAnim发生于fragment1跳转到fragment2的过程中:
    enterAnim是fragment2的入场动画、exitAnim是fragment1的离场动画
    (2)popEnterAnim和popExitAnim发生于fragment2返回到fragment1的过程中:
    popEnterAnim为返回发生后,fragment1的入场动画
    popExitAnim为返回时,fragment2的离场动画

按照我们最开始设计的跳转关系去完成导航文件,最终是这样的:

即:默认展示登录页面,登录页面可以跳转到注册页面或者是重置密码页面,同时在注册页面和重置密码页面也可以返回到登陆页面

同理,我们完成main_nav_graph的导航关系,如下

因为我们仿照的是微信的底部导航栏,home、contact、find、me四个fragment其实是平级的,它们之间并不存在导航关系。当然我们也可以根据自己的实际业务使用不同的action来设计不同维度和复杂度的导航关系。 

至此,导航文件也有了,剩下的就是创建一个支持导航关系的容器了

(3)导航主机

以LoginActivity为例

布局文件:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".LoginActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/login_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true"
        app:navGraph="@navigation/login_nav_graph"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

创建FragmentContainerView作为导航主机(Navigation Host),这里有几个地方需要说明:

  • android:name="androidx.navigation.fragment.NavHostFragment"是固定的写法
  • app:defaultNavHost的作用是将该FragmentContainerView标记为默认的导航主机,这意味着这个FragmentContainerView会拦截系统的后退按钮事件。当用户点击后退按钮时,Navigation组件会按照导航图中的历史记录进行后退操作,而不是直接关闭Activity。并且在一个Activity中,通常只需要一个NavHostFragment作为导航主机。设置app:defaultNavHost="true"可以确保只有这一个NavHostFragment响应导航操作和后退按钮事件,避免多个NavHostFragment之间的冲突。
  • app:navGraph是将导航文件与导航主机相关联

LoginActivity:

class LoginActivity : AppCompatActivity() {

    private lateinit var binding: ActivityLoginBinding
    private lateinit var navController:NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityLoginBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val navHostFragment = supportFragmentManager.findFragmentById(R.id.login_nav_host_fragment) as NavHostFragment
        navController = navHostFragment.navController
    }

    public fun getNavController(): NavController {
        return navController
    }

    override fun onSupportNavigateUp(): Boolean {
        return findNavController(R.id.login_nav_host_fragment).navigateUp()
    }
}

核心代码:获取NavController

说明,这里声明了一个方法,将获取到的navController返回了出去,主要是供Fragment中进行调用,因为这里Activity只是容器,具体的UI交互,是在对应的Fragment上面的,以LoginFragment为例:

class LoginFragment : Fragment() {
    private lateinit var binding: FragmentLoginBinding
    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        binding = FragmentLoginBinding.inflate(layoutInflater)
        val activity = requireActivity() as LoginActivity
        navController = activity.getNavController()
        initListener()
        return binding.root
    }

    private fun initListener() {
        binding.login.setOnClickListener {
            val intent = Intent(activity, MainActivity::class.java)
            startActivity(intent)
        }

        binding.register.setOnClickListener {
            navController.navigate(R.id.action_loginFragment_to_resetPasswordFragment)
        }

        binding.reset.setOnClickListener {
            navController.navigate(R.id.action_loginFragment_to_resetPasswordFragment)
        }
    }

}

通过这两行代码,fragment获取到了activity的navcontroller

  val activity = requireActivity() as LoginActivity
        navController = activity.getNavController()

之后就可以操作导航事件了,如下

binding.register.setOnClickListener {
            navController.navigate(R.id.action_loginFragment_to_resetPasswordFragment)
        }

        binding.reset.setOnClickListener {
            navController.navigate(R.id.action_loginFragment_to_resetPasswordFragment)
        }

说明,这里面的

R.id.action_loginFragment_to_resetPasswordFragment

R.id.action_loginFragment_to_resetPasswordFragment

就是login_nav_graph.xml文件中定义的两个action的id

我相信写到这里,你基本上就能够看出来整个的调用机制了

 (4)Navigation+BottomNavigationView实现底部导航

上面讲了普通的fragment切换,那么关于带底部导航栏的切换,也还是很有必要说明以下的

主界面布局

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/main_nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="0dp"
        android:layout_height="0dp"
        app:defaultNavHost="true"
        app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintVertical_bias="0.0"
        app:navGraph="@navigation/main_nav_graph" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigationView"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        app:menu="@menu/main_menu"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

显然,页面中只是增加了BottomNavigationView,对应的UI结构如下

 说明:

我这里使用了menu来实现了底部导航栏的几个item内容的导入,代码如下

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <item
        android:id="@+id/homeFragment"
        android:icon="@mipmap/message"
        android:title="首页"
        app:showAsAction="ifRoom" />

    <item
        android:id="@+id/contactFragment"
        android:icon="@mipmap/contact"
        android:title="联系人"
        app:showAsAction="ifRoom" />

    <item
        android:id="@+id/findFragment"
        android:icon="@mipmap/find"
        android:title="发现"
        app:showAsAction="ifRoom" />

    <item
        android:id="@+id/meFragment"
        android:icon="@mipmap/me"
        android:title="我"
        app:showAsAction="ifRoom" />
</menu>

重点说明:这里面四个item的id并不是随意定义的,一定要与main_nav_graph.xml文件中对应的几个fragment的id保持一致,否则,点击底部导航栏的按钮,是无法触发对应的fragment切换的!!!如下

 这里面只是UI上对应了,如何让bottomnavigationview与navcontroller也关联到一起呢

MainActivity.class

class MainActivity : AppCompatActivity() {

    private lateinit var binding:ActivityMainBinding
    private lateinit var navController: NavController

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_nav_host_fragment) as NavHostFragment
        navController = navHostFragment.navController
        binding.bottomNavigationView.setupWithNavController(navController)
    }

    override fun onSupportNavigateUp(): Boolean {
        return findNavController(R.id.main_nav_host_fragment).navigateUp()
    }

}

核心代码就是这一句了:

  binding.bottomNavigationView.setupWithNavController(navController)

补充内容

Fragment间数据通信的两种方式

先看效果

说明:

LoginFragment跳转到RegisterFragment使用SafeArgs方式

LoginFragment跳转到ResetpasswordFragment使用Bundle方式 

(1)SafeArgs(推荐)

Android官方推荐使用Safe Args来实现Fragment间数据通信,原因主要包括以下几个方面:

  1. 类型安全: Safe Args提供了类型安全的方式来传递参数。在navigation graph XML文件中定义的每个参数都有明确的数据类型(例如字符串、整数、布尔值等)。这将自动为这些参数生成对应的Args类,并提供get和set方法,从而确保在编译时就能捕获到类型不匹配的问题,而不是在运行时才出现崩溃。

  2. 清晰性与可读性: 在navigation graph中直接指定参数及其类型使得整个应用导航结构更加清晰。通过查看XML文件,开发者可以很容易地了解哪些参数在Fragment之间传递,以及它们的类型是什么。

  3. 减少代码量和错误: 使用Safe Args不需要手动创建和解析Bundle对象来传递数据,这大大减少了出错的可能性。自动生成的Args类简化了参数传递过程,使得开发者可以直接操作对象而非键值对,提高了编码效率。

  4. 生命周期感知: Safe Args配合Navigation组件一起使用时,能够更好地适应Android组件的生命周期变化。即使目标Fragment因为配置更改(如屏幕旋转)而重新创建,传递的参数也能得到妥善保存和恢复。

  5. 易于维护: 随着项目规模的增长,Safe Args能帮助保持代码的整洁和组织有序。当需要修改或添加新的参数时,只需要在navigation graph文件中更新即可,同时会自动反映到相关的Args类中,无需在多个地方手动同步修改。

具体的使用过程如下:

1.引用插件
项目级build.gradle

buildscript {
   ...
    dependencies {
        ...
        classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.5.3"
    }
}

模块级build.gradle

plugins {
    ...
    id 'androidx.navigation.safeargs.kotlin'
}

引用完成之后,Sync项目,Rebuild项目

2.实际使用:修改login_nav_graph.xml文件,增加argument参数

<fragment
        android:id="@+id/loginFragment"
        android:name="com.swy.navigationdemo.login.LoginFragment"
        android:label="fragment_login"
        tools:layout="@layout/fragment_login">
        <action
            android:id="@+id/action_loginFragment_to_registerFragment"
            app:destination="@id/registerFragment">
            <argument
                android:name="data1"
                app:argType="string"
                android:defaultValue=""/>
        </action>
        <action
            android:id="@+id/action_loginFragment_to_resetPasswordFragment"
            app:destination="@id/resetPasswordFragment">
        </action>
    </fragment>

说明:我在自己环境上调试的时候,发现我定义的argument属性在定义name的时候,总是会提示'xxx' is not a valid destination for tag 'argument'这样的错误,网上也没有找到相关的解释和解决方法,但是经过实际测试,这个地方报红并不影响使用,如图

LoginFragment.java

binding.register.setOnClickListener {
            val data1 = "这是使用safe args方式从登录界面传递的数据"
            navController.navigate(LoginFragmentDirections.actionLoginFragmentToRegisterFragment(data1))
        }

RegisterFragment.java

val data1 = arguments?.getString("data1")
binding.textData1.text = data1

 说明:

上面展示的是单一参数,多参数也是支持的,如下:

比如这里,我又增加了一个data3,那么在LoginFragment中,使用逗号隔开两个参数即可,如下

binding.register.setOnClickListener {
            val data1 = "这是使用safe args方式从登录界面传递的数据"
            val data3 = "data3"
            navController.navigate(LoginFragmentDirections.actionLoginFragmentToRegisterFragment(data1,data3))
        }

RegisterFragment

val data1 = arguments?.getString("data1")
val data3 = arguments?.getString("data3")
binding.textData1.text = data1+data3

最终的效果 

可见, 上面说的safeargs的优点,确实是做到了易于维护

易于维护: 随着项目规模的增长,Safe Args能帮助保持代码的整洁和组织有序。当需要修改或添加新的参数时,只需要在navigation graph文件中更新即可,同时会自动反映到相关的Args类中,无需在多个地方手动同步修改。

(2)Bundle

LoginFragment

binding.reset.setOnClickListener {
            val data2 = "这是使用普通Bundle方式从登录界面传递的数据"
            val bundle = Bundle();
            bundle.putString("data2",data2)
            navController.navigate(R.id.action_loginFragment_to_resetPasswordFragment,bundle)
        }

ResetPasswordFragment

val bundle = arguments
val data2 = bundle?.getString("data2")
binding.textData2.text = data2

至此,Navigation fragment间数据通信的两种方式的简单介绍就结束了

说明:关于safeargs的使用,前面讲解的并不是全部的实现方式,只是其中的一种,就比如我这里从LoginFragment跳转到RegisterFragment,我的argument是在LoginFragment里面定义的,我看网上还有讲解的是也可以在RegisterFragment里面定义,包括Bundle也可以与argument属性有联动关系等等之类的吧,大家有兴趣可以都了解一下

到目前为止,简单的demo算是初具雏形,源码如下

demo 源码

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

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

相关文章

Stable Diffusion模型概述

Stable Diffusion 1. Stable Diffusion能做什么&#xff1f;2. 扩散模型2.1 正向扩散2.2 反向扩散 3. 训练如何进行3.1 反向扩散3.2 Stable Diffusion模型3.3 潜在扩散模型3.4 变分自动编码器3.5 图像分辨率3.6 图像放大 4. 为什么潜在空间是可能的&#xff1f;4.1 在潜在空间中…

【智慧地球】星图地球 | 星图地球超算数据工场

当前空天信息处理涉及并发并行的大量计算问题&#xff0c;需要高性能计算、智能计算联合调度&#xff0c;以此来实现多算力融合&#xff1b;而我国算力产业规模快速增长&#xff0c;超算算力资源正需要以任务驱动来统筹。 基于此&#xff0c;中科星图与郑州中心展开紧密合作&a…

Qt学习_17_一些关于QTableWidget的记录

1 QTableWidget::clear() 程序异常退出 近日&#xff0c;项目中使用到QTableWidget&#xff0c;遇到一个问题&#xff0c;项目需要清空这个表格&#xff0c;但是无论调用clear()&#xff0c;clearContents()&#xff0c;程序都报&#xff1a;程序异常退出。 而且项目程序还比较…

OpenVINS学习5——VioManager.cpp/h学习与注释

前言 之前又看到说VioManager.cpp/h是OpenVINS中的核心程序&#xff0c;这次就看看这里面都写了啥&#xff0c;整体架构什么样&#xff0c;有哪些函数功能。具体介绍&#xff1a; VioManager类 整体分析 VioManager类包含 MSCKF 工作所需的状态和其他算法。我们将测量结果输…

二维码地址门牌管理系统:物业管理的未来趋势

文章目录 前言一、数字化管理与便捷服务二、身份认证与安全保障三、业主便利与贴心服务四、未来发展趋势 前言 在数字化时代&#xff0c;物业管理面临着不断增加的挑战。为了提高管理效率、服务业主&#xff0c;二维码门牌管理系统应运而生。本文将探讨这一新型管理方式&#…

【OpenBMC】的内部README 模板

OpenBMC 本项目的AST2500分支核心代码的机型是ast2500-default&#xff0c;克隆代码后进入编译环境的命令为&#xff1a; source setup ast2500-default 一、源码下载、配置以及编译 重要&#xff1a;请参阅confluence 详细步骤 二、代码使用方法 目前所有自定义修改的代码…

虚拟机添加显示屏

1、关闭虚拟机&#xff0c;虚拟机在为关机的情况下&#xff0c;虚拟机设置->显示器->监视器 都是灰色的&#xff0c;不能设置&#xff1b; 2、虚拟机设置->显示器->监视器 “监视器数量” 设置为2 “拉伸模式” 不要勾选 点确定 3、点击 查看->循环使用多个…

蜥蜴目标检测数据集VOC格式1400张

蜥蜴&#xff0c;一种爬行动物&#xff0c;以其独特的形态和习性&#xff0c;成为了人们关注的焦点。 蜥蜴的外观多样&#xff0c;体型大小不一。它们通常拥有长条的身体、四肢和尾巴&#xff0c;鳞片覆盖全身&#xff0c;这使得它们能够在各种环境中轻松移动。大多数蜥蜴拥有…

深度学习 | 多模态算法

AIGC也就是AI内容生成已经成为新一轮人工智能发展的热点和必然趋势&#xff0c;它使得大规模高质量的创作变得更加容易。 一 、InstructGPT模型 1、GPT系列回顾 chatGPT和InstructGPT都使用了指示学习和基于人工反馈的强化学习来指导模型的训练&#xff0c;不同点仅仅是在采集数…

计算机视觉技术-单发多框检测(SSD)

单发多框检测&#xff08;SSD&#xff09;&#xff08;Liu et al., 2016&#xff09;。 该模型简单、快速且被广泛使用。尽管这只是其中一种目标检测模型&#xff0c;但本节中的一些设计原则和实现细节也适用于其他模型。 下图描述了单发多框检测模型的设计。 此模型主要由基础…

GitHub项目推荐:IDE-3D

项目地址 GitHub - MrTornado24/IDE-3D: [SIGGRAPH Asia 2022] IDE-3D: Interactive Disentangled Editing For High-Resolution 3D-aware Portrait Synthesisx 项目简述 这是一个交互式的3D画图工具。无论是改图还是成图&#xff0c;都能使用。是一个很有意思的项目。 项目…

2024,清洁家电卷向“全、智、廉、拓”与“出海”

文 | 智能相对论 作者 | 佘凯文 岁末&#xff0c;又到了一年一度盘点全年、筹划未来的重要节点。在今年经济大环境整体趋向稳定的背景中&#xff0c;许多行业都交还算过得去的成绩单&#xff0c;清洁家电正是其中一员。 特别是在整体家电大环境依旧严峻的前提下&#xff0c;…

Leetcode算法系列| 11. 盛最多水的容器

目录 1.题目2.题解C# 解法一&#xff1a;暴力C# 解法二&#xff1a;双指针&#xff08;左指针大于右指针&#xff0c;left&#xff09;C# 解法三&#xff1a;双指针优化&#xff08;左指针小于等于最小高度&#xff0c;left&#xff09;Java 解法一&#xff1a;双指针Python3 解…

xshell设置终端类型为xterm-256color (解决oh-my-tmux颜色失真问题)

文章目录 问题描述解法效果检验 问题描述 在xshell远程连接服务器时&#xff0c;tmux色彩有问题&#xff08;tmux配置为Oh my tmux&#xff09;&#xff0c;如下&#xff1a; 这色彩明显是8位的色彩。 现在终端的标配就是类型为 xterm-256color&#xff0c;其支持256位的真彩…

Maven简介及环境搭建和基本使用(Java开发中的实用工具)

一、概述 Maven 是 Apache 软件基金会的一个开源项目,是一个优秀的项目构建工具,它 用来帮助开发者管理项目中的 jar,以及 jar 之间的依赖关系、完成项目的编译、 测试、打包和发布等工作。 Maven的相关概念 pom.xml文件&#xff1a;里面可以配置相关信息&#xff0c;指导ma…

第一课:Transformer

第一课&#xff1a;Transformer 文章目录 第一课&#xff1a;Transformer1、学习总结&#xff1a;什么是语言模型&#xff1f;大语言模型&#xff08;LLM&#xff09;技术演变史注意力机制Transformer结构课程ppt及代码地址 2、学习心得&#xff1a;3、经验分享&#xff1a;4、…

内联函数的作用

目的 主要为了提升程序运行速度。 分析 当程序调用一个函数时&#xff0c;程序暂停执行当前指令&#xff0c;跳到函数体处执行&#xff0c;在函数执行完后&#xff0c;返回原来的位置继续执行。如果该函数为内联函数&#xff0c;则不需跳&#xff0c;是因为该内联函数直接插…

Python开发GUI常用库PyQt6和PySide6介绍之四:开发示例

Python开发GUI常用库PyQt6和PySide6介绍之四&#xff1a;开发示例 Python开发GUI常用库PyQt6 和 PySide6 介绍系列&#xff0c;已发表的&#xff1a; 之一&#xff1a;简介与安装https://blog.csdn.net/cnds123/article/details/135069944 之二&#xff1a;设计师&#xff0…

python的初识(print+intput函数和变量的基本运用)

#主页传送&#xff1a;江南的江 #每日鸡汤&#xff1a;你要知是非以不辩为解脱&#xff0c;烦恼以忍辱为智慧&#xff0c;办事以尽力为有功&#xff0c;处人以真诚为品格。做人的方略是&#xff1a;把好自己的口&#xff0c;明了心中的事&#xff0c;干好手里的活&#xff0c;走…

鸿蒙 Ark Ui UIAbility组件生命周期

前言&#xff1a; 各位同学有段时间没有见面 因为一直很忙所以就没有去更新博客。最近有在学习这个鸿蒙的ark ui开发 因为鸿蒙不是发布了一个鸿蒙next的测试版本 明年会启动纯血鸿蒙应用 所以我就想提前给大家写一些博客文章 今天要讲的是生命周期&#xff1a; 效果图 &#…