JavaFX高级布局教程:优化GridPane使用技巧
发布时间: 2024-10-23 16:35:02 阅读量: 82 订阅数: 23 


javaFx 学习--之布局菜单

# 1. JavaFX与GridPane基础介绍
JavaFX 是一个用于构建富客户端应用程序的软件平台,它提供了一组丰富的用户界面组件,以及强大的图形和媒体包,可用于构建高性能的桌面和Web应用程序。GridPane 是 JavaFX 中用于布局的一个容器,它能够按照网格的形式来安排子节点的位置。利用 GridPane,开发者可以将界面划分为行和列,每个子节点都可以放置在这些行和列交叉所形成的单元格中,从而快速构建出结构化的界面布局。
在本章节中,我们首先会概括介绍 JavaFX 和 GridPane 的基本概念,然后深入探讨其在应用程序中的基本用法,包括如何创建一个简单的 GridPane 布局。我们还将通过代码示例来演示如何向 GridPane 添加组件,并简述其核心属性的作用。通过本章,读者将能够理解 GridPane 的基本原理,并初步掌握其在实际开发中的应用。
# 2. 深入理解GridPane布局机制
## GridPane组件的结构和属性
### GridPane的节点定位
在GridPane布局中,组件(Node)的定位是通过指定行索引(row index)和列索引(column index)来实现的。每个组件被放置在由行和列交叉所形成的单元格中。行和列的索引都是从0开始计数的,这意味着GridPane中的第一个组件将位于0行0列的位置。
为了对组件进行定位,我们使用`GridPane.setConstraints()`方法,其中参数包括组件本身以及其所在的行和列索引。例如,以下代码将一个按钮放置在GridPane的第1行第2列的位置:
```java
Button myButton = new Button("Click Me");
GridPane.setConstraints(myButton, 1, 2);
gridPane.getChildren().add(myButton);
```
行和列的索引可以是任意的正整数,这意味着我们可以在布局中创建任意大的网格。如果未指定行或列索引,则默认位置为0。此外,还可以使用`GridPane.setHalignment()`和`GridPane.setValignment()`方法来控制组件在单元格内的水平和垂直对齐。
### 利用行和列约束控制布局
行和列约束是GridPane布局中的关键,它们控制着组件在网格中的具体位置。每个组件都可以有行约束和列约束,这些约束可以精确到是哪一行或哪一列,也可以是跨多行或多列。
跨列(columnspan)和跨行(rowspan)是两个特别重要的属性,它们允许组件跨越多个单元格。通过设置这两个属性,开发者可以创建更复杂的布局,比如跨越多列或多行的表头。
```java
// 创建一个按钮并跨越三列
Button wideButton = new Button("Wide Button");
GridPane.setConstraints(wideButton, 0, 1, 3, 1); // (0,1) 表示起始行和列索引,3 表示跨越3列,1 表示跨越1行
gridPane.getChildren().add(wideButton);
```
通过调整跨行和跨列属性,可以实现非常灵活的布局效果,同时也增加了布局的复杂度。开发者需要合理使用这些属性,以避免布局过于拥挤或组件间存在不必要的间隙。
## GridPane内部边距和间隙
### 设置行间距和列间距
GridPane提供了设置行间距(row gap)和列间距(column gap)的功能,这些间距决定了行与行之间以及列与列之间的空隙大小。通过使用`GridPane.setHgap()`和`GridPane.setVgap()`方法,可以设置水平和垂直间距。
```java
GridPane gridPane = new GridPane();
gridPane.setHgap(10); // 设置列间距为10像素
gridPane.setVgap(15); // 设置行间距为15像素
```
设置合适的行间距和列间距可以使布局看起来更加整洁,有利于区分不同的组件。在设计界面时,这些间隙应根据组件大小和视觉效果进行调整。较大的间距有助于分离相邻组件,提供清晰的分组效果,而较小的间距可能使界面看起来更紧凑,组件之间的关联性更强。
### 理解边距对组件布局的影响
边距(margin)是组件周围的空间,对于组件的最终布局有很大影响。在GridPane中,每个组件都可以有一个边距,这将影响组件在单元格内的相对位置。通过使用`GridPane.setMargin()`方法,可以为组件设置边距。
```java
Button button = new Button("Button");
GridPane.setConstraints(button, 1, 2);
GridPane.setMargin(button, new Insets(5, 10, 5, 10)); // 设置组件的上、右、下、左的边距分别为5、10、5、10像素
gridPane.getChildren().add(button);
```
边距的使用可以增加组件之间的视觉隔离,使得用户界面更加易读。合理地使用边距,可以避免组件之间产生视觉上的拥挤感,从而提升整体的用户体验。需要注意的是,边距的设置应当考虑到界面的全局布局和整体美观,避免过分使用导致界面过于零散。
## 多重网格和嵌套GridPane
### 创建多重网格的策略
在设计复杂的用户界面时,单一的网格布局可能无法满足需求。多重网格可以通过在单个GridPane内部嵌套其他GridPane来实现,允许在不同层级和位置创建独立的网格结构。
多重网格设计策略的核心在于将界面划分为不同的区域,每个区域都是一个独立的网格,它们之间可以相互独立工作,也可以相互协作,形成复杂的布局层次。
```java
// 创建两个嵌套的GridPane实例
GridPane innerGrid = new GridPane();
GridPane outerGrid = new GridPane();
// 在内部GridPane中添加组件
innerGrid.add(new Label("Inner Grid"), 0, 0);
// 将内部GridPane添加到外部GridPane中
GridPane.setConstraints(innerGrid, 0, 0); // 定位内部GridPane到外部GridPane的第0行第0列
outerGrid.getChildren().add(innerGrid);
// 将外部GridPane添加到场景中
scene.setRoot(outerGrid);
```
多重网格提供了一种将复杂界面分解为可管理部分的方法。当界面的某些区域需要不同的布局策略时,多重网格特别有用。例如,可以将一个用于显示数据的网格嵌入到一个包含菜单和工具栏的网格中,从而创建一个带有侧边栏的复杂用户界面。
### 嵌套GridPane的设计模式
嵌套GridPane是一种常见的设计模式,特别是在需要创建层次化的布局时。通过嵌套,可以在父GridPane中创建灵活的子布局,这为创建复杂的用户界面提供了极高的自由度和灵活性。
在设计模式上,嵌套GridPane通常遵循一种主从关系,即外部GridPane控制整体布局的主结构,而内部嵌套的GridPane则负责实现特定功能区域的详细布局。
```java
// 在GridPane中创建一个子GridPane来作为特定的布局区域
GridPane mainGrid = new GridPane();
GridPane detailsGrid = new GridPane();
// 对detailsGrid进行布局设置
detailsGrid.addRow(0, new Label("Name:"), new TextField());
detailsGrid.addRow(1, new Label("Address:"), new TextField());
// 将detailsGrid作为一个组件放置在mainGrid中的特定位置
GridPane.setConstraints(detailsGrid, 0, 0, 2, 1, HPos.CENTER, VPos.CENTER);
mainGrid.getChildren().add(detailsGrid);
// 将mainGrid设置为场景的根节点
scene.setRoot(mainGrid);
```
嵌套GridPane在实际应用中可以有很多变体,比如可以将GridPane嵌入到其他类型的布局容器中,也可以将GridPane与其他布局容器如HBox、VBox等组合使用,形成更加丰富的布局效果。需要注意的是,嵌套层级不宜过深,过多的嵌套可能会导致维护困难和性能下降。合理的嵌套深度取决于具体的应用场景和设计需求。
# 3. 实践中的GridPane布局优化
## 3.1 有效使用GridPane的动态特性
### 动态特性在布局中的重要性
在现代的用户界面设计中,动态特性是提高用户体验的重要因素之一。动态特性允许布局能够根据数据变化、用户操作或窗口大小调整等事件作出响应。GridPane作为JavaFX中强大的布局管理器之一,提供了丰富的API来支持这些动态特性。通过结合绑定(Binding)和监听器(Listeners),开发者可以创建既美观又功能强大的用户界面。
### 利用绑定和监听器优化布局
在JavaFX中,绑定是一种将UI组件的属性与数据模型连接起来的方式。当数据模型发生变化时,绑定可以自动更新UI组件的状态。结合监听器,我们可以监听UI事件或数据变化,并对界面进行相应的调整。以下是一个简单的例子,展示了如何在GridPane中使用绑定来动态调整文本节点的字体大小:
```java
import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class DynamicGridPaneExample extends Application {
@Override
public void start(Stage primaryStage) {
GridPane gridPane = new GridPane();
Label label = new Label("动态文本");
// 绑定字体大小
label.styleProperty().bind(
Bindings.concat("-fx-font-size: ",
Bindings.when(label.textProperty().isEmpty()).then(14).otherwise(18), "px")
);
gridPane.add(label, 0, 0);
Scene scene = new Scene(gridPane, 300, 250);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void main(String[] args) {
launch(args);
}
}
```
在这个例子中,`Bindings.when().then().otherwise()` 方法被用来设置条件绑定。如果标签文本为空,字体大小就设置为14像素;否则设置为18像素。这种动态调整UI组件的字体大小的做法,可以在实际应用中根据不同的条件(例如,不同的用户状态或者数据状态)来调整界面的显示。
### 动态调整网格大小和行/列数
除了绑定UI组件的属性之外,我们还可以动态地改变GridPane的行和列数,以适应内容的变化。例如,我们可以创建一个动态的表格界面,其中行数会随着数据的增加而增加:
```java
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.layout.GridPane;
import javafx.stage.Stage;
public class DynamicTableExample extends Application {
public static void main(String[] args) {
launch(args);
}
@Override
public void start(Stage primaryStage) {
TableView<Item> table = new TableView<>();
table.setEditable(true);
TableColumn<Item, String> idCol = new TableColumn<>("ID");
idCol.setCellValueFactory(new PropertyValueFactory<>("id"));
TableColumn<Item, String> nameCol = new TableColumn<>("Name");
nameCol.setCellValueFactory(new PropertyValueFactory<>("name"));
table.getColumns().addAll(idCol, nameCol);
// 假设这是一个数据模型列表
for (int i = 0; i < 25; i++) {
Item item = new Item();
item.setId(String.valueOf(i));
item.setName("Item " + i);
table.getItems().add(item);
}
GridPane gridPane = new GridPane();
gridPane.add(table, 0, 0);
Scene scene = new Scene(gridPane, 300, 400);
primaryStage.setScene(scene);
primaryStage.show();
}
class Item {
private StringProperty id;
private StringProperty name;
public Item() {
this.id = new SimpleStringProperty();
this.name = new SimpleStringProperty();
}
public String getId() {
return id.get();
}
public void setId(String id) {
this.id.set(id);
}
public String getName() {
return name.get();
}
public void setName(String name) {
this.name.set(name);
}
}
}
```
在这个例子中,`TableView` 被添加到 `GridPane` 中,并根据数据模型动态填充了若干行数据。当新数据被添加到 `TableView` 中时,界面会自动扩展以显示新的行,无需手动调整 `GridPane` 的行数。
代码逻辑逐行解读:
- `TableView<Item>` 创建了一个可以编辑的表格视图。
- `TableColumn` 定义了表格中显示的列。
- `PropertyValueFactory` 用于填充表格列。
- 通过循环创建并添加了25个数据项到表格中。
- `GridPane` 容器把表格添加到界面中。
- 最终,场景被设置并显示出来。
参数说明:
- `TableView<Item>` 中的泛型 `Item` 表示数据模型。
- `TableColumn` 的构造器参数 `"ID"` 和 `"Name"` 定义了列的标题。
- `PropertyValueFactory` 的参数 `"id"` 和 `"name"` 对应于模型中的属性名。
此节内容中介绍了GridPane布局优化的动态特性,通过绑定和监听器实现布局的动态调整,使UI能够根据不同的数据状态或用户行为进行相应的展示调整。在本节的后半部分,演示了如何实现网格大小和行/列数的动态调整,具体体现在创建动态表格界面的过程中。通过这些方法,开发者可以更好地管理复杂的数据展示需求,提升用户界面的灵活性和动态响应能力。
# 4. GridPane高级应用技巧
## 4.1 创新的用户界面设计
### 4.1.1 利用GridPane实现复杂的布局
GridPane布局管理器的灵活性允许开发者构建复杂的用户界面。通过利用其行和列的约束机制,以及边距和间隙的设置,可以实现细致入微的界面设计。
#### 高级布局实现
使用`GridPane`的多个行和列,我们可以创建具有不同行列跨度的复杂布局。例如,在一个名为`complexLayout`的`GridPane`中,我们可能希望有一个占据两列和三行的卡片视图,以及围绕卡片视图的多个按钮和输入字段。代码示例如下:
```java
GridPane complexLayout = new GridPane();
complexLayout.setStyle("-fx-background-color: #e2e2e2;");
complexLayout.addRow(0, new Label("Label 1"), new Label("Label 2"));
complexLayout.add(new Button("Button 1"), 2, 0);
complexLayout.add(new TextField(), 2, 1);
// 创建卡片视图
CardView cardView = new CardView();
GridPane.setConstraints(cardView, 1, 1, 2, 3);
complexLayout.getChildren().add(cardView);
```
在上述代码中,`GridPane.setConstraints`方法的第三个和第四个参数定义了`cardView`所占用的列跨度和行跨度。通过这种方式,可以实现复杂的布局需求。
#### 布局的响应式调整
在响应式设计中,布局可能需要根据不同的屏幕尺寸或用户操作进行调整。使用`GridPane`的动态特性,可以调整其行数和列数,以及行和列的大小,以适应不同的布局需求。通过监听窗口大小变化事件,可以动态地更新布局约束。
```java
scene.widthProperty().addListener((observable, oldValue, newValue) -> {
adjustLayoutToWidth(newValue);
});
scene.heightProperty().addListener((observable, oldValue, newValue) -> {
adjustLayoutToHeight(newValue);
});
private void adjustLayoutToWidth(double width) {
// 根据新的宽度调整列宽或添加/删除列
}
private void adjustLayoutToHeight(double height) {
// 根据新的高度调整行高或添加/删除行
}
```
在上述伪代码中,`adjustLayoutToWidth`和`adjustLayoutToHeight`方法将响应窗口尺寸的变化,动态调整布局。这种方式确保了界面在不同设备和屏幕尺寸上的适应性。
### 4.1.2 与其他布局容器的结合使用
虽然`GridPane`本身可以处理复杂的布局需求,但有时与其他布局容器结合使用会更加高效。例如,可以使用`BorderPane`来创建更高级的布局,其中`GridPane`可以作为其中一部分来使用。
#### 结合BorderPane使用
`BorderPane`将界面分为五个部分:上、下、左、右和中心。`GridPane`可以非常方便地放在中心位置,或根据需要放在其他位置。
```java
BorderPane root = new BorderPane();
GridPane centerPane = new GridPane();
// 在此填充GridPane的布局
root.setCenter(centerPane);
```
当与`BorderPane`结合时,可以利用`GridPane`灵活的行和列约束来实现复杂的中心区域布局,而`BorderPane`的其他区域可以包含其他控制元素,如侧边栏或顶部标题栏。
#### 组合布局的优势
组合布局提供了更高的灵活性和模块化,允许开发者针对不同的功能区域使用最适合的布局管理器。这样不仅使得代码更加清晰,而且也利于后续的维护和更新。
## 4.2 GridPane在实际项目中的应用
### 4.2.1 构建动态表格界面
在实际的软件应用中,经常需要动态展示表格数据。`GridPane`可以用来构建响应式的动态表格界面,它允许通过编程方式动态添加和移除表格行和列。
#### 表格数据的动态加载
为了加载动态表格数据,我们首先需要创建一个`GridPane`,然后根据数据模型动态地添加行和列,以及相应的数据节点。
```java
List<DataRow> data = fetchData(); // 获取数据方法,返回数据列表
int rowNumber = 0;
for (DataRow row : data) {
// 创建行和列,并将数据填充到对应的节点中
GridPane rowPane = new GridPane();
// 填充数据到rowPane
gridPane.addRow(rowNumber++, rowPane);
}
```
在上述代码中,`DataRow`代表数据模型,`fetchData`是从数据源获取数据的方法。我们遍历数据列表,并为每个数据创建一个`GridPane`行,然后将这些行添加到主`GridPane`中。
#### 列宽和行高的自适应
对于动态加载的表格数据,合理地设置列宽和行高是提高可读性的关键。`GridPane`提供了多种方法来动态调整列宽和行高,以适应内容的大小。
```java
// 遍历数据和列,为每个单元格设置合适的宽度
for (int col = 0; col < data.get(0).getColumnCount(); col++) {
for (int row = 0; row < data.size(); row++) {
// 获取单元格内容并计算最佳宽度
Node cellContent = getCellContent(data.get(row), col);
double bestWidth = calculateBestWidth(cellContent);
GridPane.setHalignment(cellContent, HPos.CENTER);
GridPane.setHgrow(cellContent, Priority.ALWAYS);
GridPane.setColumnIndex(cellContent, col);
GridPane.setRowIndex(cellContent, row);
GridPane.setMinWidth(cellContent, bestWidth);
GridPane.setMaxWidth(cellContent, bestWidth);
}
}
```
在这个例子中,`calculateBestWidth`方法根据内容计算出最佳宽度,并将`GridPane.setMinWidth`和`GridPane.setMaxWidth`设置为相同的值以固定宽度。`GridPane.setHgrow`和`GridPane.setHalignment`方法用于确保内容水平居中,并允许列宽在必要时扩展。
### 4.2.2 创建自适应响应式布局
`GridPane`不仅能够处理复杂的静态布局,也能够通过编程方式实现响应式布局。响应式布局能够根据用户的显示设备和屏幕大小动态调整界面布局。
#### 实现响应式布局的策略
实现响应式布局需要结合使用CSS样式和JavaFX的布局API。在`GridPane`中,可以通过监听场景大小的变化,并在事件处理中更新`GridPane`的行和列属性来实现。
```java
scene.widthProperty().addListener((obs, oldVal, newVal) -> {
adjustColumns(newVal);
});
scene.heightProperty().addListener((obs, oldVal, newVal) -> {
adjustRows(newVal);
});
private void adjustColumns(double width) {
// 根据宽度调整各列的宽度
}
private void adjustRows(double height) {
// 根据高度调整各行的高度
}
```
在上述代码中,`adjustColumns`和`adjustRows`方法根据窗口大小的变化动态调整列宽和行高。这允许界面在不同设备上自适应显示。
#### 使用CSS增强布局的响应性
CSS样式可以用于定义`GridPane`中元素的默认样式,包括边距、填充、字体大小、颜色等。通过CSS,可以更灵活地控制界面的外观,并可以轻松地为不同屏幕尺寸和设备提供不同的样式规则。
```css
.grid-pane {
-fx-padding: 10px;
-fx-background-color: #fff;
-fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.6), 10, 0.5, 0, 0);
}
.grid-pane .column-header {
-fx-background-color: #444;
-fx-text-fill: #eee;
}
```
在这个CSS示例中,定义了`GridPane`的默认边距、背景颜色和阴影效果。此外,还可以为特定的列或行标题定义样式,增强布局的视觉层次和美观度。
## 4.3 性能优化与调试技巧
### 4.3.1 GridPane内存和性能监控
随着应用的复杂性增加,内存和性能监控变得至关重要。`GridPane`虽然在许多情况下表现良好,但在处理大量动态组件时,可能会成为性能瓶颈。
#### 监控内存和性能的策略
在使用`GridPane`时,应定期检查内存使用情况,以及界面更新对性能的影响。Java VisualVM是监控Java应用内存和性能的有力工具,可以用来监控JavaFX应用。
为了优化性能,应尽量避免不必要的布局更新。当添加或移除组件时,应尽可能地减少对界面的重绘。`GridPane`提供了`requestLayout`方法,可以在更新完布局后调用,以便进行一次性的布局计算。
```java
// 更新布局后调用
gridPane.requestLayout();
```
这有助于减少不必要的计算和重绘,从而提高性能。
#### 使用Java Flight Recorder (JFR)
除了使用Java VisualVM之外,Java Flight Recorder (JFR) 提供了高级别的性能监控功能,非常适合用于生产环境中对性能敏感的应用。JFR可以帮助开发者识别性能瓶颈,包括`GridPane`在渲染和布局更新中可能遇到的问题。
### 4.3.2 问题诊断和调试的高级技术
当遇到性能问题或布局渲染错误时,高效的问题诊断和调试至关重要。可以使用JavaFX的`Debug`工具来获取丰富的调试信息。
#### 使用JavaFX的Debug工具
JavaFX提供了一系列的调试工具和参数,通过`-Dprism.debug`参数可以开启额外的调试信息。
```shell
java -Dprism.debug=true -jar application.jar
```
该参数能够在控制台输出一些有用的信息,如渲染性能统计和内存消耗情况,这些信息可以帮助开发者定位问题。
#### 利用Scene Builder的调试功能
Scene Builder是JavaFX官方推荐的布局工具,它提供了一个可视化的环境,可以帮助开发者更直观地创建和调试界面。使用Scene Builder,可以进行拖放操作快速布局,并实时预览布局效果,非常适合开发过程中的调试工作。
此外,Scene Builder还支持导出布局的FXML文件,开发者可以将此文件导入IDE进行进一步的分析和调试。通过阅读FXML代码,开发者可以更细致地理解布局的构造,从而更有效地进行问题诊断和优化。
通过上述高级应用技巧,开发者可以利用`GridPane`构建出复杂且具有高度响应性的用户界面。同时,结合性能优化与调试技术,可以确保最终应用既美观又具有高效的性能。
# 5. GridPane未来展望与最佳实践
## 5.1 JavaFX与GridPane的未来趋势
随着JavaFX的发展,GridPane作为其中的一员也在不断进步。了解这些未来趋势对于开发者来说至关重要,因为它将指导我们在未来的设计和开发方向。
### 5.1.1 跟踪JavaFX的更新和改进
JavaFX社区一直很活跃,不断有新的更新和功能被引入。对于GridPane来说,最新版本可能已经包含了一些优化,比如性能提升、新的布局属性或者对现代Java版本更好的兼容性。
- **社区关注**: 参与JavaFX社区,关注官方博客和论坛可以让你及时了解最新动态。
- **功能迭代**: 观察新版本的JavaFX,了解它是否引入了新的属性或者方法,可以帮助我们更好地控制布局。
### 5.1.2 探索GridPane在新版本中的新特性
每个新版本的JavaFX都可能为GridPane带来新的特性和改进。这意味着开发者需要不断学习和适应,以便能够利用这些新特性提高开发效率和用户体验。
- **新特性的应用**: 例如,新版本可能允许更灵活地处理组件的尺寸策略或提供新的CSS样式类。
- **实际应用案例**: 通过查看社区分享的新案例,我们可以了解这些新特性是如何被应用到实际项目中的。
## 5.2 建立最佳实践和编码规范
在使用GridPane时,建立最佳实践和编码规范可以帮助维护代码质量,提高开发效率。
### 5.2.1 形成GridPane布局的代码规范
代码规范不仅有助于团队协作,还可以使代码更加一致、易于维护。
- **命名约定**: 使用清晰的命名来描述GridPane中的组件,例如 `userGridPane`, `addressTable`。
- **布局逻辑**: 对于复杂的布局,采用模块化的布局策略,将大型GridPane分解为小的、可管理的部分。
### 5.2.2 分享社区中的成功案例和经验
社区中的成功案例和经验可以为我们提供宝贵的参考和灵感。
- **开源项目**: 观察和分析开源项目中的GridPane使用情况。
- **博客文章**: 阅读相关博客文章,学习其他开发者是如何解决特定问题的。
## 5.3 资源和工具推荐
利用正确的资源和工具可以让我们的开发工作事半功倍。
### 5.3.1 推荐的开发工具和插件
选择适合GridPane开发的工具和插件,可以帮助我们更高效地完成任务。
- **集成开发环境(IDE)**: 如IntelliJ IDEA或Eclipse提供了对JavaFX的完美支持。
- **布局设计工具**: 某些工具,如 Scene Builder,可以让我们用可视化的方式编辑GridPane布局。
### 5.3.2 附加资源链接和学习材料
- **官方文档**: JavaFX官方文档提供了关于GridPane的详尽指导和API参考。
- **在线教程**: 许多网站提供了针对GridPane的详细教程,如Oracle官方教程、DZone等。
通过了解和利用这些资源,我们可以更好地掌握GridPane的使用,并将其有效地应用于项目中。
0
0
相关推荐






