Kugou的歌词秀如图:
我模拟的效果如图所示.
鼠标选中后如图:
歌词秀有以下细节注意点:
1、没有“窗口”,直接在桌面上绘制歌词
2、歌词文字是彩色的,且颜色渐变。已唱歌词与未唱歌词的渐变色不同。歌词、、文字有黑色边框,以便于周围背景清晰区分
3、歌词可拖动,当鼠标移上去时会变成可拖动的形状
用Java实现,有以下技术点:
1、透明窗口
这个需要借助JNA来实现,通过
System.setProperty("sun.java2d.noddraw", "true");
WindowUtils.setWindowTransparent(this,true);
使得窗口透明
2、渐变的彩色文字,使用GradientPaint填充一个BufferedImage,BufferedImage的渐变色即为歌词的渐变色。然后取得歌词的形状,
用此BufferedImage填充即可。比较麻烦的是文字的黑色边框,这个最后想了一个办法就是分别向上下左右偏移一个像素绘制
黑色的歌词,然后在其上绘制正常的彩色渐变歌词,这样最终的叠加相关就正好是我们需要的效果。
import java.awt.AlphaComposite; import java.awt.Color; import java.awt.Cursor; import java.awt.Font; import java.awt.FontMetrics; import java.awt.GradientPaint; import java.awt.Graphics; import java.awt.Graphics2D; import java.awt.RenderingHints; import java.awt.TexturePaint; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.font.FontRenderContext; import java.awt.font.TextLayout; import java.awt.geom.Rectangle2D; import java.awt.geom.RoundRectangle2D; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileReader; import java.io.InputStreamReader; import java.io.LineNumberReader; import java.util.LinkedList; import java.util.List; import javax.swing.*; import com.sun.jna.Native; import com.sun.jna.Pointer; import com.sun.jna.examples.WindowUtils; import com.sun.jna.examples.win32.User32; import com.sun.jna.examples.win32.W32API; import com.sun.jna.ptr.IntByReference; /** * 卡拉OK歌词效果,模仿Kugou桌面上的歌词秀 * @author 李涛 * @version v1.0 2011-09-11 * * 歌词秀有以下细节注意点: * 1、没有“窗口”,直接在桌面上绘制歌词 * 2、歌词文字是彩色的,且颜色渐变。已唱歌词与未唱歌词的渐变色不同。歌词、、文字有黑色边框,以便于周围背景清晰区分 * 3、歌词可拖动,当鼠标移上去时会变成可拖动的形状 * * 用Java实现,有以下技术点: * 1、透明窗口 * 这个需要借助JNA来实现,通过 * System.setProperty("sun.java2d.noddraw", "true"); * WindowUtils.setWindowTransparent(this,true); * 使得窗口透明 * * 2、渐变的彩色文字,使用GradientPaint填充一个BufferedImage,BufferedImage的渐变色即为歌词的渐变色。然后取得歌词的形状, * 用此BufferedImage填充即可。比较麻烦的是文字的黑色边框,这个最后想了一个办法就是分别向上下左右偏移一个像素绘制 * 黑色的歌词,然后在其上绘制正常的彩色渐变歌词,这样最终的叠加相关就正好是我们需要的效果。 */ public class LyncWin extends JDialog { /** * */ private static final long serialVersionUID = 1L; private JLabel infoLabel; private MyCloseButton closeButton; static List<String> msgList = new LinkedList<String>(); public LyncWin() { setTitle("卡拉OK歌词Demo"); setBounds(300, 200, 800, 110); final ContentPane panel = new ContentPane(); this.setContentPane(panel); MyMouseListener m = new MyMouseListener(this, panel); panel.addMouseListener(m); panel.addMouseMotionListener(m); getContentPane().setLayout(null); this.getRootPane().setOpaque(false); closeButton = new MyCloseButton(this); closeButton.setOpaque(false); closeButton.setBounds(0, 0, 18, 18); closeButton.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e) { System.exit(0); }}); closeButton.setVisible(false); add(closeButton); setResizable(false); this.setUndecorated(true); setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE); //com.sun.awt.AWTUtilities.setWindowOpacity(this, 0.93f); //com.sun.awt.AWTUtilities.setWindowShape(this, new Ellipse2D.Double(0, 0, getWidth(),getHeight())); this.setAlwaysOnTop(true); initMsg(); new Timer(30, new ActionListener() { public void actionPerformed(ActionEvent e) { LyncWin.this.repaint();//infoLabel.setText(msgList.get(i++)); } }).start(); panel.addMouseListener(new MouseAdapter(){ @Override public void mouseEntered(MouseEvent e) { closeButton.animateShow(); panel.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR)); } @Override public void mouseExited(MouseEvent e) { closeButton.animateHide(); }}); System.setProperty("sun.java2d.noddraw", "true"); WindowUtils.setWindowTransparent(this,true); this.setVisible(true); } // private void makeWinTransparent() { // Pointer winPointer = Native.getComponentPointer(this); // W32API.HWND hwnd = new W32API.HWND(); // hwnd.setPointer(winPointer); // IntByReference color = new IntByReference(this.getBackground().getRGB()); // // User32.INSTANCE.SetWindowLong(hwnd, User32.GWL_EXSTYLE, User32.INSTANCE.GetWindowLong(hwnd, User32.GWL_EXSTYLE)|User32.WS_EX_LAYERED); // User32.INSTANCE.SetLayeredWindowAttributes(hwnd, this.getBackground().getRGB(), (byte)220, User32.LWA_COLORKEY); // this.setVisible(false); // this.setVisible(true); // } void initMsg(){ try { LineNumberReader lnr = new LineNumberReader(new InputStreamReader(Class.class.getResourceAsStream("/Msg.ini"))); while(lnr.ready()){ msgList.add(lnr.readLine()); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } class ContentPane extends JPanel{ private static final long serialVersionUID = 1L; int i = 0; //哪一行歌曲文字 int length = 0; //本行文字的宽度 String msg = null; BufferedImage bufferedImage; //已唱文字的渐变色彩 private Color gradientStart = new Color(238,254,218); private Color gradientCenter = new Color(153,254,17); private Color gradientEnd = new Color(232,254,3); //未唱文字的渐变色彩 private Color gradientEndU = new Color(14,104,0); private Color gradientStartU = new Color(134,242,32); public ContentPane(){ setFont(new Font("黑体",Font.BOLD,40)); this.setOpaque(false); this.setForeground(this.getBackground()); } @Override protected void paintComponent(Graphics g){ if(msg ==null){ msg = msgList.get(0); } Graphics2D g2 = (Graphics2D)g.create(); RenderingHints hints = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); hints.put(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR); hints.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_QUALITY); g2.setRenderingHints(hints); g2.setColor(new Color(0,0,0)); g2.setFont(getFont()); FontMetrics fm = getFontMetrics(getFont()); Rectangle2D rect = fm.getStringBounds(msg, g2); if(length>rect.getWidth()){ length = 0; if(i>=msgList.size()){ i=0; msg = msgList.get(i++); fm = getFontMetrics(getFont()); rect = fm.getStringBounds(msg, g2); }else{ msg = msgList.get(++i); fm = getFontMetrics(getFont()); rect = fm.getStringBounds(msg, g2); } } int x = 0; int y = 48; //当关闭按钮可见时,说明鼠标移上来了,此时绘制一个半透明的底纹,以便于用户操作(否则只有当鼠标在文字轮廓 //上时才能收到鼠标事件 if(closeButton.isVisible()){ Graphics2D newg = (Graphics2D)g.create(); newg.setColor(Color.gray); newg.setComposite(AlphaComposite.SrcOver.derive((float)(closeButton.alpha*0.5))); newg.fillRoundRect(x,y-((int)rect.getHeight()-fm.getDescent()*3),(int)rect.getWidth(),(int)rect.getHeight(),10,10); newg.dispose(); } //上下左右各偏离1个像素绘制黑色歌词,经过后面的彩色歌词覆盖后,即变成文字的黑色轮廓 g2.drawString(msg, x-1, y); g2.drawString(msg, x+1, y); g2.drawString(msg, x, y+1); g2.drawString(msg, x, y-1); //绘制渐变彩色歌词 createBufferedImage(fm, rect,length++); TexturePaint tp = new TexturePaint(bufferedImage, rect); FontRenderContext frc = g2.getFontRenderContext(); TextLayout tl = new TextLayout(msg, getFont(), frc); g2.setPaint(tp); g2.translate(0, y); g2.fill(tl.getOutline(null)); g2.translate(0, -y); //g2.drawImage(bufferedImage, null, x, y+14); g2.dispose(); } //采用渐变色绘制文字 protected void createBufferedImage(FontMetrics fm, Rectangle2D rectStr,int length) { int width = (int)rectStr.getWidth(); int height = (int)rectStr.getHeight()-fm.getDescent()*2; bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); //绘制已唱的文字 Graphics2D g2 = bufferedImage.createGraphics(); GradientPaint painter = new GradientPaint(0, 0, gradientStart, 0, height / 2, gradientCenter); g2.setPaint(painter); Rectangle2D rect = new Rectangle2D.Double(0, 0, length, height / 2.0); g2.fill(rect); painter = new GradientPaint(0, height / 2, gradientCenter, 0,height, gradientEnd); g2.setPaint(painter); rect = new Rectangle2D.Double(0, height / 2.0 , length,height); g2.fill(rect); painter = new GradientPaint(0, height / 2-2, gradientCenter, 0,height, gradientCenter); g2.setPaint(painter); rect = new Rectangle2D.Double(0, (height / 2.0)-2 , length, 4); g2.fill(rect); //绘制未唱的文字 painter = new GradientPaint(0, 0, gradientStartU, 0, height, gradientEndU); g2.setPaint(painter); rect = new Rectangle2D.Double(length, 0, width-length, height); g2.fill(rect); g2.dispose(); } } public static void main(String[] args){ try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { e.printStackTrace(); } new LyncWin(); } }