Java第n次入门之画板的实现

本次我们主要实现了一个画板的工具,具体功能有画固定图形(直线、矩形等java自带画图方法)、曲线、立体图、分形图、概率画图、递归画图以及画笔颜色切换。

下面我们逐步介绍。

一,窗体及按钮的可视化实现

java提供了一个窗体类JFrame,按钮类JButton,使用这些类创建对象可以快速地实现窗体和按钮的可视化

	JFrame jf = new JFrame("画板");//创建窗体对象jf  设置标题
	//窗体初始化属性
	jf.setSize(800,600);//窗体大小,横x纵y
	jf.setLocationRelativeTo(null);//使窗体显示在屏幕中央
	jf.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//窗体关闭时即程序退出
	
	//使用流式布局
	FlowLayout flayout = new FlowLayout();
	jf.setLayout(flayout);	
	//增加画图选项按钮 增加到界面上
	String[] jname = {
    
    "直线","矩形","曲线","分形图","立体图","三点分形","概率画图","递归直线"};
	for(int i=0;i<jname.length;i++) {
    
    
		JButton jline = new JButton(jname[i]);
		jf.add(jline);
	}

在设计布局的时候我们采用了流式布局,流式布局中页面中的元素的宽度按照屏幕分辨率自动进行适配调整,也就是我们常说的适配,它可以保证当前屏幕分辨率发生改变的时候,页面中的元素大小也可以跟着改变,所以流式布局是设计页面时常用的一种布局。这样就能产生一个可视化的窗体:


在创建窗体对象和按钮对象中,我们要进行导包操作。java中有两个包:
1,javax.swing (可视化组件类)
2,java.awt (元素组件类)

import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;

import javax.swing.JButton;
import javax.swing.JFrame;

二,增加监听,实现画直线、矩形、曲线、立体图功能

在这里我们继承ActionListener接口,重写actionPerformed()方法,获取按钮名称;继承MouseListenerMouseMotionListener接口,重写其中的方法,当鼠标释放时根据不同条件实现画直线、矩形、立体图的功能,曲线根据鼠标拖拽时实现。

	//创建监听事件处理类    监听到动作之后如何处理由dlistener对象处理    具体功能在DrawListener类中
	DrawListener dlistener = new DrawListener();
	//给界面加鼠标监听 画图功能是在鼠标不同状态时实现的  画曲线时既要监听动作即按钮是否被按下(获取按下时坐标)  还要监听鼠标移动(获取实时坐标)
	jf.addMouseListener(dlistener);
	jf.addMouseMotionListener(dlistener);
	
	//增加画图选项 增加到界面上 增加监听
	String[] jname = {
    
    "直线","矩形","曲线","分形图","立体图","三点分形","概率画图","递归直线"};
	for(int i=0;i<jname.length;i++) {
    
    
		JButton jline = new JButton(jname[i]);
		jf.add(jline);
		jline.addActionListener(dlistener);//按钮增加动作监听,判断按钮是否被点击
	}

重写ActionListener、MouseListener和MouseMotionListener的方法

public class DrawListener implements MouseListener,ActionListener,MouseMotionListener{
    
    
	Graphics g = null;//创建Graphics类属性,Grahpics不能创建对象,Grahpics是个抽象类
	int xDown,yDown,xUp,yUp,x,y ;//鼠标按下坐标、释放坐标、实时坐标
	String Bname;//按钮名称
	@Override
	//重写鼠标监听器的方法
    public void mouseClicked(MouseEvent e) {
    
    
    	System.out.println("鼠标点击");
    }

    public void mousePressed(MouseEvent e) {
    
    
    	//获取按下时的坐标
    	 xDown=e.getX();
    	 yDown=e.getY();
    	 
    	System.out.println("鼠标按下");
    }

    public void mouseReleased(MouseEvent e) {
    
    
    	//获取释放时坐标
    	 xUp=e.getX();
    	 yUp=e.getY();
    	 //根据按钮画指定图形
       	 if(Bname.equals("直线")) {
    
    
       		System.out.println(Bname);
       		g.drawLine(xDown, yDown, xUp, yUp);
       	} 
       	else if(Bname.equals("矩形")) {
    
    
       		System.out.println(Bname);
       		if(xDown<xUp) {
    
    
       		g.drawRect(xDown,yDown, xUp-xDown,yUp-yDown);
       		}else {
    
    
       			g.drawRect(xDown-(xDown-xUp),yDown-(yDown-yUp), Math.abs(xUp-xDown), Math.abs(yUp-yDown));
       		}
       	}else if(Bname.equals("立体图")) {
    
    
       		for(int i=0;i<100;i++){
    
    
    			g.setColor(new Color(i*2, 100+i, i*2+50));
    			g.fillOval(xUp+i/3, yUp+i/3, 100-i, 100-i);//通过不断画填充圆以及改变颜色的方法达到立体的效果
    		}
       	}
       	 
    	System.out.println("鼠标释放");
    }

    public void mouseEntered(MouseEvent e) {
    
    
    	System.out.println("鼠标进入");
    }

    public void mouseExited(MouseEvent e) {
    
    
    	System.out.println("鼠标退出");
    }
    
    @Override
    //鼠标移动监听器的方法
    //鼠标拖拽
    public void mouseDragged(MouseEvent e) {
    
    
    	if(Bname.equals("曲线")) {
    
    
        	x=e.getX();//获取鼠标实时坐标
        	y=e.getY();
            g.drawLine(xDown,yDown,x,y);
            xDown=x;//曲线是由一个个小线段构成,上一条线段的终止坐标作为下一条线段的起始坐标
            yDown=y;
    	}

    }
    //鼠标移动
    public void mouseMoved(MouseEvent e) {
    
    

    }
    
    @Override
    //动作监听器的方法  按下之后要执行的动作
    public void actionPerformed(ActionEvent e) {
    
    
    	 Bname = e.getActionCommand();//获取按钮名字,鼠标释放时根据不同名字实现具体功能
    }
     
} 

三,颜色切换的功能

1,创建颜色数组 添加到界面上同时增加监听。

Color[] color = {
    
    Color.RED,Color.GREEN,Color.BLUE};
	for(int i=0;i<color.length;i++) {
    
    
		JButton jcolor = new JButton();
		jcolor.setBackground(color[i]);
		jcolor.setPreferredSize(new Dimension(30,30));
		jf.add(jcolor);
		jcolor.addActionListener(dlistener);
	}

2,得到按钮的背景颜色,将画笔的颜色设置为相应的背景颜色。

 public void actionPerformed(ActionEvent e) {
    
    
    	 //获取当前事件源内容
    	JButton jbu = (JButton)e.getSource();//获取当前事件源对象,是个按钮类型的对象jbu
    	 Color color = jbu.getBackground();//得到按钮背景颜色
    	 if(e.getActionCommand().equals("")) {
    
    //得到按钮上的字符串,根据有无字符串判断改变颜色或者获得获得字符串名字
    		g.setColor(color);//改变画笔颜色,将按钮背景颜色设置为画笔颜色
    	 }else {
    
    
    		 Bname = e.getActionCommand();//不是颜色按钮则获得按钮上的字符串
    	 }
    }

四,分形图的绘制

分形图的公式:

分形图的参数:

代码如下:

 if(Bname.equals("分形图")) {
    
    
       		//System.out.println(Bname);
       		double a=-1.4,b=1.6,c=1.0,d=0.7;
       		double x=0.0,y=0.0;//初始值
       		for(int i=0;i<25500;i++) {
    
    
       			//公式
       			double temx = Math.sin(a*y)+c*Math.cos(a*x);
       			double temy = Math.sin(b*x)+d*Math.cos(b*y);
       			//将所得的点作为下一次迭代点的值
       			x=temx;
       			y=temy;
       			//将所得的数扩大并移到我们鼠标点击处。强制转型,drawline()方法中参数只能是整型。注意将参数用括号括起
       			int drawx = (int)(temx*50+xDown);
       			int drawy = (int)(temy*50+yDown);
       			//g.setColor(new Color(0,0,i/100));
       			g.drawLine(drawx, drawy, drawx, drawy);//该方法的参数只能是整型   由点连成线
       			//打印坐标
       			System.out.println(drawx+"    "+drawy);
       		}
       	}

我们利用循环多次画图,并将计算得到的点的值作为下一次计算的值带入公式中,得到我们想要的图像:

五,三点分形图

三点分形图的要求如下:

我们先采用math.random()方法生成4个随机坐标A(x1,y1),B(x2,y2),C(x3,y3),P(xp,yp),然后根据题目要求得知,每次画图时要随机从A、B、C三个点中选出一个点与P点进行计算画图,于是我们使用Random类中nextInt(3)方法,该方法可以每次随机生成数字0或者1或者2,正好三种情况,每种情况对应选中A或者B或者C中的一点,然后再将计算得到的点的坐标重写赋给P点,利用循环多次计算,这样就能画出我们想要的图形了。
代码如下:

if(Bname.equals("三点分形")) {
    
       //平面随机生成三个点  再随机生成点xp  掷色子选中三点之一
       		Random random = new Random();
       		int x1 = (int)(Math.random()*500);//生成随机坐标x1
       		int y1 = (int)(Math.random()*500);//生成随机坐标y1
       		int x2 = (int)(Math.random()*500);//生成随机坐标x2
       		int y2 = (int)(Math.random()*500);//生成随机坐标y2
       		int x3 = (int)(Math.random()*500);//生成随机坐标x3
       		int y3 = (int)(Math.random()*500);//生成随机坐标y3
       		int xp = (int)(Math.random()*500);//生成随机点xp
       		int yp = (int)(Math.random()*500);//生成随机点yp
       		for(int i=0;i<10000;i++) {
    
    
       			int k=random.nextInt(3);
       			if(k==0) {
    
    
       				g.drawLine((x1+xp)/2,(y1+yp)/2,(x1+xp)/2,(y1+yp)/2);//画点
       				System.out.println((x1+xp)/2+"   "+(y1+yp)/2);
       				xp=(x1+xp)/2;//重新给定P点
       				yp=(y1+yp)/2;
       			}else if(k==1) {
    
    
       				g.drawLine((x2+xp)/2,(y2+yp)/2,(x2+xp)/2,(y2+yp)/2);//画点
       				System.out.println((x2+xp)/2+"   "+(y2+yp)/2);
       				xp=(x2+xp)/2;//重写给定P点
       				yp=(y2+yp)/2;
       			}else if(k==2) {
    
    
       				g.drawLine((x3+xp)/2,(y3+yp)/2,(x3+xp)/2,(y3+yp)/2);//画点
       				System.out.println((x3+xp)/2+"   "+(y3+yp)/2);
       				xp=(x3+xp)/2;//重写给定P点
       				yp=(y3+yp)/2;
       			}
       		}
       	}

得到的图形如下:

六,概率画图

概率画图的要求如下:

题目中要求每次画图在5种情况中选取一种,且5种情况出现的概率相同。我们联想到掷硬币实验,只要掷的次数多,正反面出现的概率就几乎相同。于是采用和三点分形产生随机数一样的思想,利用多次循环,每次产生0~4这五个数中的其中一个,只要循环次数足够多,那这5个数产生的概率就相同,符合题目要求,将每一个数对应于题目中的一组参数进行画图。
代码如下:

if (Bname.equals("概率画图")) {
    
    //画图分为5种情况   每种情况概率相等   每次仅画其中的一种情况
       		double a,b,c,d,e1,f;//参数   不同情况参数不一样
       		double x=0.0,y=0.0;//初始值
       		Random random = new Random();
       		for(int i=0;i<10000;i++) {
    
    
       		int k=random.nextInt(5);//随机产生0~5中的一个数
       		if(k==0) {
    
    
   				a=0.1950;b=-0.4880;c=0.3440;d=0.4430;e1=0.4431;f=0.2452;//不同情况参数不一样
   				//公式
   				double temx=a*x+b*y+e1;
   				double temy=c*x+d*y+f;
   				//扩大 移位 强制转型
   				int drawx = (int)(temx*500+xDown);
   				int drawy = (int)(temy*500+yDown);
   				g.drawLine(drawx, drawy, drawx, drawy);//倒图
   				//g.drawLine(drawx, -drawy+800, drawx, -drawy+800)//正确画法  将y坐标取反再加上画布高  将图形拉入画布中
   				//重新赋值为下一次迭代计算做准备
   				x=temx;
   				y=temy;
   				System.out.println(drawx+"  "+drawy);
   				
       		}else if(k==1) {
    
    
   				a=0.4620;b=0.4140;c=-0.2520;d=0.3610;e1=0.2511;f=0.5692;
   				double temx=a*x+b*y+e1;
   				double temy=c*x+d*y+f;
   				int drawx = (int)(temx*500+xDown);
   				int drawy = (int)(temy*500+yDown);
   				g.drawLine(drawx, drawy, drawx, drawy);//倒图
   				//g.drawLine(drawx, -drawy+800, drawx, -drawy+800)//正确画法  将y坐标取反再加上画布高  将图形拉入画布中
   				System.out.println(drawx+"  "+drawy);
   				x=temx;
   				y=temy;
       		}else if(k==2) {
    
    
   				a=-0.6370;b=0.0000;c=0.0000;d=0.5010;e1=0.8562;f=0.2512;
   				double temx=a*x+b*y+e1;
   				double temy=c*x+d*y+f;
   				int drawx = (int)(temx*500+xDown);
   				int drawy = (int)(temy*500+yDown);
   				g.drawLine(drawx, drawy, drawx, drawy);//倒图
   				//g.drawLine(drawx, -drawy+800, drawx, -drawy+800)//正确画法  将y坐标取反再加上画布高  将图形拉入画布中
   				System.out.println(drawx+"  "+drawy);
   				x=temx;
   				y=temy;
       		}else if(k==3) {
    
    
   				a=-0.0350;b=0.0700;c=-0.4690;d=0.0220;e1=0.4884;f=0.5069;
   				double temx=a*x+b*y+e1;
   				double temy=c*x+d*y+f;
   				int drawx = (int)(temx*500+xDown);
   				int drawy = (int)(temy*500+yDown);
   				g.drawLine(drawx, drawy, drawx, drawy);//倒图
   				//g.drawLine(drawx, -drawy+800, drawx, -drawy+800)//正确画法  将y坐标取反再加上画布高  将图形拉入画布中
   				System.out.println(drawx+"  "+drawy);
   				x=temx;
   				y=temy;
       		}else if(k==4) {
    
    
   				a=-0.0580;b=-0.0700;c=0.4530;d=-0.1110;e1=0.5976;f=0.0969;
   				double temx=a*x+b*y+e1;
   				double temy=c*x+d*y+f;
   				int drawx = (int)(temx*500+xDown);
   				int drawy = (int)(temy*500+yDown);
   				g.drawLine(drawx, drawy, drawx, drawy);//倒图
   				//g.drawLine(drawx, -drawy+800, drawx, -drawy+800)//正确画法  将y坐标取反再加上画布高  将图形拉入画布中
   				System.out.println(drawx+"  "+drawy);
   				x=temx;
   				y=temy;
   			}
       		}

       		}

将上段代码运行后我们发现得到的图与我们想要的效果是倒的

这是因为java中画板的坐标以左上角为顶点,y轴坐标向下为正,于是我们在代码中将画图点的y坐标取反,再加上画布高,使图像显现在画布中。

g.drawLine(drawx, -drawy+800, drawx, -drawy+800);

七,递归画图

在之前的画图过程中我们都是采用for循环的语句进行画图,这次我们采用一种新的方式画图——递归
递归,简而言之就是自己调用自己,递归有三要素
1,递归终止条件。如果递归没有终止条件,那么程序将陷入死循环中。
2,递归终止时的处理办法。
3,提取重复的逻辑,减小问题规模。这是递归问题的核心。
这里我们将要解决的问题是依次画一条直线的左1/3和右1/3,达到如下图的效果:

实现步骤具体如下:
1,我们自己定义了一个方法用来画递归直线,有5个参数,分别是向递归结束靠近的参数、线段起点x坐标、线段起点y坐标、线段终点x坐标、线段终点y坐标。

public void MyDrawLine(int n,int x1,int y1,int x2,int y2) 

2,重复逻辑。

        g.drawLine(x1, y1, x2, y2);//初始线
    	int xL1=x1;int yL1=y1+40;int xL2=x1+(x2-x1)/3;int yL2=y1+40;//计算左三等分线坐标
    	MyDrawLine(n-1,xL1,yL1,xL2,yL2);//递归调用,每调用一次n-1
    	//System.out.println(n);
    	int xR1=x2-(x2-x1)/3;int yR1=y2+40;int xR2=x2;int yR2=y2+40;//计算右三等分线坐标
    	MyDrawLine(n-1,xR1,yR1,xR2,yR2);//递归调用,每调用一次n-1
    	//System.out.println(n);

每画完一条线之后,重新计算点的坐标,将新的坐标带入调用自己,同时递归参数减1,实现递归的功能。
3,递归结束条件及处理方法。

    	if(n==0) {
    
    //递归终止条件
    		return;//终止处理
    	}

当参数n不断减小到0的时候,退出程序,递归结束。
4,在鼠标释放的函数中调用该方法,输入初始值,实现功能。

if(Bname.equals("递归直线")) {
    
    
        MyDrawLine(6,xDown,yDown,xDown+600,yDown);//三等分画线,6为递归结束条件
       	    
       	}

得到的效果图如下:

基于以上内容,我们的画板功能就全部实现了,完整代码如下:
(https://blog.csdn.net/weixin_43722843/article/details/110382336)

猜你喜欢

转载自blog.csdn.net/weixin_43722843/article/details/110317582