<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Java桌面技術(shù)

    Java Desktop Technology

    常用鏈接

    統(tǒng)計(jì)

    友情連接

    最新評(píng)論

    布局管理器面面觀

    本系列文章將系統(tǒng)地介紹在AWT-Swing組件體系下如何使用布局管理器,從概念開始并結(jié)合JDK1.6 API源代碼講述布局管理器工作原理,然后介紹如何自定義布局管理器并給出2個(gè)自定義的實(shí)現(xiàn)——FormLayout、CenterLayout,同時(shí)還將介紹如何使用絕對(duì)定位解決布局問題,最后以通過xml配置文件聲明及布局組件結(jié)束本文。
    本文包括如下部分:
    一、布局管理器簡(jiǎn)介與工作原理
    二、如何編寫自定義布局管理器
    三、FormLayout實(shí)現(xiàn)
    四、CenterLayout實(shí)現(xiàn)
    五、如何使用絕對(duì)定位解決布局問題
    六、通過xml配置文件定義及布局組件

                                  第一部分:布局管理器簡(jiǎn)介與工作原理
    布局管理器是一個(gè)實(shí)現(xiàn)了LayoutManager接口或LayoutManager2接口并且能夠確定一個(gè)容器內(nèi)部所有組件大小和位置的對(duì)象。盡管組件能夠提供大小和對(duì)齊的提示信息,但是一個(gè)容器的布局管理器將最終決定組件的尺寸和位置。

    布局管理器的工作原理
    基本的布局管理器要實(shí)現(xiàn)LayoutManager接口。LayoutManager接口聲明了5個(gè)基本方法:
    void addLayoutComponent(String name, Component comp)
    void layoutContainer(Container parent)
    Dimension minimumLayoutSize(Container parent)
    Dimension preferredLayoutSize(Container parent)
    void removeLayoutComponent(Component comp)

    LayoutManager2接口在LayoutManager接口之上添加了4個(gè)方法:
    void addLayoutComponent(Component comp, Object constraints)
    float getLayoutAlignmentX(Container target)
    float getLayoutAlignmentY(Container target)
    void invalidateLayout(Container target)
    Dimension maximumLayoutSize(Container target)

    以上方法是構(gòu)成布局管理器的所有方法,只有當(dāng)容器添加了布局管理器時(shí),這些方法才可能被調(diào)用到。下面一一講述這些方法的調(diào)用時(shí)機(jī)。

    “void addLayoutComponent(String name, Component comp)”和“void addLayoutComponent(Component comp, Object constraints)”兩個(gè)方法是當(dāng)向容器內(nèi)添加組件時(shí)候可能被調(diào)用。具體調(diào)用那個(gè)由add方法的參數(shù)決定。
    Javadoc中是這么說明的:對(duì)于前者的注解是“如果布局管理器使用每組件字符串,則將組件 comp 添加到布局,并將它與 name 指定的字符串關(guān)聯(lián)。”;后者則是“使用指定約束對(duì)象,將指定組件添加到布局。”
    例如,使用java.awt.Container類的“Component add(String name, Component comp)”方法添加組件comp時(shí)候,如果該容器(container)設(shè)置了布局管理器,那么該布局管理器的“void addLayoutComponent(String name, Component comp)”方法將被調(diào)用;使用java.awt.Container類的 
    “void add(Component comp, Object constraints)”方法添加組件時(shí),該容器的布局管理器(如果有且實(shí)現(xiàn)了LayoutManager2接口)的“void addLayoutComponent(Component comp, Object constraints)”將被調(diào)用。例如下面這行代碼:
    ....add(new JButton(),BorderLayout.CENTER);
    就會(huì)調(diào)用布局管理器的void add(Component comp, Object constraints)。如果你查看java.awt.BorderLayout的源碼,會(huì)發(fā)現(xiàn)BorderLayout實(shí)現(xiàn)的是LayoutManager2接口。

    我們看一下JDK源碼是怎樣的調(diào)用關(guān)系。記住,讀源碼是學(xué)習(xí)開源技術(shù)最徹底的方法。
    在java.awt.Container的所有add(...)方法中,都是最終調(diào)用“protected void addImpl(Component comp, Object constraints, int index)”這個(gè)實(shí)現(xiàn),add方法的參數(shù)不同,調(diào)用addImpl時(shí)候傳入的參數(shù)也不同。例如,Component add(String name, Component comp)方法的實(shí)現(xiàn)是這樣的:
     public Component add(String name, Component comp) {
          addImpl(comp, name, -1);
          return comp;
    }
    void add(Component comp, Object constraints)方法的實(shí)現(xiàn)是這樣的:
    public void add(Component comp, Object constraints) {
          addImpl(comp, constraints, -1);
    }

    “addImpl”方法實(shí)現(xiàn)很長(zhǎng),不可能全部給出,但是有一段對(duì)分析布局管理器有幫助:
    protected void addImpl(Component comp, Object constraints, int index) {
    ......
        /* Notify the layout manager of the added component. */
        if (layoutMgr != null) {
           if (layoutMgr instanceof LayoutManager2) {
               ((LayoutManager2)layoutMgr).addLayoutComponent(comp, constraints);
           } else if (constraints instanceof String) {
               layoutMgr.addLayoutComponent((String)constraints, comp);
           }
        }
    ......
    }
    如果這個(gè)容器設(shè)置了布局管理器(layoutMgr != null),那么檢查layoutMgr是否實(shí)現(xiàn)的是LayoutManager2接口,如果是就調(diào)用布局管理器的“void addLayoutComponent(Component comp, Object constraints)”方法,否則(實(shí)現(xiàn)的是LayoutManager接口)再判斷constraints是否是String類型,如果是就調(diào)用布局管理器的“void addLayoutComponent(String name, Component comp)”方法。
    到此為止,布局管理器的“void addLayoutComponent(String name, Component comp)”和“void addLayoutComponent(Component comp, Object constraints)”兩個(gè)方法調(diào)用時(shí)機(jī)已經(jīng)非常明了了,同時(shí)我們還了解了一點(diǎn),那就是如果布局管理器實(shí)現(xiàn)的是LayoutManager2接口,那么它的“void addLayoutComponent(String name, Component comp)”永遠(yuǎn)不會(huì)被awt框架調(diào)用到,除非你顯示地調(diào)用。

    LayoutManager接口的“void removeLayoutComponent(Component comp)”方法,是在容器移除子組件時(shí)候被調(diào)用。打開JDK源代碼,java.awt.Container的移除組件的方法實(shí)現(xiàn)如下:
    public void remove(Component comp) {
       synchronized (getTreeLock()) {
         if (comp.parent == this)  {
          /* Search backwards, expect that more recent additions are more likely to be removed. */
          Component component[] = this.component;
          for (int i = ncomponents; --i >= 0; ) {
            if (component[i] == comp) {
              remove(i);
            }
          }
        }
      }
    }
    可以看出,每個(gè)添加到容器的組件都被保存在component[]中,刪除組件時(shí)會(huì)遍歷數(shù)組,發(fā)現(xiàn)被刪除的組件調(diào)用public void remove(int index)執(zhí)行刪除。在remove(int index)方法中同樣有我們關(guān)注的調(diào)用。
    public void remove(int index) {
    ......
      if (layoutMgr != null) {
           layoutMgr.removeLayoutComponent(comp);
      }
    ......
    }
    可見,組件從父容器移除過程中會(huì)調(diào)用布局管理器(如果設(shè)置了布局管理器)的“void removeLayoutComponent(Component comp)”方法。

    下一步一并介紹“Dimension minimumLayoutSize(Container parent)”、“Dimension preferredLayoutSize(Container parent)”、“Dimension maximumLayoutSize(Container target)”、“float getLayoutAlignmentX(Container target)”、“float getLayoutAlignmentY(Container target)”這5個(gè)方法。
    有時(shí)候,需要自定義一個(gè)組件為它的容器布局管理器提供關(guān)于大小的提示信息,通過指定組件的最小、首選、最大大小維數(shù)可以提供大小的提示信息??梢哉{(diào)用組件的方法來設(shè)置大小提示信息——setMinimumSize、setPreferredSize、setMaximumSize,或者重寫其對(duì)應(yīng)的get...Size方法同樣可以實(shí)現(xiàn)。注意setSize(Dimension d)與set...Size(Dimension preferredSize)是不一樣的,前者能最終確定組件大小,但是只能用在絕對(duì)布局(不設(shè)置任何布局管理器)的情況下;后者是給該組件大小提供關(guān)于大小的提示信息,是給布局管理器看的。但是提示畢竟是提示,最終決定組件大小還是布局管理器決定,提示信息只能算是參考。但是話說回來,布局管理器應(yīng)該嚴(yán)格按照組件的尺寸提示信息行事,例如不應(yīng)該把組件的尺寸設(shè)置成小于它的提示最小尺寸等。有時(shí)候preferredSize屬性會(huì)比size更重要,因?yàn)榻M件框架內(nèi)部通??紤]組件的首選尺寸而不是實(shí)際尺寸的值。例如要實(shí)現(xiàn)JTree不同結(jié)點(diǎn)有不同的高度(QQ上被選中的好友節(jié)點(diǎn)會(huì)加大尺寸顯示),就可以重寫DefaultTreeCellRenderer的getPreferredSize實(shí)現(xiàn)。
    除了提供大小提示信息以外,還可以提供對(duì)齊提示。例如,兩個(gè)組件的上邊界對(duì)齊??梢酝ㄟ^調(diào)用組件的setAlignmentX和setAlignmentY方法,或重寫對(duì)應(yīng)的get方法來設(shè)置對(duì)齊提示,但是大多數(shù)布局管理器會(huì)忽略該提示。為了簡(jiǎn)單起見,只給出preferredLayoutSize的調(diào)用源代碼,其余方法調(diào)用時(shí)機(jī)相似。java.awt.Container類的getPreferredSize方法定義如下:
    public Dimension getPreferredSize() {
        return preferredSize();
    }
    @Deprecated
    public Dimension preferredSize() {
        /* Avoid grabbing the lock if a reasonable cached size value is available. */
        Dimension dim = prefSize;
        if (dim == null || !(isPreferredSizeSet() || isValid())) {
            synchronized (getTreeLock()) {
                prefSize = (layoutMgr != null) ? layoutMgr.preferredLayoutSize(this) : super.preferredSize();
                dim = prefSize;
            }
        }
        if (dim != null) {
            return new Dimension(dim);
        } else{
            return dim;
        }
    }
    由此可以看到在preferredSize中調(diào)用到了layoutMgr.preferredLayoutSize(this),參數(shù)就是當(dāng)前Container的實(shí)例。

    LayoutManager2接口的“void invalidateLayout(Container target)”方法,在JavaDoc的注釋為“使布局失效,指示如果布局管理器緩存了信息,則應(yīng)該將其丟棄。”,讓我們結(jié)合JDK源碼看看該方法何時(shí)被調(diào)用。在java.awt.Container類中,invalidate方法定義如下:
    public void invalidate() {
        LayoutManager layoutMgr = this.layoutMgr;
        if (layoutMgr instanceof LayoutManager2) {
            LayoutManager2 lm = (LayoutManager2) layoutMgr;
            lm.invalidateLayout(this);
        }
        super.invalidate();
    }

    如果在此容器上安裝的 LayoutManager 是一個(gè) LayoutManager2 實(shí)例,則在該實(shí)例上調(diào)用 LayoutManager2.invalidateLayout(Container),并提供此 Container 作為參數(shù)”。這個(gè)函數(shù)在JavaDoc中的注解為:“使容器失效。該容器及其之上的所有父容器被標(biāo)記為需要重新布置。此方法經(jīng)常被調(diào)用,所以內(nèi)部實(shí)現(xiàn)必須簡(jiǎn)潔。
    我們?cè)陧槺憧纯?#8220;super.invalidate();”是如何實(shí)現(xiàn)的,java.awt.Container的基類是java.awt.Component,其invalidate方法實(shí)現(xiàn)如下:
    public void invalidate() {
        synchronized (getTreeLock()) {
            /* Nullify cached layout and size information.
             * For efficiency, propagate invalidate() upwards only if
             * some other component hasn't already done so first.
            */
            valid = false;
            if (!isPreferredSizeSet()) {
                prefSize = null;
            }
            if (!isMinimumSizeSet()) {
                minSize = null;
            }
            if (!isMaximumSizeSet()) {
                maxSize = null;
            }
            if (parent != null && parent.valid) {
                parent.invalidate();
            }
        }
    }
    在java.awt.Component類的invalidate實(shí)現(xiàn)中,把prefSize 、minSize 、maxSize這3個(gè)提示屬性給清空(如果大小提示是通過重寫get...Size強(qiáng)制為特定常量或自定義計(jì)算規(guī)則,那么上述清空操作可能對(duì)你沒有實(shí)際意義),并且延著層次關(guān)系發(fā)送到父組件。因?yàn)閟wing組件的基類是javax.swing.JComponent,繼承層次關(guān)系是
    java.lang.Object
      java.awt.Component
          java.awt.Container
              javax.swing.JComponent

    所以對(duì)于所有swing組件來說,如果不重寫invalidate方法,都會(huì)是這樣的調(diào)用行為。
    那么LayoutManager2接口的實(shí)現(xiàn)中“void invalidateLayout(Container target)”方法中應(yīng)該做些什么?其實(shí)有些布局管理器的實(shí)現(xiàn)中是忽略的,例如java.awt.BorderLayout。
    正如JavaDoc所說的那樣“使布局失效,指示如果布局管理器緩存了信息,則應(yīng)該將其丟棄。”,應(yīng)該按照J(rèn)avaDoc要求的那樣去做就行了。例如java.awt.BoxLayout布局的實(shí)現(xiàn):
    public synchronized void invalidateLayout(Container target) {
            checkContainer(target);
            xChildren = null;
            yChildren = null;
            xTotal = null;
            yTotal = null;
    }
    但是也必須警惕,LayoutManager2接口的invalidateLayout(Container target)方法調(diào)用也很頻繁,當(dāng)組件尺寸改變時(shí),該方法就會(huì)被調(diào)用,因此釋放緩存信息時(shí)要小心。


    對(duì)于布局管理器來說,最重要的方法莫過于“void layoutContainer(Container parent)”。因?yàn)榻M件的最終布局都是在該方法中實(shí)現(xiàn)的。這個(gè)方法在很多情況下都會(huì)被awt-swing框架自動(dòng)調(diào)用,例如改變組件的字體、容器尺寸改變等都會(huì)觸發(fā)該方法的調(diào)用。布局管理器的layoutContainer方法并不會(huì)真正繪制組件,它只是調(diào)用每個(gè)組件的setSize、setLocation、setBounds方法來設(shè)置組件的大小和位置。對(duì)于自定義組件來說,可以調(diào)用revalidate強(qiáng)制實(shí)現(xiàn),或者調(diào)用容器的doLayout也可以強(qiáng)制實(shí)現(xiàn)。當(dāng)調(diào)用一個(gè)組件的revalidate方法時(shí),一個(gè)請(qǐng)求將通過包含層次關(guān)系發(fā)送到第一個(gè)容器,容器的大小會(huì)不會(huì)被容器的大小調(diào)整而影響通過調(diào)用容器的isValidateRoot方法來確定。然后容器被重新布局。
    如果你直接調(diào)用容器的doLayout,可以達(dá)到強(qiáng)制布局的效果。JDK源代碼中java.awt.Container的doLayout實(shí)現(xiàn)如下:
    public void doLayout() {
        layout();
    }
    @Deprecated
    public void layout() {
        LayoutManager layoutMgr = this.layoutMgr;
        if (layoutMgr != null) {
            layoutMgr.layoutContainer(this);
        }
    }
    可見doLayout方法是直接調(diào)用布局管理器的layoutContainer方法。
    此外再給出java.awt.Container的validate方法實(shí)現(xiàn)代碼:
    public void validate() {
        /* Avoid grabbing lock unless really necessary. */
        if (!valid) {
            boolean updateCur = false;
            synchronized (getTreeLock()) {
                if (!valid && peer != null) {
                    ContainerPeer p = null;
                    if (peer instanceof ContainerPeer) {
                        p = (ContainerPeer) peer;
                    }
                    if (p != null) {
                        p.beginValidate();
                    }
                    validateTree();
                    valid = true;
                    if (p != null) {
                        p.endValidate();
                        updateCur = isVisible();
                    }
                }
            }
            if (updateCur) {
                updateCursorImmediately();
            }
        }
    }
    注意“ validateTree();”方法,再給出 validateTree()方法實(shí)現(xiàn):
    protected void validateTree() {
        if (!valid) {
            if (peer instanceof ContainerPeer) {
                ((ContainerPeer)peer).beginLayout();
            }
            doLayout();
            Component component[] = this.component;
            for (int i = 0 ; i < ncomponents ; ++i) {
                Component comp = component[i];
                if ((comp instanceof Container) && !(comp instanceof Window) && !comp.valid) {
                    ((Container)comp).validateTree();
                } else {
                    comp.validate();
                }
            }
            if (peer instanceof ContainerPeer) {
                ((ContainerPeer)peer).endLayout();
            }
        }
        valid = true;
    }
    可以看出在validateTree方法執(zhí)行過程中調(diào)用了“doLayout();”方法。也就是說會(huì)調(diào)用到LayoutManager接口的void layoutContainer(Container parent)方法。
    再給出javax.swing.JComponent類setFont方法實(shí)現(xiàn):
    public void setFont(Font font) {
        Font oldFont = getFont();
        super.setFont(font);
        // font already bound in AWT1.2
        if (font != oldFont) {
            revalidate();
            repaint();
        }
    }
    因?yàn)樽煮w的改變會(huì)影響到組件的尺寸,因此也涉及到布局。如果你查看JDK API相關(guān)源碼,就會(huì)發(fā)現(xiàn)很多情況下“revalidate();”、“ repaint();”兩個(gè)方法是一起被先后調(diào)用的。這兩個(gè)方法都是線程安全的,不需要在事件分發(fā)線程中調(diào)用它們。
    layoutContainer(Container parent)在很多地方都會(huì)被調(diào)用的。因此可以這樣理解:凡是能影響組件尺寸改變的條件都可能觸發(fā)該方法的調(diào)用。那么在layoutContainer中需要做的就是,根據(jù)收集到的組件提示信息、約束條件、容器的內(nèi)部邊框、組件的可見性及布局規(guī)則等因素對(duì)組件進(jìn)行最終定位。

    到此為止,有關(guān)布局管理器的整體介紹和工作原理就告一段落。學(xué)習(xí)布局管理器的最終目的是學(xué)會(huì)如何自定義布局管理器,好,準(zhǔn)備進(jìn)入下一部分的學(xué)習(xí),但是之前最好要把上面講述的消化一遍,尤其是接口方法的調(diào)用時(shí)機(jī),這將是自定義布局管理器的基礎(chǔ)。

    由于平時(shí)比較緊,文章基本是周末空閑時(shí)間寫,而且目前的工作方向不再是gooey了,所以寫一篇帖很不容易,準(zhǔn)備一篇好貼更難。布局管理器這塊本人一直想發(fā)表下自己的觀點(diǎn),敬請(qǐng)關(guān)注。

    posted on 2007-11-18 15:15 sun_java_studio@yahoo.com.cn(電玩) 閱讀(10287) 評(píng)論(4)  編輯  收藏 所屬分類: NetBeans 、Swing

    評(píng)論

    # re: 布局管理器面面觀 2007-11-18 15:52 ivin

    終于出新的文章 了。一直都在期待啊。天天都打開樓主的blog看看有沒有新的文章。一口氣看完了,寫的非常好,學(xué)了不少東西,期待樓主以后寫更好的文章。  回復(fù)  更多評(píng)論   

    # re: 布局管理器面面觀 2007-11-18 20:33 歡樂的豬

    不錯(cuò),尤其期待第六部分的內(nèi)容  回復(fù)  更多評(píng)論   

    # re: 布局管理器面面觀 2007-11-18 21:51 dragoon

    期待后面的內(nèi)容……  回復(fù)  更多評(píng)論   

    # re: 布局管理器面面觀 2007-12-04 14:12 herui

    頂,樓主寫的很好,期待以后的文章  回復(fù)  更多評(píng)論   

    TWaver中文社區(qū)
    主站蜘蛛池模板: 在线免费视频一区| 久久青青草原亚洲av无码| 亚洲Aⅴ在线无码播放毛片一线天| 成年女人男人免费视频播放| 男女啪啪免费体验区| 久久精品国产亚洲AV网站| 男人的好看免费观看在线视频| 精品国产亚洲AV麻豆| 精品亚洲综合在线第一区| 成人免费无毒在线观看网站| 一区在线免费观看| 国产精品亚洲精品观看不卡| 亚洲一区二区三区乱码A| 国产成人精品免费视频大| 日韩毛片在线免费观看| 亚洲精品综合久久中文字幕| 国产无遮挡吃胸膜奶免费看视频| 国色精品va在线观看免费视频 | 亚洲精品国产首次亮相| 情人伊人久久综合亚洲| 女人被弄到高潮的免费视频| 免费无码一区二区三区蜜桃 | 久久成人免费电影| 国产亚洲视频在线| 亚洲精品自在线拍| 亚洲一区无码中文字幕| 日本免费人成黄页在线观看视频| 久久久久免费精品国产小说| 全部一级一级毛片免费看| 亚洲一区二区三区免费在线观看| 亚洲综合精品香蕉久久网| 日韩中文无码有码免费视频| 免费观看久久精彩视频| 一级做a爰片久久毛片免费陪 | h视频免费高清在线观看| 亚洲综合av一区二区三区 | 亚洲欧美日韩久久精品| 久久精品国产亚洲av麻豆小说| 亚洲精品线路一在线观看| 成人毛片18女人毛片免费| 亚洲精品免费在线观看|