精华内容
下载资源
问答
  • AsciiPanel, 使用代码页 437显示为ascii终端的java控件 AsciiPanel AsciiPanel模拟代码页 437 ASCII终端显示。 它支持代码页 437.任意前景颜色。任意背景颜色和任意终端大小的256个字符。默认终端大小为 80 x24字符...
  • 现在卡顿解决了,又出现了另一个问题,运行时,因为panel控件背景色设置的透明,发现它显示背景色为窗体背景色,而不是pictureBox里面的图片。 就这个问题想了好久,现在找到办法了。将panel下面的控件设置...

    之前为了图省事儿,直接给Form窗体设置的背景图片,发现这样运行的时候窗体特别的卡顿,于是改为放一个pictureBox控件,由这个控件加载图片后作为背景。

    现在卡顿解决了,又出现了另一个问题,运行时,因为panel控件背景色设置的透明,发现它显示的背景色为窗体背景色,而不是pictureBox里面的图片。

    就这个问题想了好久,现在找到办法了。将panel下面的控件设置Parent 即可。

    this.panel1.Parent = this.picturebox1;

    http://www.cnblogs.com/chengxiaohui/articles/1921608.html 博主:fumen

    https://www.cnblogs.com/wenqiang1266/p/7479589.html

    在这里插入图片描述

    展开全文
  • Java AWT/Swing实现规则窗体和控件

    千次阅读 多人点赞 2019-05-09 05:27:28
    终于重写这个话题了。 缘由 2003年是我写Java的第一年,2004年是我写Java的第二年。 由于是自学,又是大专,没有科班的基础,所以不是很care算法和数据结构,因为Java可以快速作出一...如何用Java实现一个规则...

    Oracle裁员,关注了下Oracle,但依旧并不喜欢这家公司,一直觉得它经营不好Java。就好像Microsoft经营不好Linux一样(很奇怪,但总觉得会有这么一天!) 不写Java已经十余年了,但最近还是想起一些点滴。花了一个半的夜晚,完成了此文。

    本文献给为我们送雨伞而把皮鞋?湿了的猛人。


    终于有机会重写这个话题了。我承诺读完本文,对Java如何实现不规则控件将会有非常清晰的认知。

    什么是自省?作为一个做技术的工程师,在以往学习和工作的过程中肯定曾经遇到过超级多的问题,其中有一些是棘手的,很难解决的,最终不了了之的问题。过了些时日以后,重新审视,看看以往的这些疑难问题现在是不是可以解决了,这就是一种自省的态度。即便是对于已经解决的问题,以及已经掌握的理论,重新审视看有没有更优的解法,有没有新的思想,这很必要。

    缘由

    2003年是我接触Java的第一年,2004年是我接触Java的第二年。

    由于是自学Java,又是大专,没有科班的基础,所以最初不是很care算法和数据结构,因为Java可以快速作出一个肉眼可以看到的GUI,所以我选择了Java而不是C/C++,同时由于MFC这些和微软的系统强相关,也就是说,同时放弃了VC++。

    但是直到2006年我都被一个问题所困扰,即:
    如何用Java实现一个不规则的窗体或者控件?

    之所以有这个问题,是因为我那时觉得像豪杰超级解霸这种播放器的 换肤 功能非常牛逼,比较有趣,如果说做GUI,当然不能少了这个。让人惊奇的是,一个播放器界面竟然可以是一个如下般非规则的样式:
    在这里插入图片描述
    这太帅了!我也想用Java做。

    但是Java的界面只能是那种四四方方的样子, 如何能把这种四四方方的界面裁剪成类似上面的样子 ,这是我进入这个这个行业以来 第一个要解决的问题。

    我当时使用的Java SDK版本是J2sdk 1.4.2版本,经典版本。

    2006年12月Java 1.6发布之前,网上根本搜不到答案,我自己也是有时间就折腾,几乎把Swing/AWT的API都撸遍了,也没有结果。最终,我是通过Eclipse的SWT解决了问题。

    Eclipse的SWT让很多有同样需求的人眼前一亮。我的第一份工作就是基于SWT开发的。

    是的Eclipse SWT有可以切割Java窗体和控件的API,具体例子我就不说了,网上一搜一大片。问题是,我想用标准的J2SE来做这些啊!

    后来,我是通过JNI的方式,调用自己写的本地C/C++库来完成切割窗体的。我也是因为要写这个本地C代码,从而 顺便 开始学习C语言编程。

    这种功能用本地代码写是很容易的,比如在微软的Windows系统,就可以用Windows API来做,而在Linux系统,则可以用基于X11的Gnome,KDE来完成,毕竟本地的GUI库必然要支持这些功能。

    然而Java作为其承诺的 一次编写,到处运行 的跨平台语言,不可能用同一个接口去兼容不同操作系统平台的不同GUI效果,所以Java也就根本就没有提供这样的功能。

    Java的公共API只能完成所有操作系统平台都支持的操作,并且还要保证达到同样的效果,至少不能差太多。我们知道Windows和UNIX/Linux的X11完全就不是一个东西,甚至都不是一类东西…

    Java实现不规则控件这件事

    这里还需要再PS一下。

    关于Java实现不规则控件这件事,很多人纠结过,实际上Java作为承诺的跨平台语言,它的优势根本就不在开发GUI程序,GUI这种是严重依赖本地系统的。

    虽然Eclipse的SWT曾经给人眼前一亮的的感觉,但它依然不是万金油。

    SWT不需要显式的进行JNI操作,不需要引入额外的包,处在集成开发环境Eclipse本身,直接拿来就能用。但是它只是将JNI隐藏了。说白了,SWT之所以可以做出和本地系统几乎一样的GUI控件,它所做的就是 将Win32 API,X11 API这种本地API封装成了Java的API ,是的,说白了就是一层封装,没什么大不了的。

    在Java SE 1.6之前,Java AWT/Swing要想做不规则控件,需要你自己去折腾JNI,而SWT替你把这一切封装好了,这才是它的唯一优势。

    这不,Java SE 1.6出来后,SWT的优势就不在了。因为它在Java内部做了同样的事情。


    使用Java SE 1.6如何实现不规则窗口

    到了2006年12月份,Sun放出了Java SE 1.6,带来了福音!

    Java SE 1.6提供的 com.sun.awt.AWTUtilities 和Eclipse SWT一样,封装了JNI操作,让程序员可以直接使用,就像不知道JNI这回事一样。

    在这之后,当我们再次搜索 如何用Java实现不规则窗体 这个话题时,答案就有了,但是基本都是同一个答案:
    通过com.sun.awt.AWTUtilities提供的方法来完成!

    比如:

    com.sun.awt.AWTUtilities.setWindowShape(Window window, Shape shape);
    

    只需要初始化一个Shape对象,就能实现一个任意形状的窗口,我以圆形窗口为例:

    import java.awt.*;
    import javax.swing.*;
    import java.awt.geom.Ellipse2D;
    
    public class CircleFrame extends JFrame {
    	public CircleFrame () {
    		super("Triangle Frame");
    		setSize(500,500);
    		setUndecorated(true);
    	}
    
        public static void main(String[] args) {
    	CircleFrame frame = new CircleFrame();
    	frame.setVisible(true);
    	com.sun.awt.AWTUtilities.setWindowShape(frame, new  Ellipse2D.Double(0, 0, 500, 500));
        }
    
    }
    

    效果如下:
    在这里插入图片描述
    但是这明显不是我想要的,折腾了小两年,最后新版本提供了一行代码搞定,感觉受到了伤害。并且,在编译CircleFrame的时候,提示如下:

    root@name-VirtualBox:~# javac CircleFrame.java
    CircleFrame.java:16: warning: AWTUtilities is internal proprietary API and may be removed in a future release
    	com.sun.awt.AWTUtilities.setWindowShape(frame, new  Ellipse2D.Double(0, 0, 500, 500));
    	           ^
    1 warning
    

    但这种API是完全无法把控的,明显这种API使用JNI的方式实现特定的功能,严重依赖了平台,并且API本身提供方拥有最终解释权,换句话说,使用这种API是危险⚠️的,搞不好哪天就废弃或者改变了!毕竟com.sun的包嘛…不稳定,现如今这公司都不在了,唏嘘。


    正确的做法,当然是 不依赖API,所有事情自己做了。

    Java AWT 和 Java Swing


    当初在学习Java的时候,了解到Java提供了两种GUI API:

    • Java AWT
    • Java Swing

    我当时特意了解到这两者的本质不同:

    • Java AWT
      调用操作系统GUI平台原生的操作来生成控件,换句话说,AWT的容器,控件只是操作系统GUI容器,控件在JVM里面的一个影子,AWT操作的是系统原生的容器,控件,它们称作AWT组件的 本地同位体
      优势:效率高。
      劣势:依赖底层,不同系统的观感会不同。
    • Java Swing
      除了顶层容器,其它的控件都是不依赖底层操作系统GUI平台的,换句话说,Swing的容器和控件是JVM自己画出来的。
      优势:跨平台,因为画法是一样的。
      劣势:效率低,慢。

    不管怎样,本文将用上述AWT和Swing分别采用的两种方法,即调用底层的GUI操作以及自己画,来实现不规则的窗体和控件:

    • 不规则窗体将实现一个三角形的窗体。
    • 不规则控件将实现一个三角形按钮安装在上述三角形窗体中。

    Java AWT实现不规则控件

    先看AWT的Frame如何来做。先看Java代码:

    import java.awt.*;
    import java.awt.event.*;
    public class AWTDemo extends Frame {
    
    	// 三角形按钮btn
    	Button btn;
    	//本地方法,用来将窗口和按钮切割成三角形
    	private native void cutWindow(String title);
    	static {
    		System.loadLibrary("cutWindow");
    	}
    
    	public AWTDemo(String title, String btn_title) {
    		super(title);
    		setSize(500,500);
    		// 果断去掉窗口上方的标题栏
    		setUndecorated(true);
    
    		// 生成一个四四方方的按钮对象
    		btn = new Button(btn_title);
    		// 按钮事件处理,第一次按下换个字,第二次按下退出
    		btn.addActionListener(new ActionListener() {
    			public void actionPerformed(ActionEvent e) {
    				if (e.getSource() instanceof Button) {
    					Button bu = (Button)e.getSource();
    					if (bu.getLabel().equals("AWT button"))
    						bu.setLabel("To Exit");
    					else
    						System.exit(0);
    
    				}
    			}
    		});
    		// 黄色按钮
    		btn.setBackground(Color.YELLOW);
    		add(btn);
    	}
    
    	public static void main(String args[]) {
    		String title = "abc";
    		String btnpeer = "sun-awt-X11-XButtonPeer";
    		String btn_title = "AWT button";
    		// 实例化一个Frame对象
    		AWTDemo frame = new AWTDemo(title, btn_title);
    		// 显示它
    		frame.setVisible(true);
    		try {
    			// 至此为止一切都是四四方方的
    			Thread.sleep(2000);
    			frame.cutWindow(title);
    			// 窗口成了三角形
    			Thread.sleep(2000);
    			frame.cutWindow(btnpeer);
    			// 按钮也成了三角形
    		} catch (Exception e) {
    		}
    	}
    }
    

    先看效果,再说JNI代码:
    在这里插入图片描述
    点击一下三角形的黄色按钮:
    在这里插入图片描述

    这是如何实现的呢?嗯,这是调用了本地方法cutWindow,要理解cutWindow的逻辑,这里简单说点X Window的东西,如果是在Windows平台做实验,那么就需要了解下Windows API了。

    X Window系统是一个超级复杂的C/S模式的GUI系统,其实它的架构是比较简单的,类似我们远程登录的Telnet/SSH这些。我来类比一下:

    • Telnet/SSH
      你在 负责输入输出显示的客户端 上用 键盘 敲入字符以及控制键,字符序列传输至Telnet/SSH服务器处理,然后服务器回复另一字符序列,Telnet/SSH客户端收到后解释它们,以便按照协议规范在终端 回显字符,最终生成一个CLI。
    • X window
      你在 负责输入/输出和显示的X服务器 上用 键盘,鼠标 执行一系列操作,这些操作被X服务器传输至X客户端处理,然后X客户端按照X协议规范回复数据序列,X服务器收到后解释它们,以便按照X协议规范决定 在X服务器的Display 显示器哪个像素绘制什么颜色,最终生成一个GUI。

    它们非常类似,简直是一个逻辑。唯一不同的是,X window貌似和Telnet/SSH的客户端/服务器是反着的。事实上理解起来很简单,毕竟X服务器就是用来显示的嘛。

    X Window系统封装了一系列的容器,控件,比如画布,按钮,复选框…X客户端可以以这些封装好的容器,控件为单位进行操作。

    深入的X window本文不谈,现在回到Java AWT程序。

    刚才说了,每一个AWT容器或者控件,都只是操作系统本地GUI的一个容器或者控件的影子,那么 如果我们想操作这个AWT对象,就要找到它的本地对象!

    我们用X系统的实现之一X11提供的 xwininfo 命令来探究一下内外。关于xwininfo,了解下面的就够了:

    0.你可以把display 理解成一个显示器加上一套鼠标/键盘的套件。
    1. X系统一个Display对象的所有窗体控件均按照Tree形式嵌套组织,比如一个Frame上的Button就是该Frame的child,这与Linux的进程组织非常类似。
    2. xwininfo可以枚举系统当前Display上的所有的窗体,并且给出其组织关系。

    简单修改一下代码,做一个 AWTDemo2.java ,去掉那些本地方法调用,仅仅保留主干:

    import java.awt.*;
    public class AWTDemo2 extends Frame {
    
    	Button btn;
    
    	public AWTDemo2(String title, String btn_title) {
    		super(title);
    		setSize(500,500);
    		setUndecorated(true);
    
    		btn = new Button(btn_title);
    		add(btn);
    	}
    
    	public static void main(String args[]) {
    		String title = "abc";
    		String btn_title = "AWT button";
    		AWTDemo2 frame = new AWTDemo2(title, btn_title);
    		frame.setVisible(true);
    	}
    }
    

    先执行 java AWTDemo2 打开AWT Frame界面,然后我们通过 xwininfo 看看能不能找到我们的AWT对象。

    root@name-VirtualBox:~# xwininfo -tree -root
    	...
               # 下面的Frame就是我们的AWT Frame
         0x1c00007 "abc": ("sun-awt-X11-XFramePeer" "AWTDemo2")  500x500+67+27  +67+27
            2 children:
            0x1c0001f "FocusProxy": ("Focus-Proxy-Window" "FocusProxy")  1x1+-1+-1  +66+26
            0x1c0001c "Content window": ("sun-awt-X11-XContentWindow" "AWTDemo2")  500x500+0+0  +67+27
               1 child:# 下面的这个child就是我们的AWT Button
               0x1c00020 "sun-awt-X11-XButtonPeer": ("sun-awt-X11-XButtonPeer" "AWTDemo")  500x500+0+0  +67+27
         ...
    

    果真是找到了:

    0x1c00007 "abc": ("sun-awt-X11-XFramePeer" "AWTDemo2")  500x500+67+27  +67+27
    0x1c00020 "sun-awt-X11-XButtonPeer": ("sun-awt-X11-XButtonPeer" "AWTDemo2")  500x500+0+0  +67+27
    

    Java代码里没有任何与外界的交互,但是X系统里还是找到了。所以说:
    Frame/Button这种AWT组件对于X系统是可见的,同样在Windows系统上,它也是可见的!

    如果我们换成Swing控件呢?相同的布局,只是将Frame换成JFrame,将Button换成JButton,如何呢?让我们试一下:

    import javax.swing.*;
    
    public class SwingDemo2 extends JFrame {
    
    	JButton btn;
    
    	public SwingDemo2(String title) {
    		super(title);
    		setSize(500,500);
    		setUndecorated(true);
    
    		btn = new JButton("Swing button");
    		add(btn);
    	}
    
    	public static void main(String args[]) {
    		String title = "abc";
    		SwingDemo2 demo = new SwingDemo2(title);
    		demo.setVisible(true);
    	}
    }
    

    用java SwingDemo2执行的同时,xwininfo的结果如何呢?看一下:

         0x2000007 "abc": ("sun-awt-X11-XFramePeer" "SwingDemo2")  500x500+67+27  +67+27
            2 children:
            0x200001f "FocusProxy": ("Focus-Proxy-Window" "FocusProxy")  1x1+-1+-1  +66+26
            0x200001c "Content window": ("sun-awt-X11-XContentWindow" "SwingDemo2")  500x500+0+0  +67+27
    

    Content window不再有Button这个child了!那么这个JButton到底在哪里呢?

    JButton对于X系统是不可见的,同样在Windows系统上,它也是不可见的!Java Swing除了顶层容器,其它都是自己在JVM里画大的,JVM系统外不可见!

    这下就清晰多了!我们通过xwininfo彻底理解了AWT和Swing到底区别在哪里了!


    是时候上JNI调用的本地代码了!这是用C写成的,基于X11编写。

    所谓的 本地代码 ,顾名思义就是跨平台解释执行的Java JVM之外的不受JVM控制的代码,脱离Java规范的约束 。这种支持在Java规范里叫做JNI。类似内联汇编对C语言的降维打击(内联汇编不受外部C语言规范的约束!)一样,本地代码也能对Java代码实施降维打击。所以如果你写了本地代码,你一定要知道你在干什么,通过Java的调试方法是无法知道本地代码的细节的!

    接下来,若要Java代码和本地代码对接起来,需要一个 接口 ,该接口就是通过 javah AWTDeom 生成的一个 AWTDemo.h 的C/C++头文件:

    #include <jni.h>
    
    JNIEXPORT void JNICALL Java_AWTDemo_cutWindow
      (JNIEnv *, jobject, jstring);
    

    接下来就是实现 Java_AWTDemo_cutWindow 函数了。

    JNI规范要求单独做一个动态连接库,在Linux系统,就是一个叫做 libAWTDemo.so 的动态库文件,注意,文件名一定要有 lib 前缀,因为Linux加载器就是这样加载库的。

    我下面直接给出带有注释的 cutWindow.c 文件:

    #include <X11/Xos.h>
    // 这个shape extension非常重要
    #include <X11/extensions/shape.h>
    #include <X11/Xlib.h>
    #include <X11/Xutil.h>
    #include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <stdio.h>
    #include "AWTDemo.h"
    
    // 该函数执行根据名字查找组件句柄的逻辑,不解释,详情参见X11编程文档。
    // 如果是Windows系统,那么就是另一种方式获取组件的句柄了。
    // 注意:这个例子并没有借助使用AWT本地同位体的概念,所以需要手工自己查找
    Window findWindowByName(Display *dpy, Window parent, char *name)
    {
    	unsigned int num, i;
    	Window *subwindow, dummy;
    	Window result = 0;
    	char *title;
    
    	// 按照名称查找,如果名称匹配,就返回当前的组件。
    	XFetchName(dpy, parent, &title);
        	if (title && !strcmp(name, title)) {
    		return parent;
    	}
    
    	// 否则递归查找其所有的children
    	if (!XQueryTree(dpy, parent, &dummy, &dummy, &subwindow, &num))
    		return 0;
    
    	for (i = 0; i < num; i ++)  {
    		result = findWindowByName(dpy, subwindow[i], name);
    		if (result) {
    			goto free_and_ret;;
    		}
    	}
    
    free_and_ret:
    	if (subwindow)
    		XFree ((char *)subwindow);
    	return result;
    }
    
    JNIEXPORT void JNICALL Java_AWTDemo_cutWindow (JNIEnv *jenv, jobject o1, jstring title)
    {
        Window window;
        Display *dpy;
        Region region;
        XPoint p[3];
        XPoint pbtn[3];
    
       unsigned char *name = NULL;
       // 首先需要把Frame或者Button的title通过JNI传递到C/C++代码里
       name = (*jenv)->GetStringUTFChars(jenv, title, 0);
    
        dpy = XOpenDisplay(":0");
        if (!dpy) {
            exit(1);
        }
    
    	// 按照X11的API文档查找名字为name的容器或者控件,也就是做类似于xwininfo命令做的事情
    	// 这实际上是一个枚举所有组件的过程。
    	// 事实上,可以通过AWT本地同位体将window句柄通过参数传递过来的。为了不引入同位体的概念,暂时不考虑。
        window = findWindowByName(dpy, DefaultRootWindow(dpy), name);
        if (!window) {
            exit(1);
        }
    	// Frame的三角形外形三点定义
        p[0].x = 250; p[0].y = 125;
        p[1].x = 450; p[1].y = 425;
        p[2].x = 50; p[2].y = 425;
    	
    	// Button的三角形外形三点定义
        pbtn[0].x = 250; pbtn[0].y = 190;
        pbtn[1].x = 320; pbtn[1].y = 290;
        pbtn[2].x = 180; pbtn[2].y = 290;
    
        if (!strcmp(name, "abc")) { 
        	// Frame的区域
        	region = XPolygonRegion(p, 3, EvenOddRule);
        } else {
        	// Button的区域
        	region = XPolygonRegion(pbtn, 3, EvenOddRule);
        }
        // 设置组件的外形
        XShapeCombineRegion(dpy, window, ShapeBounding, 0, 0, region, ShapeSet);
    	// close该Display时会flush/repaint组件。
        XCloseDisplay(dpy);
    }
    

    代码写好了,接下来看看如何将其做成动态链接库。JNI的规范里对这个步骤也有说明,我下面直接给出:

    root@name-VirtualBox:~# gcc -fPIC -c -I"/usr/lib/jvm/java-8-openjdk-amd64/include" -I"/usr/lib/jvm/java-8-openjdk-amd64/include/linux" cutWindow.c -o cutWindow.o
    root@name-VirtualBox:~# gcc -shared -o libcutWindow.so cutWindow.o
    

    OK!现如今,该例子的AWTDemo.class运行所需的所有依赖都已经准备好了,我们希望JVM在当前路径下去加载 libcutWindow.so

    root@name-VirtualBox:~# javac AWTDemo.java
    root@name-VirtualBox:~# java -Djava.library.path=. AWTDemo
    

    执行起来就是上面图示的效果!

    本地代码是通过控件的Title来查找本地同位体的,如果是Windows平台,查找组件的操作要简单的多,只有有API可用:

    HWND FindWindowA(
      LPCSTR lpClassName,
      LPCSTR lpWindowName
    );
    

    详情参见:https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-findwindowa
    【PS:我十多年前的不规则窗口版本就是在Windows平台做的,用的就是上面这个FindWindowA】

    事实上可以通过参数将本地同位体的控件句柄直接从Java代码传递到本地代码中的,这个后面再说。

    上述的本地代码动态库是基于X11实现的,我本来想用GTK,KDE来做例子,然而这些并不是原汁原味的,会引入很多额外的东西,比如看了GTK代码后,就会纠结于GTK是怎么回事…我们是在做Java不规则控件,而不是在学习GTK。所以越底层越好。

    在实现中,代码中使用了X11的Shape extension,这是一个扩展,并不包含在原生的X11中,基于它可以实现不规则的窗口和控件,它非常复杂,详细参见:
    https://en.wikipedia.org/wiki/Shape_extension
    https://www.x.org/releases/X11R7.7/doc/xextproto/shape.html
    本文不赘述,还是那句话,Java以外的,点到为止。

    AWT版本的自定义不规则组件的介绍就是这么多。接下来看看Swing的版本。


    Java Swing实现不规则控件

    前面说了,Java Swing除了顶层的容器,其它的控件组件都是自己画出来的,那么要实现不规则的JFrame,作为顶层容器的JFrame,由于其依然是映射到本地的真实窗口,所以依然是上面的JNI的方法去切割。

    重点是JButton的不规则化如何来做。换句话说,如何把它 画出来

    JButton在本地的GUI系统里根本就找不到,就像上面xwininfo展示的结果那样,怎么办?

    画一个就是了!

    如何来画呢?我还是直接给出完整的代码吧, SwingDemo.java 列如下:

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
    
    // 从JButton派生一个子类,实现三角形JButton
    class triangleButton extends JButton {
    	// 此Button的三角形区域
    	Polygon triangle;
    	triangleButton(String title, int x[], int y[]) {
    		super(title);
    		// 根据参数初始化三角形区域
    		triangle = new Polygon(x, y, 3);
    		// 不显示按钮边框!完全由我们自己绘制的三角形来决定
    		setBorderPainted(false);
    		setContentAreaFilled(false);
    	}
    
    	// 重写paintComponent方法,区分按钮按下和释放时显示不同的颜色,显得逼真!
    	public void paintComponent(Graphics g) {
    		if (this.getModel().isPressed()) {
    			g.setColor(Color.BLACK);
    		} else {
    			g.setColor(Color.LIGHT_GRAY);
    		}
    		// 用不同的颜色画同一个三角形
    		// 如果能细化边缘凸凹高亮,那就更美观了!但是那样代码太长。
    		g.fillPolygon(triangle);
    		super.paintComponent(g);
    	}
    
    	// 重写contains,判断鼠标当前的焦点是不是属于该按钮的区域范围内。
    	// 这是Java Swing的创举,委托UI管理器来实现范围限定约束的托管!
    	public boolean contains(int x, int y) {
    		if (triangle.contains(x, y))
    			return true;
    		return false;
    	}
    }
    
    public class SwingDemo extends JFrame {
    	JButton btn;
    	// 定义按钮三角形的三点
    	int button_x[] = {250, 315, 185};
    	int button_y[] = {327, 227, 227};
    
    	// 用户切割JFrame的本地方法
    	private native void cutWindow(String title);
    	static {
    		System.loadLibrary("cutWindow");
    	}
    
    	public SwingDemo(String title) {
    		super(title);
    		setSize(500,500);
    		setUndecorated(true);
    
    		btn = new triangleButton("Swing button", button_x, button_y);
    		btn.addActionListener(new ActionListener() {
    			public void actionPerformed(ActionEvent e) {
    				if (e.getSource() instanceof triangleButton) {
    					triangleButton bu = (triangleButton)e.getSource();
    					if (bu.getText().equals("Swing button"))
    						bu.setText("To Exit");
    					else
    						System.exit(0);
    
    				}
    			}
    		});
    		add(btn);
    	}
    
    	public static void main(String args[]) {
    		String title = "abc";
    		SwingDemo demo = new SwingDemo(title);
    		demo.setVisible(true);
    		// 切割JFrame为三角形,同AWTDemo
    		demo.cutWindow(title);
    	}
    }
    

    当执行 java SwingDemo 时,展示一下效果:
    在这里插入图片描述
    点击一下试试看,点击的瞬间,三角形变成了黑色,这个太快了,没法截屏,但是松开鼠标后,按钮的字变了:在这里插入图片描述
    再点一下,如代码逻辑所示,退出。

    别看这是Swing画出来的,但 这是真正的三角形按钮,不仅仅是视觉上的,你只有点击那个小三角形区域,才会有效果,别的区域是不行的。

    Swing版本的不规则窗口和按钮总结下来就是:

    • Swing窗口依然采用JNI的方式在X11 API实现的动态库里进行切割;
    • Swing窗口上的按钮,自己编码绘制完成不规则化。

    纯Java的完整例子(?窗口,?按钮)

    能不能不用JNI?

    可以的。

    不用JNI实现窗口切割,不用说也能猜到原理,Java的工具包自己帮你JNI了呗。

    当我们已经理解了上述细节后,表示可以完全hold住java的api后,便可以自由使用一开始我并不提倡的Java自带的com.sun.awt.AWTUtilities了。

    com.sun.awt.AWTUtilities ,它就是用JNI实现窗口切割的。那么我便使用它直接来完成一个不规则窗口,不然我还要自己写本地代码,我又不想深入去学习X11。那么OK,我直接用AWTUtilities了!

    这个例子中,我使用一双大皮鞋图片作为窗口,一双小皮鞋图片作为按钮,窗口的形状是大皮鞋,按钮的形状是小皮鞋,皮鞋是不规则的,所以窗口和控件是不规则的。

    代码只有一个文件, SkinShoeDemo.java ,列如下:

    import javax.swing.*;
    import java.awt.*;
    import java.awt.event.*;
    import java.awt.geom.*;
    import java.awt.image.*;
    import javax.imageio.ImageIO;
    import java.net.URL;
    import java.io.*;
    
    // 实现不规则的小皮鞋按钮
    class LittleSkinShoeButton extends JButton {
    	ImageIcon img;
    	BufferedImage imgpixe;
    	LittleSkinShoeButton(ImageIcon img, String icon){
    		super();
    		this.img = img;
    		setBorderPainted(false);
    		setContentAreaFilled(false);
    		setSize(img.getIconWidth(),img.getIconHeight());
    		try{
    			// 需要完整的图片像素来确定哪些像素属于皮鞋,哪些像素不属于皮鞋,这个涉及到“抠图”,后面会讲
    			imgpixe = ImageIO.read(SkinShoeDemo.class.getResource(icon));
    		} catch (Exception e){
    			System.exit(0);
    		}
    	}
    
    	// 当鼠标点击小皮鞋按钮“皮鞋区域”内部时,要展示出被点击的样子来,向下凹陷一下。
    	public void paintComponent(Graphics g){
    		if(this.getModel().isPressed()){
    			// 向下凹陷5个像素,向右平移5个像素,感觉像是被点击了。
    			g.drawImage(img.getImage(), 5, 5, this);
    		}else{
    			// 不被点击时,保持在原来的位置。
    			g.drawImage(img.getImage(),0,0,this);
    		}
    	}
    
    	// 重写contains方法。
    	public boolean contains(int x,int y){
    		int rgb,alpha;
    		try{
    			rgb = imgpixe.getRGB(x,y);
    			// 获取像素的alpha值,如果是被“抠去”的,就不属于皮鞋内部。
    			// 当初制作皮鞋图的时候,不是皮鞋范围的都“抠掉成透明”的了。
    			alpha = (rgb>>24) & 0xFF;
    			if(alpha != 0){
    				System.out.println("属于小皮鞋范围,点击有效:[" + x + "," + y );
    				return true;
    			}
    		}catch(ArrayIndexOutOfBoundsException e){
    		}
    		System.out.println("不属于小皮鞋范围,点击无效:[" + x + "," + y );
    		return false;
    	}
    }
    
    // 实现JFrame的背景图片,即一双大皮鞋。
    class SkinShoePanel extends JComponent {
        private Image image;
        public SkinShoePanel(Image image) {
            this.image = image;
        }
    	
    	// 重绘,也就是画大皮鞋
        protected void paintComponent(Graphics g) {
            g.drawImage(image, 0, 0, this);
        }
    }
    
    // 大皮鞋窗口主类
    public class SkinShoeDemo extends JFrame {
    
    	JButton btn;
    
    	private JPanel pixieimgPanel;
    	public SkinShoeDemo(BufferedImage img, ImageIcon btn_img, String icon) {
    		super("SkinShoe");
    		setSize(img.getWidth(), img.getHeight());
    
    		btn = new LittleSkinShoeButton(btn_img, icon);
    		btn.addActionListener(new ActionListener() {
    			public void actionPerformed(ActionEvent e) {
    				if (e.getSource() instanceof LittleSkinShoeButton) {
    					// TODO
    				}
    			}
    		});
    		this.setContentPane(new SkinShoePanel(img));
    		btn.setLocation(280, 280);
    		this.setUndecorated(true); // 这个必须调用
    		// 按钮添加在画布上。
    		this.getContentPane().add(btn);
    	}
    
    	public Shape getSkinshoeShape(BufferedImage pixieimg) {
    		Area pixie = new Area();
    		int width;
    		int height;
    		int x,y, temp; // temp起到了优化的作用,批量添加区域
    		int rgb, alpha;
    
    		width = pixieimg.getWidth();
    		height = pixieimg.getHeight();
    		for (y = 0; y < height; y++)  {
    			temp = 0;
    			for (x = 0; x < width; x++) {
    				rgb = pixieimg.getRGB(x, y);
    		 		alpha = (rgb>>24)&0xFF;
    		 		/* 下面的if-else语句的含义就是下面注释版的if语句的优化版,即:
    		 		 * 将“不透明”的像素拼接成一个“区域”。
    		 		 * 所有“不透明”的像素就是在抠图时没有被抠掉的像素。
    		 		 * 如果是使用下面注释版本的话,要一个像素一个像素添加,那么大一双皮鞋,要两分钟!!
    		 		 * if (alpha != 0) {
    		 		 *		Rectangle temppixe = new Rectangle(x, y, 1, 1);
    				 *		pixie.add(new Area(temppixe));
    		 		 * }
    		 		 */
    		 		if(alpha != 0) {
    					if (temp == 0)
    						temp = x;
    				} else {
    					if (temp != 0) {
    						Rectangle temppixe = new Rectangle(temp, y, x - temp, 1);
    						pixie.add(new Area(temppixe));
    						temp = 0;
    					}
    				}
    			}
    		}
    		return pixie;
    	}
    
    	public static void main(String args[]) {
    		// 22.png就是哪个小皮鞋
    		String pixieicon = "22.png";
    		// 11.png是那双大皮鞋
    		File file = new File("11.png");
    		BufferedImage pixieImage = null;
    		ImageIcon button_icon = null;
    		try {
    			pixieImage = ImageIO.read(file);
    			button_icon = new ImageIcon(SkinShoeDemo.class.getResource(pixieicon));
    		} catch (Exception e) {
    		}
    		SkinShoeDemo demo = new SkinShoeDemo(pixieImage, button_icon, pixieicon);
    		demo.setVisible(true);
    		// 切割吧!
    		com.sun.awt.AWTUtilities.setWindowShape(demo, demo.getSkinshoeShape(pixieImage));
    	}
    }
    

    看看效果呗:
    在这里插入图片描述
    只要不是大皮鞋区域,鼠标点击都是透明的:
    在这里插入图片描述

    这是真正意义的 不规则窗口


    关于抠图

    我现在简单说一下这个效果的前置要求,必须对两双皮鞋的图片进行 抠图 预处理。

    先来看一下定义窗口外观的大皮鞋原图:
    在这里插入图片描述
    显然,按照常规,这是一张 四四方方 的图,矩形的。也就是说,这张图包含了 皮鞋前景白色背景 两个部分。如果拿这个图做我们的不规则窗口的11.png,将会是失败的,因为那个白色的背景并不会由于其Alpha值(含义马上会讲,这里只是代码的观感)等于0而被排除在有效范围之外:

    rgb = pixieimg.getRGB(x, y);
    alpha = (rgb>>24)&0xFF;
    

    Alpha的值等于0而被放逐在我们需要的有效区域以外,但是原图的矩形区域内包括背景在内的所有像素的Alpha值均不为0,为什么?

    我们单看白色的背景,换句话讲,白色背景的特性如下:

    • 颜色:有颜色,白色
    • 透明度:不透明(是的,它并不透明!)

    一张图片的每一个像素,均会包含上述两个性质,一个颜色,一个透明度。这两个性质被保存在一个4字节的数字里,其中的颜色占据3个字节,分别保存三原色的各自分量,余下的1个字节保存透明度信息,很巧妙。

    最常用的定义,4个字节定义如下:

    typedef struct RGB_info {
    u8 rgbBlue; 	// 蓝色分量
    u8 rgbGreen; 	// 绿色分量
    u8 rgbRed; 		// 红色分量
    u8 rgbAlpha; 	// 透明度
    } RGB。
    

    这个叫做 带Alpha通道的RGB24 标准。

    很少有图片在生成的时候就会设置某些像素的Alpha为完全透明,所以这个需要我们自己来 ,把背景抠掉即可,所谓的抠掉,就是将背景像素的Alpha值设置为0。

    一般抠图程序很容易写,但是比较麻烦,原理很简单, 只要能通过RGB颜色信息识别出前景和背景,那么把背景像素的Alpha设置为0即可。

    问题是如何识别。在我们这个例子中,很简单,把白颜色的给设置透明即可,但是有些像素并不是纯白,而是 接近白,灰白… 这让程序去定义一个范围吗?似乎这个范围如何来界定又是一个问题,最终就陷入了AI,哦,很高大上的领域!

    还是用肉眼识别吧,一切抠图者自己来决定。这就要使用抠图工具了。这种工具一般让你自己用圈点指针自己标示那些部分要设置为透明,比如套索,画笔之类的。

    昨晚让老婆用美图秀秀给帮忙把皮鞋前景给抠出来,但是没有成功,后来我找了一个在线的工具:
    https://ue.818ps.com/clip/
    在这里插入图片描述
    还算挺方便的。反正也只是用一次,足够了。

    最终把那双要做按钮的小皮鞋也完成了抠图:
    在这里插入图片描述
    用这两张图就可以制作不规则窗口和不规则按钮了。

    最后让我们的SkinShoeDemo换一张图,看看效果:
    在这里插入图片描述
    给包括小小在内的演示了这个之后,都说好。


    X11的畅想

    本来我是想用JNI调用X11实现的动态库来实现这个 大皮鞋?窗口 的,但是失败了。我一直以为这是很容易成功的。

    当初既然我可以用三个点来定义一个三角形,我就自然而然想到可以用 皮鞋轮廓的N个像素点 来定义一个N边形来 模拟大皮鞋?围绕着的区域 ,N是多少呢?就看大皮鞋图案的外轮廓有多少个点了。这个想法很合理。

    首先我要先得到大皮鞋的轮廓。

    我用下面的代码获得,命名为Outline.java:

    import java.awt.image.*;
    import java.io.*;
    import javax.imageio.ImageIO;
    
    public class Outline {
    	static File src = null; 
    	static File dst = null;
    	static BufferedImage img = null;
    	static BufferedImage outline = null;
    
    	public static void main(String[] args) throws IOException {
    		int i, j, width, height;
    		int rgb, rgb1, rgb2, a1, a2, a3;
    		src = new File(args[0]);
    		dst = new File("outline_" + args[0]);
    		img = ImageIO.read(src);
    		width = img.getWidth();
    		height = img.getHeight();
    
    		outline = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    		// 纵向扫描
    		for (i = 1; i < width - 1; i++) {
    			for (j = 1; j < height-1; j++) {
    				// 捕捉变化
    				rgb = img.getRGB(i, j);
    				rgb1 = img.getRGB(i + 1, j);
    				rgb2 = img.getRGB(i - 1, j);
    				a1 = (rgb>>24)&0xFF;
    				a2 = (rgb1>>24)&0xFF;
    				a3 = (rgb2>>24)&0xFF;
    				if(a1 != 0 && (a2 == 0 || a3 == 0)) {
    					// 为了轮廓线清晰,特使用白色来描绘“边界像素周边的四个点”
    					rgb |= 0xffffffff;
    					outline.setRGB(i, j, rgb);
    					outline.setRGB(i, j - 1, rgb);
    					outline.setRGB(i, j + 1, rgb);
    					outline.setRGB(i + 1, j, rgb);
    					outline.setRGB(i - 1, j, rgb);
    				}
    			}
    		}
    
    		// 横向扫描
    		for (i = 1; i < height - 1; i++) {
    			for( j = 1; j < width-1; j++) {
    				// 捕捉变化
    				rgb = img.getRGB(i, j);
    				rgb1 = img.getRGB(i, j + 1);
    				rgb2 = img.getRGB(i, j - 1);
    				a1 = (rgb>>24)&0xFF;
    				a2 = (rgb1>>24)&0xFF;
    				a3 = (rgb2>>24)&0xFF;
    				if (a1 != 0 && (a2 == 0|| a3 == 0)) {
    					// 为了轮廓线清晰,特使用白色来描绘“边界像素周边的四个点”
    					rgb |= 0xffffffff;
    					outline.setRGB(i, j, rgb);
    					outline.setRGB(i, j + 1, rgb);
    					outline.setRGB(i, j - 1, rgb);
    					outline.setRGB(i + 1, j, rgb);
    					outline.setRGB(i - 1, j, rgb);
    				}
    			}
    		}
    
    		ImageIO.write(outline, "png", dst);
    	}
    }
    

    以上这个程序,当我用大皮鞋图片的名称 11.png 作为参数执行时,会输出一张轮廓图片 outline_11.png ,输出图片效果如下图所示:
    在这里插入图片描述
    嗯,是一双皮鞋?!!

    我用这个轮廓干什么呢?我要得到一个数组啊。于是我在上述代码的 outline.setRGB 处打印一个序列:

    int idx = 0;
    ...
    System.out.println("outl[" + idx + "].x=" + i + "; out[" + idx + "].y=" + j + ";");
    idx ++;
    

    打印的结果就是:

    root@name-VirtualBox:~# java Outline 11.png |more
    outl[0].x=29; out[0].y=722;
    outl[1].x=29; out[1].y=723;
    outl[2].x=29; out[2].y=724;
    outl[3].x=29; out[3].y=725;
    outl[4].x=29; out[4].y=726;
    outl[5].x=29; out[5].y=728;
    outl[6].x=29; out[6].y=729;
    outl[7].x=29; out[7].y=730;
    outl[8].x=29; out[8].y=731;
    outl[9].x=29; out[9].y=732;
    ...
    outl[1567].x=772; out[1567].y=293;
    

    我将这个打印结果重定向到一个文件中,然后将其include到JNI的本地代码。

    我的意图是希望这些 XPoint 可以生成一个 1568边形! 我希望它们可以定一个Region,然后让X11 shape去框定窗口的裁剪范围:

    outl[9].x=29; out[9].y=732;
    ...
    outl[1567].x=772; out[1567].y=293;
    	
    region = XPolygonRegion(outl, 1568, EvenOddRule);
    XShapeCombineRegion(dpy, window, ShapeBounding, 0, 0, region, ShapeSet);
    

    初看这些操作,和 Java com.sun.awt.AWTUtilities.setWindowShape 的操作几乎是一样的,定义一个Region而已,我用1568边形来框定这个窗口的范围,可行啊!

    然而事与愿违!

    问题出在了 如何画N边形 这件事上。我们希望系统会 逐个地将我们的皮鞋外轮廓点连接起来,形成一个多边形 ,但系统如何解释 逐个地 ,这是问题。

    请注意 XPolygonRegion 函数,它的最后一个参数确定了 如何判断一个点是否在该Region的内部。 它的取值只有两个:

    The fill-rule defines what pixels are inside (drawn) for paths given in XFillPolygon() requests and can be set to EvenOddRule or WindingRule. For EvenOddRule , a point is inside if an infinite ray with the point as origin crosses the path an odd number of times. For WindingRule , a point is inside if an infinite ray with the point as origin crosses an unequal number of clockwise and counterclockwise directed path segments. A clockwise directed path segment is one that crosses the ray from left to right as observed from the point. A counterclockwise segment is one that crosses the ray from right to left as observed from the point. The case where a directed line segment is coincident with the ray is uninteresting because you can simply choose a different ray that is not coincident with a segment.

    For both EvenOddRule and WindingRule, a point is infinitely small, and the path is an infinitely thin line. A pixel is inside if the center point of the pixel is inside and the center point is not on the boundary. If the center point is on the boundary, the pixel is inside if and only if the polygon interior is immediately to its right (x increasing direction). Pixels with centers on a horizontal edge are a special case and are inside if and only if the polygon interior is immediately below (y increasing direction).

    此乃问题之所在了。fill-rule 的局限导致了皮鞋内部的点不一定被判定为 内部

    虽然没能成功使用外轮廓数组构造一个Region调用XShapeCombineRegion完成不规则窗口的切割,但是我知道已经提供 com.sun.awt.AWTUtilities.* 的肯定是有办法做到的:

    • 要么自己调用JNI,加以适配
    • 要么直接返回UNSPPORTED

    没空研究X11细节,GUI本来也就不是我的关注点。但偶尔,我依然会花点时间探究一下若干年前遗落的问题,这是改变不了的事实。


    JNI直接操作AWT本地同位体

    洋洋洒洒写到这里,相信已经把Java如何调用JNI或者自身的API实现不规则窗体说的很清楚了,但是还有点小遗憾。

    我们看上文中引述的本地代码 cutWindow.c ,其中操作的window句柄是通过字符串Title查找而来的:

    window = findWindowByName(dpy, DefaultRootWindow(dpy), name);
    XShapeCombineRegion(dpy, window, ShapeBounding, 0, 0, region, ShapeSet);
    

    如果启动了同样Title的两个实例,会怎样?这就不得不将这多个同样Title的window通过别的键值进行区分。

    正确且直接的做法应该是Java直接将window句柄通过参数传递进来!这样就可以直接操作明确的window控件了!

    问题是Java代码中如何获得本地同位体的句柄呢?理论上来讲,这个系统底层的概念在Java API的层面应该是不可见的,Java代码只认识 跨平台的Frame/JFrame 这种,不可能会认识 Windows的HWND,X11的Window 的!

    然而,如果你不追求通用性,不追求跨平台(窗口不规则切割这件事本来就是平台相关的),办法嘛,必然是有的。我下面直接给出代码吧。

    1. 先看 PeerDemo.java:
    import java.awt.*;
    import java.awt.peer.*;
    import sun.awt.X11.*;
    
    public class PeerDemo extends Frame {
    
    	private native void cutWindow(long display, long hwnd);
    	static {
    		System.loadLibrary("cutWindow");
    	}
    	WindowPeer peer = null;
    	XBaseWindow base = null;
    	long hwnd = 0;
    
    	public PeerDemo() {
    		setSize(500,500);
    		setUndecorated(true);
    		setVisible(true);
    		this.peer = (WindowPeer)this.getPeer();
    		this.base =(XBaseWindow)peer;
    		this.hwnd = base.getWindow();
    	}
    
    	public static void main(String args[]) {
    		PeerDemo demo = new PeerDemo();
    
    		demo.cutWindow(XToolkit.getDisplay(), demo.hwnd);
    	}
    }
    
    1. 再看本地方法声明 PeerDemo.h:
    JNIEXPORT void JNICALL Java_PeerDemo_cutWindow
      (JNIEnv *, jobject, jlong, jlong);
    
    1. 最后看本地动态库代码 cutWindow.c:
    #include <X11/extensions/shape.h>
    # include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <jni.h>
    
    JNIEXPORT void JNICALL Java_PeerDemo_cutWindow (JNIEnv *jenv, jobject o1, jlong display, jlong hwnd)
    {
    	Window window;
    	Display *disp;
    	Region region;
    	XPoint p[3];
    
    	disp = (Display *)display;
    	window = (Window)hwnd;
    
    	p[0].x = 250; p[0].y = 100;
    	p[1].x = 450; p[1].y = 425;
    	p[2].x = 50; p[2].y = 425;
    
    	region = XPolygonRegion(p, 3, EvenOddRule);
    	XShapeCombineRegion(disp, window, ShapeBounding, 0, 0, region, ShapeSet);
    }
    

    非常简单的一气呵成,看看效果:
    在这里插入图片描述
    编译时的Warning是必然的,Java的Doc上已经说的很明白了:
    在这里插入图片描述
    getPeer意味着关联了本地,承诺跨平台的Java怎么可能暴露出这么低层次的接口呢。

    结束了吗?嗯,差不多了。但是且慢!

    还记得上文中我的失败吗?我想勾勒出皮鞋?的轮廓,然后把这些轮廓的像素点(超级多的点,至少好几千个吧)传递给本地代码,企图在本地代码中用这些轮廓点构建出一个X11的Region结构体:

    region = XPolygonRegion(outline_points, 3188/*举个例子,或许更多吧*/, EvenOddRule);
    

    然后呢,将其传递给X11的 XShapeCombineRegion 函数进行切割。然而遗憾的是,X11并不是如我希望的那般将这些点按照皮鞋的样子顺序连接起来成为一个皮鞋外形的,它有自己的连接方式。很遗憾,失败了(也许是我对X11不太了解,我暂时只能理解到这个程度)。

    然而,X11提供了另一种 构建任意形状 的方法,即 组合矩形

    这很好理解,既然所有的图像在计算机中都是一个个的像素组成的,而每一个像素就是一个长宽均为1的矩形,那么 任意形状至少可以用这么多像素矩形组合而成 。作为优化,还可以将矩形的数量减少到最少,这是一个算法问题,我这里仅仅给出一个思想。比如,依然是那个皮鞋的轮廓,矩形可以如下分割:
    在这里插入图片描述
    细微之处我没有画,反正就是这个意思。

    X11提供了组合不同大小矩形的API,即:

    void XShapeCombineRectangles (
    	Display *dpy,
    	XID dest,
    	int destKind,
    	int xOff,
    	int yOff,
    	XRectangle *rects,
    	int n_rects,
    	int op,
    	int ordering);
    

    参数很丰富,我还真没搞明白,详情可以参见X11 Shape Extension的文档:
    http://www.xfree86.org/current/shape.pdf

    不过给出个例子还是可以的。Java代码如下:

    import java.awt.*;
    import java.awt.peer.*;
    import sun.awt.X11.*;
    import javax.imageio.ImageIO;
    import java.net.URL;
    import java.io.*;
    import java.awt.geom.*;
    import java.awt.image.*;
    
    public class PeerDemo extends Frame {
    
    	private native void cutWindow(long display, long window);
    	static {
    		System.loadLibrary("cutWindow");
    	}
    	WindowPeer peer = null;
    	XBaseWindow base = null;
    	long hwnd = 0;
    
    	public PeerDemo() {
    		setSize(500,500);
    		setLocation(200,300);
    		setUndecorated(true);
    		setVisible(true);
    		this.peer = (WindowPeer)this.getPeer();
    		this.base =(XBaseWindow)peer;
    		this.hwnd = base.getWindow();
    	}
    
    	public static void main(String args[]) {
    		PeerDemo demo = new PeerDemo();
    		demo.cutWindow(XToolkit.getDisplay(), demo.hwnd);
    	}
    }
    

    本地代码如下:

    #include <X11/extensions/shape.h>
    # include <stdio.h>
    #include <unistd.h>
    #include <stdlib.h>
    #include <jni.h>
    
    JNIEXPORT void JNICALL Java_PeerDemo_cutWindow (JNIEnv *jenv, jobject obj, 
    										jlong display, jlong hwnd)
    {
    	Window window;
    	Display *disp; 
    	XRectangle rects[4];
    	XRectangle *pRect = &rects[0];
    
    	disp = (Display *)display;
    	window = (Window)hwnd;
    
    	// 我们组合下面4个矩形:
    	rects[0].x = 0;
    	rects[0].y = 0;
    	rects[0].width = 100;
    	rects[0].height = 100;
    	rects[1].x = 100;
    	rects[1].y = 0;
    	rects[1].width = 100;
    	rects[1].height = 50;
    	rects[2].x = 200;
    	rects[2].y = 0;
    	rects[2].width = 10;
    	rects[2].height = 400;
    	rects[3].x = 0;
    	rects[3].y = 100;
    	rects[3].width = 10;
    	rects[3].height = 300;
    
    	XShapeCombineRectangles(disp, window, 
    							ShapeBounding, 0, 0, pRect, 4, 
    							ShapeSet, YXSorted);
    
    }
    

    效果如下图所示:
    在这里插入图片描述
    有点这个意思了。如果把这些矩形按照这个思路不断细化,就可以出现皮鞋外观了。


    什么是编程

    还记得 《Java Swing 2nd Edition》 这本书吗?

    我当时买了中文版之后,太厚,切割成了3部分,然而最终还是没有读完,惭愧。现在它的代码资源放到github了:
    https://resources.oreilly.com/examples/9780596004088
    我记得第28章的那个圆形飞梭控件非常好,嗯,Java Swing自己定义的不规则控件,是的,它是Swing自己画出来的。当时非常震撼。

    现在回头想想,有啥好震撼的呢,计算机屏幕上的所有像素不都是画出来的吗?关键是 如何画 才是根本,而这又涉及到了算法。

    如果不知道JNI,很难用Java做出不规则控件,如果知道了JNI,至少知道了有个渠道可以做,然而此时你还必须懂Win32 API/X11 API这些,你才能真的动手去做。之后如果这些全都懂了呢?就以为自己可以任意画界面了吗?

    不不不,这才到了关键的地方,这才是刚刚开始,比如上文中所说的,如何把皮鞋切割成数量最少的矩形,这比如何调用X11 API重要得多了,这里面奥妙太深了。

    换句话说,你想画画,目前你只是买了本子,画笔,画板,并且知道了如何使用它们,这些都是 必先利其器 的事,并不是画画本身!

    所以说呢,不是精通几个API,精通几门编程语言语法,精通几个工具的使用,就是精通编程了。甚至即便你精通很多系统底层的调试方法,你也不一定懂编程。你只是精通工具如何使用而已。

    比如我,我精通如何摆置系统底层,如何摆置协议栈,但是这并不意味着我精通编程,编程的核心是算法,而不是如何摆置代码本身。

    编程是一种如何组织逻辑的艺术,它不是一种如何使用编程语言的技术 编程语言只有一种用法,就是 用对它

    换句话说,如果你精于如何组织逻辑,那么即便是使用自然语言,你也可以精通编程(这通常见于物理科学家,律师,外交官等职业)。编程语言只是实现这种逻辑组织的手段而已(所以生物学家,文学家很多也精于几种编程语言,大多数用于数据分析)。所以,一定要把 会编程语言会编程 区别开来。

    以上形而上的说法如何形而下落地?这就要将行业的分工细化为 算法工程 两个方向。

    算法侧重业务逻辑本身,而工程则是为了更好的组织算法,使得其符合工业约束,成为优质的产品。比如说可扩展性这个就是工程要做的事,而效率则大部分是算法的工作。

    最后,我觉得我还是不会编程,在历经的学习和工作过程中,我掌握了系统的工作原理,底层的调试技巧,但是我依然吃力于编程,这意味着我的逻辑组织混乱,但是这并不是什么缺点更不是缺陷,这意味着我可以天马行空,而这正是定位棘手问题所需要的。所以说,摆正自己的位置最重要,不会编程没什么丢人的。


    勘误:全文均没有出现 “一双皮鞋” ,仅仅是 “一只皮鞋” 而已!更正。

    浙江温州皮鞋湿,下雨进水不会胖。

    展开全文
  • Java Swing制作多行滚动歌词显示控件

    千次阅读 2014-07-17 11:33:50
    首先上图一张,为最终制作的效果图,喜欢或感到失望的朋友可以先行离开 大家已经看到效果图了。那么下面就介绍设计思路和源代码 首先要想显示歌词,就要对歌词文件进行抽象。下面这个类是对某一行歌词...

    1. 首先上图一张,为最终制作的效果图,不喜欢或感到失望的朋友可以先行离开


      大家已经看到效果图了。那么下面就介绍设计思路和源代码

      首先要想显示歌词,就要对歌词文件进行抽象。下面这个类是对某一行歌词文件进行了抽象。

      1. /* 
      2.  * To change this template, choose Tools | Templates 
      3.  * and open the template in the editor. 
      4.  */  
      5. package musicbox.model.lyric;  
      6.   
      7. /** 
      8.  * 
      9.  * @author Randyzhao 
      10.  */  
      11. public class LyricStatement {  
      12.   
      13.     private long time = 0;//时间, 单位为10ms   
      14.     private String lyric = "";//歌词   
      15.   
      16.     /* 
      17.      * 获取时间 
      18.      */  
      19.     public long getTime() {  
      20.     return time;  
      21.     }  
      22.     /* 
      23.      * 设置时间 
      24.      * time: 被设置成的时间 
      25.      */  
      26.   
      27.     public void setTime(int time) {  
      28.     this.time = time;  
      29.     }  
      30.     /* 
      31.      * 设置时间 
      32.      * time: 被设置成的时间字符串, 格式为mm:ss.ms 
      33.      */  
      34.   
      35.     public void setTime(String time) {  
      36.     String str[] = time.split(":|\\.");  
      37.     this.time = Integer.parseInt(str[0]) * 6000 + Integer.parseInt(str[1]) * 100 +   
      38.         Integer.parseInt(str[2]);  
      39.     }  
      40.     /* 
      41.      * 获取歌词 
      42.      */  
      43.   
      44.     public String getLyric() {  
      45.     return lyric;  
      46.     }  
      47.     /* 
      48.      * 设置歌词 
      49.      */  
      50.   
      51.     public void setLyric(String lyric) {  
      52.     this.lyric = lyric;  
      53.     }  
      54.     /* 
      55.      * 打印歌词 
      56.      */  
      57.   
      58.     public void printLyric() {  
      59.     System.out.println(time + ": " + lyric);  
      60.     }  
      61. }  
      /*
       * To change this template, choose Tools | Templates
       * and open the template in the editor.
       */
      package musicbox.model.lyric;
      
      /**
       *
       * @author Randyzhao
       */
      public class LyricStatement {
      
          private long time = 0;//时间, 单位为10ms
          private String lyric = "";//歌词
      
          /*
           * 获取时间
           */
          public long getTime() {
      	return time;
          }
          /*
           * 设置时间
           * time: 被设置成的时间
           */
      
          public void setTime(int time) {
      	this.time = time;
          }
          /*
           * 设置时间
           * time: 被设置成的时间字符串, 格式为mm:ss.ms
           */
      
          public void setTime(String time) {
      	String str[] = time.split(":|\\.");
      	this.time = Integer.parseInt(str[0]) * 6000 + Integer.parseInt(str[1]) * 100 + 
      		Integer.parseInt(str[2]);
          }
          /*
           * 获取歌词
           */
      
          public String getLyric() {
      	return lyric;
          }
          /*
           * 设置歌词
           */
      
          public void setLyric(String lyric) {
      	this.lyric = lyric;
          }
          /*
           * 打印歌词
           */
      
          public void printLyric() {
      	System.out.println(time + ": " + lyric);
          }
      }
      
      特别注意成员变量time表示该行歌词显示的时间,单位是 10ms 这是为了和歌词文件中时间的单位统一。

      某一行歌词可以用一个LyricStatement类的实例来表示。那么一个歌词文件就可以解析为一个List<LyricStatement>。为了方便测试,以下附上本人自己写的一个歌词文件解释器。

      1. /* 
      2.  * To change this template, choose Tools | Templates 
      3.  * and open the template in the editor. 
      4.  */  
      5. package musicbox.model.lyric;  
      6.   
      7. import java.io.BufferedReader;  
      8. import java.io.FileInputStream;  
      9. import java.io.IOException;  
      10. import java.io.InputStreamReader;  
      11. import java.net.URLDecoder;  
      12. import java.util.ArrayList;  
      13. import java.util.List;  
      14. import java.util.regex.Matcher;  
      15. import java.util.regex.Pattern;  
      16.   
      17. /** 
      18.  * 
      19.  * @author Randyzhao 
      20.  */  
      21. public class LyricReader {  
      22.   
      23.     BufferedReader bufferReader = null;                         //读取文件实例   
      24.     public String title = "";                                   //歌曲题目   
      25.     public String artist = "";                                  //演唱者   
      26.     public String album = "";                                   //专辑   
      27.     public String lrcMaker = "";                                //歌词制作者   
      28.     List<LyricStatement> statements = new ArrayList<LyricStatement>();      //歌词   
      29.     /*  
      30.      * 实例化一个歌词数据. 歌词数据信息由指定的文件提供.  
      31.      * fileName: 指定的歌词文件.  
      32.      */  
      33.   
      34.     public LyricReader(String fileName) throws IOException {  
      35.     //in case the space in the fileName is replaced by the %20   
      36.     FileInputStream file = new FileInputStream(URLDecoder.decode(fileName, "UTF-8"));  
      37.     bufferReader = new BufferedReader(new InputStreamReader(file, "GB2312"));  
      38.   
      39.     //将文件数据读入内存   
      40.     readData();  
      41.     }  
      42.   
      43.     public List<LyricStatement> getStatements() {  
      44.     return statements;  
      45.     }  
      46.   
      47.     /* 
      48.      * 读取文件中数据至内存.  
      49.      */  
      50.     private void readData() throws IOException {  
      51.     statements.clear();  
      52.     String strLine;  
      53.     //循环读入所有行   
      54.     while (null != (strLine = bufferReader.readLine())) {  
      55.         //判断该行是否为空行   
      56.         if ("".equals(strLine.trim())) {  
      57.         continue;  
      58.         }  
      59.         //判断该行数据是否表示歌名   
      60.         if (null == title || "".equals(title.trim())) {  
      61.         Pattern pattern = Pattern.compile("\\[ti:(.+?)\\]");  
      62.         Matcher matcher = pattern.matcher(strLine);  
      63.         if (matcher.find()) {  
      64.             title = matcher.group(1);  
      65.             continue;  
      66.         }  
      67.         }  
      68.         //判断该行数据是否表示演唱者   
      69.         if (null == artist || "".equals(artist.trim())) {  
      70.         Pattern pattern = Pattern.compile("\\[ar:(.+?)\\]");  
      71.         Matcher matcher = pattern.matcher(strLine);  
      72.         if (matcher.find()) {  
      73.             artist = matcher.group(1);  
      74.             continue;  
      75.         }  
      76.         }  
      77.         //判断该行数据是否表示专辑   
      78.         if (null == album || "".equals(album.trim())) {  
      79.         Pattern pattern = Pattern.compile("\\[al:(.+?)\\]");  
      80.         Matcher matcher = pattern.matcher(strLine);  
      81.         if (matcher.find()) {  
      82.             album = matcher.group(1);  
      83.             continue;  
      84.         }  
      85.         }  
      86.         //判断该行数据是否表示歌词制作者   
      87.         if (null == lrcMaker || "".equals(lrcMaker.trim())) {  
      88.         Pattern pattern = Pattern.compile("\\[by:(.+?)\\]");  
      89.         Matcher matcher = pattern.matcher(strLine);  
      90.         if (matcher.find()) {  
      91.             lrcMaker = matcher.group(1);  
      92.             continue;  
      93.         }  
      94.         }  
      95.         //读取并分析歌词   
      96.         int timeNum = 0;                                        //本行含时间个数   
      97.         String str[] = strLine.split("\\]");                //以]分隔   
      98.         for (int i = 0; i < str.length; ++i) {  
      99.         String str2[] = str[i].split("\\[");            //以[分隔   
      100.         str[i] = str2[str2.length - 1];  
      101.         if (isTime(str[i])) {  
      102.             ++timeNum;  
      103.         }  
      104.         }  
      105.         for (int i = 0; i < timeNum; ++i) //处理歌词复用的情况   
      106.         {  
      107.         LyricStatement sm = new LyricStatement();  
      108.         sm.setTime(str[i]);  
      109.         if (timeNum < str.length) //如果有歌词   
      110.         {  
      111.             sm.setLyric(str[str.length - 1]);  
      112.         }  
      113.         statements.add(sm);  
      114.         }  
      115. //          if(1==str.length)                                   //处理没有歌词的情况   
      116. //          {   
      117. //              Statement sm = new Statement();   
      118. //              sm.setTime(str[0]);   
      119. //              sm.setLyric("");   
      120. //              statements.add(sm);   
      121. //          }   
      122.     }  
      123.   
      124.     //将读取的歌词按时间排序   
      125.     sortLyric();  
      126.     }  
      127.     /* 
      128.      * 判断给定的字符串是否表示时间.  
      129.      */  
      130.   
      131.     private boolean isTime(String string) {  
      132.     String str[] = string.split(":|\\.");  
      133.     if (3 != str.length) {  
      134.         return false;  
      135.     }  
      136.     try {  
      137.         for (int i = 0; i < str.length; ++i) {  
      138.         Integer.parseInt(str[i]);  
      139.         }  
      140.     } catch (NumberFormatException e) {  
      141.         return false;  
      142.     }  
      143.     return true;  
      144.     }  
      145.     /* 
      146.      * 将读取的歌词按时间排序.  
      147.      */  
      148.   
      149.     private void sortLyric() {  
      150.     for (int i = 0; i < statements.size() - 1; ++i) {  
      151.         int index = i;  
      152.         double delta = Double.MAX_VALUE;  
      153.         boolean moveFlag = false;  
      154.         for (int j = i + 1; j < statements.size(); ++j) {  
      155.         double sub;  
      156.         if (0 >= (sub = statements.get(i).getTime() - statements.get(j).getTime())) {  
      157.             continue;  
      158.         }  
      159.         moveFlag = true;  
      160.         if (sub < delta) {  
      161.             delta = sub;  
      162.             index = j + 1;  
      163.         }  
      164.         }  
      165.         if (moveFlag) {  
      166.         statements.add(index, statements.get(i));  
      167.         statements.remove(i);  
      168.         --i;  
      169.         }  
      170.     }  
      171.     }  
      172.     /* 
      173.      * 打印整个歌词文件 
      174.      */  
      175.   
      176.     private void printLrcDate() {  
      177.     System.out.println("歌曲名: " + title);  
      178.     System.out.println("演唱者: " + artist);  
      179.     System.out.println("专辑名: " + album);  
      180.     System.out.println("歌词制作: " + lrcMaker);  
      181.     for (int i = 0; i < statements.size(); ++i) {  
      182.         statements.get(i).printLyric();  
      183.     }  
      184.     }  
      185.   
      186.     /** 
      187.      * @param args 
      188.      * @throws IOException  
      189.      */  
      190.     public static void main(String[] args) throws IOException {  
      191.     /* 
      192.      * 测试"[", "]"的ASCII码 
      193.      */  
      194. //      {   
      195. //          char a='[', b = ']';   
      196. //          int na = (int)a;   
      197. //          int nb = (int)b;   
      198. //          System.out.println("a="+na+", b="+nb+"\n");   
      199. //      }   
      200.         /* 
      201.      * 测试匹配[]. 注: [应用\[表示. 同理]应用\]表示.  
      202.      */  
      203. //      {   
      204. //          String strLyric = "[02:13.41][02:13.42][02:13.43]错误的泪不想哭却硬要留住";   
      205. //          String str[] = strLyric.split("\\]");   
      206. //          for(int i=0; i<str.length; ++i)   
      207. //          {   
      208. //              String str2[] = str[i].split("\\[");   
      209. //              str[i] = str2[str2.length-1];   
      210. //              System.out.println(str[i]+" ");   
      211. //          }   
      212. //      }   
      213.         /* 
      214.      * 测试匹配[ti:]. 注: [应用\[表示. 同理]应用\]表示.  
      215.      */  
      216. //      {   
      217. //          String strLyric = "[ti:Forget]";   
      218. //          Pattern pattern = Pattern.compile("\\[ti:(.+?)\\]");   
      219. //          Matcher matcher = pattern.matcher(strLyric);   
      220. //          if(matcher.find())   
      221. //            System.out.println(matcher.group(1));   
      222. //      }   
      223.         /* 
      224.      * 测试排序算法 
      225.      */  
      226. //      {   
      227. //          Vector<Double> vect=new Vector<Double>();   
      228. //          vect.add(5.0);   
      229. //          vect.add(28.0);   
      230. //          vect.add(37.0);   
      231. //          vect.add(10.0);   
      232. //          vect.add(25.0);   
      233. //          vect.add(40.0);   
      234. //          vect.add(27.0);   
      235. //          vect.add(35.0);   
      236. //          vect.add(70.0);   
      237. //          vect.add(99.0);   
      238. //          vect.add(100.0);   
      239. //             
      240. //          for(int i=0;i<vect.size();++i)   
      241. //          {   
      242. //              System.out.println(vect.elementAt(i));   
      243. //          }   
      244. //             
      245. //          for(int i=0;i<vect.size()-1;++i)   
      246. //          {   
      247. //              int index=i;   
      248. //              double delta=Double.MAX_VALUE;   
      249. //              boolean moveFlag = false;   
      250. //              for(int j=i+1;j<vect.size();++j)   
      251. //              {   
      252. //                  double sub;   
      253. //                  if(0>=(sub=vect.get(i)-vect.get(j)))   
      254. //                  {   
      255. //                      continue;   
      256. //                  }   
      257. //                  moveFlag=true;   
      258. //                  if(sub<delta)   
      259. //                  {   
      260. //                      delta=sub;   
      261. //                      index=j+1;   
      262. //                  }   
      263. //              }   
      264. //              if(moveFlag)   
      265. //              {   
      266. //                  vect.add(index, vect.elementAt(i));   
      267. //                  vect.remove(i);    
      268. //                  System.out.println("第"+i);   
      269. //                  --i;   
      270. //              }   
      271. //          }   
      272. //   
      273. //          System.out.println("排序后");   
      274. //          for(int i=0;i<vect.size();++i)   
      275. //          {   
      276. //              System.out.println(vect.elementAt(i));   
      277. //          }   
      278. //      }   
      279.   
      280.     /* 
      281.      * 测试由字符串转化为双精度时间 
      282.      */  
      283. //      {   
      284. //          String stime="02:03.09";   
      285. //          String str[] = stime.split(":|\\.");   
      286. //          for(int i=0;i<str.length;++i)   
      287. //          {   
      288. //              System.out.print("时间"+str[i]+":");   
      289. //          }   
      290. //          double dtime = Integer.parseInt(str[0])*60+Integer.parseInt(str[1])+Integer.parseInt(str[2])*0.01;   
      291. //          System.out.println("time="+dtime);   
      292. //      }   
      293.   
      294.     /* 
      295.      * 测试整个类 
      296.      */  
      297.     {  
      298.   
      299.         LyricReader ld = new LyricReader("D:\\music\\海盗.lrc");              //路径\\输入文件名   
      300.         ld.printLrcDate();  
      301.     }  
      302.     }  
      303. }  
      /*
       * To change this template, choose Tools | Templates
       * and open the template in the editor.
       */
      package musicbox.model.lyric;
      
      import java.io.BufferedReader;
      import java.io.FileInputStream;
      import java.io.IOException;
      import java.io.InputStreamReader;
      import java.net.URLDecoder;
      import java.util.ArrayList;
      import java.util.List;
      import java.util.regex.Matcher;
      import java.util.regex.Pattern;
      
      /**
       *
       * @author Randyzhao
       */
      public class LyricReader {
      
          BufferedReader bufferReader = null;							//读取文件实例
          public String title = "";									//歌曲题目
          public String artist = "";									//演唱者
          public String album = "";									//专辑
          public String lrcMaker = "";								//歌词制作者
          List<LyricStatement> statements = new ArrayList<LyricStatement>();		//歌词
          /* 
           * 实例化一个歌词数据. 歌词数据信息由指定的文件提供. 
           * fileName: 指定的歌词文件. 
           */
      
          public LyricReader(String fileName) throws IOException {
      	//in case the space in the fileName is replaced by the %20
      	FileInputStream file = new FileInputStream(URLDecoder.decode(fileName, "UTF-8"));
      	bufferReader = new BufferedReader(new InputStreamReader(file, "GB2312"));
      
      	//将文件数据读入内存
      	readData();
          }
      
          public List<LyricStatement> getStatements() {
      	return statements;
          }
      
          /*
           * 读取文件中数据至内存. 
           */
          private void readData() throws IOException {
      	statements.clear();
      	String strLine;
      	//循环读入所有行
      	while (null != (strLine = bufferReader.readLine())) {
      	    //判断该行是否为空行
      	    if ("".equals(strLine.trim())) {
      		continue;
      	    }
      	    //判断该行数据是否表示歌名
      	    if (null == title || "".equals(title.trim())) {
      		Pattern pattern = Pattern.compile("\\[ti:(.+?)\\]");
      		Matcher matcher = pattern.matcher(strLine);
      		if (matcher.find()) {
      		    title = matcher.group(1);
      		    continue;
      		}
      	    }
      	    //判断该行数据是否表示演唱者
      	    if (null == artist || "".equals(artist.trim())) {
      		Pattern pattern = Pattern.compile("\\[ar:(.+?)\\]");
      		Matcher matcher = pattern.matcher(strLine);
      		if (matcher.find()) {
      		    artist = matcher.group(1);
      		    continue;
      		}
      	    }
      	    //判断该行数据是否表示专辑
      	    if (null == album || "".equals(album.trim())) {
      		Pattern pattern = Pattern.compile("\\[al:(.+?)\\]");
      		Matcher matcher = pattern.matcher(strLine);
      		if (matcher.find()) {
      		    album = matcher.group(1);
      		    continue;
      		}
      	    }
      	    //判断该行数据是否表示歌词制作者
      	    if (null == lrcMaker || "".equals(lrcMaker.trim())) {
      		Pattern pattern = Pattern.compile("\\[by:(.+?)\\]");
      		Matcher matcher = pattern.matcher(strLine);
      		if (matcher.find()) {
      		    lrcMaker = matcher.group(1);
      		    continue;
      		}
      	    }
      	    //读取并分析歌词
      	    int timeNum = 0;										//本行含时间个数
      	    String str[] = strLine.split("\\]");				//以]分隔
      	    for (int i = 0; i < str.length; ++i) {
      		String str2[] = str[i].split("\\[");			//以[分隔
      		str[i] = str2[str2.length - 1];
      		if (isTime(str[i])) {
      		    ++timeNum;
      		}
      	    }
      	    for (int i = 0; i < timeNum; ++i) //处理歌词复用的情况
      	    {
      		LyricStatement sm = new LyricStatement();
      		sm.setTime(str[i]);
      		if (timeNum < str.length) //如果有歌词
      		{
      		    sm.setLyric(str[str.length - 1]);
      		}
      		statements.add(sm);
      	    }
      //			if(1==str.length)									//处理没有歌词的情况
      //			{
      //				Statement sm = new Statement();
      //				sm.setTime(str[0]);
      //				sm.setLyric("");
      //				statements.add(sm);
      //			}
      	}
      
      	//将读取的歌词按时间排序
      	sortLyric();
          }
          /*
           * 判断给定的字符串是否表示时间. 
           */
      
          private boolean isTime(String string) {
      	String str[] = string.split(":|\\.");
      	if (3 != str.length) {
      	    return false;
      	}
      	try {
      	    for (int i = 0; i < str.length; ++i) {
      		Integer.parseInt(str[i]);
      	    }
      	} catch (NumberFormatException e) {
      	    return false;
      	}
      	return true;
          }
          /*
           * 将读取的歌词按时间排序. 
           */
      
          private void sortLyric() {
      	for (int i = 0; i < statements.size() - 1; ++i) {
      	    int index = i;
      	    double delta = Double.MAX_VALUE;
      	    boolean moveFlag = false;
      	    for (int j = i + 1; j < statements.size(); ++j) {
      		double sub;
      		if (0 >= (sub = statements.get(i).getTime() - statements.get(j).getTime())) {
      		    continue;
      		}
      		moveFlag = true;
      		if (sub < delta) {
      		    delta = sub;
      		    index = j + 1;
      		}
      	    }
      	    if (moveFlag) {
      		statements.add(index, statements.get(i));
      		statements.remove(i);
      		--i;
      	    }
      	}
          }
          /*
           * 打印整个歌词文件
           */
      
          private void printLrcDate() {
      	System.out.println("歌曲名: " + title);
      	System.out.println("演唱者: " + artist);
      	System.out.println("专辑名: " + album);
      	System.out.println("歌词制作: " + lrcMaker);
      	for (int i = 0; i < statements.size(); ++i) {
      	    statements.get(i).printLyric();
      	}
          }
      
          /**
           * @param args
           * @throws IOException 
           */
          public static void main(String[] args) throws IOException {
      	/*
      	 * 测试"[", "]"的ASCII码
      	 */
      //		{
      //			char a='[', b = ']';
      //			int na = (int)a;
      //			int nb = (int)b;
      //			System.out.println("a="+na+", b="+nb+"\n");
      //		}
      		/*
      	 * 测试匹配[]. 注: [应用\[表示. 同理]应用\]表示. 
      	 */
      //		{
      //			String strLyric = "[02:13.41][02:13.42][02:13.43]错误的泪不想哭却硬要留住";
      //			String str[] = strLyric.split("\\]");
      //			for(int i=0; i<str.length; ++i)
      //			{
      //				String str2[] = str[i].split("\\[");
      //				str[i] = str2[str2.length-1];
      //				System.out.println(str[i]+" ");
      //			}
      //		}
      		/*
      	 * 测试匹配[ti:]. 注: [应用\[表示. 同理]应用\]表示. 
      	 */
      //		{
      //			String strLyric = "[ti:Forget]";
      //			Pattern pattern = Pattern.compile("\\[ti:(.+?)\\]");
      //			Matcher matcher = pattern.matcher(strLyric);
      //			if(matcher.find())
      //			  System.out.println(matcher.group(1));
      //		}
      		/*
      	 * 测试排序算法
      	 */
      //		{
      //			Vector<Double> vect=new Vector<Double>();
      //			vect.add(5.0);
      //			vect.add(28.0);
      //			vect.add(37.0);
      //			vect.add(10.0);
      //			vect.add(25.0);
      //			vect.add(40.0);
      //			vect.add(27.0);
      //			vect.add(35.0);
      //			vect.add(70.0);
      //			vect.add(99.0);
      //			vect.add(100.0);
      //			
      //			for(int i=0;i<vect.size();++i)
      //			{
      //				System.out.println(vect.elementAt(i));
      //			}
      //			
      //			for(int i=0;i<vect.size()-1;++i)
      //			{
      //				int index=i;
      //				double delta=Double.MAX_VALUE;
      //				boolean moveFlag = false;
      //				for(int j=i+1;j<vect.size();++j)
      //				{
      //					double sub;
      //					if(0>=(sub=vect.get(i)-vect.get(j)))
      //					{
      //						continue;
      //					}
      //					moveFlag=true;
      //					if(sub<delta)
      //					{
      //						delta=sub;
      //						index=j+1;
      //					}
      //				}
      //				if(moveFlag)
      //				{
      //					vect.add(index, vect.elementAt(i));
      //					vect.remove(i);	
      //					System.out.println("第"+i);
      //					--i;
      //				}
      //			}
      //
      //			System.out.println("排序后");
      //			for(int i=0;i<vect.size();++i)
      //			{
      //				System.out.println(vect.elementAt(i));
      //			}
      //		}
      
      	/*
      	 * 测试由字符串转化为双精度时间
      	 */
      //		{
      //			String stime="02:03.09";
      //			String str[] = stime.split(":|\\.");
      //			for(int i=0;i<str.length;++i)
      //			{
      //				System.out.print("时间"+str[i]+":");
      //			}
      //			double dtime = Integer.parseInt(str[0])*60+Integer.parseInt(str[1])+Integer.parseInt(str[2])*0.01;
      //			System.out.println("time="+dtime);
      //		}
      
      	/*
      	 * 测试整个类
      	 */
      	{
      
      	    LyricReader ld = new LyricReader("D:\\music\\海盗.lrc");				//路径\\输入文件名
      	    ld.printLrcDate();
      	}
          }
      }
      
      有了歌词解释器和一个歌词列表,下面就可以进行歌词显示控件的设计了。

      由于在Swing框架中设计歌词显示控件,那么最好的选择就是继承一个JPanel控件。当需要刷新屏幕上歌词的时候将多行歌词绘制在一个Image上面,然后重写paint函数。

      以下是程序代码。

      1. /* 
      2.  * To change this template, choose Tools | Templates 
      3.  * and open the template in the editor. 
      4.  */  
      5. package musicbox.view;  
      6.   
      7. import java.awt.AlphaComposite;  
      8. import java.awt.Color;  
      9. import java.awt.Dimension;  
      10. import java.awt.Font;  
      11. import java.awt.FontMetrics;  
      12. import java.awt.Graphics;  
      13. import java.awt.Graphics2D;  
      14. import java.awt.Image;  
      15. import java.awt.RenderingHints;  
      16. import java.io.File;  
      17. import java.io.IOException;  
      18. import java.net.URISyntaxException;  
      19. import java.net.URL;  
      20. import java.util.List;  
      21. import java.util.logging.Level;  
      22. import java.util.logging.Logger;  
      23. import javax.imageio.ImageIO;  
      24. import javax.swing.JPanel;  
      25. import musicbox.model.lyric.LyricStatement;  
      26.   
      27. /** 
      28.  * Used to display the lyric 
      29.  * @author Randyzhao 
      30.  */  
      31. public class LyricDisplayer extends JPanel {  
      32.   
      33.     protected final Color CURRENT_LINE_COLOR = Color.green;  
      34.     protected final Color OTHER_LINE_COLOR = Color.GRAY;  
      35.     //the lines other than the current line to be displayed   
      36.     protected final int UP_DOWN_LINES = 8;  
      37.     //the list of lyric statements to be displayed   
      38.     protected List<LyricStatement> statements;  
      39.     //the index of next statement to be dispalyed in the statements   
      40.     protected int index;  
      41.     protected Image backgroundImage = null;  
      42.     private String backGroundImagePath = null;  
      43.     protected Image bufferImage = null;  
      44.     //the size when the bufferImage is drawn   
      45.     private Dimension bufferedSize;  
      46.   
      47.     public String getBackGroundImagePath() {  
      48.     return backGroundImagePath;  
      49.     }  
      50.   
      51.     public void setBackGroundImagePath(String backGroundImagePath) {  
      52.     this.backGroundImagePath = backGroundImagePath;  
      53.     }  
      54.   
      55.     /** 
      56.      * get ready to display 
      57.      * @param statements  
      58.      */  
      59.     public void prepareDisplay(List<LyricStatement> statements) {  
      60.     this.statements = statements;  
      61.     this.index = -1;  
      62.     this.setFont(new Font("微软雅黑", Font.PLAIN, 20));  
      63.     }  
      64.   
      65.     /** 
      66.      * display a lyric by the index 
      67.      * @param index  
      68.      */  
      69.     public void displayLyric(int index) {  
      70.   
      71.     this.index = index;  
      72.     this.drawBufferImage();  
      73. //  System.out.println("draw " + index + " " + this.statements.get(index).getLyric());   
      74.     this.paint(this.getGraphics());  
      75.     }  
      76.   
      77.     /** 
      78.      * draw a line of lyric in the middle of the Graphics2D 
      79.      * @param lyric 
      80.      * @param g2d  
      81.      */  
      82.     protected void drawLineInMiddle(int height, String lyric, Graphics2D g2d, Color color) {  
      83.     int width = this.getWidth();  
      84.     FontMetrics fm = g2d.getFontMetrics();  
      85.     g2d.setColor(color);  
      86.     int x = (this.getWidth() - fm.stringWidth(lyric)) / 2;  
      87.     g2d.drawString(lyric, x, height);  
      88.     }  
      89.   
      90.     /** 
      91.      * Draw the buffered image. Used to realize the double-buffering. 
      92.      */  
      93.     protected void drawBufferImage() {  
      94.     Image tempBufferedImage = this.createImage(this.getWidth(), this.getHeight());  
      95.     this.bufferedSize = this.getSize();  
      96.     if (this.backgroundImage == null) {  
      97.         //get background image   
      98.         URL url = getClass().getResource(this.backGroundImagePath);  
      99.   
      100.         try {  
      101.         backgroundImage = ImageIO.read(url);  
      102.         //缩放图片   
      103.         backgroundImage = backgroundImage.getScaledInstance(this.getWidth(), this.getHeight(), 20);  
      104.         } catch (IOException ex) {  
      105.         ex.printStackTrace();  
      106.         }  
      107.   
      108.   
      109.     }  
      110.     Graphics2D g2d = (Graphics2D) tempBufferedImage.getGraphics();  
      111.     g2d.setFont(new Font("楷体", Font.PLAIN, 25));  
      112.     g2d.drawImage(this.backgroundImage, 00this.getWidth(), this.getHeight(), null);  
      113.   
      114.     g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,  
      115.         RenderingHints.VALUE_ANTIALIAS_ON);  
      116.     if (this.statements != null && this.statements.size() != 0) {  
      117.         //draw current line   
      118.         g2d.setFont(new Font("楷体", Font.PLAIN, 35));  
      119.         this.drawLineInMiddle(this.getHeight() / 2,  
      120.             this.statements.get(index).getLyric(), g2d, this.CURRENT_LINE_COLOR);  
      121.         int perHeight = g2d.getFontMetrics().getHeight() + 5;  
      122.         g2d.setFont(new Font("楷体", Font.PLAIN, 25));  
      123.         //draw down lines   
      124.         for (int i = index - UP_DOWN_LINES; i < index; i++) {  
      125.         if (i < 0) {  
      126.             continue;  
      127.         }  
      128.         if (index - i > UP_DOWN_LINES / 2) {  
      129.             //set transparance   
      130.             float ratio = (float) (i - index + UP_DOWN_LINES) / (UP_DOWN_LINES / 2) / 1.2f;  
      131.             g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,  
      132.                 ratio));  
      133.         } else {  
      134.             g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,  
      135.                 1.0f));  
      136.         }  
      137.         this.drawLineInMiddle(this.getHeight() / 2 - (index - i) * perHeight,  
      138.             this.statements.get(i).getLyric(), g2d, this.OTHER_LINE_COLOR);  
      139.         }  
      140.         //draw up lines   
      141.         for (int i = index + 1; i < index + UP_DOWN_LINES; i++) {  
      142.         if (i >= this.statements.size()) {  
      143.             break;  
      144.         }  
      145.         if (i - index > UP_DOWN_LINES / 2) {  
      146.             //set transparance   
      147.             float ratio = (float) (index + UP_DOWN_LINES - i) / (UP_DOWN_LINES / 2) / 1.2f;  
      148.             g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,  
      149.                 ratio));  
      150.         } else {  
      151.             g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,  
      152.                 1.0f));  
      153.         }  
      154.         this.drawLineInMiddle(this.getHeight() / 2 + (i - index) * perHeight,  
      155.             this.statements.get(i).getLyric(), g2d, this.OTHER_LINE_COLOR);  
      156.         }  
      157.     } else {  
      158.         //statements is empty   
      159.         this.drawLineInMiddle(this.getHeight() / 2,  
      160.             "未找到相应的歌词文件", g2d, this.CURRENT_LINE_COLOR);  
      161.     }  
      162.   
      163.     //copyt the buffered image   
      164.     this.bufferImage = tempBufferedImage;  
      165.     }  
      166.   
      167.     /** 
      168.      * This method is override in order to display the lyric in the panel 
      169.      * @param g  
      170.      */  
      171.     @Override  
      172.     public void paint(Graphics g) {  
      173.     if (this.isVisible() == false) {  
      174.         return;  
      175.     }  
      176.     super.paint(g);  
      177.   
      178.     //draw buffered image    
      179.     if (this.bufferImage == null || this.getWidth() != this.bufferedSize.getWidth()  
      180.         || this.getHeight() != this.bufferedSize.getHeight()) {  
      181.         this.drawBufferImage();  
      182.     }  
      183.     //copy the double buffer   
      184.     g.drawImage(bufferImage, 00null);  
      185.   
      186.     }  
      187. }  
      /*
       * To change this template, choose Tools | Templates
       * and open the template in the editor.
       */
      package musicbox.view;
      
      import java.awt.AlphaComposite;
      import java.awt.Color;
      import java.awt.Dimension;
      import java.awt.Font;
      import java.awt.FontMetrics;
      import java.awt.Graphics;
      import java.awt.Graphics2D;
      import java.awt.Image;
      import java.awt.RenderingHints;
      import java.io.File;
      import java.io.IOException;
      import java.net.URISyntaxException;
      import java.net.URL;
      import java.util.List;
      import java.util.logging.Level;
      import java.util.logging.Logger;
      import javax.imageio.ImageIO;
      import javax.swing.JPanel;
      import musicbox.model.lyric.LyricStatement;
      
      /**
       * Used to display the lyric
       * @author Randyzhao
       */
      public class LyricDisplayer extends JPanel {
      
          protected final Color CURRENT_LINE_COLOR = Color.green;
          protected final Color OTHER_LINE_COLOR = Color.GRAY;
          //the lines other than the current line to be displayed
          protected final int UP_DOWN_LINES = 8;
          //the list of lyric statements to be displayed
          protected List<LyricStatement> statements;
          //the index of next statement to be dispalyed in the statements
          protected int index;
          protected Image backgroundImage = null;
          private String backGroundImagePath = null;
          protected Image bufferImage = null;
          //the size when the bufferImage is drawn
          private Dimension bufferedSize;
      
          public String getBackGroundImagePath() {
      	return backGroundImagePath;
          }
      
          public void setBackGroundImagePath(String backGroundImagePath) {
      	this.backGroundImagePath = backGroundImagePath;
          }
      
          /**
           * get ready to display
           * @param statements 
           */
          public void prepareDisplay(List<LyricStatement> statements) {
      	this.statements = statements;
      	this.index = -1;
      	this.setFont(new Font("微软雅黑", Font.PLAIN, 20));
          }
      
          /**
           * display a lyric by the index
           * @param index 
           */
          public void displayLyric(int index) {
      
      	this.index = index;
      	this.drawBufferImage();
      //	System.out.println("draw " + index + " " + this.statements.get(index).getLyric());
      	this.paint(this.getGraphics());
          }
      
          /**
           * draw a line of lyric in the middle of the Graphics2D
           * @param lyric
           * @param g2d 
           */
          protected void drawLineInMiddle(int height, String lyric, Graphics2D g2d, Color color) {
      	int width = this.getWidth();
      	FontMetrics fm = g2d.getFontMetrics();
      	g2d.setColor(color);
      	int x = (this.getWidth() - fm.stringWidth(lyric)) / 2;
      	g2d.drawString(lyric, x, height);
          }
      
          /**
           * Draw the buffered image. Used to realize the double-buffering.
           */
          protected void drawBufferImage() {
      	Image tempBufferedImage = this.createImage(this.getWidth(), this.getHeight());
      	this.bufferedSize = this.getSize();
      	if (this.backgroundImage == null) {
      	    //get background image
      	    URL url = getClass().getResource(this.backGroundImagePath);
      
      	    try {
      		backgroundImage = ImageIO.read(url);
      		//缩放图片
      		backgroundImage = backgroundImage.getScaledInstance(this.getWidth(), this.getHeight(), 20);
      	    } catch (IOException ex) {
      		ex.printStackTrace();
      	    }
      
      
      	}
      	Graphics2D g2d = (Graphics2D) tempBufferedImage.getGraphics();
      	g2d.setFont(new Font("楷体", Font.PLAIN, 25));
      	g2d.drawImage(this.backgroundImage, 0, 0, this.getWidth(), this.getHeight(), null);
      
      	g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
      		RenderingHints.VALUE_ANTIALIAS_ON);
      	if (this.statements != null && this.statements.size() != 0) {
      	    //draw current line
      	    g2d.setFont(new Font("楷体", Font.PLAIN, 35));
      	    this.drawLineInMiddle(this.getHeight() / 2,
      		    this.statements.get(index).getLyric(), g2d, this.CURRENT_LINE_COLOR);
      	    int perHeight = g2d.getFontMetrics().getHeight() + 5;
      	    g2d.setFont(new Font("楷体", Font.PLAIN, 25));
      	    //draw down lines
      	    for (int i = index - UP_DOWN_LINES; i < index; i++) {
      		if (i < 0) {
      		    continue;
      		}
      		if (index - i > UP_DOWN_LINES / 2) {
      		    //set transparance
      		    float ratio = (float) (i - index + UP_DOWN_LINES) / (UP_DOWN_LINES / 2) / 1.2f;
      		    g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
      			    ratio));
      		} else {
      		    g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
      			    1.0f));
      		}
      		this.drawLineInMiddle(this.getHeight() / 2 - (index - i) * perHeight,
      			this.statements.get(i).getLyric(), g2d, this.OTHER_LINE_COLOR);
      	    }
      	    //draw up lines
      	    for (int i = index + 1; i < index + UP_DOWN_LINES; i++) {
      		if (i >= this.statements.size()) {
      		    break;
      		}
      		if (i - index > UP_DOWN_LINES / 2) {
      		    //set transparance
      		    float ratio = (float) (index + UP_DOWN_LINES - i) / (UP_DOWN_LINES / 2) / 1.2f;
      		    g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
      			    ratio));
      		} else {
      		    g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
      			    1.0f));
      		}
      		this.drawLineInMiddle(this.getHeight() / 2 + (i - index) * perHeight,
      			this.statements.get(i).getLyric(), g2d, this.OTHER_LINE_COLOR);
      	    }
      	} else {
      	    //statements is empty
      	    this.drawLineInMiddle(this.getHeight() / 2,
      		    "未找到相应的歌词文件", g2d, this.CURRENT_LINE_COLOR);
      	}
      
      	//copyt the buffered image
      	this.bufferImage = tempBufferedImage;
          }
      
          /**
           * This method is override in order to display the lyric in the panel
           * @param g 
           */
          @Override
          public void paint(Graphics g) {
      	if (this.isVisible() == false) {
      	    return;
      	}
      	super.paint(g);
      
      	//draw buffered image 
      	if (this.bufferImage == null || this.getWidth() != this.bufferedSize.getWidth()
      		|| this.getHeight() != this.bufferedSize.getHeight()) {
      	    this.drawBufferImage();
      	}
      	//copy the double buffer
      	g.drawImage(bufferImage, 0, 0, null);
      
          }
      }
      
      下面进行简单的解释。

      当LyricDisplayer的实例初始化之后,外部代码应该调用它的prepareDisplay函数。告诉它显示的歌词列表,调用setBackGroundImagePath函数,告诉它歌词背景图片所在的位置。


      之后当需要显示某一句歌词的时候,调用displayLyric函数,参数是prepareDisplay函数参数中歌词列表对应歌词的index。此时LyricDisplayer实例会调用自己的drawBufferImage函数来重新绘制Image。

      在绘制的时候,

      1. if (this.backgroundImage == null) {  
      2.     //get background image   
      3.     URL url = getClass().getResource(this.backGroundImagePath);  
      4.   
      5.     try {  
      6.     backgroundImage = ImageIO.read(url);  
      7.     //缩放图片   
      8.     backgroundImage = backgroundImage.getScaledInstance(this.getWidth(), this.getHeight(), 20);  
      9.     } catch (IOException ex) {  
      10.     ex.printStackTrace();  
      11.     }  
      12.   
      13.   
      14. }  
      	if (this.backgroundImage == null) {
      	    //get background image
      	    URL url = getClass().getResource(this.backGroundImagePath);
      
      	    try {
      		backgroundImage = ImageIO.read(url);
      		//缩放图片
      		backgroundImage = backgroundImage.getScaledInstance(this.getWidth(), this.getHeight(), 20);
      	    } catch (IOException ex) {
      		ex.printStackTrace();
      	    }
      
      
      	}

      这段代码用于从硬盘中读取背景文件并缩放至JPanel的大小。如果JPanel大小没有变化,而且之前已经初始化过背景图片,那么不要重复初始化。

      之后主要就是应用Graphics2D中的drawString函数来将一个字符串绘制在Image上面。

      1. FontMetrics fm = g2d.getFontMetrics();  
      FontMetrics fm = g2d.getFontMetrics();
      上面这语句初始化一个FontMerics对象,可以调用它的stringWidth函数来计算它对应的graphics2D对象中的一行字的高度,方便你计算绘制的位置。


      在调用drawString函数之前,你可以调用setComposite函数,如以下代码

      1. float ratio = (float) (index + UP_DOWN_LINES - i) / (UP_DOWN_LINES / 2) / 1.2f;  
      2.         g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,  
      3.             ratio));  
       float ratio = (float) (index + UP_DOWN_LINES - i) / (UP_DOWN_LINES / 2) / 1.2f;
      		    g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
      			    ratio));

      这样可以设置接下来绘制的字符串的透明度,这样就实现了淡入淡出效果。

      绘制完Image后调用paint函数将Image刷到屏幕上。这样的设计相当于实现了一个双缓冲。如果直接在JPanle上绘制那么屏幕一定会闪。

      在paint函数中

      1. if (this.bufferImage == null || this.getWidth() != this.bufferedSize.getWidth()  
      2.         || this.getHeight() != this.bufferedSize.getHeight()) {  
      3.         this.drawBufferImage();  
      4.     }  
      if (this.bufferImage == null || this.getWidth() != this.bufferedSize.getWidth()
      		|| this.getHeight() != this.bufferedSize.getHeight()) {
      	    this.drawBufferImage();
      	}

      这句话是判断如果原来已经绘制过Image并且JPanel尺寸和绘制Image的时候相比没有改变,那么不用重新绘制Image,直接把它刷到屏幕上来。
    展开全文
  • java设置frame背景图片-总结设置背景图片,实现背景图片+上层控件的层叠化效果
  • 为什么自定义控件无法显示

    万次阅读 2015-04-09 18:27:00
    自定义控件不显示 忘记在初始化内部元素之后进行 addView() 操作 忘记设置子控件 LayoutParams 宽高 子控件被其它控件所遮盖 自定义控件预览layout 预览layout布局 at android.view.ViewGroup....

    前言

    由于 Android 提供的控件并不多(其实已经很多了),但介于产品经理们的奇思妙想以及我们程序猿们为了不重复造轮子(其实是为了偷懒),或者仅仅是为了去探索下未知的领域,总之,一句话:我们需要自定义控件!!不过自定义控件的坑其实也是很大的,很多时候我们都不得不仰天长叹,为毛你(自定义控件)还TM不显示,老子已经为你花费了比陪女朋友还多的时间了(话说你有女朋友吗。。。);这次我们来梳理下为什么自定义控件(主要是组合控件,原谅楼主功力比较渣)不显示的各种原因。

    原因

    1. 忘记在初始化内部元素之后进行 addView() 操作,这个问题是我经常犯的错误,因为没有将子控件添加到父控件上,所以导致自定义控件无法显示子控件

    2. 忘记设置子控件 LayoutParams 宽高,举个栗子:我们在new 一个 TextView 的时候,需要设置它的宽和高,我们可以通过以下方式来设置 TextView 的宽高:
      RelativeLayout.LayoutParams lp = RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
      这里需要注意一点,就是LayoutParams有很多种,每个布局类型都有一个,例如:RelativeLayout.LayoutParams, LinearLayout.LayoutParams…另外还有一个ViewGroup.LayoutParams,要使用哪一个LayoutParams要看你当前编写的子控件的父控件是什么,如果它(父控件)是一个LinearLayout,那么这个子空间就应该使用LinearLayout.LayoutParams,如果父控件不属于五大布局类型,那么就使用ViewGroup.LayoutParams。

    3. 子控件被其它控件所遮盖,如果我们在自定义组合控件的时候使用inflate引入外部layout布局,那么我们应该很少出现这种问题,但有时候我们需要使用代码初始化子控件,进行布局,这时候由于没有直观的显示,我们可能误操作将某些子控件的宽高设置不到位,进而影响它自身或周边控件的显示

    4. 子控件背景色和父控件背景色一样,这个时候其实我已经成功的编写好了自定义控件,但是我有一个自定义属性可以引入Drawable来设置我的自定义控件中的子控件,由于我引入的Drawable是一个白色图案,我的自定义控件背景色也是白色的,所以看起来会以为是自定义控件除了问题(没想到这种情况都让我遇到 -_-||)

    以上就是我总结的一些可能导致自定义控件无法显示的原因,如果你有什么意见和建议,欢迎来留言讨论~

    异常

    在编写自定义控件的过程中,我也遇到过很多异常,这里谈一下还记得一些异常Exception:

    at android.view.ViewGroup.resetResolvedLayoutDirection(ViewGroup.java:6081)
    出现这个异常其实是自定义控件在初始化过程中错误的添加了自身:addView(this), 我们需要将子控件进行addView添加操作,但是对自定义控件自身进行addView操作是错误的。

    最后想起一件事情,那就是我们在 Android Studio 和 Eclipse 中的layout布局中使用自定义控件的时候,有的时候是可以预览layout布局的,正常显示自定义控件,但有的时候就不会显示或者显示旧版本的,这是因为,layout预览调用的时编译好的 Class 类,而我们刚刚编写或者修改的时候,其实并没有将自定义控件编译进去,这时候我们只要Build运行一下项目即可正常使用预览了。

    参考链接

    http://zhidao.baidu.com/link?url=tl1U-TWUbCIeGJOoGHK1pdiOu90D-G6GLwaEsq825gXTbbxn6wRyq4f_WPFhN37vfON3VOKEzGUa95zz8aTgEa

    展开全文
  • Android自定义控件文本居中显示

    千次阅读 2015-05-09 22:24:32
    第一次在这里写博客,各位大侠勿喷啊,小弟在...一时也知道从何开始,知道对于android自定义控件的了解还是挺薄弱的,就从此开始吧 1、先自定义一个view,先来个简单的吧,自定义一个TextView体验一下,这里列出了
  • Android显示网络图片一般都是将网络连接得到的输入流转为化位图对象(Bitmap),再将这个对象交给ImageView对象或其他控件显示。相信大家对ImageView显示网络图片的方法已经很了解了,百度教程也一大把(大多没有...
  • 今天在工作中遇到一个需求:点击按钮后改变页面中相关显示并改变点击按钮的背景图片显示,最后决定用checkbox来实现,但是修改背景图片后发现图片的大小显示不正常,还是按钮图片原始尺寸来显示...
  • Android 原创九宫格方块显示控件

    千次阅读 2013-12-07 22:54:06
    Android 原创九宫格方块显示控件
  • 使用matlabcontrol控件实现java与matlab混合编程一、首先附上参考过的文章二、matlabcontrol-4.1.0控件下载三、应用1.首先编写好matlab算法函数2.新建web project项目(或java项目),导入matlabcontrol控件3.前端...
  • JAVA布局中的控件问题

    2015-07-22 01:22:44
    设置FlowLayout布局后,添加了若干个组件,但只有第一个显示出来,设置布局也是只显示第一个,而且设置的窗口背景色也没效果,为什么? 代码如下: public class WindowUI extends JFrame { JLabel biaoqian1,...
  • Java自定义控件实现步骤

    千次阅读 2017-03-31 12:46:26
    开发自定义控件的步骤: 1、了解View的工作原理  2、 编写继承自View的子类 3、 为自定义View类增加属性  4、 绘制控件  5、 响应用户消息  6 、自定义回调函数    一、View结构原理 ...
  • JAVA Swing Datepicker(日历控件)

    万次阅读 热门讨论 2009-02-19 13:14:00
    此日历控件在www.eltima.com上给的Swing library4.0的jar包里提出出来 并重新封装在DatePicker类中 将必要的函数由私有状态提升为公开状态或将混淆过的代码重新命名. 下在地址:...
  • 基于JxBrowser的浏览器控件封装实现Java Swing的浏览器集成背景实现目标实现代码运行效果完整的代码及依赖jar文件下载 背景 进期客户提出在一个Java Swing项目要集成另外2个系统: 1、集成Fine Report(基于Java Web...
  • Java GUI之美化JButton控件

    千次阅读 多人点赞 2018-06-24 16:13:39
    Java ( Eclipse )环境下, Button 样式问题解释如下:在jframe中的button样式是可以随意改变的设置好了就是固定的样式。样式设置如下:1、对JButton大小的设置 因为JButen是属于小器件类型的,所以一般的setSize...
  • 效果图:下面看如何实现:控件:实现功能的xml:&lt;?xml version="1.0" encoding="utf-8"?&gt; &lt;layer-list xmlns:android="...//边框的颜色
  • java中设置JButton的背景图片,并在它上面显示文字

    万次阅读 多人点赞 2015-08-05 14:56:28
    //设置控件是否透明,true为不透明,false为透明 enter.setContentAreaFilled(false);//设置图片填满按钮所在的区域 enter.setMargin(new Insets(0, 0, 0, 0));//设置按钮边框和标签文字之间的距离 enter....
  • 目录 一、概述 前情提要: 思路: 通过java代码动态设置控件 style效果图: 二、代码示例 ...1.在attr.xml中声明自定义 style 属性 ...2. 在attr.xml中,为自定义 ...其实在Android控件支持通过java代码动态设...
  • 1.Composite设置背景透明(以避免设置子控件背景代码):   Java代码  ...Composite contenier = ... //设置背景模式为SWT.INHERIT_DEFAULT,则子控件背景显示模式为透明了.   contenier.setBackgroundMod
  • 看了以前的帖子@raistlic说是重绘问题,但是按帖子方法把setVisible放在添加控件后面起作用。使用getContentPane()invalidate.()总是提示找到符号。 ``` import java.awt.*; import java.awt.event.*; import ...
  • 转自http://blog.csdn.net/guijava/article/details/1802680    一、 SWT 简介 Java语言的声望和它在桌面应用程序(GUI程序)所取得的成
  • 注意,我这里指的高亮一部分,而不是全部高亮。你会怎么做?我知道会不会有一些初学者会想到的是,让这些子字符串分部于不同的TextView,再对每个TextView进行单独处理。当然,如果你已经是一个已经有一些经验的...
  •   因为需求的缘故,需要对liview显示项做黑白相间的处理: 其实就是在函数public View getView(int position, View convertView, ViewGroup parent) 中,加上: [java] view p
  • java swing 窗口背景色设置

    千次阅读 2019-12-02 14:33:04
    JButton jb = new JButton("按钮"); jb.setForeground(Color.RED);//设置前景色为红色 ... 看了其他教程,在Frame中应该是可以直接用setForeground来设置背景色,而在Jframe中是可以的 参考 https://blo...
  • java编写满足以下要求的...③ 鼠标进入按钮时,在该按钮上显示“★”,将该按钮背景色设置为红色。 ④ 鼠标移出按钮时,隐藏该按钮上的文字,将该按钮背景色设置为绿色。 说明:“★”可经 虚拟键盘 -> 特殊符号 输入
  • JavaSwing控件的属性配置和优势 最近学习了有关JavaGUI的相关的知识,个人感觉和Android控件的设置有点类似,可以设置布局,填充数据,以及设置监听事件等等,面对新的API,想通过近期的几篇博客把遇到的问题,和...
  • 背景项目整合react native时,将原生的一个九宫格图片显示,封装成了RN控件,并提供了一个source属性@ReactProp(name = "source")问题在js端使用该控件时,通过state初始化时给source赋值,然后,当添加图片是,通过...
  • 2.出现 JButton 按钮而不显示背景图片。 前提: 为 JPanel 设置背景图片常用的两种方式: 1.定义一个 ImageIcon 对象,并指定图片。把这个 ImageIcon 放到一个 JLabel 中,再将这个 JLabel 放到要显示的 ...
  • java写一个桌面软件exe(一),基本控件

    万次阅读 2018-12-14 19:12:00
    本文对java自带的javax.swing.*包下有图形开发组件,今天就对基本控件做些基本介绍, 基本控件 JFrame(主窗口) 这个控件为一个基本窗口控件,里面可以装除JFrame外的其他控件比如JPanel,JButton等,看个基本效果 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 34,672
精华内容 13,868
关键字:

java只显示背景不显示控件

java 订阅