接着上一部分,继续分析开头提出的几个问题。
业务处理类(ViewerService)主要处理图片浏览器的大部分业务逻辑,包括打开图片、放大图片、缩小图片、浏览上一张图片、浏览下一张图片。
在这个类中使用了单态模式,即类内存中只能创建一个实例对象,将类的构造方法的访问权限定义为private(于是无法在类的外部产生类的对象),在类的内部定义一个可以返回类的对象的方法,这个方法需要是静态方法,(于是可以在类的外部调用该静态方法返回类的对象),同时静态方法只能访问静态变量,所以类内部产生的对象的变量也要是静态的,代码如下:
//私有构造器 private ViewerService(){} private static ViewerService service = new ViewerService(); //获取单态实例 public static ViewerService getInstance() { return service; }
这样做的原因是该业务处理类的对象不是无状态的JAVA对象,该类中定义了一些保存业务状态的属性,包括当前浏览的文件目录、当前浏览的图片文件、文件目录的文件集合、图
片放大和缩小的比例。如果访问的是同一个实例,这些表示业务状态的属性就会被所有访问者共享,其中一个访问者改变了属性的值,其他访问者也会受到影响。
(对于这一部分,我也不是特别明白,尽量去理解吧,总会有豁然开朗的一天)
不管是菜单栏的菜单项还是工具栏的按钮,都必须要有事件响应,这里有两种实现方案。第一种,是初学者比较容易想到的,直接为菜单定义一个事件监听器,代码如下:
//为菜单定义事件监听器 ActionListener menuListener= new ActionListener(){ public void actionPerformed(ActionEvent e){ service.menuDo(ViewerFrame.this,e.getActionCommand()); } }; //新建一个菜单项,并注册事件监听器 JMenuItem menuItem = new JMenuItem(MenuItemArr[i][j]); menuItem.addActionListener(getActionListener()); 这样的方法会带来一系列的if…else,请看代码: //实现菜单的单击 public void menuDo(ViewerFrame frame, String cmd) { //打开 if(cmd.equals("打开(O)")){ open(frame); } //放大 if(cmd.equals("放大(M)")){ zoom(frame,true); } //缩小 if(cmd.equals("缩小(O)")){ zoom(frame,false); } //上一个 if(cmd.equals("上一个(X)")){ last(frame); } //下一个 if(cmd.equals("下一个(P)")){ next(frame); } //退出 if(cmd.equals("退出(X)")){ System.exit(0); } }
下面看第二种实现方式,以工具栏上的按钮的事件响应为例。
在看第二种实现方式之前,先用第一种方式来实现:
与菜单实现不同的是,这里创建一个ViewerAction类,在这个类中定义事件监听器。
String[] toolarr = {"open","last","next","big","small"}; for(int i = 0;i<toolarr.length;i++){ ViewerAction action = new ViewerAction( new ImageIcon("img/"+ toolarr[i]+ ".gif", toolarr[i],this) ); //以图标创建一个新的button JButton button = new JButton( action ); //把button加入到工具条中 toolbar.add(button); }
使用了"open","last","next","big","small"等字符串来标识应该使用ViewerService的哪些方法,这意味着我们需要在actionPerformed方法中来做这些判断。
public class ViewerAction extends AbstractAction { String name =null; ViewerFrame frame = null; public ViewerAction(ImageIcon icon, String name, ViewerFrame frame) { super(name,icon); this.name = name; this.frame = frame; } public void actionPerformed(ActionEvent e) { if(this.name.equals("open")){ //在这里调用打开文件对话框的方法 }else if(this.name.equals("last")){ //在这里调用上一张图片的方法 } else if(this.name.equals("next")){ //在这里调用下一张图片的方法 } else if(this.name.equals("big")){ //在这里调用放大图片的方法 } else if(this.name.equals("small")){ //在这里调用缩小图片的方法 } } }
下面使用另一种方法,可以避免这些if… else语句。
先是创建一个Action接口,提供一个execute方法。这一步用到了命令模式:
在创建工具栏上的按钮时,将原来的字符串更换成实现类的全限定名:
String [] toolarr = {"org.crazyit.viewer.OpenAction", "org.crazyit.viewer.LastAction","org.crazyit.viewer.NextAction", "org.crazyit.viewer.BigAction","org.crazyit.viewer.SmallAction"}; String [] toolimg = {"open","last","next","big","small"}; for(int i=0;i<toolarr.length;i++){ //为了美观,想用图片的方式创建JButton ViewerAction action = new ViewerAction(new ImageIcon("images/"+toolimg[i]+".png"),toolarr[i],this); //为工具栏创建按钮 JButton button = new JButton(action); button.setHideActionText(true); //默认情况下对图片的操作按钮不可用 if(i!=0){ button.setEnabled(false); } //将按钮添加到工具栏上 toolBar.add(button); }
那么在构造ViewerAction用这个参数来记录下具体的某个实现类的类名,为ViewerAction编写一个工具方法,使用反射得到某个实现类的一个实例:
private Action getAction(String actionName) { try{ if(this.action ==null){ //创建Action实例 Action action = (Action) Class.forName(actionName).newInstance(); this.action = action; } return this.action; }catch(Exception ex){ return null; }
注意,使用反射来创建的实例是唯一的,在事件监听器中使用该工具方法,返回该实例,并调用该实例的execute()方法。
用这种方法就避免了大量的if…else,也具有一定的扩展性,使用了多态。
如何实现打开图片?
如何放大或者缩小图片?
如何实现浏览“上一张”或“下一张”图片?
首先用ViewerFileChooser对象的showOpenDialog方法弹出一个文件选择框,在用户选择了某个图像文件后,点击确定按钮后,该方法会返回一个值APPROVE_OPTION。
这时需要把这个文件所在的文件夹中的所有图像文件都缓存起来,这样是为了不用每次都去搜索这个文件夹内的文件,也是为了方便“上一张”和“下一张”的定位,缓存的文件都保存在currentFiles中,它的类型是ArrayList<File>。这里还要考虑问题的是,在用户第一次打开某个图片文件后,它所在的文件夹中的文件被缓存到了currentFiles中,在用户第二次打开某个图片文件后,它所在的文件夹可能已经发生了变化,这时需要把新的文件夹中的所有文件缓存到currentFiles中,所以在每次用户打开文件图片时,都要判断文件夹是否改变,如果改变了,要重新进行缓存:
if(cd != this.currentDirectory || this.currentDirectory==null){ //获取fileChooser的所有FileFilter FileFilter [] fileFilters = fileChooser.getChoosableFileFilters(); //获取读取到的文件 File files[] = cd.listFiles(); this.currentFiles = new ArrayList<File>(); //将符合自定义过滤器的文件缓存下来 for(File file : files){ for(FileFilter filter: fileFilters){ //如果是图片文件 if(filter.accept(file)){ //把符合过滤器的文件加到currentFiles中 this.currentFiles.add(file); } } } }
如果文件路径发生了变化,或者这是第一次打开文件,就执行if语句中的代码。
对于图片的放大或者缩小,使用了Image中的一个叫getScaledInstance的方法。
ImageIcon newIcon = new ImageIcon(icon.getImage().getScaledInstance(width, -1, Image.SCALE_DEFAULT));
其中参数width表示将图像缩放到的宽度,height表示将图像缩放到的高度,hints表示指示用于图像重新取样的算法类型的标志,其实就是选择一种缩放算法。
如果width或height为负数,则替换该值以维持初始图像尺寸的高宽比。
对于取上一张或下一张图片的方法就相对容易了,因为在打开文件的时候,已经把文件夹下的文件缓存在了currentFiles中,只需要获得当前文件的索引,然后按照索引加1或索引减1来找到上一张文件或下一张文件。