Java模仿Kugou,实现歌词秀
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(){ @Overridepublic void mouseEntered(MouseEvent e) {closeButton.animateShow();panel.setCursor(Cursor.getPredefinedCursor(Cursor.MOVE_CURSOR));}@Overridepublic 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 blocke.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());}@Overrideprotected 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();}}