实际上,如果百度“字幕遮挡器,很可以找到一些结果,但多半是不透明的,不符合我的使用需要,再者自己写这种小工具是很有趣的学习过程。这个学习过程中有一些心得,不得不记录一二。
程序用途
对字幕进行遮挡(学习英语用)
在网页中对一段文字加上底色,提高阅读时的注意力程度(个人需求)
两种用途如下图所示
功能要求
根据程序的用途可以看出,该程序必须实现以下功能:
半透明窗体
总在最前
大小调整
拖动
关闭
后三个功能实际上都是因为无标题,所以需要自己实现。此外,还有一些锦上添花的功能:
颜色选择(包括不透明度的调节)
切换是否总在最前
记忆颜色与位置
防止窗口缩得过小而无法找到
后来在编程实现的过程中也会按这些功能来描述。
编程实现
语言: Java
程序的功能并不复杂,因此结构上也偷了些懒。一个主类Cover
,其中调用起继承自JFrame
的CoverFrame
。
这里都是一些很定式的写法,没什么特别的
public class Cover {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
CoverFrame frame = new CoverFrame();
...
}
});
}
}
class CoverFrame extends JFrame {
...
}
程序的各项主要功能基本上是在CoverFrame
中实现的,具体将在后文一一叙述。
半透明窗体
半透明窗体的关键是先要去掉窗体的标题和边框,然后给该Frame设置半透明背景色即可。
class CoverFrame extends JFrame {
...
private Color color = new Color(0, 0, 0, 200); //半透明
...
public CoverFrame() {
...
setUndecorated(true); //去掉边框
...
setBackground(color);
...
getContentPane().setBackground(color);
}
}
这部分比较简单,不过值得一说的是,如果窗体将保持半透明,即窗体不会被设置成不透明色的话(因为后面加入了颜色选择,用户完全可能选择完全不透明的颜色),只要对CoverFrame
对象setBackground(color)
即可。
但一旦选择了完全不透明的颜色(Alpha值为255),窗口则会变为默认的灰色。避免这种情况,就需要把Frame的ContentPane也设置成相同顔色getContentPane().setBackground(color)
。
总在最前
想要窗体总在最前也比较简单,有一个现成的函数setAlwaysOnTop
来控制。要让程序能切换是否总在最前,也只要在CoverFrame中设置一个布尔型的state,作为是否总在最前的开关,并添加一个JCheckBoxMenuItem
到窗口的JPopupMenu
中去。
class CoverFrame extends JFrame {
...
private JPopupMenu popupMenu = new JPopupMenu(); //右键菜单
private Color color = new Color(0, 0, 0, 200);
private boolean onTop = true; //默认总在最前
...
public CoverFrame() {
setAlwaysOnTop(onTop);
...
CoverFrame that = this;
// set up popup menus
...
JMenuItem topItem = new JCheckBoxMenuItem("Always On Top", true); //带勾选框,默认勾选
topItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
onTop = !onTop;
setAlwaysOnTop(onTop); //改变当前状态
}
});
popupMenu.add(topItem);
...
}
}
拖动与大小调整
这两个功能本来应该是一个窗体固有的,但因为把标题与边框都去掉了,现在都需要自己来实现。因为两个功能的代码犬牙交错,所以放在一起来讲了。
拖动的原理是,点击时记录点击位置(相对于窗口原点),拖动时获得鼠标在屏幕上的绝对位置,这一位置减去之前记录的相对点击位置,就是新的窗口的位置了,这样就实现了窗口的拖动功能。
大小调整的思路则是判断点击位置是处于窗口的边缘位置,如果是,改变鼠标指针。如果位置处于左侧或者上方,拖动里改变宽/高,并改变窗口位置,如果在右侧或者正文,拖动时改变窗口的宽/高。
class CoverFrame extends JFrame {
private Point point = new Point(0, 0); //用于保存点击位置
...
public CoverFrame() {
...
addMouseListener(new MouseAdapter() { //监听鼠标点击
public void mousePressed(MouseEvent event) {
// record the point where you begin to drag
point = event.getPoint(); //记录点击位置
popupEvent(event); //右键菜单
}
public void mouseReleased(MouseEvent event) {
popupEvent(event);
}
private void popupEvent(MouseEvent event) {
if (event.isPopupTrigger()) {
popupMenu.show(event.getComponent(), event.getX(),
event.getY()); //在右键位置显示菜单
}
}
} );
addMouseMotionListener(new MouseMotionListener() {
// 用来标识点击区域(上下左右)
private boolean top = false;
private boolean down = false;
private boolean left = false;
private boolean right = false;
final private int GAP = 3;
public void mouseMoved(MouseEvent event) {
//窗体的宽高
int width = getWidth();
int height = getHeight();
//点击位置(相对)
int x = event.getX();
int y = event.getY();
top = false;
down = false;
left = false;
right = false;
if (Math.abs(y) <= GAP) {
top = true;
} else if (Math.abs(y-height) <=GAP) {
down = true;
}
if (Math.abs(x) <= GAP) {
left = true;
} else if (Math.abs(x-width) <=GAP) {
right = true;
}
//如果判断在边缘就改变鼠标指针
setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
if (top || down)
setCursor(Cursor.getPredefinedCursor(Cursor.N_RESIZE_CURSOR));
if (left || right)
setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
if ((left && top) || (right && down))
setCursor(Cursor.getPredefinedCursor(Cursor.NW_RESIZE_CURSOR));
if ((right && top) || (left && down))
setCursor(Cursor.getPredefinedCursor(Cursor.NE_RESIZE_CURSOR));
}
public void mouseDragged(MouseEvent event) {
bounds = getBounds();
if (!(top || down || left || right)) {
// 在中间拖动窗口
Point absPoint = event.getLocationOnScreen();
// set the location of window relate to where you click
absPoint.translate(-(int)point.getX(),
-(int)point.getY());
setLocation(absPoint);
} else {
//在四角缩放窗体
if (top) {
bounds.setLocation((int)bounds.getX(), (int)bounds.getY() + event.getY());
bounds.setSize((int)bounds.getWidth(), (int)bounds.getHeight() - event.getY());
}
if (down) {
bounds.setSize((int)bounds.getWidth(), event.getY());
}
if (left) {
bounds.setLocation((int)bounds.getX() + event.getX(), (int)bounds.getY());
bounds.setSize((int)bounds.getWidth() - event.getX(), (int)bounds.getHeight());
}
if (right) {
bounds.setSize(event.getX(),(int)bounds.getHeight());
}
validateBounds();
setBounds(bounds);
}
}
} );
关闭与保存设置
关闭本来其实是没啥说的,就算是没有标题栏,但是因为把颜色和位置信息记录下来,还是有一些要注意的地方。
一般通过按键关闭窗口,会调用dispose
函数,但是这样的话,并不会触发windowClose的事件。要主动发出这一事件才可以。
首先先设置关闭里的默认动作,并监听关闭事件,在保存设置后再退出。程序中设置的保存就很简单的在文本文件中存了几个数字,这里就不细说了。
public class Cover {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
public void run() {
CoverFrame frame = new CoverFrame();
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); //自己处理关闭
frame.setVisible(true);
frame.addWindowListener(new WindowAdapter(){
@Override
public void windowClosing(WindowEvent e) {
super.windowClosing(e);
frame.writeCfg(); // 保存设置
System.out.println("window is closed!");
System.exit(0);
}
});
}
});
}
}
但是问题来了,如果按Alt+F4
关闭,那么设置会被保存,但直接dispose
关闭程序,则不保存。这是因为没有触发关闭的事件。主动把一个关闭事件添加到事件队列中,然后再dispose
就解决了这一问题
class CoverFrame extends JFrame {
...
private JPopupMenu popupMenu = new JPopupMenu();
...
public CoverFrame() {
...
// set up popup menus
JMenuItem closeItem = new JMenuItem("Close");
closeItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
// make sure close event is catched
WindowEvent wev = new WindowEvent(that,
WindowEvent.WINDOW_CLOSING);
Toolkit.getDefaultToolkit().getSystemEventQueue().postEvent(wev); //主动把一个关闭事件添加到事件队列中
dispose(); // close window
}
});
popupMenu.add(closeItem);
...
}
}
颜色修改
这个功能其实也不难,主要就利用了Java中自带的选色窗口。不细说了,给代码片段了。同样要注意的是,同时设置CoverFrame
的底色和ContentPane
的底色。
class CoverFrame extends JFrame {
...
private JPopupMenu popupMenu = new JPopupMenu();
...
public CoverFrame() {
...
JMenuItem pickItem = new JMenuItem("Pick Color");
pickItem.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent event) {
Color newColor;
setAlwaysOnTop(false);
newColor = JColorChooser.showDialog(null, "pick your color",
color);
setAlwaysOnTop(onTop);
if(newColor != null) {
color = newColor;
setBackground(color);
getContentPane().setBackground(color);
repaint();
}
}
});
popupMenu.add(pickItem);
...
}
}
总结
周五花了一个下午写了这个程序,边学边做所以比较慢,又花了不少时间写这些东西,不过越写越感觉,只是一种心得的记录而已,应该没有什么参考价值。
完整的代码放在了
http://git.oschina.net/macuss...
两百多行不太多,思路这里基本介绍了,功能吧基本能用。
本来想把参考的帖子都致敬一下的,不过有点麻烦,不好意思了。反正也是个人日记而已。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。