Swing换肤

JavaSE JFC技术 (AWT + Swing + Graphics2D):完全不改变原生Swing代码,换肤。

 -->Swing换肤_第1张图片Swing换肤_第2张图片Swing换肤_第3张图片Swing换肤_第4张图片Swing换肤_第5张图片Swing换肤_第6张图片

源码打包:Swing_lnfImpl.zip (23.5Kb)

结构:

源码:

package com.han.lnf;

import javax.swing.*;
import javax.swing.plaf.LayerUI;
import javax.swing.plaf.synth.SynthLookAndFeel;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.geom.Point2D;
import java.beans.PropertyChangeEvent;
import java.text.ParseException;

/**
 * Class note: Created by Gaowen on 14-1-9.
 */
public class TestCase extends JPanel {
    private static JLayer<JPanel> jLayer;
    private static WaitLayerUI waitLayerUI;
    private static Timer stopper;
    private static JButton orderButton;

    public static void main(String[] args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                createAndShowGUI();
            }
        });
    }

    private static void createAndShowGUI() {
        initLookAndFeel();// specify the L&F
        final LayerUI<JPanel> spotlightLayerUI = new SpotlightLayerUI();
        waitLayerUI = new WaitLayerUI();
        stopper = new Timer(4000, new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                waitLayerUI.stop();
                jLayer.setUI(spotlightLayerUI);
            }
        });
        stopper.setRepeats(false);
        JPanel contentPane = new TestCase();
        jLayer = new JLayer<>(contentPane, spotlightLayerUI);
        final JFrame f = new JFrame("Custom L&F");
        f.add(jLayer);
        f.getRootPane().setDefaultButton(orderButton);
        f.setSize(290, 180);
        f.setResizable(false);
        f.setLocationRelativeTo(null);
        f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        f.setVisible(true);
    }

    private static void initLookAndFeel() {
        SynthLookAndFeel lookAndFeel = new SynthLookAndFeel();
        try {
            lookAndFeel.load( TestCase.class.getResourceAsStream("lnfimpl/synth.xml"), TestCase.class);
        } catch (ParseException e) {
            System.err.println("There is an error in parsing xml of Synth");
            e.printStackTrace();
        }
        try {
            UIManager.setLookAndFeel(lookAndFeel);
        } catch (UnsupportedLookAndFeelException e) {
            System.err.println("Synth is not a supported look and feel");
            e.printStackTrace();
        }
    }

    private static class SpotlightLayerUI extends LayerUI<JPanel> {
        private boolean mActive;
        private int mX, mY;

        @Override
        public void installUI(JComponent c) {
            super.installUI(c);
            @SuppressWarnings("unchecked")
            JLayer<JPanel> jLayer = (JLayer<JPanel>) c;
            jLayer.setLayerEventMask(AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK);
        }

        @Override
        public void uninstallUI(JComponent c) {
            super.uninstallUI(c);
            @SuppressWarnings("unchecked")
            JLayer<JPanel> jLayer = (JLayer<JPanel>) c;
            jLayer.setLayerEventMask(0);
        }

        @Override
        public void paint(Graphics g, JComponent c) {
            super.paint(g, c);// paint the view
            Graphics2D g2 = (Graphics2D) g.create();
            if (mActive) {
                /* Create a radial gradient, transparent in the middle */
                Point2D center = new Point2D.Float(mX, mY);
                float radius = 72;
                float[] dist = {0.0f, 1.0f};
                Color[] colors = {new Color(0.0f, 0.0f, 0.0f, 0.0f), Color.BLACK};
                RadialGradientPaint p = new RadialGradientPaint(center, radius, dist, colors);
                g2.setPaint(p);
                g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .6f));
                g2.fillRect(0, 0, c.getWidth(), c.getHeight());
            }
            g2.dispose();
        }

        @Override
        protected void processMouseEvent(MouseEvent e, JLayer<? extends JPanel> l) {
            if (e.getID() == MouseEvent.MOUSE_ENTERED) {
                mActive = true;
            } else if (e.getID() == MouseEvent.MOUSE_EXITED) {
                mActive = false;
            }
            l.repaint();
        }

        @Override
        protected void processMouseMotionEvent(MouseEvent e, JLayer<? extends JPanel> l) {
            Point p = SwingUtilities.convertPoint(e.getComponent(), e.getPoint(), l);
            mX = p.x;
            mY = p.y;
            l.repaint();
        }
    }

    private static class WaitLayerUI extends LayerUI<JPanel> implements ActionListener {
        private boolean mIsRunning;
        private boolean mIsFadingOut;
        private Timer mTimer;
        private int mAngle;
        private int mFadeCount;
        private final int mFadeLimit = 15;// immutable, so declared as final

        @Override
        public void paint(Graphics g, JComponent c) {
            super.paint(g, c);// paint the view
            if (!mIsRunning) return;
            float fade = mFadeCount / mFadeLimit;
            Graphics2D g2 = (Graphics2D) g.create();
			/* Gray it out, in fact it uses the FOREGROUND of L&F */
            Composite urComposite = g2.getComposite();
            g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, .5f * fade));
            int w = c.getWidth();
            int h = c.getHeight();
            g2.fillRect(0, 0, w, h);
            g2.setComposite(urComposite);
			/* Paint the wait indicator */
            int s = Math.min(w, h) / 5;
            int cx = w / 2;
            int cy = h / 2;
            g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
            g2.setStroke(new BasicStroke(s / 4, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND));
            g2.setPaint(Color.white);
            g2.rotate(Math.PI * mAngle / 180, cx, cy);
            for (int i = 0; i < 12; i++) {
                float scale = (11.0f - i) / 11.0f;
                g2.drawLine(cx + s, cy, cx + s * 2, cy);
                g2.rotate(-Math.PI / 6, cx, cy);
                g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, scale * fade));
            }
            g2.dispose();
        }

        @Override
        public void actionPerformed(ActionEvent e) {
            if (mIsRunning) {
                firePropertyChange("tick", 0, 1);
                mAngle += 3;
                if (mAngle >= 360) {
                    mAngle = 0;
                }
                if (mIsFadingOut) {
                    if (--mFadeCount == 0) {
                        mIsRunning = false;
                        mTimer.stop();
                    }
                } else if (mFadeCount < mFadeLimit) {
                    mFadeCount++;
                }
            }
        }

        private void start() {
            if (mIsRunning) return;
			/* Run a thread for animation */
            mIsRunning = true;
            mIsFadingOut = false;
            mFadeCount = 0;
            int fps = 24;
            int tick = 1000 / fps;
            mTimer = new Timer(tick, this);
            mTimer.start();
        }

        private void stop() {
            mIsFadingOut = true;
        }

        @Override
        public void applyPropertyChange(PropertyChangeEvent pce, JLayer<? extends JPanel> l) {
            if ("tick".equals(pce.getPropertyName())) {
                l.repaint();
            }
        }
    }

    TestCase() {
        ButtonGroup entreeGroup = new ButtonGroup();
        JRadioButton radioButton = new JRadioButton("Beef", true);
        JRadioButton radioButton2 = new JRadioButton("Chicken");
        JRadioButton radioButton3 = new JRadioButton("Vegetable");
        entreeGroup.add(radioButton);
        entreeGroup.add(radioButton2);
        entreeGroup.add(radioButton3);
        JCheckBox jCheckBox = new JCheckBox("Ketchup");
        JCheckBox jCheckBox2 = new JCheckBox("Mustard");
        JCheckBox jCheckBox3 = new JCheckBox("Pickles");
        JLabel label = new JLabel("Special requests:");
        JTextField tf = new JTextField(15);
        final JLabel info = new JLabel();
        info.setName("customLabel");
        orderButton = new JButton("Place Order");
        orderButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                info.setText("default button clicked");
                if (stopper.isRunning()) return;
                stopper.start();
                waitLayerUI.start();
                jLayer.setUI(waitLayerUI);
            }
        });
        JButton cancelButton = new JButton("Cancel");
        cancelButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                info.setText("cancel button clicked");
            }
        });
        add(radioButton);
        add(radioButton2);
        add(radioButton3);
        add(jCheckBox);
        add(jCheckBox2);
        add(jCheckBox3);
        add(label);
        add(tf);
        add(orderButton);
        add(cancelButton);
        add(info);
    }
}



皮肤:

<?xml version="1.0" encoding="UTF-8"?>
<synth>
    <!-- A backing style, it is good practice to do this -->
    <style id="backingStyle">
        <opaque value="true"/>
        <font name="Dialog" size="12"/>
        <state>
            <color type="BACKGROUND" value="#D8D987"/>
            <color type="FOREGROUND" value="#9400d3"/>
            <color type="TEXT_FOREGROUND" value="#3C3C3C"/>
        </state>
    </style>
    <bind style="backingStyle" type="region" key=".*"/>
    <style id="button">
        <font name="Monotype Corsiva" size="18"/>
        <property key="Button.defaultButtonFollowsFocus" type="boolean" value="false"/>
        <!-- Shift the text one pixel when pressed -->
        <property key="Button.textShiftOffset" type="integer" value="1"/>
        <!-- set size of buttons -->
        <insets top="0" left="10" bottom="0" right="10"/>
        <!-- The state behavior is like an cascading filter -->
        <state>
            <imagePainter id="defaultStatePainter" method="buttonBackground"
                          path="lnfimpl/button/graphics/btn_general_normal.png" sourceInsets="3 3 3 3"/>
        </state>
        <state value="DISABLED">
            <imagePainter method="buttonBackground" path="lnfimpl/button/graphics/btn_disabled.png"
                          sourceInsets="3 2 4 3"/>
        </state>
        <state value="DEFAULT">
            <imagePainter method="buttonBackground" path="lnfimpl/button/graphics/btn_special.png"
                          sourceInsets="4 4 4 4"/>
        </state>
        <state value="PRESSED">
            <imagePainter method="buttonBackground" path="lnfimpl/button/graphics/btn_general_pressed.png"
                          sourceInsets="4 4 4 4"/>
        </state>
        <state value="MOUSE_OVER">
            <imagePainter method="buttonBackground" path="lnfimpl/button/graphics/btn_general_hover.png"
                          sourceInsets="4 4 4 4"/>
        </state>
        <state value="FOCUSED">
            <painter idref="defaultStatePainter" method="buttonBackground"/>
            <object id="buttonFocusedPainter" class="com.han.lnf.lnfimpl.button.ButtonFocusedPainter"/>
            <object id="bgColor" class="javax.swing.plaf.ColorUIResource">
                <int>218</int>
                <int>116</int>
                <int>62</int>
            </object>
            <defaultsProperty key="Button.background" type="idref" value="bgColor"/>
            <painter idref="buttonFocusedPainter" method="buttonBorder"/>
        </state>
    </style>
    <bind style="button" type="region" key="Button"/>
    <style id="textfield">
        <!-- font here must support Chinese characters -->
        <font name="Microsoft YaHei UI" size="11"/>
        <color id="caretColor" value="MAGENTA"/>
        <property key="TextField.caretForeground" type="idref" value="caretColor"/>
        <insets top="3" left="5" bottom="4" right="5"/>
        <state>
            <color type="TEXT_FOREGROUND" value="#008b8b"/>
            <color type="TEXT_BACKGROUND" value="#ffa349"/>
            <imagePainter method="textFieldBorder" path="lnfimpl/textfield/graphics/text_field_normal.png"
                          sourceInsets="5 5 5 5" paintCenter="false"/>
        </state>
        <state value="DISABLED">
            <imagePainter method="textFieldBorder" path="lnfimpl/textfield/graphics/text_field_disabled.png"
                          sourceInsets="5 5 5 5" paintCenter="true"/>
        </state>
        <state value="FOCUSED">
            <imagePainter method="textFieldBorder" path="lnfimpl/textfield/graphics/text_field_pressed.png"
                          sourceInsets="5 5 5 5" paintCenter="false"/>
        </state>
    </style>
    <bind style="textfield" type="region" key="TextField"/>
    <style id="label">
        <font name="Impact" size="12"/>
    </style>
    <bind style="label" type="region" key="Label"/>
    <style id="customLabel" clone="label">
        <font name="Comic Sans MS" size="12"/>
    </style>
    <bind style="customLabel" type="name" key="custom.*"/>
    <style id="checkbox">
        <font name="Segoe Script" size="12"/>
        <insets top="4" left="2" bottom="4" right="4"/>
        <imageIcon id="check_off_normal" path="lnfimpl/checkbox/graphics/cb_un_normal.png"/>
        <imageIcon id="check_off_disable" path="lnfimpl/checkbox/graphics/cb_un_disable.png"/>
        <imageIcon id="check_off_pressed" path="lnfimpl/checkbox/graphics/cb_un_pressed.png"/>
        <imageIcon id="check_on_normal" path="lnfimpl/checkbox/graphics/cb_normal.png"/>
        <imageIcon id="check_on_disable" path="lnfimpl/checkbox/graphics/cb_disable.png"/>
        <imageIcon id="check_on_pressed" path="lnfimpl/checkbox/graphics/cb_pressed.png"/>
        <state>
            <property key="CheckBox.icon" value="check_off_normal"/>
        </state>
        <state value="SELECTED and DISABLED">
            <property key="CheckBox.icon" value="check_on_disable"/>
        </state>
        <state value="SELECTED and PRESSED">
            <property key="CheckBox.icon" value="check_on_pressed"/>
        </state>
        <state value="SELECTED and FOCUSED">
            <property key="CheckBox.icon" value="check_on_normal"/>
            <object id="checkboxFocusedPainter" class="com.han.lnf.lnfimpl.checkbox.CheckBoxFocusedPainter"/>
            <object id="bgColor" class="javax.swing.plaf.ColorUIResource">
                <int>29</int>
                <int>88</int>
                <int>32</int>
            </object>
            <defaultsProperty key="CheckBox.background" type="idref" value="bgColor"/>
            <painter idref="checkboxFocusedPainter" method="checkBoxBackground"/>
        </state>
        <state value="SELECTED and ENABLED">
            <property key="CheckBox.icon" value="check_on_normal"/>
        </state>
        <state value="DISABLED">
            <property key="CheckBox.icon" value="check_off_disable"/>
        </state>
        <state value="PRESSED">
            <property key="CheckBox.icon" value="check_off_pressed"/>
        </state>
        <state value="FOCUSED">
            <painter idref="checkboxFocusedPainter" method="checkBoxBackground"/>
        </state>
    </style>
    <bind style="checkbox" type="region" key="CheckBox"/>
    <style id="radiobutton">
        <font name="Segoe Print" size="12"/>
        <insets top="4" left="2" bottom="4" right="4"/>
        <imageIcon id="radio_off_normal" path="lnfimpl/radiobutton/graphics/rb_un_normal.png"/>
        <imageIcon id="radio_off_disable" path="lnfimpl/radiobutton/graphics/rb_un_disable.png"/>
        <imageIcon id="radio_off_pressed" path="lnfimpl/radiobutton/graphics/rb_un_pressed.png"/>
        <imageIcon id="radio_on_normal" path="lnfimpl/radiobutton/graphics/rb_normal.png"/>
        <imageIcon id="radio_on_disable" path="lnfimpl/radiobutton/graphics/rb_disable.png"/>
        <imageIcon id="radio_on_pressed" path="lnfimpl/radiobutton/graphics/rb_pressed.png"/>
        <state>
            <property key="RadioButton.icon" value="radio_off_normal"/>
        </state>
        <state value="SELECTED and DISABLED">
            <property key="RadioButton.icon" value="radio_on_disable"/>
        </state>
        <state value="SELECTED and PRESSED">
            <property key="RadioButton.icon" value="radio_on_pressed"/>
        </state>
        <state value="SELECTED and FOCUSED">
            <property key="RadioButton.icon" value="radio_on_normal"/>
            <object id="radiobuttonFocusedPainter" class="com.han.lnf.lnfimpl.radiobutton.RadioButtonFocusedPainter"/>
            <painter idref="radiobuttonFocusedPainter" method="radioButtonBackground"/>
        </state>
        <state value="SELECTED and ENABLED">
            <property key="RadioButton.icon" value="radio_on_normal"/>
        </state>
        <state value="DISABLED">
            <property key="RadioButton.icon" value="radio_off_disable"/>
        </state>
        <state value="PRESSED">
            <property key="RadioButton.icon" value="radio_off_pressed"/>
        </state>
        <state value="FOCUSED">
            <painter idref="radiobuttonFocusedPainter" method="radioButtonBackground"/>
        </state>
    </style>
    <bind style="radiobutton" type="region" key="RadioButton"/>
    <style id="panel">
        <object id="panelPainter" class="com.han.lnf.lnfimpl.panel.PanelPainter"/>
        <object id="backgroundColor" class="javax.swing.plaf.ColorUIResource">
            <int>255</int>
            <int>255</int>
            <int>255</int>
        </object>
        <defaultsProperty key="Panel.background" type="idref" value="backgroundColor"/>
        <object id="foregroundColor" class="javax.swing.plaf.ColorUIResource">
            <int>200</int>
            <int>200</int>
            <int>200</int>
        </object>
        <defaultsProperty key="Panel.foreground" type="idref" value="foregroundColor"/>
        <painter idref="panelPainter" method="panelBackground"/>
    </style>
    <bind style="panel" type="region" key="Panel"/>
</synth>

package com.han.lnf.lnfimpl.button;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.UIManager;
import javax.swing.plaf.synth.SynthContext;
import javax.swing.plaf.synth.SynthPainter;

public class ButtonFocusedPainter extends SynthPainter {
    @Override
    public void paintButtonBorder(SynthContext context, Graphics g, int x, int y, int w, int h) {
        Graphics2D g2 = (Graphics2D) g.create();
        float[] arr = {8, 2, 2, 6};
        Color background = UIManager.getColor("Button.background");
        g2.setColor(background);
        g2.setStroke(new BasicStroke(1.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 4, arr, 0));
        g2.drawRoundRect(x + 4, y + 4, w - 8, h - 8, 10, 10);
        g2.dispose();
    }
}
package com.han.lnf.lnfimpl.checkbox;

import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.UIManager;
import javax.swing.plaf.synth.SynthContext;
import javax.swing.plaf.synth.SynthPainter;

public class CheckBoxFocusedPainter extends SynthPainter {
    @Override
    public void paintCheckBoxBackground(SynthContext context, Graphics g, int x, int y, int w, int h) {
        Graphics2D g2 = (Graphics2D) g.create();
        float[] arr = {8, 2, 2, 6};
        Color background = UIManager.getColor("CheckBox.background");
        g2.setColor(background);
        g2.setStroke(new BasicStroke(1.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 4, arr, 0));
        g2.drawRoundRect(x + 28, y + 7, w - 30, h - 13, 10, 10);
        g2.dispose();
    }
}
package com.han.lnf.lnfimpl.panel;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.CubicCurve2D;

import javax.swing.UIManager;
import javax.swing.plaf.synth.SynthContext;
import javax.swing.plaf.synth.SynthPainter;

public class PanelPainter extends SynthPainter {
    @Override
    public void paintPanelBackground(SynthContext context, Graphics g, int x, int y, int w, int h) {
        Graphics2D g2 = (Graphics2D) g.create();
        Color background = UIManager.getColor("Panel.background");
        g2.setColor(background);
        g2.fillRect(x, y, w, h);
        Color foreground = UIManager.getColor("Panel.foreground");
        g2.setColor(foreground);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        CubicCurve2D.Double arc2d = new CubicCurve2D.Double(0, h / 4d, w / 3d, h / 10d, .67 * w, 1.5 * h, w, h / 8d);
        g2.draw(arc2d);
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
        g2.dispose();
    }
}
package com.han.lnf.lnfimpl.radiobutton;

import java.awt.BasicStroke;
import java.awt.Graphics;
import java.awt.Graphics2D;

import javax.swing.plaf.synth.SynthContext;
import javax.swing.plaf.synth.SynthPainter;

public class RadioButtonFocusedPainter extends SynthPainter {
    @Override
    public void paintRadioButtonBackground(SynthContext context, Graphics g, int x, int y, int w, int h) {
        Graphics2D g2 = (Graphics2D) g.create();
        float[] arr = {8, 2, 2, 6};
        g2.setStroke(new BasicStroke(1.5f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 4, arr, 0));
        g2.drawRoundRect(x + 28, y + 8, w - 30, h - 14, 10, 10);
        g2.dispose();
    }
}

由于本人不是专门做美工的,所以界面有定制的功能,但是定制的不算漂亮~望喜欢

这个和Android的xml皮肤比较像,不过我觉得Android的更强大,因为它xml里面可以使用NinePatch Tech。不过幸运的是,安卓的NinePatch技术可以由源码过度到JavaSE Swing里,但是必须是Java paint代码。。所以Synth可以使用NinePatch技术,但不如Android直接在XML中描述方便。

利用 Synth 可以创建出完全专业的外观,Java 1.4 中发布的 GTK+ 和 Windows XP 外观就完全是用 Synth 创建的。(那时它不是一个已公布的 API。) 
由于我上面的例子实践中用了20张png图片,加载速度以及内存占用比Metal的L&F可能差一点,不过如果是复杂界面或者创建数量多的Components时,Synth和Metal还是差不多的。比如,我这个例子中(XP + JDK7u45 + Eclipse Kepler)如果注销掉

initLookAndFeel();// specify the L&F

这行代码,得到的Metal经典外观时占用的内存为25Kb,如果不注销,使用的定制的外观时内存为29Kb,这些内存和启动速度我觉得都比JavaFX应用要好。
如果使用100 个组件的界面来测试:

当时设计时遇到一个问题:
当使用自定义感官时,比如我上面的L&F或者使用jgoodies的UIManager.setLookAndFeel(new com.jgoodies.looks.windows.WindowsLookAndFeel());
则如果定义文本框等,像JTextField,JTextPane时,在用智能拼音输入时,输入窗口会出现方框形式的乱码。取消这种观感的设置既可解决问题(比如注销掉initLookAndFeel();// specify the L&F)。原因是这种观感不支持中文。怎么让自定义的感官支持呢?

解决了,是因为字体的原因,Arial不支持中文的显示。改为雅黑就行了,可以使用Component的getFont()以及Font的canDisplay(int codePoint)或者canDisplayUpTo(String)来判断是否支持特定的字符显示。




你可能感兴趣的:(swing,JavaSE,Graphics2D,awt,JFC)