问题背景:用Swing写一个播放器,要求进度条能够灵活拖动改变当前播放时间。

一、确定用什么组件实现

说到Swing中能显示进度的,我们立马想到了进度条。但是进度条这东西设计的初衷只是给你看进度的,而不具备自由调节的功能。因此马上就pass掉了。

Swing中还有另一个灵活的组件:JSlider,也就是滑动条。这个条只要多思考是可以产生一些妙用的。

二、解决多线程中更新JSlider值冲突问题

我们可以在播放器中用一个线程去setValue,随着播放时间推进,从而使滑动条推进。但是问题来了:另一个线程不断更新滑动条的值时,你会发现你去拖动滑动条是无效的,又回到原来的位置。思考一番后我了解到在拖动过程,另一个线程已经修改了滑动条的值,你拖动的这个值已经失效了。

对此我想了一个觉得比较巧妙的办法,先看一段小代码:

// 拖动播放时间条
timeBar.addMouseListener(new MouseAdapter() {
    @Override
    public void mousePressed(MouseEvent e) {
        isAdjustingTimeBar = true;
    }

    @Override
    public void mouseReleased(MouseEvent e) {
        player.seek((double) timeBar.getValue() / TIME_BAR_MAX);
        isAdjustingTimeBar = false;
    }
});

先看mousePressed,鼠标在滑动条里面按下时,设置isAdjustingTimeBartrue,标志正在调整滑动条;另外,在鼠标松开时,再设置播放进度(seek),然后再把isAdjustingTimeBar置为false

为什么要用isAdjustingTimeBar呢?看了下面的代码就知道了

if (!isAdjustingTimeBar) 
	timeBar.setValue((int) (player.getCurrScale() * TIME_BAR_MAX));

在另一线程,isAdjustingTimeBar可以控制是否去不断更新滑动条。这样就不会因为频繁更新滑动条的值导致用户拖动的值失效了!

补充:JSlider为我们提供了一个getValueIsAdjusting方法让我们判断是否在调整它,当鼠标对它进行任何操作时(点击、拖动),都返回true

上面的代码可以替换为:

// 拖动播放时间条
timeBar.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseReleased(MouseEvent e) {
        player.seek((double) timeBar.getValue() / TIME_BAR_MAX);
    }
});
if (!timeBar.getValueIsAdjusting()) 
	timeBar.setValue((int) (player.getCurrScale() * TIME_BAR_MAX));

三、按住和拖动JSlider,滑块能跳到鼠标所在的位置,并且能够拖动,但不改变实际播放进度

参照QQ音乐的时间条,在按下拖动时,滑块会立马响应到鼠标位置,但是播放进度不会改变,松开鼠标才会调整播放进度

javaswing增加进度条 swing 进度条_ide


但JSlider按下后会一点点往前跳格子,并不会立即到鼠标位置。这是JSlider默认的MouseListener在作怪吗?其实并不是,这归因于JSlider默认UI的TrackListener,它继承于MouseInputAdapter

javaswing增加进度条 swing 进度条_滑动条_02


它使得JSlider点击后会依次向前移动单位长度,看起来确实很丑。

因此我们要自定义SliderUI,写一个SliderUI继承BasicSliderUI,里面有如下的关键代码

@Override
protected TrackListener createTrackListener(JSlider slider) {
    return new TrackListener() {
        @Override
        public void mousePressed(MouseEvent e) {
            slider.setValueIsAdjusting(true);
            slider.setValue(slider.getMaximum() * (e.getX() - trackRect.x) / trackRect.width);
        }

        @Override
        public void mouseDragged(MouseEvent e) {
            slider.setValueIsAdjusting(true);
            slider.setValue(slider.getMaximum() * (e.getX() - trackRect.x) / trackRect.width);
        }
    };
}

createTrackListener这个方法就覆盖了原来的TrackListener,我们通过定义自己的按下(mousePressed)和拖动(mouseDragged)事件让滑块立即响应鼠标操作。注意:setValueIsAdjusting必须设置为true,表示滑块正在被操作。

四、松开鼠标,滑块能跳到鼠标所在位置并改变播放进度

刚刚我们实现了按下拖动时滑块的立即响应,但那只是滑块响应了,怎么让播放器响应呢?其实实现起来也不难,只需要添加MouseListener监听鼠标松开事件(mouseReleased)即可。

请看下面的代码:

timeBar.addMouseListener(new MouseAdapter() {
    @Override
    public void mouseReleased(MouseEvent e) {
        double t = player.getMusicInfo().getDuration() * timeBar.getValue() / TIME_BAR_MAX;
        player.seek(t);
        seekLrc(t);
    }
});

按下拖动后松开鼠标的事件处理音乐播放器和歌词进度改变,就可以轻松调节播放器进度了!

最终我写的效果如下:

javaswing增加进度条 swing 进度条_ide_03

相信你现在已经有思路去实现一个播放条了,加油,代码人!