目的
如图实现一个商品列表,纵向支持滚动,当横向宽度变化时,商品自动向右向上填充。
当鼠标悬浮在商品上时,商品边框显示。当点击商品时,控制台打印商品名称。
分析
滚动容器依然使用 ScrolledComposite
,容器中的内容要随着容器宽度变化自动填充,需要是使用 Rowlayout
布局方式。在Rowlayout 布局中,每一个商品信息及特效使用 GC 实现。具体见项目 https://gitee.com/xzbd/epx
关键实现
该功能依然集成在RCP中,大致步骤如下:
- plugin.xml中配置编辑器
<!-- 编辑器-滚动容器-商品列表 -->
<editor
class="com.xzbd.editors.ScrollGoodListEditor"
default="false"
extensions="scgl"
icon="icons/20220721/16/paobing.png"
id="com.xzbd.editors.ScrollGoodListEditor"
name="编辑器-滚动容器-商品列表">
</editor>
- 定义编辑器
package com.xzbd.editors;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.part.EditorPart;
import com.xzbd.views.ScrollGoodListEditorViewer;
public class ScrollGoodListEditor extends EditorPart {
public static String ID = "com.xzbd.editors.ScrollGoodListEditor";
public boolean isDirty = false;
private ScrollGoodListEditorViewer viewer;
@Override
public void doSave(IProgressMonitor monitor) {
clearDirty();
}
@Override
public void doSaveAs() {
}
@Override
public void init(IEditorSite site, IEditorInput input) throws PartInitException {
this.setSite(site);
this.setInput(input);
}
@Override
public boolean isDirty() {
return isDirty;
}
@Override
public boolean isSaveAsAllowed() {
return false;
}
@Override
public void createPartControl(Composite parent) {
viewer = new ScrollGoodListEditorViewer(parent, this);
}
@Override
public void setFocus() {
}
/**
* 转换编辑器状态
*
* @param isDirty
*/
public void changeDirtyState(boolean isDirty) {
this.isDirty = isDirty;
firePropertyChange(PROP_DIRTY);
}
public void toDirty() {
changeDirtyState(true);
}
public void clearDirty() {
changeDirtyState(false);
}
}
- 实现编辑器视图
package com.xzbd.views;
import org.apache.commons.lang.math.RandomUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Font;
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.layout.FillLayout;
import org.eclipse.swt.layout.RowData;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Widget;
import com.xzbd.constants.ColorConstant;
import com.xzbd.editors.ScrollGoodListEditor;
import com.xzbd.utils.AppPrinter;
import com.xzbd.utils.CompositeUtil;
import com.xzbd.utils.SWTResourceManager;
/**
*
* 滚动容器--商品列表示例
*
*/
public class ScrollGoodListEditorViewer {
private String[] iconNames = { "bingkuai.png", "ganlan.png", "guodong.png", "jiroujuan.png", "kafei.png",
"kele.png", "mantou.png", "meishikafei.png", "mianhuatang.png", "nailao.png", "paobing.png", "qishui.png",
"shutiao.png", "tiantong.png", "xuegao.png", "zhenzhunaicha.png" };
private ScrollGoodListEditor editor;
private Composite parent;
public ScrollGoodListEditorViewer(Composite parent, ScrollGoodListEditor editor) {
this.parent = parent;
// 缓存 Editor
setEditor(editor);
// 绘制UI
buildPageUI();
}
// 创建UI
private void buildPageUI() {
// 设置 parent 布局
parent.setLayout(new FillLayout());
// 生成滚动容器
Composite scContent = CompositeUtil.getAutoFillScrollCmp(parent);
for (int i = 0; i <= 50 + RandomUtils.nextInt(50); i++) {
Composite itemCmp = new Composite(scContent, SWT.NONE);
itemCmp.setLayoutData(new RowData(160, 120));
// 选定图片
String iconName = iconNames[RandomUtils.nextInt(iconNames.length)];
String imgPath = "/icons/20220721/32/" + iconName;
Image img = SWTResourceManager.getImage(ScrollGoodListEditorViewer.class, imgPath);
// GC 绘制
Boolean[] lineTags = { false };
itemCmp.addPaintListener(e -> {
GC gc = e.gc;
Rectangle clientArea = itemCmp.getClientArea();
Rectangle imgBounds = img.getBounds();
int imgWidth = (clientArea.width - imgBounds.width) / 2;
// int imgHeight = (clientArea.height - imgBounds.height) / 2;
gc.drawImage(img, imgWidth, 15);
String text = iconName.substring(0, iconName.lastIndexOf("."));
Font font = SWTResourceManager.getFont("Microsoft YaHei", 14, SWT.NORMAL);
if (lineTags[0]) {
gc.setLineWidth(5);
gc.setForeground(ColorConstant.MAIN_BLUE);
gc.drawRectangle(0, 0, clientArea.width, clientArea.height);
font = SWTResourceManager.getBoldFont(font);
}
gc.setFont(font);
Point textExtentPoint = gc.textExtent(text);
gc.setForeground(ColorConstant.MAIN_BLUE);
int textWidth = (clientArea.width - textExtentPoint.x) / 2;
int textHeight = clientArea.height - textExtentPoint.y - 10;
gc.drawText(text, textWidth, textHeight, true);
});
// 事件绑定
Listener enterListener = e -> {
lineTags[0] = true;
itemCmp.redraw();
};
itemCmp.addListener(SWT.MouseEnter, enterListener);
Listener exitListener = e -> {
lineTags[0] = false;
itemCmp.redraw();
};
itemCmp.addListener(SWT.MouseExit, exitListener);
itemCmp.addListener(SWT.MouseUp, e->{
AppPrinter.println("您挑选了商品: " + iconName);
});
}
}
public ScrollGoodListEditor getEditor() {
return editor;
}
public void setEditor(ScrollGoodListEditor editor) {
this.editor = editor;
}
private void addDirtyListener(Widget com) {
com.addListener(SWT.Modify, e -> {
editor.toDirty();
});
}
}
- 滚动容器关键代码
/**
* 生成滚动容器
* <div>
* parent 布局方式需是 FillLayout,返回的容器布局方式是 RowLayout
* </div>
* @param parent
* @return
*/
public static Composite getAutoFillScrollCmp(Composite parent) {
ScrolledComposite scrollComposite = new ScrolledComposite(parent, SWT.V_SCROLL | SWT.BORDER);
Composite scContent = new Composite(scrollComposite, SWT.NONE);
RowLayout rowLayout = new RowLayout();
rowLayout.spacing = 16;
rowLayout.marginWidth = 24;
RowLayout layout = new RowLayout(SWT.HORIZONTAL);
layout.wrap = true;
scContent.setLayout(layout);
scrollComposite.setContent(scContent);
scrollComposite.setExpandVertical(true);
scrollComposite.setExpandHorizontal(true);
scrollComposite.addControlListener(ControlListener.controlResizedAdapter(e -> {
Rectangle r = scrollComposite.getClientArea();
scrollComposite.setMinSize(scContent.computeSize(r.width, SWT.DEFAULT));
}));
return scContent;
}
效果
总结
依赖 ScrolledComposite
,Rowlayout
, GC
实现了一个滚动容器,该容器支持纵向滚动,横向自动适配填充。适合用于类似与商品列表等展示的场景。