12. 明確的告訴java 2d你將要完成的繪制,而不是使用一個(gè)更為通用的方式,這樣能夠帶來更好的性能。
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. 圖像合成,其中最為有用的三個(gè)規(guī)則分別是clear、SrcOver(swing缺省)和SrcIn。
Clear:是擦掉一個(gè)圖像的背景以便使他變得完全透明的一個(gè)容易的方式,可以將其理解為Photoshop中的橡皮擦,通過Clear可以清除任意形狀的區(qū)域。
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: 其運(yùn)算公式為Ar = As + Ad * (1 - As), Cr = Cs + Cd * (1 - As), 其中Ar為結(jié)果Alpha,As表示源圖像的Alpha,As為目的圖像的Alpha,Cr表示(RGB)中每個(gè)通道的結(jié)果值,Cs為源圖像中(RGB)單個(gè)通道的值,Cd為目的圖像的單個(gè)通道值。
一般的用法為在目的圖像上繪制半透明的源圖像。
SrcIn:位于目的地內(nèi)部的那部分源代替目的地,位于目的地之外的那部分源丟棄掉。
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個(gè)步驟完成,見下例:
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. 渲染這個(gè)物體上下顛倒的一個(gè)副本
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. 模糊這個(gè)副本的一部分以使它淡出,隨著它遠(yuǎn)離最初的物體。
19 GradientPaint mask;
20 //目的顏色RGB無關(guān)重要,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),這里包含兩個(gè)數(shù)組參數(shù),其中第一個(gè)float類型的數(shù)組包含漸變中使用的每個(gè)顏色的位置。每一對(duì)位置/顏色被稱為一個(gè)停頓,見下例:
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. 優(yōu)化漸變的3個(gè)技巧:
1) 緩存這個(gè)漸變:該解決方案是把這個(gè)漸變變成一個(gè)圖像并僅僅繪制那個(gè)圖像,但是缺點(diǎn)是需要消耗更多的內(nèi)存。
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) 更巧妙的緩存:當(dāng)繪制一個(gè)垂直或者水平漸變時(shí),每一列或者每一行都是相同的,因此可以只是保留一列或者一行的數(shù)據(jù),然在需要覆蓋漸變時(shí)在拉伸該列或者該行。
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) 使用循環(huán)漸變的優(yōu)化:如果漸變只是被覆蓋組件高度的一半時(shí),如以下代碼:
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 }
該代碼將會(huì)從組件的(0,0)到(0,height/2)繪制漸變,同時(shí)利用這個(gè)漸變的最后顏色填充剩下的像素,為了做到這一點(diǎn),java 2d將不斷的檢查是否當(dāng)前的像素位于這個(gè)漸變區(qū)域的外面,因此對(duì)于成千上萬的像素來說,將會(huì)花費(fèi)很多時(shí)間。如果使用循環(huán)漸變的方式,java 2d內(nèi)部在渲染的時(shí)候?qū)?huì)不進(jìn)行該判斷,從而大大提高了整體的效率,見如下代碼:
1 //循環(huán)GradientPaint
2 new GradientPaint(new Point(0,0),Color.WHITE,new Point(0,getHeight())
,Color.DARK_GRAY,true/*該標(biāo)志表示循環(huán)*/);
3 //循環(huán)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每個(gè)顏色通道的亮度增加40%,B通道增加30/256=12%的顏色分量。
6 RescaleOp op = new RescaleOp(factors, offsets, null);
7 return op.filter(srcImage,null);
8 }
18. 玻璃窗格的基本繪制技巧:
1) 給當(dāng)前JFrame安裝玻璃窗格,安裝后該玻璃窗格的缺省顯示方式是隱藏顯示,即setVisible(false),如果之前JFrame已經(jīng)使用了玻璃窗格,本次操作只是替換一個(gè)新的對(duì)象,那么該窗格的visible屬性將和原有窗格的visible屬性保持一致。
1 public ApplicationFrame() { //ApplicationFrame為應(yīng)用程序的主窗體,繼承自JFrame
2 initComponents();
3 //安裝玻璃窗格,glassPane是JComponent的子類。
4 setGlassPane(glassPane = new ProgressGlassPane());
5 }
2) 實(shí)現(xiàn)玻璃窗格的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) 主窗體中的工作線程需要調(diào)用的方法,以便更新進(jìn)度條的顯示狀態(tài)
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. 玻璃窗格中屏蔽輸入事件,上例中繪制的玻璃窗格只是完成了基本的顯示效果,用戶仍然可以操作玻璃窗格覆蓋下的控件,這樣會(huì)給用戶帶來非常迷惑的感覺,因此需要屏蔽玻璃窗格覆蓋下的控件獲取來自鼠標(biāo)和鍵盤的事件。
1) 為玻璃窗格控件自身添加空的鼠標(biāo)和鍵盤的監(jiān)聽器
1 public ProgressGlassPane() {
2 // blocks all user input
3 addMouseListener(new MouseAdapter() { });
4 addMouseMotionListener(new MouseMotionAdapter() { });
5 addKeyListener(new KeyAdapter() { });
6 }
2) 以上操作只是較好的屏蔽了鼠標(biāo)事件,但是對(duì)于鍵盤事件,由于swing將鍵盤事件直接發(fā)送到當(dāng)前聚焦的控件,因此如果有一組控件已經(jīng)獲取了焦點(diǎn),它仍然可以收到鍵盤按鍵事件,甚至可以通過tab或ctrl+tab在各個(gè)控件之間切換焦點(diǎn)。要完成該功能,需要在玻璃窗體變成可見時(shí)調(diào)用requestFocusInWindow()以奪取焦點(diǎn),因此該段代碼仍然需要放在該對(duì)象的構(gòu)造函數(shù)中,如下:
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) 此時(shí)用戶仍然可以通過tab鍵將焦點(diǎn)傳入玻璃窗格覆蓋的控件中,因此需要在構(gòu)造函數(shù)中調(diào)用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. 屏蔽玻璃窗格中部分區(qū)域的鼠標(biāo)事件,比如在一個(gè)完全透明的窗格中的左下角繪制一個(gè)公司的logo,其他部分則完全透明,此時(shí),如果用戶將鼠標(biāo)放到玻璃窗格下面的控件上方時(shí),由于JFrame的最頂層組件是玻璃窗格,因此他攔截了鼠標(biāo)光標(biāo)的顯示效果,比如其下擺放了一組輸入框,如果沒有玻璃窗格,那么當(dāng)鼠標(biāo)停留在控件上方時(shí),swing會(huì)根據(jù)實(shí)際控件的類型更新鼠標(biāo)光標(biāo)的形狀。此時(shí)由于玻璃窗格的存在,swing將無法在完成此項(xiàng)功能,因此我們需要為玻璃窗格組件重載public boolean contains(int x,int y)方法,以便通知swing框架,哪些x,y值不包含在玻璃窗格的攔截范圍之內(nèi),見如下代碼:
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的一個(gè)容器,是一個(gè)容納幾個(gè)子層的面板,swing框架依賴一個(gè)分層窗格以顯示必須橫跨其他組件的特定組件。分層窗格的每一層都通過一個(gè)整數(shù)來識(shí)別,這個(gè)整數(shù)定義為在這個(gè)層的堆棧的深度。最大值表示這個(gè)堆棧的最高層次,即顯示層的最上方。JLayerPane提供幾個(gè)層標(biāo)識(shí)符以便容易的把組件插入到正確的層。
JLayeredPane.DEFAULT_LAYER = 0; 一般放置按鈕和表格等正規(guī)組件。
JLayeredPane.PALETTE_LAYER = 100; 一般用于面板和浮動(dòng)工具欄。
JLayeredPane.MODAL_LAYER = 200; 模式對(duì)話框。
JLayeredPane.POPUP_LAYER = 300; 顯示彈出式窗口,包括工具提示、組合框下拉列表、框架菜單和上下文菜單。
JLayeredPane.DRAG_LAYER = 400; 用于顯示拖拽操作過程中的項(xiàng)。
swing用間隔100的單位設(shè)置這些層,以便使用者可以在他們之間容易的插入自己的層而不引起問題。具體插入方法如下:
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使用了普通的布局管理器,該管理器將不會(huì)考慮JLayeredPane中各個(gè)組件的層級(jí)關(guān)系,而是簡單的將他們視為同一層級(jí),并且繼續(xù)按照該管理器既有的布局邏輯管理所有的組件,即便他們位于JLayeredPane的不同層級(jí)。
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的框架中只存在一個(gè)RepaintManager,可以通過RepaintManager的靜態(tài)方法currentManager獲取,用戶也可以根據(jù)自己的需要自定義一個(gè)RepaintManager的子類,同時(shí)通過setCurrentManager方法設(shè)置新的RepaintManager。該類主要用于攔截所有swing組件通過repaint方法刷新組件的顯示區(qū)域,該類在攔截并處理后,在交給EDT繼續(xù)處理,因此有些特殊的效果需要通過重載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,當(dāng)用戶代碼調(diào)用repaint之后,swing框架會(huì)將需要重繪的臟區(qū)域
10 //傳遞給RepaintManager的addDirtyRegion方法,該方法中將會(huì)根據(jù)自己的需要自行擴(kuò)展臟區(qū)域,
11 //之后在通過調(diào)用父類RepaintManager缺省的addDirtyRegion方法,將更新后的重繪區(qū)域重新交給
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,則將當(dāng)前需要重繪的區(qū)域直接覆蓋到相應(yīng)的反射區(qū)域,以便是
23 //相應(yīng)的反射區(qū)域也能和原本需要更新區(qū)域一同更新。
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 }