论坛首页 Java企业应用论坛

Java Swing实现的仿QQ气泡消息聊天窗口效果

浏览 31018 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2015-07-07   最后修改:2015-07-07
因为公司项目的原因近段时间一直没有更新,抱歉哈。今天开始尽可能每隔两天就继续跟一下。上面讲到了我的聊天窗口的上下两个图文消息显示及输入区域都是基于JTextPane的派生子类,为了实现无论输入中文还是英文、有无空格分隔(作为英文单词分隔)都实现自动换行的效果。下面讲讲聊天窗口的上半部分聊天消息显示区域的具体实现,尤其是气泡的绘制、图文的混排实现。
先讲讲气泡的实现思路。开始我想过很多气泡的实现方法,在研究了JTextPane的文档类及其内容插入删除排版后,我想利用JTextPane能够插入JComponent的特点,直接把一个JLabel或者JTextPane给插入进去,作为段落显示,同时对这个JLabel或者JTextPane进行自绘,形成圆角矩形边框,模拟气泡的效果。但是后来发现这种方法对于段落的实际格式控制,尤其是气泡大小随聊天窗口区域变化而改变的适应能力不行,且气泡的小箭头出不来,所以放弃了。后来在网上看到了一篇很早的帖子在讨论关于利用VC和MFC在RichEdit中模拟QQ聊天气泡效果,得到了一点启发。气泡的绘制并不是与段落插入在同一个层面上,气泡是直接在历史消息显示区域的JTextPane的自绘中去实现,也就是在JTextPane的paintComponent方法中去绘制,而段落文本、图片的插入还是在JTextPane的层面上用其Document对象去负责。这里就存在一个问题,JTextPane怎么知道气泡绘制的区域大小和位置坐标?我是通过一个支持多线程并发的消息队列来保存每条消息的段落显示区域的大小及其位置。这个队列在JTextPane的Document插入每条消息的时候,对消息的段落区域进行计算,得到其显示区域大小及位置坐标,然后保存在队列中,而JTextPane的paintComponent方法里则不断对这个消息队列进行迭代枚举得到每条消息的显示区域的大小及位置然后依此进行气泡的绘制。这个队列采用的是Java里的concurrentLinkedQueue,能够实现基本的多线程无锁队列读写操作,这个特性在后面还会用得到,而且非常重要。下面是历史消息显示区域的JTextPane子类的自绘实现,当然,这里的气泡绘制包含了小箭头效果的实现,同时能够支持左右两边不同头像方向情况下,气泡箭头方向也能够自适应调整:

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.Point;
import java.awt.RenderingHints;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedQueue;

/**
 * Created by dolphin on 15-03-05.
 */
public class JIMHistoryTextPane extends JIMSendTextPane {

	private ConcurrentLinkedQueue<Message> messageConcurrentLinkedQueue;
	private ConcurrentHashMap<Integer, Image> senderHeadImageConcurrentHashMap;
	private Color otherMessageColor = new Color(188, 237, 245);
	private Color otherMessageBorderColor = new Color(156, 205, 213);
	private Color selfMessageColor = new Color(230, 230, 230);
	private Color selfMessageBorderColor = new Color(198, 198, 198);

	public JIMHistoryTextPane() {
		setEditable(false); // 用于显示历史消息,因此必须为只读模式,不允许用户修改内容
		setOpaque(false); // 设置成背景透明后,完全自绘才会看到效果
	}

	@Override
	public void paintComponent(Graphics g) {
		Graphics2D g2D = (Graphics2D) g;
		g2D.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // 反锯齿平滑绘制

		// 通过对并发消息链表遍历绘制全部消息气泡、消息发出者头像
		if (messageConcurrentLinkedQueue != null) {
			Iterator<Message> iterator = messageConcurrentLinkedQueue.iterator();
			while (iterator.hasNext()) {
				Message message = iterator.next();

				Point point = message.getMessagePaintLeftTop();

				if (point != null) {
					// 绘制消息发出者头像
					if (senderHeadImageConcurrentHashMap != null) {
						Image image = senderHeadImageConcurrentHashMap.get(message.getSenderHeadImageID());
						if (image != null) {
							if (message.isSelfSender()) {
								g2D.drawImage(image, this.getWidth() - image.getWidth(null) - 9, point.y - 25, null);
							} else {
								// 消息发出者是别人,则头像靠左显示
								g2D.drawImage(image, 9, point.y - 25, null);
							}
						}
					}

					// 绘制额消息气泡左边小箭头
					int xPoints[] = new int[3];
					int yPoints[] = new int[3];

					if (message.isSelfSender()) {
						// 绘制自己消息圆角消息气泡矩形
						g2D.setColor(selfMessageColor);
						g2D.fillRoundRect(point.x - 7, point.y - 7, message.getMessagePaintWidth() + 14, message.getMessagePaintHeight() + 14, 10, 10);
						// 绘制圆角消息气泡边框
						g2D.setColor(selfMessageBorderColor);
						g2D.drawRoundRect(point.x - 7, point.y - 7, message.getMessagePaintWidth() + 14, message.getMessagePaintHeight() + 14, 10, 10);

						// 消息发出者是自己,则头像靠右显示
						xPoints[0] = (point.x - 7) + (message.getMessagePaintWidth() + 14);
						yPoints[0] = point.y;
						xPoints[1] = xPoints[0] + 7;
						yPoints[1] = point.y;
						xPoints[2] = xPoints[0];
						yPoints[2] = point.y + 7;

						g2D.setColor(selfMessageColor);
						g2D.fillPolygon(xPoints, yPoints, 3);
						g2D.setColor(selfMessageBorderColor);
						g2D.drawPolyline(xPoints, yPoints, 3);
						g2D.setColor(selfMessageColor);
						g2D.drawLine(xPoints[0], yPoints[0] + 1, xPoints[2], yPoints[2] - 1);
					} else {
						// 绘制别人消息圆角消息气泡矩形
						// 绘制圆角消息气泡矩形
						g2D.setColor(otherMessageColor);
						g2D.fillRoundRect(point.x - 7, point.y - 7, message.getMessagePaintWidth() + 14, message.getMessagePaintHeight() + 14, 10, 10);
						// 绘制圆角消息气泡边框
						g2D.setColor(otherMessageBorderColor);
						g2D.drawRoundRect(point.x - 7, point.y - 7, message.getMessagePaintWidth() + 14, message.getMessagePaintHeight() + 14, 10, 10);

						// 消息发出者是别人,则头像靠左显示
						xPoints[0] = point.x - 7;
						yPoints[0] = point.y;
						xPoints[1] = xPoints[0] - 7;
						yPoints[1] = point.y;
						xPoints[2] = xPoints[0];
						yPoints[2] = point.y + 7;

						g2D.setColor(otherMessageColor);
						g2D.fillPolygon(xPoints, yPoints, 3);
						g2D.setColor(otherMessageBorderColor);
						g2D.drawPolyline(xPoints, yPoints, 3);
						g2D.setColor(otherMessageColor);
						g2D.drawLine(xPoints[0], yPoints[0] + 1, xPoints[2], yPoints[2] - 1);
					}
				}
			} // while
		}

		super.paintComponent(g); // 执行默认组件绘制(消息文本、图片以及段落显示等内容)
	}

	public void setMessageConcurrentLinkedQueue(ConcurrentLinkedQueue<Message> messageConcurrentLinkedQueue) {
		this.messageConcurrentLinkedQueue = messageConcurrentLinkedQueue;
	}

	public void setSenderHeadImageConcurrentHashMap(ConcurrentHashMap<Integer, Image> senderHeadImageConcurrentHashMap) {
		this.senderHeadImageConcurrentHashMap = senderHeadImageConcurrentHashMap;
	}
}


下一次再分享一下如何对收到的消息在历史消息显示区域JTextPane中进行段落控制,以及每条消息气泡显示区域的大小及位置如何计算。(未完待续)
0 请登录后投票
   发表时间:2015-07-07  
有用,收下了,谢谢。
0 请登录后投票
   发表时间:2015-08-04  
楼主, 源码能提供一份么, 最近我也做一份类似的东西, 但是无从下手啊, 试了很多方法, 都不行
0 请登录后投票
   发表时间:2015-10-15  
请问楼主Message类是如何定义的?
0 请登录后投票
   发表时间:2015-11-24  
同问楼主,Message类是如何定义的,谢谢
0 请登录后投票
   发表时间:2015-12-06  
请问楼主能不能更新完?
0 请登录后投票
   发表时间:2015-12-25  
请问楼主能不能更新完呀?
0 请登录后投票
   发表时间:2016-01-28  
楼主,快回来更新吧,等得花儿也谢了
0 请登录后投票
   发表时间:2016-04-19  
楼主块回来更贴吧。
0 请登录后投票
   发表时间:2016-04-19  
哈哈,楼主,大家都在等你呀,有空的话希望能来更新一下咯。不然留个联系方式也行嘛。
0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics