读书笔记(Java极富客户端效果开发之二)

12.  明确的告诉java 2d你将要完成的绘制,而不是使用一个更为通用的方式,这样能够带来更好的性能。

 1     //画线的bad way
2 Shape line = new Line2D.Double(LINE_X, BAD_Y, LINE_X + 50, BAD_Y + 50);
3 g2d.draw(line);
4
5 //画线的good way
6 g.drawLine(LINE_X, GOOD_Y, LINE_X + 50, GOOD_Y + 50);
7
8 //画rectangle的bad way
9 Shape rect = new Rectangle(RECT_X, BAD_Y, 50, 50);
10 g2d.fill(rect);
11
12 //画rectangle的good way
13 g.fillRect(RECT_X, GOOD_Y, 50, 50);

13.  图像合成,其中最为有用的三个规则分别是clear、SrcOver(swing缺省)和SrcIn。
       Clear:是擦掉一个图像的背景以便使他变得完全透明的一个容易的方式,可以将其理解为Photoshop中的橡皮擦,通过Clear可以清除任意形状的区域。

 1     public void exampleForClear() {
2 BufferedImage image = new BufferedImage(200,200,BufferedImage.TYPE_INT_ARGB);
3 Graphics2D g2 = image.createGraphics();
4 //draw something here.
5 //...
6 //Erase the content of the image.
7 g2.setComposite(AlphaComposite.Clear);
8 //The color,the Paint, etc. do not matter
9 g2.fillRect(0,0,image.getWidth(),image.getHeight());
10 }

       SrcOver: 其运算公式为Ar = As + Ad * (1 - As), Cr = Cs + Cd * (1 - As), 其中Ar为结果Alpha,As表示源图像的Alpha,As为目的图像的Alpha,Cr表示(RGB)中每个通道的结果值,Cs为源图像中(RGB)单个通道的值,Cd为目的图像的单个通道值。
       一般的用法为在目的图像上绘制半透明的源图像。
       SrcIn:位于目的地内部的那部分源代替目的地,位于目的地之外的那部分源丢弃掉。

 1     protected void paintComponent(Graphics g) {
2 BufferedImage temp = new BufferedImage(getWidth(), getHeight(),
3 BufferedImage.TYPE_INT_ARGB);
4 Graphics2D g2 = temp.createGraphics();
5
6 if (shadow.isSelected()) {
7 int x = (getWidth() - image.getWidth()) / 2;
8 int y = (getHeight() - image.getHeight()) / 2;
9 g2.drawImage(image, x + 4, y + 10, null);
10
11 Composite oldComposite = g2.getComposite();
12 g2.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_IN, 0.75f));
13 g2.setColor(Color.BLACK);
14 g2.fillRect(0, 0, getWidth(), getHeight());
15 g2.setComposite(oldComposite);
16 g2.drawImage(image, x, y, null);
17 } else {
18 int x = (getWidth() - image.getWidth()) / 2;
19 int y = (getHeight() - image.getHeight()) / 2;
20 g2.drawImage(image, x, y, null);
21
22 Composite oldComposite = g2.getComposite();
23 g2.setComposite(AlphaComposite.SrcIn);
24 x = (getWidth() - landscape.getWidth()) / 2;
25 y = (getHeight() - landscape.getHeight()) / 2;
26 g2.drawImage(landscape, x, y, null);
27 g2.setComposite(oldComposite);
28 }
29
30 g2.dispose();
31 g.drawImage(temp, 0, 0, null);
32 }

14.  利用渐变完成的反射效果,主要分为3个步骤完成,见下例:

 1     private BufferedImage createReflection(BufferedImage image) {
2 int height = image.getHeight();
3
4 BufferedImage result = new BufferedImage(image.getWidth(), height * 2,
5 BufferedImage.TYPE_INT_ARGB);
6 Graphics2D g2 = result.createGraphics();
7 //1. 想渲染正常物体一样渲染它。
8 g2.drawImage(image, 0, 0, null);
9
10 //2. 渲染这个物体上下颠倒的一个副本
11 g2.scale(1.0, -1.0);
12 g2.drawImage(image, 0, -height - height, null);
13 g2.scale(1.0, -1.0);
14
15 // Move to the origin of the clone
16 g2.translate(0, height);
17
18 //3. 模糊这个副本的一部分以使它淡出,随着它远离最初的物体。
19 GradientPaint mask;
20 //目的颜色RGB无关重要,alpha值必须为0。
21 mask = new GradientPaint(0, 0, new Color(1.0f, 1.0f, 1.0f, 0.5f),
22 0, height / 2, new Color(1.0f, 1.0f, 1.0f, 0.0f));
23 Paint oldPaint = g2.getPaint();
24 g2.setPaint(mask);
25 // Sets the alpha composite
26 g2.setComposite(AlphaComposite.DstIn);
27 //尽量覆盖全部颠倒图像,以避免因覆盖不全而造成的伪影。
28 g2.fillRect(0, 0, image.getWidth(), height);
29 g2.dispose();
30 return result;
31 }

15.  线性渐变LinearGradientPaint(float startX,float startY,float endX,float endY,float[] fractions,Color[] colors),这里包含两个数组参数,其中第一个float类型的数组包含渐变中使用的每个颜色的位置。每一对位置/颜色被称为一个停顿,见下例:

 1     protected void paintComponent(Graphics g) {
2 Graphics2D g2 = (Graphics2D) g;
3 Paint oldPaint = g2.getPaint();
4 LinearGradientPaint p;
5
6 p = new LinearGradientPaint(0.0f, 0.0f, 0.0f, 20.0f,
7 new float[] { 0.0f, 0.5f, 0.501f, 1.0f },
8 new Color[] { new Color(0x63a5f7),
9 new Color(0x3799f4),
10 new Color(0x2d7eeb),
11 new Color(0x30a5f9) });
12 g2.setPaint(p);
13 g2.fillRect(0, 0, getWidth(), 21);
14 g2.setPaint(oldPaint);
15 super.paintComponent(g);
16 }

16.  优化渐变的3个技巧:

      1) 缓存这个渐变:该解决方案是把这个渐变变成一个图像并仅仅绘制那个图像,但是缺点是需要消耗更多的内存。

 1     protected void paintComponent(Graphics g) {
2 if (gradientImage == null
3 || gradientImage.getWidth() != getWidth()
4 || gradientImage.getHeight() != getHeight()) {
5 gradientImage = new BufferedImage(getWidth(),getHeigth(),BufferedImage.TYPE_INT_RGB);
6 Graphics2D g2d = (Graphics2D)gradientImage.getGraphics();
7 g2d.setPaint(backgroundGradient);
8 g2d.fillRect(0,0,getWidth(),getHeight());
9 g2d.dispose()
10 }
11 g.drawImage(gradientImage,0,0,null);
12 }

      2) 更巧妙的缓存:当绘制一个垂直或者水平渐变时,每一列或者每一行都是相同的,因此可以只是保留一列或者一行的数据,然在需要覆盖渐变时在拉伸该列或者该行。

 1     protected void paintComponent(Graphics g) {
2 if (gradientImage == null || gradientImage.getHeight() != getHeight()) {
3 gradientImage = MineCompatible.createCompatibleImage(1,getHeight());
4 Graphics2D g2d = (Graphics2D)gradientImage.getGraphics();
5 g2d.setPaint(backgroundGradient);
6 g2d.fillRect(0,0,1,getHeight());
7 g2d.dispose();
8 }
9 g.drawImage(gradientImage,0,0,getWidth(),getHeigth(),null);
10 }

      3) 使用循环渐变的优化:如果渐变只是被覆盖组件高度的一半时,如以下代码:

1     protected void paintComponent(Graphics g) {
2 Graphics2D g2d = (Graphics2D)g.createGraphics();
3 g2d.setPaint(new GradientPaint(0.0f,0.0f,Color.WHITE,0.0f,getHeigth()/2.0f,Color.DARK_GRAY);
4 g2d.fillRect(0,0,getWidth(),getHeight());
5 }

      该代码将会从组件的(0,0)到(0,height/2)绘制渐变,同时利用这个渐变的最后颜色填充剩下的像素,为了做到这一点,java 2d将不断的检查是否当前的像素位于这个渐变区域的外面,因此对于成千上万的像素来说,将会花费很多时间。如果使用循环渐变的方式,java 2d内部在渲染的时候将会不进行该判断,从而大大提高了整体的效率,见如下代码:

1     //循环GradientPaint
2 new GradientPaint(new Point(0,0),Color.WHITE,new Point(0,getHeight())
,Color.DARK_GRAY,
true/*该标志表示循环*/);
3 //循环LinearGradientPaint
4 new LinearGradientPaint(new Point(0,0),new Point(0,getHeigth())
,
new float[] {0.0f,1.0f},new Color[] {Color.WHITE,Color.DARK_GRAY}
,MultipleGradientPaint.CycleMethod.REPEAT);

17. 图像处理:
     1) AffineTransformOp

1     public BufferedImage makeeAffineTransformOp(BufferedImage srcImage) {
2 //高度和宽度均为源图像的50%。
3 AffineTransform transform = AffineTransform.getScaleInstance(0.5, 0.5);
4 AffineTransformOp op = new AffineTransformOp(transform,AffineTransformOp.TYPE_BILINEAR);
5 return op.filter(srcImage,null);
6 }

     2) RescaleOp

1     private BufferedImage makeRescaleOp(BufferedImage srcImage) {
2 BufferedImage dstImage = null;
3 float[] factors = new float[] { 1.4f, 1.4f, 1.4f };
4 float[] offsets = new float[] { 0.0f, 0.0f, 30.0f };
5 //RGB每个颜色通道的亮度增加40%,B通道增加30/256=12%的颜色分量。
6 RescaleOp op = new RescaleOp(factors, offsets, null);
7 return op.filter(srcImage,null);
8 }

18. 玻璃窗格的基本绘制技巧:
     1) 给当前JFrame安装玻璃窗格,安装后该玻璃窗格的缺省显示方式是隐藏显示,即setVisible(false),如果之前JFrame已经使用了玻璃窗格,本次操作只是替换一个新的对象,那么该窗格的visible属性将和原有窗格的visible属性保持一致。

1     public ApplicationFrame() { //ApplicationFrame为应用程序的主窗体,继承自JFrame
2 initComponents();
3 //安装玻璃窗格,glassPane是JComponent的子类。
4 setGlassPane(glassPane = new ProgressGlassPane());
5 }

     2) 实现玻璃窗格的paintComponent方法

 1     protected void paintComponent(Graphics g) {
2 // enables anti-aliasing
3 Graphics2D g2 = (Graphics2D) g;
4 g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
5 RenderingHints.VALUE_ANTIALIAS_ON);
6
7 // gets the current clipping area
8 Rectangle clip = g.getClipBounds();
9
10 // sets a 65% translucent composite
11 AlphaComposite alpha = AlphaComposite.SrcOver.derive(0.65f);
12 Composite composite = g2.getComposite();
13 g2.setComposite(alpha);
14
15 // fills the background
16 g2.setColor(getBackground());
17 g2.fillRect(clip.x, clip.y, clip.width, clip.height);
18 // centers the progress bar on screen
19 FontMetrics metrics = g.getFontMetrics();
20 int x = (getWidth() - BAR_WIDTH) / 2;
21 int y = (getHeight() - BAR_HEIGHT - metrics.getDescent()) / 2;
22
23 // draws the text
24 g2.setColor(TEXT_COLOR);
25 g2.drawString(message, x, y);
26 // goes to the position of the progress bar
27 y += metrics.getDescent();
28 // computes the size of the progress indicator
29 int w = (int) (BAR_WIDTH * ((float) progress / 100.0f));
30 int h = BAR_HEIGHT;
31
32 // draws the content of the progress bar
33 Paint paint = g2.getPaint();
34
35 // bar's background
36 Paint gradient = new GradientPaint(x, y, GRADIENT_COLOR1, x, y + h
, GRADIENT_COLOR2);
37 g2.setPaint(gradient);
38 g2.fillRect(x, y, BAR_WIDTH, BAR_HEIGHT);
39
40 // actual progress
41 gradient = new LinearGradientPaint(x, y, x, y + h,GRADIENT_FRACTIONS
, GRADIENT_COLORS);
42 g2.setPaint(gradient);
43 g2.fillRect(x, y, w, h);
44 g2.setPaint(paint);
45
46 // draws the progress bar border
47 g2.drawRect(x, y, BAR_WIDTH, BAR_HEIGHT);
48 g2.setComposite(composite);
49 }

       3) 主窗体中的工作线程需要调用的方法,以便更新进度条的显示状态

 1     public void setProgress(int progress) {
2 int oldProgress = this.progress;
3 this.progress = progress;
4
5 // computes the damaged area
6 FontMetrics metrics = getGraphics().getFontMetrics(getFont());
7 int w = (int) (BAR_WIDTH * ((float) oldProgress / 100.0f));
8 int x = w + (getWidth() - BAR_WIDTH) / 2;
9 int y = (getHeight() - BAR_HEIGHT) / 2;
10 y += metrics.getDescent() / 2;
11
12 w = (int) (BAR_WIDTH * ((float) progress / 100.0f)) - w;
13 int h = BAR_HEIGHT;
14 //The reason why uses the following repaint(x, y, w, h) not repaint() is to
15 //avoid repainting all the area to improve the performance.
16 repaint(x, y, w, h);
17 }

19.  玻璃窗格中屏蔽输入事件,上例中绘制的玻璃窗格只是完成了基本的显示效果,用户仍然可以操作玻璃窗格覆盖下的控件,这样会给用户带来非常迷惑的感觉,因此需要屏蔽玻璃窗格覆盖下的控件获取来自鼠标和键盘的事件。
      1) 为玻璃窗格控件自身添加空的鼠标和键盘的监听器

1     public ProgressGlassPane() {
2 // blocks all user input
3 addMouseListener(new MouseAdapter() { });
4 addMouseMotionListener(new MouseMotionAdapter() { });
5 addKeyListener(new KeyAdapter() { });
6 }

      2) 以上操作只是较好的屏蔽了鼠标事件,但是对于键盘事件,由于swing将键盘事件直接发送到当前聚焦的控件,因此如果有一组控件已经获取了焦点,它仍然可以收到键盘按键事件,甚至可以通过tab或ctrl+tab在各个控件之间切换焦点。要完成该功能,需要在玻璃窗体变成可见时调用requestFocusInWindow()以夺取焦点,因此该段代码仍然需要放在该对象的构造函数中,如下:

 1     public ProgressGlassPane() {
2 // blocks all user input
3 addMouseListener(new MouseAdapter() { });
4 addMouseMotionListener(new MouseMotionAdapter() { });
5 addKeyListener(new KeyAdapter() { });
6
7 //This event will be triggered when this component turn to be visible.
8 addComponentListener(new ComponentAdapter() {
9 public void componentShown(ComponentEvent evt) {
10 requestFocusInWindow();
11 }
12 });
13 }

       3) 此时用户仍然可以通过tab键将焦点传入玻璃窗格覆盖的控件中,因此需要在构造函数中调用setFocusTraversalKeysEnabled(false)以便禁用该功能。

 1     public ProgressGlassPane() {
2 // blocks all user input
3 addMouseListener(new MouseAdapter() { });
4 addMouseMotionListener(new MouseMotionAdapter() { });
5 addKeyListener(new KeyAdapter() { });
6
7 setFocusTraversalKeysEnabled(false);
8 //This event will be triggered when this component turn to be visible.
9 addComponentListener(new ComponentAdapter() {
10 public void componentShown(ComponentEvent evt) {
11 requestFocusInWindow();
12 }
13 });
14 }

20.  屏蔽玻璃窗格中部分区域的鼠标事件,比如在一个完全透明的窗格中的左下角绘制一个公司的logo,其他部分则完全透明,此时,如果用户将鼠标放到玻璃窗格下面的控件上方时,由于JFrame的最顶层组件是玻璃窗格,因此他拦截了鼠标光标的显示效果,比如其下摆放了一组输入框,如果没有玻璃窗格,那么当鼠标停留在控件上方时,swing会根据实际控件的类型更新鼠标光标的形状。此时由于玻璃窗格的存在,swing将无法在完成此项功能,因此我们需要为玻璃窗格组件重载public boolean contains(int x,int y)方法,以便通知swing框架,哪些x,y值不包含在玻璃窗格的拦截范围之内,见如下代码:

 1     @Override
2 public boolean contains(int x, int y) {
3 //when none of mouse events exist
4 if (getMouseListeners().length == 0 &&
5 getMouseMotionListeners().length == 0 &&
6 getMouseWheelListeners().length == 0 &&
7 getCursor() == Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR)) {
8 if (image == null) {
9 return false;
10 } else {
11 int imageX = getWidth() - image.getWidth();
12 int imageY = getHeight() - image.getHeight();
13
14 // if the mouse cursor is on a non-opaque(transparent) pixel
// , mouse events
are allowed
16 int inImageX = x - imageX;
17 int inImageY = y - imageY;
18
19 if (inImageX >= 0 && inImageY >= 0 &&
20 inImageX < image.getWidth() && inImageY < image.getHeight()) {
21 int color = image.getRGB(inImageX, inImageY);
22 //it must be transparent if alpha is 0.
23 return (color >> 24 & 0xFF) > 0;
24 }
25 return x > imageX && x < getWidth() && y > imageY && y < getHeight();
26 }
27 }
28 return super.contains(x, y);
29 }

21.  分层窗格:JLayoutPane组件是swing的一个容器,是一个容纳几个子层的面板,swing框架依赖一个分层窗格以显示必须横跨其他组件的特定组件。分层窗格的每一层都通过一个整数来识别,这个整数定义为在这个层的堆栈的深度。最大值表示这个堆栈的最高层次,即显示层的最上方。JLayerPane提供几个层标识符以便容易的把组件插入到正确的层。
      JLayeredPane.DEFAULT_LAYER = 0;     一般放置按钮和表格等正规组件。
      JLayeredPane.PALETTE_LAYER = 100;    一般用于面板和浮动工具栏。
      JLayeredPane.MODAL_LAYER = 200;        模式对话框。
      JLayeredPane.POPUP_LAYER = 300;        显示弹出式窗口,包括工具提示、组合框下拉列表、框架菜单和上下文菜单。
      JLayeredPane.DRAG_LAYER = 400;        用于显示拖拽操作过程中的项。
      swing用间隔100的单位设置这些层,以便使用者可以在他们之间容易的插入自己的层而不引起问题。具体插入方法如下:

1     private void addLayeredComponent() {
2 MyComponent validator = new MyComponent();
3 JLayeredPane layeredPane = getRootPane().getLayeredPane();
4 //分层组件需要使用OverlayLayout布局管理器,或者使用自定义的管理器才能让该层的组件正确的显示
5 layeredPane.setLayout(new OverlayLayout(layeredPane));
6 layeredPane.add(validator, (Integer)(JLayeredPane.DEFAULT_LAYER + 50));
7 }

      如果JLayeredPane使用了普通的布局管理器,该管理器将不会考虑JLayeredPane中各个组件的层级关系,而是简单的将他们视为同一层级,并且继续按照该管理器既有的布局逻辑管理所有的组件,即便他们位于JLayeredPane的不同层级。

 1     private void loadImagesInLayers() {
2 layeredPane.setLayout(new FlowLayout());
3 for (int i = 2; i <= 5; i++) {
4 String name = "images/photo" + i + ".jpg";
5 URL url = getClass().getResource(name);
6 Icon icon = new ImageIcon(url);
7 JLabel label = new JLabel(icon);
8 layeredPane.add(label,(Integer)(JLayeredPane.DEFAULT_LAYER + (i - 1) * 2));
9 }
10 }

22.  重绘管理器(RepaintManager):在Swing的框架中只存在一个RepaintManager,可以通过RepaintManager的静态方法currentManager获取,用户也可以根据自己的需要自定义一个RepaintManager的子类,同时通过setCurrentManager方法设置新的RepaintManager。该类主要用于拦截所有swing组件通过repaint方法刷新组件的显示区域,该类在拦截并处理后,在交给EDT继续处理,因此有些特殊的效果需要通过重载RepaintManager才能很好的完成。如下代码:

 1     //class ReflectionRepaintManager extends RepaintManager
2 private void installRepaintManager() {
3 ReflectionRepaintManager manager = new ReflectionRepaintManager();
4 RepaintManager.setCurrentManager(manager);
5 }
6
7 class ReflectionRepaintManager extends RepaintManager
8 {
9 //该方法重载自RepaintManagr,当用户代码调用repaint之后,swing框架会将需要重绘的脏区域
10 //传递给RepaintManager的addDirtyRegion方法,该方法中将会根据自己的需要自行扩展脏区域,
11 //之后在通过调用父类RepaintManager缺省的addDirtyRegion方法,将更新后的重绘区域重新交给
12 //swing的EDT去处理。
13 public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
14 Rectangle dirtyRegion = getDirtyRegion(c);
15 int lastDeltaX = c.getX();
16 int lastDeltaY = c.getY();
17 Container parent = c.getParent();
18 while (parent instanceof JComponent) {
19 if (!parent.isVisible()) {
20 return;
21 }
22 //如果父类是反射Panel,则将当前需要重绘的区域直接覆盖到相应的反射区域,以便是
23 //相应的反射区域也能和原本需要更新区域一同更新。
24 if (parent instanceof ReflectionPanel) {
25 x += lastDeltaX;
26 y += lastDeltaY;
27 int gap = contentPane.getHeight() - h - y;
28 h += 2 * gap + h;
29 lastDeltaX = lastDeltaY = 0;
30 c = (JComponent)parent;
31 }
32 lastDeltaX += parent.getX();
33 lastDeltaY += parent.getY();
34 parent = parent.getParent();
35 }
36 super.addDirtyRegion(c, x, y, w, h);
37 }
38 }
posted @ 2011-07-16 18:49  OrangeAdmin  阅读(1357)  评论(0编辑  收藏  举报