论坛首页 Java企业应用论坛

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

浏览 31017 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2015-05-28  
最近公司启动了内部即时通信系统项目,准备实现类似腾讯通RTX、IMO那样的企业即时通信功能,除了标准的文字消息以外,还要支持语音、视频、文件传输,并与企业ERP、OA、HR等系统实现数据互通,以IM作为以后企业内部应用的入口门户。客户端除了PC以外,还要支持Android、iOS。作为PC客户端,目前提出的要求是至少要能够支持Windows和Mac OS X。为了尽可能减少开发复杂性,我们采用了Java作为前后端开发语言。PC客户端采用Java开发,在IM基本功能方面,聊天窗口采用现在流行的气泡模式(如Windows及Mac OS X上的新版QQ聊天窗口)。由于不同于传统的文本消息显示模式(支持图文混合),气泡消息显示模式需要将收到的对方消息靠左显示,自己发出的消息靠右显示,同时还要进行气泡的绘制,气泡内的消息仍然是有段落格式自动适应能力的。气泡内的消息仍然要支持图文混合,且在拖拉聊天窗口区域导致其大小发生改变时,聊天消息段落也要自动适应折行,消息中的图片能够自动根据聊天窗口区域进行缩放,因此气泡也要能够自动进行大小调整……上述这些功能,都是在对QQ聊天窗口进行比较仔细的测试和研究后,确定的功能需求。从难度上来讲,确实有一定的挑战性。下面先贴几张截图来看看目前实现的效果:

1. 基本的气泡消息聊天窗口效果



2. 改变聊天窗口大小时,消息段落能够自动折行,气泡能够自适应调整大小(gif动画,有点大)



3. 气泡消息能够支持图文混合,改变聊天窗口大小时,气泡消息中的图片可以自适应缩放(gif动画,有点大)



当然,气泡消息图文混合也支持gif动画图片文件,这里就不演示了。后面会向大家介绍一下基本的技术实现思路,算是抛砖引玉,欢迎大家一起讨论。
  • 大小: 60.7 KB
  • 大小: 6.3 MB
  • 大小: 8.3 MB
   发表时间:2015-05-31  
吊炸天啊,大神,求源码学习
0 请登录后投票
   发表时间:2015-06-02  
楼主我爱你
0 请登录后投票
   发表时间:2015-06-03  
牛!功力很深厚啊!
0 请登录后投票
   发表时间:2015-06-03   最后修改:2015-06-03
下面开始分享一下如何用Java Swing实现的仿QQ气泡消息聊天窗口效果。针对这个截图的测试程序窗口,我把它分成上下两个部分,采用JSplitter,下面是输入消息的区域,上面是显示消息的区域。这两个区域都是采用JTextPane的派生子类实现的。我的逻辑是,先定义输入消息区域JTextPane子类,类名为JIMSendTextPane,然后再基于JIMSendTextPane派生一个新的子类JIMHistoryTextPane作为显示消息区域。两个JTextPane的派生子类都采用JScrollPane放置,并将各自的JScrollPane设置为水平滚动条永远禁止,垂直滚动条按需出现。为什么要创建JTextPane的派生子类,主要是考虑到Java自带的JTextPane存在一个问题。我们知道默认情况下,如果JTextPane所在的JScrollPane禁止水平滚动条出现时,JTextPane是具备基于单词(Word)为最小单位的自动换行功能的。对于中文而言,最小单位就是一个汉字;对于英文或拉丁语言而言,就是以空格为单位的字母组合。但是有一个情况,如果你一直输入英文字母,中间不空格,那么JTextPane就会认为这是一个很长的单词,一直没有结束,那么它是不会自动换行的。这显然是不符合我们要求的。为了能够一劳永逸地解决这个问题,使JTextPane能够在任何情况下都能对超出宽度的内容进行自动换行,就需要对JTextPane进行派生子类定义,对其内部负责换行的地方做些修改。具体代码如下:

import javax.swing.JTextPane;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BoxView;
import javax.swing.text.ComponentView;
import javax.swing.text.Element;
import javax.swing.text.IconView;
import javax.swing.text.LabelView;
import javax.swing.text.ParagraphView;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;

/**
 * 该类是真正实现超长单词都能自动换行的 JTextPane 的子类
 * Java 7 以下版本的 JTextPane 本身都能实现自动换行,对
 * 超长单词都能有效,但从 Java 7 开始读超长单词就不能自动
 * 换行,导致 JTextPane 的实际宽度变大,使得滚动条出现。
 * 下面的方法是对这个 bug 的较好修复。
 *
 * Created by dolphin on 15-2-3.
 */
public class JIMSendTextPane extends JTextPane {

	// 内部类
	// 以下内部类全都用于实现自动强制折行

	private class WarpEditorKit extends StyledEditorKit {

		private ViewFactory defaultFactory = new WarpColumnFactory();

		@Override
		public ViewFactory getViewFactory() {
			return defaultFactory;
		}
	}

	private class WarpColumnFactory implements ViewFactory {

		public View create(Element elem) {
			String kind = elem.getName();
			if (kind != null) {
				if (kind.equals(AbstractDocument.ContentElementName)) {
					return new WarpLabelView(elem);
				} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
					return new ParagraphView(elem);
				} else if (kind.equals(AbstractDocument.SectionElementName)) {
					return new BoxView(elem, View.Y_AXIS);
				} else if (kind.equals(StyleConstants.ComponentElementName)) {
					return new ComponentView(elem);
				} else if (kind.equals(StyleConstants.IconElementName)) {
					return new IconView(elem);
				}
			}

			// default to text display
			return new LabelView(elem);
		}
	}

	private class WarpLabelView extends LabelView {

		public WarpLabelView(Element elem) {
			super(elem);
		}

		@Override
		public float getMinimumSpan(int axis) {
			switch (axis) {
				case View.X_AXIS:
					return 0;
				case View.Y_AXIS:
					return super.getMinimumSpan(axis);
				default:
					throw new IllegalArgumentException("Invalid axis: " + axis);
			}
		}
	}

	// 本类

	// 构造函数
	public JIMSendTextPane() {
		super();
		this.setEditorKit(new WarpEditorKit());
	}
}


这个派生子类JIMSendTextPane作为输入消息区域,是完全能够满足要求了,也可以实现插入图片,图文混排,文字样式自定义等等。但是对于显示消息的区域,这个派生子类还不够。因为显示消息的区域涉及到聊天消息气泡、头像的绘制,这必须依靠JTextPane派生子类的自绘方式实现。当然,显示消息区域也必须要具备任意情况下的超宽内容换行,而JIMSendTextPane恰好已具备了这个换行能力,所以显示消息区域要以JIMSendTextPane为父类,再进行子类派生。(未完待续)
0 请登录后投票
   发表时间:2015-06-07  
cyberniuniu 写道
下面开始分享一下如何用Java Swing实现的仿QQ气泡消息聊天窗口效果。针对这个截图的测试程序窗口,我把它分成上下两个部分,采用JSplitter,下面是输入消息的区域,上面是显示消息的区域。这两个区域都是采用JTextPane的派生子类实现的。我的逻辑是,先定义输入消息区域JTextPane子类,类名为JIMSendTextPane,然后再基于JIMSendTextPane派生一个新的子类JIMHistoryTextPane作为显示消息区域。两个JTextPane的派生子类都采用JScrollPane放置,并将各自的JScrollPane设置为水平滚动条永远禁止,垂直滚动条按需出现。为什么要创建JTextPane的派生子类,主要是考虑到Java自带的JTextPane存在一个问题。我们知道默认情况下,如果JTextPane所在的JScrollPane禁止水平滚动条出现时,JTextPane是具备基于单词(Word)为最小单位的自动换行功能的。对于中文而言,最小单位就是一个汉字;对于英文或拉丁语言而言,就是以空格为单位的字母组合。但是有一个情况,如果你一直输入英文字母,中间不空格,那么JTextPane就会认为这是一个很长的单词,一直没有结束,那么它是不会自动换行的。这显然是不符合我们要求的。为了能够一劳永逸地解决这个问题,使JTextPane能够在任何情况下都能对超出宽度的内容进行自动换行,就需要对JTextPane进行派生子类定义,对其内部负责换行的地方做些修改。具体代码如下:

import javax.swing.JTextPane;
import javax.swing.text.AbstractDocument;
import javax.swing.text.BoxView;
import javax.swing.text.ComponentView;
import javax.swing.text.Element;
import javax.swing.text.IconView;
import javax.swing.text.LabelView;
import javax.swing.text.ParagraphView;
import javax.swing.text.StyleConstants;
import javax.swing.text.StyledEditorKit;
import javax.swing.text.View;
import javax.swing.text.ViewFactory;

/**
 * 该类是真正实现超长单词都能自动换行的 JTextPane 的子类
 * Java 7 以下版本的 JTextPane 本身都能实现自动换行,对
 * 超长单词都能有效,但从 Java 7 开始读超长单词就不能自动
 * 换行,导致 JTextPane 的实际宽度变大,使得滚动条出现。
 * 下面的方法是对这个 bug 的较好修复。
 *
 * Created by dolphin on 15-2-3.
 */
public class JIMSendTextPane extends JTextPane {

	// 内部类
	// 以下内部类全都用于实现自动强制折行

	private class WarpEditorKit extends StyledEditorKit {

		private ViewFactory defaultFactory = new WarpColumnFactory();

		@Override
		public ViewFactory getViewFactory() {
			return defaultFactory;
		}
	}

	private class WarpColumnFactory implements ViewFactory {

		public View create(Element elem) {
			String kind = elem.getName();
			if (kind != null) {
				if (kind.equals(AbstractDocument.ContentElementName)) {
					return new WarpLabelView(elem);
				} else if (kind.equals(AbstractDocument.ParagraphElementName)) {
					return new ParagraphView(elem);
				} else if (kind.equals(AbstractDocument.SectionElementName)) {
					return new BoxView(elem, View.Y_AXIS);
				} else if (kind.equals(StyleConstants.ComponentElementName)) {
					return new ComponentView(elem);
				} else if (kind.equals(StyleConstants.IconElementName)) {
					return new IconView(elem);
				}
			}

			// default to text display
			return new LabelView(elem);
		}
	}

	private class WarpLabelView extends LabelView {

		public WarpLabelView(Element elem) {
			super(elem);
		}

		@Override
		public float getMinimumSpan(int axis) {
			switch (axis) {
				case View.X_AXIS:
					return 0;
				case View.Y_AXIS:
					return super.getMinimumSpan(axis);
				default:
					throw new IllegalArgumentException("Invalid axis: " + axis);
			}
		}
	}

	// 本类

	// 构造函数
	public JIMSendTextPane() {
		super();
		this.setEditorKit(new WarpEditorKit());
	}
}


这个派生子类JIMSendTextPane作为输入消息区域,是完全能够满足要求了,也可以实现插入图片,图文混排,文字样式自定义等等。但是对于显示消息的区域,这个派生子类还不够。因为显示消息的区域涉及到聊天消息气泡、头像的绘制,这必须依靠JTextPane派生子类的自绘方式实现。当然,显示消息区域也必须要具备任意情况下的超宽内容换行,而JIMSendTextPane恰好已具备了这个换行能力,所以显示消息区域要以JIMSendTextPane为父类,再进行子类派生。(未完待续)


果然是大神,以前我还在为这个问题纠结,为什么jdk版本高了这个好用的功能反而没了
0 请登录后投票
   发表时间:2015-06-07  
嗯,是的,原来Java 6都还是好的,但是到了Java 7好像就出现这种问题了,你说它是个bug也行,但也许Oracle认为这不算bug,他们认为以空格为区分是很正当的吧,呵呵。。。
0 请登录后投票
   发表时间:2015-06-07  
cyberniuniu 写道
嗯,是的,原来Java 6都还是好的,但是到了Java 7好像就出现这种问题了,你说它是个bug也行,但也许Oracle认为这不算bug,他们认为以空格为区分是很正当的吧,呵呵。。。


中文是没有问题的,不管在jdk几里面都可以换行,到了7之后英文和数字就没有了

大神,继续更贴,坐等学习
0 请登录后投票
   发表时间:2015-06-20  
大神,为何不继续更贴了?
0 请登录后投票
   发表时间:2015-06-24  
明天更新,这几天忙别的事了
0 请登录后投票
论坛首页 Java企业应用版

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