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

什么是布局(Layout)

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

自定义布局

有的时候,使用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 <code>true</code> 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》)
这些矩形用于对图像中的人脸位置进行标注,我们希望当图像大小和位置改变的时候,这些矩形在图像上的相对位置保持不变。
这里写图片描述
这里写图片描述

这种需求,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<ActiveRectangle> annRects=EMPTY_RECTS;
    private static final List<ActiveRectangle> EMPTY_RECTS=new ArrayList<ActiveRectangle>();

    /**
     * @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<ActiveRectangle>();
            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,指定了初始的位置和尺寸。这样它才能与父窗口同步缩放。如下图
这里写图片描述
如果注释掉 btnNewButton.setLayoutData(new Rectangle(189, 95, 80, 27));这一行,效果是这样的
这里写图片描述

参考

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

  • 0
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

10km

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值