一个图库软件的分享(一)

          曾经接了一个设计公司的图库软件项目,设计师做设计时,经常要参考一些素材,这些素材由各设计师按类别上传到公司服务器给大家分享,长期积累下素材数量达到了上百个分类,上万副图片,总共几百G的文件,设计师靠windows访问服务器来进行素材的查阅,很难找到理想中的素材。

         在架构的选型上,无非是BS和CS两种模式,BS模式是流行手法,但是文件上传功能始终觉得用户体验不好,同时也不支持按文件夹上传的功能,另一方面,客户端涉及到一系列的用户操作,而浏览器下的菜单开发也比较费劲,所以我采用了更传统的CS方式来实现图库,用java swing来实现图库的客户端,而服务端也开发相应的java服务。但swing开发对我来说是个比较大的挑战,首先没有实战经验,很多东西要现捣鼓,其次美工没法介入太多,很多效果都得自己调。但我相信一句话,那就是谁也不是生下来就能搞开发的,不会就学,所以挑战归根到底是个时间问题,不算难点。

         那么图库最基本的需求就是一方面能快速检索,令一方面也能支持分类管理和图片素材的上传。这些素材中,很多都是非常精美的效果图,单一文件非常大,基本都在四五m左右,最大的有60多m,如果要支持网络下的快速检索,那就只能使用缩略图了,于是自然的采用了一个非常优秀的缩略图软件JMagick。

我列出了一些问题,并且敲定了相关方案:

         如何实现文件上传?          

         既然选择了cs方式,只能自己实现文件上传了,也考虑了ftp的方式,但由于服务端需要将上传后的图片生成缩略图,ftp的服务端事件根本没法抓取,所以不如自己实现文件传输来得简单和可控。所以我采用了xsocket+自定义传输协议的方式来实现文件上传。

         因为通讯上涉及到很多种操作的交互,比如请求缩略图、上传文件、图片管理(删除、重命名等)、请求原始图片(下载原始图片)等等一系列操作,所以协议上还是采用了xml的方式,但现在来看,xml的打包和解析实际上太重量级,改为json数据结构会更好一些。一个合理的图片上传包数据应该包含文件名、文件长度、要上传到的分组以及图片字节流等内容。

         如何控制图片的展示?

         用户要快速检索,那必须要求在一屏中能同时看到多幅(20幅)排好的图片,感兴趣的才打开浏览,那在这里展示缩略图是最好的,那这里缩略图就有一个排列和加载的问题,因为本身图片文件大小是不一致的,做成缩略图后图片大小也不一致,加载有快有慢,而且不希望按顺序挨个加载显示,最好的效果是打开一屏时,图片先用空位置占位,各个缩略图再独立加载,互不影响,加载完后自然显示

         既然缩略图要异步展示,那在网络通讯上,发起图片请求再等待服务器返回的方式就达不到效果了,那我干脆在每个客户端也像服务端一样,也设置TCP服务,客户端只管向服务端请求图片而不等待返回,而服务端收到图片请求后,按照客户端提供的“客户端服务”信息,将图片数据发送到对应客户端所在TCP服务端口。总之客户端只管请求就可以了。

public void showGridData() {     
        List<ImageDetailEntity> data = ImagePageBean.getInstance()
				.getCurrentQueryInfo();
        jPanel.removeAll();   //jPanel是母板,所有缩略图在其上布局

	int imgSize = data.size();

	int rows = imgSize / cols;
	if (imgSize % cols > 0) {
		rows = rows + 1;
	}
		
	jPanel.setLayout(new GridLayout(rows, cols));
		
	for (int i = 0; i < imgSize; i++) {
		ImageDetailEntity one = data.get(i);
		String id = one.getImgId();
		String oldfileName = one.getFileName();
		final JPanel pan = new JPanel();
		ImageGridPanel ip = ImageGridPanel.createEmptyPanel(one,
				cellSize + 20); // 创建显示图片加载的面板
		imagePanelMap.put(id, ip);
			
		pan.setMaximumSize(new Dimension(cellSize, cellSize + 20));
		pan.add(ip);
		pan.setName(id);
		pan.setToolTipText(oldfileName);
			
		jPanel.add(pan);   //将空的缩略图先占位
			
		pan.setDoubleBuffered(true);   //双缓冲提升显示效果
		pan.addMouseListener(new MouseListener() {

			public void mouseClicked(MouseEvent me) {
                                  ....//鼠标事件,点击缩略图的响应事件,比如加菜单之类
                         }

		});
		jPanel.add(pan);
		this.updateUI();
	}
	jPanel.setVisible(true);

}
public static ImageGridPanel createEmptyPanel(ImageDetailEntity idEntity,int panelSize) {
	ImageGridPanel panel = new ImageGridPanel();
	panel.panelSize = panelSize + 20;
	Dimension size = new Dimension(panelSize, panelSize + 20);
	panel.setPreferredSize(size);
	String overviewName = idEntity.getOverviewName();

         //将当前的占位图实例缓存,等文件下载完毕后再操作占位图实例,重刷占位图界面而显示图片
	gridPanelMap.put(idEntity.getImgId(), panel);  

	try {
                //此处开辟了一个线程去请求服务器上的图片,不会阻塞占位图的显示
		ImagePanel.loadGridImage(idEntity.getImgId(), new File(
				DataConstants.ImageSaveDirectoryClient + overviewName),
				panelSize, panelSize);

	} catch (IOException e) {
		e.printStackTrace();
		DataConstants.printErrorLog("创建空Panel出错:" + e.getMessage());
	}
	return panel;
}
 
//占位图每次updateUI都会触发该方法,这个方法很有意思,g.drawXXX可以渲染所有展示
public void paintComponent(Graphics g) {
	if (img == null) {
		if (infoString == null) {
			g.drawString("图片加载中", 30, 30);
		} else {
			g.drawString(infoString, 30, 30);
		}
		return;
	}
               
        //以下是给缩略图画外框,使展现有立体感
	g.setColor(Color.lightGray);
	int tmpSize = Math.max(img.getWidth(null), img.getHeight(null))
		+ padding * 2;
	g.drawRect(1, 1, tmpSize - 1, tmpSize - 1); // 画外框
	g.drawRect(startLeft - 2, startTop - 2, img.getWidth(null) + 4, img
			.getHeight(null) + 4); // 画内框
	g.drawLine(startLeft, startTop + img.getHeight(null) + 4, startLeft - 2
			+ img.getWidth(null) + 6, startTop + img.getHeight(null) + 4);
	g.drawLine(startLeft - 2 + img.getWidth(null) + 6, startTop, startLeft
			- 2 + img.getWidth(null) + 6, startTop + img.getHeight(null)
			+ 4);
         //将图片渲染到panel
	if (img != null) {
		g.drawImage(img, startLeft, startTop, Color.RED, null); 
	} else {
		g.setColor(Color.red);
		g.drawString("无图像信息", startLeft + 100, startTop + 100);
	}
        //图片下方的标题展示
	if (fileName != null && !"".equals(fileName)) {
		g.setColor(Color.black);
		int len1 = fileName.getBytes().length;
		int len2 = fileName.length();
		if (len1 != len2) {// 表示有中文【后注:当时这个判断中文的手法不太好】
			if (len2 <= 15) {
				int left = 8 + (150 - len2 * 5) / 2;
				g.drawString(fileName, left, tmpSize + 15);
			} else {
				g.drawString(fileName.substring(0, 12) + "...", 8,
						tmpSize + 15);
			}
		} else {
			int left = 0;
			if (len1 <= 30) {
				left = 8 + (150 - len1 * 5) / 2;
				g.drawString(fileName, left, tmpSize + 15);
			} else {
				g.drawString(fileName.substring(0, 27) + "...", 8,
						tmpSize + 15);
			}
		}
	}
}

     这样一个简单的缩略图格点展现就完成了。

     还有很多有趣的细节,在之后的博文会陆续分享,感兴趣的同学可以问我要源码,欢迎交流。

      界面示例见附件

猜你喜欢

转载自huilet.iteye.com/blog/1504842
今日推荐