资讯专栏INFORMATION COLUMN

自制字幕遮挡器

zqhxuyuan / 2767人阅读

摘要:实际上,如果百度字幕遮挡器,很可以找到一些结果,但多半是不透明的,不符合我的使用需要,再者自己写这种小工具是很有趣的学习过程。

实际上,如果百度“字幕遮挡器,很可以找到一些结果,但多半是不透明的,不符合我的使用需要,再者自己写这种小工具是很有趣的学习过程。这个学习过程中有一些心得,不得不记录一二。

程序用途

对字幕进行遮挡(学习英语用)

在网页中对一段文字加上底色,提高阅读时的注意力程度(个人需求)

两种用途如下图所示

功能要求

根据程序的用途可以看出,该程序必须实现以下功能:

半透明窗体

总在最前

大小调整

拖动

关闭

后三个功能实际上都是因为无标题,所以需要自己实现。此外,还有一些锦上添花的功能:

颜色选择(包括不透明度的调节)

切换是否总在最前

记忆颜色与位置

防止窗口缩得过小而无法找到

后来在编程实现的过程中也会按这些功能来描述。

编程实现

语言: Java

程序的功能并不复杂,因此结构上也偷了些懒。一个主类Cover,其中调用起继承自JFrameCoverFrame
这里都是一些很定式的写法,没什么特别的

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...

两百多行不太多,思路这里基本介绍了,功能吧基本能用。

本来想把参考的帖子都致敬一下的,不过有点麻烦,不好意思了。反正也是个人日记而已。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/67335.html

相关文章

  • 前端算法之弹幕设计

    摘要:本文愿从弹幕设计这个场景来描述算法在前端中的应用,我们先来看下实现效果图弹幕效果开场之前我们先来描述弹幕开发的难度,再集中精力描述算法设计的思路。轨道轨道这个角色很重要,它可以解决弹幕位置计算速度控制碰撞检测问题。 大家都说前端写页面较多,几乎用不到算法。本文愿从弹幕设计这个场景来描述算法在前端中的应用,我们先来看下实现效果: showImg(https://segmentfault....

    Bryan 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<