安卓開發(3)—1— Activity

3.1 Activity是什麼:

在前面安卓概述中有提到,Activity是Android開發中的四大元件,所有在app裡可以看到的東西都是Activity裡面的,Activity主要是用來和使用者直接進行互動的。

3.2 Activity的基本用法

最開始建立的HelloAndroid採用的是IDE自帶的MainActivy,這次直接建立一個什麼都沒有的Android專案來方便學習:

其它的配置參考:https://www.cnblogs.com/Sna1lGo/p/14823681.html 該部落格。

3.2.1 手動建立Activity

採用了Empty Activity建立專案後,app/src/main/java/包名/ 資料夾是空的目錄:

所有就需要我們手動來新增Activity元件,右鍵包名,新建一個Empty Activity:

勾選Generate Layout File會自動為該Activity建立一個對應的佈局檔案。勾選Launcher Activity表示會為專案開啟向下相容舊版系統的模式,這個需要勾上不然相容不了。這裡為了學習,所以就不勾選Generate a Layout File ,自己手動建立佈局檔案。

在Android的專案中任何一個Activity檔案都應該重寫 onCreate函式,預設建立生成的onCreate函式很簡單,就是呼叫了父類的onCreate函式而已。

3.2.2 建立和載入佈局檔案

Android程式講究的是邏輯和檢視分離,最好每一個Activity都有一個佈局檔案對應,佈局檔案就是資原始檔裡面用來顯示內容的檔案。

手動建立一個佈局檔案:右鍵app/src/main/res目錄 選擇New然後選擇目錄,先建立一個名叫layout的目錄,然後再右鍵layout目錄,建立一個layout xml File檔案,將其按下圖配置:

該layout檔案可以檢視多種格式,Code就是XML原始碼模式,Split就是xml和視覺化模式一樣一半,Design就是視覺化設計模式。

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

</LinearLayout>

現在通過XML程式碼模式新增一個Button控制元件給佈局檔案:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <Button
      android:id="@+id/button1"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="Button 1"
      />
</LinearLayout>

可以通過旁邊的desigh看到預覽視角,添加了一個名為Button 1的按鈕,接下來講一下這段xml程式碼:

    <Button
      android:id="@+id/button1"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="Button 1"
      />
     
第一個:android:id="@+id/button1"
  @+id 這種用法比較少見,但是先不看加號 @id/button1不就是xml中引用資源的用法嗎,只不過把string換成了id而已。
  這裡的@+id就是定義一個id資源的用法,如果要定義一個id資源就需要採用 @+id/id_name這種用法。
 
第二個:android:layout_width="match_parent"
這裡指定了佈局檔案的寬度,match_parent表示和父類的寬度一樣。(寬度也就是橫著的長度)

第三個:android:layout_height="wrap_content"
指定了佈局檔案的高度,wrap_content表明和父類的高度一樣。(高度也就是豎著的長度)

第四個:android:text="Button 1"
指定了佈局檔案的文字內容。

Activity檔案建立了,layout資源佈局檔案也建立了,接下來要做的就是把Activity和layout佈局檔案結合在一起:

        setContentView(R.layout.first_layout)
      這裡呼叫了一個setContentView函式,來給該Activity載入一個佈局檔案,而setContentView一般的呼叫是給它傳一個佈局檔案的id。
      其中安卓(1)裡面講了在Android專案中呼叫資源的方式:
      可以看到這裡定義了一個應用程式名字的字串,有兩種方式可以拿來引用它:

1:在程式碼裡面通過R.string.app_name可以獲得該字串的引用

2:在X M L中通過 @string/app_name可以獲得該字串的引用

其中的string部分是可以替換的,如果用的是圖片資源就可以替換成drawable,如果是應用圖示就可以替換成mipmap,佈局檔案就可以替換成layout
這裡因為我們在res資原始檔中有定義layout下的first_layout檔案,所以就可以直接呼叫了。

3.2.3 在AndroidManifest檔案中註冊

所有的Activity元件都需要在AndroidMainifest檔案中註冊才能生效,實際上這裡通過前面的流程,FirstActivity已經被註冊了:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.example.helloworld3">

  <application
      android:allowBackup="true"
      android:icon="@mipmap/ic_launcher"
      android:label="@string/app_name"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:supportsRtl="true"
      android:theme="@style/Theme.HelloWorld3">
      <activity android:name=".FirstActivity"></activity>
  </application>

</manifest>

當自動建立元件的時候Android Studio就會自動新增AndroidManifest.xml檔案,來防止程式允許崩潰,也是AS的一種人性化。

在<activity>標籤中,使用了android:name來指定具體註冊哪一個Activity,那麼這裡的.FirstActivity是什麼呢,其實就是com.example.helloworld3.FirstActivity的縮寫,就是com.example.hellworld3這個專案下的包名的Activity元件的名字:

其實這也是因為在該AndroidManifest.xml檔案中前面的包(package)指定了是com.example.helloworld3,所以後面才可以這樣寫。

這樣註冊了Activity後還不行,因為還需要指定主Activity,就好比在C語言中指定main函式一樣。要指定主Activity直接在AndroidMainifest中的<activity>標籤中新增<intent-filter>標籤就好了,並在該intent-filter標籤中新增兩行程式碼:<action android:name="android.intent.action.MAIN"/><category android:name="android.intent.category.LAUNCHER" /> 就好了。還可以新增android:label來指定Activity標題欄中的內容。需要注意的是,給主Activity指定label不僅會成為標題欄中的內容,還會成為啟動器(launcher)中應用程式顯示的內容。

修改後的AndroidManifest:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="com.example.helloworld3">

  <application
      android:allowBackup="true"
      android:icon="@mipmap/ic_launcher"
      android:label="@string/app_name"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:supportsRtl="true"
      android:theme="@style/Theme.HelloWorld3">
      <activity android:name=".FirstActivity"
          android:label="This is FirstActivity">
          <intent-filter>
              <action android:name="android.intent.action.MAIN"/>
              <category android:name="android.intent.category.LAUNCHER" />
          </intent-filter>


      </activity>
  </application>

</manifest>

這樣該Activity就是這個程式的主Activity,點選開啟APP時就是首先開啟該Activity就和main函式一樣,啟動C程式首先啟動main函式。

這樣就實現了一個簡單的Activity了:

總結如何建立Activity

Android專案講究的是將邏輯和檢視分離,所以要建立一個Activity不僅需要新增程式碼邏輯,還需要添加布局資源文,在 app/src/main/java/包名 目錄下新建Activity程式碼邏輯,然後再在layout中新增Activity佈局資源,最後再再AndroidManifest中檢查是否有註冊該Activity(一般都會自動註冊)。要使用佈局檔案的什麼內容就在在 app/src/main/java/包名 目錄下新建Activity程式碼邏輯中使用,要讓Activity展示什麼內容就在Activity的資源佈局檔案的XML中修改。

3.2.4 在Activity中使用Toast

Toast是Android提供的一種提醒方式,用來在程式中給使用者通知,在一段時間後會自動消失,且不會佔用螢幕任何資源。

要建立Toast,首先需要定義一個彈出Toast的觸發點,正好我們剛剛設計的Activity有一個button按鈕,就讓這個按鈕的點選事件作為彈出Toast的出發點。在該Activity的onCreate中新增以下程式碼:

var button1: Button = findViewById(R.id.button1)
button1.setOnClickListener {
Toast.makeText(this,"You clicked Button 1",Toast.LENGTH_SHORT).show()
  }
var button1: Button = findViewById(R.id.button1)
//通過findViewById該API函式來獲取在佈局檔案中定義的元素
//這裡通過前面在定義佈局檔案中定義的ID來引入獲取得佈局檔案中的按鈕的例項 button1就是對應的button例項
//findViewById函式返回的是一個繼承自View的泛型物件,所以kotlin沒有辦法自動推導它是Button還是其它控制元件
//所以需要顯示地將button1這個變數宣告稱Button型別。
    button1.setOnClickListener {
Toast.makeText(this,"You clicked Button 1",Toast.LENGTH_SHORT).show()
  }
//呼叫setOnClickListener()方法給這個button按鈕註冊一個監聽器,當被OnClick(被點選)的時候就會啟動
//該監聽器中使用了Toast.makeText()函式,Toast剛剛介紹過了,就是在APP中彈出內容的一個類
//然後該makeText()函式就是給該Toast修改內容,然後.show()函式,就是展示該Toast
//makeText()中有三個引數,第一個引數要傳的內容是一個context,由於Activity本身也是一個Context物件
//這裡可以直接將該Activity傳進去,第二個引數是Toast顯示的文字內容,第三個引數是Toast顯示的時長
//第三個引數有兩個巨集定義可以選:Toast.LENGTH_SHORT和Toast.LENGTH_LONG

3.2.5 在Activity中使用Menu

選單這個內容也是一個很常見的內容。

首先在res目錄下新建一個menu資料夾,來存放選單資源。接著建立一個名為main的Menu原始碼檔案:

然後在該新建的main.xml選單原始檔中新增以下程式碼:

    <item
      android:id="@+id/add_item"
      android:title="Add"/>
  <item
      android:id="@+id/remove_item"
      android:title="Remove"/>

這裡建立了兩個選單項,其中<item>這個標籤是用來建立具體的某一個選單項,然後通過android:id來給該選單項指定唯一的一個識別符號,通過android:title來給該選單項指定名稱。

然後,還需要在Activity中重寫onCreateOptionsMenu()該函式,因為該函式涉及選單和Activity的聯絡:

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
      menuInflater.inflate(R.menu.main,menu);
      return true
  }

在講解這段程式碼的時候,需要先講解一個關於Kotlin語法糖的知識。Java Bean是一個很簡單的類:

public class Book
{
private int pages;

public int getPages()
{
return pages;
}
public void setPages(int pages)
{
this.pages = pages
}
}

在Kotlin中呼叫該語法結構的Java程式碼時有一種非常簡便的辦法:

val book = Book()
book.pages = 500
val bookpage = book.pages

看起來是在瞎JB寫,但是其實它是在後面自動呼叫了Book類的getPages和setPages函式。

這樣類比到剛剛的Menu程式碼裡面:

 menuInflater.inflate(R.menu.main,menu)
//這裡實際上就是呼叫了getMenuInflater()方法得到了menuInflater物件,再呼叫它的inflate方法
//inflate方法有兩個引數:第一個引數表明通過哪一個Menu資原始檔來建立選單
//第二個引數用來指定選單項新增到哪一個Menu物件中,這裡使用該Activity中重寫的onCreateOptionsMenu
//傳入的menu引數,表示新增到該Activity預設的選單中
//如果該函式返回的是true表明會將新建的選單顯示出來
//如果返回的是false表明不會將新建的選單顯示出來

當然光顯示出來是不夠的,還需要給選單新增響應,這樣才可以完美配合。在Activity中重寫onOptionsItemSelected()方法:

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
      when(item.itemId)
      {
          R.id.add_item -> Toast.makeText(this,"You Clicked Add",Toast.LENGTH_LONG).show()
          R.id.remove_item -> Toast.makeText(this,"You Clicked Remove",Toast.LENGTH_LONG).show()
      }

      return true
  }

這樣生成的APP右上角就會有一個三個點的按鈕,這個按鈕就是選單按鈕,點開就是我們寫的Add和Remove內容,單機選單的內容,會彈出Toast訊息。

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
      when(item.itemId)
      {
          R.id.add_item -> Toast.makeText(this,"You Clicked Add",Toast.LENGTH_LONG).show()
          R.id.remove_item -> Toast.makeText(this,"You Clicked Remove",Toast.LENGTH_LONG).show()
      }

      return true
  }

這段程式碼,通過區分item的itemId來分辨是選擇了選單的哪一個選項,ItemId是前面在res資原始檔中的menu中定義的選單資源,然後就通過Toast來達到響應的提示內容。

3.2.6 銷燬一個Activity

前面講述了手動建立Activity,現在要學習怎麼銷燬一個Activity,呼叫一個內建的API,finish()就好了。

這裡我們把button按鍵響應的按鈕監聽器程式碼改一下,呼叫一下一個API就好了:

這樣再單擊button1這個按鈕,就會自動銷燬Activity。

3.2.0 總結

Android專案講究的是邏輯和檢視分離,程式碼邏輯主要是在Activity中,檢視則是在res資源中,通過在res資源中定義的一些識別符號,可以直接在程式碼邏輯中使用來一一對應確定。

3.3 使用intent在Activity之間穿梭

一個完整的Android專案是會在多個Activity之間交換,這一節就是分析如何切換Activity。

3.3.1 使用顯示Intent

新建一個Activity檔案,這次勾選上Generate Layout File,自動配置佈局檔案,但不要勾選:

AS會自動幫我們生成一些必要檔案:

只不過自動生成的layout檔案有點看不懂:

將其替換為和第一個自己手動建立的Activity原始碼類似的框架:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <Button
      android:id="@+id/button2"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="Button 2"
      />
</LinearLayout>

該程式碼還是定義了一個Button按鈕,只不過這次改名為Button2了,Activity的程式碼也自動幫我們生成了,暫時也不用改吧,然後還有建立Activity的最後一步,就是在AndroidManifest檔案中註冊該Activity(通常AS會自動幫忙註冊:

這樣一個新的Activity就建立完成了,其實還是挺簡單的,按照流程來就好了。

這個新建的Activity並不是一個主Activity所以也沒有必要新增intent-filter標籤來修飾,那麼如何來啟動這個Activity呢,就要用到Android裡面的一個新的概念:Intent

Intent:是Android中的各種元件之間進行互動的一種重要方式,不僅可以用來指明動作,還可以用來傳遞資料。Intent一般用來啟動Activity,啟動Service以及傳送廣播等場景(這裡先專注於啟動Activity)。

Intent一般分為兩種:顯示Intent和隱式Intent。

先分析顯示Intent:Intent有多個鉤子函式的過載,其中一個是Intent(Context packageContext,Class<?>cls),第一個引數Context要求提供一個啟動Activity的上下文,第二個引數class用於指定想要啟動的目標Activity,通過這個建構函式就可以構建出Intent,那麼如何啟動這個intent呢,Activity類中提供了一個startActivity()方法專門用來啟動Activity,它接受一個Intent引數,這裡將構建好的intent傳入到startActivity()函式中,就可以啟動目標Activity了。

//修改FirstActivity中的button點選事件來實現:
      var button1: Button = findViewById(R.id.button1)
      button1.setOnClickListener {
          val intent = Intent(this,SecondActivity::class.java)
          startActivity(intent)
      }
//這裡首先新建了一個Intent物件,第一個引數傳入this,也就是把FirstActivity作為上下文
//第二個引數傳入了SecondActivity::class.java作為啟動目標的activity
//在Kotlin中的SecondActivity::class.java就和java裡面的SecondActivity.class的寫法一樣
//接下來再通過這個startActivity()來執行這個Intent就好了。

總結:建立一個intent來指定下一個Activity,然後再通過API呼叫Intent就可以跳轉到下一個Activity。

3.3.2 隱式呼叫Intent

隱式呼叫比顯示呼叫要麻煩很多,採用一些列的action和category等資訊來呼叫,然後由系統去分析這個Intent,並找出合適的Activity來啟動,系統會自動找出可以響應該隱士Intent的Activity來呼叫,通過在<active>標籤下配置<intent-filter>的內容,可以指定當前的Activity能夠響應的action和category:

        <activity android:name=".SecondActivity">
          <intent-filter>
              <action android:name="com.example.activitytest.ACTION_START"/>
              <category android:name="android.intent.category.DEFAULT"/>
          </intent-filter>
      </activity>

在這個<action>標籤中我們指明瞭當前Activity中可以響應com.example.activitytest.ACTION_START這個action,而<category>這個標籤中則附加了一些附加資訊,更精確地指明瞭當前Activity能夠響應的Intent中還可能帶有的category,只有action和category中的內容同時匹配Intent中的內容時,這個Activity才能響應該Intent。

修改FirstActivity中按鈕的點選事件:

        var button1: Button = findViewById(R.id.button1)
      button1.setOnClickListener {
          val intent = Intent("com.example.activitytest.ACTION_START")
          startActivity(intent)
      }

這裡使用了Intent的另一個建構函式,直接將action傳了進來,而category有一個預設值:android.intent.category.DEFAULT,所以這裡就可以不用傳參了。每一個Intent中只能指定一個action,但能指定多個category,當這個Intent觸發時,隨之繫結的都會觸發。

3.3.3 更多的隱式呼叫Intent用法

使用隱式Intent不僅可以啟動自己程式內的Activity,還可以啟動其它程式的Activity。

修改FirstActivity中按鈕點選事件的程式碼:

        var button1: Button = findViewById(R.id.button1)
      button1.setOnClickListener {
          val intent = Intent(Intent.ACTION_VIEW)
          intent.data = Uri.parse("https://www.baidu.com")
          startActivity(intent)
      }

這裡指定了Intent的action是Intent.ACTION_VIEW這是android系統內建的動作,它的常量值為android.intent.action.VIEW,然後通過Uri.parse()函式,將一個網址解析為uri物件,再呼叫intent的setData()函式將這個uri物件傳遞進去。最後的內建API:startActivity函式就是直接呼叫了這個intent對應的Activity。

這樣再單擊按鈕button1就會直接進入系統內建的瀏覽器並且訪問前面輸入的url網站了。

還可以在AndroidManifest檔案中的<intent-filter>標籤中再配置一個<data>標籤,用於更精確地指定當前Activity能夠響應的資料。

<data>可以配置一下資料:只有當<data>標籤中指定的內容和Intent中攜帶的Data內容完全一致時,才能響應對應的Intent。

   
android:scheme 用於指定資料的協議部分,就好比前面的url的https這一部分
android:host 用於指定資料的主機名部分:好比前面那個的www.baidu.com
android:port 用於指定資料的埠部分,一般緊隨在主機名後面
android:path 用於指定域名後面的部分
android:mimeType 用於指定可以處理的資料型別,允許使用萬用字元的方式來指定。

比如前面的響應瀏覽器訪問百度,如果把android:scheme指定為https那麼就只能響應https協議的Intent了。

例如:這裡我們新建一個Activity來響應網頁的intent。

1 新建一個Activity叫ThirdActivity,再新建一個layout佈局檔案叫third_layout :

然後修改third_laytout檔案內容為以下程式碼:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:orientation="vertical"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <Button
      android:id="@+id/button3"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:text="button3"
      />
</LinearLayout>

ThirdActivity的程式碼可以不用更改,最後再在AndroidManifest.xml中修改ThirdActivty的註冊資訊:

程式碼講解

這裡首先用 action和category指定了intent的呼叫響應值,然後<data android:scheme中指定了響應的協議必須是https協議。

這樣再啟用後就會有選擇了,是使用普通的chrome還是使用只能有https。

3.3.4 向下一個Activity傳遞資料

使用Intent啟動Activity的時候還可以傳遞資料。

Intent中提供了一系列的putExtra()函式的過載,可以把想要傳遞的資料暫存在Intent中,然後在啟動另一個Activity後,只需要將其從Intent資料中取出就可以了。

比如說:在FirstActivity中有一個字串想傳遞給ThirdActivity中就可以這樣寫:

button1.setOnClickListener{
val data = "Hello ThirdActivity"
val intent = Intent(this,ThirdActivity::class.java)
intent.putExtra("extra_data",data)
startActivity(intent)
}

這段程式碼採用的顯示傳遞intent,然後通過putExtra()來傳遞字串,putExtra的第一個引數是健用於之後從Intent中取值,第二個引數才是真正要傳遞的資料。

然後再在ThirdActivity中將傳遞的資料取出來列印,程式碼如下:

class ThirdActivity : AppCompatActivity() {
  override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.third_layout)
      val extraData = intent.getStringExtra("extra_data")
      Log.d("ThirdActivity","extra data is $extraData")
  }
}