本次我们主要实现了一个画板的工具,具体功能有画固定图形(直线、矩形等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()方法,获取按钮名称;继承MouseListener、MouseMotionListener接口,重写其中的方法,当鼠标释放时根据不同条件实现画直线、矩形、立体图的功能,曲线根据鼠标拖拽时实现。
//创建监听事件处理类 监听到动作之后如何处理由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)