问题背景:用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
,鼠标在滑动条里面按下时,设置isAdjustingTimeBar
为true
,标志正在调整滑动条;另外,在鼠标松开时,再设置播放进度(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音乐的时间条,在按下和拖动时,滑块会立马响应到鼠标位置,但是播放进度不会改变,松开鼠标才会调整播放进度
但JSlider按下后会一点点往前跳格子,并不会立即到鼠标位置。这是JSlider默认的MouseListener在作怪吗?其实并不是,这归因于JSlider默认UI的TrackListener
,它继承于MouseInputAdapter
它使得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);
}
});
按下和拖动后松开鼠标的事件处理音乐播放器和歌词进度改变,就可以轻松调节播放器进度了!
最终我写的效果如下:
相信你现在已经有思路去实现一个播放条了,加油,代码人!