使用Java开发桌面即时通讯程序遇到的问题

项目:https://www.lking.top/?p=87

1. JPanel面板绘制背景图片问题。

参考大佬:https://www.jb51.net/article/101516.htm

本项目中顶部标题栏即使用该方法设置背景。
在这里插入图片描述

 @Override
    protected void paintComponent(Graphics g) {
        super.paintComponent(g);
        
        g.drawImage(CommonUtils.getImage(R.Images.SIGN_IN_TOP_BG),0,0,this.getWidth()+700,this.getHeight()+600,this);
    }

附 JFrame设置背景:https://blog.csdn.net/weixin_43670802/article/details/90487386

2. 圆形图片转换问题。

项目图片处理ImageUtils类

package loveqq.utils;

import loveqq.config.R;
import loveqq.model.entity.LQUser;

import javax.imageio.ImageIO;

import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * @author Jason
 * @version 1.0
 * @date 12/22/2019 2:40 PM
 * @describe: Image Utilities.
 */
public class ImageUtils {

    /**
     * @author: Jason
     * @date: 12/22/2019
     * @time: 7:22 PM
     * @param
     * @return
     * @describe: Obtain Network Image. By Image's Child Class -->BufferedImage
     */
    public static BufferedImage getImageByURL(String url){
        BufferedImage bufferedImage=null;
        HttpURLConnection connection=null;
        URL netURL=null;
       try{
            netURL=new URL(url);
            connection=(HttpURLConnection) netURL.openConnection();
            connection.setConnectTimeout(5000);
            connection.connect();
            int responseCode=connection.getResponseCode();
            //IF Connection Successful.
            if(responseCode==200){
                bufferedImage= ImageIO.read(connection.getInputStream());
            }
            return bufferedImage;
       } catch (MalformedURLException e) {
           e.printStackTrace();
       } catch (IOException e) {
           e.printStackTrace();
       }finally {
           if(connection!=null){
               connection.disconnect();
           }

       }

       return null;


    }
    /**
     * @author: Jason
     * @date: 12/22/2019
     * @time: 7:55 PM
     * @param
     * @return
     * @describe: Scale Image.(Shape is Square Size:200x200)
     */
    public static BufferedImage getScaledImageInHighQuality(BufferedImage originImage){
        //Origin Image's Size.
        int originWidth=originImage.getWidth();
        int originHeight=originImage.getHeight();
        //Origin Image Transparency Type.
        int imageType=originImage.getColorModel().getTransparency();

        //New Image's Size.
//        int smallWidth=originImage.getWidth()/R.Configs.IMAGE_SCALE_MULTIPLE;
//        int smallHeight=originImage.getHeight()/R.Configs.IMAGE_SCALE_MULTIPLE;

        int smallWidth=R.Configs.IMAGE_SCALED_SIZE;
        int smallHeight=R.Configs.IMAGE_SCALED_SIZE;

        BufferedImage smallImage=new BufferedImage(smallWidth,smallHeight,imageType);
        //Config Image Rendering Value.
        //New's Image Antialiasing
        RenderingHints renderingHints=new RenderingHints(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        //Create New's Image By High Quality
        renderingHints.put(RenderingHints.KEY_RENDERING,RenderingHints.VALUE_RENDER_QUALITY);
        Graphics2D graphics2D=smallImage.createGraphics();
        graphics2D.drawImage(originImage,0,0,smallWidth,smallHeight,0,0,originWidth,originHeight,null);
        graphics2D.dispose();

        return  smallImage;
    }
    public static BufferedImage getRoundImage(BufferedImage originImage){
        //Origin Image's Size.
        int originWidth=originImage.getWidth();
        int originHeight=originImage.getHeight();
        BufferedImage roundImage=new BufferedImage(originWidth,originHeight,BufferedImage.TYPE_4BYTE_ABGR);
        Graphics2D graphics2D=roundImage.createGraphics();
        //Set Antialiasing
        graphics2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING,RenderingHints.VALUE_ANTIALIAS_ON);
        Ellipse2D.Double ellipse=new Ellipse2D.Double(0,0,originWidth,originHeight);
        graphics2D.setClip(ellipse);
        graphics2D.drawImage(originImage,0,0,null);
        //graphics2D.setBackground(Color.green);
        graphics2D.dispose();

        return roundImage;
    }
    /**
     * @author: Jason
     * @date: 12/22/2019
     * @time: 8:19 PM
     * @param
     * @return
     * @describe: Write Image To File.
     */
    public static boolean writeToFile(BufferedImage image, File file){
        try {
            ImageIO.write(image,R.Configs.HEAD_PORTRAIT_FORMAT,file);
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }

    /**
     * @author: Jason
     * @date: 12/22/2019
     * @time: 11:14 PM
     * @param
     * @return
     * @describe:
     */
    public static boolean obtainNetworkImage(String url,File file){
        return writeToFile(getRoundImage(getScaledImageInHighQuality(getImageByURL(url))),file);
    }
}

思考:转换为圆形图片时,new 的 BufferedImage为透明背景,调用其Graphics2D二维画笔,设置圆形绘画区域,将原图画出。
注意:非自动调用的画笔需要dispose释放。
另外注意ImageIO的用法。

参考
https://my.oschina.net/u/3677987/blog/2254143
https://blog.csdn.net/zhawabcd/article/details/78345032
https://blog.csdn.net/u010995220/article/details/51176088
https://blog.csdn.net/qq_27292113/article/details/58168341

https://blog.csdn.net/qq_19714937/article/details/68631829
https://blog.csdn.net/qq_39505065/article/details/90247666
https://blog.csdn.net/qiaolevip/article/details/6738823?utm_source=blogxgwz2

3. 界面拖动问题。

项目中为面板顶级父类BasePanel设置了拖动可选功能

package loveqq.base;

import loveqq.view.components.TopTitleBar;

import javax.swing.*;
import java.awt.*;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;

/**
 * @author Jason
 * @version 1.0
 * @date 12/10/2019 3:55 PM
 * @describe: Base JPanel Class
 */
public abstract class BasePanel extends JPanel {
    //Pressed Point
    private Point pressPoint;


    public BasePanel(){
        super();
        this.initPanel();
        this.initComponents();
        this.addComponents();
        this.addListeners();
    }

    /**
     * @author: Jason
     * @date: 12/10/2019
     * @time: 3:49 PM
     * @param
     * @return void
     * @describe: Init Panel
     */
    protected abstract void initPanel();
    /**
     * @author: Jason
     * @date: 12/10/2019
     * @time: 3:50 PM
     * @param
     * @return void
     * @describe: Init Component
     */
    protected abstract void initComponents();
    /**
     * @author: Jason
     * @date: 12/10/2019
     * @time: 3:51 PM
     * @param
     * @return void
     * @describe: Add Components for Panel.
     */
    protected abstract void addComponents();
    /**
     * @author: Jason
     * @date: 12/10/2019
     * @time: 3:53 PM
     * @param
     * @return void
     * @describe: Add Listener for Panel
     */
    protected abstract void addListeners();
    /**
     * @author: Jason
     * @date: 12/10/2019
     * @time: 11:36 PM
     * @param
     * @return void
     * @describe: Enable Panel Drag
     */
    protected void enableDrag(BaseFrame frame){
        //Control Frame Move
        this.addMouseListener(new MouseAdapter(){

            public void mousePressed(MouseEvent e){
                pressPoint=e.getPoint();
                BasePanel.this.requestFocus();
            }
        });
        this.addMouseMotionListener(new MouseMotionAdapter(){
            public void mouseDragged(MouseEvent e){
                Point currentPoint=e.getPoint();
                Point locationPoint=frame.getLocation();
                int x=locationPoint.x+currentPoint.x-pressPoint.x;
                int y=locationPoint.y+currentPoint.y-pressPoint.y;
                frame.setLocation(x,y);
            }

        });
    }
    protected void enableDrag(BaseDialog dialog){
        //Control Frame Move
        this.addMouseListener(new MouseAdapter(){

            public void mousePressed(MouseEvent e){
                pressPoint=e.getPoint();
                BasePanel.this.requestFocus();
            }
        });
        this.addMouseMotionListener(new MouseMotionAdapter(){
            public void mouseDragged(MouseEvent e){
                Point currentPoint=e.getPoint();
                Point locationPoint=dialog.getLocation();
                int x=locationPoint.x+currentPoint.x-pressPoint.x;
                int y=locationPoint.y+currentPoint.y-pressPoint.y;
                dialog.setLocation(x,y);
            }

        });
    }
    /**
     * @author: Jason
     * @date: 12/10/2019
     * @time: 11:43 PM
     * @param
     * @return void
     * @describe: Init Title Bar
     */
    protected void initTitleBar(TopTitleBar topTitleBar){
        this.add(topTitleBar);
    }

}

总结:拖动效果实现主要通过两个类:MouseListenerMouseMotionListener ,首先当鼠标按下(MouseListener),则记录点击处的Point,即x,y坐标,然后如果鼠标拖动,则持续调用MouseMotionListener中的拖动事件,持续记录拖动Point,然后计算后设置窗口的Location。

参考
https://blog.csdn.net/weixin_43670802/article/details/103287097

4. 窗口修饰抛异常问题。

为窗口设置透明度或其他修饰操作时,如引用LookAndFeel皮肤包时抛出:java.awt.IllegalComponentStateException: The frame is decorated

解决方法:this.setUndecorated(true);

注:使用该方法会自动取消默认标题栏。

参考
https://zhidao.baidu.com/question/415187807.html

5. 模拟文本框提示文本功能。

项目中登录界面的输入框提示语。
使用焦点监听FocusListener接口即可实现。
项目代码(还涉及到本地图片文件检测读取模块):

public static void initHintFocus(HeadPortraitPane headPortraitPane,JTextComponent component,JPasswordField jPasswordField, String hint){


        //Initial Style
        component.setFont(CommonUtils.getDefaultFont(Font.PLAIN,17));
        //Set Component Margin
        component.setMargin(new Insets(0,10,0,0));
        component.setForeground(Color.GRAY);

        //IF it is Password Field
        if(component instanceof JPasswordField){
            ((JPasswordField) component).setEchoChar((char)0);
        }

        component.setText(hint);

        component.addFocusListener(new FocusListener() {
            @Override
            public void focusGained(FocusEvent e) {
                String temp=component.getText();
                if(temp.equals(hint)||temp.equals(R.Strings.EMPTY_CONTENT)){
                    component.setText(R.Strings.EMPTY_CONTENT);
                    //Switch Input Style
                    SwingUtilities.invokeLater(()->{
                        //style
                        component.setForeground(Color.BLACK);
                        //IF it is Password Field
                        if(component instanceof JPasswordField){
                            ((JPasswordField) component).setEchoChar('●');
                        }

                    });
                }
            }

            @Override
            public void focusLost(FocusEvent e) {
                String temp=component.getText();

                //IF it is Account Field.
                if((component instanceof JTextField)&&!(component instanceof JPasswordField)){
                    File headPortraitFile=new File(R.DataDirectory.PERSONAL_DATA_PATH+"\"+temp+"\"+R.DataDirectory.ME_PARENT_PATH_NAME+"\"+R.DataDirectory.FRIENDS_IMAGE_PATH_NAME+"\"+R.DataDirectory.HEAD_PORTRAIT_NAME);

                    //Check HeadPortrait Whether Exists.
                    if(headPortraitFile.exists()){
                        SwingUtilities.invokeLater(()->{
                            headPortraitPane.setHeadPortrait(CommonUtils.getDIVImage(headPortraitPane.getWidth(),headPortraitPane.getHeight(),headPortraitFile.getPath()));

                            headPortraitPane.repaint();
                        });
                    }else{
                        SwingUtilities.invokeLater(()->{

                            headPortraitPane.setHeadPortrait(CommonUtils.getDIVImage(headPortraitPane.getWidth(), headPortraitPane.getHeight(), R.Images.DEFAULT_HEAD_PORTRAIT));

                            headPortraitPane.repaint();
                        });
                    }
                    //Check the Password File whether existed.
                    File currentUserDataPath=new File(R.DataDirectory.PERSONAL_DATA_PATH+"\"+component.getText()+"\"+R.DataDirectory.ME_PARENT_PATH_NAME+"\"+R.DataDirectory.ME_DATA_PATH_NAME+"\"+R.DataDirectory.ME_DATA_NAME);
                    System.out.println(currentUserDataPath.getPath());
                    DataInputStream dataInputStream=null;
                    if(currentUserDataPath.exists()){
                        try {
                            dataInputStream=new DataInputStream(new FileInputStream(currentUserDataPath));
                            String tempPassword=dataInputStream.readUTF();

                            if(!(tempPassword.trim().equals(R.Strings.EMPTY_CONTENT))){
                                SwingUtilities.invokeLater(()->{
                                    jPasswordField.setForeground(Color.BLACK);
                                    jPasswordField.setEchoChar('●');
                                    jPasswordField.setText(tempPassword);
                                });
                            }


                        } catch (FileNotFoundException ex) {
                            ex.printStackTrace();
                        } catch (IOException ex) {
                            ex.printStackTrace();
                        }finally {

                            if(dataInputStream!=null){
                                try {
                                    dataInputStream.close();
                                } catch (IOException ex) {
                                    ex.printStackTrace();
                                }
                            }
                        }

                    }

                }


                if(temp.equals(R.Strings.EMPTY_CONTENT)){
                    //Switch Input Style
                    SwingUtilities.invokeLater(()->{
                        //style
                        component.setForeground(Color.GRAY);

                        //IF it is Password Field.
                        if(component instanceof JPasswordField){
                            ((JPasswordField) component).setEchoChar((char)0);
                        }
                    });

                    component.setText(hint);
                }
            }

        });
    }

参考
https://blog.csdn.net/yanjingtp/article/details/79282365

6. 事件阻拦问题。

本项目中由于登录面板的拖动功能与文本框提示语的转换都是通过监听实现。
所以容易造成焦点传递问题。
阻拦解决方法:在这里插入图片描述
另外在Java中的JTextField会默认获取焦点,如果不想让其默认获得焦点,则:
解决方法:在上层容器中设置.setFocusable(true);
在这里插入图片描述

参考
https://zhidao.baidu.com/question/748425127114965252.html

7. 组件边距问题。

项目中登录面板的输入框文字与边缘有轻微边距。
实现方法:

 		//Set Component Margin
        component.setMargin(new Insets(0,10,0,0));

在这里插入图片描述

参考
http://www.hackerav.com/?post=69976


待更新…

8. GUI桌面程序超链接问题。

项目中注册用户与找回密码采用了超链接的形式,说白了就是要打开浏览器访问指定的网址。
在Java中提供了Desktop类。
在这里插入图片描述
项目实例:

this.addMouseListener(new MouseAdapter() {
            @Override
            public void mousePressed(MouseEvent e) {
                if(Desktop.isDesktopSupported()&&Desktop.getDesktop().isSupported(Desktop.Action.BROWSE)){
                    try {
                        //Attention here URI&URL&URN.
                        Desktop.getDesktop().browse(URI.create(url));
                    } catch (IOException ex) {
                        ex.printStackTrace();
                    }
                }
            }

注意:此处为URI(统一资源标记符),不是URL(统一资源定位符)。

URI = Uniform Resource Identifier 统一资源标志符URL = Uniform Resource
Locator 统一资源定位符URN = Uniform Resource Name
统一资源名称大白话,就是URI是抽象的定义,不管用什么方法表示,只要能定位一个资源,就叫URI,本来设想的的使用两种方法定位:1,URL,用地址定位;2,URN
用名称定位。

参考
https://www.zhihu.com/question/21950864/answer/66779836
https://blog.csdn.net/qq_32595453/article/details/80563142
https://www.zhihu.com/question/21950864

另外,还有一种方式可以实现超链接,不过我没有试过,这个其实是执行了dos命令。
如: Runtime.getRuntime().exec("cmd.exe /c start " + "http://www.google.com");
cmd /c +command表示执行完命令后关闭。

可以参考:https://jingyan.baidu.com/article/cd4c2979266c30756e6e6091.html

在这里插入图片描述

参考
https://blog.csdn.net/xietansheng/article/details/78453718
https://blog.csdn.net/sinat_37765046/article/details/79078365
https://blog.csdn.net/MK259/article/details/7306790

9. GUI桌面程序正常关闭执行代码片段问题。

本程序由于是聊天程序,需要有上线下线功能,所以在客户端,用户每次关闭程序后,服务端应该收到下线通知。所以应该有一个方法总是在程序关闭时执行。
解决方法:Runtime.getRuntime().addShutdownHook(Thread hook);
在这里插入图片描述

注意:该方法仅限于正常关闭,即eixt返回值为0的情况下。
所以,我为了用户在线的可靠性增加了脉搏跳动机制(固定间隔向服务器发送在线标记报文数据包),然后服务端如果在一段时间内,没有收到在线标记数据包则会判定用户下线。这个方法用来防止程序非正常关闭而没有执行shutdownHook的情况。

10. EDT(Event Dispatch Thread)事件分发线程的了解。

参考
http://www.imooc.com/wenda/detail/569351
http://www.360doc.com/content/12/1022/01/820209_242885907.shtml
https://cloud.tencent.com/developer/ask/51914
https://blog.csdn.net/qq_32725491/article/details/78701620
这个事件调度线程是一个由AWT管理的特殊线程。基本上,它是一个在无限循环中运行的线程,处理事件。

这个java.awt.EventQueue.invokeLater和javax.swing.SwingUtilities.invokeLater方法是提供在事件队列上运行的代码的一种方法。编写在多线程环境中安全的UI框架非常困难,因此AWT作者决定,他们只允许在单个特殊线程上对GUI对象进行操作。所有事件处理程序都将在这个线程上执行,所有修改GUI的代码也应该在这个线程上运行。

现在AWT通常不检查您是否从另一个线程发出GUI命令(C#的WPF框架确实这样做了),这意味着您可以编写大量代码,并且对此非常不知情,不会遇到任何问题。但这可能导致未定义的行为,所以最好的做法是始终确保GUI代码在事件分派线程上运行。invokeLater提供执行此操作的机制。

一个典型的例子是,您需要运行一个长时间运行的操作,比如下载一个文件。因此,启动一个线程来执行此操作,然后,当它完成时,您可以使用invokeLater更新UI。如果你不使用invokeLater相反,您只是直接更新UI,您可能有一个竞争条件,并且可能会发生未定义的行为。

维基百科有更多信息

另外,如果您好奇为什么AWT作者不只是使工具包多线程,这里是篇好文章。

总之一句话,该干啥事的线程干啥事,别掺,尤其是更新GUI的线程,不能干重活。

11. 在Java中实现MD5加密问题。

出于用户隐私原因,项目中需要将用户的密码转为MD5码然后发送至服务器,与服务器数据库中的用户MD5密码比对。
项目实例:

 /**
     * @author: Jason
     * @date: 12/16/2019
     * @time: 8:57 PM
     * @param  md5Bytes
     * @return
     * @describe: md5 bytes to md5 hex string (No use the method)
     */
    public static String bytesToHexString(byte[] md5Bytes){
        StringBuffer hexBuffer=new StringBuffer();
        int digital;
        for(int i=0;i<md5Bytes.length;i++){
            digital=md5Bytes[i];
            if(digital<0)
                digital+=256;
            if(digital<16)
                hexBuffer.append("0");
            hexBuffer.append(Integer.toHexString(digital));
        }
        return hexBuffer.toString().toUpperCase();
    }
    /**
     * @author: Jason
     * @date: 12/16/2019
     * @time: 8:57 PM
     * @param  password
     * @return
     * @describe: Get MD5 HEX Code
     */
    public static String getMD5(char[] password){
        String md5=null;
        try {
            md5=new String(password);
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            //MD5 Array To Hex String
            md5=new BigInteger(1,messageDigest.digest(md5.getBytes("UTF-8"))).toString(16);

            return md5.toUpperCase();// Return Uppercase
        }catch(NoSuchAlgorithmException | UnsupportedEncodingException e){

            e.printStackTrace();
            return md5;

        }catch (Exception e){
            e.printStackTrace();
            return md5;
        }
    }

主要涉及两个类:加密类MessageDigest类,与基本整数操作类BigInteger类。
在这里插入图片描述
在这里插入图片描述

12. 使用非本地字体问题。

项目中并没有涉及此问题。

参考:https://www.cnblogs.com/interdrp/p/5034270.html

public Font getDefinedFont() {
        if (definedFont == null) {
            InputStream is = null;
            BufferedInputStream bis = null;
            try {
                is = ReYoFont.class.getResourceAsStream("/reyo.ttf");
                bis = new BufferedInputStream(is);
                // createFont返回一个使用指定字体类型和输入数据的新 Font。<br>
                // 新 Font磅值为 1,样式为 PLAIN,注意 此方法不会关闭 InputStream
                definedFont = Font.createFont(Font.TRUETYPE_FONT, bis);
                // 复制此 Font对象并应用新样式,创建一个指定磅值的新 Font对象。
                definedFont = definedFont.deriveFont(30);
            } catch (FontFormatException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (null != bis) {
                        bis.close();
                    }
                    if (null != is) {
                        is.close();
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return definedFont;
    }

13. 数据库连接超时问题。

参考:https://blog.csdn.net/a704397849/article/details/93797529

14. 鼠标点击问题。

参考:https://blog.csdn.net/ruanjwei1986/article/details/5998591

public class MyMouseListener extends MouseAdapter {
public void mouseClicked(MouseEvent evt) {
if (evt.getClickCount() == 3) {
// 处理鼠标三击
} else if (evt.getClickCount() == 2) {
// 处理鼠标双击
}
}
}

处理鼠标右键

public mouseClicked(MouseEvent e){

if(e.isMetaDown()){//检测鼠标右键单击

}

如何在小程序中处理鼠标中间键?

new MyMouseListener());

public class MyMouseListener extends MouseAdapter {
public void mouseClicked(MouseEvent evt) {
if ((evt.getModifiers() &?
InputEvent.BUTTON1_MASK) != 0) {
processLeft(evt.getPoint());
//处理鼠标左键单击
}
if ((evt.getModifiers() &?
InputEvent.BUTTON2_MASK) != 0) {
processMiddle(evt.getPoint());
//处理鼠标中间键单击
}
if ((evt.getModifiers() &?
InputEvent.BUTTON3_MASK) != 0) {
processRight(evt.getPoint());
//处理鼠标右键单击

15.JLabel标签居中问题。

label.setHorizontalAlignment(JLabel.CENTER);

16.组件位置移动问题。

项目中为了模仿收到消息自动将联系人面板跳转至第一条,特意使用Container容器类的特有方法实现。
在使用Box类(Container类的子类)规定好垂直方向后,删除收到信息的联系人面板,然后使用Box类已经继承到的方法:在这里插入图片描述
项目实例:

public FriendPane getFriendPane(int id){
        Component[] components=verticalBox.getComponents();
        for(Component component:components){
            FriendPane tempFriendPane=(FriendPane)component;
            if(tempFriendPane.getId()==id){

                verticalBox.remove(tempFriendPane);
                verticalBox.add(tempFriendPane,0);
                verticalBox.validate();
                verticalBox.repaint();
                scrollPane.validate();
                scrollPane.repaint();

                return tempFriendPane;
            }
        }
        return null;
    }

17.滚动面板滚动至底部问题。

项目中因在JScrollPane中放的是组件,所以不能像文本域一样调整光标移至最后一行。
项目实例:

//Move To ScrollPane Bottom.
messageListScrollPane.getViewport().setViewPosition(new Point(0,messageListScrollPane.getVerticalScrollBar().getMaximum()));

附:JTextAreaJScrollPane中设置移动至底部的方法。

jTextArea.setCaretPosition(jTextArea.getDocument().getLength());

参考:https://www.cnblogs.com/hujunzheng/p/3995643.html

18.组件大小获取问题(如自适应,则需绘制出后获取)。

项目中气泡是根据文字自适应大小的,所以出现了换行问题,然后我是把所有的气泡面板都放到了Box面板布局中,然后需要为每个气泡面板设置大小。所以需要获取气泡的尺寸(宽高)。
正因如此,发现了一个问题:即组件如果是自适应的大小,则没有绘制显示之前,是无法获取到其大小的。

项目实例:

  verticalBox.add(Box.createVerticalStrut(2));
                    verticalBox.add(messagePane);
                    verticalBox.add(Box.createVerticalStrut(2));
                    verticalBox.validate();
                    verticalBox.repaint();
                    //Move To ScrollPane Bottom.
                    messageListScrollPane.getViewport().setViewPosition(new Point(0,messageListScrollPane.getVerticalScrollBar().getMaximum()));
                    messageListScrollPane.validate();
                    messageListScrollPane.repaint();

                    //After View Visible ,then set size.
                    Dimension messagePaneSize=new Dimension(R.Dimensions.CHAT_VIEW_WIDTH-40,bubblePane.getHeight()+5);
                    messagePane.setSize(messagePaneSize);
                    messagePane.setMaximumSize(messagePaneSize);
                    messagePane.setMinimumSize(messagePaneSize);
                    messagePane.setPreferredSize(messagePaneSize);

                    verticalBox.validate();
                    verticalBox.repaint();
                    messageListScrollPane.validate();
                    messageListScrollPane.repaint();

19.气泡字体换行问题(字符串宽度计算)。

项目中的气泡规定一定宽度换行,所以需要计算每条消息字符串的宽度。
涉及一个类:FontMetrics
在这里插入图片描述
构造方法:FontMetrics(Font font);

基本过程:先构造FontMetrics类(使用要计算的字体),然后利用循环计算每一个字符的宽度,当计算过的字符宽度和超过一定宽度,则在本次循环处为字符串插入换行符‘ ’,Continue…
项目实例:

  private void commonMessage(StringBuffer message){

        contentArea=new JTextArea();
        //contentArea.setMaximumSize(new Dimension(MAX_WIDTH,200));
        contentArea.setForeground(Color.WHITE);
        contentArea.setFont(CommonUtils.getDefaultFont(Font.PLAIN,20));
        contentArea.setMargin(new Insets(5,5,5,5));


        contentArea.setOpaque(false);






        int tempWidth=0;
        int len=message.length();
        FontMetrics fontMetrics=contentArea.getFontMetrics(contentArea.getFont());

        for(int i=0;i<len;i++){
            tempWidth+=fontMetrics.charWidth(message.charAt(i));
            if(tempWidth>MAX_WIDTH){![在这里插入图片描述](https://img-blog.csdnimg.cn/20200214221801210.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MzY3MDgwMg==,size_16,color_FFFFFF,t_70)
                message.insert(i,"

");
                tempWidth=0;
            }
        }
        contentArea.setText(message.toString());
        //contentArea.setLineWrap(true);
        this.add(contentArea);
    }

参考
https://www.cnblogs.com/hujunzheng/p/3989510.html

20.说一说图文混合的气泡问题。

项目没有实现该功能,但实现的话需要涉及一个类:JTextPane
在这里插入图片描述

21.关于状态恢复问题。

项目中为了不再重复new对象,且不想每次都因为重新new对象而将用户输入的记录清除,所以采用了“隐藏”的方式在解决该问题。
即.setVisible(false);或者dispose();
在这里插入图片描述
在这里插入图片描述

项目中使用的是第一种:

}else if(frame instanceof ChatView){
                        frame.setVisible(false);
                    }
                    //else if etc...

然后开启的时候判断一下是否已经创建即可。

22.消息提醒问题。

项目实例:

//Play Sound.
        try {
            FileInputStream fileInputStream=new FileInputStream(R.Sounds.PROMPT_SOUND);
            AudioStream audioStream=new AudioStream(fileInputStream);
            AudioPlayer.player.start(audioStream);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }

参考
https://blog.csdn.net/cjx798280904/article/details/17917073

补充:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

原文地址:https://www.cnblogs.com/tfxz/p/12621575.html