Flutter GTK窗口集成插件gtk_window的使用

Flutter GTK窗口集成插件gtk_window的使用

一个为flutter添加了一些缺失的gtk4窗口功能的包。适用于Linux,但也可以在MacOS和Windows上工作。

特性

  • 窗口命令按钮(最大化、最小化、关闭)
  • 当可以弹出视图时,显示返回按钮
  • 自定义前导和尾随小部件
  • 底部的PreferredSize小部件
  • 支持亮模式和暗模式
  • 标题栏根据窗口焦点状态进行反应
  • 在窗口调整大小时调用允许你添加自定义逻辑
  • 圆角(感谢 handy_window

开始使用

Linux

为了获得圆角,在 my_application_activate 类中将以下两行移到最后:

gtk_window_set_default_size(window, 1280, 720);
//gtk_widget_show(GTK_WIDGET(window));

改为:

static void my_application_activate(GApplication* application) {
...

gtk_widget_show(GTK_WIDGET(window));
gtk_widget_show(GTK_WIDGET(view));
}

GTKHeaderBar 的使用

它是AppBar小部件的替代品。因此,它允许你只需替换Material即可用于桌面客户端。

基本标题

import 'package:gtk_window/gtk_window.dart';

Scaffold(
    appBar: GTKHeaderBar(
        middle: Text('example title'),
      ),
)

带有溢出汉堡菜单的基本标题

import 'package:gtk_window/gtk_window.dart';

Scaffold(
    appBar: GTKHeaderBar(
        middle: Text('example title'),
        trailing: ElevatedButton(
            child: Icon(Icons.menu)
        ),
      ),
)

带搜索底部的基本标题

import 'package:gtk_window/gtk_window.dart';

Scaffold(
    appBar: GTKHeaderBar(
        middle: Text('Settings'),
        trailing: GTKButton(
            child: Icon(Icons.menu)
        ),
        bottom: PreferredSize(
            preferredSize: const Size.fromHeight(48),
            child: TextField()
        )
      ),
)

复杂分割视图带底部搜索栏

查看此分支

当前限制

  • 操作系统不原生处理appbar,因此在使用窗口管理器时无法隐藏。
  • 目前右键点击标题栏没有任何效果。

为什么不直接使用flutter提供的原生标题栏?

由于flutter团队支持的平台种类繁多,他们必须捆绑功能以在尽可能多的平台上简化开发。因此,他们制作的GTK appbar版本与MacOS和Windows的相似,它们只是悬浮在内容上方,没有太多控制权。在GTK中,appbar可以包含许多小部件,而没有这个包,你将无法控制它们。

免责声明

我对GTK本身不是很熟练,欢迎任何建议。

使用的插件

实际上,我没有为此包编写任何特定于平台的代码。我依赖于handy_window和window_manager维护者的出色工作。

完整示例代码

import 'package:flutter/material.dart';
import 'package:gtk_window/gtk_window.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  // 这个小部件是你的应用的根。这是你应用程序的状态。
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData.light()
          .copyWith(scaffoldBackgroundColor: const Color(0xFFfafafa)),
      darkTheme: ThemeData.dark()
          .copyWith(scaffoldBackgroundColor: const Color(0xFF242424)),
      home: const MyHomePage(title: 'GTK window Demo'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  // 这个小部件是你的应用的主页。它是有状态的,意味着它有一个状态对象(下面定义),该对象包含影响其外观的字段。
  // 这个类是状态的配置。它保存了由父级(在这个例子中是App小部件)提供的值(在这种情况下是标题),并用于State的build方法。子类中的字段总是标记为"final"。

  final String title;

  [@override](/user/override)
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;

  void _incrementCounter() {
    setState(() {
      // 这次对setState的调用告诉Flutter框架某些事情已经改变,这导致它重新运行下面的构建方法,以便显示可以反映更新值。如果我们不调用setState()改变_counter,那么构建方法就不会再次被调用,因此看起来什么都不会发生。
      _counter++;
    });
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    // 这个方法每次调用setState时都会被重新运行,比如上面的_incrementCounter方法所做的那样。
    //
    // Flutter框架已经被优化为使重新运行构建方法变得快速,因此你可以重建任何需要更新的东西,而不是逐个更改小部件实例。
    return Scaffold(
      appBar: GTKHeaderBar(
        leading: [
          IconButton(
            icon: const Icon(Icons.menu),
            onPressed: () {
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(
                  content: Text("Menu button pressed"),
                ),
              );
            },
          ),
        ],
        trailing: [
          IconButton(
            icon: const Icon(Icons.search),
            onPressed: () {
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(
                  content: Text("Search button pressed"),
                ),
              );
            },
          ),
          IconButton(
            icon: const Icon(Icons.more_vert),
            onPressed: () {
              ScaffoldMessenger.of(context).showSnackBar(
                const SnackBar(
                  content: Text("More button pressed"),
                ),
              );
            },
          ),
        ],
        // 这里我们从MyHomePage对象获取值,该对象是由App.build方法创建的,并使用它来设置我们的appbar标题。
        middle: Text(widget.title),
      ),
      body: Center(
        // Center是一个布局小部件。它接受一个子元素并将其定位在父元素的中间。
        child: Column(
          // Column也是一个布局小部件。它接受一个小部件列表并垂直排列它们。默认情况下,它水平调整自身大小以适应其子元素,并尝试与其父元素一样高。
          //
          // 调试绘制(按控制台中的“p”,选择Android Studio中的Flutter Inspector中的“切换调试绘制”操作,或Visual Studio Code中的“切换调试绘制”命令)可以看到每个小部件的线框。
          //
          // Column有各种属性可以控制其大小和位置。在这里我们使用mainAxisAlignment来垂直居中子元素;主轴是垂直方向(因为Columns是垂直的,所以交叉轴是水平的)。
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text(
              'You have pushed the button this many times:',
            ),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.headline4,
            ),
            ElevatedButton(
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => const Scaffold(
                      appBar: GTKHeaderBar(
                        middle: Text("Second page"),
                      ),
                      body: Center(
                        child: Text("Second page"),
                      ),
                    ),
                  ),
                );
              },
              child: const Text('Go to Second Page'),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _incrementCounter,
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ), // 这个逗号使得自动格式化更美观
    );
  }
}

更多关于Flutter GTK窗口集成插件gtk_window的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter GTK窗口集成插件gtk_window的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,关于Flutter与GTK窗口集成的插件gtk_window,这里提供一个简单的代码示例来展示如何在Flutter应用中嵌入并使用GTK窗口。请注意,由于这是一个相对特殊和高级的使用场景,你可能需要一些额外的配置和依赖。

首先,确保你已经安装了必要的依赖项,包括Flutter和GTK开发环境。然后,你可能需要创建一个Flutter插件或使用现有的插件来实现GTK窗口的集成。由于gtk_window可能不是一个官方或广泛使用的插件名称,这里我们假设你有一个自定义的插件或者通过某种方式能够调用GTK功能。

以下是一个简化的示例,展示如何在Flutter中调用GTK窗口(注意:这只是一个概念性的示例,实际实现可能需要根据具体的插件或库进行调整):

Flutter 端代码

  1. 创建一个新的Flutter项目(如果还没有的话):
flutter create flutter_gtk_integration
cd flutter_gtk_integration
  1. 修改pubspec.yaml文件,添加对自定义GTK插件的依赖(假设插件名为gtk_window_plugin):
dependencies:
  flutter:
    sdk: flutter
  gtk_window_plugin:
    path: ../path/to/your/gtk_window_plugin  # 使用本地路径或发布到pub.dev的插件
  1. lib/main.dart中编写代码来调用GTK窗口:
import 'package:flutter/material.dart';
import 'package:gtk_window_plugin/gtk_window_plugin.dart'; // 假设这是你的GTK插件包

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Flutter GTK Integration'),
        ),
        body: Center(
          child: ElevatedButton(
            onPressed: () async {
              // 调用GTK窗口插件的方法
              await GtkWindowPlugin.showGtkWindow();
            },
            child: Text('Show GTK Window'),
          ),
        ),
      ),
    ),
  }
}

GTK 插件端代码

这里假设你已经创建了一个Flutter插件来封装GTK功能。以下是一个简化的插件代码结构示例:

  1. 插件的lib/gtk_window_plugin.dart文件
import 'dart:async';

class GtkWindowPlugin {
  static const MethodChannel _channel = MethodChannel('your.channel.name/gtk_window');

  static Future<void> showGtkWindow() async {
    try {
      await _channel.invokeMethod('showGtkWindow');
    } on PlatformException catch (e) {
      print("Failed to invoke: '${e.message}'.");
    }
  }
}
  1. 插件的android/CMakeLists.txtios/等原生代码配置(这里主要关注Linux/GTK部分):
  • Linux平台配置

    linux/目录下创建必要的CMake和配置文件来链接GTK库。

    CMakeLists.txt 示例:

    cmake_minimum_required(VERSION 3.10)
    
    project(gtk_window_plugin)
    
    find_package(PkgConfig REQUIRED)
    pkg_check_modules(GTK3 REQUIRED gtk+-3.0)
    
    include_directories(${GTK3_INCLUDE_DIRS})
    link_directories(${GTK3_LIBRARY_DIRS})
    
    add_library(gtk_window_plugin SHARED
        gtk_window_plugin.c
    )
    
    target_link_libraries(gtk_window_plugin ${GTK3_LIBRARIES})
    

    gtk_window_plugin.c 示例(一个简单的GTK窗口实现):

    #include <gtk/gtk.h>
    #include <flutter_linux_plugin_registrar.h>
    
    static void show_gtk_window(FlutterLinuxPluginRegistrar* registrar) {
      GtkWidget *window;
      gtk_init(NULL, NULL);
    
      window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
      gtk_window_set_title(GTK_WINDOW(window), "GTK Window from Flutter");
      gtk_window_set_default_size(GTK_WINDOW(window), 400, 300);
      g_signal_connect(window, "destroy", G_CALLBACK(gtk_main_quit), NULL);
    
      gtk_widget_show_all(window);
      gtk_main();
    }
    
    void flutter_gtk_window_plugin_register_with_registrar(
        FlutterLinuxPluginRegistrar* registrar) {
      g_autoptr(GMethodCallback) show_window_callback =
          g_cclosure_new(G_CALLBACK(show_gtk_window), NULL, NULL);
      g_signal_connect(registrar->messenger, "handle-method-call",
                       G_CALLBACK(FlutterLinuxPluginRegistrarHandleMethodCall),
                       show_window_callback);
      flutter_method_channel_set_method_call_handler(
          flutter_linux_plugin_registrar_get_channel(registrar, "your.channel.name/gtk_window"),
          [](FlutterMethodChannel* channel, FlutterMethodCall* call,
             FlutterMethodResponse* response) -> void {
            const gchar* method = flutter_method_call_get_name(call);
            if (g_strcmp0(method, "showGtkWindow") == 0) {
              show_gtk_window(flutter_linux_plugin_registrar_get_registrar(channel));
              flutter_method_response_success(response);
            } else {
              flutter_method_response_not_implemented(response);
            }
          }, NULL);
    }
    

请注意,上述代码是一个非常简化的示例,用于展示如何在Flutter中调用GTK窗口。实际实现中,你可能需要处理更多的细节,比如插件注册、错误处理、线程同步等。此外,由于GTK和Flutter的运行环境差异,确保在正确的线程上调用GTK函数是非常重要的。

希望这个示例能帮助你理解如何在Flutter中集成GTK窗口。如果你有更具体的需求或遇到问题,请提供更多的上下文,以便给出更详细的指导。

回到顶部