1. 程式人生 > >Java圖形介面基礎案例-飛行的小球

Java圖形介面基礎案例-飛行的小球

前面幾次一直都在講靜物,做了烏龜,做了星星,有想法的coder們就在想怎麼樣才能讓這些東西動起來呢,這也就是推動技術發展的動力,好在這次總算是給大家帶來了能讓物體動起來的技術,我們現在先不已前面的烏龜和星星為案例繼續講如何寫運動的物體,還是先從最簡單的物體開始,反正原理都一樣,掌握了這個做其他的也會動,這次用一個小球作為案例的切入口

飛行的小球

好了,按照上一次講的做法,首先我們要列出一個步驟出來,看了標題就知道現在要做一個可以飛行的小球。好的,讓我們來大概寫一下步驟:
1.先做一個窗體
2.然後就是要畫一個小球
3.最後一步就是要實現讓小球落下來
按照步驟先畫出一個圓,這個程式碼已經寫了這麼多次,應該很熟熟練了,我就不詳細說了,直接上程式碼了

    import java.awt.*;
public class MyBall {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Frame w = new Frame();
        w.setSize(300, 400);

        MyOval mp = new MyOval();
        w.add(mp);

        w.show();
    }

}
class MyOval extends Panel
{
public void paint(Graphics g){ g.fillOval(30, 30, 20, 20); } }

出現如圖所示的這樣一個黑的實心圓
這裡寫圖片描述
下一步,我們來看看如何實現動畫,想想看怎麼可以看到運動的小球。我們假設小球是從上往下運動,這也符合物理原理,一個小球自由落體的掉下來,那麼我們試想一下,小球的運動軌跡就是一條垂直於你的窗體下邊界的一條直線,我說過窗體裡其實有一個笛卡爾座標系,所有的點都有對應的座標,既然運動軌跡是一條垂直於窗體下邊界的直線,也就意味著改變的就是Y軸的資料,我告訴你螢幕的左上方是座標原點,向下是正方向的Y座標軸,向右是正方向的X座標軸,這樣一來,那小球下落的軌跡的點的Y值是不斷增加的,搞清楚了原理,我們在想一個問題,怎麼表示小球是自由落體呢,其實很簡單,及時不斷的重畫小球,這種機械的事情人類不擅長做,直接交給電腦去完成。小球的Y座標不斷的增加,每增加一下,都抹去原有的小球,重畫一個。還記得之前將的迴圈嗎?迴圈可以不斷實現增加,至於重畫怎麼實現,其實也簡單,這次案例的最後我會講述。先跟著我看一下下面這段程式碼

//主函式省略,和第一段程式碼的主函式一樣
class MyOval extends Panel{
    public void paint(Graphics g){
        int x=30,y=30;
        while(true){
        g.fillOval(x, y, 20, 20);
        y++;
    }
}
}

現在拉分析一下,這裡展現的是paint方法裡的程式碼,我們現實看見了int x=30,int y=30(也可寫成int x=30,y=30其實是一個意思,只是博主偷懶)這兩句話是用來宣告變數的,又來了一個新詞,到底什麼是變數?變數從字面上理解就是能變化的量,上一段程式碼把小球的座標給寫死了30,30,不能變就沒有辦法動,現在換成了可以改變的x和y。問題是在Java裡,x和y需要事先宣告,說未來我需要一個可以變化的量,叫做x,以後我要在x裡放整數,所以之前加上int,這是必須的,規定好的語法格式,這裡我事先給x一個初值30。
下面是while是迴圈,之前我們看到的是一個叫for的迴圈,如果通常知道需要迴圈多少次,就用for,若是不知道要迴圈多少次,就用while。奇怪了,怎麼就不知道要迴圈多少次呢?舉個例子,老師體罰你去操場跑10圈,這個是知道迴圈次數的,所以就用for迴圈迴圈10次。若老師就讓你一直跑著,你知道你能跑幾圈嗎?所以這個時候就要用while迴圈了,裡面的true是什麼意思呢?就是說你如果還有力氣那隻能一直跑下去。Java裡能用true表示,想法不能就用相反對應的詞false來表示。true和false都是基本資料型別boolean的值,這個型別也就這兩個值,通過boolean值來判斷迴圈是否退出。
這裡的while其實比較特別,一直都是true,也就是說,這個迴圈永遠不會停,除非程式不運行了,我們通常把這樣的迴圈叫做死迴圈,這裡是希望動畫一直繼續下去,大多數情況下是要避免死迴圈的。
在看迴圈裡有一個y++;就是讓y這個變數加1的意思,實際上y++就是y=y+1的意思

使用執行緒

這段程式碼加上主函式我們來執行一下看看結果是什麼?
這裡寫圖片描述
我們看到了一條向下的黑線,這是因為雖然能夠不斷的在新的位置畫圓,但是過去畫的圓並沒有消失啊,這不符合我們的要求啊,有些聰明的人到這兒而去找到了擦除的辦法,也做出來動畫,但是一直有一個問題得不到很好的解決——小球飛行的時候使用者什麼操作都做不了,程式完全被迴圈佔用,要是隔一會兒讓y++一下,才能解決問題,目前最好的辦法是執行緒
什麼執行緒?又來了一個概念,我去翻了一下教科書,書上這麼寫:“執行緒是通過利用CPU的輪轉,讓程式中不同的程式碼同時執行的機制。”唉,反正這就是教科書,就整一些讓人蒙圈的定義。那我就竟讓說的通俗一些讓大家都可以理解。還是要舉一個例子,這個例子是我老師和我說的
大家都知道在城市裡生活人與人之間都比較冷漠,你也常常不知道對門或者隔壁住的是誰,但是有孩子的就不同了,因為你每天都要帶著孩子到樓下玩,這樣就能遇到很多帶孩子的人,孩子在一旁玩,大人無聊起來就會聊天,一來二去聊熟了。假設有一天天不好,於是我提議大家到我家來玩玩,結果來了4個孩子還有他們的家長,這麼多人在一起我得找點兒事兒做,大家提議打麻將 ,可是無奈我不會啊,於是我被分配到看孩子,這樣假設我面前有5個哇哇哭的孩子,我的任務是哄他們不讓他們哭,我試了很多吃的和玩具,只有這個玩具放到孩子手裡他們都不哭了,下一步我就要找5個這樣的玩具,但是問題是我只有一個啊,咋辦?於是我想到了一個辦法打,將玩具給一個寶寶,不哭了之後搶下來,假設寶寶不會立刻哭,然後再給下一個寶寶,這個寶寶不哭之後再搶下來再 給下一個,就這樣不斷的重複著,如果技術高超這樣用一個玩具就能讓5個寶寶都不哭。對照一下定義,玩具是我們的CPU,我們沒有5個COU只有一個,但是我們5個程式都要同時執行,怎麼辦?只裡面叫做輪轉,就是這個CPU輪著給每個CPU用,如果每個程式輪到10毫秒,從巨集觀上看好像每個程式都在執行,其實微觀上來看,每個時刻都只有一個程式在執行。問題是,到目前為止的描述叫做程序,是不同的程式,又是我們希望一個程式裡有兩段並列的程式碼同時執行,也使用類似的機制,不同的是這是一個程式。早看一遍:“執行緒是利用CPU的輪轉,讓程式中不同的程式碼同時執行機制。”其實同時執行是假的,只是人這麼認為。
好了,瞭解了執行緒的道理,我們看Java是怎麼寫執行緒的。在這個階段,我們不深究執行緒的細節,可以使用就好了,我們在MyOval的類裡面加上一個run方法:

class MyOval extends Panel{
  public void paint(Graphics g){
    g.fillOval(30,30,20,20);
 }
  public void run(){
 }
}

這個run就是一個新來的寶寶,CPU在核實的情況下會輪到run那裡,要說這個run也是程式的入口。問題是你寫個run在MyOval這個類裡面,作業系統也不知道啊,你問我為什麼當時paint系統就知道,這是因為Panel裡面就有這個機制,你一繼承Panel,就相當於告訴作業系統,我有重畫機制了,而執行緒不僅僅應用在Panel這個類上,幾乎什麼類都可以包含run,所以需要更新機制,我們在類的後面加上implements Runnable,叫做實現Runnable介面。介面又是一個複雜的概念,這次先記住怎麼寫吧,這樣寫是為了讓系統知道里面有一個run,後續的部落格更新我也會來詳細的介紹介面這個概念。

class MyOval extends Panel implements Runnable{
  public void paint(Graphics g){
    g.fillOval(20,20,20,20);
 }
  public void run(){
 }
}

到這裡還不行,你還要告訴系統,這裡有一個寶寶也要玩具

import java.awt.*;
public class MyBall {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Frame w = new Frame();
        w.setSize(300, 400);

        MyOval mp = new MyOval();
        w.add(mp);

        Thread t  = new Thread(mp);
        t.start();

        w.show();
    }

}

增加的

Thread t  = new Thread(mp);
        t.start();

用了一個新的獨享來包裝這個寶寶,就是t,他是Thread的物件,特別是在new這個Thread物件的時候就放進去mp。mp是什麼?它是MyOval物件的引用,再看看MyOval,裡面有一個run,這樣就關聯起來了,經過這個討論這個討厭的執行緒八股文程式碼,我們終於可以開始了讓小球動起來的程式碼了。
問題是我們不能再run裡面畫圖,一個重要的理由是在run方法裡,訪問不到變數g,因為大括弧限定了變數的範圍,意思是你在一個大括弧開始和結束的範圍內宣告變數,就在這個大括弧裡有效,離開這個大括弧就無效了。這麼設計是有道理的,但是現在我們先不管這個道理是什麼,寫的程式碼多了,就自然而然的會發現這樣設計的好處。現在我們的思路是,把小球的座標改成變數,宣告在paint方法的外面,或者說是MyOval類中,這樣變數就定義成了MyOval類的成員,變數處於類的大括弧中,在整個類的作用域中有效,那麼paint方法和run方法都能使用座標變數,我們在paint方法中取座標值,在run方法裡改變座標變數。
注意下面程式碼的順序發生了變化,因為MyOval越來越長

import java.awt.*;
public class MyBall {

    public static void main(String[] args) {
        // TODO Auto-generated method stub

        Frame w = new Frame();
        w.setSize(300, 400);

        MyOval mp = new MyOval();
        w.add(mp);

        Thread t  = new Thread(mp);
        t.start();

        w.show();
    }

}
class MyOval extends Panel implements Runnable{
    int x=30,y=30;
    public void paint(Graphics g){
        g.fillOval(x, y, 20, 20);
}
    public void run(){
        y++;
    }
}

有些程式碼順序實在沒辦法表達了,比如畫圓的時候不能用x和y,因為這個時候還沒有宣告x和y,而且寫到這裡,你還不知道將座標變成可變的,所以這個地方還用30和30,寫完這個再將座標改成變數,另外實現的Runnable這個介面也是後來才加上去的,原則有兩個:一是一直在寫沒有語法錯誤的程式碼:二是符合正常人的邏輯思維。
現在執行一下,看看小球沒有動吧,沒動,為什麼?明明是y++了,哦!原來y座標雖然變了,但是影象沒有重畫。問題是怎麼重畫影象?要知道重畫是系統的事情,你不能在程式中自己呼叫,Java提供了一個repaint方法,發出repaint()的呼叫,這個請求將傳送回系統,系統見到後便會呼叫paint()方法,還是系統重畫,你發出這個請求。下面我只擷取MyOval類的程式碼,前面的程式碼沒有區別。

class MyOval extends Panel implements Runnable{
 int x=30;
 int y=30;
 public void paint(Graphics g){
 g.fillOval(x,y,20,20);
 }
 public void run(){
 y++;
 repaint();
 }
 }

這下子該有動畫了吧!還沒有?不可能,一定是有動畫了,只是你沒有看出來,小球確實是動了,只不過移動了一個畫素點,肉眼很難識別,然後重畫,執行緒就結束了,要想看到動畫,你得看到動畫,你得讓y座標不斷的向下移動,不斷的重畫,用迴圈。

class MyOval extends Panel implements Runnable{
    int x=30,y=30;
    public void paint(Graphics g){
        g.fillOval(x, y, 20, 20);
}
    public void run(){
        while(true){
        y++;
        repaint();
    }
    }
}

這下看一下結果,是不是連小球都沒了,因為小球目前正飛往地球的另一面,電腦太快了,y座標很快就比視窗的高度還大,我們的y座標超出視窗邊緣的時候,將他拉回視窗的上邊緣。

class MyOval extends Panel implements Runnable{
    int x=30,y=30;
    public void paint(Graphics g){
        g.fillOval(x, y, 20, 20);
}
    public void run(){
        while(true){
        y++;
        if(y>400){
            y=0;
        }
        repaint();
    }
    }
}

這裡寫圖片描述
今天的講解總算也是讓小球動了起來,還算是沒有百忙下面我還有幾點東西要補充
## 執行緒和程序的區別##
程序是一個獨立執行的程式,比如一個正在執行的記事本和一個正在執行的瀏覽器,這是兩個程序,加入我們啟動了兩次記事本,那麼也得到了兩個程序,程式在執行的時候,系統會在記憶體中分配一塊獨立的空間給這個程式,兩個執行的記事本就有兩塊獨立的空間,兒執行緒是在一個程序中,能夠執行兩段程式碼。
即便你不寫多執行緒的程式,Java程式本身就是多執行緒的,main方法被呼叫的時候多執行緒機制早就已經存在,我們將程式所處的執行緒稱為前臺執行緒。在Java中,為前臺執行緒提供服務的後臺執行緒會伴隨著執行起來,所以後臺執行緒也就稱為“守護執行緒”或“精靈執行緒”,JVM的垃圾回收機制就是由後臺執行緒完成的。當前臺執行緒都死亡後,後臺執行緒會自動死亡。
故事的最後還好是要說拜拜,今天的內容就到這裡了,還是要寫個20遍,就算不明白寫得多了自然就懂了