精华帖 (0) :: 良好帖 (9) :: 新手帖 (0) :: 隐藏帖 (3)
|
|
---|---|
作者 | 正文 |
发表时间:2009-03-04
最后修改:2009-03-06
在查看JPopMenu的代码时候突发奇想, 想实现一个环形的弹出菜单,说干就干。 我们都知道, swing 组件的位置和大小是由于layout 管理的,所以想实现环形的弹出菜单就必须实现一个环形的布局管理器。请看我的实现 效果 package info.mikewang.gui.layout; import static java.lang.Math.PI; import static java.lang.Math.round; import static java.lang.Math.sin; import static java.lang.Math.cos; import java.awt.Component; import java.awt.Container; import java.awt.Dimension; import java.awt.LayoutManager; import java.awt.Point; import java.awt.Rectangle; public class CircleLayout implements LayoutManager { int maxCompWidth = 0; // 最大组件宽度 int maxCompHeight = 0; // 最大组件高度 int lc = 0; // 组件数(边的个数,即多边形边的个数) double v = 1.5D; // 缩放因子 double cd = 0; // 组件和组件之间的距离(中心点到中心点) int l; // 外截正方形边长/2 int lw = 0; // 调整过的边长 int lh = 0; @Override public void addLayoutComponent(String name, Component comp) { } @Override public void layoutContainer(Container parent) { synchronized (parent.getTreeLock()) { Dimension d = parent.getPreferredSize(); Point cp = new Point(d.width / 2, d.height / 2); double dx = 0, dy = 0; int cpx = 0, cpy = 0; for (int i = 0; i < lc; i++) { // 下面两行的代码要是看不懂的话,就去翻翻初中2年纪的几何书吧 dx = sin(i * (2 * PI / lc)) * l; dy = -cos(i * (2 * PI / lc)) * l; cpx = (int) (cp.x + dx); cpy = (int) (cp.y + dy); Point cpp = new Point(cpx, cpy); Component comp = parent.getComponent(i); Dimension compSize = comp.getPreferredSize(); comp.setBounds(calcR(cpp, compSize)); } } } @Override public Dimension minimumLayoutSize(Container parent) { lc = parent.getComponentCount(); Component[] components = parent.getComponents(); for (Component component : components) { Dimension d = component.getPreferredSize(); if (d.width > this.maxCompWidth) { this.maxCompWidth = d.width; } if (d.height > this.maxCompHeight) { this.maxCompHeight = d.height; } } calc(); return new Dimension(lw, lh); } @Override public Dimension preferredLayoutSize(Container parent) { return minimumLayoutSize(parent); } @Override public void removeLayoutComponent(Component comp) { } private void calc() { cd = maxCompWidth * v; // 根据正多边形的一条边长,计算出这个多边形外切圆形的外切矩形的边长(近似值) // 使用正弦定理, double x = cd / sin(2 * PI / lc); l = (int) round(x); lh = 2 * l + maxCompHeight; lw = 2 * l + maxCompWidth; } private Rectangle calcR(Point p, Dimension d) { int y = p.y - d.height / 2; int x = p.x - d.width / 2; return new Rectangle(new Point(x, y), d); } } 环形 弹出菜单实现 package info.mikewang.gui.layout; import javax.swing.JPopupMenu; public class CirclePopupMenu extends JPopupMenu { private static final long serialVersionUID = 7318307166817760768L; public CirclePopupMenu() { this.setLayout(new CircleLayout()); } } 有个问题, 顺便在这里问一下: 如何才能把弹出窗口的背景设置成透明呀?? 声明:ITeye文章版权属于作者,受法律保护。没有作者书面许可不得转载。
推荐链接
|
|
返回顶楼 | |
发表时间:2009-03-05
最后修改:2009-03-05
改进一下
进过一天的学习 1 终于将背景搞成了“透明” (其实不是透明, 只是用了屏幕拷贝来实现而已) 2 修正了弹出窗口坐标问题(原来弹出的起点在菜单的左上角, 现在改为圆心) 3 对事件响应区做了修改, 非MenuItem不再响应事件 已知bug 当Item < 3 时候,布局管理器不能正常工作, 因为用到了三角函数,所以必须要在能构成三角形的情况下才能工作 这个bug 明天一定修正 修改的代码点评 package info.mikewang.gui.layout; import java.awt.Component; import java.awt.Dimension; import java.awt.Graphics; import java.awt.Point; import java.awt.Rectangle; import java.awt.Robot; import java.awt.Toolkit; import java.awt.event.ContainerEvent; import java.awt.event.ContainerListener; import java.awt.image.BufferedImage; import javax.swing.JPopupMenu; public class CirclePopupMenu extends JPopupMenu { private static final long serialVersionUID = 7318307166817760768L; boolean compChanged = true; Rectangle[] componentRects; public CirclePopupMenu() { this.setLayout(new CircleLayout()); setBorderPainted(false); // 加个监听器, 防止运行时增加或者减少菜单项影响下面的事件响应区的判断 addContainerListener(new ContainerListener() { @Override public void componentAdded(ContainerEvent e) { compChanged = true; } @Override public void componentRemoved(ContainerEvent e) { compChanged = true; } }); } //判断当前点是否在响应区域 // 实现的原理 // 挖掉除了menuitem外的所有区域 // ( 只有存在item的地方才响应事件 因为菜单本身是“透明”的 ) @Override public boolean contains(int x, int y) { // 如果内部的组件发生了改变(增加或者删除), 则要重新计算事件响应的区域 if (compChanged) { int compSize = getComponentCount(); componentRects = new Rectangle[compSize]; Point menuPoint = getLocationOnScreen(); Component c = null; Point cp = null; Dimension d = null; // 算法其实很简单,就是坐标的平移 for (int i = 0; i < compSize; i++) { c = getComponent(i); cp = c.getLocationOnScreen(); int dx = cp.x - menuPoint.x; int dy = cp.y - menuPoint.y; d = c.getPreferredSize(); Rectangle r = new Rectangle(dx, dy, d.width, d.height); componentRects[i] = r; } compChanged = false; } // 真正的判断在这里 for (Rectangle r : this.componentRects) { if (r.contains(x, y)) { return true; } } return false; } // 覆盖 show 方法, 将弹出的位置修改到圆心 @Override public void show(Component invoker, int x, int y) { setInvoker(invoker); Point invokerOrigin; if (invoker != null) { invokerOrigin = invoker.getLocationOnScreen(); long lx, ly; lx = ((long) invokerOrigin.x) + ((long) x); ly = ((long) invokerOrigin.y) + ((long) y); // ------ // 平移弹出位置到中间(就是圆心处) Dimension myDim = this.getPreferredSize(); lx = lx - myDim.width / 2; ly = ly - myDim.height / 2; // ------ if (lx > Integer.MAX_VALUE) lx = Integer.MAX_VALUE; if (lx < Integer.MIN_VALUE) lx = Integer.MIN_VALUE; if (ly > Integer.MAX_VALUE) ly = Integer.MAX_VALUE; if (ly < Integer.MIN_VALUE) ly = Integer.MIN_VALUE; setLocation((int) lx, (int) ly); } else { setLocation(x, y); } setVisible(true); } @Override public void paintComponent(Graphics g) { // 拷贝屏幕作为弹出菜单的背景(假透明) BufferedImage background = null; try { Robot rbt = new Robot(); Toolkit tk = Toolkit.getDefaultToolkit(); Dimension dim = tk.getScreenSize(); background = rbt.createScreenCapture(new Rectangle(0, 0, (int) dim .getWidth(), (int) dim.getHeight())); } catch (Exception ex) { ex.printStackTrace(); } Point pos = getLocationOnScreen(); Point offset = new Point(-pos.x, -pos.y); g.drawImage(background, offset.x, offset.y, null); } } 其他的代码都改的不多, 在附件中,请下载。 谢谢! |
|
返回顶楼 | |
发表时间:2009-03-06
最后修改:2009-03-06
再次改进
1, 修复事件区域的判断的bug 2, 现在 当item < 3 时, 布局管理器可以正常工作了! 代码修改的很少, 基本都有注释, 有兴趣的朋友可以看一下。 |
|
返回顶楼 | |
发表时间:2009-03-08
不错啊,正要学习 swing,就是不知道从何入手
|
|
返回顶楼 | |
发表时间:2009-03-08
不错 ,最近也弄swing了 不过一直停留在ctrl+c和ctrl+v的水平
|
|
返回顶楼 | |
发表时间:2009-03-30
这个是不是也适合 其他组件的布局呢?
|
|
返回顶楼 | |
发表时间:2009-03-30
gml520 写道 这个是不是也适合 其他组件的布局呢?
当然可以 图中的5个按钮就是使用的原型布局。 setLayout(new CircleLayout()); add(new JButton("Button1")); add(new JButton("Button2")); add(new JButton("Button3")); add(new JButton("Button4")); add(new JButton("Button5")); |
|
返回顶楼 | |
发表时间:2009-03-31
不错不错 真是好想法
|
|
返回顶楼 | |
发表时间:2009-03-31
mikewang 写道 gml520 写道 这个是不是也适合 其他组件的布局呢?
当然可以 图中的5个按钮就是使用的原型布局。 setLayout(new CircleLayout()); add(new JButton("Button1")); add(new JButton("Button2")); add(new JButton("Button3")); add(new JButton("Button4")); add(new JButton("Button5")); 不错,相当的好啊,有了这个布局,有很多东西都可以用的上了。 太感谢你了。 |
|
返回顶楼 | |
发表时间:2009-04-17
我执行TestW的结果为什么是这个?
只有弹出的菜单全部在java窗口内,才正常。 |
|
返回顶楼 | |