前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >java SWT:自定义布局(Layout)实现组件自动缩放显示

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

作者头像
10km
发布2019-05-25 22:31:48
1.6K0
发布2019-05-25 22:31:48
举报
文章被收录于专栏:10km的专栏10km的专栏

版权声明:本文为博主原创文章,转载请注明源地址。 https://cloud.tencent.com/developer/article/1433717

什么是布局(Layout)

窗口布局(Layout)其实是指Composite中组件的一种定位原则的实现,当Composite改变大小时,会自动调用Composite初始化时设置的Layout对象来重新调整所有组件的位置。

一般的UI框架都提供了一些默认布局,比如SWT中的FillLayout,GridLayout…如果使用WindowBuilder开发UI,可以在Design界面下看到所有SWT提供的布局对象,见下图

自定义布局

有的时候,使用SWT提供的布局是无法满足需要的,这种情况下,就需要自实现所需的特殊布局。

实现自定义的Layout并不复杂,

以下是org.eclipse.swt.widgets.Layout的简要注释说明:

代码语言:javascript
复制
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的javadocundefined 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

代码语言:javascript
复制
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

代码语言:javascript
复制
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》

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2016年12月03日,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 什么是布局(Layout)
  • 自定义布局
  • 组件自动缩放显示
  • 参考
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档