小菜鸟的JAVA学习日记002——画图板

这篇博客详细介绍了如何使用JAVA构建一个简单的画图板应用。作者探讨了画图板类Panel的实现,包括继承JPanel的原因,以及如何利用Graphics对象进行绘图。还讨论了监听器类Listener的设计,如使用MouseAdapter简化鼠标事件处理。此外,还提到了图形类Shape的创建,用于保存和重绘图形,以及实现后退和清空功能的策略。最后,文章涵盖了设置线条粗细和橡皮擦功能的实现细节。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1、大致思路:

  • 画图板类Panel

要制作画图板的大致框架,首先需要一个画板(JPanel),然后是选择要画的图形、画笔的颜色、粗细以及后退、清除的按钮(JButton),最后将这个画板和所有按钮以流式布局的方式加到最终的大框架(JFrame)上,画图板的大致结构就完成了,主要需要用到的就是以上括号里的三个类。

需要注意的是,关于我自己写的这个画图板类Panel到底应该继承JFrame类还是继承JPanel类有两种想法:
1、继承JFrame,作为一个新型的框架类,在此类中编写显示界面的方法时再new一个JPanel类的画板加到this上,显示this。
2、继承JPanel,作为一个新型画板类,在此类中编写显示界面的方法时再new一个JFrame类的框架,将this加到这个框架上,显示这个框架。

个人觉得应该用第二种想法,因为到后面会涉及重写用于重绘的paint()函数,既然图形是画在画板JPanel上的,那么重绘也必然要针对画板JPanel,也就是说我要重写的是JPanel中的paint()函数,而不是JFrame中的,那么就只有继承JPanel,才能重写JPanel中的paint()函数,从而达到针对性重绘的目的。

在设置画图板显示之后,再利用Graphics graphics = this.getGraphics()在自己写的这个新型画板类的对象上获得画笔,并将画笔传到监听器类Listener中,在监听器中监听鼠标的各种行为并用此画笔画出对应的图形。注意:一定要在画图板设置可见之后再获得画笔,否则得到的画笔是空指针。

  • 监听器类Listener

点击各按钮时,监听按钮点击事件需要用到ActionListener接口,画图时需要用到鼠标,因此也需要监听鼠标行为的相关监听器接口。使用时无论是定点位置监听鼠标行为的MouseListener,还是监听鼠标移动拖动的MouseMotionListener,还是两者兼而有之,都可以只用MouseAdapter来代替。MouseAdapter是一个类,其中含有MouseListener接口和MouseMotionListener接口的所有方法,所以继承了MouseAdapter类之后,不仅可以使用监听鼠标行为的所有常用方法,还不用像继承那两个接口那样必须把不用的方法也重写一遍,岂不美哉!

想清楚需要继承的接口与类之后,就要开始思考画每一个图形的每一步细节。画这个图形的每一步时,鼠标在这一刻的行为是什么?这个行为对应的是我需要重写的哪个函数?我在重写这个函数时,要在里面得到哪些坐标,还要做哪些其他的事以帮助完成整个图形的绘制?

  • 画直线:按下——拖拽——释放。

1、首先要在画板上某个点按下鼠标(对应mousePressed(MouseEvent e){}函数),点击的同时在对应的行为函数中获得该点的坐标X1,Y1。

public void mousePressed(MouseEvent e){
    if("直线".equals(shapename)){
        x1 = e.getX(); y1 = e.getY(); //得到头点坐标
    }
}

2、从这一点开始,用鼠标朝某一个方向进行拖拽,直到另一个点,然后释放鼠标(对应mouseReleased(MouseEvent e)函数),释放的同时在对应的行为函数中获取释放点的坐标X2,Y2,此时已经获得此线段的头坐标(X1,Y1)和(X2,Y2),调用画笔画直线的方法drawLine( X1, Y1, X2, Y2)即可画出一条直线。

public void mouseReleased(MouseEvent e){
    if("直线".equals(shapename)){
    		x2 = e.getX(); y2 = e.getY(); //得到尾点坐标
    		graphics.drawLine(x1, y1, x2, y2); //连接头尾两点画直线
    	}
}
  • 画三角形:画直线——点击(对应mouseClicked(MouseEvent e)函数)第三个点连接这条直线的首尾两点。
    注意:由于点击 = 按下 + 释放,当鼠标点击时,监听器监听到鼠标行为的顺序依次是:按下——释放——点击,系统会先执行按下和释放所对应的的函数,最后再执行点击函数,因此为了使得点击的操作不受按下和释放的影响,在最后点击第三点画出三角形时,必须想办法避开前面的按下和释放操作,只执行点击操作,最简单的方法就是设置一个变量,画三角形时每完成一步,就让该变量+1,每次根据这个变量的大小来判断该进行哪一步,该避开哪一步,最后一个三角形画完后,使该变量回归初始值即可。
private int triangle = 1; //设置变量

public void mousePressed(MouseEvent e){
    if("三角形".equals(shapename) && triangle==1){
    		x1 = e.getX(); y1 = e.getY();
    		triangle++; //执行完第一步,变量+1
    	}
}

public void mouseReleased(MouseEvent e){
    if"三角形".equals(shapename) && triangle==2){ //如果变量为2的话才执行此操作
    		x2 = e.getX(); y2 = e.getY(); 
    		graphics.drawLine(x1, y1, x2, y2); //连接头尾两点画直线
    		triangle++; //执行完第二步,变量+1
    	}
}

public void mouseClicked(MouseEvent e){
		if("三角形".equals(shapename) && triangle==3){ //如果变量为3的话才执行此操作
			x = e.getX(); y = e.getY(); //得到第三点的坐标
			graphics.drawLine(x, y, x1, y1);
		    graphics.drawLine(x, y, x2, y2); //连接第三点与已画直线的首尾两点形成三角形
			triangle = 1; //三角形画完,变量回归初始值
		}
	}
  • 画多边形:画直线——for(i=0;i<n;i++){点击某点,连接该点与上一直线的尾点形成新的直线}——双击某点(e.getClickCount()==2),连接上一直线的尾点以及第一条直线的首点,形成封闭多边形。
    注意:与三角形一样,在点击和双击时画多边形涉及计算机执行按下——释放——点击操作的顺序问题,解决的方法同样是设置一个对应的变量,使得该变量随着画图进程的前进而+1,根据此变量的大小在画图进程的不同时刻执行唯一的操作,最后再让其回归初始值。
private int triangle = 1 //设置变量

public void mousePressed(MouseEvent e){
    	if("多边形".equals(shapename) && polygon==1){
    		x1 = e.getX(); y1 = e.getY();
    		polygon++; 执行完第一步,变量+1
    	}
    }

public void mouseReleased(MouseEvent e){
    	if("多边形".equals(shapename) && polygon==2){ //当变量为2时才执行此操作
    		x2 = e.getX(); y2 = e.getY();
    		graphics.drawLine(x1, y1, x2, y2); //连接头尾两点画直线
    		polygon++; 执行完第二步,变量+1
		}
    }
    
public void mouseClicked(MouseEvent e){
		if("多边形".equals(shapename) && polygon>2){ //当变量大于2时才执行此操作
			x = e.getX(); y = e.getY(); 
			graphics.drawLine(x, y, x2, y2); 
			x2 = x; y2 = y; //每多画一条边,就将坐标变换,以便下一次点击时画下一条边
			polygon++; //每多画一条边,变量+1
		}
		if("多边形".equals(shapename) && e.getClickCount()==2){ //双击时
			x = e.getX(); y = e.getY();
			graphics.drawLine(x, y, x1, y1);
		    graphics.drawLine(x, y, x2, y2); //连接头尾两点,形成封闭多边形
			polygon = 1; //变量回归初始值
		}
	}

画三角形与多边形的方法都是在画直线方法的基础上演化而来的,只是多涉及了点击、双击和每一步坐标的变换,在此不做过于详细的讲解。

  • 画矩形:鼠标操作方法与画直线相同,得到X1,Y1,X2,Y2。画矩形方法为drawRect(x,y,width,height),其中x,y是最终画出的矩形左上角的顶点坐标,因此无论你是朝哪个方向画的矩形,一定要得到其左上角的顶点坐标,即((X1,X2中的最小值),(Y1,Y2中的最小值))。而宽width和高height则分别对应X1与X2之差的绝对值和Y1与Y2之差的绝对值。
public void mousePressed(MouseEvent e){
    if("直线".equals(shapename)){
        x1 = e.getX(); y1 = e.getY(); //得到头点坐标
    }
}

public void mouseReleased(MouseEvent e){
    if("直线".equals(shapename)){
    		x2 = e.getX(); y2 = e.getY(); //得到尾点坐标
    		graphics.drawRect(Math.min(x1, x2), Math.min(y1, y2),Math.abs(x1-x2), Math.abs(y1-y2)); //画矩形
    	}
}
  • 画椭圆:与画矩形类似,只是方法为drawOval(x,y,width,height)。
public void mousePressed(MouseEvent e){
    if("椭圆".equals(shapename)){
        x1 = e.getX(); y1 = e.getY(); //得到头点坐标
    }
}

public void mouseReleased(MouseEvent e){
    if("椭圆".equals(shapename)){
    		x2 = e.getX(); y2 = e.getY(); //得到尾点坐标
    		graphics.drawOval(Math.min(x1, x2), Math.min(y1, y2),Math.abs(x1-x2), Math.abs(y1-y2)); //画椭圆
    	}
}

此外还有填充矩形、填充椭圆和3D矩形等类似图形,都是与最基础的椭圆、矩形画法同一模式,只是方法参数不同而已,在此不做过于详细的解释。

  • 画曲线:点击——不断拖动。

从微元法的角度思考,曲线可以看做是由很多条小直线收尾连接而成的,因此在鼠标拖动(对应mouseDragged(MouseEvent e)函数)时,可以在对应行为函数中先画一条直线,然后立刻此直线的尾坐标(X2,Y2)转变为下一直线的头坐标(X1,Y1),这样一来,在鼠标拖动过程中,系统会不断执行mouseDragged(MouseEvent e)函数,也就会不断地画首尾相连的小直线,最终连成一条曲线。

 public void mousePressed(MouseEvent e){
    if("曲线".equals(shapename)){
        x1 = e.getX(); y1 = e.getY(); //得到头点坐标
    }
}

 public void mouseDragged(MouseEvent e){
    	if("曲线".equals(shapename)){
    		x2 = e.getX(); y2 = e.getY(); //得到尾点坐标
    		graphics.drawLine(x1, y1, x2, y2); //连接头尾两点画微元小直线
    		x1 = x2; y1 = y2; //调换坐标,准备循环画微元小直线
    	}
    }
  • 图形类Shape

1、图形的重绘:
按理说,画笔传到了监听器类中,就可以直接一步一步画出各种图形,不用再增加其它类了。但之所以还要增加一个图形类,是因为画图板的重绘函数paint()并没有重绘出你自己画出的这些图形,导致在画图的过程中,一旦界面稍微动一下,所有图形都会全部消失。paint()函数的主要作用是在界面移动或者发生某些形变(缩小、最小化)时,系统会自动调用paint()函数,以保证paint()函数中的内容不会随着界面的变化而消失。这时就需要将你已经画过的所有图形的数据、信息以某种形式保存起来,并且重写paint()函数,将所有画过的图形在重写的paint()函数中再重绘一遍,你画过的图形就不会随着界面的变化而消失了。保存的形式,就是再写一个图形类Shape,将不同图形的绘制方法写在Shape类的画图方法中,并用这个类创建一个图形数组,每画一个图形,就将它存在这个Shape数组中,最后在重绘函数paint()中调用数组中每个图形对象对应的画图方法,即可重绘出图形。

public void paint(Graphics graphics){
		super.paint(graphics); //必须先调用父类的paint()函数
		for(int i=0;i<shapeArray.length;i++){
			if(shapeArray[i]!=null){
				shapeArray[i].DrawShape(graphics); //重绘
			}
		}
	}

2、后退和清空的实现:
一旦监听器监听到点击了“后退”按钮,那么就将已经保存的图形数组中的最后一个图形对象设置为null,在paint()函数中重绘时加上一个条件语句if(a[]!=null),然后想办法调用paint()函数,使得所有图形重绘一次,那么你所画得最后一个图形就不会被重绘出,当场消失,后退功能就得以实现。然而paint()函数不可以被人为直接调用,只有在界面发生变化时才会触发它的调用,如果要人为调用paint()函数,只能通过调用repaint()函数来间接调用paint()函数。因此,将画板传到监听器类,并在“后退”的最后调用其repaint()函数,就可以人为实现重绘,最终达到后退目的。
至于清空,和后退一个道理,只需将图形数组中的所有图形设置为null,再用repaint()人为重绘即可。

public void actionPerformed(ActionEvent e){
	if("后退".equals(e.getActionCommand())){
			shapeArray[num-1] = null;
			panel.repaint();
			num--;
		}else if("清空".equals(e.getActionCommand())){
			for(int i=0;i<num;i++){
				shapeArray[i] = null;
			}
			panel.repaint();
			num = 0;
		}
	}

补充:关于设置粗线、细线与橡皮擦:

为一个画笔graphics设置线条粗细的方法:利用Graphics2D类。此类可理解为二维画笔,将Graphics类的普通画笔强制转型为Graphics2D类的二维画笔之后,就可以为此画笔设置粗细。具体代码为:

Graphics2D graphics2d = (Graphics2D)graphics; //强制转型为二维画笔
graphics2d.setStroke(new BasicStroke(1f)); //设置粗细为1f

设置完粗细后,后面用画笔时无论用graphics2d还是用graphics都可以,此时这两个画笔已经是同一个画笔了,而且都是二维画笔。

至于橡皮擦,想简单点无非就是要使鼠标划过的地方被画板的背景色覆盖,其实不就是用颜色为画板背景色的画笔来画曲线吗?所以当执行橡皮擦按钮的命令时,就将画笔颜色设置为画板背景色,并且调用画曲线的方法就可以了,同时还要注意用Graphics2D将这个“橡皮擦画笔”的粗细设置得粗一点,这样这个橡皮擦能一次性擦干净的面积就更大。

2、具体代码:

  • 画图板类Panel
public class Panel extends JPanel{
	
	public static void main(String args[]){
		Panel panel = new Panel();
		panel.showP();
	}
	
	public Shape []shapeArray = new Shape [1000000]; //设置保存图形的数组
	
	public void showP(){
		JFrame jFrame = new JFrame("画图板");
		jFrame.setSize(900, 800);
		jFrame.setResizable(false);
		jFrame.setLocationRelativeTo(null);
		jFrame.setDefaultCloseOperation(3);
		
		FlowLayout flowLayout = new FlowLayout();
		jFrame.setLayout(flowLayout);
		
		Listener listener = new Listener();
		
		//加图形选择按钮
		String name[] = {"直线","三角形","矩形","椭圆","多边形","曲线","填充矩形","3D矩形","填充椭圆"};
		for(int i=0;i<9;i++){
			JButton jButton = new JButton(name[i]);
			jButton.setPreferredSize(new Dimension(90, 30));
			jFrame.add(jButton);
			jButton.addActionListener(listener);
		}
		
		//加线与橡皮擦选择按钮
		String line[] = {"细线","粗线","橡皮擦"};
		for(int i=0;i<3;i++){
			JButton jButton = new JButton(line[i]);
			jButton.setPreferredSize(new Dimension(90, 30));
			jFrame.add(jButton);
			jButton.addActionListener(listener);
		}
		
		//加颜色选择按钮
		Color color[] = {Color.BLACK,Color.RED,Color.PINK,Color.ORANGE,Color.YELLOW,Color.GREEN,Color.CYAN,Color.BLUE,Color.GRAY};
		for(int i=0;i<9;i++){
			JButton jButton = new JButton();
			jButton.setPreferredSize(new Dimension(40, 40));
			jButton.setBackground(color[i]);
			jFrame.add(jButton);
			jButton.addActionListener(listener);
		}
		
		//加后退清空按钮
		JButton jButton001 = new JButton("后退");
		jButton001.setPreferredSize(new Dimension(60, 30));
		JButton jButton002 = new JButton("清空");
		jButton002.setPreferredSize(new Dimension(60, 30));
		jFrame.add(jButton001);
		jFrame.add(jButton002);
		jButton001.addActionListener(listener);
		jButton002.addActionListener(listener);
		
		//将画板加到界面上
		this.setPreferredSize(new Dimension(888, 700));
		this.setBackground(Color.WHITE);
		jFrame.add(this);
		this.addMouseListener(listener);
		this.addMouseMotionListener(listener);
		
	    jFrame.setVisible(true);
		
	    //生成画笔并进行相关初始设置
		Graphics graphics = this.getGraphics();
		Graphics2D graphics2d = (Graphics2D)graphics;
		graphics2d.setStroke(new BasicStroke(1f));
		graphics.setColor(Color.BLACK);
		
		//传相关参数到监听器类
		listener.Pass(graphics, shapeArray, this);
	}
	
	//重写重绘函数
	public void paint(Graphics graphics){
		super.paint(graphics); 
		for(int i=0;i<shapeArray.length;i++){
			if(shapeArray[i]!=null){
				shapeArray[i].DrawShape(graphics);
			}
		}
	}
}
  • 监听器类Listener
public class Listener extends MouseAdapter implements ActionListener{
	private Graphics graphics;
	private Color color ;
	private String shapename;
	private int x1,y1,x2,y2,x,y;
	private Shape shapeArray [];
	private int num = 0;
	private int triangle = 1;
	private int polygon = 1;
	String line = "细线";
	Panel panel = new Panel();
	
	//传递参数函数
	public void Pass(Graphics graphics, Shape shapeArray[], Panel panel){
		this.graphics = graphics ;
		this.shapeArray = shapeArray;
		this.panel = panel;
	}
	
	//按钮被点击
	public void actionPerformed(ActionEvent e){
		if("".equals(e.getActionCommand())){
			JButton jButton = new JButton();
			jButton = (JButton)e.getSource();
			color = jButton.getBackground();
			graphics.setColor(color);
		}else if("橡皮擦".equals(e.getActionCommand())){
			line = "橡皮擦";
		}else if("粗线".equals(e.getActionCommand())){
			line = "粗线";
		}else if("细线".equals(e.getActionCommand())){
			line = "细线";
		}else if("后退".equals(e.getActionCommand())){
			shapeArray[num-1] = null;
			panel.repaint();
			num--;
		}else if("清空".equals(e.getActionCommand())){
			for(int i=0;i<num;i++){
				shapeArray[i] = null;
			}
			panel.repaint();
			num = 0;
		}else{
			shapename = e.getActionCommand();
		}
	}
	
	//鼠标点击
	public void mouseClicked(MouseEvent e){
		if("三角形".equals(shapename) && triangle==3){
			x = e.getX(); y = e.getY();
			Shape shape = new Shape(shapename, line, color, x1, y1, x2, y2, x, y);
			shape.DrawShape(graphics);
			shapeArray[num++] = shape;
			triangle = 1;
		}
		if("多边形".equals(shapename) && polygon>2){
			x = e.getX(); y = e.getY();
			Shape shape = new Shape(shapename, line, color, x1, y1, x2, y2, x, y);
			shape.DrawShape(graphics);
			shapeArray[num++] = shape;
			x2 = x; y2 = y;
			polygon++;
		}
		if("多边形".equals(shapename) && e.getClickCount()==2){
			x = e.getX(); y = e.getY();
			Shape shape = new Shape("三角形", line, color, x1, y1, x2, y2, x, y);
			shape.DrawShape(graphics);
			shapeArray[num++] = shape;
			polygon = 1;
		}
	}
	
    //鼠标按下
    public void mousePressed(MouseEvent e){
    	if("直线".equals(shapename)||"矩形".equals(shapename)||"填充矩形".equals(shapename)||"3D矩形".equals(shapename)||
    			"椭圆".equals(shapename)||"填充椭圆".equals(shapename)||"曲线".equals(shapename)||"橡皮擦".equals(line)){
    		x1 = e.getX(); y1 = e.getY();
    	}
    	if("三角形".equals(shapename) && triangle==1){
    		x1 = e.getX(); y1 = e.getY();
    		triangle++;
    	}
    	if("多边形".equals(shapename) && polygon==1){
    		x1 = e.getX(); y1 = e.getY();
    		polygon++;
    	}
    }
 
    //鼠标释放
    public void mouseReleased(MouseEvent e){
    	if("直线".equals(shapename)){
    		x2 = e.getX(); y2 = e.getY();
    		Shape shape = new Shape(shapename,line,color,x1,y1,x2,y2);
    		shape.DrawShape(graphics); 
    		shapeArray[num++] = shape; 
    	}
    	if("三角形".equals(shapename) && triangle==2){
    		x2 = e.getX(); y2 = e.getY();
    		Shape shape = new Shape("直线",line,color,x1,y1,x2,y2);
    		shape.DrawShape(graphics);
    		shapeArray[num++] = shape;
    		triangle++;
    	}
    	if("矩形".equals(shapename)||"填充矩形".equals(shapename)||"3D矩形".equals(shapename)){
    		x2 = e.getX(); y2 = e.getY();
    		Shape shape = new Shape(shapename,line,color,x1,y1,x2,y2);
    		shape.DrawShape(graphics);
    		shapeArray[num++] = shape;
    	}
    	if("椭圆".equals(shapename)||"填充椭圆".equals(shapename)){
    		x2 = e.getX(); y2 = e.getY();
    		Shape shape = new Shape(shapename,line,color,x1,y1,x2,y2);
    		shape.DrawShape(graphics);
    		shapeArray[num++] = shape;
    	}
    	if("多边形".equals(shapename) && polygon==2){
    		x2 = e.getX(); y2 = e.getY();
    		Shape shape = new Shape("直线",line,color,x1,y1,x2,y2);
    		shape.DrawShape(graphics);
    		shapeArray[num++] = shape;
    		polygon++;
		}
    }
    
    //鼠标拖拽
    public void mouseDragged(MouseEvent e){
    	if("曲线".equals(shapename)){
    		x2 = e.getX(); y2 = e.getY();
    		Shape shape = new Shape("直线",line,color,x1,y1,x2,y2);
    		shape.DrawShape(graphics);
    		shapeArray[num++] = shape;
    		x1 = x2; y1 = y2;
    	}else if("橡皮擦".equals(line)){
    		x2 = e.getX(); y2 = e.getY();
    		Shape shape = new Shape("直线",line,color,x1,y1,x2,y2);
    		shape.DrawShape(graphics);
    		x1 = x2; y1 = y2;
    	}
    }
}
  • 图形类Shape
public class Shape {
	private int x1,y1,x2,y2,x,y;
	public String name,line;
	Color color;
	
	//构造函数一
	public Shape(String name,String line,Color color,int x1,int y1,int x2,int y2){
		this.name = name;
		this.line = line;
		this.color = color;
		this.x1 = x1; this.y1 = y1;
		this.x2 = x2; this.y2 = y2;
	}
	
	//构造函数二
	public Shape(String name,String line,Color color,int x1,int y1,int x2,int y2,int x,int y){
		this.name = name;
		this.line = line;
		this.color = color;
		this.x1 = x1; this.y1 = y1;
		this.x2 = x2; this.y2 = y2;
		this.x = x; this.y = y;
	}
	
	//画图方法
	public void DrawShape(Graphics graphics){
		graphics.setColor(color);
		Graphics2D graphics2d = (Graphics2D)graphics;
		switch (line) {
		case "橡皮擦": graphics2d.setStroke(new BasicStroke(30f)); 
		               graphics2d.setColor(Color.WHITE); break;
		case "粗线": graphics2d.setStroke(new BasicStroke(3f)); break;
		case "细线": graphics2d.setStroke(new BasicStroke(1f)); break;
		}
		switch (name){
		case "直线": graphics.drawLine( x1, y1, x2, y2); break;
		case "三角形": graphics.drawLine(x, y, x1, y1);
		               graphics.drawLine(x, y, x2, y2); break;
		case "矩形": graphics.drawRect(Math.min(x1, x2), Math.min(y1, y2),Math.abs(x1-x2), Math.abs(y1-y2)); break;
		case "填充矩形": graphics.fillRect(Math.min(x1, x2), Math.min(y1, y2),Math.abs(x1-x2), Math.abs(y1-y2)); break;
		case "3D矩形": graphics.fill3DRect(Math.min(x1, x2), Math.min(y1, y2),Math.abs(x1-x2), Math.abs(y1-y2),true); break;
		case "椭圆": graphics.drawOval(Math.min(x1, x2), Math.min(y1, y2),Math.abs(x1-x2), Math.abs(y1-y2)); break;
		case "填充椭圆": graphics.fillOval(Math.min(x1, x2), Math.min(y1, y2),Math.abs(x1-x2), Math.abs(y1-y2)); break;
		case "多边形": graphics.drawLine(x, y, x2, y2); break;
		}
	}
}

3、最终效果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值