Swing 边框(二)

7.2 Examining the Predefined Borders

现在我们已经描述了边框基础,现在我们来了解一下每一个预定义的特定边框,在某种程度上以复杂性的顺序进行描述。

7.2.1 EmptyBorder Class

由逻辑上来说,空边框就是在其内部不进行任何绘制的边框。当我们在使用一个通常的AWT容器并且需要覆盖insets()或是getInsets()方法时我们可以使用EmptyBorder。他可以使得我们保留组件周围的额外空间从而略微向外一点扩展屏幕组件或是修改居中或是调整某些方面。图7-3显示了一个空边框以及一个非空边框。

swing_7_3

EmptyBorder有两个构造函数以及两个BorderFactory的工厂方法:

public static Border createEmptyBorder()
Border emptyBorder = BorderFactory.createEmptyBorder();
public static Border createEmptyBorder(int top, int left, int bottom, int right)
Border emptyBorder = BorderFactory.createEmptyBorder(5, 10, 5, 10);
public EmptyBorder(Insets insets)
Insets insets = new Insets(5, 10, 5, 10);
Border EmptyBorder = new EmptyBorder(insets);
public EmptyBorder(int top, int left, int bottom, int right)
Border EmptyBorder = new EmptyBorder(5, 10, 5, 10);

每一个都允许我们以方法特定的方式来自定义边框的insets。无参数的版本会使用零insets创建一个空的边框;否则,我们可以使用AWT Insets实例或是insets片段来指定insets。在默认情况下,EmptyBorder是透明的。

注意:当我们使用零insets创建一个空边框时,我们应该使用工厂方法来创建边框,而避免直接使用构造函数。这可以使用工厂创建一个共享的空边框。如果我们所希望做的是隐藏边框,而且组件是一个AbstractButton子类,则只需要调用setBorderPainted(false)。

7.2.2 LineBorder Class

LineBorder是围绕组件周围用户义定宽度的单色行边框。他可以具有方角或是圆角。如果我们希望修改不同边的粗细,我们需要使用MatteBorder,我们会在本章稍后进行讨论。图7-4显示了一个青筋LineBorder的示例,在这个例子中两个边框分别为1像素与12像素宽,带圆角以及不带圆角。

创建LineBorder

LineBorder有三个构造函数,两个工厂方法以及两个BorderFactory工厂方法:

public LineBorder(Color color)
Border lineBorder = new LineBorder (Color.RED);
 
public LineBorder(Color color, int thickness)
Border lineBorder = new LineBorder (Color.RED, 5);
 
public LineBorder (Color color, int thickness, boolean roundedCorners)
Border lineBorder = new LineBorder (Color.RED, 5, true);
 
public static Border createBlackLineBorder()
Border blackLine = LineBorder.createBlackLineBorder();
 
public static Border createGrayLineBorder()
Border grayLine = LineBorder.createGrayLineBorder();
 
public static Border createLineBorder(Color color)
Border lineBorder = BorderFactory.createLineBorder(Color.RED);
 
public static Border createLineBorder(Color color, int thickness)
Border lineBorder = BorderFactory.createLineBorder(Color.RED, 5);

注意:LineBorder工厂方法工作如下:如果我们两次创建相同的边框,则会返回相同的LineBorder对象。然而,如同所有的对象对比,我们应总是使用equals()方法来检测对象相同。

每一个方法允许我们自定义边框的颜色与线的粗细。如果没有指定粗细,则默认值为1。LineBorder的两个工厂方法可以用于通常使用的黑色与灰色。因为边框填充整个insets区域,所以LineBorder是不透明的,除非他们是圆角。所以,边框的透明性是圆角设置相反的。

设置LineBorder属性

表7-1列出了由AbstractBorder继承的borderOpaque属性以及LineBorder的特定属性。

LineBorder属性

属性名 数据类型

访问性

borderOpaque Boolean

只读

lineColor Color

只读

roundedCorners boolean

只读

thickness int

只读

7.2.3 BevelBorder Class

BevelBorder以三维外观绘制边框,其可以表现为升起或是降低。当边框升起时,在边框的底部与右边会出现阴影效果。当降低时,阴影的位置会相反。图7-5显示了带有默认与自定义颜色的升起与降低BevelBorder。

Swing_7_5

在组件周围绘制一对一像素宽的线可以产生三维外观的模拟效果。非阴影的边框侧边以被称为highlight颜色进行绘制,而其他两边以shadow颜色进行绘制。highlight颜色与shadow颜色对于BevelBorder的外边与内边使用不同的阴影进行绘制。所以,一个BevelBorder总共使用四种不同的颜色。图7-6显示这四种颜色是如何组合在一起的。

swing_7_6

BevelBorder有三个构造函数以及一个工厂方法,同时还有BorderFactory创建BevelBorder对象的五个工厂方法:

public BevelBorder(int bevelType)
Border bevelBorder = new BevelBorder(BevelBorder.RAISED);
 
public static Border createBevelBorder(int bevelType)
Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.RAISED);
 
public static Border createLoweredBevelBorder()
Border bevelBorder = BorderFactory.createLoweredBevelBorder();
 
public static Border createRaisedBevelBorder()
Border bevelBorder = BorderFactory.createRaisedBevelBorder();
 
public BevelBorder(int bevelType, Color highlight, Color shadow)
Border bevelBorder = new BevelBorder(BevelBorder.RAISED, Color.PINK, Color.RED);
 
public static Border createBevelBorder(int bevelType, Color highlight, Color shadow)
Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.RAISED,
  Color.PINK, Color.RED);
 
public BevelBorder(int bevelType, Color highlightOuter, Color highlightInner,
  Color shadowOuter, Color shadowInner)
Border bevelBorder = new BevelBorder(BevelBorder.RAISED, Color.PINK,
  Color.PINK.brighter(), Color.RED, Color.RED.darker());
 
public static Border createBevelBorder(int bevelType, Color highlightOuter,
  Color highlightInner, Color shadowOuter, Color shadowInner)
Border bevelBorder = BorderFactory.createBevelBorder(BevelBorder.RAISED,
  Color.PINK, Color.PINK.brighter(), Color.RED, Color.RED.darker());

每一个方法都可以使得我们自定义斜面类型以及边框中明亮与阴影的颜色。斜面类型是通过下面两个值来指定的:BevelBorder.RAISED或是BevelBorder.LOWERED。如果没有指定明亮与阴影颜色,则会通过检测边框组件的背景颜色来生成合适的颜色。如果我们指定了相应的颜色,记住明亮颜色应亮一些,通常可以通过调用theColor.brighter()方法来实现。在默认情况下,BevelBorder是不透明的。

7.2.4 SoftBevelBorder Class

SoftBevelBorder是BevelBorder的近亲。这个组件会包围四角,所以他们的边并不尖利,而他使用下边与右边的相应外边颜色只绘制一条线。如图7-7所示,升起与落下的SoftBevelBorder与BevelBorder基本相同。

swing_7_7

SoftBevelBorder有三个构造函数:

public SoftBevelBorder(int bevelType)
Border softBevelBorder = new SoftBevelBorder(SoftBevelBorder.RAISED);
public SoftBevelBorder(int bevelType, Color highlight, Color shadow)
Border softBevelBorder = new SoftBevelBorder(SoftBevelBorder.RAISED, Color.RED,
  Color.PINK);
public SoftBevelBorder(int bevelType, Color highlightOuter, Color highlightInner,
  Color shadowOuter, Color shadowInner)
Border softBevelBorder = new SoftBevelBorder(SoftBevelBorder.RAISED, Color.RED,
  Color.RED.darker(), Color.PINK, Color.PINK.brighter());

每一个方法都允许我们指定斜面类型以及边框内的明亮与阴影颜色。斜面类型是通过下面的值来指定的:SoftBevelBorder.RAISED或是SoftBevelBorder.LOWEERED。与BevelBorder类似,默认的颜色是由背景色得来的。一个SoftBevelBorder并不完全适应所指定的insets区域,所以SoftBevelBorder通常创建为透明的。

并没有静态的BorderFactory方法来创建这种边框。

7.2.5 EtchedBorder Class

EtchedBorder是BevelBorder的一种特殊情况,但是并不其子类。当BevelBorder的外层明亮颜色与内层阴影颜色相同,并且外层阴影颜色与内层明亮颜色相同,则我们就得到了EtchedBorder。图7-8显示了一个升起来落下的EtchedBorder的样子。

swing_7_8

EtchedBorder有四个构造函数,同时有四个用于创建EtchedBorder对象的BorderFactory工厂方法:

public EtchedBorder()
Border etchedBorder = new EtchedBorder();
 
public EtchedBorder(int etchType)
Border etchedBorder = new EtchedBorder(EtchedBorder.RAISED);
 
public EtchedBorder(Color highlight, Color shadow)
Border etchedBorder = new EtchedBorder(Color.RED, Color.PINK);
 
public EtchedBorder(int etchType, Color highlight, Color shadow)
Border etchedBorder = new EtchedBorder(EtchedBorder.RAISED, Color.RED,
  Color.PINK);
 
public static Border createEtchedBorder()
Border etchedBorder = BorderFactory.createEtchedBorder();
 
public static Border createEtchedBorder(Color highlight, Color shadow)
Border etchedBorder = BorderFactory.createEtchedBorder(Color.RED, Color.PINK);
 
public static Border createEtchedBorder(EtchedBorder.RAISED)
Border etchedBorder = BorderFactory.createEtchedBorder(Color.RED, Color.PINK);
 
public static Border createEtchedBorder(int type, Color highlight, Color shadow)
Border etchedBorder = BorderFactory.createEtchedBorder(EtchedBorder.RAISED,
  Color.RED, Color.PINK);

每一个方法都允许我们指定Etch类型以及边框中的明亮与阴影颜色。如果没有指定Etch类型,则边框是落下的。与BevelBorder与SoftBevelBorder类似,我们可以通过两个常量来指定Etch类型:EtchedBorder.RAISED或是EtchedBorder.LOWERED。如果没有指定颜色,则由传递给paintBorder()的组件的背景颜色得到合适的颜色。默认情况下,所有的EtchedBorder对象是不透明的。

7.2.6 MatteBorder Class

MatteBorder是最通用的边框之一。他有两种形式。第一种形式如图7-9所示,在图中显示了一个MatteBorder,以与LineBorder类似的方式使用特定的颜色填充边框,但是在每一边有不同的粗细(有时一个普通的LineBorder并不能处理)。

Swing_7_9

第二种形式在边框区域内使用一个Icon进行连接。如果我们由一个Image对象创建,则这个Icon可以是一个ImageIcon,或者是我们通过实现Icon接口自己创建。图7-10显示了两种实现。

Swing_7_10

有七个构造函数以及两个BorderFactory工厂方法可以用来创建MatteBorder对象:

public MatteBorder(int top, int left, int bottom, int right, Color color)
Border matteBorder = new MatteBorder(5, 10, 5, 10, Color.GREEN);
 
public MatteBorder(int top, int left, int bottom, int right, Icon icon)
Icon diamondIcon = new DiamondIcon(Color.RED);
Border matteBorder = new MatteBorder(5, 10, 5, 10, diamondIcon);
 
public MatteBorder(Icon icon)
Icon diamondIcon = new DiamondIcon(Color.RED);
Border matteBorder = new MatteBorder(diamondIcon);
 
public MatteBorder(Insets insets, Color color)
Insets insets = new Insets(5, 10, 5, 10);
Border matteBorder = new MatteBorder(insets,  Color.RED);
 
public MatteBorder(Insets insets, Icon icon)
Insets insets = new Insets(5, 10, 5, 10);
Icon diamondIcon = new DiamondIcon(Color.RED);
Border matteBorder = new MatteBorder(insets, diamondIcon);
 
public static MatteBorder createMatteBorder(int top, int left, int bottom,
  int right, Color color)
Border matteBorder = BorderFactory.createMatteBorder(5, 10, 5, 10, Color.GREEN);
 
public static MatteBorder createMatteBorder(int top, int left, int bottom,
  int right, Icon icon)
Icon diamondIcon = new DiamondIcon(Color.RED);
Border matteBorder = BorderFactory.createMatteBorder(5, 10, 5, 10, diamondIcon);

每一个方法都允许我们自定义在边框区域内映射的内容。当连接Icon时,如果我们没有指定边框insets的尺寸,则使用实现的图标维度。

7.2.7 CompoundBorder Class

在EmptyBorder之后,CompundBorder也许是使用最简单的预定义边框之一了。他使用两个已存在的边框,使用组合设计模式将其组合在一个边框中。一个Swing组件只有一个与其相关联的边框,所以,CompundBorder允许我们在将边框关联到一个组件之前组合边框。图7-11显示了应用CompundBorder的两个例子。左边的边框是斜面线边框。右边是一个六个线边框,多个边框组合在一起。

Swing_7_11

创建CompundBorder

ComoundBorder有两个构造函数,以及用于创建CompondBorder对象的两个BorderFactory工厂方法(在这里无参数构造函数以及工厂方法是完全没用的,因为并没有setter方法用于稍后修改组合边框,所以在这里并没有显示示例源码):

public CompoundBorder()
public static CompoundBorder createCompoundBorder()
public CompoundBorder(Border outside, Border inside)
Border compoundBorder = new CompoundBorder(lineBorder, matteBorder);
public static CompoundBorder createCompoundBorder(Border outside, Border inside)
Border compoundBorder = BorderFactory.createCompoundBorder(lineBorder,
  matteBorder);

组合边框的透明性依赖于所包含的边框的透明性。如果所包含的两个边框都是不透明的,那么组合边框也是不透明的。否则,组合边框就是透明的。

配置属性

除了由AbstractBorder继承的borderOpaque属性以外,表7-2列出了CompoundBorder添加的两个只读属性。

CompoundBorder属性

属性名 数据类型

访问性

borderOpaque boolean

只读

insideBorder Border

只读

outsideBorder Border

只读

7.2.8 TitledBorder Class

TitledBorder也许是最有趣,用起来最复杂的边框。TitledBorder允许我们在组件周围放置一个文本字符串。除了可以包围单一的组件,我们在还可以在一个组件组周围放置一个TitledBorder,例如JRadioButton对象,只要他们位于一个容器内,例如JPanel。TitledBorder的使用比较困难,但是有许多方法可以简化其使用。图7-12显示了一个简单的TitledBorder以及一个略为复杂一些的TitledBorder。

Swing_7_12

创建TitledBorder

有六个构造函数以及六个BorderFactory工厂方法可以用来创建TitledBorder对象。每一个方法都允许我们自定义文本,位置,以及在一个特定的边框内的标题外观。当没有特别指定时,当前的观感控制边框,标题颜色,以及标题字体。默认的标题位置位于左上角,而默认的标题是空字符串。标题边框至少总是部分透明的,因为标题下部的区域是可以看过的。所以,isBorderOpaque()报告false。

如果我们查看下面的方法,则这些方法会很容易理解。首先显示的是构造方法;然后显示的是等同的BorderFactory方法。

public TitledBorder(Border border)
Border titledBorder = new TitledBorder(lineBorder);
 
public static TitledBorder createTitledBorder(Border border)
Border titledBorder = BorderFactory.createTitledBorder(lineBorder);
 
public TitledBorder(String title)
Border titledBorder = new TitledBorder("Hello");
 
public static TitledBorder createTitledBorder(String title)
Border titledBorder = BorderFactory.createTitledBorder("Hello");
 
public TitledBorder(Border border, String title)
Border titledBorder = new TitledBorder(lineBorder, "Hello");
 
public static TitledBorder createTitledBorder(Border border, String title)
Border titledBorder = BorderFactory.createTitledBorder(lineBorder, "Hello");
 
public TitledBorder(Border border, String title, int justification, int position)
Border titledBorder = new TitledBorder(lineBorder, "Hello", TitledBorder.LEFT,
  TitledBorder.BELOW_BOTTOM);
 
public static TitledBorder createTitledBorder(Border border, String title,
  int justification, int position)
Border titledBorder = BorderFactory.createTitledBorder(lineBorder, "Hello",
  TitledBorder.LEFT, TitledBorder.BELOW_BOTTOM);
 
public TitledBorder(Border border, String title, int justification, int position,
  Font font)
Font font = new Font("Serif", Font.ITALIC, 12);
Border titledBorder = new TitledBorder(lineBorder, "Hello", TitledBorder.LEFT,
  TitledBorder.BELOW_BOTTOM, font);
 
public static TitledBorder createTitledBorder(Border border, String title,
  int justification, int position, Font font)
Font font = new Font("Serif", Font.ITALIC, 12);
Border titledBorder = BorderFactory.createTitledBorder(lineBorder, "Hello",
  TitledBorder.LEFT, TitledBorder.BELOW_BOTTOM, font);
 
public TitledBorder(Border border, String title, int justification, int position,
  Font font, Color color)
Font font = new Font("Serif", Font.ITALIC, 12);
Border titledBorder = new TitledBorder(lineBorder, "Hello", TitledBorder.LEFT,
  TitledBorder.BELOW_BOTTOM, font, Color.RED);
 
public static TitledBorder createTitledBorder(Border border, String title,
  int justification, int position, Font font, Color color)
Font font = new Font("Serif", Font.ITALIC, 12);
Border titledBorder = BorderFactory.createTitledBorder(lineBorder, "Hello",
  TitledBorder.LEFT, TitledBorder.BELOW_BOTTOM, font, Color.RED);

配置属性

与其他的预定义边框不同,标题边框有六个setter方法在边框创建之后修改其属性。如表7-3所示,我们可以修改一个标题的底部边框,标题,绘制颜色,字体,文本适应,以及文本位置。

TitledBorder属性

属性名 数据类型

访问性

border Border

读写

borderOpaque boolean

只读

title String

读写

titleColor Color

读写

titleFont Font

读写

titleJustification int

读写

titlePosition int

读写

TitledBorder中的标题字符串的文本适应是通过四个类常量来指定的:

  • CENTER:标题放在中间。
  • DEFAULT_JUSTIFICATION:使用默认设置来放置文本。该值等同于LEFT。
  • LEFT:将标题放在左边。
  • RIGHT:将标题放在右边。

图7-13显示了具有不同文本适应的相同的TitledBorder。

Swing_7_13

我们可以将标题字符串放在由下面的七个类常量指定的六个不同的位置:

  • ABOVE_BOTTOM:将标题放在底线上部。
  • ABOVE_TOP:将标题放在顶线上部。
  • BELOW_BOTTOM:将标题放在底线下部。
  • BELOW_TOP:将标题放在顶线下部。
  • BOTTOM:将标题放在底线上。
  • DEFAULT_POSITION:使用默认设置放置文本。这个值等同于TOP。
  • TOP:将标题放在顶线上。

图7-14显示了TitledBorder的标题可以放置的六个不同位置。

Swing_7_14

因为TitledBorder包含另一个Border,我们可以组合多个边框来在一个边框中放置多个标题。例如,图7-15显示了在边框的顶部与底部显示标题。

Swing_7_15

用来生成图7-15的程序源码显示在列表7-2中。

package swingstudy.ch07;
 
import java.awt.BorderLayout;
import java.awt.EventQueue;
 
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.border.TitledBorder;
 
public class DoubleTitle {
 
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
 
		Runnable runner = new Runnable() {
			public void run() {
				JFrame frame = new JFrame("Double Title");
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
				TitledBorder topBorder = BorderFactory.createTitledBorder("Top");
				topBorder.setTitlePosition(TitledBorder.TOP);
 
				TitledBorder doubleBorder = new TitledBorder(topBorder, "Bottom", TitledBorder.RIGHT, TitledBorder.BOTTOM);
 
				JButton doubleButton = new JButton();
				doubleButton.setBorder(doubleBorder);
 
				frame.add(doubleButton, BorderLayout.CENTER);
				frame.setSize(300, 100);
				frame.setVisible(true);
			}
		};
		EventQueue.invokeLater(runner);
	}
 
}

自定义TitledBorder观感

表7-4显示了TitledBorder的UIResource相关属性的集合。他有三个不同的属性。

TitledBorder UIResource元素

属性字符串

对象类型

TitledBorder.font

font

TitledBorder.titleColor

Color

TitledBorder.border

Border

7.3 Creating Your Own Borders

当我们想要创建我们自己的独特的边框时,我们或者可以直接实现Border接口创建新类,或者是我们可以扩展AbstractBorder类。正如前面所提到的,扩展AbstractBorder是更好的方法,因为其中进行优化,从而特定的Swing类可以千年虫AbstractBorder的特定方法。例如,如果一个边框是一个AbstractBorder,当获取边框的Insets时,JComponent可以重用Insets对象。所以,当获取insets时只有少量的对象需要创建与销毁。

除了考虑继承AbstractBorder与自己实现Border接口以外,我们需要考虑我们是否需要一个静态边框。如果我们将一个边框关联到一个按钮,我们希望这个按钮能够进行信息选择。我们必须检测传递给paintBorder()方法的组件,并进行相应的响应。另外,我们应该在组件不可以选择时绘制一个禁止的边框。尽管setEnabled(false)可以禁止组件的选择,如果组件有一个与其相关联的边框,边框仍然进行绘制,尽管他已经被禁止。图7-6实际显示了一个考虑了传递给边框的paintBorder()方法的组件选项的边框。

Swing_7_16

列表7-3显示了自定义边框与示例程序的源码。

package swingstudy.ch07;
 
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.EventQueue;
import java.awt.Graphics;
import java.awt.Insets;
 
import javax.swing.AbstractButton;
import javax.swing.ButtonModel;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.border.AbstractBorder;
import javax.swing.border.Border;
 
public class RedGreenBorder extends AbstractBorder {
 
	public boolean isBorderOpaque() {
		return true;
	}
 
	public Insets getBorderInsets(Component c) {
		return new Insets(3, 3, 3, 3);
	}
 
	public void paintBorder(Component c, Graphics g, int x, int y, int width, int height) {
		Insets insets = getBorderInsets(c);
		Color horizontalColor;
		Color verticalColor;
		if(c.isEnabled()) {
			boolean pressed = false;
			if(c instanceof AbstractButton) {
				ButtonModel model = ((AbstractButton)c).getModel();
				pressed = model.isPressed();
			}
			if(pressed) {
				horizontalColor = Color.RED;
				verticalColor = Color.GREEN;
			}
			else {
				horizontalColor = Color.GREEN;
				verticalColor = Color.RED;
			}
		}
		else {
			horizontalColor = Color.LIGHT_GRAY;
			verticalColor = Color.LIGHT_GRAY;
		}
		g.setColor(horizontalColor);
 
		g.translate(x, y);
 
		// Top
		g.fillRect(0, 0, width, insets.top);
 
		// Bottom
		g.fillRect(0, height-insets.bottom, width, insets.bottom);
 
		g.setColor(verticalColor);
 
		// Left
		g.fillRect(0, insets.top, insets.left, height-insets.top-insets.bottom);
 
		// Right
		g.fillRect(width-insets.right, insets.top, insets.right, height-insets.top-insets.bottom);
 
		g.translate(-x, -y);
	}
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub
 
		Runnable runner = new Runnable() {
			public void run() {
				JFrame frame = new JFrame("My Border");
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
 
				Border border = new RedGreenBorder();
 
				JButton helloButton = new JButton("Hello");
				helloButton.setBorder(border);
 
				JButton braveButton = new JButton("Brave New");
				braveButton.setBorder(border);
				braveButton.setEnabled(false);
 
				JButton wordButton =  new JButton("World");
				wordButton.setBorder(border);
 
				frame.add(helloButton, BorderLayout.NORTH);
				frame.add(braveButton, BorderLayout.CENTER);
				frame.add(wordButton, BorderLayout.SOUTH);
 
				frame.setSize(300, 100);
				frame.setVisible(true);
			}
		};
 
		EventQueue.invokeLater(runner);
	}
 
}

7.4 Summary

在本章中,我们了解了Border接口及其预定义实现的使用。同时我们了解了如何使用由BorderFactory类所提供的工厂设计模式来创建预定义的边框。最后,我们了解了如何定义我们自己的边框以及为什么继承AbstractBorder是有益的。

在第8章中,我们将会了解底层的组件,并且检测Swing中可用的类似于窗口的容器。

原文地址:https://www.cnblogs.com/dyllove98/p/2461905.html