<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    Feng.Li's Java See

    抓緊時(shí)間,大步向前。
    隨筆 - 95, 文章 - 4, 評(píng)論 - 58, 引用 - 0
    數(shù)據(jù)加載中……

    個(gè)Java畫圖板程序的設(shè)計(jì)

    本文講述一個(gè)畫圖板應(yīng)用程序的設(shè)計(jì),屏幕抓圖如下。這篇文章帶有三個(gè)附件,其中兩個(gè)jar文件都是j2sdk1.4.2_08編譯打包,包含源代碼,可執(zhí)行,如下表:
    附件名稱及鏈接 詳情
    jDraw_basic.jar 本文是基于這個(gè)基本版本的,屏幕抓圖顯示的也是這個(gè)基本版本的界面。
    jDraw_extended.jar 在基礎(chǔ)版本上稍加擴(kuò)展,加入文件讀存功能,即可將所畫的圖存入一個(gè)模型文件(特定的格式,見下)或者從文件中讀取,也可以將其導(dǎo)出到一個(gè)PNG格式的文件。由于擴(kuò)展功能不是本文的重點(diǎn),并且也不復(fù)雜,所以文中就不在對(duì)其進(jìn)行闡述。它的源代碼只是在基本版本上增加了一些內(nèi)容。
    jdraw_demo.zip 屏幕抓圖中的圖形的模型文件,屬于純文本格式,為了節(jié)省空間,將其壓縮了一下,解壓縮取出其中的jdraw_demo.jdw文件后再使用。按理說(shuō),SGML/XML的格式才是正途,不過(guò)這只是個(gè)簡(jiǎn)單的應(yīng)用,不用那么大動(dòng)干戈了,就走個(gè)“邪道”吧:)

    『IShape』

    這是所有圖形類(此后稱作模型類)都應(yīng)該實(shí)現(xiàn)接口,外部的控制類,比如畫圖板類就通過(guò)這個(gè)接口跟模型類“交流”。名字開頭的I表示它是一個(gè)接口(Interface),這是eclipse用的一個(gè)命名法則,覺得挺有用的,就借鑒來(lái)了。這個(gè)接口定義了兩個(gè)方法:

    public void draw(java.awt.Graphics2D g);每個(gè)實(shí)現(xiàn)IShape的類都在這個(gè)方法里面指定它的圖形顯示代碼。public void processCursorEvent(java.awt.event.MouseEvent evt, int type);這個(gè)方法是在圖形(被用戶)繪制過(guò)程中,發(fā)生相關(guān)的鼠標(biāo)點(diǎn)擊和移動(dòng)事件時(shí)調(diào)用的。第一個(gè)參數(shù)就是所發(fā)生的鼠標(biāo)事件對(duì)象;第二個(gè)參數(shù)取值于IShape所定義的三個(gè)常數(shù):RIGHT_PRESSED, LEFT_RELEASED,和CURSOR_DRAGGED。

    下面這個(gè)class diagram顯示了所有圖形類的結(jié)構(gòu)圖。FreeShape, RectBoundedShape,和PolyGon這三個(gè)類直接實(shí)現(xiàn)了IShape接口。其中,F(xiàn)reeShape和RectBoundedShape是抽象類,分別代表不規(guī)則圖形(比如鉛筆畫圖)和以一個(gè)長(zhǎng)方形為邊界的規(guī)則圖形,由于分屬于這兩個(gè)類別的圖形對(duì)于鼠標(biāo)事件的處理基本上都是一致的,所以就抽象出來(lái)這兩個(gè)父類,避免重復(fù)代碼。PolyGon是一個(gè)具體類,它的命名沒有采用Polygon是為了避免同java.awt.Polygon重名。它代表的圖形是多邊形,由于它獨(dú)特的鼠標(biāo)處理方式,它不屬于上面兩種類型圖形的任何一種,所以它直接實(shí)現(xiàn)了IShape接口。

    IShape接口所定義的兩個(gè)方法到底是怎么被用到的呢?這個(gè)問題現(xiàn)在還不能立刻解答。在下面的部分,我們先講述FreeShape所定義的不規(guī)則圖形及其兩個(gè)具體子類PolyLine和Eraser,然后在這個(gè)基礎(chǔ)上講述一個(gè)縮略版的畫圖板類,到那個(gè)時(shí)候,上面問題的答案也就自然揭曉了。之后,我們?cè)倮^續(xù)講述其他的圖形類。

    『FreeShape』

    講到FreeShape,我們不得不先說(shuō)一下PointsSet這個(gè)類。這是一個(gè)util類,被FreeShape和PolyGon用到,代表一個(gè)有序的點(diǎn)集合,并提供方便的方法來(lái)加入新的點(diǎn)和讀取點(diǎn)坐標(biāo)。為了方便對(duì)模型類代碼的理解,這里列出PointsSet類的API。

    public PointsSet();用默認(rèn)的初始容量(10)創(chuàng)建一個(gè)對(duì)象。public PointsSet(int initCap);用指定的初始容量(initCap)創(chuàng)建一個(gè)對(duì)象。public void addPoint(int x, int y);加入一個(gè)新的點(diǎn)到這個(gè)集合的末端;如果舊的末端點(diǎn)跟新的點(diǎn)重合,則不重復(fù)加入。public int[][] getPoints();將所有點(diǎn)以一個(gè)二維數(shù)組(int[2][n])返回。第一行是x坐標(biāo),第二行是y坐標(biāo)。public int[][] getPoints(int x, int y);類似上一個(gè)方法,只是最后將參數(shù)指定的點(diǎn)加在末尾(無(wú)論是否跟集合末端的點(diǎn)重合);這個(gè)方法只被PolyGon用到。

    好了,來(lái)看下面代碼中FreeShape對(duì)IShape接口的實(shí)現(xiàn)。FreeShape有三個(gè)屬性變量:color, stroke,和pointsSet。權(quán)限設(shè)成protected當(dāng)然是給子類用啦。color就是色彩了,stroke用來(lái)指定使用線條的粗細(xì)(當(dāng)然,Stroke類的對(duì)象還可以指定交接點(diǎn)形狀之類的屬性,不過(guò)這里都使用其默認(rèn)值了),pointsSet當(dāng)然就是包含了所有控制點(diǎn)(這里叫控制點(diǎn)似乎不太恰當(dāng),因?yàn)槠鋵?shí)無(wú)法利用這些點(diǎn)來(lái)“控制”的,不過(guò)也想不到其他恰當(dāng)?shù)拿?,就這么叫吧)集合。值得注意的是構(gòu)造函數(shù)里面包含了起始點(diǎn)的坐標(biāo),這個(gè)點(diǎn)在函數(shù)里面被加到了控制點(diǎn)集中。

    這類圖形對(duì)鼠標(biāo)事件的處理很簡(jiǎn)單,它只對(duì)IShape.CURSOR_DRAGGED類型的事件感興趣,每當(dāng)發(fā)生這類事件的時(shí)候,就把鼠標(biāo)拖拽到的新的點(diǎn)加入到控制點(diǎn)集中。當(dāng)然了,根據(jù)上面看到的PointsSet.addPoint(int,int)這個(gè)方法的“個(gè)性”,這個(gè)點(diǎn)是否真的被加入還要看它是否跟舊的末端點(diǎn)重合。

    import java.awt.*;import java.awt.event.MouseEvent;public abstract class FreeShape implements IShape {        protected Color color;    protected Stroke stroke;    protected PointsSet pointsSet;      protected FreeShape(Color c, Stroke s, int x, int y) {        pointsSet = new PointsSet(50);        color = c;        stroke = s;        pointsSet.addPoint(x, y);    }        public void processCursorEvent(MouseEvent e, int t) {        if (t != IShape.CURSOR_DRAGGED)            return;        pointsSet.addPoint(e.getX(), e.getY());    }}

    FreeShape類沒有實(shí)現(xiàn)IShape接口的draw(Graphics2D)方法,很明顯,這個(gè)方法是留給子類來(lái)完成的。PolyLine和Eraser繼承了FreeShape,分別代表鉛筆繪出的圖形和橡皮擦。其中PolyLine的構(gòu)造函數(shù)結(jié)構(gòu)跟其父類相似,直接調(diào)用父類的super方法來(lái)完成;相比之下,Eraser類就有點(diǎn)“叛逆”了,它的參數(shù)里面用一個(gè)JComponent替換了Color。Eraser類是通過(guò)畫出跟畫圖板背景色彩一致的線條來(lái)掩蓋原有圖形而實(shí)現(xiàn)橡皮擦的效果的,但由于畫圖板的背景色是可以調(diào)的(見抓圖的Color Settings部分),直接給Eraser的構(gòu)造函數(shù)一個(gè)色彩對(duì)象不太合適,所以干脆將畫圖板自己(JComponent)傳了進(jìn)來(lái),這樣,每次Eraser設(shè)定圖形色彩時(shí),都直接問畫圖板要它的背景色。來(lái)看一下PolyLine對(duì)draw(Graphics2D)方法的實(shí)現(xiàn):

        public void draw(Graphics2D g) {        g.setColor(color);        g.setStroke(stroke);        int[][] points = pointsSet.getPoints();        int s = points[0].length;        if (s == 1) {            int x = points[0][0];            int y = points[1][0];            g.drawLine(x, y, x, y);        } else {            g.drawPolyline(points[0], points[1], s);        }    }

    這個(gè)方法里面有一個(gè)if-else結(jié)構(gòu),由于構(gòu)造函數(shù)里面已經(jīng)將起始點(diǎn)加入控制點(diǎn)集中,所以pointsSet.getPoints()會(huì)至少返回一個(gè)點(diǎn)。利用Graphics.drawPolyline(int[],int[],int)畫圖時(shí),如果只有一個(gè)點(diǎn),它是不會(huì)畫出來(lái)東西的,所以檢查一下點(diǎn)數(shù),如果只有一個(gè),則改用Graphics.drawLine(int,int,int,int)將這個(gè)點(diǎn)畫出來(lái)。Eraser的draw(Graphics2D)方法跟上面基本上完全一樣,只是傳給Graphics.setColor(Color)的參數(shù)是通過(guò)JComponent.getBackground()得到的。

    『TestBoard』

    現(xiàn)在就來(lái)看一個(gè)精簡(jiǎn)版的畫圖板類:TestBoard。下面的代碼,是通過(guò)代碼注釋進(jìn)行解釋的。需要注意的是,TestBoard本身還不能直接運(yùn)行,需要把它放到一個(gè)JFrame里面才行。同時(shí)畫圖工具的切換也需要外部的控件來(lái)處理。不過(guò)這些都比較簡(jiǎn)單了,就不多說(shuō)了。

    import java.awt.*;import java.awt.event.*;import javax.swing.*;import java.util.ArrayList;public class TestBoard extends JPanel                          implements MouseListener, MouseMotionListener {        //定義一些常量    public static final int TOOL_PENCIL = 1;    public static final int TOOL_ERASER = 2;    public static final Stroke STROKE = new BasicStroke(1.0f);    public static final Stroke ERASER_STROKE = new BasicStroke(15.0f);    private ArrayList shapes;     //保存所有的圖形對(duì)象(IShape)    private IShape currentShape;  //指向當(dāng)前還未完成的圖形    private int tool; //代表當(dāng)前使用的畫圖工具(TOOL_PENCIL或TOOL_ERASER)    public TestBoard() {        //進(jìn)行一些初始化        shapes = new ArrayList();        tool = TOOL_PENCIL;        currentShape = null;                //安裝鼠標(biāo)監(jiān)聽器        addMouseListener(this);        addMouseMotionListener(this);    }        //外部的控制界面可以通過(guò)這個(gè)方法切換畫圖工具    public void setTool(int t) {        tool = t;    }        //override JPanel的方法。通過(guò)調(diào)用IShape.draw(Graphics2D)方法來(lái)顯示圖形    protected void paintComponent(Graphics g) {        super.paintComponent(g);        int size = shapes.size();        Graphics2D g2d = (Graphics2D) g;        for (int i=0; i<size; i++) {            ((IShape) shapes.get(i)).draw(g2d);        }    }        public void mousePressed(MouseEvent e) {        /* 當(dāng)左鍵點(diǎn)擊時(shí),currentShape肯定指向null。根據(jù)當(dāng)前畫圖工具創(chuàng)建相應(yīng)圖形對(duì)象,           將currentShape指向它,并把這個(gè)對(duì)象加入到對(duì)象集合(shapes)中。另外,調(diào)用           repaint()方法將畫圖板的畫面更新一下。 */        if (e.getButton() == MouseEvent.BUTTON1) {            switch (tool) {            case TOOL_PENCIL:                currentShape = new PolyLine(getForeground(),                                                STROKE, e.getX(), e.getY());                break;            case TOOL_ERASER:                currentShape = new Eraser(this, ERASER_STROKE,                                                e.getX(), e.getY());                break;            }            shapes.add(currentShape);            repaint();        /* 當(dāng)右鍵點(diǎn)擊并且currentShape不指向null時(shí),調(diào)用currentShape的          processCursorEvent(MouseEvent,int)方法,類型參數(shù)是      IShape.RIGHT_PRESSED。 repaint()*/        } else if (e.getButton() == MouseEvent.BUTTON3 && currentShape != null) {            currentShape.processCursorEvent(e, IShape.RIGHT_PRESSED);            repaint();        }    }        public void mouseDragged(MouseEvent e) {        /* 當(dāng)鼠標(biāo)拖拽并且currentShape不指向null時(shí)(這種情況下,左鍵肯定處于          按下狀態(tài)),調(diào)用currentShape的processCursorEvent(MouseEvent,int)方法,          類型參數(shù)是IShape.CURSOR_DRAGGED。 repaint()*/        if (currentShape != null) {            currentShape.processCursorEvent(e, IShape.CURSOR_DRAGGED);            repaint();        }    }        public void mouseReleased(MouseEvent e) {        /* 當(dāng)左鍵被松開并且currentShape不指向null時(shí)(這個(gè)時(shí)候,currentShape          肯定不會(huì)指向null的,多檢查一次,保險(xiǎn)),調(diào)用currentShape的          processCursorEvent(MouseEvent,int)方法,類型參數(shù)是          IShape.CURSOR_DRAGGED。 repaint()*/        if (e.getButton() == MouseEvent.BUTTON1 && currentShape != null) {            currentShape.processCursorEvent(e, IShape.LEFT_RELEASED);            currentShape = null;            repaint();        }    }        //對(duì)下面這些事件不感興趣    public void mouseClicked(MouseEvent e) {}    public void mouseEntered(MouseEvent e) {}    public void mouseExited(MouseEvent e) {}    public void mouseMoved(MouseEvent e) {}    }

    至此,整個(gè)程序的流程就很清楚了,文章開頭部分的問題也被解開了。接下來(lái),就繼續(xù)來(lái)看其他的模型類。

    『RectBoundedShape』

    RectBoundedShape構(gòu)造函數(shù)的結(jié)構(gòu)跟FreeShape一樣,在色彩和線條的運(yùn)用上也是一樣的,也只對(duì)鼠標(biāo)拖拽事件感興趣。不過(guò),它只有兩個(gè)控制點(diǎn),起始點(diǎn)和結(jié)束點(diǎn),所以,不需要用到PointsSet。本來(lái),RectBoundedShape這個(gè)類是比FreeShape簡(jiǎn)單的,在處理鼠標(biāo)拖拽事件時(shí)只要將結(jié)束點(diǎn)設(shè)置到新拖拽到的點(diǎn)就可以了。不過(guò),這里我們多加入一個(gè)的功能,就是在shift鍵按下的情況下,讓圖形的邊界是個(gè)正方形(取原邊界中較短的那條邊)。這個(gè)功能是由regulateShape(int,int)這個(gè)方法來(lái)完成的,它的代碼相當(dāng)簡(jiǎn)短,就不多做解釋了 。

    import java.awt.*;import java.awt.event.MouseEvent;public abstract class RectBoundedShape implements IShape {        protected Color color;    protected Stroke stroke;      protected int startX, startY, endX, endY;        protected RectBoundedShape(Color c, Stroke s, int x, int y) {        color = c;        stroke = s;        startX = endX = x;        startY = endY = y;    }        public void processCursorEvent(MouseEvent e, int t) {        if (t != IShape.CURSOR_DRAGGED)            return;        int x = e.getX();        int y = e.getY();        if (e.isShiftDown()) {            regulateShape(x, y);        } else {            endX = x;            endY = y;        }    }        protected void regulateShape(int x, int y) {        int w = x - startX;        int h = y - startY;        int s = Math.min(Math.abs(w), Math.abs(h));        if (s == 0) {            endX = startX;            endY = startY;        } else {            endX = startX + s * (w / Math.abs(w));            endY = startY + s * (h / Math.abs(h));        }    }    }

    有了RectBoundedShape這個(gè)父類打下的基礎(chǔ),它下面的子類所要做的事情就是畫圖啦。所有子類的構(gòu)造函數(shù)跟父類都是一樣的結(jié)構(gòu),基本上也都是直接調(diào)用super的構(gòu)造函數(shù),只是Diamond這個(gè)類為了提高畫圖效率,“私下”定義了一個(gè)數(shù)組。RectBoundedShape的子類包括Line, Rect, Oval, 和Diamond。除了Diamond需要根據(jù)邊界長(zhǎng)方形進(jìn)行稍微計(jì)算求得菱形的四個(gè)點(diǎn)外,它們的圖形都可以直接利用Graphics類提供的方法很方便的畫出來(lái),詳情可以參看源代碼,就不多說(shuō)了?,F(xiàn)在看一下Line這個(gè)類。不同于其它幾個(gè)類,在shift鍵按下的情況下,根據(jù)角度不同,我們想畫出45度線,水平線,或者豎直線。所以,Line這個(gè)類不使用其父類定義的processCursorEvent(MouseEvent,int)方法,而是自己定義了一套。父類中regulateShape(int,int)方法的權(quán)限設(shè)成protected也是為了給Line用的。代碼如下:

        public void processCursorEvent(MouseEvent e, int t) {        if (t != IShape.CURSOR_DRAGGED)            return;        int x = e.getX();        int y = e.getY();        if (e.isShiftDown()) {            //這個(gè)情況單獨(dú)處理,不然就要除以0了            if (x - startX == 0) { //豎直                endX = startX;                endY = y;            } else {                //由于對(duì)稱性,只要算斜率的絕對(duì)值                float slope = Math.abs(((float) (y - startY)) / (x - startX));                //小于30度,變成水平的                if (slope < 0.577) {                    endX = x;                    endY = startY;                //介于30度跟60度中間的,變成45度,利用父類的regulateShape(int,int)完成                } else if (slope < 1.155) {                    regulateShape(x, y);                //大于60度,變成豎直的                } else {                    endX = startX;                    endY = y;                }            }        //如果shift鍵沒有按下,跟父類一樣處理        } else {            endX = x;            endY = y;        }    }

    『PolyGon』

    用戶畫多邊形的步驟是這樣的,先在一點(diǎn)按下鼠標(biāo)左鍵,定義一個(gè)頂點(diǎn),然后將鼠標(biāo)拖拽到多邊形的下一個(gè)頂點(diǎn),點(diǎn)鼠標(biāo)右鍵將這個(gè)點(diǎn)記錄,之后重復(fù)這個(gè)步驟直到所有頂點(diǎn)都記錄,松開左鍵,多邊形完成。在多邊形完成前,顯示出來(lái)的不是閉合圖形,當(dāng)左鍵松開時(shí),圖形自動(dòng)閉合。對(duì)于最后一個(gè)頂點(diǎn),用戶不用點(diǎn)右鍵也會(huì)被自動(dòng)記錄的。好了,來(lái)看一下這個(gè)過(guò)程是怎么來(lái)完成的。方便起見,直接用注釋在代碼上解釋了。

    import java.awt.*;import java.awt.event.MouseEvent;public class PolyGon implements IShape {        //類似于FreeShape和RectBoundedShape的變量    private Color color;    private Stroke stroke;    //記錄所有頂點(diǎn)坐標(biāo),姑且稱之為頂點(diǎn)集    private PointsSet pointsSet;      //記錄多邊形是否完成。true表示完成    private boolean finalized;        //記錄畫圖過(guò)程中鼠標(biāo)被拖拽到的點(diǎn),姑且稱之為浮點(diǎn)吧^_^    private int currX, currY;        public PolyGon(Color c, Stroke s, int x, int y) {        pointsSet = new PointsSet();        color = c;        stroke = s;        pointsSet.addPoint(x, y);        //剛開始先把浮點(diǎn)設(shè)置到起始頂點(diǎn)        currX = x;        currY = y;        finalized = false;    }        public void processCursorEvent(MouseEvent e, int t) {        //首先更新浮點(diǎn)坐標(biāo)        currX = e.getX();        currY = e.getY();        //右鍵按下時(shí),將浮點(diǎn)加入到頂點(diǎn)集里        if (t == IShape.RIGHT_PRESSED) {            pointsSet.addPoint(currX, currY);        //左鍵按下時(shí),設(shè)置多邊形到完成狀態(tài),并且將浮點(diǎn)加入頂點(diǎn)集中        } else if (t == IShape.LEFT_RELEASED) {            finalized = true;            pointsSet.addPoint(currX, currY);        }        /* 注意:上面的if-else結(jié)構(gòu)只包含了RIGHT_PRESSED和LEFT_RELEASED兩種情況,           不過(guò),這個(gè)方法也處理了CURSOR_DRAGGED這種情況,就是更新浮點(diǎn)坐標(biāo) */    }        public void draw(Graphics2D g) {        g.setColor(color);        g.setStroke(stroke);        if (finalized) {            //一旦圖形完成,浮點(diǎn)就不再用到了            int[][] points = pointsSet.getPoints();            int s = points[0].length;            //這部分跟PolyLine類似            if (s == 1) {                int x = points[0][0];                int y = points[1][0];                g.drawLine(x, y, x, y);            } else {                g.drawPolygon(points[0], points[1], s);            }        } else { //圖形沒完成的情況下,顯示的時(shí)候要用到浮點(diǎn)            int[][] points = pointsSet.getPoints(currX, currY);            g.drawPolyline(points[0], points[1], points[0].length);        }    }        }

    『其他』

    DrawingBoard(extends JPanel)是附件程序中用的畫圖板類,它是在TestBoard類上的一個(gè)擴(kuò)展,加入了其他的模型類。另外,它提供了一些方法讓外部控制界面來(lái)設(shè)置繪圖色,畫圖板背景色,畫圖線條,橡皮擦大小(也是通過(guò)改變線條實(shí)現(xiàn)的)。這些就不再一一贅述了。

    AppFrame(extends JFrame)用來(lái)放畫圖板和控制面板。

    此外,在稍微變動(dòng)代碼的情況下,還可以加入新的圖形類,當(dāng)然這些類要實(shí)現(xiàn)IShape接口,比如,直接繼承RectBoundedShape,定義新的圖形顯示代碼。 

    posted on 2007-05-25 12:19 小鋒 閱讀(1546) 評(píng)論(0)  編輯  收藏


    只有注冊(cè)用戶登錄后才能發(fā)表評(píng)論。


    網(wǎng)站導(dǎo)航:
     
    主站蜘蛛池模板: 国产成人免费高清激情视频| 国产成人免费永久播放视频平台| 亚洲人成网www| 皇色在线视频免费网站| 亚洲精品乱码久久久久久V| 亚洲?v女人的天堂在线观看| 中文字幕手机在线免费看电影| 久久精品国产亚洲AV麻豆王友容| 久久午夜免费视频| 午夜亚洲乱码伦小说区69堂| 久久精品亚洲视频| 台湾一级毛片永久免费| 无套内谢孕妇毛片免费看看| 久久精品国产亚洲网站| 毛片免费视频观看| 亚欧国产一级在线免费| 久久亚洲AV成人出白浆无码国产| 成年美女黄网站色大免费视频| v片免费在线观看| 亚洲视频在线观看地址| 国产真实伦在线视频免费观看| a毛片免费观看完整| 亚洲日韩国产欧美一区二区三区 | 国内外成人免费视频| 一级特级aaaa毛片免费观看| 亚洲网站视频在线观看| 国产成人免费手机在线观看视频 | xxx毛茸茸的亚洲| 亚洲人成伊人成综合网久久久 | 影音先锋在线免费观看| av成人免费电影| 亚洲国产成人综合精品| 亚洲av最新在线网址| 四虎影永久在线高清免费| 99久热只有精品视频免费观看17| 免费无码午夜福利片| 亚洲电影在线播放| 亚洲日产无码中文字幕| 日本特黄a级高清免费大片| 日韩精品在线免费观看| 爱情岛亚洲论坛在线观看|