美文网首页从零开始制作Java版高仿QQ
从零开始制作高仿QQ(一)——为窗口添加背景

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

作者: 一个帅气的小哥哥 | 来源:发表于2018-12-26 00:45 被阅读0次

    目录

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

    背景

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

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

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

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

    开发环境

    • Eclipse Java Oxygen(哪个版本的我给忘了……)
    • JDK-10(但在开发的时候把编译的级别设成了1.7,谁叫老师装的JDK-1.8呢)
    图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文件夹中)

    相关文章

      网友评论

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

        本文链接:https://www.haomeiwen.com/subject/hmxclqtx.html