java消除
1. java 雙緩沖,消除閃爍 的問題
[轉]雙緩沖在畫板程序中的應用
1.用雙緩沖解決畫板程序中的刷新問題
我們用Java編制畫板程序的時候,總是存在一個刷新的問題:當Canvas所在的窗口最小化或者被其他應用程序遮擋後,再次恢復,Canvas上的圖形
數據將被部分或者完全擦除掉.
通常解決這個問題的方法是在Canvas的paint()函數中重繪圖形,但是由於在繪圖的過程中產生了大量的數據,重新在Canvas上繪制這些數據將導
致大量的系統開銷,還會產生閃爍,故該方法可行但並不可取.
利用雙緩沖技術可以很好的解決這個問題,其主要原理是開辟兩個圖形緩沖區,一個是前台的顯示緩沖(也就是Canvas),一個是後台的圖形緩沖(
通常是Image).用戶在繪制圖形時,我們對這兩個緩沖區進行同步更新,相當於為前台的數據作了一個後台備份.當前台的圖形被遮蓋需要恢復的
時候,我們就可以用這個後台備份來恢復,具體方法是重寫paint()函數,將備份好的圖像一次性的畫到屏幕上去.
為什麼是paint()?這里需要先了解一下有關Java處理AWT繪圖的基礎知識:Java的顯示更新是由一個AWT線程來控制完成的.該線程主要負責兩種
與顯示更新相關的情況.第一種情況稱為曝光,表示部分顯示區域毀壞或需要清除.這種情況發生時,系統直接調用paint()方法;第二種情況是程
序決定重畫顯示區域,添加一些新的內容,此時需要程序調用成員的repaint()方法,repaint()方法調用成員的update(),update()再調用paint()
方法.顯然我們所說的就是第一種.只要Canvas組件所在的窗口最小化或者被其他應用程序遮擋住,系統就會調用paint()對畫布進行重繪.如果我
們在paint()方法中什麼都不做,就只能眼睜睜的看著辛辛苦苦畫的線條一旦被覆蓋就再也看不見了.
作為起點,請先看一個最簡單的畫板程序,請注意,以下程序使用的是j2sdk1.4.1版本,在Win98的IE下(不是Tencent Explorer)全部測試通過:
//:WBApplet.java
import java.applet.*;
public class WBApplet extends Applet{
final static int DEFAULT_BOARDWIDTH=700;
final static int DEFAULT_BOARDHEIGHT=400;
public void init(){
super.init();
setLayout(null);
whiteBoard = new WhiteBoard(this);
whiteBoard.reshape(0,0,DEFAULT_BOARDWIDTH,DEFAULT_BOARDHEIGHT);
add(whiteBoard);
}
WhiteBoard whiteBoard;
}
///:~
//:WhiteBoard.java
java.awt.*;
import java.awt.event.*;
public class WhiteBoard extends Canvas implements MouseMotionListener,MouseListener{
final static int DEFAULT_BOARDWIDTH=700;
final static int DEFAULT_BOARDHEIGHT=400;
int x0,y0,x1,y1;
WhiteBoard(WBApplet WBApplet1){
parent = WBApplet1;
addMouseMotionListener(this);
addMouseListener(this);
}
synchronized public void update_buffer(Graphics g,DrawItem data) {
g.drawLine(data.x0,data.y0,data.x1,data.y1);
}
public void mouseReleased(MouseEvent e){}
public void mouseEntered(MouseEvent e){}
public void mouseExited(MouseEvent e){}
public void mouseClicked(MouseEvent e){}
public void mouseMoved(MouseEvent e){}
public void mouseDragged(MouseEvent e){
x1=e.getX();
y1=e.getY();
Graphics g = getGraphics();
update_buffer(g,new DrawItem(x0,y0,x1,y1));
g.dispose();
x0=x1;
y0=y1;
}
public void mousePressed(MouseEvent e){
x0 =e.getX();
y0 =e.getY();
}
WBApplet parent;
}
class DrawItem{
DrawItem(int x0,int y0,int x1,int y1){
this.x0=x0;
this.y0=y0;
this.x1=x1;
this.y1=y1;
}
int x0;
int y0;
int x1;
int y1;
}
///:~
我們將白板需完成的所有邏輯操作封裝在了一個WhiteBoard類中,以方便主程序的Applet調用.同時,定義了一個繪圖的數據類DrawItem,用來封
裝圖形數據.繪圖的操作都寫在update_buffer中.顯然,這個程序無法實現刷新後的恢復,我們需要使用雙緩沖技術.
為了實現雙緩沖,首先定義圖形緩沖區如下
private Image off_screen_buf;
private Graphics off_screen_gc;
並在WhiteBoard類的構造函數中對其進行初始化
off_screen_buf =parent.createImage(DEFAULT_BOARDWIDTH,DEFAULT_BOARDHEIGHT);
off_screen_gc = off_screen_buf.getGraphics();
在處理用戶繪制圖形的函數中,我們使用update_buffer對顯示緩沖和圖形緩沖同時進行更新
Graphics g = getGraphics();
update_buffer(g,new DrawItem(x0,y0,x1,y1));//前台更新畫布
update_buffer(off_screen_gc,new DrawItem(x0,y0,x1,y1));//後台更新緩沖
g.dispose();
顯然,後台的更新操作是不可視的,所以是off-screen.
最後,重寫paint()方法,調用_from_offscreen_buf(g)將圖形緩沖區的圖像畫到屏幕上去.
public void paint(Graphics g){
_from_offscreen_buf(g);
}
void _from_offscreen_buf(Graphics g){
if(g != null)
g.drawImage(off_screen_buf, 0, 0, null);
}
就是這么簡單的幾行代碼,就可以讓我們完全的避免圖形不能恢復的問題.下面是WhiteBoard經改進後的完整代碼.
//:WhiteBoard.java
import java.awt.*;
import java.awt.event.*;
public class WhiteBoard extends Canvas implements MouseMotionListener,MouseListener{
final static int DEFAULT_BOARDWIDTH=700;
final static int DEFAULT_BOARDHEIGHT=400;
int x0,y0,x1,y1;
WhiteBoard(WBApplet WBApplet1){
parent = WBApplet1;
off_screen_buf =parent.createImage(DEFAULT_BOARDWIDTH,DEFAULT_BOARDHEIGHT);
off_screen_gc = off_screen_buf.getGraphics();
addMouseMotionListener(this);
addMouseListener(this);
}
synchronized public void update_buffer(Graphics g,DrawItem data) {
g.drawLine(data.x0,data.y0,data.x1,data.y1);
}
public void mouseMoved(MouseEvent e){}
public void mouseReleased(MouseEvent e){}
public void mouseEntered(MouseEvent e){}
public void mouseExited(MouseEvent e){}
public void mouseClicked(MouseEvent e){}
public void mouseDragged(MouseEvent e){
x1=e.getX();
y1=e.getY();
Graphics g = getGraphics();
update_buffer(g,new DrawItem(x0,y0,x1,y1));
update_buffer(off_screen_gc,new DrawItem(x0,y0,x1,y1));
g.dispose();
x0=x1;
y0=y1;
}
public void mousePressed(MouseEvent e){
x0 =e.getX();
y0 =e.getY();
}
public void paint(Graphics g){
_from_offscreen_buf(g);//把這句話屏蔽掉,就不能恢復用戶繪制的圖形了
}
void _from_offscreen_buf(Graphics g){
if(g != null)
g.drawImage(off_screen_buf, 0, 0, null);
}
private Image off_screen_buf;
private Graphics off_screen_gc;
WBApplet parent;
}
class DrawItem{
DrawItem(int x0,int y0,int x1,int y1){
this.x0=x0;
this.y0=y0;
this.x1=x1;
this.y1=y1;
}
int x0;
int y0;
int x1;
int y1;
}
///:~
運行一下,看是不是不一樣了.這一次你想讓你畫的東西消失都不可能了.為了將這個原理說清楚,以上的代碼我都沒有編寫的太復雜,下一次我們
會創建更加復雜,更加完善的畫板程序.
2.用雙緩沖實現各種圖形的繪制
在一個畫板程序中,用戶應該能夠用畫筆繪制各種圖形,除了上一節實現的自由畫法(Freehand)外,還應該可以畫直線,長方體,橢圓等等.以繪制
直線為例,我們都知道,只有在松開滑鼠鍵之後,直線才實實在在的顯示在了畫布上,而在拖拽滑鼠的過程中,直線在畫布中的顯示是隨著滑鼠的箭
頭方位的變化而不斷更新的.體現在程序中,這是一個不斷擦除,顯示,再擦除,再顯示的過程.擦除的是箭頭上一個點和起點間的直線,顯示的是箭
頭當前點和起點間的的直線.這個顯示的過程由update_buffer負責,而這個擦除的工作則和上一節出理刷新一樣,由_from_offscreen_buf來
完成.實際上,所謂擦除,也就是將畫板恢復到某一個原來的時刻.
這一個過程在下面一個修改後的拖拽操作的處理程序中完成:
public void mouseDragged(MouseEvent e){
Graphics g = getGraphics();
_from_offscreen_buf(g);
x1=e.getX();
y1=e.getY();
update_buffer(g,new DrawItem(x0,y0,x1,y1));
g.dispose();
}
注意,在該方法中,我們沒有對後台緩沖進行更新,這是因為滑鼠在拖拽的時候,雖然畫板上會顯示線條,但是這條直線並沒有真正的畫下去.那麼
在什麼時候應該對後台緩沖更新呢?顯然,是在滑鼠松開的時候.我們需要在mouseReleased方法中做這個工作.
public void mouseReleased(MouseEvent e){
Graphics g = getGraphics();
_from_offscreen_buf(g);
x1=e.getX();
y1=e.getY();
update_buffer(g,new DrawItem(x0,y0,x1,y1));
update_buffer(off_screen_gc,new DrawItem(x0,y0,x1,y1));
g.dispose();
}
可以看到,只有在滑鼠松開的時候,畫到畫板上的直線才最後確定了,我們才能夠將這一條線備份到緩沖區裡面去.
下面是升級後的完整的WhiteBoard.java程序.
//:WhiteBoard.java
import java.awt.*;
import java.awt.event.*;
public class WhiteBoard extends Canvas implements MouseMotionListener,MouseListener{
final static int DEFAULT_BOARDWIDTH=700;
final static int DEFAULT_BOARDHEIGHT=400;
int x0,y0,x1,y1;
WhiteBoard(WBApplet WBApplet1){
parent = WBApplet1;
off_screen_buf =parent.createImage(DEFAULT_BOARDWIDTH,DEFAULT_BOARDHEIGHT);
off_screen_gc = off_screen_buf.getGraphics();
addMouseMotionListener(this);
addMouseListener(this);
draw_mode=2;
}
synchronized public void update_buffer(Graphics g,DrawItem data) {
g.drawLine(data.x0,data.y0,data.x1,data.y1);
}
public void mouseMoved(MouseEvent e){}
public void mouseReleased(MouseEvent e){
switch(draw_mode){
case 2:
Graphics g = getGraphics();
_from_offscreen_buf(g);
x1=e.getX();
y1=e.getY();
update_buffer(g,new DrawItem(x0,y0,x1,y1));
update_buffer(off_screen_gc,new DrawItem(x0,y0,x1,y1));
g.dispose();
}
}
public void mouseEntered(MouseEvent e){}
public void mouseExited(MouseEvent e){}
public void mouseClicked(MouseEvent e){}
public void mouseDragged(MouseEvent e){
switch(draw_mode){
case 1:
x1=e.getX();
y1=e.getY();
Graphics g = getGraphics();
update_buffer(g,new DrawItem(x0,y0,x1,y1));
update_buffer(off_screen_gc,new DrawItem(x0,y0,x1,y1));
g.dispose();
x0=x1;
y0=y1;
break;
case 2:
Graphics g1 = getGraphics();
_from_offscreen_buf(g1);
x1=e.getX();
y1=e.getY();
update_buffer(g1,new DrawItem(x0,y0,x1,y1));
g1.dispose();
}
}
public void mousePressed(MouseEvent e){
x0 =e.getX();
y0 =e.getY();
}
public void paint(Graphics g){
_from_offscreen_buf(g);
}
void _from_offscreen_buf(Graphics g){
if(g != null)
g.drawImage(off_screen_buf, 0, 0, null);
}
private int draw_mode;
private Image off_screen_buf;
private Graphics off_screen_gc;
WBApplet parent;
}
class DrawItem{
DrawItem(int x0,int y0,int x1,int y1){
this.x0=x0;
this.y0=y0;
this.x1=x1;
this.y1=y1;
}
int x0;
int y0;
int x1;
int y1;
}
///:~
注意到,在這個程序裡面我們創建了一個新的私有變數draw_mode,用來存儲繪圖模式的代號.在這里,我們使用1來代表自由繪畫,2來代表畫直線.
在構造函數中為draw_mode定義初值可以使我們對不同種類圖形繪制的調試很方便,在上面的程序中,我們定義的是2,如果賦值為1,則又回到自由
繪畫的模式.事實上,我們應該在這樣的一個框架上把程序不斷的擴充和完善.