Swing:LookAndFeel 教程第一篇——手把手教你写出自己的 LookAndFeel

本文是 LookAndFeel 系列教程的第一篇。

是我在对 Swing 学习摸索中的一些微薄经验。

我相信,细致看全然系列之后。你就能写出自己的 LookAndFeel。

你会发现 Swing 原来能够这样美。

--------------------------------------------------------------------------------

引言:

我第一次接触 Java 要追溯到非常多年前做毕业设计的时候。

那天我和同学来到了一个微型软件公司(三程序猿、一会计、一老总)。

第一次接触到了面向对象,第一次接触到了 Java,

非常奇妙的。在公司某技术大牛的帮助下完毕了一个 HelloWorld 之后,我便開始接触 Swing。

这位技术大牛能够说是我在 Java 之路上的领路人,我之后的代码风格非常大程度上就是受他的影响。

不得不说,从那时起,我就和 Swing 结下了不解之缘。

尽管毕设做完之后许久没有再碰 Java……

直到两年前,工作转型。从project实施转为软件开发,才又拾起了 Java,

第一个大项目就是 Swing 的项目,不得不说这就是缘分。

尽管阔别 Swing 多年,可是当年领路人的一句话我一直牢记于心:

“Swing 真正的精髓就是感观(LookAndFeel),假设能开发感观了,那才是真正的进入到 Swing 的核心领域。”

关于这一点。可能仁者见仁,智者见智。可是,它正是我指引我前进的航标。

所以,当公司的 Swing 项目提出了开发定制皮肤的需求时。我毫不犹豫的应下了这个差事。

这是何其巧妙与奇妙的缘分啊……

随着项目的展开。随着对 Swing 源代码的深入解读,LookAndFeel 的大门在我的面前渐渐清晰,并已在隐隐开启。

终于,历时两个月,我成功的完毕了人生的第一套 LookAndFeel。

尽管外观依旧不够美观,尽管功能还有不少缺陷,尽管代码还是略显幼稚,

可是门已打开,前方的道路一片光明……

所以今天写这篇文。就是为了告诉大家一些我的经验,告诉大家一个真正的 Swing。

我在开发这套 LookAndFeel 之前,尽管浅浅的接触过一些 Swing,可是也谈不上多深的基础。

在不断摸索中,走了不少歪路,但依旧还是在两个月内完毕了开发,所以……

假设你有良好的 Swing 基础。那你理解 LookAndFeel 的速度一定飞快;

假设你没有哪怕一点点的 Swing 基础,没有关系。我带领你从还有一个角度踏入 Swing 的领域;

假设你是不 Java 爱好者。那也无妨,Swing/LookAndFeel 优美的 MVC 模式会给你一个良好的编程思想。

就像这些年不断冒出的修真小说上描写叙述的那样——

不管是修仙,修禅,还是修魔——都是修真。

道虽不同,但大道相通。

--------------------------------------------------------------------------------

题外话:在看本文之前,你最好已经安装了一个 JDK。

由于安装了 JDK 你才干看到 Java 的源代码。你才干更好的理解本文。

JDK 安装之后。在其安装文件夹中有个 src.zip 压缩包。里面就放着 Java 源代码。

作为 LookAndFeel 教程的第一篇,本文要说些什么?

先列个提纲吧:

1、为什么要用 Swing 而不用 AWT

2、什么是 LookAndFeel

3、一个 Swing 控件是怎么由 MVC 结构组成的

4、一个 Swing 控件是怎样绘制——准确说是怎样通过 UI 类来绘制的

5、一个 Swing 控件是怎样获得相应于自己的那个 UI 类对象的

这是第一篇的主要内容,也是 LookAndFeel 的核心部分,

看完这篇,你至少会知道什么是 LookAndFeel。

尽管你可能临时还不能写出一个自己的 LookAndFeel。

可是。这就是開始。沿着这条路走下去,你肯定会成功。

以下開始正文:

--------------------------------------------------------------------------------

一、为什么要用 Swing 而不用 AWT

Java 官方的 GUI 有两套:AWT 以及 Swing。

在这里。我们稍微探讨一下这两者之间的历史关系,

可是不讨论官方的 GUI 和一些非官方的 GUI (比如 SWT)之间孰优孰劣。

我们今天要说的重点是——Swing。

为什么是 Swing 而不是 AWT 呢?

由于早在十多年前。Java 官方就发觉了 AWT 控件的缺陷——这种重量级方案想做到平台一致性。难度太大。

不但界面难以美观,为了保证平台一致性。官方开发者不断的去除 AWT 控件中可能会引起平台差异性的特性。最后导致 AWT 控件的功能也异常薄弱。

在这个时候,官方大胆的抛弃了 AWT 控件,在 AWT 事件机制的基础上推出了 Swing。

从此之后。官方仅仅对 Swing 进行更新维护,而停止了对 AWT 控件的更新维护。

也就是说,假设你如今还在学习 AWT 控件。那就等于是在学习一种被官方抛弃。并停止更新维护长达十年之久的技术。

Swing 是什么?

Swing 是 Java 官方推出的,绝大部分控件都由 Graphics2D 绘制的一种轻量级 GUI 方案。其全部的轻量级控件都继承自 JComponent 类。

须要注意的是,Swing 中依旧有三个重量级控件:JFrame。JDialog,JWindow。

只是它们都是窗口,他们都继承自 Window 类。

而不管是 JComponent 还是 Window 它们都继承自 Container 类,这事实上也就意味着:全部的 Swing 控件。都能够做控件容器。

--------------------------------------------------------------------------------

二、什么是 LookAndFeel

如今,我们直接切入重点:Swing 是怎样通过 Graphics2D 绘制这些控件的呢?

答案就是 LookAndFeel 机制。

那什么是 LookAndFeel 呢?

通俗的说,这就是皮肤;

从功能上说。这是一种批量管理 Swing 控件外观的机制;

从根源来说,这是 Swing 的核心。

官方正式推出的 LookAndFeel 在 Java 7 版本号中已经添加到了 4 套。

如今,让我们简单的预览一下它们的效果吧——

1、(官方)MetalLookAndFeel(Swing 默认的 LookAndFeel):

类路径:javax.swing.plaf.metal.MetalLookAndFeel

跨平台性:可跨平台

2、(官方)WindowsLookAndFeel:

类路径:com.sun.java.swing.plaf.windows.WindowsLookAndFeel

跨平台性:限于 Windows 平台

3、(官方)MotifLookAndFeel:

类路径:com.sun.java.swing.plaf.motif.MotifLookAndFeel

跨平台性:可跨平台

4、(官方)NimbusLookAndFeel:

类路径:javax.swing.plaf.nimbus.NimbusLookAndFeel

跨平台性:可跨平台

5、(本人任意之作)UltimaLookAndFeel:

跨平台性:可跨平台

6、最后看一下这个……

这是什么?这就是 AWT……

事实上不是我不想给它加上 Tab 页,实在是在 AWT 里找不到 Tab 页控件……

事实上也不是我不想给它加个标题边框,实在是找不到设置 AWT 控件边框的方法……

呵呵,看出为什么 AWT 会被抛弃了么?

话说 MetalLookAndFeel 作为 Swing 的默认 LookAndFeel 实在有些寒碜。

而官方在 Java 7 中正式推出的 NimbusLookAndFeel 则要美观的多。

而我的 UltimaLookAndFeel 是以 MetalLookAndFeel 为基础的一个优化版。

眼下有蓝色、绿色、黑色三种风格。

各位看到这里是不是有点激动了?原来 Swing 并非仅仅能像默认的 LookAndFeel 那样寒碜。

别急,放心,看完我的这一系列文章,你就能写出一个属于自己的 LookAndFeel。想多美观就能有多美观,仅仅要你有好的美工基础……

顺便说一下。由于我个人无美工基础。所以我的 UltimaLookAndFeel 外观以简洁为主。求美工……

你问怎样换 LookAndFeel?

在启动你的程序前:

UIManager.setLookAndFeel(…);

就可以

假设是在程序已经启动之后再换 LookAndFeel,那在上面那句之后,建议再加上:

SwingUtilities.updateComponentTreeUI(…);

--------------------------------------------------------------------------------

三、一个 Swing 控件是怎么由 MVC 结构组成的

说到 LookAndFeel 又不得不提一下 Swing 优美的 MVC 模式。

所谓 MVC 就是:模型(Model)、视图(View)、控制器(Controller)这种一种结构。

Swing 中,差点儿全部的控件都能够清晰的分解成这三大部分。

就拿 JButton 来举例,我们能够这样分解:

JButton——控制器;

ButtonModel——模型。其最常见的详细实现类是:DefaultButtonModel。

ButtonUI——视图,其最常见的详细实现类是:MetalButtonUI;

JButton 负责控制,ButtonModel 提供模型,而 ButtonUI 实现展示。

也就是说,基本上全部的 Swing 控件都是由一个 Control 类、一个 Model 类、一个 UI 类组成的。

部分过于简单和数据无关的控件无 Model 类,比如 JPanel……

全部的 Control 类你都非常熟悉。

大部分 Model 类你也非常熟悉。

而 UI 类。你可能不是非常熟悉。

没关系。今天之后,你将熟悉它们!

--------------------------------------------------------------------------------

四、一个 Swing 控件是怎样绘制——准确说是怎样通过 UI 类来绘制的

那 Swing 是怎样通过 UI 类来绘制控件的呢?

要说清晰这个问题。我们先来说一下 Swing 控件在 UI 线程中的绘制过程:

不管是 repaint 还是什么,在绘制控件时。终于都会产生一个 PaintEvent 然后排入 UI 线程的事件处理序列。

而 UI 线程在处理 Swing 控件的 PaintEvent 时。终于都会调用到控件的 paint 方法。

所以我们如今看一下 JComponent 的 paint 方法是怎么写的:

public void paint(Graphics g) {
    //……
    if(!rectangleIsObscured(clipX,clipY,clipW,clipH)) {
        if (!printing) {
            paintComponent(co);
            paintBorder(co);
        } else {
            printComponent(co);
            printBorder(co);
        }
    }
    if (!printing) {
        paintChildren(co);
    } else {
        printChildren(co);
    }
    //……
}

略去了非常多,但那不是重点,重点是在 paint 方法中调用了 paintComponent 方法。

好,再看看 paintComponent 方法:

protected void paintComponent(Graphics g) {
    if (ui != null) {
        Graphics scratchGraphics = (g == null) ? null : g.create();
        try {
            ui.update(scratchGraphics, this);
        }
        finally {
            scratchGraphics.dispose();
        }
    }
}

我们看到了什么?抓重点,那就是:

ui.update(scratchGraphics, this);

这个 ui 是什么?看 JComponent 中的声明:

protected transient ComponentUI ui;

原来是个 ComponentUI,顺便说一下,这个 ComponentUI 就是全部 UI 类的祖先类。

看这些是为了干什么?

OK,我们来总结一下:

一个控件要绘制。就必定调用到它的 paint 方法;

而默认的 paint 方法中会调用到 paintComponent 方法;

paintComponent 方法中又会调用 UI 类的 update 方法;

也就是说。一个控件的绘制,和其 UI 类中的 update 方法是息息相关的。

看,UI 类成功的和控件绘制关联上了。

好的。这个问题攻克了,我们看下一个问题吧。

--------------------------------------------------------------------------------

五、一个 Swing 控件是怎样获得相应于自己的那个 UI 类对象的

各个控件的 UI 类又是怎么被设置到 Control 类中的呢?

为了说明这个问题。我们再来看个样例,这次拿 JPanel 来开刀:

我们要看的是它的构造方法,不管我们怎么构造一个 JPanel,在其内部终于都是调用的这种方法:

public JPanel(LayoutManager layout, boolean isDoubleBuffered){
    setLayout(layout);
    setDoubleBuffered(isDoubleBuffered);
    setUIProperty("opaque", Boolean.TRUE);
    updateUI();
}

重点是最后一句:

updateUI();

事实上假设你去细致研究各个 Swing 控件的构造方法的源代码。会发现其终于都会调用到 updateUI 这种方法

所以如今重点转移了,来看 updateUI 方法:

public void updateUI() {
    setUI((PanelUI)UIManager.getUI(this));
}

setUI 的运行过程我就不再累述,有兴趣的能够自己看源代码,总之最后,它会对 ui 对象赋值。

这里出现了一个非常重要的类——UIManager。这是什么呢?

在 LookAndFeel 机制中,会有大量的键值对存放在一个 UIDefaults(事实上就是个 HashTable)中。

这些键值对记录了控件的边框、各种部分的颜色、字体等等,当中也包含了这个控件相应的 UI 类的类名。

而 UIManager 就是方便我们调用或替换这些键值对的一个管理工具类。

UIManager.getUI(this) 又是怎样返回一个 UI 类的对象呢?

我们来看看:

public static ComponentUI getUI(JComponent target) {
    maybeInitialize();
    ComponentUI ui = null;
    LookAndFeel multiLAF = getLAFState().multiLookAndFeel;
    if (multiLAF != null) {
        ui =multiLAF.getDefaults().getUI(target);
    }
    if (ui == null) {
        ui = getDefaults().getUI(target);
    }
    return ui;
}

如今大家应该都学会抓重点了吧?

重点是:

getDefaults().getUI(target);

它又是怎么运行的呢?

public ComponentUI getUI(JComponent target) {
    Object cl = get("ClassLoader");
    ClassLoader uiClassLoader =
        (cl != null) ?

(ClassLoader)cl :target.getClass().getClassLoader();
    Class<? extends ComponentUI> uiClass =
        getUIClass(target.getUIClassID(), uiClassLoader);
    Object uiObject = null;
    //……略去反射部分的源代码
    return (ComponentUI)uiObject;
}

看到这句代码没:

getUIClass(target.getUIClassID(), uiClassLoader);

原来是通过控件类中的 getUIClassID 返回的“键”。来获得 UI 类的类名在 UIDefaults 中的“值”。然后反射生成 UI 类的对象。

看一下 JPanel 中的 getUIClassID 方法:

private static final String uiClassID = "PanelUI";

public String getUIClassID() {
    return uiClassID;
}

又到一段总结时……

在控件构造时。都会去调用 updateUI 方法。

在控件的 updateUI 方法中。会通过 UIManager 去获取 ui 对象。

而 UIManager 去获取 ui 对象时,是通过控件的 uiClassID 这个“键”去获得 UIDefaults 中的相应的“值”。

而最后依据返回的类名,反射生成一个 UI 类的对象。返回给 updateUI 方法。

再通过 setUI 方法赋值给 ui 成员变量。

--------------------------------------------------------------------------------

好的。第一篇 LookAndFeel 教程就快结束了。

我们最后来看一下 LookAndFeel 类中的一个方法,你会明确非常多事。

MetalLookAndFeel 类,initClassDefaults 方法:

    protected void initClassDefaults(UIDefaults table)
    {
        super.initClassDefaults(table);
        final String metalPackageName = "javax.swing.plaf.metal.";
        Object[] uiDefaults = {
                   "ButtonUI", metalPackageName+ "MetalButtonUI",
                 "CheckBoxUI", metalPackageName+ "MetalCheckBoxUI",
                 "ComboBoxUI", metalPackageName + "MetalComboBoxUI",
              "DesktopIconUI", metalPackageName + "MetalDesktopIconUI",
              "FileChooserUI", metalPackageName + "MetalFileChooserUI",
            "InternalFrameUI", metalPackageName + "MetalInternalFrameUI",
                    "LabelUI", metalPackageName + "MetalLabelUI",
       "PopupMenuSeparatorUI", metalPackageName + "MetalPopupMenuSeparatorUI",
              "ProgressBarUI", metalPackageName + "MetalProgressBarUI",
              "RadioButtonUI", metalPackageName + "MetalRadioButtonUI",
                "ScrollBarUI", metalPackageName + "MetalScrollBarUI",
               "ScrollPaneUI", metalPackageName + "MetalScrollPaneUI",
                "SeparatorUI", metalPackageName + "MetalSeparatorUI",
                   "SliderUI", metalPackageName + "MetalSliderUI",
                "SplitPaneUI", metalPackageName + "MetalSplitPaneUI",
               "TabbedPaneUI", metalPackageName + "MetalTabbedPaneUI",
                "TextFieldUI", metalPackageName + "MetalTextFieldUI",
             "ToggleButtonUI", metalPackageName + "MetalToggleButtonUI",
                  "ToolBarUI", metalPackageName + "MetalToolBarUI",
                  "ToolTipUI", metalPackageName + "MetalToolTipUI",
                     "TreeUI", metalPackageName + "MetalTreeUI",
                 "RootPaneUI", metalPackageName + "MetalRootPaneUI",
        };
        table.putDefaults(uiDefaults);
    }

如今你明确了么?

"ButtonUI" 就是那个“键”;

"javax.swing.plaf.metal.MetalButtonUI" 就是那个“值”;

原来在 updateUI 中,获得 ui 对象时,用到的那个键值对关系,就是在这里相应上的。

所以,假设你打算自己写一套 LookAndFeel,当你写了一个 UI 类之后应该怎么和控件相应上呢?

答案就是改写 LookAndFeel 类中的 initClassDefaults 方法。

第一篇。就到这里了~

to be continue...

原文地址:https://www.cnblogs.com/zhchoutai/p/8726824.html

时间: 2024-10-14 14:03:45

Swing:LookAndFeel 教程第一篇——手把手教你写出自己的 LookAndFeel的相关文章

小程序初体验:手把手教你写出第一个小程序(一)

本文笔者将根据quick start中的范例代码,带大家简单地剖析一下小程序的运行方式,并介绍小程序开发中一些通用的特性,带着大家一步步写出自己的小程序. 适用对象:前端初学者,对小程序开发感兴趣者 tips:由于笔者也是一位前端菜鸟,所以尽量用简单直白的语言为大家讲解,如有说的不到位的地方,还望多多指教. 吊了我们一年胃口的小程序终于和大家见面了.经过了一天的发酵,小程序已经成为了今天的超级头条(汪汪哭晕在厕所). 经历了一天媒体对小程序的狂轰滥炸,相信大家对于小程序一定充满了好奇.与其跟风转

手把手教你写专利申请书/怎样申请专利

手把手教你写专利申请书·怎样申请专利 摘要小前言(一)申请前的准备工作    1.申请前查询    2.其它方面的考虑    3.申请文件准备(二)填写专利申请系列文档    1.实际操作步骤    2.详细操作    3.经验分享.注意事项(三)关于费用(四)其它的话參考资源提示常见问题的问与答 摘要: 怎样写好专利申请?由于非常多专利申请人都是第一次申请,因此,可能有一种神奇和些许恐惧.本文写的是怎样写专利申请书,手把手教你写专利申请并提供申请专利时的注意事项,专利申请费用及费用减缓等相关參

手把手教你写电商爬虫-第三课 实战尚妆网AJAX请求处理和内容提取

系列教材: 手把手教你写电商爬虫-第一课 找个软柿子捏捏 手把手教你写电商爬虫-第二课 实战尚妆网分页商品采集爬虫 看完两篇,相信大家已经从开始的小菜鸟晋升为中级菜鸟了,好了,那我们就继续我们的爬虫课程. 上一课呢一定是因为对手太强,导致我们并没有完整的完成尚妆网的爬虫. 吭吭~,我们这一课继续,争取彻底搞定尚妆网,不留任何遗憾. 我们先回顾一下,上一课主要遗留了两个问题,两个问题都和ajax有关. 1.由于是ajax加载下一页,导致下一页url并不会被系统自动发现. 2.商品页面的价格是通过a

手把手教你写电商爬虫-第四课 淘宝网商品爬虫自动JS渲染

系列教程: 手把手教你写电商爬虫-第一课 找个软柿子捏捏 手把手教你写电商爬虫-第二课 实战尚妆网分页商品采集爬虫 手把手教你写电商爬虫-第三课 实战尚妆网AJAX请求处理和内容提取 老规矩,爬之前首先感谢淘宝公布出这么多有价值的数据,才让我们这些爬虫们有东西可以搜集啊,不过淘宝就不用我来安利了 广大剁手党相信睡觉的时候都能把网址打出来吧. 工欲善其事,必先利其器,先上工具: 1.神箭手云爬虫,2.Chrome浏览器 3.Chrome的插件XpathHelper 不知道是干嘛的同学请移步第一课

手把手教你写电商爬虫-第五课 京东商品评论爬虫 一起来对付反爬虫

系列教程: 手把手教你写电商爬虫-第一课 找个软柿子捏捏 手把手教你写电商爬虫-第二课 实战尚妆网分页商品采集爬虫 手把手教你写电商爬虫-第三课 实战尚妆网AJAX请求处理和内容提取 手把手教你写电商爬虫-第四课 淘宝网商品爬虫自动JS渲染 四节课过去了,咱们在爬虫界也都算见过世面的人,现在再来一些什么ajax加载之类的小鱼小虾应该不在话下了,即使是淘宝这种大量的ajax,我们 祭上我们的核武器,也轻松应对了,这一课主要是来看看除了技术上的页面处理外,我们还会遇上更棘手的问题,就是反爬虫,当然现

手把手教你写专利申请书/如何申请专利

http://blog.csdn.net/johnsuna/article/details/3492145 手把手教你写专利申请书·如何申请专利 摘要小前言(一)申请前的准备工作    1.申请前查询    2.其他方面的考虑    3.申请文件准备(二)填写专利申请系列文档    1.实际操作步骤    2.具体操作    3.经验分享.注意事项(三)关于费用(四)其他的话参考资源提示常见问题的问与答 摘要: 如何写好专利申请?由于很多专利申请人都是第一次申请,因此,可能有一种神秘和些许恐惧.

手把手教你写电商爬虫-第二课 实战尚妆网分页商品采集爬虫

系列教程 手把手教你写电商爬虫-第一课 找个软柿子捏捏 如果没有看过第一课的朋友,请先移步第一课,第一课讲了一些基础性的东西,通过软柿子"切糕王子"这个电商网站好好的练了一次手,相信大家都应该对写爬虫的流程有了一个大概的了解,那么这课咱们就话不多说,正式上战场,对垒尚妆网. 首先,向我们被爬网站致敬,没有他们提供数据,我们更是无从爬起,所以先安利一下尚妆网: 经营化妆品时尚购物,大数据为驱动,并依托智能首饰为入口的新一代智慧美妆正品电子商务平台.其创始团队来自天猫.支付宝.欧莱雅.薇姿

手把手教你写网络爬虫(3):开源爬虫框架对比

手把手教你写网络爬虫(3) 作者:拓海 摘要:从零开始写爬虫,初学者的速成指南! 封面: 介绍 大家好!我们从今天开始学习开源爬虫框架Scrapy,如果你看过<手把手>系列的前两篇,那么今天的内容就非常容易理解了.细心的读者也许会有疑问,为什么不学出身名门的Apache顶级项目Nutch,或者人气飙升的国内大神开发的Pyspider等框架呢?原因很简单,我们来看一下主流爬虫框架在GitHub上的活跃度: Project Language Star Watch Fork Nutch Java 1

Android开发之手把手教你写ButterKnife框架(二)

欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52664112 本文出自:[余志强的博客] 上一篇博客Android开发之手把手教你写ButterKnife框架(一)我们讲了ButterKnife是什么.ButterKnife的作用和功能介绍以及ButterKnife的实现原理. 本篇博客主要讲在android studio中如何使用apt. 一.新建个项目, 然后创建一个module名叫processor 新建m