我们大多数的时候,都在抱怨Swing界面简陋,其实说这话的大多数和我一样都是一个Swing的新手,或者说是一个桌面开发的新手,诸如瑞星那样的应用其实可见也没几个是原生态的Windows的桌面控件,先不谈我们可以选择look&feel来改变我们的主题,甚至实现office2007的那样的效果,Swing本身的MVC模式就为我们提供了无限的可能;在上一篇文章中我们实现了按钮的改头换面,其实那种方式是一种强耦合的界面改换,在本文中我们将利用Swing本身所提倡的方式来实现控件的美化;
--------------------------------------------------------------------------------
Swing的千千静听
下图是一个java Swing版本的千千静听风格的播放器,在里面我们已经见不到Swing的影子(如图一);
图一.绚丽的Swing播放器
--------------------------------------------------------------------------------
打造绚丽的Slider
首先我们必须在基础的BasicUI上扩展出自己的UI界面,代码清单如下:
view plaincopy to clipboardprint?
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.hadeslee.yoyoplayer.util;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JSlider;
import javax.swing.plaf.basic.BasicSliderUI;
/**
*
* @author hadeslee
*/
public class YOYOSliderUI extends BasicSliderUI {
//以下代码分别表示用于指示的图标的三种状态的图片
private Image thumbImage = null;//普通按的图片
private Image thumbOverImage;//鼠标在上面的图片
private Image thumbPressedImage = null;//按下面的图片
//分别是已经激活了的bar的背景和没有激活的bar的背景
private Image backgroundImage = null;//普通背景图片
private Image activeBackImage;//激活的背景图片
//一些设置变量
private JSlider parentSlider = null;
private Dimension thumbDim = null;
private int newThumbHeight = -1;
private int thumbXOffset = 0;
private int thumbYOffset = 0;
private boolean hideThumb = false;
public YOYOSliderUI(JSlider slider) {
super(slider);
//保存了solider的引用;
parentSlider = slider;
}
public void setThumbImage(Image img) {
thumbImage = img;
thumbDim = new Dimension(thumbImage.getWidth(null), thumbImage.getHeight(null));
}
public void setThumbPressedImage(Image img) {
thumbPressedImage = img;
}
public void setActiveBackImage(Image activeBackImage) {
this.activeBackImage = activeBackImage;
}
public void setThumbOverImage(Image thumbOverImage) {
this.thumbOverImage = thumbOverImage;
}
protected Dimension getThumbSize() {
return thumbDim;
}
public void forceThumbHeight(int h) {
newThumbHeight = h;
}
public void setThumbXOffset(int x) {
thumbXOffset = x;
}
public void setThumbYOffset(int y) {
thumbYOffset = y;
}
public void setHideThumb(boolean hide) {
hideThumb = hide;
}
public void setBackgroundImages(Image img) {
backgroundImage = img;
}
public void paintFocus(Graphics g) {
}
//在这里画出bar上面的指针
public void paintThumb(Graphics g) {
//如果隐藏指针,则不用画了
if (hideThumb == true) {
return;
}
Image img = thumbImage;
if (img != null) {
if (thumbPressedImage != null) {
//判断出正在拖动中,按钮现实获取焦点的显示状态
if (parentSlider.getValueIsAdjusting()) {
img = thumbPressedImage;
}
}
if (newThumbHeight >= 0) {
//根据solider的引用判断出bar的方向;
if (parentSlider.getOrientation() == JSlider.HORIZONTAL) {
//thumbRect.x可以得到当前的指针被鼠标拖放在哪个位置,由基类实现
//thumnXoffset则是一个浮动值,你可以用来微调
//(parentSlider.getHeight() - img.getHeight(slider)) / 2是由
//y+img.getHeight/2=parentSlider.getHeight/2的公式而来,使得bar和指针的中线重合
//上面动态的确定了指针的位置,每次指针移动都会重新刷新页面
//width和height是指针img本身的高度和宽度就可以了
g.drawImage(img, thumbRect.x + thumbXOffset, (parentSlider.getHeight() - img.getHeight(slider)) / 2, img.getWidth(null), newThumbHeight, null);
} else {
g.drawImage(img, (parentSlider.getWidth() - img.getWidth(slider)) / 2, thumbRect.y + thumbYOffset, img.getWidth(null), newThumbHeight, null);
}
} else {
if (parentSlider.getOrientation() == JSlider.HORIZONTAL) {
g.drawImage(img, thumbRect.x + thumbXOffset, (parentSlider.getHeight() - img.getHeight(slider)) / 2, img.getWidth(null), img.getHeight(null), null);
} else {
g.drawImage(img, (parentSlider.getWidth() - img.getWidth(slider)) / 2, thumbRect.y + thumbYOffset, img.getWidth(null), img.getHeight(null), null);
}
}
}
}
//下面的方法主要是用来绘制标志的
public void paintTrack(Graphics g) {
//获得引用的solider的高度和宽度
int width = parentSlider.getWidth();
int height = parentSlider.getHeight();
//得到标尺的宽度
int all = parentSlider.getMaximum() - parentSlider.getMinimum();
int value = parentSlider.getValue();
//绘制没有被激活的部分显示的背景图片
if (backgroundImage != null) {
//判断方向
if (parentSlider.getOrientation() == JSlider.HORIZONTAL) {
//把整个图片画出来就可以了
g.drawImage(backgroundImage, 0, (height - backgroundImage.getHeight(slider)) / 2, parentSlider);
} else {
g.drawImage(backgroundImage, (width - backgroundImage.getWidth(parentSlider)) / 2, 0, parentSlider);
}
}
if (activeBackImage != null) {
//用来定位已经激活的那部分的图片,这里最关键的是在于如何判断已经激活了的值
//在标尺上面的定位width * value / all
if (parentSlider.getOrientation() == JSlider.HORIZONTAL) {
g.drawImage(activeBackImage, 0, (height - activeBackImage.getHeight(slider)) / 2, width * value / all, activeBackImage.getHeight(slider), 0, 0, width * value / all, activeBackImage.getHeight(slider), slider);
} else {
int sx = (width - activeBackImage.getWidth(parentSlider)) / 2;
g.drawImage(activeBackImage, sx, height - (height * value / all), sx + activeBackImage.getWidth(slider), height, 0, height - (height * value / all), activeBackImage.getWidth(slider), height, slider);
}
}
}
public void setThumbLocation(int x, int y) {
super.setThumbLocation(x, y);
parentSlider.repaint();
}
}
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package com.hadeslee.yoyoplayer.util;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Image;
import javax.swing.JSlider;
import javax.swing.plaf.basic.BasicSliderUI;
/**
*
* @author hadeslee
*/
public class YOYOSliderUI extends BasicSliderUI {
//以下代码分别表示用于指示的图标的三种状态的图片
private Image thumbImage = null;//普通按的图片
private Image thumbOverImage;//鼠标在上面的图片
private Image thumbPressedImage = null;//按下面的图片
//分别是已经激活了的bar的背景和没有激活的bar的背景
private Image backgroundImage = null;//普通背景图片
private Image activeBackImage;//激活的背景图片
//一些设置变量
private JSlider parentSlider = null;
private Dimension thumbDim = null;
private int newThumbHeight = -1;
private int thumbXOffset = 0;
private int thumbYOffset = 0;
private boolean hideThumb = false;
public YOYOSliderUI(JSlider slider) {
super(slider);
//保存了solider的引用;
parentSlider = slider;
}
public void setThumbImage(Image img) {
thumbImage = img;
thumbDim = new Dimension(thumbImage.getWidth(null), thumbImage.getHeight(null));
}
public void setThumbPressedImage(Image img) {
thumbPressedImage = img;
}
public void setActiveBackImage(Image activeBackImage) {
this.activeBackImage = activeBackImage;
}
public void setThumbOverImage(Image thumbOverImage) {
this.thumbOverImage = thumbOverImage;
}
protected Dimension getThumbSize() {
return thumbDim;
}
public void forceThumbHeight(int h) {
newThumbHeight = h;
}
public void setThumbXOffset(int x) {
thumbXOffset = x;
}
public void setThumbYOffset(int y) {
thumbYOffset = y;
}
public void setHideThumb(boolean hide) {
hideThumb = hide;
}
public void setBackgroundImages(Image img) {
backgroundImage = img;
}
public void paintFocus(Graphics g) {
}
//在这里画出bar上面的指针
public void paintThumb(Graphics g) {
//如果隐藏指针,则不用画了
if (hideThumb == true) {
return;
}
Image img = thumbImage;
if (img != null) {
if (thumbPressedImage != null) {
//判断出正在拖动中,按钮现实获取焦点的显示状态
if (parentSlider.getValueIsAdjusting()) {
img = thumbPressedImage;
}
}
if (newThumbHeight >= 0) {
//根据solider的引用判断出bar的方向;
if (parentSlider.getOrientation() == JSlider.HORIZONTAL) {
//thumbRect.x可以得到当前的指针被鼠标拖放在哪个位置,由基类实现
//thumnXoffset则是一个浮动值,你可以用来微调
//(parentSlider.getHeight() - img.getHeight(slider)) / 2是由
//y+img.getHeight/2=parentSlider.getHeight/2的公式而来,使得bar和指针的中线重合
//上面动态的确定了指针的位置,每次指针移动都会重新刷新页面
//width和height是指针img本身的高度和宽度就可以了
g.drawImage(img, thumbRect.x + thumbXOffset, (parentSlider.getHeight() - img.getHeight(slider)) / 2, img.getWidth(null), newThumbHeight, null);
} else {
g.drawImage(img, (parentSlider.getWidth() - img.getWidth(slider)) / 2, thumbRect.y + thumbYOffset, img.getWidth(null), newThumbHeight, null);
}
} else {
if (parentSlider.getOrientation() == JSlider.HORIZONTAL) {
g.drawImage(img, thumbRect.x + thumbXOffset, (parentSlider.getHeight() - img.getHeight(slider)) / 2, img.getWidth(null), img.getHeight(null), null);
} else {
g.drawImage(img, (parentSlider.getWidth() - img.getWidth(slider)) / 2, thumbRect.y + thumbYOffset, img.getWidth(null), img.getHeight(null), null);
}
}
}
}
//下面的方法主要是用来绘制标志的
public void paintTrack(Graphics g) {
//获得引用的solider的高度和宽度
int width = parentSlider.getWidth();
int height = parentSlider.getHeight();
//得到标尺的宽度
int all = parentSlider.getMaximum() - parentSlider.getMinimum();
int value = parentSlider.getValue();
//绘制没有被激活的部分显示的背景图片
if (backgroundImage != null) {
//判断方向
if (parentSlider.getOrientation() == JSlider.HORIZONTAL) {
//把整个图片画出来就可以了
g.drawImage(backgroundImage, 0, (height - backgroundImage.getHeight(slider)) / 2, parentSlider);
} else {
g.drawImage(backgroundImage, (width - backgroundImage.getWidth(parentSlider)) / 2, 0, parentSlider);
}
}
if (activeBackImage != null) {
//用来定位已经激活的那部分的图片,这里最关键的是在于如何判断已经激活了的值
//在标尺上面的定位width * value / all
if (parentSlider.getOrientation() == JSlider.HORIZONTAL) {
g.drawImage(activeBackImage, 0, (height - activeBackImage.getHeight(slider)) / 2, width * value / all, activeBackImage.getHeight(slider), 0, 0, width * value / all, activeBackImage.getHeight(slider), slider);
} else {
int sx = (width - activeBackImage.getWidth(parentSlider)) / 2;
g.drawImage(activeBackImage, sx, height - (height * value / all), sx + activeBackImage.getWidth(slider), height, 0, height - (height * value / all), activeBackImage.getWidth(slider), height, slider);
}
}
}
public void setThumbLocation(int x, int y) {
super.setThumbLocation(x, y);
parentSlider.repaint();
}
}
我们首先必须保证我们的激活的背景图片和没有被激活的背景图片是一样长的:如下图2,图3,
图2,没有激活的声音
图3,激活了的声音
然后就是运用graphics一个重要的截取图片的方法,吧激活了声音的部分截取下来;
view plaincopy to clipboardprint?
public abstract boolean drawImage(Image img,
int dx1, int dy1, int dx2, int dy2,
int sx1, int sy1, int sx2, int sy2,
ImageObserver observer);
public abstract boolean drawImage(Image img,
int dx1, int dy1, int dx2, int dy2,
int sx1, int sy1, int sx2, int sy2,
ImageObserver observer);
代码清单如下:
view plaincopy to clipboardprint?
g.drawImage(activeBackImage, 0, (height - activeBackImage.getHeight(slider)) / 2, width * value / all, activeBackImage.getHeight(slider), 0, 0, width * value / all, activeBackImage.getHeight(slider), slider);
g.drawImage(activeBackImage, 0, (height - activeBackImage.getHeight(slider)) / 2, width * value / all, activeBackImage.getHeight(slider), 0, 0, width * value / all, activeBackImage.getHeight(slider), slider);
可以继承一个自己的JSolider,如下:
view plaincopy to clipboardprint?
package com.hadeslee.yoyoplayer.util;
import javax.swing.JSlider;
public class YOYOSlider extends JSlider {
private static final long serialVersionUID = 20071214L;
public YOYOSlider() {
super();
setDoubleBuffered(true);
}
public boolean isRequestFocusEnabled() {
setValueIsAdjusting(true);
repaint();
return super.isRequestFocusEnabled();
}
public void setHideThumb(boolean hide) {
((YOYOSliderUI) getUI()).setHideThumb(hide);
}
}
package com.hadeslee.yoyoplayer.util;
import javax.swing.JSlider;
public class YOYOSlider extends JSlider {
private static final long serialVersionUID = 20071214L;
public YOYOSlider() {
super();
setDoubleBuffered(true);
}
public boolean isRequestFocusEnabled() {
setValueIsAdjusting(true);
repaint();
return super.isRequestFocusEnabled();
}
public void setHideThumb(boolean hide) {
((YOYOSliderUI) getUI()).setHideThumb(hide);
}
}
然后将UI和控件结合起来,如下:
view plaincopy to clipboardprint?
YOYOSlider yoyo = new YOYOSlider();
YOYOSliderUI ui = new YOYOSliderUI(yoyo);
yoyo.setOpaque(false);
yoyo.setMaximum(max);
yoyo.setMinimum(min);
yoyo.setValue(value);
yoyo.setOrientation(orientation);
ui.setThumbImage(ball1);
ui.setThumbOverImage(ball2);
ui.setThumbPressedImage(ball3);
ui.setBackgroundImages(bg1);
ui.setActiveBackImage(bg2);
yoyo.setUI(ui);
yoyo.addChangeListener(listener);
return yoyo;
YOYOSlider yoyo = new YOYOSlider();
YOYOSliderUI ui = new YOYOSliderUI(yoyo);
yoyo.setOpaque(false);
yoyo.setMaximum(max);
yoyo.setMinimum(min);
yoyo.setValue(value);
yoyo.setOrientation(orientation);
ui.setThumbImage(ball1);
ui.setThumbOverImage(ball2);
ui.setThumbPressedImage(ball3);
ui.setBackgroundImages(bg1);
ui.setActiveBackImage(bg2);
yoyo.setUI(ui);
yoyo.addChangeListener(listener);
return yoyo;
这个时候我们就得到了一个绚丽的solider;从下图4中我们可以清晰的看见激活和未激活部分的区别,高亮了激活的部分;
图4,高亮的激活部分:
--------------------------------------------------------------------------------
备注
1)首先图片的背景必须是透明的,这样才能只显示有效的空间部分(以上图片的时候背景是黑色的,其实在ps下面是透明的),格式是支持透明的png格式;
2)在设置控件大小的时候必须用
view plaincopy to clipboardprint?
pos.setBounds(10, 108, 270, 15);
volume.setBounds(43, 130, 82, 15);
pan.setBounds(160, 72, 90, 13);
this.add(pos);
this.add(volume);
this.add(pan);
pos.setBounds(10, 108, 270, 15);
volume.setBounds(43, 130, 82, 15);
pan.setBounds(160, 72, 90, 13);
this.add(pos);
this.add(volume);
this.add(pan);
反复调整好大小,不然会出现真实的solider和我们的图片的大小不相符,导致指针溢出等现象;