您现在的位置是:网站首页 > 代码编程 > JAVA开发JAVA开发
【原】Swing界面优化JTabbedPane详细图文教程
不忘初心 2017-11-09 围观() 评论() 点赞() 【JAVA开发】
简介:在使用一些聊天软件的时候,如果打开了多个好友窗口,就会被整合到一起,有一个tab页签用来切换,这个功能在swing中对应的就是JTabbedPane组件,沿袭了
在使用一些聊天软件的时候,如果打开了多个好友窗口,就会被整合到一起,有一个tab页签用来切换,这个功能在swing中对应的就是JTabbedPane组件,沿袭了swing一贯的风格,这个组件的原生UI简直丑的不能看,今天就来教大家如何定制自己的UI。
没错,上面这个图就是原生UI,不知道大家能不能忍,反正我是忍不了。
图片中,我已经做了标注,它大体就分为两块儿,上面的tab,加上下面的一个pane,内容区域每次只能展示一个,点击哪个tab就展示对应的pane,目测是一个卡片布局,所以就算是自己来实现一个,也不是不可能的事情,只不过要花费很多的时间来做细节,毕竟一个成熟的组件是要考虑到任何场景的,我们就简单点,直接优化一下它的UI。
经过之前的几篇文章,大家应该都已经看到,基本上swing的任何组件,都是有对应的paint方法来负责绘制UI的,内容区域就是一个矩形,可以不用优化,自己根据需要调整一下背景和边框颜色,不过在使用的时候,放入了其他组件,背景就被盖住了,也就无所谓背景色了,所以这次优化的主要着力点就是在tab页签上面。
还是依照“扁平化”的理念来优化UI,思路如下:
1、改变tab的形状、大小、颜色、边框;
2、改变pane区域的边框颜色;
查看BasicTabbedPaneUI的代码,利用工具的快捷键搜一下,直接就能看到关于tab绘制的相关方法,分别是paintTabArea、paintTab、paintTabBackground、paintTabBorder这四个
第一个方法是总入口,会被paintComponent方法调用
public void paintComponent(Graphics g) {
super.paintComponent(g);
BasicTabbedPaneUI.this.paintTabArea(g, tabPane.getTabPlacement(), tabPane.getSelectedIndex());
if (tabScroller.croppedEdge.isParamsSet() && tabContainer == null) {
Rectangle croppedRect = rects[tabScroller.croppedEdge.getTabIndex()];
g.translate(croppedRect.x, croppedRect.y);
tabScroller.croppedEdge.paintComponent(g);
g.translate(-croppedRect.x, -croppedRect.y);
}
}
再来看一下paintTabArea方法的代码
protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) {
int tabCount = tabPane.getTabCount();
Rectangle iconRect = new Rectangle(),
textRect = new Rectangle();
Rectangle clipRect = g.getClipBounds();
// Paint tabRuns of tabs from back to front
for (int i = runCount - 1; i >= 0; i--) {
int start = tabRuns[i];
int next = tabRuns[(i == runCount - 1)? 0 : i + 1];
int end = (next != 0? next - 1: tabCount - 1);
for (int j = start; j <= end; j++) {
if (j != selectedIndex && rects[j].intersects(clipRect)) {
paintTab(g, tabPlacement, rects, j, iconRect, textRect);
}
}
}
// Paint selected tab if its in the front run
// since it may overlap other tabs
if (selectedIndex >= 0 && rects[selectedIndex].intersects(clipRect)) {
paintTab(g, tabPlacement, rects, selectedIndex, iconRect, textRect);
}
}
它又去调用了paintTab方法
protected void paintTab(Graphics g, int tabPlacement,
Rectangle[] rects, int tabIndex,
Rectangle iconRect, Rectangle textRect) {
Rectangle tabRect = rects[tabIndex];
int selectedIndex = tabPane.getSelectedIndex();
boolean isSelected = selectedIndex == tabIndex;
if (tabsOpaque || tabPane.isOpaque()) {
paintTabBackground(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
tabRect.width, tabRect.height, isSelected);
}
paintTabBorder(g, tabPlacement, tabIndex, tabRect.x, tabRect.y,
tabRect.width, tabRect.height, isSelected);
...... // 此处代码省略,太占地方
paintFocusIndicator(g, tabPlacement, rects, tabIndex,
iconRect, textRect, isSelected);
}
我们可以看到,paintTab方法,分别去调用了border和background的绘制方法。
与此次优化无关的代码,我都以省略号代替了,大家可能会好奇,为什么最后那个paintFocusIndicator方法么有去掉呢?起初我也是去掉了的,可是后来发现,这个方法也需要优化一下,否则会在tab获得焦点的出现一条虚线边框。
最后再来看一下tab大小的调整,方法内容我们可以直接不管,因为最终的宽高只是一个数字,我们只需要灵活改变这个数字即可,我们可以从super中拿到这个值,然后在此基础上加加减减。
// 计算tab宽度
protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
......
}
// 计算tab高度
protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) {
......
}
优化的时候,我们只需要重写paintTabBorder、paintTabBackground、calculateTabWidth、calculateTabHeight,先上一张图片看看成果(代码在后面会统一贴出来)
下面我们再来看一下关于pane内容区域的相关代码,官方也只是提供了一个绘制边框的方法paintContentBorder,这个方法很有特色,他并不是跟我们一贯的思维一样,直接去绘制一个矩形的四条边,而是分别去绘制,top、left、bottom、right
protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
...... // 省略的代码,是用来计算坐标和宽高的,也就是x、y、w、h的值
paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h);
paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h);
paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h);
}
四条边的具体绘制方法,我们就不去看了,边框就是两个点之间的一条线段,在这里可以理解为两个坐标之间的一条线段,肯定是一个drawLine方法来搞定。
好了,优化思路中提到的点,已经分别找到对应的优化方法了,下面直接给出重写的UI代码:
package com.wolffy.ui;
import javax.swing.UIManager;
import javax.swing.plaf.basic.BasicTabbedPaneUI;
import java.awt.Color;
import java.awt.FontMetrics;
import java.awt.GradientPaint;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Insets;
import java.awt.Rectangle;
/**
* Created by SongFei on 2017/11/8.
*/
public class MyTabbedPaneUI extends BasicTabbedPaneUI {
// 不知道是个什么东西,绘制pane时,计算坐标和宽高的方法需要用到,直接从父类拷贝过来的
private boolean tabsOverlapBorder = UIManager.getBoolean("TabbedPane.tabsOverlapBorder");
// 边框和背景的颜色
private Color SELECT_COLOR = new Color(57, 181, 215);
/*// 绘制整个选项卡区域
@Override
protected void paintTabArea(Graphics g, int tabPlacement, int selectedIndex) {
// 如果没有特殊要求,就使用默认的绘制方案
super.paintTabArea(g, tabPlacement, selectedIndex);
}*/
// 绘制tab页签的边框
@Override
protected void paintTabBorder(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
// 测试了一下,发现没有边框更好看一点儿
/*g.setColor(SELECT_COLOR);
switch (tabPlacement) {
case LEFT:
g.drawLine(x, y, x, y + h - 1);
g.drawLine(x, y, x + w - 1, y);
g.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
break;
case RIGHT:
g.drawLine(x, y, x + w - 1, y);
g.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
g.drawLine(x + w - 1, y, x + w - 1, y + h - 1);
break;
case BOTTOM:
g.drawLine(x, y, x, y + h - 1);
g.drawLine(x + w - 1, y, x + w - 1, y + h - 1);
g.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
break;
case TOP:
default:
g.drawLine(x, y, x, y + h - 1);
g.drawLine(x, y, x + w - 1, y);
g.drawLine(x + w - 1, y, x + w - 1, y + h - 1);
}*/
}
// 绘制选中的选项卡背景色
@Override
protected void paintTabBackground(Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h, boolean isSelected) {
Graphics2D g2d = (Graphics2D) g;
GradientPaint gradient;
switch (tabPlacement) {
case LEFT:
if (isSelected) {
gradient = new GradientPaint(x + 1, y, SELECT_COLOR, x + w, y, Color.WHITE, true);
} else {
gradient = new GradientPaint(x + 1, y, Color.LIGHT_GRAY, x + w, y, Color.WHITE, true);
}
g2d.setPaint(gradient);
g.fillRect(x + 1, y + 1, w - 1, h - 2);
break;
case RIGHT:
if (isSelected) {
gradient = new GradientPaint(x + w, y, SELECT_COLOR, x + 1, y, Color.WHITE, true);
} else {
gradient = new GradientPaint(x + w, y, Color.LIGHT_GRAY, x + 1, y, Color.WHITE, true);
}
g2d.setPaint(gradient);
g.fillRect(x, y + 1, w - 1, h - 2);
break;
case BOTTOM:
if (isSelected) {
gradient = new GradientPaint(x + 1, y + h, SELECT_COLOR, x + 1, y, Color.WHITE, true);
} else {
gradient = new GradientPaint(x + 1, y + h, Color.LIGHT_GRAY, x + 1, y, Color.WHITE, true);
}
g2d.setPaint(gradient);
g.fillRect(x + 1, y, w - 2, h - 1);
break;
case TOP:
default:
if (isSelected) {
gradient = new GradientPaint(x + 1, y, SELECT_COLOR, x + 1, y + h, Color.WHITE, true);
} else {
gradient = new GradientPaint(x + 1, y, Color.LIGHT_GRAY, x + 1, y + h, Color.WHITE, true);
}
g2d.setPaint(gradient);
g2d.fillRect(x + 1, y + 1, w - 2, h - 1);
}
}
// 绘制TabbedPane容器的四周边框样式
@Override
protected void paintContentBorder(Graphics g, int tabPlacement, int selectedIndex) {
// 如果不想要边框,直接重写一个空方法
// super.paintContentBorder(g, tabPlacement, selectedIndex);
// 这些计算坐标和宽高的代码,直接从父类拷贝出来重用即可
int width = tabPane.getWidth();
int height = tabPane.getHeight();
Insets insets = tabPane.getInsets();
Insets tabAreaInsets = getTabAreaInsets(tabPlacement);
int x = insets.left;
int y = insets.top;
int w = width - insets.right - insets.left;
int h = height - insets.top - insets.bottom;
switch (tabPlacement) {
case LEFT:
x += calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
if (tabsOverlapBorder) {
x -= tabAreaInsets.right;
}
w -= (x - insets.left);
break;
case RIGHT:
w -= calculateTabAreaWidth(tabPlacement, runCount, maxTabWidth);
if (tabsOverlapBorder) {
w += tabAreaInsets.left;
}
break;
case BOTTOM:
h -= calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
if (tabsOverlapBorder) {
h += tabAreaInsets.top;
}
break;
case TOP:
default:
y += calculateTabAreaHeight(tabPlacement, runCount, maxTabHeight);
if (tabsOverlapBorder) {
y -= tabAreaInsets.bottom;
}
h -= (y - insets.top);
}
// 四个边框的绘制方法,都自己重写一遍,方便控制颜色和一些特效
paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
paintContentBorderLeftEdge(g, tabPlacement, selectedIndex, x, y, w, h);
paintContentBorderBottomEdge(g, tabPlacement, selectedIndex, x, y, w, h);
paintContentBorderRightEdge(g, tabPlacement, selectedIndex, x, y, w, h);
}
// 绘制容器左侧边框,不是tab,是pane
// 上下左右,都可以重写方法来绘制,相应的方法:paintContentBorder*Edge(),由paintContentBorder()方法统一调用
@Override
protected void paintContentBorderLeftEdge(Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {
g.setColor(SELECT_COLOR);
g.drawLine(x, y, x, y + h - 2);
}
@Override
protected void paintContentBorderTopEdge(Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {
g.setColor(SELECT_COLOR);
g.drawLine(x, y, x + w - 2, y);
}
// 右边和下边,这两个需要注意,x + w 和 y + h 已达到边框临界,必须减掉几个数值,否则边框会显示不出来
@Override
protected void paintContentBorderRightEdge(Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {
g.setColor(SELECT_COLOR);
g.drawLine(x + w - 1, y, x + w - 1, y + h - 1);
}
@Override
protected void paintContentBorderBottomEdge(Graphics g, int tabPlacement, int selectedIndex, int x, int y, int w, int h) {
g.setColor(SELECT_COLOR);
g.drawLine(x, y + h - 1, x + w - 1, y + h - 1);
}
// 绘制选中某个Tab后,获得焦点的样式
@Override
protected void paintFocusIndicator(Graphics g, int tabPlacement, Rectangle[] rects, int tabIndex, Rectangle iconRect, Rectangle textRect, boolean isSelected) {
// 重写空方法,主要用来去掉虚线
}
// 计算tab页签的宽度
@Override
protected int calculateTabWidth(int tabPlacement, int tabIndex, FontMetrics metrics) {
// 可根据placement来做不同的调整
return super.calculateTabWidth(tabPlacement, tabIndex, metrics) + 20;
}
// 计算tab页签的高度
@Override
protected int calculateTabHeight(int tabPlacement, int tabIndex, int fontHeight) {
// 可根据placement来做不同的调整
return super.calculateTabHeight(tabPlacement, tabIndex, fontHeight) + 20;
}
}
写一个测试类
package com.wolffy.frame;
import com.wolffy.ui.MyTabbedPaneUI;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JTabbedPane;
import javax.swing.WindowConstants;
import java.awt.BorderLayout;
/**
* Created by SongFei on 2017/11/8.
*/
public class TabPaneFrame extends JFrame {
private static final long serialVersionUID = 474825076033661007L;
private JPanel jPanel;
public TabPaneFrame() {
initGUI();
}
private void initGUI() {
setSize(700, 400);
setUndecorated(true);// 把边框去了,优化的效果能看的更明显
setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
jPanel = new JPanel();
//jPanel.setBackground(Color.ORANGE);
jPanel.setLayout(new BorderLayout());
getContentPane().add(jPanel, BorderLayout.CENTER);
JTabbedPane tab = new JTabbedPane(JTabbedPane.TOP);
tab.setUI(new MyTabbedPaneUI());
JPanel jPanel1 = new JPanel();
jPanel1.add(new JButton("AAA111"));
JPanel jPanel2 = new JPanel();
jPanel2.add(new JButton("AAA222"));
JPanel jPanel3 = new JPanel();
jPanel3.add(new JButton("AAA333"));
JPanel jPanel4 = new JPanel();
jPanel4.add(new JButton("AAA444"));
JPanel jPanel5 = new JPanel();
jPanel5.add(new JButton("AAA555"));
tab.add(jPanel1, "AAA111");
tab.add(jPanel2, "AAA222");
tab.add(jPanel3, "AAA333");
tab.add(jPanel4, "AAA444");
tab.add(jPanel5, "AAA555");
jPanel.add(tab, BorderLayout.CENTER);
}
public static void main(String[] args) {
TabPaneFrame paneFrame = new TabPaneFrame();
paneFrame.setVisible(true);
//loginFrame.pack();
paneFrame.setLocationRelativeTo(null);
}
}
案例中,我们将最外层的边框去了,这样优化的效果能看得更加明显,不废话了,上图:
再来一张tab放到左侧的效果图:
好了,到这里为止,整个UI的优化就完毕了!
看完文章,有任何疑问,请加入群聊一起交流!!!
很赞哦! ()
相关文章
- JTabbedPane第二次优化,添加关闭按钮
- 自定义JTabbedPane详细图文教程
- IntelliJ IDEA 2024.2发布之后强推新UI,如何恢复老的经典UI界面
- Uninstall hasn't detected folder of intelli] lDEA installation. Probablyuninstall.exe was moved from the installation folder.
- 公众号CPS变现新宠:微赚淘客查券返利机器人,开启智能省钱赚钱新时代
- 高返利优惠券公众号推荐
- 返利公众号可信吗安全吗?返利机器人哪个佣金高?
- 唯品会购物返利公众号大揭秘:哪些真正好用的返利公众号?
- 饿了么优惠券免费领取:哪些公众号值得推荐?
- 哪些有用可靠的微信公众号能领淘宝优惠券?
标签云
猜你喜欢
- IntelliJ IDEA 2019.2已经可以利用补丁永久破解激活了
- IntelliJ IDEA 2019.3利用补丁永久破解激活教程
- IntelliJ IDEA高版本最灵活的永久破解激活方法(含插件激活,时长你说了算)
- Jetbrains全家桶基于ja-netfilter的最新破解激活详细图文教程
- IntelliJ IDEA 2022.1永久破解激活教程(亲测可用,持续更新)
- 分享几个正版 IntelliJ IDEA 激活码(破解码、注册码),亲测可用,持续更新
- ja-netfilter到底需不需要mymap,2021.3.2版本激活失效?
- 如何激活idea2022.1及以上版本中的插件(亲测可用)
- 【史上最全】IntelliJ IDEA最新2022.1版本安装和激活视频教学(含插件)
- IntelliJ IDEA 2022.2 版本最新2099年永久激活方法,亲测可用,也可以开启新UI了。
站点信息
- 网站程序:spring + freemarker
- 主题模板:《今夕何夕》
- 文章统计:篇文章
- 标签管理:标签云
- 微信公众号:扫描二维码,关注我们