1. JPanel面板绘制背景图片问题。
本项目中顶部标题栏即使用该方法设置背景。
@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);
}
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;
public class ImageUtils {
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;
}
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;
}
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;
}
}
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;
public abstract class BasePanel extends JPanel {
//Pressed Point
private Point pressPoint;
public BasePanel(){
super();
this.initPanel();
this.initComponents();
this.addComponents();
this.addListeners();
}
protected abstract void initPanel();
protected abstract void initComponents();
protected abstract void addComponents();
protected abstract void addListeners();
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);
}
});
}
protected void initTitleBar(TopTitleBar topTitleBar){
this.add(topTitleBar);
}
}
总结:拖动效果实现主要通过两个类:MouseListener与MouseMotionListener ,首先当鼠标按下(MouseListener),则记录点击处的Point,即x,y坐标,然后如果鼠标拖动,则持续调用MouseMotionListener中的拖动事件,持续记录拖动Point,然后计算后设置窗口的Location。
4. 窗口修饰抛异常问题。
为窗口设置透明度或其他修饰操作时,如引用LookAndFeel皮肤包时抛出:java.awt.IllegalComponentStateException: The frame is decorated
解决方法:this.setUndecorated(true);
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);
}
}
});
}
6. 事件阻拦问题。
本项目中由于登录面板的拖动功能与文本框提示语的转换都是通过监听实现。
所以容易造成焦点传递问题。
阻拦解决方法:
另外在Java中的JTextField会默认获取焦点,如果不想让其默认获得焦点,则:
解决方法:在上层容器中设置.setFocusable(true);
7. 组件边距问题。
项目中登录面板的输入框文字与边缘有轻微边距。
实现方法:
//Set Component Margin
component.setMargin(new Insets(0,10,0,0));
待更新…
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
用名称定位。
另外,还有一种方式可以实现超链接,不过我没有试过,这个其实是执行了dos命令。
如: Runtime.getRuntime().exec("cmd.exe /c start " + "http://www.google.com");
cmd /c +command表示执行完命令后关闭。
9. GUI桌面程序正常关闭执行代码片段问题。
本程序由于是聊天程序,需要有上线下线功能,所以在客户端,用户每次关闭程序后,服务端应该收到下线通知。所以应该有一个方法总是在程序关闭时执行。
解决方法:Runtime.getRuntime().addShutdownHook(Thread hook);
注意:该方法仅限于正常关闭,即eixt返回值为0的情况下。
所以,我为了用户在线的可靠性增加了脉搏跳动机制(固定间隔向服务器发送在线标记报文数据包),然后服务端如果在一段时间内,没有收到在线标记数据包则会判定用户下线。这个方法用来防止程序非正常关闭而没有执行shutdownHook的情况。
10. EDT(Event Dispatch Thread)事件分发线程的了解。
这个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密码比对。
项目实例:
public static String bytesToHexString(byte[] md5Bytes){
StringBuffer hexBuffer=new StringBuffer();
int digital;
for(int i=0;i
digital=md5Bytes[i];
if(digital<0)
digital+=256;
if(digital<16)
hexBuffer.append("0");
hexBuffer.append(Integer.toHexString(digital));
}
return hexBuffer.toString().toUpperCase();
}
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. 使用非本地字体问题。
项目中并没有涉及此问题。
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。
// 新 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. 数据库连接超时问题。
14. 鼠标点击问题。
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()));
附:JTextArea在JScrollPane中设置移动至底部的方法。
jTextArea.setCaretPosition(jTextArea.getDocument().getLength());
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类(使用要计算的字体),然后利用循环计算每一个字符的宽度,当计算过的字符宽度和超过一定宽度,则在本次循环处为字符串插入换行符‘\n’,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
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,"\n\r");
tempWidth=0;
}
}
contentArea.setText(message.toString());
//contentArea.setLineWrap(true);
this.add(contentArea);
}
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();
}
补充: