• 读书笔记(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 }
  • 相关阅读:
    BZOJ3391: [Usaco2004 Dec]Tree Cutting网络破坏
    python总结二
    python总结一
    评论详解
    C++入门篇十三
    C++入门篇十二
    C++入门篇十一
    C++入门篇十
    C++入门篇九
    c++入门篇八
  • 原文地址:https://www.cnblogs.com/orangeform/p/2108328.html
Copyright © 2020-2023  润新知