基于Android嵌入式平臺傳感器應用開發(fā)水平儀
摘要:詳細介紹了如何利用Android系統(tǒng)的傳感器開發(fā)水平儀應用的全過程。通過對該案例開發(fā)的講解,介紹了傳感器應用的開發(fā)方法以及通過Eclipse開發(fā)Android應用程序的過程。
1Android平臺簡介
互聯(lián)網(wǎng)巨頭Google公司于2007年11月5日推出了全新的嵌入式軟件平臺---Android,該平臺由操作系統(tǒng)、中間件、用戶界面以及應用軟件組成,是一個真正開放的移動應用開發(fā)平臺。
2007年11月初,Google與其他33家手機廠商、軟硬件供應商、手機芯片供應商、移動運營商聯(lián)合組成了開放手機聯(lián)盟(OpenHandsetAlliance),發(fā)布了名為Android的手機軟件平臺,并宣布該平臺完全開放。同時Google希望Android平臺成為一套標準化、開放式的移動嵌入式軟件平臺。
由于Android系統(tǒng)具有開發(fā)性、平等性、無界性以及方便性等優(yōu)點,所以很快被業(yè)界所接受。從2008年初開始,越來越多的開發(fā)人員投身到Android應用的開發(fā)當中。
而Android系統(tǒng)的一大亮點之一就是傳感器的使用,利用傳感器可以開發(fā)出很多新奇有趣的應用程序。例如計步器、水平儀,甚至在很多游戲中都可以使用傳感器來操作游戲。傳感器的種類有很多種,其中包括加速度傳感器、姿態(tài)傳感器、磁場傳感器、溫度傳感器以及光傳感器等,介紹的水平儀應用就是使用姿態(tài)傳感器的。
2案例功能
將結合水平儀案例的開發(fā)詳細介紹如何在Android平臺下開發(fā)傳感器應用,下面首先對水平儀的功能及界面進行簡單的介紹。
2。1程序界面
程序運行后的效果如圖1所示,用戶可以通過調整手機的姿態(tài)來控制界面中各個氣泡的位置。與真正的水平儀一樣,在使用手機水平儀時,需要將手機平放到某個平面上才可以。
圖1 水平儀應用程序界面
2。2軟件功能 運行該程序,當改變手機的姿態(tài)時,界面中的氣泡便會根據(jù)手機的姿態(tài)向高處進行相應的移動。 當手機所處的平面水平時,各個氣泡都應該位于中間的指定區(qū)域。 3開發(fā)環(huán)境搭建 正式進入代碼開發(fā)之前,首先需要對開發(fā)環(huán)境進行搭建,其搭建步驟如下所列。 (1)安裝Java開發(fā)環(huán)境JDK。 (2)從網(wǎng)上下載Android開發(fā)環(huán)境SDK的壓縮包,并將其解壓到磁盤上的某個位置。 (3)將SDK解壓目錄中的tools目錄添加到系統(tǒng)的PATH環(huán)境變量中。 (4)下載并安裝Eclipse集成開發(fā)環(huán)境。 (5)為Eclipse安裝Android開發(fā)插件ADT,并在Eclipse的Preferences中配置Android插件的SDKLocation。 (6)在Eclipse的AVDManager中創(chuàng)建Android虛擬設備(AVD),并啟動模擬器。 (7)下載并安裝用來調試Android傳感器應用的Sensorsimulator傳感器模擬器軟件。 (8)在模擬器中安裝Sensorsimulator所對應的apk文件并對其進行調試使Sensorsimulator應用程序能夠與Android模擬器進行通信。 4開發(fā)前的準備 前面完成了開發(fā)環(huán)境的搭建,但在正式進行代碼開發(fā)之前,還需要再做一些開發(fā)前的準備工作,其步驟如下: (1)首先啟動之前安裝好Eclipse。 (2)然后依次點擊File|New|Other|Android|AndroidProject進入項目的創(chuàng)建界面。 (3)在項目創(chuàng)建界面中,輸入項目的名稱、所使用的目標平臺、所在的包名等信息,如圖2所示。
圖2 在Eclipse 中創(chuàng)建Android 項目
(4)點擊"Finish"完成項目的創(chuàng)建。 (5)在程序中將會用到的圖片資源存放到項目文件夾的res/drawable-mdpi目錄下,如圖3所示。 圖3 圖片資源 (6)為應用程序引入調試時使用的Sensorsimulator支持jar包,該jar包位于Sensorsimulator安裝目錄中的bin目錄下: 5自定義View的開發(fā) 本案例需要自定義一個View來繪制水平儀的用戶界面,首先需要在項目文件夾的src/wyf/ytl目錄下創(chuàng)建一個名為Main-View的java類,并使其繼承自View類,其代碼框架如下: packagewyf。ytl;//聲明所在包 importandroid。content。Context;//引入Context類 importandroid。graphics。Bitmap;//引入Bitmap類 importandroid。graphics。BitmapFactory;//引入相關類 importandroid。graphics。Canvas;//引入Canvas類 importandroid。graphics。Color;//引入Color類 importandroid。graphics。Paint;//引入Paint類 importandroid。graphics。RectF;//引入RectF類 importandroid。graphics。Paint。Style;//引入Style類 importandroid。util。AttributeSet;//引入AttributeSet類 importandroid。view。View;//引入View類 publicclassMainViewextendsView{ Paintpaint=newPaint();//畫筆 //圖片資源的聲明 BitmapshangBitmap1;//上面的大矩形圖 BitmapshangBitmap2;//上面的氣泡 BitmapzuoBitmap1;//左面的大矩形圖 BitmapzuoBitmap2;//左面圖的氣泡 BitmapzhongBitmap1;//中間的大圓圖 BitmapzhongBitmap2;//中間的小氣泡 BitmapxiaBitmap1;//右下的矩形圖[!--empirenews.page--] BitmapxiaBitmap2;//右下的氣泡 //背景矩形的位置聲明 intshang1_X=60;//上面的大矩形圖 intshang1_Y=12; intzuo1_X=12;//左面的大矩形圖 intzuo1_Y=60; intzhong1_X=65;//中間的大圓圖 intzhong1_Y=65; intxia1_X=145;//右下的矩形圖 intxia1_Y=145;//水泡的位置聲明 intshang2_X;//上面的氣泡XY坐標 intshang2_Y; intzuo2_X;//左面圖的氣泡XY坐標 intzuo2_Y; intzhong2_X;//中間的小氣泡XY坐標 intzhong2_Y; intxia2_X;//右下的氣泡XY坐標 intxia2_Y; publicMainView(Contextcontext,AttributeSetattrs){ super(context,attrs); initBitmap();//初始化圖片資源 initLocation();//初始化氣泡的位置 } privatevoidinitBitmap(){//初始化圖片的方法 …//該處省略了部分代碼,將在后面進行介紹 } privatevoidinitLocation(){//初始化氣泡位置的方法 …//該處省略了部分代碼,將在后面進行介紹 } @Override protectedvoidonDraw(Canvascanvas){//重寫的繪制方法 super。onDraw(canvas); …//該處省略了部分代碼,將在后面進行介紹 } } 上述代碼中的initBitmap以及initLocation方法是對界面進行初始化方法,而onDraw方法會根據(jù)需要繪制整個界面。 MainView類構造器中調用了兩個單獨的方法對整個界面進行了初始化,這是一種非常好的編程習慣。因為把不同功能的代碼各自編寫成獨立的方法可以使主邏輯清晰,且各個方法中的代碼都不是很長,會大大提高代碼的可讀性以及可維護性。 完成了代碼框架的開發(fā)后就可以對其中各個方法進行開發(fā)了,首先開發(fā)的是圖片資源的初始化方法,其代碼如下: privatevoidinitBitmap(){//初始化圖片資源的方法 shangBitmap1=BitmapFactory。decodeResource(getResources(),R。drawable。shang1); shangBitmap2=BitmapFactory。decodeResource(getResources(),R。drawable。shang2); zuoBitmap1=BitmapFactory。decodeResource(getResources(),R。drawable。zuo1); zuoBitmap2=BitmapFactory。decodeResource(getResources(),R。drawable。zuo2); zhongBitmap1=BitmapFactory。decodeResource(getResources(),R。drawable。zhong1); zhongBitmap2=BitmapFactory。decodeResource(getResources(),R。drawable。zhong2); xiaBitmap1=BitmapFactory。decodeResource(getResources(),R。drawable。xia1); xiaBitmap2=BitmapFactory。decodeResource(getResources(),R。drawable。xia2); }
上述代碼為initBitmap方法的全部代碼,其作用是對程序中所有的圖片資源進行初始化,在開發(fā)該方法之前,應該確保所有的圖片資源已經(jīng)存放到了指定的目錄下。
完成了圖片資源初始化方法的開發(fā)后,便可對氣泡位置初始化方法initLocation進行開發(fā)了,其代碼如下:
privatevoidinitLocation(){//初始化氣泡位置的方法
shang2_X=shang1_X+shangBitmap1。getWidth()/2-shangBitmap2。getWidth()/2;
shang2_Y=shang1_Y+shangBitmap1。getHeight()/2-shangBitmap2。getHeight()/2;
zuo2_X=zuo1_X+zuoBitmap1。getWidth()/2-zuoBitmap2。getWidth()/2;
zuo2_Y=zuo1_Y+zuoBitmap1。getHeight()/2-zuoBitmap2。getHeight()/2;
zhong2_X=zhong1_X+zhongBitmap1。getWidth()/2-zhongBitmap2。getWidth()/2;
zhong2_Y=zhong1_Y+zhongBitmap1。getHeight()/2-zhongBitmap2。getHeight()/2;
xia2_X=xia1_X+xiaBitmap1。getWidth()/2-xiaBitmap2。getWidth()/2;
xia2_Y=xia1_Y+xiaBitmap1。getHeight()/2-xiaBitmap2。getHeight()/2;
}
在該方法中通過相應圖片的寬度和高度動態(tài)計算氣泡的初始坐標,采用此方法動態(tài)計算氣泡坐標的好處是當日后更改圖片資源后,不需要重寫修改源代碼即可正常運行,大大提高了程序的可維護性。
在完成了各個初始化方法的開發(fā)后就可以對繪制方法onDraw進行開發(fā),該方法主要負責界面的繪制工作,其代碼如下:
@Override
protectedvoidonDraw(Canvascanvas){//界面繪制方法super。onDraw(canvas);
canvas。drawColor(Color。WHITE);//設置背景色為白色
paint。setColor(Color。BLUE);//設置畫筆顏色
paint。setStyle(Style。STROKE);//設置畫筆為不填充
canvas。drawRect(5,5,315,315,paint);//繪制外邊框矩形
//畫背景矩形
canvas。drawBitmap(shangBitmap1,shang1_X,shang1_Y,paint);//上
canvas。drawBitmap(zuoBitmap1,zuo1_X,zuo1_Y,paint);//左
canvas。drawBitmap(zhongBitmap1,zhong1_X,zhong1_Y,paint);//中
canvas。drawBitmap(xiaBitmap1,xia1_X,xia1_Y,paint);//下
//開始繪制氣泡
canvas。drawBitmap(shangBitmap2,shang2_X,shang2_Y,paint);//上
canvas。drawBitmap(zuoBitmap2,zuo2_X,zuo2_Y,paint);//左
canvas。drawBitmap(zhongBitmap2,zhong2_X,zhong2_Y,paint);//中
canvas。drawBitmap(xiaBitmap2,xia2_X,xia2_Y,paint);//下
paint。setColor(Color。GRAY);//設置畫筆顏色用來繪制刻度
//繪制上面方框中的刻度
canvas。drawLine(shang1_X+shangBitmap1。getWidth()/2-7,shang1_Y,shang1_X+shangBitmap1。getWidth()/2-7,shang1_Y+shangBitmap1。getHeight()-2,paint);
canvas。drawLine(shang1_X+shangBitmap1。getWidth()/2+7,shang1_Y,shang1_X+shangBitmap1。getWidth()/2+7,shang1_Y+shangBitmap1。getHeight()-2,paint);[!--empirenews.page--]
//繪制左面方框中的刻度
canvas。drawLine(zuo1_X,zuo1_Y+zuoBitmap1。getHeight()/2-7,zuo1_X+zuoBitmap1。getWidth()-2,zuo1_Y+zuoBitmap1。getHeight()/2-7,paint);canvas。drawLine(zuo1_X,zuo1_Y+zuoBitmap1。getHeight()/2+7,zuo1_X+zuoBitmap1。getWidth()-2,zuo1_Y+zuoBitmap1。getHeight()/2+7,paint);
//繪制下面方框中的刻度
canvas。drawLine(xia1_X+xiaBitmap1。getWidth()/2-10,xia1_Y+xiaBitmap1。getHeight()/2-20,xia1_X+xiaBitmap1。getWidth()/2+20,xia1_Y+xiaBitmap1。getHeight()/2+10,paint);
canvas。drawLine(xia1_X+xiaBitmap1。getWidth()/2-20,xia1_Y+xiaBitmap1。getHeight()/2-10,xia1_X+xiaBitmap1。getWidth()/2+10,xia1_Y+xiaBitmap1。getHeight()/2+20,paint);
//中間圓圈中的刻度(小圓)
RectFoval=newRectF(zhong1_X+zhongBitmap1。getWidth()/2-10,zhong1_Y+zhongBitmap1。getHeight()/2-10,zhong1_X+zhongBitmap1。getWidth()/2+10,zhong1_Y+zhongBitmap1。getHeight()/2+10);
canvas。drawOval(oval,paint);//繪制基準線(圓)
}
在該方法中,根據(jù)相應圖片的X、Y坐標將圖片繪制到屏幕中,在圖片的繪制過程中,同樣動態(tài)根據(jù)相應圖片的寬和高計算需要繪制到的位置坐標,以提高程序的可維護性與靈活性。
6相關XML文件的編寫
完成了用于顯示水平儀界面的自定義View的Java代碼開發(fā)之后,就應該對布局XML資源文件進行編寫,以將之前開發(fā)的自定義View添加到用戶界面中。打開項目中res/layout目錄下的main。xml,在其中編寫如下的xml代碼:
<?xmlversion="1。0"encoding="utf-8"?><!--編碼格式-->
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"><!--線性布局-->
android:id="@+id/mainView"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/><!--自定義
View-->
</LinearLayout>
編寫完布局文件main。xml后,還需要開發(fā)字符串資源文件strings。xml。打開res/values下的strings。xml文件,編寫如下的代碼: <?xmlversion="1。0"encoding="utf-8"?><!--編碼方式--> 水平儀 在該文件中只是對字符串a(chǎn)pp_name進行了定義,在開發(fā)Android應用程序時,將字符串資源統(tǒng)一定義到一個xml文件中是一個很好的編程習慣。 編寫完上述的xml資源文件后,為了調試還需要為此應用程序添加網(wǎng)絡權限,打開項目根目錄下的AndroidManifest。xml文件,在""標簽之前加入下列代碼: 上述代碼的功能為此應用程序添加了訪問網(wǎng)絡的權限。 7Activity類的開發(fā) 完成了自定義View以及XML文件的開發(fā)后,就可以對用戶界面對應的Activity類進行開發(fā),首先開發(fā)該類的代碼框架,其代碼如下: packagewyf。ytl;//聲明所在包 importandroid。app。Activity;//引入相關類 importandroid。hardware。SensorListener; importandroid。hardware。SensorManager; importandroid。os。Bundle; publicclassSPYActivityextendsActivity{//繼承ActivityMainViewmv;//主View intk=45;//靈敏度 //SensorManagermySensorManager; //真機 SensorManagerSimulatormySensorManager;//測試時@Override publicvoidonCreate(BundlesavedInstanceState){super。onCreate(savedInstanceState); setContentView(R。layout。main);//設置當前用戶界面 mv=(MainView)findViewById(R。id。mainView); mySensorManager=SensorManagerSimulator。getSystemService(this,SENSOR_SERVICE);//測試時 mySensorManager。connectSimulator();//測試時 //mySensorManager=(SensorManager) //getSystemService(SENSOR_SERVICE);//真機 } privatefinalSensorListenermSensorLisener=newSensorListener(){//傳感器監(jiān)聽 //器類 …//該處省略了部分代碼,將在后面進行介紹 }; @Override protectedvoidonResume(){//添加監(jiān)聽 mySensorManager。registerListener(mSensorLisener,SensorManager。SENSOR_ORIENTATION); super。onResume(); } @Override protectedvoidonPause(){//取消監(jiān)聽 mySensorManager。unregisterListener(mSensorLisener); super。onPause(); } } 上述代碼中除了重寫了onCreate方法外,還重寫了onRe-sume以及onPause方法為mySensorManager添加或刪除監(jiān)聽,并且定義了傳感器監(jiān)聽器類mSensorLisener。 在完成了Activity類代碼框架的開發(fā)后就可以對其中傳感器的監(jiān)聽類進行開發(fā),首先給出監(jiān)聽器類的代碼框架: privatefinalSensorListenermSensorLisener= newSensorListener(){//傳感器監(jiān)聽器類 publicvoidonSensorChanged(intsensor,float[]values){…//該處省略了部分代碼,將在后面進行介紹 } @Override publicvoidonAccuracyChanged(intsensor,intaccuracy){} publicbooleanisContain(intx,inty){//判斷點是否在圓內 inttempx=(int)(x+mv。zhongBitmap2。getWidth()/2。0); inttempy=(int)(y+mv。zhongBitmap2。getWidth()/2。0); intox=(int)(mv。zhong1_X+mv。zhongBitmap1。getWidth()/2。0); intoy=(int)(mv。zhong1_X+mv。zhongBitmap1。getWidth()/2。0); if(Math。sqrt((tempx-ox)*(tempx-ox)+(tempy-oy)*(tempy-oy))>(mv。zhongBitmap1。getWidth()/2。0-mv。zhongBitmap2。getWidth()/2。0)){//不在圓內returnfalse; }else{//在圓內時 returntrue; } } }; 在傳感器監(jiān)聽類中,onSensorChanged方法用于監(jiān)聽傳感器采樣值的變化,例如手機姿態(tài)的改變等。上述代碼中的is-Contain方法用于判斷界面中間的氣泡是否出界,若出界則返回false。 完成了代碼框架的開發(fā)后,便可以對傳感器的監(jiān)聽方法onSensorChanged進行開發(fā)了,其詳細代碼如下: publicvoidonSensorChanged(intsensor,float[]values){ if(sensor==SensorManager。SENSOR_ORIENTATION){ doublepitch=values[SensorManager。DATA_Y]; doubleroll=values[SensorManager。DATA_Z]; intx=0;inty=0;//臨時變量,算中間水泡坐標時用 inttempX=0;inttempY=0;//下面氣泡的臨時變量 //開始調整x的值 if(Math。abs(roll)<=k){ mv。shang2_X=mv。shang1_X//上面的 +(int)(((mv。shangBitmap1。getWidth() -mv。shangBitmap2。getWidth())/2。0) -(((mv。shangBitmap1。getWidth() -mv。shangBitmap2。getWidth())/2。0)*roll)/k); x=mv。zhong1_X//中間的 +(int)(((mv。zhongBitmap1。getWidth() -mv。zhongBitmap2。getWidth())/2。0) -(((mv。zhongBitmap1。getWidth() -mv。zhongBitmap2。getWidth())/2。0)*roll)/k); }elseif(roll>k){ mv。shang2_X=mv。shang1_X;x=mv。zhong1_X; }else{ mv。shang2_X=mv。shang1_X+ mv。shangBitmap1。getWidth() -mv。shangBitmap2。getWidth(); x=mv。zhong1_X+mv。zhongBitmap1。getWidth() -mv。zhongBitmap2。getWidth(); } //開始調整y的值 if(Math。abs(pitch)<=k){ mv。zuo2_Y=mv。zuo1_Y//左面的 +(int)(((mv。zuoBitmap1。getHeight() -mv。zuoBitmap2。getHeight())/2。0) +(((mv。zuoBitmap1。getHeight() -mv。zuoBitmap2。getHeight())/2。0)*pitch)/k); y=mv。zhong1_Y+//中間的 (int)(((mv。zhongBitmap1。getHeight() -mv。zhongBitmap2。getHeight())/2。0) +(((mv。zhongBitmap1。getHeight() -mv。zhongBitmap2。getHeight())/2。0)*pitch)/k); }elseif(pitch>k){ mv。zuo2_Y=mv。zuo1_Y +mv。zuoBitmap1。getHeight() -mv。zuoBitmap2。getHeight(); y=mv。zhong1_Y+mv。zhongBitmap1。getHeight() -mv。zhongBitmap2。getHeight(); }else{ mv。zuo2_Y=mv。zuo1_Y;y=mv。zhong1_Y; } //下面的 tempX=-(int)(((mv。xiaBitmap1。getWidth()/2-28)*roll +(mv。xiaBitmap1。getWidth()/2-28)*pitch)/k); tempY=-(int)((-(mv。xiaBitmap1。getWidth()/2-28)*roll -(mv。xiaBitmap1。getWidth()/2-28)*pitch)/k); //限制下面的氣泡范圍 if(tempY>mv。xiaBitmap1。getHeight()/2-28){ tempY=mv。xiaBitmap1。getHeight()/2-28; } if(tempY<-mv。xiaBitmap1。getHeight()/2+28){ tempY=-mv。xiaBitmap1。getHeight()/2+28; } if(tempX>mv。xiaBitmap1。getWidth()/2-28){ tempX=mv。xiaBitmap1。getWidth()/2-28; } if(tempX<-mv。xiaBitmap1。getWidth()/2+28){ tempX=-mv。xiaBitmap1。getWidth()/2+28; } mv。xia2_X=tempX+mv。xia1_X +mv。xiaBitmap1。getWidth()/2 -mv。xiaBitmap2。getWidth()/2; mv。xia2_Y=tempY+mv。xia1_Y +mv。xiaBitmap1。getHeight()/2 -mv。xiaBitmap2。getWidth()/2; if(isContain(x,y)){//中間的水泡在圓內才改變坐標 mv。zhong2_X=x;mv。zhong2_Y=y; } mv。postInvalidate();//重繪MainView } } 在onSensorChanged方法中首先得到pitch軸以及roll軸的數(shù)值,然后根據(jù)該數(shù)值的大小調整水泡在屏幕中的位置,同時需要對水泡的坐標進行判斷,使其保持在自身所在外框的范圍內。 此時運行該程序,并保證測試工具Sensorsimulator與Android模擬器的連通,便會觀察到如圖1所示的效果,通過Sensorsimulator工具模擬手機的姿態(tài)的改變,屏幕中的水泡便隨之向高處運動。 8程序發(fā)布 完成了所有代碼的開發(fā)后,就可以將應用程序打包發(fā)布了。本案例中只需將Eclipse工具自動生成的apk文件拷出即可,按如下步驟操作。 (1)進行正式發(fā)布之前首先需要將代碼中注釋為"測試時使用"的兩處代碼刪掉,并將注釋為"真機使用"代碼的注釋去掉。 (2)完成代碼的修改后重新構建項目。 (3)打開項目文件夾下的bin目錄,其中名為SPY的apk文件便為本應用程序的安裝包。 (4)將SPY。apk文件拷貝到支持傳感器的Android手機中運行即可完成本應用程序的安裝。 9結語 通過開發(fā)基于Android平臺的傳感器應用---水平儀程序,讀者應該對Android程序的開發(fā)有了一定的了解,同時讀者也應該了解到在Android平臺下使用傳感器來豐富自己軟件的功能是十分方便的。[!--empirenews.page--] 另外,本案例雖然只對姿態(tài)傳感器進行了應用,但相信通過對本案例的學習,讀者已經(jīng)有能力對其他傳感器進行應用,開發(fā)出更具新意的吸引人的其他應用程序。