java SWT:自定义布局(Layout)实现组件自动缩放显示

什么是布局(Layout)

窗口布局(Layout)其实是指Composite中组件的一种定位原则的实现,当Composite改变大小时,会自动调用Composite初始化时设置的Layout对象来重新调整所有组件的位置。
一般的UI框架都提供了一些默认布局,比如SWT中的FillLayout,GridLayout…如果使用WindowBuilder开发UI,可以在Design界面下看到所有SWT提供的布局对象,见下图
java SWT:自定义布局(Layout)实现组件自动缩放显示_第1张图片

自定义布局

有的时候,使用SWT提供的布局是无法满足需要的,这种情况下,就需要自实现所需的特殊布局。
实现自定义的Layout并不复杂,
以下是org.eclipse.swt.widgets.Layout的简要注释说明:

package org.eclipse.swt.widgets;
import org.eclipse.swt.graphics.*;

/** * 布局抽象类, * 用于控制组件内所有子对象的位置和尺寸 */
public abstract class Layout {

/** * 必须实现的抽象方法 * 返回容器组件(父窗口)的Client区域尺寸 */
protected abstract Point computeSize (Composite composite, int wHint, int hHint, boolean flushCache);

protected boolean flushCache (Control control) {
    return false;
}

/** * 必须实现的抽象方法 * 设置所有容器组件(父窗口)内所有子组件的位置和大小 * @param composite 将被重新设置布局的容器组件(父窗口) * @param flushCache true means flush cached layout values */
protected abstract void layout (Composite composite, boolean flushCache);
}

从上面的代码可以知道,只要实现抽象类org.eclipse.swt.widgets.Layout的两个抽象方法就可以实现一个特殊布局了,SWT提供的那些默认布局类都是通过继承Layout实现的

关于Layout的详细原文说明参见SWT的javadoc
http://help.eclipse.org/neon/nftopic/org.eclipse.platform.doc.isv/reference/api/org/eclipse/swt/widgets/Layout.html

组件自动缩放显示

上一节讲完Layout的实现思路,下面就以以一个实例来说明如何实现自定义布局。
比如下面的图中矩形框,并不是画在背景图上的,而是背景透明的Composite,可以移动和改变尺寸(如何实现,参见我的上一篇博客《 java SWT入门:自定义背景透明且可鼠标拖动改变尺寸和位置的Composite》)
这些矩形用于对图像中的人脸位置进行标注,我们希望当图像大小和位置改变的时候,这些矩形在图像上的相对位置保持不变。
java SWT:自定义布局(Layout)实现组件自动缩放显示_第2张图片
java SWT:自定义布局(Layout)实现组件自动缩放显示_第3张图片

这种需求,SWT中现成的布局都不能满足要求,所以就要自己实现一个,以下是实现代码,
ActiveRectContainer.java

package net.gdface.ui;

import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.swt.SWT;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Decorations;
import org.eclipse.swt.widgets.Layout;
import org.eclipse.wb.swt.SWTResourceManager;

/** * 活动矩形显示容器 * 窗口尺寸改变时所有{@link ActiveRectangle}对象自动等比例改变 * * @author guyadong * */
public class ActiveRectContainer extends Decorations {
    /** * 创建自定义的布局对象实现窗口内的ActiveRectangle对象能根据父窗口的尺寸改变而同步等比例改变, * 以保持每一个矩形在父窗口上的相对位置不变 * @author guyadong * */
    class ZoomLayout extends Layout {
        @Override
        protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
            // 返回可以画图的区域
            Rectangle bounds = composite.getClientArea();
            return new Point(bounds.width, bounds.height);
        }
        @Override
        protected void layout(Composite composite, boolean flushCache) {
            Control[] childrens = composite.getChildren();
            Rectangle originalbounds = getBackgroundImage().getBounds();
            Rectangle bounds = composite.getBounds();
            // 计算x/y轴缩放比例
            float zoomX = (float) bounds.width / originalbounds.width;
            float zoomY = (float) bounds.height / originalbounds.height;
            for (Control children : childrens) {
                if (children instanceof ActiveRectangle) {
                    // 对于ActiveRectangle对象调用zoom方法改变bounds
                    ((ActiveRectangle) children).zoom(zoomX, zoomY);
                }else if(children.getLayoutData() instanceof Rectangle){
                    // 对于其他Control对象调用如果通过setLayoutData()设置了原始的对象尺寸,则可以根据父窗口尺寸同步改变bounds
                    Rectangle originalBounds=(Rectangle) children.getLayoutData();// 获取Control原始位置尺寸
                    children.setBounds(new Rectangle((int)(originalBounds.x*zoomX),(int)(originalBounds.y*zoomY),(int)(originalBounds.width*zoomX),(int)(originalBounds.height*zoomY)));
                }
            }
        }
    }
    protected List annRects=EMPTY_RECTS;
    private static final List EMPTY_RECTS=new ArrayList();

    /** * @param parent * @param image 显示的背景图像,为null时不显示 * @param rects 显示的矩形对象数组 * @param focusIndex 焦点矩形索引,超出 rects索引范围时无效 */
    public ActiveRectContainer(Composite parent, Image image, Rectangle[] rects, int focusIndex) {
        super(parent, SWT.BORDER|SWT.RESIZE);
        if(null!=rects&&rects.length>0){
            this.annRects=new ArrayList();
            for(Rectangle rect:rects){
                // 创建矩形对象(ActiveRectangle)
                annRects.add(new ActiveRectangle(this, false,rect));
            }
            try{
                // 设置焦点对象
                this.annRects.get(focusIndex).focus=true;
            }catch(IndexOutOfBoundsException  e){}
        }

        this.setBackgroundImage(null==image?SWTResourceManager.getMissingImage():image);
        // 设置自定义布局对象 
        this.setLayout(new ZoomLayout());
        addPaintListener(new PaintListener() {
            @Override
            public void paintControl(PaintEvent e) {
                // 调用重绘方法
                paintImage(e.gc);
            }
        });
    }

    /** * @param parent * @param url 背景图像的URL * @param rects * @param focusIndex * @see #AutoZoomRecContainer(Composite, Image, Rectangle[], int) */
    public ActiveRectContainer(Composite parent, URL url, Rectangle[] rects, int focusIndex) {
        this(parent, SWTResourceManager.getImage(url),rects, focusIndex);
    }

    /** * 将 {@link #image} 重绘图像到窗口(缩放到整个窗口) * * @param gc */
    protected final void paintImage(GC gc) {

        boolean isAdvanced = gc.getAdvanced();
        try {
            gc.setAdvanced(true);
            gc.setAntialias(SWT.ON);
            Point destSize = getSize();
            Image image = getBackgroundImage();
            Rectangle imgSize = image.getBounds();
            gc.drawImage(image, 0, 0, imgSize.width, imgSize.height, 0, 0, destSize.x, destSize.y);
        } finally {
            gc.setAdvanced(isAdvanced);
        }
    }

    /** * 以窗口中心为原点对窗口进行缩放 * @param zoomX x轴缩放比例 * @param zoomY x轴缩放比例 */
    public void zoomCenter(float zoomX, float zoomY) {
        // 以背景图像的尺寸为当前对象的原始尺寸
        Rectangle originalSize = getBackgroundImage().getBounds();
        Rectangle bounds=getBounds();
        // 缩放后的尺寸
        int width=(int) (originalSize.width*zoomX);
        int height=(int) (originalSize.height*zoomX);
        // 中心点位置
        int x=bounds.x+(bounds.width-width)/2;
        int y=bounds.y+(bounds.height- height)/2;       
        super.setBounds(x, y, width, height);
    }
    /** * x/y轴等比例缩放 * @param zoom 缩放比例 * @see #zoomCenter(float, float) */
    public void zoomCenter(float zoom) {
        zoomCenter(zoom,zoom);
    }

    @Override
    protected void checkSubclass() {
    }

}

注意:自定义布局实现在ActiveRectContainer.java的代码中是以一个内部类ZoomLayout 来实现的

以下是用WindowBuilder生成的测试代码
TestRectContainer.java

package testwb;

import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.wb.swt.SWTResourceManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import net.gdface.ui.ActiveRectContainer;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;

public class TestRectContainer {
    static final Logger logger = LoggerFactory.getLogger(TestRectContainer.class);
    protected Shell shell;

    /** * Launch the application. * @param args */
    public static void main(String[] args) {
        try {
            TestRectContainer window = new TestRectContainer();
            window.open();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /** * Open the window. */
    public void open() {
        Display display = Display.getDefault();
        createContents();
        shell.open();
        shell.layout();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) {
                display.sleep();
            }
        }
    }
    /** * 返回适合当前窗口尺寸完整显示图像的缩放比例,图像长宽都小于显示窗口时,则返回1 * * @return */
    private static float fitZoom(Point parentSize,Point childrenSize) {
        if (childrenSize.x < parentSize.x && childrenSize.y < parentSize.y)
            return 1f;
        if (childrenSize.x * parentSize.y < childrenSize.y * parentSize.x) {
            return ((float) parentSize.y) / (float)childrenSize.y;
        }
        return ((float) parentSize.x) / (float)childrenSize.x;
    }
    private static Rectangle fitBounds(Point parentSize,Point childrenSize) {
        float zoom = fitZoom(parentSize, childrenSize);
        childrenSize.x*=zoom;
        childrenSize.y*=zoom;
        return new Rectangle((parentSize.x-childrenSize.x)/2,(parentSize.y-childrenSize.y)/2,childrenSize.x,childrenSize.y);
    }
    /** * Create contents of the window. */
    protected void createContents() {
        shell = new Shell();
        shell.setText("SWT Application");
        shell.setSize(569, 459);
        Rectangle[] rects = new Rectangle[]{
                new Rectangle(50,50,200,200),
                new Rectangle(350,100,150,250),
                new Rectangle(125,300,200,321)};
        ActiveRectContainer canvas;
        //Image image = SWTResourceManager.getImage("http://pic8.nipic.com/20100704/3525627_110847063052_2.jpg");
        Image image = SWTResourceManager.getImage("J:/workspace.neon/iadbui/src/image/3525627_110847063052_2.jpg");
        //Image image = SWTResourceManager.getImage(this.getClass(),"/image/3525627_110847063052_2.jpg");
        Point imgSize=new Point(image.getBounds().width,image.getBounds().height);
        canvas = new ActiveRectContainer(shell, image,rects,0);
        canvas.setBounds(fitBounds(shell.getSize(),imgSize));
        Button btnNewButton = new Button(canvas, SWT.NONE);
        btnNewButton.setBounds(189, 95, 80, 27);
        // 如果这里通过setLayoutData设置了btnNewButton的原始尺寸,btnNewButton也会随着窗口尺寸改变而自动缩放
        btnNewButton.setLayoutData(new Rectangle(189, 95, 80, 27));
        btnNewButton.setText("New Button");
    }
}

请注意:
对于一般的Control对象,如果没有通过setLayoutData方法设置原始的尺寸位置,则Layout对其无效,所以上面的测试代码中对btnNewButton调用了setLayoutData,指定了初始的位置和尺寸。这样它才能与父窗口同步缩放。如下图
java SWT:自定义布局(Layout)实现组件自动缩放显示_第4张图片
如果注释掉 btnNewButton.setLayoutData(new Rectangle(189, 95, 80, 27));这一行,效果是这样的
java SWT:自定义布局(Layout)实现组件自动缩放显示_第5张图片

参考

《org.eclipse.swt.widgets.Layout》
《 java SWT入门:自定义背景透明且可鼠标拖动改变尺寸和位置的Composite》

你可能感兴趣的:(开发工具,java)