自定义 JTabbedPane 中的选项卡渲染顺序

Tabs Rendering Order in Custom JTabbedPane

大家好,祝我 2017 年第一个 post 新年快乐! :)

问题解释

我目前正在编写自定义代码 JTabbedPane 并且一切正常,但在呈现选项卡时我遇到了一个意想不到的(对我来说)设计问题。

问题是所有未选中的选项卡都是从左到右呈现的,并且由于我使用 GeneralPath class 自定义的形状超出了默认选项卡边界,因此呈现的每个选项卡都与选项卡的一部分重叠在它的左边。您可以在下图中查看:

如您所见,选定的选项卡与右侧的任何选项卡重叠,但未选定的选项卡,如命名的 "CustomPanel2" 会在下一个选项卡之前呈现,依此类推。

问题

我看到一个 post 谈论重写 BasicTabbedPaneUI class 的 paintTab 方法,这是我正在使用的方法,但我不知道该怎么做它所以我希望你向我展示渲染选项卡的正确方法,以便在 Google Chrome 中获得类似选项卡的内容,如下所示:

提前谢谢你,祝你有美好的一天! ;)

PD:我认为没有相关的代码可以添加。需要的话找我要。

尝试将未选中的选项卡塑造成五边形怎么样?

注意:此示例在设置 JTabbedPane#setTabLayoutPolicy (JTabbedPane.WRAP_TAB_LAYOUT)JTabbedPane#setTabPlacement (JTabbedPane.BOTTOM) 时不测试:

import java.awt.*;
import java.awt.geom.*;
import javax.swing.*;
import javax.swing.plaf.basic.*;

public class TabsOverlapTest {
  private JComponent makeUI() {
    Color selectedTabColor = UIManager.getColor("TabbedPane.selected");
    Color tabBackgroundColor = Color.LIGHT_GRAY;
    Color tabBorderColor = Color.GRAY;
    UIManager.put("TabbedPane.highlight", tabBorderColor);

    JTabbedPane tabs = new JTabbedPane();
    tabs.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
    tabs.setUI(new BasicTabbedPaneUI() {
      @Override protected void paintTabBorder(
          Graphics g, int tabPlacement, int tabIndex,
          int x, int y, int w, int h, boolean isSelected) {
      }
      @Override protected void paintFocusIndicator(
          Graphics g, int tabPlacement, Rectangle[] rects, int tabIndex,
          Rectangle iconRect, Rectangle textRect, boolean isSelected) {
      }
      @Override protected void paintContentBorderTopEdge(
          Graphics g, int tabPlacement, int selectedIndex,
          int x, int y, int w, int h) {
        super.paintContentBorderTopEdge(g, tabPlacement, selectedIndex, x, y, w, h);
        Rectangle selRect = getTabBounds(selectedIndex, calcRect);
        Graphics2D g2 = (Graphics2D) g.create();
        g2.setColor(selectedTabColor);
        g2.drawLine(selRect.x - 2, y, selRect.x + selRect.width + 2, y);
        g2.dispose();
      }
      @Override protected void paintTabBackground(
          Graphics g, int tabPlacement, int tabIndex, int x, int y, int w, int h,
          boolean isSelected) {

        Graphics2D g2 = (Graphics2D) g.create();
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                            RenderingHints.VALUE_ANTIALIAS_ON);
        int a = isSelected ? 0 : 1;

        GeneralPath shape = new GeneralPath();
        shape.moveTo(x - 3, y + h);
        shape.lineTo(x + 3, y + a);
        shape.lineTo(x + w - 3, y + a);
        shape.lineTo(x + w + 3, y + h);
        shape.closePath();
        g2.setColor(isSelected ? selectedTabColor : tabBackgroundColor);
        g2.fill(shape);

        GeneralPath border = new GeneralPath();
        if (isSelected || tabIndex == 0) {
          border.moveTo(x - 3, y + h - 1);
        } else {
          border.moveTo(x + 3, y + h - 1);
          border.lineTo(x, (y + h - 1) / 2);
        }
        border.lineTo(x + 3, y + a);
        border.lineTo(x + w - 3, y + a);
        border.lineTo(x + w + 3, y + h - 1);

        g2.setColor(tabBorderColor);
        g2.draw(border);

        g2.dispose();
      }
    });
    tabs.addTab("JTextArea",  new JScrollPane(new JTextArea()));
    tabs.addTab("JTree",      new JScrollPane(new JTree()));
    tabs.addTab("JButton",    new JButton("button"));
    tabs.addTab("JSplitPane", new JSplitPane());
    return tabs;
  }
  public static void main(String... args) {
    EventQueue.invokeLater(() -> {
      JFrame f = new JFrame();
      f.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
      f.getContentPane().add(new TabsOverlapTest().makeUI());
      f.setSize(320, 240);
      f.setLocationRelativeTo(null);
      f.setVisible(true);
    });
  }
}

好吧,我终于通过重写 BasicTabbedPaneUI class 中的 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();

    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);
            }
        }
    }

    if (selectedIndex >= 0 && rects[selectedIndex].intersects(clipRect)) {
        paintTab(g, tabPlacement, rects, selectedIndex, iconRect, textRect);
    }

}

在第二个 for 语句中,您看到条件:

(int j = start; j <= end; j++)

为了切换选项卡渲染顺序,您只需将该条件更改为:

(int j = end; j >= start; j--)