2010/02/05

LayoutManager

可以製作出LayoutManager嘛?

在某些複雜的Layout情況下,果然自己製作LayoutManager是最簡單的解決方法。
承做LayoutManager或是LayoutManager2介面來作成,LayoutManager是最基本的,而LayoutManager2是它的擴張。

說到LayoutManager時,使用AWT/Swing時常常會受到它的照顧,可是意外地,卻很少人自己製作LayoutManager。
因為已經有很多現成的LayoutManager可以用了,自己再來作總是會有「自找麻煩」的感覺。但是其實製作一個
LayoutManager並沒有這麼難,試著做一次來挑戰看看吧。不僅僅是Swing,AWT也能使用,只要做一次就能夠重複利用。

import java.awt.*;
import java.awt.event.*;
import java.util.*;

import javax.swing.*;

public class SampleApp extends JFrame implements ActionListener {
    private static final long serialVersionUID = 1L;
    
    public SampleApp(){
        this.setSize(new Dimension(300,200));
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLayout(new MyLayout());
        
        actionPerformed(null);
    }
    
    public void actionPerformed(ActionEvent e){
        JButton btn = new JButton("button");
        btn.setFont(new Font("Serif",Font.PLAIN,18));
        btn.addActionListener(this);
        this.add("",btn);
    }
    
    public static void main(String[] args) {
        new SampleApp().setVisible(true);
    }
}

class MyLayout implements LayoutManager {
    private Container container;
    private ArrayList<component> components;
    private int miniW = 50;
    private int miniH = 20;
    
    public MyLayout(){
        components = new ArrayList<component>();
    }
    
    @Override
    public void addLayoutComponent(String name, Component comp) {
        components.add(comp);
        if (container != null) container.doLayout();
    }

    @Override
    public void removeLayoutComponent(Component comp) {
        components.remove(comp);
        if (container != null) container.doLayout();
    }
    
    @Override
    public void layoutContainer(Container parent) {
        container = parent;
        int insetSize = 20;
        int w = (parent.getWidth() - insetSize * 2) / 2;
        int h = (parent.getHeight() - insetSize * 2);
        int size = components.size() == 0 ? 1 : components.size();
        int insetW = w / size;
        int insetH = h / size;
        int x = insetSize;
        int y = insetSize;
        for(Component c: components){
            c.setBounds(new Rectangle(x,y,w,insetH));
            x += insetW;
            y += insetH;
        }
    }

    @Override
    public Dimension minimumLayoutSize(Container parent) {
        return new Dimension(100,100);
    }

    @Override
    public Dimension preferredLayoutSize(Container parent) {
        return parent.getPreferredSize();
    }
}

用MyLayout來定義上面的簡易Layout,LayoutManager的Interface定義了下面五個方法

public void addLayoutComponent(String name, Component comp)
加入Component時的處理。

public void removeLayoutComponent(Component comp)
刪除Component時的處理。

public void layoutContainer(Container parent)
再Layout處理。如果發生再Layout的要求時,會再度呼叫LayoutManager的layoutContainer。

public Dimension minimumLayoutSize(Container parent)
被Layout的Container的最小大小作為Dimension物件回傳。

public Dimension preferredLayoutSize(Container parent)
被Layout的Container的建議大小作為Dimension物件回傳。

最麻煩的地方就是layoutContainer,只要能夠處理好的話,其他就很簡單了。

LayoutManager2可以用嗎?

LayoutManager的情況下,add時只傳給String。但是想到更高級的Layout的話,add時也順便傳要做該Layout的物件,
將它作為基本來達成Layout的話會更好。想要這樣的話就得使用LayoutManager2。這是LayoutManager的方法以外,還
準備了許多其他的方法。

import java.awt.*;
import java.awt.event.*;
import java.util.*;

import javax.swing.*;

public class SampleApp extends JFrame implements ActionListener {
    private static final long serialVersionUID = 1L;
    
    public SampleApp(){
        this.setSize(new Dimension(300,200));
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setLayout(new MyLayout());
        
        actionPerformed(null);
    }
    
    public void actionPerformed(ActionEvent e){
        int n = new Random().nextInt(200) + 100;
        JButton btn = new JButton("button [" + n + "]");
        btn.setFont(new Font("Serif",Font.PLAIN,18));
        btn.addActionListener(this);
        this.add(btn,new Integer(n));
    }
    
    public static void main(String[] args) {
        new SampleApp().setVisible(true);
    }
}

class MyLayout implements LayoutManager2 {
    private Container container;
    private ArrayList<component> components;
    private ArrayList<integer> widths;
    
    public MyLayout(){
        components = new ArrayList<component>();
        widths = new ArrayList<integer>();
    }
    
    @Override
    public void addLayoutComponent(String name, Component comp) {
        try {
            this.addLayoutComponent(
                comp,new Integer(Integer.parseInt(name)));
        } catch (NumberFormatException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void removeLayoutComponent(Component comp) {
        int n = -1;
        for (int i = 0;i < components.size();i++){
            if (components.get(i) == comp) n = i;
        }
        components.remove(comp);
        widths.remove(n);
        if (container != null) container.doLayout();
    }
    
    @Override
    public void layoutContainer(Container parent) {
        container = parent;
        int insetSize = 20;
        int h = (parent.getHeight() - insetSize * 2);
        int size = components.size() == 0 ? 1 : components.size();
        int insetH = h / size;
        int x = insetSize;
        int y = insetSize;
        for(int i = 0;i < components.size();i++){
            Component c = components.get(i);
            int w = widths.get(i);
            c.setBounds(new Rectangle(x,y,w,insetH));
            y += insetH;
        }
    }

    @Override
    public Dimension minimumLayoutSize(Container parent) {
        return new Dimension(100,100);
    }

    @Override
    public Dimension preferredLayoutSize(Container parent) {
        return parent.getPreferredSize();
    }

    @Override
    public void addLayoutComponent(
        Component comp, Object constraints) {
        components.add(comp);
        try {
            widths.add((Integer)constraints);
        } catch(ClassCastException ex){
            widths.add(0);
        }
        if (container != null) container.doLayout();
    }

    @Override
    public float getLayoutAlignmentX(Container target) {
        return 0;
    }

    @Override
    public float getLayoutAlignmentY(Container target) {
        return 0;
    }

    @Override
    public void invalidateLayout(Container target) {}

    @Override
    public Dimension maximumLayoutSize(Container target) {
        return new Dimension(target.getMaximumSize());
    }
}

上面的例子使用LayoutManager2,將Integer實例傳入作為寬度指定。

public void addLayoutComponent(Component comp, Object constraints)
在加入時不是傳入String而是傳入Object

public float getLayoutAlignmentX(Container target)
回傳X方向的配置值

public float getLayoutAlignmentY(Container target)
回傳Y方向的配置值

public void invalidateLayout(Container target)
使得Layout無效化

public Dimension maximumLayoutSize(Container target)
回傳Container的最大Size

參考文章:http://codezine.jp/article/detail/1626?p=4

No comments:

Post a Comment