Flutter 页面嵌入 Android原生 View

前言

文章主要讲解Flutter页面如何使用Android原生View,但用到了Flutter 和 Android原生 相互通信知识,建议先看完这篇讲解通信的文章

Flutter 与 Android原生 相互通信:BasicMessageChannel、MethodChannel、EventChannel-CSDN博客

数据观察监听,Flutter使用ValueNotifier,Android原生使用LiveData,在实体数据发生改变时,自动刷新。

效果图

图解

1、Android原生端

1.0 PlatformView

Android:ComputeLayoutPlatform.kt

package com.example.flutter_mix_android.ui.flutterplugin.platform;import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.widget.FrameLayout
import androidx.lifecycle.ViewModelProvider
import com.example.flutter_mix_android.R
import com.example.flutter_mix_android.bean.CountBean
import com.example.flutter_mix_android.databinding.LayoutComputeBinding
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.platform.PlatformView/*** 封装成PlatformView*/
class ComputeLayoutPlatform(context: Context,rootContext: Context,messenger: BinaryMessenger,viewId: Int,args: Any?,
) : FrameLayout(context), PlatformView, MethodChannel.MethodCallHandler {private lateinit var mChannel: MethodChannelprivate lateinit var bind: LayoutComputeBindingprivate lateinit var viewModel: CountBeancompanion object {// Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样private const val ANDROID_SEND_FLUTTER_DATA_NOTICE: String = "androidSendFlutterDataNotice" // Android端 向 Flutter端 发送数据private const val ANDROID_GET_FLUTTER_DATA_NOTICE: String = "androidGetFlutterDataNotice" // Android端 获取 Flutter端 数据private const val FLUTTER_SEND_ANDROID_DATA_NOTICE: String = "flutterSendAndroidDataNotice" // Flutter端 向 Android端 发送数据private const val FLUTTER_GET_ANDROID_DATA_NOTICE: String = "flutterGetAndroidDataNotice" // Flutter端 获取 Android端 数据}init {initChannel(messenger, viewId)initView()initData(rootContext, args)}/*** 初始化消息通道*/private fun initChannel(messenger: BinaryMessenger, viewId: Int) {// 创建 Android端和Flutter端的,相互通信的通道// 通道名称,两端必须一致mChannel = MethodChannel(messenger, "flutter.mix.android/compute/$viewId")// 监听来自 Flutter端 的消息通道// Flutter端调用了函数,这个handler函数就会被触发mChannel.setMethodCallHandler(this)}/*** 初始化视图*/private fun initView() {LayoutInflater.from(context).inflate(R.layout.layout_compute, this, true)bind = LayoutComputeBinding.bind(getChildAt(0))bind.add.setOnClickListener {val count: Int = viewModel.curNum.value ?: 0viewModel.curNum.value = count + 1}bind.androidSendFlutterData.setOnClickListener {androidSendFlutterData()}bind.androidGetFlutterData.setOnClickListener {androidGetFlutterData()}}/*** Android端 向 Flutter端 发送数据,PUT 操作*/private fun androidSendFlutterData() {val map: MutableMap<String, Int> = mutableMapOf<String, Int>()map["androidNum"] = viewModel.curNum.value ?: 0mChannel.invokeMethod(ANDROID_SEND_FLUTTER_DATA_NOTICE,map,object : MethodChannel.Result {override fun success(result: Any?) {Log.d("TAG", "success:$result")updateFlutterNum((result as? Int) ?: 0)}override fun error(errorCode: String,errorMessage: String?,errorDetails: Any?) {Log.d("TAG","errorCode:$errorCode --- errorMessage:$errorMessage --- errorDetails:$errorDetails")}/*** Flutter端 未实现 Android端 定义的接口方法*/override fun notImplemented() {Log.d("TAG", "notImplemented")}})}/*** Android端 获取 Flutter端 数据,GET 操作*/private fun androidGetFlutterData() {// 说一个坑,不传参数可以写null,// 但不能这样写,目前它没有这个重载方法,invokeMethod第二个参数是Object类型,所以编译器不会提示错误// mChannel.invokeMethod(ANDROID_GET_FLUTTER_DATA_NOTICE, object : MethodChannel.Result {// public void invokeMethod(@NonNull String method, @Nullable Object arguments)mChannel.invokeMethod(ANDROID_GET_FLUTTER_DATA_NOTICE,null,object : MethodChannel.Result {override fun success(result: Any?) {Log.d("TAG", "success:$result")updateGetFlutterNum((result as? Int) ?: 0)}override fun error(errorCode: String,errorMessage: String?,errorDetails: Any?) {Log.d("TAG","errorCode:$errorCode --- errorMessage:$errorMessage --- errorDetails:$errorDetails")}/*** Flutter端 未实现 Android端 定义的接口方法*/override fun notImplemented() {Log.d("TAG", "notImplemented")}})}/*** 初始化数据*/private fun initData(rootContext: Context, args: Any?) {val owner = rootContext as FlutterFragmentActivityviewModel = ViewModelProvider(owner)[CountBean::class.java]bind.countBean = viewModelbind.lifecycleOwner = owner// 获取初始化时 Flutter端 向 Android 传递的参数val map: Map<String, Int> = args as Map<String, Int>viewModel.getFlutterNum.value = map["flutterNum"]}/*** 监听来自 Flutter端 的消息通道** call: Android端 接收到 Flutter端 发来的 数据对象* result:Android端 给 Flutter端 执行回调的接口对象*/override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {// 获取调用函数的名称val methodName: String = call.methodwhen (methodName) {FLUTTER_SEND_ANDROID_DATA_NOTICE -> {// 回调结果对象// 获取Flutter端传过来的数据val flutterCount: Int? = call.argument<Int>("flutterNum")updateFlutterNum(flutterCount ?: 0)result.success("success")// 回调状态接口对象,里面有三个回调方法// result.success(result: Any?)// result.error(errorCode: String, errorMessage: String?, errorDetails: Any?)// result.notImplemented()}FLUTTER_GET_ANDROID_DATA_NOTICE -> {result.success(viewModel.curNum.value)}else -> {result.notImplemented()}}}fun updateFlutterNum(flutterCount: Int) {viewModel.flutterNum.value = flutterCount}fun updateGetFlutterNum(flutterCount: Int) {viewModel.getFlutterNum.value = flutterCount}override fun getView(): View? {return this}override fun dispose() {// 解除绑定mChannel.setMethodCallHandler(null)}}

1.1 PlatformViewFactory

Android:ComputeLayoutPlatformFactory.kt

package com.example.flutter_mix_android.ui.flutterplugin.factoryimport android.content.Context
import com.example.flutter_mix_android.ui.flutterplugin.platform.ComputeLayoutPlatform
import io.flutter.plugin.common.BinaryMessenger
import io.flutter.plugin.common.StandardMessageCodec
import io.flutter.plugin.platform.PlatformView
import io.flutter.plugin.platform.PlatformViewFactory/*** 通过PlatformView工厂,创建PlatformView*/
class ComputeLayoutPlatformFactory(private val rootContext: Context,private val messenger: BinaryMessenger, // 二进制信使
) : PlatformViewFactory(StandardMessageCodec.INSTANCE) { // 消息编解码器private lateinit var computeLayoutPlatform: ComputeLayoutPlatformoverride fun create(context: Context, viewId: Int, args: Any?): PlatformView {computeLayoutPlatform = ComputeLayoutPlatform(context, rootContext, messenger, viewId, args)return computeLayoutPlatform}}

1.2 FlutterPlugin

Android:FlutterPlugin.kt

package com.example.flutter_mix_android.ui.flutterplugin.plugin;import android.content.Context
import com.example.flutter_mix_android.ui.flutterplugin.factory.ComputeLayoutPlatformFactory
import io.flutter.embedding.engine.plugins.FlutterPlugin/*** 将AndroidView 注册为 Flutter插件** rootContext:这个context,我是用来作ViewModel观察的,setLifecycleOwner*/
class ComputeLayoutPlugin(private val rootContext: Context) : FlutterPlugin {companion object {// Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样private const val viewType: String = "com.example.flutter_mix_android.ui.flutterplugin.platform/ComputeLayoutPlatform"}/*** 连接到flutter引擎时调用*/override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) {// 将Android原生View 在Flutter引擎上注册binding.platformViewRegistry.registerViewFactory(viewType,ComputeLayoutPlatformFactory(rootContext, binding.binaryMessenger))}/*** 与flutter引擎分离时调用*/override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) {}}

1.3 注册插件

Android:MainActivity.kt

Ps:建议大家直接使用FlutterFragmentActivity平替掉FlutterActivity,因为

FlutterActivity继承于Activity

FlutterFragmentActivity继承于FragmentActivity,它实现了 LifecycleOwnerViewModelStoreOwner

package com.example.flutter_mix_android.ui.activityimport com.example.flutter_mix_android.ui.flutterplugin.plugin.ComputeLayoutPlugin
import io.flutter.embedding.android.FlutterFragmentActivity
import io.flutter.embedding.engine.FlutterEngineclass MainActivity: FlutterFragmentActivity() {override fun configureFlutterEngine(flutterEngine: FlutterEngine) {super.configureFlutterEngine(flutterEngine)// 注册为Flutter插件flutterEngine.plugins.add(ComputeLayoutPlugin(this))}}

1.4 实体 + LiveData

package com.example.flutter_mix_android.beanimport androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModelclass CountBean : ViewModel() {var curNum: MutableLiveData<Int> = MutableLiveData<Int>() // Android端点击次数var flutterNum: MutableLiveData<Int> = MutableLiveData<Int>() // Flutter端点击次数(接收到的)var getFlutterNum: MutableLiveData<Int> = MutableLiveData<Int>() // Flutter端点击次数(主动获取的)}

2、Flutter端

1.0 页面完整代码

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mix_android/bean/count_bean.dart';void main() {runApp(const MyApp());
}class MyApp extends StatelessWidget {const MyApp({super.key});@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',debugShowCheckedModeBanner: false,theme: ThemeData(colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),useMaterial3: true,),home: const MyHomePage(title: 'Flutter Demo Home Page'),);}
}class MyHomePage extends StatefulWidget {const MyHomePage({super.key, required this.title});final String title;@overrideState<MyHomePage> createState() => _MyHomePageState();
}class _MyHomePageState extends State<MyHomePage> {final CountBean countBean = CountBean();late MethodChannel channel;// Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样final String viewType = 'com.example.flutter_mix_android.ui.flutterplugin.platform/ComputeLayoutPlatform';static const String FLUTTER_SEND_ANDROID_DATA_NOTICE = 'flutterSendAndroidDataNotice'; // Flutter端 向 Android端 发送数据static const String FLUTTER_GET_ANDROID_DATA_NOTICE = 'flutterGetAndroidDataNotice'; // Flutter端 获取 Android端 数据static const String ANDROID_SEND_FLUTTER_DATA_NOTICE = 'androidSendFlutterDataNotice'; // Android端 向 Flutter端 发送数据static const String ANDROID_GET_FLUTTER_DATA_NOTICE = 'androidGetFlutterDataNotice'; // Android端 获取 Flutter端 数据/// 初始化消息通道initChannel(int viewId) {channel = MethodChannel('flutter.mix.android/compute/$viewId'); // 创建 Flutter端和Android端的,相互通信的通道// 监听来自 Android端 的消息通道// Android端调用了函数,这个handler函数就会被触发channel.setMethodCallHandler(handler);}/// 监听来自 Android端 的消息通道/// Android端调用了函数,这个handler函数就会被触发Future<dynamic> handler(MethodCall call) async {// 获取调用函数的名称final String methodName = call.method;switch (methodName) {case ANDROID_SEND_FLUTTER_DATA_NOTICE:{int androidCount = call.arguments['androidNum'];countBean.androidNum.value = androidCount;return '$ANDROID_SEND_FLUTTER_DATA_NOTICE ---> success';}case ANDROID_GET_FLUTTER_DATA_NOTICE:{return countBean.curNum.value ?? 0;}default:{return PlatformException(code: '-1', message: '未找到Flutter端具体实现函数', details: '具体描述');}}}/// Flutter端 向 Android端 发送数据,PUT 操作flutterSendAndroidData() {Map<String, int> map = {'flutterNum': countBean.curNum.value};channel.invokeMethod(FLUTTER_SEND_ANDROID_DATA_NOTICE, map).then((value) {debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Result:$value');}).catchError((e) {if (e is MissingPluginException) {debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Error:notImplemented --- 未找到Android端具体实现函数');} else {debugPrint('$FLUTTER_SEND_ANDROID_DATA_NOTICE --- Error:$e');}});}///  Flutter端 获取 Android端 数据,GET 操作flutterGetAndroidData() {channel.invokeMethod(FLUTTER_GET_ANDROID_DATA_NOTICE).then((value) {debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Result:$value');countBean.getAndroidNum.value = value ?? 0;}).catchError((e) {if (e is MissingPluginException) {debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Error:notImplemented --- 未找到Android端具体实现函数');} else {debugPrint('$FLUTTER_GET_ANDROID_DATA_NOTICE --- Error:$e');}});}/// 累计点击次数computeCount() {countBean.curNum.value += 1;}Widget computeWidget() {final ButtonStyle btnStyle = ElevatedButton.styleFrom(elevation: 0,padding: const EdgeInsets.symmetric(horizontal: 12),backgroundColor: Colors.white,shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(35)));return Padding(padding: const EdgeInsets.all(16),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [const Text('Flutter页面',style: TextStyle(color: Color(0xff0066ff),fontSize: 20,fontWeight: FontWeight.bold),),Padding(padding: const EdgeInsets.only(top: 16, bottom: 8),child: Row(children: [ValueListenableBuilder<int>(valueListenable: countBean.curNum,builder: (context, count, _) {return Text('点击次数:$count',style: const TextStyle(fontSize: 16));}),Padding(padding: const EdgeInsets.only(left: 16, right: 8),child: ElevatedButton(style: btnStyle,onPressed: computeCount,child: const Text('+1'),),),ElevatedButton(style: btnStyle,onPressed: flutterSendAndroidData,child: const Text('发送给Android端'),)],),),Padding(padding: const EdgeInsets.only(bottom: 8),child: Row(children: [ValueListenableBuilder(valueListenable: countBean.getAndroidNum,builder: (context, count, _) {return Text('获取Android页面点击次数:$count',style: const TextStyle(fontSize: 16));}),Padding(padding: const EdgeInsets.only(left: 16, right: 3),child: ElevatedButton(style: btnStyle,onPressed: flutterGetAndroidData,child: const Text('获取Android端数据'),),),],),),ValueListenableBuilder(valueListenable: countBean.androidNum,builder: (context, count, _) {return Text('接收Android端发送的点击次数:$count',style: const TextStyle(fontSize: 16));}),],),);}@overrideWidget build(BuildContext context) {return Scaffold(backgroundColor: const Color(0xffA4D3EE),body: SizedBox(width: MediaQuery.of(context).size.width,height: MediaQuery.of(context).size.height,child: SafeArea(top: true,child: Column(children: [Expanded(flex: 1,child: AndroidView(viewType: viewType, // Android原生View 在Flutter引擎上注册的唯一标识,在Flutter端使用时必须一样creationParams: {'flutterNum': countBean.curNum.value}, // Flutter端 初始化时 向Android端 传递的参数creationParamsCodec: const StandardMessageCodec(), // 消息编解码器onPlatformViewCreated: (viewId) {initChannel(viewId);// 使用 viewId 构建不同名称的 MethodChannel,// 主要应用于 多个相同AndroidView一起使用时,避免消息冲突// List<MethodChannel> mChannels = [];// mChannels.add(MethodChannel('flutter.mix.android/compute/$viewId'));// mChannels[0].invokeMethod(method)// mChannels[0].setMethodCallHandler((call) => null)},)),Expanded(flex: 1, child: computeWidget()),],),),),);}}

1.1 实体 + ValueNotifier

import 'package:flutter/cupertino.dart';class CountBean {ValueNotifier<int> curNum = ValueNotifier<int>(10); // Flutter端点击次数ValueNotifier<int> androidNum = ValueNotifier<int>(0); // Android端点击次数(接收到的)ValueNotifier<int> getAndroidNum = ValueNotifier<int>(0); // Android端点击次数(主动获取的)}

6、源码地址

https://github.com/LanSeLianMa/flutter_mix_android

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Java面试题50道

文章目录 1.谈谈你对Spring的理解2.Spring的常用注解有哪些3.Spring中的bean线程安全吗4.Spring中的设计模式有哪些5.Spring事务传播行为有几种6.Spring是怎么解决循环依赖的7.SpringBoot自动配置原理8.SpringBoot配置文件类型以及加载顺序9.SpringCloud的常用组件有哪些10.说一…

rabbitmq基础-java-5、Topic交换机

1、简介 Topic类型的Exchange与Direct相比&#xff0c;都是可以根据RoutingKey把消息路由到不同的队列。 只不过Topic类型Exchange可以让队列在绑定BindingKey 的时候使用通配符&#xff01; BindingKey 一般都是有一个或多个单词组成&#xff0c;多个单词之间以.分割&#x…

(SSO单点登录)多个系统之间如何实现账号互通

SSO具有以下优点&#xff1a; 降低访问第三方网站风险&#xff1b;降低用户名和密码的管理成本&#xff1b;提高用户试用满意度&#xff1b;SSO使用标准的身份认证和授权协议&#xff0c;如OAuth、OpenID Connect等&#xff0c;可以保障用户身份的安全性和隐私性。 单点登录最大…

文件上传技术总结

语言可解析的后缀 &#xff08;前提&#xff1a;在Apache httpd.conf 配置文件中有特殊语言的配置 AddHandler application/x-httpd-php .php 搭配大小写、双重、空格来进行 其中&#xff1a; phtml、pht、php3、php4和php5都是Apache和php认可的php程序的文件后缀 常见的…

C#使用IsLeapYear方法判断指定年份是否为闰年

目录 一、判断指定年是否为闰年的2个方法 1.使用IsLeapYear方法判断指定年份是否为闰年 2.使用自定义的算法计算指定年份是否为闰年 二、示例 1.方法1的实例 2.方法2的实例 一、判断指定年是否为闰年的2个方法 1.使用IsLeapYear方法判断指定年份是否为闰年 使用IsLeapY…

【立创EDA-PCB设计基础】6.布线铺铜实战及细节详解

前言&#xff1a;本文进行布线铺铜实战及详解布线铺铜的细节 在本专栏中【立创EDA-PCB设计基础】前面完成了布线铺铜前的设计规则的设置&#xff0c;接下来进行布线 布局原则是模块化布局&#xff08;优先布局好确定位置的器件&#xff0c;例如排针、接口、主控芯片&#xff…

司铭宇老师:门店经理培训:如何成为一位卓越的门店经理

门店经理培训&#xff1a;如何成为一位卓越的门店经理 在激烈的市场竞争中&#xff0c;门店经理作为门店的灵魂人物&#xff0c;肩负着提升门店业绩、维护品牌形象、带领团队成长等重要职责。本文将为您解析如何成为一位卓越的门店经理&#xff0c;助力您的职业生涯迈向新高峰…

【latex】在Overleaf的IEEE会议模板中,快速插入参考文献

【LaTeX】在Overleaf的IEEE会议模板中&#xff0c;快速插入参考文献 写在最前面第一步&#xff1a;在文献检索网站导出引用文献的bib文件第二步&#xff1a;编辑overleaf模版方法二&#xff1a;EduBirdie生成参考文献&#xff08;补充&#xff09;使用LaTeX在Overleaf的IEEE会议…

html火焰文字特效

下面是代码&#xff1a; <!DOCTYPE html> <html><head><meta charset"UTF-8"><title>HTML5火焰文字特效DEMO演示</title><link rel"stylesheet" href"css/style.css" media"screen" type&quo…

接口测试 02 -- JMeter入门到实战

前言 JM eter毕竟是做压测的工具&#xff0c;自动化这块还是有缺陷。 如果公司做一些简单的接口自动化&#xff0c;可以考虑使用JMeter快速完成&#xff0c;如果想做完善的接口自动化体系&#xff0c;建议还是基于Python来做。 为什么学习接口测试要先从JMeter开始&#xff1f;…

路由器配置虚拟服务器

文章目录 路由器配置虚拟服务器1.前言2.配置流程2.1 进入路由器的登录页面2.2 找到端口映射功能2.3 添加虚拟服务器2.4 查找路由器的动态IP2.5 SSH连接 路由器配置虚拟服务器 1.前言 局域网下面连接着路由器&#xff0c;路由器下面连接着服务器&#xff0c;我们自己的电脑想要…

Unity | 渡鸦避难所-8 | URP 中利用 Shader 实现角色受击闪白动画

1. 效果预览 当角色受到攻击时&#xff0c;为了增加游戏的视觉效果和反馈&#xff0c;可以添加粒子等动画&#xff0c;也可以使用 Shader 实现受击闪白动画&#xff1a;受到攻击时变为白色&#xff0c;逐渐恢复为正常颜色 本游戏中设定英雄受击时播放粒子效果&#xff0c;怪物…

什么是ORM思想?

1. ORM概念 ORM&#xff08;Object Relational Mapping&#xff09;对象关系映射模式&#xff0c;是一种技术&#xff0c;解决了面向对象与关系型数据库存互不匹配的现象。 ORM在业务逻辑层和数据库层之间充当了桥梁的作用。 2. ORM由来 在软件开发的过程中&#xff0c;通常…

【每日一题】最长交替子数组

文章目录 Tag题目来源解题思路方法一&#xff1a;双层循环方法二&#xff1a;单层循环 写在最后 Tag 【双层循环】【单层循环】【数组】【2024-01-23】 题目来源 2765. 最长交替子数组 解题思路 两个方法&#xff0c;一个是双层循环&#xff0c;一个是单层循环。 方法一&am…

C++——结构体

1&#xff0c;结构体基本概念 结构体属于用户自定义的数据类型&#xff0c;允许用户存储不同的数据类型。像int&#xff08;整型&#xff09;&#xff0c;浮点型&#xff0c;bool型&#xff0c;字符串型等都是属于系统内置的数据类型。而今天要学习的结构体则是属于我们自定义…

Redis(五)

1、布隆过滤 1.1、简介 由一个初值都为零的bit数组和多个哈希函数构成&#xff0c;可以用来快速判断集合中是否存在某个元素&#xff0c;减少占用内存&#xff0c;不保存数据信息&#xff0c;只是在内存中做出一个标记。 它实际上是一个很长的二进制数组(00000000)一系列随机h…

Linux破解密码

破解root密码&#xff08;Linux 7&#xff09; 1、先重启——e 2、Linux 16这一行 末尾加rd.break&#xff08;不要回车&#xff09;中断加载内核 3、再ctrlx启动&#xff0c;进入救援模式 4、mount -o remount&#xff0c;rw /sysroot/——&#xff08;mount挂载 o——opti…

浅学JAVAFX布局

JAVAFX FlowPane布局 Flowpane是一个容器。它在一行上排列连续的子组件&#xff0c;并且如果当前行填充满了以后&#xff0c;则自动将子组件向下推到一行 public class FlowPanedemo extends Application {Overridepublic void start(Stage stage) throws Exception {stage.s…

【网站项目】医院管理系统源码(有源码)

🙊作者简介:多年一线开发工作经验,分享技术代码帮助学生学习,独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。🌹赠送计算机毕业设计600个选题excel文件,帮助大学选题。赠送开题报告模板,帮助书写开题报告。作者完整代码目录供你选择: 《Springboot网站项目…

Java基础 - 09 Set之linkedHashSet , CopyOnWriteArraySet

LinkedHashSet和CopyOnWriteArraySet都是Java集合框架提供的特殊集合类&#xff0c;他们在特定场景下有不同的用途和特点。 LinkedHashSet是Java集合框架中的一种实现类&#xff0c;它继承自HashSet并且保持插入顺序。它使用哈希表来存储元素&#xff0c;并使用链表来维护插入…