从零开始制作Java版高仿QQ

从零开始制作高仿QQ(一)——为窗口添加背景

2018-12-26  本文已影响0人  一个帅气的小哥哥

目录

背景
开发环境
开始之前
真正的正文
  1. 制作无标题栏的窗口
  2. 给窗口添加关闭按钮
  3. 添加鼠标拖动事件
  4. 添加背景图片
本节代码
附录
  1. 相关链接
  2. 资源图片

背景

  前些日子Java课的老师给我们留了个作业,要求写一个程序来实现点对点聊天的功能,给了我们一个半月的时间,给的示例程序是一个控制台程序。这我就很不满意了,像我这样优秀的人怎么可能花一个半月只写控制台程序(咳咳,吹大了……)于是我思来想去,决定把程序写成模仿QQ聊天界面的 GUI程序。

  为了写这个程序我确实花了不少心思,各种百度找各种问题的解决方法,终于在交作业截止前三天写完了 一半的 程序。代码前后大概有两千多行吧,只模仿完了聊天界面和系统托盘图标。但是既然已经模仿了这么多,那我不妨就索性把它写成一个真正的Java版本的QQ吧,顺便在此记录下我的模仿历程,也希望我的经历能够帮助到更多的初学者少踩一些坑。

  我的Java作业源程序:Java局域网聊天儿小软件儿(本文的程序和我的作业还是有一丢丢的区别的,作业毕竟是作业,总要给老师留点面子不是嘛

  在程序的开发过程中,我还借鉴了许多前辈们的代码 但忘了保存链接 ,我会 尽可能地找到这些链接,并 在相应的章节把这些链接 中能找到的部分 贴在附录中。好了,废话不多说,下面开始正文。

开发环境

图1.2-1 Eclipse工程设置

开始之前

  怎么说我们也是要给窗口添加背景,所以我们首先要做一张窗口背景图出来。我呢,就直接用截图工具截了一个聊天窗口,然后用PS抹了抹,就成了一张“固定大小”的背景图(别着急,后面我们还会再改,这里我们只介绍怎么添加背景图)。后来嫌QQ的自带皮肤有点……于是就在手机里找了一张照片放了进去。图我贴在文末的附录里了,不许吐槽我的审美,单身人士慎入!(求小仙女保佑我Java作业满分满分,考试高分过)

真正的正文

1. 制作无标题栏的窗口

  标题栏嘛,就是放标题的栏。我们打开QQ的聊天界面,就可以发现,这个界面的“标题栏”好像和通常的标题栏长得不太一样。所以我们要先去掉窗口的标题栏和边框,然后把自定义的标题栏加进去。

  由于我们现在做的是群聊的界面,我测量了一下,得到了窗口的尺寸为891\times683px^2。因此可以用如下的代码创建我们的窗口:

package com.jianshu.main;
 
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
 
import javax.swing.JDialog;
import javax.swing.JFrame;
 
public class JavaMyOICQ extends JFrame {
    public static void main(String[] args) {
        JavaMyOICQ dlg = new JavaMyOICQ();
        dlg.setVisible(true);
    }
    
    public JavaMyOICQ() {
        super();
        this.setSize(891, 683); 
        this.setLocationRelativeTo(null);// 设置窗口在屏幕正中间显示
        this.setResizable(false);
        this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        this.setUndecorated(true);// 这句话用来阻止窗体采用本机系统修饰,这样窗体就没有标题栏和边框了
    }
}

  但是这个时候点击运行,会发现弹出了一个灰色的长方形,怎么关都关不掉,只能用任务管理器结束进程。仔细想想也对,Java窗体的关闭按钮是在标题栏上的,关了标题栏,关闭按钮可不就没了么!所以就有了下面的步骤。

2. 给窗口添加关闭按钮

  —— 什么?!不就是加一个按钮么,至于搞一个小节出来?

  话是这么说没错,但是这里有一个要注意的地方。有的同学喜欢用System.exit(0);来关闭窗口。但是这会出现一个问题,就是点了关闭按钮,窗口确实关了,但是程序也退出了。这就有点不好了吧,总不能每次想要和别人聊天都要重新登录一遍呀?所以,我们只需要把System.exit(0);改为frame.dispose();就好了。

  接下来,我们可以再添加“最大化”和“最小化”按钮,它们的代码分别如下:

frame.setExtendedState(JFrame.ICONIFIED);// 使窗口最小化到任务栏图标
frame.setExtendedState(JFrame.MAXIMIZED_BOTH);// 使窗口最大化到全屏
frame.setExtendedState(JFrame.NORMAL);// 使窗口恢复正常(设置的)大小

  还需要注意的是,在向窗口中添加控件时,要尽量先将控件添加到一个JPanel容器中,再一并添加到窗口中,方便控件的管理。

3. 添加鼠标拖动事件

  标题栏也去掉了,关闭按钮也加了,但是现在还是有一个美中不足的地方,就是程序的窗口总是在屏幕的正中央,没办法拖动了。所以接下来,我们要给我们的窗口添加鼠标拖动事件。

  “鼠标拖动事件”听起来像是一个事件,但其实它由两部分组成:一个是当鼠标按下时,要记录开始拖动时鼠标的坐标,另一个就是在鼠标拖动时记录鼠标的水平和垂直位移。这样就可以实时地计算出窗口应处的位置,并移动它。

  经过测量,可以得出QQ聊天界面的“标题栏”高度为38px,因此我们要设定只有当鼠标位于窗口的标题栏范围内时,拖动事件才有效。给窗口添加鼠标拖动事件的代码如下:

// 需要额外添加的属性
private int xOld = 0;
private int yOld = 0;
 
 
// ......
 
 
// 给窗口添加鼠标拖动事件
frame.addMouseListener(new MouseAdapter() {
        public void mousePressed(MouseEvent e) {
        xOld = e.getX();//记录鼠标按下时的坐标
        yOld = e.getY();
    }
});
 
frame.addMouseMotionListener(new MouseMotionAdapter() {
    public void mouseDragged(MouseEvent e) {
        if (yOld > 37) return;// 限定鼠标拖动事件的有效坐标范围,至于为什么不是38,请大家自行测试
        int xOnScreen = e.getXOnScreen();
        int yOnScreen = e.getYOnScreen();
        int xx = xOnScreen - xOld;
        int yy = yOnScreen - yOld;
        setLocation(xx, yy);//设置拖拽后,窗口的位置
    }
});

4. 添加背景图片

  说了这么多,总算到这节的重点了。说到添加背景图片,我可是在网上找了种方法。这些方法不是图片添加不上,就是会覆盖控件,就算没有覆盖控件的,把窗口拖到屏幕外面(一部分)再拖回来,刚才到屏幕外面的那一部分控件甚至都不会重绘(真是个不错的方法)。

  但是下面这段代码就不同了。它的原理也很简单,就是在窗口中添加一个覆盖整个窗口的JPanel控件,并重写它的paintComponent()方法,使它在绘制时将背景图片一并绘制出来。paintComponent()是Java虚拟机在触发窗口绘制事件时自动调用的方法,不需要我们手动去调用。这个方法只一个Graphics型变量作为形参。这个参数可以理解为一个画笔,我们可以用它画出任意的自己想要的东西(有关paintComponent()方法及Graphics型变量更详细的用法我们会在之后的教程中讲到,在这里就只简单的提一下)。

  还有一个问题,就是Java如何读取图片呢?其实,Java读取图片可以有两种方法:一是直接根据路径读取图片文件,二是读取资源文件中的图片文件。在这里我们选择第二种方法。

  将图片添加到Java工程的资源文件中的方法其实很简单。只需要:新建包->打开工程文件夹下的src文件夹->进入新建的包->将图片复制进文件夹->回到Eclipse->右键单击工程名称->选择Refresh 即可。

  若要读取资源文件中的图片,只需要如下代码:

import java.awt.Image;
import javax.swing.ImageIcon;
 
String FILE_PATH = "";
 
// FILE_PATH是图片的路径。
// 如:图片存放在com.jianshu.images.frameui包中,则图片路径为:
// /com/jianshu/image/frameui/main_frame_background_default.png
// 注意不要落下第一个斜杠
 
ImageIcon imageIcon = new ImageIcon(getClass().getResource(FILE_PATH));
Image image = imageIcon.getImage();

  so,给窗口添加背景图的代码如下:

// 给窗口设置背景图片
JPanel imagePanel = new JPanel() {
    public void paintComponent(Graphics g) {
        super.paintComponent(g);
        Graphics2D g2d = (Graphics2D) g;// Graphics2D比Graphics具有更强大的功能,所以在这里我们做一下类型转换
 
        // 设置画笔的抗锯齿属性
        g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
 
        g2d.drawImage(background, 0, 0, 891, 683, this);
        g2d.drawImage(functionList, 0, 40, 364, 49, this);
    }
};
frame.add(imagePanel);

  但需要注意的是,现在我们窗口上的所有控件都要先添加到imagePanel控件中,而不是直接添加到窗口里了。

本节代码

  总结一下本节完成后发生更改的所有代码。同时,我将主方法单独拿出来放到一个专门的主类中,作为程序的入口,方便之后的修改和做其它的设置。

  JavaMyOICQ.java

package com.jianshu.main;
 
import com.jianshu.frames.JGroupChatDlg;
 
public class JavaMyOICQ {
    public static void main(String[] args) {
        JGroupChatDlg dlg = new JGroupChatDlg();
        dlg.setVisible(true);
    }
}

  JGroupChatDlg.java

package com.jianshu.frames;
 
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.GridLayout;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
 
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JPanel;
 
public class JGroupChatDlg extends JFrame implements ActionListener {
    private static final long serialVersionUID = 2973650460422728478L;
    
    // 由于这里我们用了静态变量存储图片,所以就不能用getClass()方法了
    private static final Image background = new ImageIcon(JGroupChatDlg.class.getResource("/com/jianshu/images/frameui/main_frame_background_default.png")).getImage();
    private static final Image functionList = new ImageIcon(JGroupChatDlg.class.getResource("/com/jianshu/images/frameui/main_frame_function_list.png")).getImage();
    
    private boolean state = false;
    private int xOld = 0;// 辅助窗口拖动事件
    private int yOld = 0;
    
    private JPanel titleBar = new JPanel();
    
    private JPanel upButtonsPanel = new JPanel();
    private JButton minUp = new JButton();
    private JButton maxUp = new JButton();
    private JButton closeUp = new JButton();
    
    public JGroupChatDlg() {
        super();
        this.setSize(891, 683);
        this.setLocationRelativeTo(null);// 设置窗口在屏幕正中间显示
        this.setResizable(false);
        init();
        this.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
        this.setUndecorated(true);// 这句话用来阻止窗体采用本机系统修饰,这样窗体就没有标题栏和边框了
        
        this.addMouseListener(new MouseAdapter() {
            public void mousePressed(MouseEvent e) {
                xOld = e.getX();        //记录鼠标按下时的坐标
                yOld = e.getY();
            }
        });
        
        this.addMouseMotionListener(new MouseMotionAdapter() {
            public void mouseDragged(MouseEvent e) {
                if (yOld > 37) return;
                int xOnScreen = e.getXOnScreen();
                int yOnScreen = e.getYOnScreen();
                int xx = xOnScreen - xOld;
                int yy = yOnScreen - yOld;
                setLocation(xx, yy);    //设置拖拽后,窗口的位置
            }
        });
    }
    
    private void init() {
        JPanel imagePanel = new JPanel() {
            private static final long serialVersionUID = -2493397636069899072L;
            public void paintComponent(Graphics g) {
                super.paintComponent(g);
                Graphics2D g2d = (Graphics2D) g;// Graphics2D比Graphics具有更强大的功能,所以在这里我们做一下类型转换
                
                // 设置画笔的抗锯齿属性
                g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
                g2d.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
                
                g2d.drawImage(background, 0, 0, 891, 683, this);
                g2d.drawImage(functionList, 0, 40, 364, 49, this);
            }
        };
        this.add(imagePanel);
        imagePanel.setLayout(new BorderLayout());
        
        titleBar.setPreferredSize(new Dimension(891, 32));
        titleBar.setLayout(new BorderLayout());
        titleBar.setOpaque(false);
        imagePanel.add(titleBar, BorderLayout.NORTH);
        
        upButtonsPanel.setSize(96, 32);
        upButtonsPanel.setLayout(new GridLayout(1, 3));
        upButtonsPanel.add(minUp);
        upButtonsPanel.add(maxUp);
        upButtonsPanel.add(closeUp);
        minUp.addActionListener(this);
        maxUp.addActionListener(this);
        closeUp.addActionListener(this);
        
        titleBar.add(upButtonsPanel, BorderLayout.EAST);
    }
 
    public void actionPerformed(ActionEvent e) {
        if (e.getSource() == minUp) { this.setExtendedState(JFrame.ICONIFIED); }
        else if (e.getSource() == maxUp) {
            if (state) {
                this.setExtendedState(JFrame.NORMAL);
                state = false;
            }
            else {
                this.setExtendedState(JFrame.MAXIMIZED_BOTH);
                state = true;
            }
        }
        else if (e.getSource() == closeUp) { this.dispose(); }
    }
}

附录

1. 相关链接

2. 资源图片

图1.5-1 main_frame_background_default.png 图1.5-2 main_frame_function_list.png

  (注:以上图片位于com.jianshu.images.frameui文件夹中)

上一篇下一篇

猜你喜欢

热点阅读