JavaJX学习笔记

一 安装JavaJX

  • 中文文档: https://www.yiibai.com/javafx

  • JavaFX 官方文档的位置: https://docs.oracle.com/javase/8/javase-clienttechnologies.htm

  • 自己的开发环境: mac, jdk1.8

  • 自己本来是用的 2020-06版本,但是失败.后续改成 2020-09版本 才可以.

  1. Help -> Install New Software…
  2. Add…
  3. Name: e(fx)clipse Location: http://download.eclipse.org/efxclipse/updates-released/2.3.0/site/
  4. 选中出现的2行,然后下一步到最后即可. 然后重启.

二 新建第一个javaJX程序

  1. File -> New -> Project -> JavaFX / JavaFX Project
package application;
	
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;


public class Main extends Application {
	@Override
	public void start(Stage primaryStage) {
		try {
			BorderPane root = new BorderPane();
			Scene scene = new Scene(root,400,400);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch(Exception e) {
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		launch(args);
	}
}

  1. 出现上述代码, 表示成功.

三 JavaFX 项目结构

默认生成一下文件:

  • Main.java 主界面
  • application.css 样式
  • build.fxbuild 用于程序的打包发布

1 Main.java里的结构

  • Application: 应用程序
  • Stage: 舞台(窗口)
  • Scene: 场景
  • Node: 节点

Stage -> Scene -> Pane(Node Tree)

在这里插入图片描述


四 常见操作

1 添加标签 Label

Label 标签 用于显示文本

  • 演示: 添加一个 Label控件, 显示一段文本
// 添加文本
Label label = new Label("hello world!");
root.setCenter(label);

完整代码如下

package application;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application
{
	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			BorderPane root = new BorderPane();

			// 添加文本
			Label label = new Label("hello world!");
			root.setCenter(label);

			Scene scene = new Scene(root, 400, 400);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());

			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

对象的层级结构

在这里插入图片描述

2 添加按钮

  1. 添加一个按钮
Button btn = new Button("点我");
root.setCenter(btn);
  1. 添加事件处理器类 MyEventHandler (内部类)
	private class MyEventHandler implements EventHandler<ActionEvent>
	{
		@Override
		public void handle(ActionEvent e)
		{
			System.out.println("哎哟!");			
		}		
	}

注意:

  • EventHandler 是一个接口(泛型接口, 带类型参数)
  • 导包的时候要 import javafx.* 开头的包
  1. 设置处理器 将按钮和事件绑定
MyEventHandler handler = new MyEventHandler();
btn.setOnAction(handler);

或者直接写成一行

btn.setOnAction(new MyEventHandler());

  1. 上述是 先写自定义类实现接口,然后再绑定. 当我们熟练之后,可以使用 匿名类.
btn.setOnAction(new EventHandler<ActionEvent>() {
	@Override
	public void handle(ActionEvent arg0)
		{
			System.out.println("哎哟!");						
		}				
});
  1. 不建议使用 lambda 表达式
  • lambda表达式降低了可读性!新人请勿使用 lambda表达式!
  • 重点:掌握内部类的写法! 当熟练之后再写成匿名类!

完整代码

package application;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application
{
	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			BorderPane root = new BorderPane();

			// 添加一个按钮
			Button btn = new Button("hello world");
			root.setCenter(btn);

			// 事件绑定处理 方法一
			MyEventhandler handler = new MyEventhandler();
			// 给按钮绑定事件
			btn.setOnAction(handler);

			/*
			 * 事件绑定处理 方法二
			 * 上述可以简写为 btn.setOnAction(new MyEventhandler());
			 * 
			 */

			/**
			 * 事件绑定处理 方法三
			 * 匿名类
				btn.setOnAction(new EventHandler<ActionEvent>()
				{
					@Override
					public void handle(ActionEvent event)
					{
						System.out.println("触发点击事件!");
					}
	
				});
			*/

			Scene scene = new Scene(root, 400, 400);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	private class MyEventhandler implements EventHandler<ActionEvent>
	{

		@Override
		public void handle(ActionEvent event)
		{
			System.out.println("触发点击事件!");

		}

	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

3 添加图片

  • Image: 用于加载图片文件
  • ImageView: 用于显示图片
  • 位置: javafx.scene.image.*

图片的来源

Image 可以用来加载多重渠道的图片,支持bmp, jpg, jpeg, gif, png图片格式.

Image image = new Image(URL)

其中, URL: Uniform Resource Locator , URL可以指向网络图片,资源图片或本地图片.

  1. 显示网络图片

Image image = new Image(“https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png”);

完整代码

package application;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application
{
	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			BorderPane root = new BorderPane();

			// 添加网络图片
			Image image = new Image("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png");
			ImageView imageView = new ImageView();
			imageView.setImage(image);
			// 图片加入到窗口中
			root.setCenter(imageView);

			Scene scene = new Scene(root, 400, 400);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

2 显示本地文件

Image image = new Image( “file:/c:/examples/1.jpg” )

可以这么写:

File f = new File("c:/examples/1.jpg");
String url = f.toURI().toString();			
Image image= new Image(url);

简写为:

File f = new File("c:/examples/1.jpg");		
Image image= new Image(f.toURI().toString());
  • 注: 和网络图片比较: 一个是 http: 开头的, 一个是 file: 开头

完整代码

package application;

import java.io.File;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application
{
	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			BorderPane root = new BorderPane();

			/*
			 * 另外一种写法
			 * File f = new File("1.jpg"); 
			 * String url = f.toURI().toString(); 
			 * Image image = new Image(url);
			 * 
			 */

			Image image = new Image("file:1.jpg");
			ImageView imageView = new ImageView();
			imageView.setImage(image);
			root.setCenter(imageView);

			// 根据图片的宽度高度来设置窗口的宽度高度
			double width = image.getWidth();
			double height = image.getHeight();

			Scene scene = new Scene(root, width, height);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

3 显示资源文件

放在 CLASSPATH 下的文件称为资源文件(与 class 文件一起), 资源文件用包路径定位, 例如

Image image = new Image(“application/2.jpg”);

其中, application 是 package 路径.

注: Eclipse 会自动把 src 下的文件复制到 bin 目录, 程序运行时访问的是 bin 下的文件. (bin在CLASSPATH里)

完整代码

package application;

import java.io.File;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application
{
	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			BorderPane root = new BorderPane();

			// 资源写法
			Image image = new Image("application/2.jpg");
			ImageView imageView = new ImageView();
			imageView.setImage(image);
			root.setCenter(imageView);

			// 根据图片的宽度高度来设置窗口的宽度高度
			double width = image.getWidth();
			double height = image.getHeight();

			Scene scene = new Scene(root, width, height);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

4 相册案例

  1. 布局

上面显示按钮 Button,中间显示图片 ImageView

root.setTop(btnOpen); // 上面显示按钮
root.setCenter(imageView); // 中间显示图片

  1. 图片数组
	// 资源图片
	String[] imageUrls = {
		"application/res/hangmu.jpg",
		"application/res/tiangong.jpg",
		"application/res/kongjing500.jpg",
	};
	Image[] images = new Image[3];
	ImageView imageView = new ImageView();

方法中,加载图片

			for(int i=0; i<images.length; i++)
			{
				images[i] = new Image(imageUrls[i]);
			}
  1. 响应按钮点击
// 按钮点击事件的处理
			btnOpen.setOnAction(new EventHandler<ActionEvent>() {
				@Override
				public void handle(ActionEvent event)
				{
					showNext();					
				}				
			});
  1. 循环显示图片
	// 显示下个图片
	public void showNext()
	{
		currentIndex ++;
		if(currentIndex >=3) currentIndex = 0;		
		imageView.setImage( images[ currentIndex ]);
	}

完整代码

package application;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application
{

	// 资源图片
	String[] imageUrls =
	{ "application/res/1.jpg", "application/res/2.jpg", "application/res/3.jpg" };

	Image[] images = new Image[3];
	ImageView imageView = new ImageView();

	int currentIndex = 0; // 当前显示的是哪个图片

	public void showNext()
	{
		currentIndex++;
		if (currentIndex >= 3)
			currentIndex = 0;
		imageView.setImage(images[currentIndex]);
	}

	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			BorderPane root = new BorderPane();

			Button btnOpen = new Button("下一张");
			// 按钮点击事件的处理
			btnOpen.setOnAction(new EventHandler<ActionEvent>()
			{
				@Override
				public void handle(ActionEvent event)
				{
					showNext();
				}

			});

			// 加载图片
			for (int i = 0; i < images.length; i++)
			{
				images[i] = new Image(imageUrls[i]);
			}

			// 保持比例,适应窗口显示
			imageView.setPreserveRatio(true);
			imageView.setFitWidth(400);
			imageView.setFitHeight(400);

			root.setTop(btnOpen); // 上面显示按钮
			root.setCenter(imageView); // 中间显示图片

			Scene scene = new Scene(root, 400, 400);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
			
			showNext();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

五 布局

1 边缘布局 BorderPane

语法分析

无论是容器还是控件, 都是继承于 Node.

JavaFX 里有几种 Node:

  • 容器, 如 BorderPane, HBox
  • 控件, 如 Button, Label
  • 形状, 如 Circle, Rectangle ( Text 也是形状 )
  • 图表, 如 PieChart, XYChart
  • 其他, 如 Canvas, ImageView, MediaView, WebView

示例

package application;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Label;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class Main extends Application
{
	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			BorderPane root = new BorderPane();
			
			//顶部
			Label label = new Label("大桥");
			root.setTop(label);
			
			//中央
			Image image = new Image("application/bridge.jpg");
			ImageView imageView = new ImageView();
			imageView.setImage(image);
			imageView.setPreserveRatio(true);
			imageView.setFitWidth(400);
			imageView.setFitHeight(400);
			root.setCenter(imageView);
			
			
			Scene scene = new Scene(root, 400, 400);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

在这里插入图片描述

2 水平布局 HBox

语法分析

hbox.getChildren().addAll(…)
不定长度的参数: 可以任意个数的参数

hbox.getChildren().addAll(node1, node2, node3)
相当于
hbox.getChildren().add( node1 );
hbox.getChildren().add( node2 );
hbox.getChildren().add( node3 );

案例的效果和实现代码如下

在这里插入图片描述

package application;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;

public class Main extends Application
{
	@Override
	public void start(Stage primaryStage)
	{
		try
		{

			HBox hbox = new HBox();
			// 设置 HBox 竖直居中
			hbox.setAlignment(Pos.CENTER);
			// 设置距离(padding)
			hbox.setPadding(new Insets(10));

			TextField textField = new TextField();
			Button selectBtn = new Button("选择文件");
			Button uploadBtn = new Button("开始上传");

			// 控件加到 HBox中
			hbox.getChildren().addAll(textField, selectBtn, uploadBtn);

			// 让 textField 随着水平方向的增长而增长
			HBox.setHgrow(textField, Priority.ALWAYS);

			// 修改 HBox 的高度
			Scene scene = new Scene(hbox, 400, 40);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

3 控件的尺寸

在一个 HBox 中添加3个按钮, 观察它们的显示尺寸

在这里插入图片描述

默认的, 每个按钮的尺寸不同 (与文本长度相关)

思考: 如何修改每个按钮的 显示尺寸 ?

控件有3个属性:

  • 最小尺寸: Min
  • 最佳尺寸: Pref
  • 最大尺寸: Max

同时存在的话, 已最佳尺寸为主. 例如

b1.setMinWidth(10); ×
b1.setPrefWidth(200); √ 3条同时存在的话, 已最优尺寸为最终样式.
b1.setMaxWidth(400); ×

可以分别设置宽度和高度, 例如:

Button b1 = new Button("test");
b1.setMaxWidth(100);
b1.setMaxHeight(50);
b1.setMaxSize(100, 500);

几个常量

  • Control.USE_COMPUTED_SIZE (-1.0) 默认值
  • Control.USE_PREF_SIZE
  • Double.MAX_VALUE

比如: USE_COMPUTED_SIZE 控件能完成显示内容所需要的尺寸,例如, 显示 “中国” 和 “中国人民共和国” 尺寸是不一样的.

完整代码

package application;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Control;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class Main extends Application
{
	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			// 声明 HBox 对象(这里是 根节点)
			HBox hbox = new HBox();

			// 声明3个按钮
			Button btn1 = new Button("中国");
			Button btn2 = new Button("China");
			Button btn3 = new Button("中华人民共和国");

			// 设置 btn1
			btn1.setMinWidth(10); // 最小宽度
			btn1.setPrefWidth(100); // 最优宽度
			btn1.setMaxWidth(400); // 最大宽度

			// 设置 btn2
			btn2.setMinHeight(50); // 最小宽度
			
			// 设置 btn3 用常量的方式
			btn3.setMinWidth(Control.USE_COMPUTED_SIZE);
			btn3.setMinHeight(Control.USE_PREF_SIZE);

			// 将3个 button 加入到 HBox
			hbox.getChildren().addAll(btn1, btn2, btn3);

			Scene scene = new Scene(hbox, 400, 50);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

  • 结论: 显示尺寸最终由布局容器决定, min, pref, max 只是参考值.

4 控件的布局

  • 填充 Padding: 子控件(与父级)的边距
  • 间距 Spacing: 各个子控件之间的间距
  • 对齐 Alignment: 靠左,靠右,居中…
  1. 填充方法 setPadding() 方法

hbox.setPadding(new Insets(10)); // 上右下左都是 10.
hbox.setPadding(new Insets(10, 10, 30, 40)); // 单独设置 上右下左的距离.

  1. 设置间距 setSpacing() 方法

hbox.setSpacing(10); // 每个子控件之间的距离是 10

  1. 设置对齐,对齐分为 水平和竖直方向.

hbox.setAlignment(Pos.CENTER); // 这样写是 水平方向和竖直方向都居中对齐.

实现效果

在这里插入图片描述
完整代码

package application;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;

public class Main extends Application
{
	TextField a = new TextField();
	Button b = new Button("选择文件");
	Button c = new Button("上传");

	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			HBox hbox = new HBox();
			hbox.getChildren().addAll(a, b, c);

			// 填充 padding, 上下左右都是10px
			hbox.setPadding(new Insets(10));

			// 间距 每个子控件之间的距离
			hbox.setSpacing(10);

			// 对齐
			hbox.setAlignment(Pos.CENTER);  // 水平方向和竖直方向都是对齐的

			Scene scene = new Scene(hbox, 400, 40);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

增长优先

HBox.setHgrow( a, Priority.ALWAYS);

实现效果

在这里插入图片描述

精确布局

例如, 在 HBox 下有 a,b,c 三个控件

  • a:固定300px
  • b: 剩余空间的 50%
  • c: 剩余空间的 50%

这样的布局该如何实现呢?

  • 自定义类继承 HBox类
  • 然后定义各个子控件的 大小和坐标. resizeRelocate() 方法
package application;

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextField;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;

public class Main extends Application
{
	TextField a = new TextField();
	Button b = new Button("选择文件");
	Button c = new Button("上传");

	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			HBox hbox = new MyHBox();
			hbox.getChildren().addAll(a, b, c);

			Scene scene = new Scene(hbox, 500, 40);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	// 派生 HBox
	class MyHBox extends HBox
	{
		protected void layoutChildren()
		{
			// 如果完全自己计算布局, 就不需要调用 super
			super.layoutChildren();

			// 自定义布局
			double width = getWidth();
			double height = getHeight();

			// 计算每个控件的宽度
			double aa = 300;
			double bb = (width - aa) * 0.5;
			double cc = width - aa - bb;

			// 依次定位: 左上角坐标 x,y/宽,高
			double x = 0;
			a.resizeRelocate(x, 0, aa, height);
			x += aa;
			b.resizeRelocate(x, 0, bb, height);
			x += bb;
			c.resizeRelocate(x, 0, cc, height);
		}
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

实现效果

在这里插入图片描述

5 自定义容器

容器 Pane: 可以容纳多个子节点的窗口面板

现有子类:

AnchorPane, BorderPane, DialogPane, FlowPane, GirdPane, HBox, StackPane, TextFlow, TilePane, VBox…

自定义容器: 派生Pane类, 重写 layoutChildren()

注: Pane.getChildren() 可以获取它的子节点的列表.

6 布局的嵌套

思考: 这样的界面如何实现?

在这里插入图片描述

// 顶部
hbox.getChildren().addAll(textField, button);
HBox.setHgrow(textField, Priority.ALWAYS);

// BorderPane 里嵌套HBox
BorderPane root = new BorderPane();
root.setTop(hbox);
root.setCenter(textArea);

最终效果

在这里插入图片描述

完整代码

package application;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;

public class Main extends Application
{
	HBox hbox = new HBox();
	TextField textField = new TextField();
	Button button = new Button("添加");
	TextArea textArea = new TextArea();

	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			// 顶部
			hbox.getChildren().addAll(textField, button);
			HBox.setHgrow(textField, Priority.ALWAYS);

			// BorderPane 里嵌套HBox
			BorderPane root = new BorderPane();
			root.setTop(hbox);
			root.setCenter(textArea);

			// 实现点击之后,将 textField的内容加入到 TextArea中
			// 按钮点击事件
			button.setOnAction(new EventHandler<ActionEvent>()
			{

				@Override
				public void handle(ActionEvent event)
				{
					onAdd();
				}

			});

			Scene scene = new Scene(root, 400, 400);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public void onAdd()
	{
		// 在 TextField 中,取出用户输入的文本
		String str = textField.getText();
		// 把取到的文本加入到 下面的文本框中
		textArea.appendText(str + "\n");
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

六 列表控件

1 列表控件 ListView

JavaFX 有 4种控件来表示多项数据:

  • ListView: 列表
  • TreeView: 树
  • TableView: 表格
  • TreeTableView: 多列列表

创建ListView步骤

  1. 定义数据项

添加一个类,表示列表中每一项对应的数据

	static class Student
	{
		public int id;
		public String name;
		public boolean sex;
		
		public Student()
		{			
		}		
		public Student(int id, String name, boolean sex)
		{
			this.id = id;
			this.name = name;
			this.sex= sex;
		}		
	}
  1. 数据源

用一个ObservableList来存储原始数据:

ObservableList listData = FXCollections.observableArrayList();

注:ObservableList 是一个比较难懂的设计,初学时只要把它当成普通的ArrayList来用即可。

  1. 添加数据
listData.add(new Student(1, "shao", true));
listData.add(new Student(2, "wang", true));
listData.add(new Student(3, "jiang", false));
  1. 创建ListView

创建 ListView时,要指定数组项的类型 。在JavaFX里,泛型被广泛使用,大家要学习习惯它。

ListView listView = new ListView();

  1. 设置数据源

listView.setItems( listData );

  1. 设置单元格的显示

添加 ListCell 类, ListCell负责列表项里每一个Cell的显示

	static class MyListCell extends ListCell<Student>
	{
		@Override
		public void updateItem(Student item, boolean empty)
		{
			// FX框架要求必须先调用 super.updateItem()
			super.updateItem(item, empty);			
			// 自己的代码
			if (item == null)
			{
				this.setText(""); // 清空显示
			}
			else
			{
				this.setText( item.name  ); // 显示该项的值
			}
		}
	}

可以看到,这里也使用了泛型。每一处泛型的参数都是

  1. 设置单元格生成器

JavaFX里广泛使用了 Factory 的概念,大致意思是说, 由一个工厂来负责提供每个Cell

	listView.setCellFactory(new Callback<ListView<Student>,ListCell<Student>>() {
		@Override
		public ListCell<Student> call(ListView<Student> param)
		{
			return new MyListCell();
		}				
	});

可以看到,call() 方法返回的是一个 ListCell对象。

  1. 添加到布局

BorderPane root = new BorderPane();
root.setCenter(listView);

显示效果如图:

在这里插入图片描述

完整代码

package application;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.scene.Scene;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;

public class Main extends Application
{
	// 创建 ListView, 指定数据项类型
	ListView<Student> listView = new ListView<Student>();

	// 数据源
	ObservableList<Student> listData = FXCollections.observableArrayList();

	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			// 准备数据
			listData.add(new Student(1, "shao", true));
			listData.add(new Student(2, "wang", true));
			listData.add(new Student(3, "jiang", false));

			// 设置数据源
			listView.setItems(listData);

			// 设置单元格生成器 (工厂)
			listView.setCellFactory(new Callback<ListView<Student>, ListCell<Student>>()
			{

				@Override
				public ListCell<Student> call(ListView<Student> param)
				{
					return new MyListCell();
				}

			});

			BorderPane root = new BorderPane();
			root.setCenter(listView);

			Scene scene = new Scene(root, 400, 400);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	// 负责单元格Cell的显示
	static class MyListCell extends ListCell<Student>
	{
		@Override
		public void updateItem(Student item, boolean empty)
		{
			// FX框架要求必须先调用 super.updateItem()
			super.updateItem(item, empty);

			// 自己的代码
			if (item == null)
			{
				this.setText(""); // 清空显示
			} else
			{
				this.setText(item.name); // 显示该项的值
			}
		}
	}

	// 数据项
	static class Student
	{
		public int id;
		public String name;
		public boolean sex;

		public Student()
		{
		}

		public Student(int id, String name, boolean sex)
		{
			this.id = id;
			this.name = name;
			this.sex = sex;
		}

	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

2 列表项的操作

直接对ObservableList 进行增加、删除、和修改操作,ListView会被通知到并自动更新内容。

  1. 增加一项
	public void onInsert()
	{
		String name = textField.getText();
		listData.add(new Student(0, name, true));		
	}
  1. 删除当前选中项
	public void onRemove()
	{
		int index = listView.getSelectionModel().getSelectedIndex(); //当前选中项
		if(index >=0 )
		{
			listData.remove( index );
		}		
	}
  1. 修改当前选中项
	public void onChange()
	{
		String name = textField.getText();
		int index = listView.getSelectionModel().getSelectedIndex();
		if(index >=0 )
		{
			Student s = listData.get(index);
			s.name = name;
			listData.set(index, s);
		}	
	}

实现效果

在这里插入图片描述

完整代码

package application;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.stage.Stage;
import javafx.util.Callback;

public class Main extends Application
{
	// 创建ListView,指定数据项类型
	ListView<Student> listView = new ListView<Student>();

	// 数据源
	ObservableList<Student> listData = FXCollections.observableArrayList();

	HBox hbox = new HBox();
	TextField textField = new TextField();
	Button btnInsert = new Button("增加");
	Button btnRemove = new Button("删除");
	Button btnChange = new Button("修改");

	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			// 准备数据
			listData.add(new Student(1, "shao", true));
			listData.add(new Student(2, "wang", true));
			listData.add(new Student(3, "jiang", false));

			// 设置数据源
			listView.setItems(listData);

			// 设置单元格生成器(工厂)
			listView.setCellFactory(new Callback<ListView<Student>, ListCell<Student>>()
			{

				@Override
				public ListCell<Student> call(ListView<Student> param)
				{
					return new MyListCell();
				}

			});

			BorderPane root = new BorderPane();
			root.setCenter(listView);

			Scene scene = new Scene(root, 400, 400);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();

			hbox.getChildren().addAll(textField, btnInsert, btnRemove, btnChange);
			HBox.setHgrow(textField, Priority.ALWAYS);
			root.setTop(hbox);
			btnInsert.setOnAction(new EventHandler<ActionEvent>()
			{

				@Override
				public void handle(ActionEvent event)
				{
					onInsert();
				}

			});

			btnRemove.setOnAction(new EventHandler<ActionEvent>()
			{

				@Override
				public void handle(ActionEvent event)
				{
					onRemove();
				}

			});

			btnChange.setOnAction(new EventHandler<ActionEvent>()
			{

				@Override
				public void handle(ActionEvent event)
				{
					onChange();
				}

			});

		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	public void onInsert()
	{
		String name = textField.getText();
		listData.add(new Student(0, name, true)); // 里面会通知 ListView
	}

	public void onRemove()
	{
		int index = listView.getSelectionModel().getSelectedIndex();
		if (index >= 0)
		{
			listData.remove(index); // 里面会通知 ListView
		}
	}

	public void onChange()
	{
		String name = textField.getText();
		int index = listView.getSelectionModel().getSelectedIndex();
		if (index >= 0)
		{
			Student s = listData.get(index);
			s.name = name;
			listData.set(index, s); // 里面会通知 ListView
		}
	}

	// 负责单元格 Cell 的显示
	static class MyListCell extends ListCell<Student>
	{

		@Override
		protected void updateItem(Student item, boolean empty)
		{
			// FX框架要求必须先调用 super.updateItem()
			super.updateItem(item, empty);

			// 自己的代码
			if (item == null)
			{
				this.setText(""); // 清空
			} else
			{
				this.setText(item.name);// 显示该项的值
			}
		}

	}

	// 数据项
	static class Student
	{
		public int id;
		public String name;
		public boolean sex;

		public Student()
		{

		}

		public Student(int id, String name, boolean sex)
		{
			this.id = id;
			this.name = name;
			this.sex = sex;
		}

	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

3 多项选择

  • 源代码看不懂

4 鼠标事件处理

  • 鼠标事件 MouseEvent

通常在 GUI 开发时, 我们关注的鼠标事件有:

  • 鼠标左键点击(选中)
  • 鼠标左键双击(打开)
  • 鼠标右键点击(上下文菜单)
  • 鼠标拖拽…

在 MouseEvent 对象里, 能得到以下信息:

  • event.getButton() 按钮(左,中,右)
  • event.getClickCount() 移动(0), 单击(1), 双击(2)
  • event.getX() 点击位置(窗口坐标)
  • event.getScenex() 点击位置(屏幕坐标)

鼠标点击事件的处理

  • listView.setOnMouseClicked(EventHandler)

5 实战练习-阅读器

七 树控件

1 树控件

树控件, TreeView, 以树状结构来显示数据用法与 ListView 类似

注意:

  1. TreeItem 类用于表示树上的一个节点
  2. 一个 TreeView 必须有一个根节点
  3. TreeItem 可以添加图标 TreeItem.setGraphic()

步骤

  1. 数据项, 先定义一个类,表示每一个树节点所对应的数据
	private static class ItemData
	{
		public String name; // 文件名或目录名
		boolean isDir = false; // 是否目录
		
		public ItemData() 
		{			
		}
		public ItemData(boolean isDir, String name)
		{
			this.isDir = isDir;
			this.name = name;
		}		
	}
  1. 单元格的显示, TreeCell 负责每个树节点的显示
	private static class MyTreeCell extends TreeCell<ItemData>
	{
		@Override
		protected void updateItem(ItemData item, boolean empty)
		{
			super.updateItem(item, empty);
			if(item == null)
			{
				this.setText("");
			}
			else
			{
				this.setText(item.name);
			}
		}		
	}
  1. 添加节点

向 TreeView 里添加节点

(1)添加根节点

TreeItem item_root = new TreeItem( … )
treeView.setRoot ( item_root )

(2)添加子节点

TreeItem item_c = new TreeItem (…)
TreeItem item_java = new TreeItem (…)
item_root.getChildren().addAll ( item_c, item_java )

(3)继续添加子节点 …

在每个TreeItem 下又可以添加下一层子节点,最终形成一个树状结构

完整代码

package application;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TreeCell;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;

public class Main extends Application
{

	TreeView<ItemData> treeView = new TreeView<ItemData>();

	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			BorderPane root = new BorderPane();

			initTreeView();
			root.setCenter(treeView);

			Scene scene = new Scene(root, 400, 400);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	// 初始化树
	private void initTreeView()
	{
		ItemData data_root = new ItemData(true, "啊发你好");
		TreeItem<ItemData> item_root = new TreeItem<ItemData>(data_root);

		ItemData data_c = new ItemData(true, "C/C++学习指南");
		TreeItem<ItemData> item_c = new TreeItem<ItemData>(data_c);

		ItemData data_java = new ItemData(true, "Java学习指南");
		TreeItem<ItemData> item_java = new TreeItem<ItemData>(data_java);

		ItemData data_common = new ItemData(true, "公共基础课程");
		TreeItem<ItemData> item_common = new TreeItem<ItemData>(data_common);

		item_root.getChildren().addAll(item_c, item_java, item_common);

		// 在"Java学习指南系列" 下添加子节点
		if (true)
		{
			ItemData data = new ItemData(false, "快速入门");
			item_java.getChildren().add(new TreeItem<ItemData>(data));
		}
		if (true)
		{
			ItemData data = new ItemData(false, "高级语法");
			item_java.getChildren().add(new TreeItem<ItemData>(data));
		}

		// 在"公共基础课程" 下添加子节点
		if (true)
		{
			ItemData data = new ItemData(false, "CentOS使用教程");
			item_common.getChildren().add(new TreeItem<ItemData>(data));
		}
		if (true)
		{
			ItemData data = new ItemData(false, "MySQL使用教程");
			item_common.getChildren().add(new TreeItem<ItemData>(data));
		}

		// 设置根节点
		treeView.setRoot(item_root);

		// 设置 CellFactory
		treeView.setCellFactory(new Callback<TreeView<ItemData>, TreeCell<ItemData>>()
		{

			@Override
			public TreeCell<ItemData> call(TreeView<ItemData> param)
			{
				return new MyTreeCell();
			}

		});

	}

	// 单元格的使用
	private static class MyTreeCell extends TreeCell<ItemData>
	{
		@Override
		protected void updateItem(ItemData item, boolean empty)
		{
			super.updateItem(item, empty);
			if (item == null)
			{
				this.setText("");
			} else
			{
				this.setText(item.name);
			}
		}
	}

	private static class ItemData
	{
		public String name; // 文件名或目录
		public boolean isDir = false; // 是否目录

		public ItemData()
		{

		}

		public ItemData(boolean isDir, String name)
		{
			this.isDir = isDir;
			this.name = name;
		}
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

2 多列树控件

TreeTableView:

特点:

  • 整体是树状结构
  • 支持多列显示
  • 基于行进行操作, 每行为一个 TreeItem, 每列显示一个字段.

3 实战练习 - 文件浏览器

八 对话框

1 简单提示框 Alert

什么叫模式对话框(Model)?

  • showAndWait() 时, 当前输入的焦点在对话框里,后面的界面无法输入.
  • 程序卡在 showAndWait() 不往下走, 直到对话框关闭.
			// 弹窗显示
			Alert warning = new Alert(AlertType.WARNING);
			warning.setHeaderText("出错");
			warning.setContentText("用户名错误");
			warning.showAndWait();

完整代码

package application;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Alert;
import javafx.scene.control.Alert.AlertType;
import javafx.scene.control.Button;
import javafx.scene.control.PasswordField;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application
{
	// 用户输入框
	TextField username = new TextField();
	// 密码框
	PasswordField password = new PasswordField();
	// 登录按钮
	Button login = new Button("登录");

	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			BorderPane root = new BorderPane();

			VBox vbox = new VBox();
			vbox.getChildren().addAll(username, password, login);
			// 设置子控件的间距
			vbox.setSpacing(10);

			root.setCenter(vbox);
			// application.css 样式定义
			root.setId("root");

			username.setPromptText("用户名"); // 提示文字
			password.setPromptText("密码"); // 密码
			login.setMaxWidth(Double.MAX_VALUE); // 按钮放宽显示

			Scene scene = new Scene(root, 400, 400);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();

			// 登录按钮事件
			login.setOnAction(new EventHandler<ActionEvent>()
			{

				@Override
				public void handle(ActionEvent event)
				{
					doLogin();
				}

			});

		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	private void doLogin()
	{
		String _user = username.getText();
		String _pass = password.getText();

		// 检查用户名和密码
		if (!_user.equals("shaofa") || !_pass.equals("123456"))
		{
			System.out.println("用户名/密码错误!");

			// 弹窗显示
			Alert warning = new Alert(AlertType.WARNING);
			warning.setHeaderText("出错");
			warning.setContentText("用户名错误");
			warning.showAndWait();

			System.out.println("下一步 ...");
		} else
		{
			Alert warning = new Alert(AlertType.INFORMATION);
			warning.setHeaderText("成功");
			warning.setContentText("欢迎进入系统!");
			warning.showAndWait();
		}
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

显示效果

在这里插入图片描述

2 自定义对话框

除了 Alert 之外, 自带的对话框类还有 TextInputDialog, ChoiceDialog.

示例

package application;

import java.util.Optional;

import javafx.application.Application;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ButtonBar.ButtonData;
import javafx.scene.control.ButtonType;
import javafx.scene.control.Dialog;
import javafx.scene.control.DialogPane;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application
{
	TextArea textArea = new TextArea();

	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			BorderPane root = new BorderPane();

			root.setCenter(textArea);

			Scene scene = new Scene(root, 400, 400);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();

			// 添加按钮
			Button add = new Button("添加");
			root.setTop(add);
			add.setOnAction(new EventHandler<ActionEvent>()
			{

				@Override
				public void handle(ActionEvent event)
				{
					onAdd();
				}

			});

		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	private void onAdd()
	{
		// 对话框内容
		VBox content = new VBox();
		TextField t_id = new TextField();
		TextField t_name = new TextField();
		TextField t_phone = new TextField();
		t_id.setPromptText("学号");
		t_name.setPromptText("姓名");
		t_phone.setPromptText("手机号");
		content.setSpacing(10);
		content.getChildren().addAll(t_id, t_name, t_phone);

		// Dialog -> DialogPane -> Root Node
		DialogPane dialogPane = new DialogPane();
		dialogPane.setContent(content);

		// 添加按钮
		ButtonType ok = new ButtonType("确定", ButtonData.OK_DONE);
		dialogPane.getButtonTypes().add(ok);

		// 创建对话框
		Dialog<ButtonType> dlg = new Dialog<ButtonType>();
		dlg.setDialogPane(dialogPane);
		dlg.setTitle("添加学生信息");

		// 显示对话框, 并接受返回结果
		Optional<ButtonType> result = dlg.showAndWait();
		if (result.isPresent() && result.get().getButtonData() == ButtonData.OK_DONE)
		{
			int id = Integer.valueOf(t_id.getText());
			String name = t_name.getText();
			String phone = t_phone.getText();
			textArea.appendText("学号: " + id + "\t姓名: " + name + "\t手机号: " + phone + "\n");
		}
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

显示效果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3 对话框的封装

  • 封装成一个工具类, 可以重复使用

使用场景

  • 该对话框需在多处都有调用
  • 该对话框内容比较负责

4 文件对话框

使用 FileChooser 可以打开一个文件选择对话框

  • 以读方式选择一个文件
  • 以写方式选择一个文件

区别:

  • 读方式时, 要选择一个已经存在的文件
  • 写方式时, 可以选择存在的文件, 也可以新输入一个文件

九 菜单栏

1 菜单栏

  • 菜单栏 MenuBar

2 右键菜单

  • 上下文菜单 Context Menu
package application;

import javafx.application.Application;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.scene.Scene;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.ListCell;
import javafx.scene.control.ListView;
import javafx.scene.control.MenuItem;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
import javafx.util.Callback;

public class Main extends Application
{
	// 创建ListView, 指定数据项类型
	ListView<Student> listView = new ListView<Student>();

	// 数据源
	ObservableList<Student> listData = FXCollections.observableArrayList();

	// ListView 的上下文菜单
	ContextMenu listContextMenu = new ContextMenu();

	@Override
	public void start(Stage primaryStage)
	{
		try
		{
			// 准备数据
			listData.add(new Student(1, "shao", true));
			listData.add(new Student(2, "wang", true));
			listData.add(new Student(3, "jiang", false));

			// 设置数据源
			listView.setItems(listData);

			// 设置单元格生成器(工厂)
			listView.setCellFactory(new Callback<ListView<Student>, ListCell<Student>>()
			{

				@Override
				public ListCell<Student> call(ListView<Student> param)
				{
					return new MyListCell();
				}

			});

			initContextMenu();

			BorderPane root = new BorderPane();
			root.setCenter(listView);

			Scene scene = new Scene(root, 400, 400);
			scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
			primaryStage.setScene(scene);
			primaryStage.show();
		} catch (Exception e)
		{
			e.printStackTrace();
		}
	}

	// 初始化上下文菜单
	private void initContextMenu()
	{
		MenuItem menuItemRemove = new MenuItem("删除");
		menuItemRemove.setOnAction((ActionEvent e) -> {
			removeItem();
		});

		// 添加菜单项
		listContextMenu.getItems().add(menuItemRemove);

		// 给ListView设置上下文菜单
		listView.setContextMenu(listContextMenu);
	}

	// 删除当前选中的项
	private void removeItem()
	{
		int index = listView.getSelectionModel().getSelectedIndex();
		if (index >= 0)
		{
			listData.remove(index);
		}
	}

	// 负责单元格Cell的显示
	static class MyListCell extends ListCell<Student>
	{
		@Override
		public void updateItem(Student item, boolean empty)
		{
			// FX框架要求必须先调用 super.updateItem()
			super.updateItem(item, empty);

			// 自己的代码
			if (item == null)
			{
				this.setText(""); // 清空显示
			} else
			{
				this.setText(item.name); // 显示该项的值
			}
		}
	}

	// 数据项
	static class Student
	{
		public int id;
		public String name;
		public boolean sex;

		public Student()
		{
		}

		public Student(int id, String name, boolean sex)
		{
			this.id = id;
			this.name = name;
			this.sex = sex;
		}
	}

	public static void main(String[] args)
	{
		launch(args);
	}
}

3 实战练习

十 样式单

1 使用样式单

  • CSS, Cascading Style Sheets 用于定义界面的显示样式(背景, 边框, 字体等)

-fx-padding: 填充

-fx-font-size: 字体大小

-fx-text-fill: 文字颜色

2 实战练习-列表的样式

  • 2
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值