Android編程示例:建立機場計劃模擬器應用程序

在本文中,咱們將演示如何使用Android Studio和Java編程語言建立一個示例Android應用程序,從「臨時」實現高級響應用戶界面的功能。本文中討論的應用程序將實現機場航班時刻表模擬的功能。在開發生命週期中,咱們將實現Android應用程序的響應式用戶界面,用於呈現「到達」和「離開」航班的列表,並提供動態生成和更新實時模式中的航班信息的功能。java

咱們將大力強調幾個Java語言編程方面,並深刻研究容許咱們提供高級Android應用程序的編程技術的數量,包括從一開始就建立響應app's drawer和navigation bar應用程序的方面,提供咱們本身的custom views而且layouts,如custom search view bar with action button,覆蓋默認功能通用app's action bar,保持tabbed layout,渲染recycler views ,不一樣於listviews或gridviews容許建立的自定義外觀的項目lists由應用程序所呈現的數據,與建立各類佈局多重嵌套fragments,使用 bottom navigation view等android

除了應用程序的界面特定主題,咱們還將瞭解如何建立一個用Java編寫的高效代碼來實現生成和操做數據內容的功能,以及如何提供操做數據的代碼和應用程序的用戶界面。git

具體來講,咱們將實施機場航班時刻表模擬器的功能,該模擬器生成隨機航班數據集並經過在實時模式中過濾掉航班來模擬航班到達和延長時間線來操縱這些數據,動態更新航班列表被渲染。爲此,咱們將使用和討論諸如使用Android應用程序的後臺任務,使用計時器等主題。web

背景

先決條件(在咱們開始以前...)

在咱們開始討論以前,讓咱們花點時間仔細研究一下到目前爲止咱們特別須要的開發工具和庫來構建和運行咱們的第一個Android應用程序。編程

由於,咱們即將使用Java編程語言來部署咱們運行Android的第一個應用程序,咱們必須安裝Java SE。爲此,咱們須要從http://www.codesocang.com/下載並安裝Java Standard Edition - SE平臺。反過來,Java SE平臺包含在PC上構建和運行用Java編寫的代碼所需的全部庫和模塊。數組

在咱們成功安裝Java SE平臺以後,咱們還須要正確安裝IDE和建立Android應用程序項目所需的特定庫,並構建運行咱們正在部署的應用程序的代碼。各類IDE,編程語言和庫的數量,例如由Android開發社區受權的Microsoft Visual Studio / C#.NET Xamarin或Android Studio,能夠有效地用於建立和部署Android應用程序。app

在本文中,爲了提供Android應用程序開發生命週期的效率,平臺兼容性以及開發流程,咱們將特別使用Android Studio和Java編程語言來實現此目的。框架

這就是爲何,在咱們在以前的配置步驟中安裝Java SE平臺以後,須要並強烈建議在開發機器上下載並安裝Android Studio(https://developer.android.com/studio/)。dom

咱們可能已經注意到,安裝的Android Studio包含許多開發工具,包括IDE,Java SDK和NDK庫,Android系統模擬器,Gradle / Maven - java編譯器的「make」實用程序,能夠更輕鬆地編譯和編譯用Java編程語言編寫的連接代碼。編程語言

反過來,Android Studio的IDE是一個高效且響應迅速的工具,用於輕鬆建立和編輯Android源碼以及實現基本應用程序功能的Java代碼。

除了高效便捷的IDE以外,Android Studio軟件包還包括爲各類目標(手機,平板電腦,可穿戴設備,Android電視......)開發Android應用程序所需的Java SDK庫。具體來講,Android Studio IDE容許經過SDK管理器下載和安裝適用於各類Android系統版本的SDK,SDK Manager是Android Studio的一部分,或者可選地按期使用Java SDK發行版中的本機SDK管理器。

爲了編譯和連接正在建立的應用程序,Android Studio的軟件包還包括上面提到的Gradle / Maven'make'實用程序。在Android Studio中建立咱們的第一個Android應用程序項目時,Gradle組件已下載並配置爲與Android Studio的IDE一塊兒使用。每次,當咱們構建和運行Android應用程序的項目時,Gradle實用程序正在執行編譯和連接特定的任務,例如建立包含內置Android應用程序的apk包,能夠在模擬器上運行或者一個Android設備。在開發生命週期中,因爲已經建立和配置了項目,所以咱們可使用Gradle實用程序的多個版本,就像在本文的項目建立部分中討論的那樣。 

爲了可以在調試開發階段運行應用程序,Android Studio還包括一個支持各類Android系統版本的Android設備模擬器,可經過Android Studio的模擬器管理器從Google和Android開發社區網站源碼下載。在模擬器上運行應用程序與在目標Android設備上運行應用程序很是類似。

在本文的下一部分中,咱們將演示如何在安裝的Android Studio環境中建立咱們的第一個Android應用程序項目。

摘要

建立您的第一個Android App項目

在咱們成功知足上面討論的全部安裝和配置要求以後,咱們要作的第一件事就是運行Android Studio並建立一個項目來實現咱們的機場航班時刻表模擬Android應用程序功能。爲此,咱們將使用Android Studio主對話框切換啓動新的Android Studio項目選項:

在此以後,Android項目建立對話框將出如今屏幕上:

在這個對話框中,咱們必須指定一個應用程序名稱(在這種狀況下,它是` AirportApp`),公司域(例如,` epsilon.com`)來正確配置應用程序包,項目位置,特別是包name,在咱們的例子中是` com.epsilon.airportapp`。在咱們提供了建立項目所需的全部信息後,單擊此對話框底部的下一個按鈕。

在此步驟以後,咱們必須正確選擇並指定咱們的應用程序的目標設備,包括正確的外形(「手機」或「平板電腦」),最小的SDK及其版本,以及Android系統發佈版本:

在咱們成功選擇了目標設備和Android發佈版本以後,咱們還必須選擇一種應用程序的活動。活動一般是java類實現功能,負責應用程序的主窗口建立,事件處理以及完成其餘用戶交互特定任務。實際上,擴展泛型Activity類或其餘派生類的java 類是任何現有Android應用程序的主類:

在這種特殊狀況下,咱們開始咱們的第一個Android應用程序開發生命週期,選擇一個空活動做爲咱們的機場計劃模擬器應用程序的主要活動。此外,咱們將定製和加強默認的空活動,以提供執行機場計劃模擬任務所需的功能。

Android應用程序建立階段的最後一步是配置基於活動的java類別名,生成特定的活動佈局,以及配置應用程序的向後兼容庫。爲此,咱們必須繼續下一個配置對話框:

在最後一步中,我指定一個應用程序基於活動的java類名稱,該名稱將對應於正在生成的特定活動佈局xml文件名。此外,咱們必須指定是否要提供應用程序與舊版Android的向後兼容性。

因爲咱們已經配置了應用程序的活動,所以在最後階段生成特定項目並打開Android Studio的IDE主窗口:

在本文的下一部分中,咱們將簡要介紹使用Android Studio建立的Android應用程序的項目結構。

Android App的項目結構

在這一點上,讓咱們仔細看看應用程序項目建立後打開的Android Studio IDE主窗口左上角的應用程序解決方案樹。一般,解決方案樹顯示正在建立的項目的內容,該內容與保存到特定位置的目錄結構徹底對應(例如,「 D:\ AirportApp 」)。

AndroidManifest.xml中

文件夾「清單」是顯示在應用解決方案樹頂部的第一個文件夾。它基本上只包含一個文件' AndroidManifest.xml '。如下文件主要包含運行正在建立的應用程序所需的xml格式的全部配置數據。AndroidManifest.xml文件具備如下結構,對於全部Android應用程序都徹底相同:

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

  package="com.epsilon.arthurvratz.airportapp">
  <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/AppTheme.NoActionBar">
     <activity android:name=".AirportActivity"

        android:theme="@style/AppTheme.NoActionBar"

        android:windowSoftInputMode="stateHidden"

        android:configChanges="orientation|screenSize|keyboard|keyboardHidden">
        <intent-filter>
           <action android:name="android.intent.action.MAIN" />
           <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
     </activity>
  </application>
</manifest>

 

AndroidManifest.xml文件的第二行包含manifest標記,其屬性提供命名空間和應用程序的包名稱信息。它還包含一個嵌套標籤application ,該標籤具備定義標籤,文本方向和建立的應用程序的一對圖標的屬性數。由應用程序標籤的屬性指定的圖標和標籤基本上顯示在應用程序的主窗口中。應用程序標記還包含一個定義默認應用程序主題的屬性(例如,android:theme="@style/AppTheme")。或者,咱們可能但願修改現有的或嚮應用程序標記添加更多屬性,以便提供應用程序主窗口的自定義外觀和行爲。例如,咱們可能想要更改值android:theme屬性,以便咱們的應用程序將覆蓋默認的泛型並使用其本身的應用程序操做欄實現。爲此,咱們須要將如下標記的值更改成android:theme="@style/AppTheme.NoActionBar".

一般,application標記具備嵌套標記的數量,例如activity標記,用於提供主應用程序活動的一組配置屬性。默認狀況下,activity標記只有一個屬性,用於定義主應用程序活動的名稱(例如android:name=".AirportActivity")。要修改應用程序的主要活動配置參數,咱們可能須要向如下標記添加更多屬性:

android:theme="@style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden"
android:configChanges="orientation|screenSize|keyboard|keyboardHidden">

 

在這種特殊狀況下,咱們將如下配置屬性添加到activity上面列出的機場計劃模擬器應用主標籤中。第一個屬性是咱們以前在application 上面標記中指定的屬性的副本。如下屬性用於指定將顯示正在運行的應用程序中的默認通用應用程序操做欄。第二個屬性android:windowSoftInputMode="stateHidden"用於指定在啓動應用程序時不會自動呈現的軟輸入方法。最後一個屬性 android:configChanges="orientation|screenSize|keyboard|keyboardHidden"提供應用程序覆蓋的配置更改列表。這意味着應用程序將處理如下更改,而不是Android系統。具體來講,應用程序將處理屏幕旋轉並根據當前屏幕方向呈現正確的界面佈局變化(例如' portrait'或' landscape')。

應用程序標記還包含最內層嵌套標記的數量,例如intent-filter,action和category。在action和category裏面的意圖標籤intent-filter標籤指定的主應用程序的入口點。特別是,這些標籤指定當前'.AirportActivity' 是主應用程序的活動:

<intent-filter>
   <action android:name="android.intent.action.MAIN" />
   <category android:name="android.intent.category.LAUNCHER" />
</intent-filter>

 

Gradle腳本

如今,讓咱們來看看位於應用解決方案樹底部的' Gradle Scripts '兄弟。如下文件夾包含配置上一節中提到的gradle'make'實用程序所需的全部腳本文件,包括項目' '或' '模塊的兩個' build.gradle '文件實例。第一個build.gradle文件包含如下內容:AirportAppapp

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
   
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.1.3'
       

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

 

如下文件是包含Gradle存儲庫的基本配置的非xml文件,包括其構建版本(例如 'com.android.tools.build:gradle:3.1.3')。在項目配置期間,如下文件的內容一般保持不變。
 
可是,第二個build.gradle文件特別感興趣。第二個build.gradle文件基本上包含應用程序的項目模塊依賴項的定義。例如:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    defaultConfig {
        applicationId "com.epsilon.arthurvratz.airportapp"
        minSdkVersion 24
        targetSdkVersion 28
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
    }
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:28.0.0-alpha3'
    implementation 'com.android.support:support-v4:28.0.0-alpha3'
    implementation 'com.android.support:support-v13:28.0.0-alpha3'
    implementation 'com.android.support:design:28.0.0-alpha3'
    implementation 'com.android.support:recyclerview-v7:28.0.0-alpha3'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.2'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
    implementation 'org.jetbrains:annotations-java5:15.0'
}

 

爲了可以使用Android支持庫,咱們必須v.4,v.7,v.13將如下行添加到此文件的部分:RecyclerViewConstraintLayout dependencies
 
<span Courier New",Courier,mono; font-size: 12px; font-size-adjust: none; font-stretch: 100%; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; line-height: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-shadow: none; text-transform: none; -webkit-text-stroke-width: 0px; white-space: pre; word-spacing: 0px;">    
implementation 'com.android.support:appcompat-v7:28.0.0-alpha3' implementation 'com.android.support:support-v4:28.0.0-alpha3' implementation 'com.android.support:support-v13:28.0.0-alpha3' implementation 'com.android.support:design:28.0.0-alpha3' implementation 'com.android.support:recyclerview-v7:28.0.0-alpha3' implementation 'com.android.support.constraint:constraint-layout:1.1.2'</span>

 

反過來,' gradle-wrapper.properties '和' local.properties '文件都是另外一個特別的興趣:
 
gradle-wrapper.properties:
#Thu Jul 26 06:49:16 EEST 2018
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

 

local.properties:
## This file must *NOT* be checked into Version Control Systems,
# as it contains information specific to your local configuration.
#
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
#Thu Jul 26 15:02:12 EEST 2018
sdk.dir=C\:\\AndroidSDK

在此文件中,咱們能夠指定gradle實用程序版本或Android SDK位置的絕對路徑。爲此,咱們必須修改這兩個文件的如下行:

distributionUrl=https\://services.gradle.org/distributions/gradle-4.9-all.zip
sdk.dir=C\:\\Android\AndroidStudio\SDK
注意:若是您的gradle更改的版本> gradle-4.6-all.zip,,那麼你就還須要禁用「 配置需求 」中的「選項文件」>「 設置 」>「 創建,實施,部署 「> 編譯器 」。

應用程序的活動和佈局文件

在咱們徹底符合全部應用程序的項目配置步驟以後,讓咱們看看咱們將來應用程序的活動java實現文件和主應用程序的佈局xml文件。主應用程序的佈局文件位於「 res / layout 」文件夾下,名稱爲「 activity_airport.xml 」。如下文件最初包含' android.support.constraint.ConstraintLayout'標記,這是空應用程序的默認佈局。
要修改的主要應用程序的佈局,並添加咱們的Android應用程序的界面組件,如其餘直列布局或控制(即「若干意見」),咱們必須使用Android Studio的佈局設計或手動編輯如下佈局文件:

 

爲了可以在Android Studio的設計器中編輯佈局,您還必須修改能夠位於應用程序項目的「 res / values 」文件夾中的「 styles.xml 」 :
 
<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Base.Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item>
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>

</resources>

 

 
具體來講,您必須parent將' style'標籤的' '屬性值更改parent="Theme.AppCompat.Light.DarkActionBar"爲  parent="Base.Theme.AppCompat.Light.DarkActionBar"。
 
如下佈局是默認的空應用程序佈局,將在討論應用程序的開發生命週期期間進行更改。或者,咱們能夠經過手動編輯' activity_airport.xml '佈局文件來添加對應用程序佈局內容的更改:
 
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    tools:context=".AirportActivity">

    <TextView

        android:layout_width="wrap_content"

        android:layout_height="19dp"

        android:text="Hello World!"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintLeft_toLeftOf="parent"

        app:layout_constraintRight_toRightOf="parent"

        app:layout_constraintTop_toTopOf="parent" />

</android.support.constraint.ConstraintLayout>

 

此外,咱們將在本文的後續部分之一中提供有關如何使用約束佈局來構建響應式應用程序界面的詳細指南。
 
咱們此時要討論的最後一個方面是應用程序的主要活動實現文件' com.epsilon.airportapp / AirportActivity.java':
package com.epsilon.airportapp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class AirportActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_airport);
    }
}

 

如下文件包含在第一個' com.epsilon.airportapp '文件夾中,幷包含' AirportActivity'擴展泛型AppCompatActivity'類的java類的聲明。最初,AirportActivity類只包含一個重寫方法' OnCreate',實現將應用程序佈局呈現爲正在建立的應用程序的主內容視圖的功能。如下方法實現了調用onCreate超類中的' '方法,或者接受主應用程序的佈局resource-id' R.layout.activity_airport'的setContentView方法,併爲主應用程序的佈局提供了基本的呈現功能。未來,咱們會修改' AirportActivity''
 

應用程序的主要佈局Bluep rint

此時,咱們的主要目標是建立機場計劃模擬應用程序的主要佈局設計草圖。更具體地說,主應用程序的佈局將具備如下外觀:

從上圖中能夠看出,整個主要機場應用程序的佈局包括'SearchView'最頂層的高級變體'TabLayout',其中將呈現兩個到達和離開航班列表。每一個選項卡將呈現一個'RecyclerView'顯示航班列表,' BottomNavigationView'容許導航將在'昨天','如今'和'tommorow'發生的航班列表中。「TabLayout」和「RecyclerView」由特定片斷佈局呈現,這些佈局在切換應用程序的抽屜 導航菜單項或選擇其中一個特定選項卡後顯示。
 
主應用程序的佈局主要基於' DrawerLayout'模式,這意味着應用程序的抽屜將在用戶切換應用程序主窗口左上角的操做欄按鈕時呈現。應用程序的抽屜按期可能包含基於'NavigationView',應用程序主菜單等的抽屜標題。 
 
在此以前,讓咱們回想一下,這不是項目建立嚮導生成的標準應用程序佈局。此外,咱們將討論如何以編程方式實現機場應用程序的自定義佈局。

設計應用程序的主要佈局

如今,咱們終於維護了機場應用程序的主要佈局藍圖,如今是時候建立和編輯一個或多個應用程序的佈局文件了。咱們要修改的第一個文件是' activity_airport.xml '。因爲咱們的機場應用程序旨在擁有應用程序的抽屜,咱們選擇' DrawerLayout'做爲主應用程序的佈局類型:
 
<?xml version="1.0" encoding="utf-8"?>
<!-- Use DrawerLayout as root container for activity -->
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    android:id="@+id/airport_drawer_layout"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:fitsSystemWindows="true">

    <include layout="@layout/content_frame"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"/>

    <android.support.design.widget.NavigationView        

        android:id="@+id/airport_navigation_view"

        android:layout_width="wrap_content"

        android:layout_height="match_parent"

        android:layout_gravity="start"

        android:fitsSystemWindows="true"

        app:menu="@menu/main_menu"

        app:headerLayout="@layout/nav_header_frame"/>

</android.support.v4.widget.DrawerLayout>

 

在這種狀況下,咱們android.support.v4.widget.DrawerLayout在activity_airport.xml文件中使用' '做爲根標記。在此以後,咱們還須要建立兩個嵌套標籤,例如' include'標籤,它將包含單獨文件' content_frame.xml'中包含的如下佈局的另外一部分,或' android.support.design.widget.NavigationView'標籤,聲明機場應用程序的標籤抽屜佈局。遺憾的是,因爲使用了抽屜佈局,咱們沒法使用Android Studio的佈局設計器修改上面顯示的佈局,但咱們可使用Android Studio的IDE文本編輯器手動編輯此佈局。
應用程序主要佈局的包含片斷存儲在內容框架文件中,以下所示:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout

    xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="match_parent">

    <LinearLayout

        android:id="@+id/search_bar"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        app:layout_constraintBottom_toTopOf="@+id/airport_fragment_container"

        app:layout_constraintEnd_toEndOf="parent"

        app:layout_constraintHorizontal_bias="1.0"

        app:layout_constraintStart_toStartOf="parent"

        app:layout_constraintTop_toTopOf="parent"

        app:layout_constraintVertical_bias="0.02"

        android:focusable="true"

        android:focusableInTouchMode="true">

        <requestFocus />

        <android.support.v7.widget.SearchView

            android:id="@+id/searchable"

            android:layout_width="match_parent"

            android:layout_height="wrap_content"/>

    </LinearLayout>

    <FrameLayout

        android:id="@+id/airport_fragment_container"

        android:layout_width="match_parent"

        android:layout_height="0dp"

        android:layout_marginEnd="8dp"

        android:layout_marginStart="8dp"

        app:layout_constraintBottom_toTopOf="@+id/flights_navigation"

        app:layout_constraintEnd_toEndOf="parent"

        app:layout_constraintHorizontal_bias="0.0"

        app:layout_constraintStart_toStartOf="parent"

        app:layout_constraintTop_toBottomOf="@+id/search_bar">

    </FrameLayout>

    <android.support.design.widget.BottomNavigationView

        android:id="@+id/flights_navigation"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintEnd_toEndOf="parent"

        app:layout_constraintHorizontal_bias="1.0"

        app:layout_constraintStart_toStartOf="parent"

        app:layout_constraintTop_toBottomOf="@+id/airport_fragment_container"

        app:menu="@menu/flights_navigation"

        android:theme="@style/AppTheme"/>
</android.support.constraint.ConstraintLayout>

 

在這個文件中,咱們一般使用' android.support.constraint.ConstraintLayout'標籤做爲此佈局的根標籤。如下標記具備內聯標記的數量,包括' LinearLayout',其中' android.support.v7.widget.SearchView'標記被聲明',FrameLayout'實際上聲明瞭一個框架,將以編程方式替換爲特定的片斷渲染' RecyclerView',顯示一個航班列表,'BottomNavigationView'按時間過濾掉航班的渲染選項。因爲咱們使用約束佈局做爲整個內容框架的根,所以必須正確約束全部嵌套視圖和佈局。與以前的佈局不一樣,可使用Android Studio的佈局設計器成功編輯內容框架佈局。這就是咱們選擇是否編輯特定內容框架文件或使用佈局設計器爲如下佈局中的全部視圖提供特定約束的緣由。 
 
在這種狀況下,互連內容框架中視圖的最佳方法是app:layout_constraintTop_toBottomOf向每一個視圖標記添加特定屬性,例如' ',如上面的源代碼所示。在這段代碼中,咱們從最上面的'LinearLayout'視圖標籤開始垂直和水平地向每一個視圖標籤添加布局約束屬性,以垂直方向連接全部這些屬性。
 
在這一點上,讓咱們回到定義應用程序抽屜佈局的代碼片斷。在' DrawerLayout'標籤內部聲明的另外一個視圖是' android.support.design.widget.NavigationView'。如下視圖主要用於渲染應用程序的抽屜及其菜單,如上圖所示。使用導航視圖一般須要咱們建立另外一個應用程序的抽屜佈局和特定菜單,爲應用程序的抽屜菜單聲明項目。
 
要建立這些佈局,咱們基本上須要在項目的' / res '文件夾中建立一個子文件夾,並建立名爲' main_menu.xml ' 的特定菜單佈局資源文件:
 
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" >
    <group android:checkableBehavior="single">
        <item

            android:id="@+id/flights"

            android:icon="@drawable/ic_flight_black_24dp"

            android:title="@string/flights" />
        <item

            android:id="@+id/about"

            android:icon="@drawable/ic_star_black_24dp"

            android:title="@string/about" />
    </group>
</menu>

 

在這個文件中,咱們必須聲明' menu'標籤,並在其中建立group項目的' '。在這種狀況下,如下佈局包含每一個「航班」或「關於」菜單選項的兩個項目組,顯示在應用程序的抽屜中,標題下方。
 
另外一個佈局文件' nav_header_frame.xml '包含用戶切換時在應用程序抽屜中呈現的佈局:
 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="240dp"

    android:background="@drawable/airport_nav_header"

    android:gravity="bottom"

    android:orientation="vertical"

    android:padding="16dp"

    android:theme="@style/ThemeOverlay.AppCompat.Dark">

    <ImageView

        android:id="@+id/imageView"

        android:layout_width="103dp"

        android:layout_height="99dp"

        app:srcCompat="@mipmap/ic_launcher_round" />

    <Space

        android:layout_width="352dp"

        android:layout_height="10dp" />

    <TextView

        android:id="@+id/airport_app_title"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:fontFamily="Verdana"

        android:text="@string/nav_header"

        android:textColor="@android:color/background_light"

        android:textIsSelectable="false"

        android:textSize="30sp" />

    <TextView

        android:id="@+id/airport_app_author"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        android:text="@string/airport_app_author" />

</LinearLayout>

 

要建立應用程序的抽屜佈局,咱們將使用特定的' LinearLayout'標籤。與其餘佈局(例如' CostraintLayout')不一樣,線性佈局容許僅在垂直方向上定位全部視圖,而且不須要在視圖之間設置任何約束。 
 
要定義正確的抽屜佈局,咱們必須將如下視圖標記放置在線性佈局內,以及爲應用程序的抽屜標題提供背景圖像。爲此,咱們指定如下線性佈局屬性:' android:background="@drawable/airport_nav_header"'。一般,咱們建立的線性佈局將包含如下內聯視圖:
  • ' ImageView' - 用於顯示機場應用程序的圖標;
  • ' Space' - 在線性佈局中建立特定視圖之間的間隙;
  • ' TextView' - 打印機場應用程序的標題或做者的詳細信息;

最後,由' NavigationView'及其藍圖呈現的應用程序的抽屜佈局將具備如下外觀:

在本文的下一部分中,咱們將瞭解如何實現主要機場應用程序活動的功能。

使用操做按鈕建立自定義SearchView

SearchView是出如今主應用程序窗口頂部的機場計劃模擬器應用程序的第一個控件。此時,讓咱們回到他標記的'content_frame.xml片斷,聲明搜索視圖位於如下佈局文件的全部其餘視圖以前,由' :包裹起來:'.Tandroid.support.v7.widget.SearchViewLinearLayout<font color="#007000" face=""Segoe UI",Arial,Sans-Serif">'</font>

<LinearLayout
    android:id="@+id/search_bar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toTopOf="@+id/airport_fragment_container"
    app:layout_constraintEnd_toEndOf="parent"
    app:layout_constraintHorizontal_bias="1.0"
    app:layout_constraintStart_toStartOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:layout_constraintVertical_bias="0.02"
    android:focusable="true"
    android:focusableInTouchMode="true">

    <requestFocus />

    <android.support.v7.widget.SearchView
        android:id="@+id/searchable"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

 

咱們使用線性佈局用於確保應用程序啓動後搜索視圖不會得到焦點。
因爲咱們SearchView在內容框架中聲明瞭' '標籤,所以咱們的目標是經過在java中實現特定代碼來提供功能和行爲(例如,使搜索視圖響應),這些代碼將即時處理和處理事件。咱們的搜索視圖。
 
咱們可能知道,在這個項目中咱們不會使用通用搜索視圖和應用程序欄,但會建立咱們本身的自定義搜索視圖,它結合了通用搜索視圖的基本功能和應用程序的操做欄。
 
要使用操做按鈕建立自定義搜索視圖,咱們須要建立一個新的java類,並將其命名爲「 SearchableWithButtonView擴展泛型View」類:
 
public class SearchableWithButtonView extends View {
 
         // SearchView basic functionality implementation java-code goes here...

}

 

在本課程中,咱們須要實現如下方法。setupSearchableWithButton()是咱們須要實現的第一個方法,以便爲咱們的自定義搜索視圖提供特定的外觀和行爲:
 
public void setupSearchableWithButton() {

    // Set background color of the search view
    ((ViewGroup)m_SearchView.findViewById(android.support.v7.appcompat.R.id.search_mag_icon).
            getParent()).setBackgroundColor(Color.parseColor("#ffffff"));


    // Set default custom search of the search view button icon and look of the custom search view
    this.setDefaultSearchIcon(); this.setupIconifiedByDefault();

    // Set default search hint displayed in the search view's edit text view
    m_SearchView.setQueryHint("TYPE HERE...");

    // Set default query text and remove focus from the search view
    m_SearchView.setQuery("", false); getRootView().requestFocus();

    // Instantinate the search view object and set default action button click event listener
    m_SearchView.findViewById(android.support.v7.appcompat.R.id.search_mag_icon).
            setOnClickListener(new SearchableViewListener());

    // Instantinate the search view object
    ViewGroup llSearchView = ((ViewGroup)m_SearchView.findViewById(
            android.support.v7.appcompat.R.id.search_mag_icon).getParent());

    // Instantinate object of the text editable inside the search view
    EditText searchEditText = llSearchView.findViewById(
            android.support.v7.appcompat.R.id.search_src_text);

    // Remove the search view text editable default selection
    searchEditText.setSelected(false);

    // Set text editable click event listener
    searchEditText.setOnClickListener(new SearchableViewListener());

    // Set text editable onTextChange listener
    searchEditText.addTextChangedListener(new SearchableViewListener());
}

 

在此方法中,咱們經過修改背景顏色,搜索視圖按鈕圖標,在應用程序啓動時從搜索視圖中刪除默認選擇和焦點來更改通用搜索視圖的外觀和行爲,而且還設置處理程序(即偵聽器)各類搜索視圖事件,例如單擊用做應用程序主操做按鈕的搜索視圖按鈕,文本編輯和文本可編輯視圖點擊等。
 
這些事件處理程序實現爲' SearchableWithButtonView '子類,在其中聲明:
 
public class SearchableViewListener
            implements OnClickListener, TextWatcher {
        @Override
        public void onClick(View view) {

            // Check if the custom search view button was clicked
            if (android.support.v7.appcompat.R.
                    id.search_mag_icon == view.getId()) {

                // If so, perform a check if the default action bar icon was set
                if (!isDefaultIcon) {

                    // If not, set the default icon by invoking setDefaultSearchIcon() method
                    setDefaultSearchIcon();

                    // Terminate the onClick handler method execution
                    return;
                }


                // Invoke onClick(...) method from the main app's activity class
                m_ClickListener.onClick(view);
            }

            // Otherwise, set navigation-back search icon
            else setNavBackSearchIcon();
        }

        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            // Invoke the beforeTextChange(...) method from app's activity class (e.g. its parent)
            m_TextWatcherListener.beforeTextChanged(charSequence, i, i1, i2);
        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
<span Segoe UI",Arial,Sans-Serif; font-size: 14px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-shadow: none; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">                     // Set navigation-back icon and invoke the onTextChanged(...) method </span>
            <span Segoe UI",Arial,Sans-Serif; font-size: 14px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-shadow: none; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">// from app's activity class (e.g. its parent)</span>
            setNavBackSearchIcon(); m_TextWatcherListener.onTextChanged(charSequence, i, i1, i2);
        }

        @Override
        public void afterTextChanged(Editable editable) {

            // Perform a check if the editable string is empty
            if (editable.toString().isEmpty())

                // If so, set default search view icon
                setDefaultSearchIcon();

            // Invoke the afterTextChanged(...) method from its parent 
            m_TextWatcherListener.afterTextChanged(editable);
        }
    }

 

另外,  「 SearchableWithButtonView」類有如下方法:

下面列出的方法將自定義搜索視圖的外觀更改成uniconfied:

private void setupIconifiedByDefault() {
     // Disable the iconfied mode to make the search view fill the entire area horizontally
     m_SearchView.setIconified(false);
     m_SearchView.setIconifiedByDefault(false);
 }

 

如下方法使用應用程序操做欄按鈕的自定義圖標替換通用搜索視圖的默認圖標

private void setDefaultSearchIcon() {
    // Replace the default search view icon with the action button icon
    this.isDefaultIcon = true;
    this.replaceSearchIcon(R.drawable.ic_dehaze_white_24dp);
}

 

如下方法使用導航後退圖標替換默認操做欄按鈕圖標:

private void setNavBackSearchIcon() {
    // Check if the default icon was set
    if (this.isDefaultIcon == true) {
        // If so, replace search view icon with navigation-back icon
        this.isDefaultIcon = false;
        this.replaceSearchIcon(R.drawable.ic_arrow_back_black_24dp);
        // Run the search view icon animation
        this.setupAnimation();
    }
}

 

如下方法將默認搜索視圖按鈕圖標替換爲從應用程序資源中檢索到的圖標:

private void replaceSearchIcon(int resDefaultIcon) {
    // Instantinate search view button icon object and set the custom icon
    // by calling setImageDrawable method that accepts the icon object retrieved
    // from the app's resources by calling the context's getDrawable(...) method
    ((ImageView)m_SearchView.findViewById(android.support.v7.appcompat.R.id.search_mag_icon)).
            setImageDrawable(m_Context.getDrawable(resDefaultIcon));
    // Start animating icon
    this.setupAnimation();
}

 

此方法用於設置搜索視圖圖標的動畫

private void setupAnimation() {

    // Instantinate search view icon object
    final ImageView searchIconView = m_SearchView.findViewById(
            android.support.v7.appcompat.R.id.search_mag_icon);

    // Compute the icon's width and height values
    int searchIconWidth = searchIconView.getWidth();
    int searchIconHeight = searchIconView.getHeight();

    // Instantinate RotateAnimation class object and specify the rotation params
    RotateAnimation searchIconAnimation = new RotateAnimation(0f, 360f,
            searchIconWidth / 2, searchIconHeight / 2);
    // Set animation interpolator
    searchIconAnimation.setInterpolator(new LinearInterpolator());
    // Set animation repeat count
    searchIconAnimation.setRepeatCount(Animation.INFINITE);
    // Set animation duration
    searchIconAnimation.setDuration(700);

    // Start animating the icon
    searchIconView.startAnimation(searchIconAnimation);

    // Perform a delay for 700ms after the icon animation ends
    new Handler().postDelayed(new Runnable() {
        @Override
        public void run() {
            searchIconView.setAnimation(null);
        }
    }, 700);

}

 

經過使用如下方法,咱們覆蓋了與搜索視圖對象一塊兒使用的findViewById(...)方法的基本功能:

// Override the default findViewById method to be used to instantinate
// search view object
private SearchView findSearchViewById(int resId) {
    return ((Activity)m_Context).findViewById(resId);
}

 

經過調用這兩個方法,咱們設置了主應用程序的activity類中使用的click事件監聽器和文本更改事件監聽器:

public void setSearchButtonClickListener(@Nullable OnClickListener clickListener) {
    // Set click listener class object of its parent
    m_ClickListener = clickListener;
}
public void setTextWatchListener(@Nullable TextWatcher textWatchListener) {
    // Set text change watcher listener class object of its parent
    m_TextWatcherListener = textWatchListener;
}

 

如今,因爲咱們已經使用操做按鈕實現了自定義搜索視圖,如今是時候將其功能添加到主應用程序的活動中,以下所示:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_airport);

    // Instantinating the drawer layout object
    m_DrawerLayout = findViewById(R.id.airport_drawer_layout);
    // Instantinating the navigation view object
    m_navigationView = findViewById(R.id.airport_navigation_view);

    // Instantinating our custom search view object
    m_searchableWithButtonView =
            new SearchableWithButtonView(AirportActivity.this, R.id.searchable);

    // Setting up our custom search view
    m_searchableWithButtonView.setupSearchableWithButton();
    // Adding the text change watcher listener
    m_searchableWithButtonView.setTextWatchListener(new SearchableWithButtonListener());
    // Adding the search view action button click event listener
    m_searchableWithButtonView.setSearchButtonClickListener(new SearchableWithButtonListener());

    // Setup app's drawer menu click event listener
    m_navigationView.setNavigationItemSelectedListener(m_NavigationBarListener);

    // ...

 

在overriden onCreate方法中,咱們一般執行抽屜佈局和導航視圖對象的即時化,設置咱們的自定義搜索視圖並添加特定的事件處理程序。要處理各類搜索視圖的事件,咱們必須聲明一個子類' SearchableWithButtonListener'實現' View.OnClickListener'或' TextWatcher'事件處理泛型類:

public class SearchableWithButtonListener implements View.OnClickListener, TextWatcher
    {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
        }

        @Override
        public void afterTextChanged(Editable editable) {

        }

        @Override
        public void onClick(View view) {
            // Perform a check if the app's drawer open
            if (!m_DrawerLayout.isDrawerOpen(GravityCompat.START))
                // If not, open the app's drawer
                m_DrawerLayout.openDrawer(GravityCompat.START);
        }
    }

 

經過本文下一節中討論的如下類的方法實現的功能。在這種狀況下,咱們將僅討論onClick(...)此類中方法的一種實現。如下方法經過調用方法實現應用程序的抽屜打開功能DrawerLayout.openDrawer(...) 。

正如咱們在openDrawer(...)處理自定義操做欄單擊事件後觸發該方法時已經討論的那樣,應用程序的抽屜打開,顯示應用程序的主菜單。此時咱們還必須經過調用'm_navigationView.setNavigationItemSelectedListener(m_NavigationBarListener)'接受偵聽器類對象做爲其單個參數的方法來提供菜單項單擊事件處理。如下代碼實現了重寫導航菜單項click事件監聽器類:

private class NavigationBarListener implements
        NavigationView.OnNavigationItemSelectedListener
{
    // This method handles the navigation menu item click events
    public boolean onNavigationItemSelected(MenuItem menuItem) {
        // set item as selected to persist highlight
        menuItem.setChecked(true);

        //...

        if (m_DrawerLayout.isDrawerOpen(GravityCompat.START))
            m_DrawerLayout.closeDrawers();

        return true;
    }
}

 

建立選項卡式應用程序的佈局

正如咱們已經討論過的,機場應用程序旨在響應用戶的輸入並顯示各類內容,具體取決於應用程序的抽屜導航菜單中的選項或用戶切換的選項卡。特別是在應用程序的抽屜導航菜單中切換「飛行」菜單項後,它一般會呈現選項卡式佈局。每一個選項卡基本上顯示由回收者視圖呈現的航班列表。爲實現這一點,咱們將使用片斷。' Fragment'是應用程序佈局的動態建立和渲染部分,包含其餘佈局或視圖,或二者兼而有之。

在這種狀況下,到目前爲止咱們要作的是建立特定的片斷佈局和咱們本身的實現內容呈現功能的java類。正如咱們已經討論過的那樣,兩個標籤「到達」和「離開」將出如今主應用程序的窗口中。在每一個選項卡中,咱們將呈現' RecyclerView'顯示已安排的航班列表。爲了提供選項卡式佈局功能,咱們將使用' TabbedLayout'inside inside' LinearLayout',這是FlightsFragment當用戶在應用程序的抽屜導航菜單中切換第一個菜單項'flight'時顯示的根佈局。航班片斷佈局在' res / layout / fragment_flights.xml '文件中實現:

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

    xmlns:tools="http://schemas.android.com/tools"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    android:id="@+id/flights_fragment"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:orientation="vertical"

    tools:context=".FlightsFragment">

    <android.support.design.widget.TabLayout

        android:id="@+id/flights_destination_tabs"

        android:layout_width="match_parent"

        android:layout_height="wrap_content"

        app:tabMaxWidth="0dp"

        app:tabMode="fixed"

        app:tabGravity="fill">

    <android.support.design.widget.TabItem

        android:id="@+id/arrivals_tab"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:icon="@drawable/ic_flight_land_black_24dp"

        android:text="@string/arrivals_tab" />

    <android.support.design.widget.TabItem

        android:id="@+id/departures_tab"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"

        android:icon="@drawable/ic_flight_takeoff_black_24dp"

        android:text="@string/departures_tab" />

    </android.support.design.widget.TabLayout>

    <android.support.v4.view.ViewPager

        android:id="@+id/flights_destination_pager"

        android:layout_width="wrap_content"

        android:layout_height="wrap_content"/>

    <requestFocus/>
</LinearLayout>

 

在航班片斷線性佈局中,咱們聲明瞭兩個標籤:' android.support.design.widget.TabLayout'和' android.support.v4.view.ViewPager'。第一個標籤基本上定義了標籤式佈局,其中包含兩個選項卡,用於「到達」或「離開」航班渲染,顯示在主應用程序窗口的搜索視圖下方。經過聲明第二個標籤' ViewPager'咱們提供了在一個整個屏幕之間滑動渲染到另外一個屏幕的功能。
 
因爲選項卡式佈局和視圖分頁器呈現爲片斷,咱們必須建立一個單獨的java類FlightsFragment「擴展泛型android.support.v4.app.Fragment」類:
 
FlightsFragmentImpl.java:
 
package com.epsilon.arthurvratz.airportapp;

import android.net.Uri;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;

import java.util.ArrayList;

public class FlightsFragmentImpl extends android.support.v4.app.Fragment implements
        ArrivalsFragment.OnFragmentInteractionListener,
        DeparturesFragment.OnFragmentInteractionListener
{
        public RecyclerView m_RecyclerView;
        public RecyclerView.Adapter m_RecyclerAdapter;
        public RecyclerView.LayoutManager m_LayoutManager;

        public void setupFlightsRecyclerView(RecyclerView recyclerView, ArrayList<AirportDataModel> dataSet)
        {

            // Setting the recycler view object
            m_RecyclerView = recyclerView;

            // Setting the recycler view has a fixed size
            m_RecyclerView.setHasFixedSize(true);

            // Instantinating the linear layout manager object
            m_LayoutManager = new LinearLayoutManager(getContext());

            // Setting up the recycler view's layout manager
            m_RecyclerView.setLayoutManager(m_LayoutManager);

            // Instantinating the flights recycler view's adapter object
            // and adding the flights dataset to the flights recycler view's adapter
            m_RecyclerAdapter = new FlightsRecyclerAdapter(dataSet, getContext());

            // Setting up the <span Segoe UI",Arial,Sans-Serif; font-size: 14px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-shadow: none; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">flights recycler view's adapter object</span>
            m_RecyclerView.setAdapter(m_RecyclerAdapter);
    }

    @Override
    public void onFragmentInteraction(Uri uri) {

    }
}

 

 
FlightsFragment.java:
 
package com.epsilon.arthurvratz.airportapp;

import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

public class FlightsFragment extends FlightsFragmentImpl
{
    private TabLayout m_TabLayout;
    private ViewPager m_ViewPager;

    final private TabSelectedListener
            m_TabSelListener = new TabSelectedListener();

    public ArrivalsFragment m_ArrivalsFragment;
    public DeparturesFragment m_DeparturesFragment;

    private class TabSelectedListener implements TabLayout.OnTabSelectedListener
    {
        @Override
        public void onTabSelected(TabLayout.Tab tab) {
            m_ViewPager.setCurrentItem(tab.getPosition());
        }

        @Override
        public void onTabUnselected(TabLayout.Tab tab) {

        }

        @Override
        public void onTabReselected(TabLayout.Tab tab) {

        }
    }

    private OnFragmentInteractionListener mListener;

    public FlightsFragment() {
        // Required empty public constructor
    }

    public static FlightsFragment newInstance() {
        return new FlightsFragment();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {

        // Inflating the flights fragment view's object
        View FlightsFragmentView =
                    inflater.inflate(R.layout.fragment_flights, container, false);

        // Instantinating the tab layout object
        m_TabLayout = FlightsFragmentView.findViewById(R.id.flights_destination_tabs);

        // Instantinating view pager object
        m_ViewPager = FlightsFragmentView.findViewById(R.id.flights_destination_pager);

        // Instantinating the tab layout's pager adapter
        FlightsDestPagerAdapter pagerAdapter = new FlightsDestPagerAdapter(
              getChildFragmentManager(), m_TabLayout.getTabCount());

        // Instantinating the arrivals fragment object
        m_ArrivalsFragment = ArrivalsFragment.newInstance();

        // Instantinating the departures fragment object 
        m_DeparturesFragment = DeparturesFragment.newInstance();

        // Adding the arrivals and departure fragment objects to the view pager adapter
        pagerAdapter.add(m_ArrivalsFragment);
        pagerAdapter.add(m_DeparturesFragment);

        // Setting up the view pager adapter
        m_ViewPager.setAdapter(pagerAdapter);

        // Adding the generic page sliding event listener
        m_ViewPager.addOnPageChangeListener(
                new TabLayout.TabLayoutOnPageChangeListener(m_TabLayout));
        m_TabLayout.addOnTabSelectedListener(m_TabSelListener);

        return FlightsFragmentView;
    }

    public void onButtonPressed(Uri uri) {
        if (mListener != null) {
            mListener.onFragmentInteraction(uri);
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    @Override
    public void onFragmentInteraction(Uri uri) {

    }

    public interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        void onFragmentInteraction(Uri uri);
    }
}

 

爲了實現航班片斷功能,咱們實際上定義了兩個java類。第一個類' FlightsFragmentImpl'擴展了泛型' android.support.v4.app.Fragment '並實現了下面討論的OnFragmentInteractionListener' ArrivalsFragment'和' DepartureFragment'類的' '功能。下面的類只實現了一個方法' setupFlightsRecyclerView(...)',它接受了一個回收器視圖對象或ArrayList本文後面討論的數據集對象的兩個參數。此方法的主要目的是設置回收器視圖的適配器,該適配器用於保存在其中一個選定選項卡中顯示的回收器視圖中呈現的數據。
 
另外一個類' FlightsFragment'擴展' FlightsFragmentImpl '的功能,並OnCreateView經過向視圖頁面適配器添加特定的到達和離開片斷對象,提供動態設置選項卡布局和在'覆蓋方法中查看尋呼機的基本功能。' FlightsDestPagerAdapter'java-class實現了view pager適配器的基本功能:
 
package com.epsilon.arthurvratz.airportapp;

import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;

import java.util.ArrayList;

public class FlightsDestPagerAdapter extends FragmentPagerAdapter {

    private ArrayList<Fragment> m_Fragments = new ArrayList<Fragment>();

    public FlightsDestPagerAdapter(FragmentManager FragmentMgr, int NumberOfTabs) {
        super(FragmentMgr);
    }

    public void add(Fragment fragment)
    {
        m_Fragments.add(fragment);
    }

    @Override
    public Fragment getItem(int position) {
        return m_Fragments.get(position);
    }

    @Override
    public int getCount() {
        return m_Fragments.size();
    }
}

 

如下類的實現主要基於使用' ArrayList<Fragment>'用於存儲通用' Fragment'類對象數組的功能。
 
最後,要渲染航班片斷,咱們必須覆蓋onNavigationItemSelected(...)' AirportActivity.NavigationBarListener'類中的' '方法。如下方法主要用於處理應用程序抽屜導航菜單中的事件,並具備如下實現:
 
private class NavigationBarListener implements
        NavigationView.OnNavigationItemSelectedListener
{
    public boolean onNavigationItemSelected(MenuItem menuItem) {
        // set item as selected to persist highlight
        menuItem.setChecked(true);

        // Instantinate the fragment manager transaction coordinator object
        m_FragmentTran = m_FragmentMgr.beginTransaction();

        // Perform a check if the flights menu item was selected
        if (menuItem.getItemId() == R.id.flights)

            // If so, replace the airport_fragment_container frame layout
            // with specific flight fragment by using its object.
            m_FragmentTran.replace(R.id.airport_fragment_container,
                FlightsFragment.newInstance());

        else if (menuItem.getItemId() == R.id.about) {}

        m_FragmentTran.addToBackStack(null); m_FragmentTran.commit();

        // Check if the app's drawer is still open
        if (m_DrawerLayout.isDrawerOpen(GravityCompat.START))

            // If so, close the app's drawer
            m_DrawerLayout.closeDrawers();

        return true;
    }

    public void setupInitialFragment()
    {
        if (m_FragmentMgr == null)

            // Instantinate the support fragment manager object
            m_FragmentMgr = getSupportFragmentManager();

            // Begin fragments transaction
            m_FragmentTran = m_FragmentMgr.beginTransaction();

            // Add the default flights fragment object and commit transaction
            m_FragmentTran.add(R.id.airport_fragment_container,
                  FlightsFragment.newInstance()).commit();
    }
}

 

如下類還實現了另外一個方法' setupInitialFragment(...)',用於在應用程序的主活動代碼中調用初始片斷時,在重寫方法' OnCreate(...)'中,當主應用程序的活動被即時化時:
 
protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_airport);

        //m_ActionToolBar = findViewById(R.id.airport_actionbar);
        m_DrawerLayout = findViewById(R.id.airport_drawer_layout);
        m_navigationView = findViewById(R.id.airport_navigation_view);
        m_flightsNavigationView = findViewById(R.id.flights_navigation);

        //setSupportActionBar(m_ActionToolBar);
        //this.setupActionBar(R.drawable.ic_dehaze_white_24dp);

        m_searchableWithButtonView =
                new SearchableWithButtonView(AirportActivity.this, R.id.searchable);

        m_searchableWithButtonView.setupSearchableWithButton();
        m_searchableWithButtonView.setTextWatchListener(new SearchableWithButtonListener());
        m_searchableWithButtonView.setSearchButtonClickListener(new SearchableWithButtonListener());

        m_navigationView.setNavigationItemSelectedListener(m_NavigationBarListener);

        m_flightsNavigationView.setSelectedItemId(R.id.flights_now);
        // Setting up initial fragment to be rendered in the main app's window
        m_NavigationBarListener.setupInitialFragment(); this.hideSoftInputKeyboard();

        //...
}

 

在本文的下一部分中,咱們將討論如何在航班片斷內呈現​​回收者視圖,顯示「到達」或「離開」航班的列表。

在RecyclerView中渲染航班

在回收站視圖中渲染航班列表是咱們即將在本文中討論的最終機場應用程序的GUI主題。正如咱們已經知道的那樣,咱們的機場應用程序顯示兩個「到達」或「離開」航班列表,並以編程方式以相似方式執行。爲了呈現航班列表,咱們所要作的就是建立兩個片斷,這些片斷將呈現到達航班或離港航班的回收者視圖:

fragment_arrivals.xml:

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

    xmlns:tools="http://schemas.android.com/tools"

    android:id="@+id/arrivals_fragment"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:layout_gravity="center_vertical|center_horizontal"

    tools:context=".ArrivalsFragment">

    <android.support.v7.widget.RecyclerView

        android:id="@+id/arrivals_recycler_view"

        android:scrollbars="vertical"

        android:layout_width="match_parent"

        android:layout_height="match_parent"/>
</LinearLayout>

 

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

    xmlns:tools="http://schemas.android.com/tools"

    android:id="@+id/departures_fragment"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:layout_gravity="center_vertical|center_horizontal"

    tools:context=".DeparturesFragment">

    <android.support.v7.widget.RecyclerView

        android:id="@+id/departures_recycler_view"

        android:scrollbars="vertical"

        android:layout_width="match_parent"

        android:layout_height="match_parent"/>
</LinearLayout>

 

咱們還建立了兩個' ArrivalsFragment'和' DeparturesFragment'的java類,它們實現了上面列出的那些片斷的功能:
 
ArrivalsFragment.java:
 
package com.epsilon.arthurvratz.airportapp;

import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;

public class ArrivalsFragment extends android.support.v4.app.Fragment {

    public  RecyclerView m_ArrivalsRecyclerView;

    public ArrayList<AirportDataModel> m_ArrivalsDataSet;

    public FlightsFragment m_FlightsFragment;

    private OnFragmentInteractionListener mListener;

    public ArrivalsFragment() {

        // Instatinate the airport app's data model and generate set of random flights
        m_ArrivalsDataSet = new AirportDataModel().InitModel(20);
    }

    public static ArrivalsFragment newInstance() {
        return new ArrivalsFragment();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        // Inflate the flights fragment layout
        View ArrivalsView =  inflater.inflate(R.layout.fragment_arrivals, container, false);

        // Get flights fragment layout object
        m_FlightsFragment =
                (FlightsFragment) this.getParentFragment();

        // Instantinate arrivals recycler view object
        m_ArrivalsRecyclerView =
                ArrivalsView.findViewById(R.id.arrivals_recycler_view);

        // Invoke setupFlightsRecyclerView method, which is the member of flight fragment class
        m_FlightsFragment.setupFlightsRecyclerView(m_ArrivalsRecyclerView, m_ArrivalsDataSet);

        return ArrivalsView;
    }

    // TODO: Rename method, update argument and hook method into UI event
    public void onButtonPressed(Uri uri) {
        if (mListener != null) {
            mListener.onFragmentInteraction(uri);
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    public interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        void onFragmentInteraction(Uri uri);
    }
}

 

DeparturesFragment.java:

package com.epsilon.arthurvratz.airportapp;

import android.content.Context;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import java.util.ArrayList;

public class DeparturesFragment extends android.support.v4.app.Fragment {

    public  RecyclerView m_DeparturesRecyclerView;

    public ArrayList<AirportDataModel> m_DeparturesDataSet;

    public FlightsFragment m_FlightsFragment;

    private OnFragmentInteractionListener mListener;

    public DeparturesFragment() {
        // Instatinate the airport app's data model and generate set of random flights
        m_DeparturesDataSet = new AirportDataModel().InitModel(20);
    }

    public static DeparturesFragment newInstance() {
        return new DeparturesFragment();
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
    
        // Inflate the flights fragment layout
        View DeparturesView =  inflater.inflate(R.layout.fragment_arrivals, container, false);
 
    // Get flights fragment layout object</span>
        m_FlightsFragment =
                (FlightsFragment) this.getParentFragment();

    // Instantinate departures recycler view object</span>
        m_DeparturesRecyclerView =
                DeparturesView.findViewById(R.id.arrivals_recycler_view);

    // Instantinate arrivals recycler view object</span>
        m_FlightsFragment.setupFlightsRecyclerView(m_DeparturesRecyclerView, m_DeparturesDataSet);

        return DeparturesView;
    }

    // TODO: Rename method, update argument and hook method into UI event
    public void onButtonPressed(Uri uri) {
        if (mListener != null) {
            mListener.onFragmentInteraction(uri);
        }
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        if (context instanceof OnFragmentInteractionListener) {
            mListener = (OnFragmentInteractionListener) context;
        } else {
            throw new RuntimeException(context.toString()
                    + " must implement OnFragmentInteractionListener");
        }
    }

    @Override
    public void onDetach() {
        super.onDetach();
        mListener = null;
    }

    public interface OnFragmentInteractionListener {
        // TODO: Update argument type and name
        void onFragmentInteraction(Uri uri);
    }
}

 

在這兩個java類中,咱們onCreateView(...)經過膨脹航班片斷佈局對象來調用setupFlightsRecyclerView(...)方法的功能,以調用' FlightFragmentImpl'類的方法:
 
public void setupFlightsRecyclerView(RecyclerView recyclerView, ArrayList<AirportDataModel> dataSet)
{
    m_RecyclerView = recyclerView;

    m_RecyclerView.setHasFixedSize(true);

    m_LayoutManager = new LinearLayoutManager(getContext());
    m_RecyclerView.setLayoutManager(m_LayoutManager);

    m_RecyclerAdapter = new FlightsRecyclerAdapter(dataSet, getContext());

    m_RecyclerView.setAdapter(m_RecyclerAdapter);
}

 

 
使用回收站視圖呈現航班列表的另外一個重要方面是回收站視圖適配器的實施。因爲「到達」或「離開」航班的回收者視圖都以相似的方式執行數據呈現,咱們只須要爲特定的回收站視圖實施單個航班回收站視圖適配器。
 
此外,咱們必須爲每一個航班項目建立一個佈局,顯示特定航班的信息,如時間,目的地,航空公司代碼,航空公司徽標,國家/地區標誌和狀態:
 
flights_item.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"

    xmlns:app="http://schemas.android.com/apk/res-auto"

    xmlns:tools="http://schemas.android.com/tools"

    android:layout_width="match_parent"

    android:layout_height="wrap_content">

    <TextView

        android:id="@+id/flight_time"

        android:layout_width="51dp"

        android:layout_height="18dp"

        android:layout_marginBottom="8dp"

        android:layout_marginTop="24dp"

        android:text="3:07pm"

        android:textAppearance="@style/TextAppearance.AppCompat.Button"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintEnd_toStartOf="@+id/airlines_logo"

        app:layout_constraintStart_toStartOf="parent"

        app:layout_constraintTop_toTopOf="parent"

        app:layout_constraintVertical_bias="0.0" />

    <ImageView

        android:id="@+id/airlines_logo"

        android:layout_width="55dp"

        android:layout_height="48dp"

        android:layout_marginBottom="8dp"

        android:layout_marginTop="8dp"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintEnd_toStartOf="@+id/flight_code"

        app:layout_constraintStart_toEndOf="@+id/flight_time"

        app:layout_constraintTop_toTopOf="parent"

        app:srcCompat="@color/text_color_secondary" />

    <TextView

        android:id="@+id/flight_code"

        android:layout_width="59dp"

        android:layout_height="wrap_content"

        android:layout_marginBottom="8dp"

        android:layout_marginTop="24dp"

        android:text="TextView"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintEnd_toStartOf="@+id/flight_destination"

        app:layout_constraintStart_toEndOf="@+id/airlines_logo"

        app:layout_constraintTop_toTopOf="parent"

        app:layout_constraintVertical_bias="0.0" />

    <TextView

        android:id="@+id/flight_destination"

        android:layout_width="68dp"

        android:layout_height="wrap_content"

        android:layout_marginBottom="8dp"

        android:layout_marginTop="24dp"

        android:text="TextView"

        android:textAppearance="@style/TextAppearance.AppCompat.Body2"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintEnd_toStartOf="@+id/country_flag"

        app:layout_constraintStart_toEndOf="@+id/flight_code"

        app:layout_constraintTop_toTopOf="parent"

        app:layout_constraintVertical_bias="0.0" />

    <ImageView

        android:id="@+id/country_flag"

        android:layout_width="51dp"

        android:layout_height="42dp"

        android:layout_marginBottom="8dp"

        android:layout_marginTop="8dp"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintEnd_toStartOf="@+id/flight_status"

        app:layout_constraintStart_toEndOf="@+id/flight_destination"

        app:layout_constraintTop_toTopOf="parent"

        app:srcCompat="@android:color/black" />

    <TextView

        android:id="@+id/flight_status"

        android:layout_width="wrap_content"

        android:layout_height="20dp"

        android:layout_marginBottom="8dp"

        android:layout_marginTop="24dp"

        android:text="TextView"

        app:layout_constraintBottom_toBottomOf="parent"

        app:layout_constraintEnd_toEndOf="parent"

        app:layout_constraintStart_toEndOf="@+id/country_flag"

        app:layout_constraintTop_toTopOf="parent"

        app:layout_constraintVertical_bias="0.0" />

</android.support.constraint.ConstraintLayout>

 

 
FlightsRecyclerAdapter.java:
 
package com.epsilon.arthurvratz.airportapp;

import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.widget.ImageView;
import android.widget.TextView;

import java.text.SimpleDateFormat;
import java.util.ArrayList;

public class FlightsRecyclerAdapter extends RecyclerView.Adapter<FlightsRecyclerAdapter.ViewHolder> {
    // Provide a reference to the views for each data item
    // Complex data items may need more than one view per item, and
    // you provide access to all the views for a data item in a view holder
    private ArrayList<AirportDataModel> m_DataModel;
    private Context m_context;

    public static class ViewHolder extends RecyclerView.ViewHolder {
        // each data item is just a string in this case
        public TextView m_TimeView;
        public TextView m_FlightCodeView;
        public TextView m_DestView;
        public TextView m_StatusView;
        public ImageView m_AirlinesLogoView;
        public ImageView m_CountryFlagView;
        public ViewHolder(View v) {
            super(v);

            // Instantinate each view object in the flights_item layout
            m_TimeView = v.findViewById(R.id.flight_time);
            m_FlightCodeView = v.findViewById(R.id.flight_code);
            m_DestView = v.findViewById(R.id.flight_destination);
            m_StatusView = v.findViewById(R.id.flight_status);

            m_AirlinesLogoView = v.findViewById(R.id.airlines_logo);
            m_CountryFlagView = v.findViewById(R.id.country_flag);
        }
    }

    // Provide a suitable constructor (depends on the kind of dataset)
    public FlightsRecyclerAdapter(ArrayList<AirportDataModel> m_dataModel, Context context) {
        m_DataModel = m_dataModel; m_context = context;
    }

    // Create new views (invoked by the layout manager)
    @Override
    public FlightsRecyclerAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
                                                   int viewType) {
        // create a new view
        View v = LayoutInflater.from(parent.getContext())
                .inflate(R.layout.flights_item, parent, false);

        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    // Replace the contents of a view (invoked by the layout manager)
    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        // Retrieve specific data for each item rendered in the flights recycler view
        // and display these values in the specific views in the flights_item layout
        holder.m_TimeView.setText(new SimpleDateFormat("HH:mm")
                    .format(m_DataModel.get(position).m_Time));

        holder.m_StatusView.setText(m_DataModel.get(position).m_Status);
        holder.m_DestView.setText(m_DataModel.get(position).m_Destination);
        holder.m_FlightCodeView.setText(m_DataModel.get(position).m_Airlines.m_Flight);

        Context airlines_logo_context = holder.m_AirlinesLogoView.getContext();
        String airlines_logo = m_DataModel.get(position).m_Airlines.m_logoResId;
        holder.m_AirlinesLogoView.setImageResource(this.getResourceIdFromString(airlines_logo_context, airlines_logo));

        Context flag_context = holder.m_CountryFlagView.getContext();
        String flag = "flag" + m_DataModel.get(position).m_DestResId;
        holder.m_CountryFlagView.setImageResource(this.getResourceIdFromString(flag_context, flag));

        // Launching animation of each view in the flights_item layout
        setAnimation(holder.m_TimeView, position);
        setAnimation(holder.m_StatusView, position);
        setAnimation(holder.m_DestView, position);
        setAnimation(holder.m_FlightCodeView, position);
        setAnimation(holder.m_AirlinesLogoView, position);
        setAnimation(holder.m_CountryFlagView, position);
}

    public void setAnimation(View view, int pos)
    {

        // Instantinating the animation object
        Animation flightAnimation = android.view.animation.
                AnimationUtils.loadAnimation(m_context, R.anim.fade_interpolator);

        // Set animation duration
        flightAnimation.setDuration(700);

        // Starting the animation
        view.startAnimation(flightAnimation);
    }

    // Return the size of your dataset (invoked by the layout manager)
    @Override
    public int getItemCount() {
        return m_DataModel.size();
    }

    public int getResourceIdFromString(Context context, String resource)
    {
        return context.getResources().getIdentifier(resource,
                "drawable", context.getPackageName());

    }
}

 

' FlightsRecyclerViewAdapter'是擴展泛型' RecyclerView.Adapter<FlightsRecyclerAdapter.ViewHolder>' 專業化功能的java類。它實現了將數據綁定到回收器視圖所需的基本功能。具體來講,它實現了一個子java類「ViewHolder」,它負責經過調用flight_item佈局中的每一個視圖的對象來呈現每一個航班項目。要呈現特定項目,應用程序正在調用onBindViewHolder(...)overriden方法,以編程方式設置要由flights_item佈局內的各類視圖顯示的特定值。

添加航班時刻表模擬功能

正如咱們在本文最開始討論的那樣,除了用於呈現特定航班數據的用戶界面外,咱們還必須實施負責生成航班數據和時間線模擬的功能。在這個應用程序中,咱們使用的模式相似於在其餘編程語言和框架中常用的模型 - 視圖 - 控制器。具體來講,在這種特殊狀況下,咱們將咱們的數據模型與執行實際飛行數據集操做的某個數據控制器相結合:
 
AirportDataModel.java:
 
package com.epsilon.arthurvratz.airportapp;

import java.util.ArrayList;
import java.util.Random;

public class AirportDataModel {

    long m_Time;
    String m_Status;
    String m_Destination;
    String m_DestResId;
    Airlines m_Airlines;

    public class Airlines
    {
        public Airlines(String logoResId, String flight)
        {
            this.m_Flight = flight;
            this.m_logoResId = logoResId;
        }

        public String m_logoResId;
        public String m_Flight;
    }

    public AirportDataModel() { }

    public AirportDataModel(long curr_time, String status, String dest, String destResId, Airlines airlines)
    {
        this.m_Airlines = airlines;
        this.m_Status = status;
        this.m_Destination = dest;
        this.m_Time = curr_time;
        this.m_DestResId = destResId;
    }

    public AirportDataModel getRandomFlight() {

        Random rand_obj = new Random();

        // Instantinate airport flights destination data class object
        AirportFlightsDestData flightsData = new AirportFlightsDestData();

        // Generate random destination city index

        int flight_rnd_index = rand_obj.nextInt(
                flightsData.m_DestCities.size() - 1);

        // Get a string value of a destination city by its random index

        String destCity = flightsData.m_DestCities.get(flight_rnd_index);

        // Get specific country flag resource id associated with the name of the city
        String destResId = flightsData.getFlagResourceByDestCity(destCity);

        // Generate letters in the flight code

        char airline_code_let1 = (char) (rand_obj.nextInt('Z' - 'A') + 'A');
        char airline_code_let2 = (char) (rand_obj.nextInt('Z' - 'A') + 'A');

        String airline_code = "\0";

        // Append letters to the airline_code string value

        airline_code += new StringBuilder().append(airline_code_let1).toString();
        airline_code += new StringBuilder().append(airline_code_let2).toString();

        String flight_code = "\0";

        // Generate four digits of the flight code
        flight_code += new StringBuilder().append(rand_obj.nextInt(9)).toString();
        flight_code += new StringBuilder().append(rand_obj.nextInt(9)).toString();
        flight_code += new StringBuilder().append(rand_obj.nextInt(9)).toString();
        flight_code += new StringBuilder().append(rand_obj.nextInt(9)).toString();

        // Construct a string containing the full airline code

        flight_code = airline_code + " " + flight_code;

        // Instantinate and construct airlines data object

        Airlines airlines = new Airlines(flightsData.m_airlinesResName.get(rand_obj.nextInt(
                flightsData.m_airlinesResName.size() - 1)), flight_code);

        // Get random status string value

        String flight_status = flightsData.m_Status.get(
                rand_obj.nextInt(flightsData.m_Status.size() - 1));

        // Generate a random flight time

        int time_hours_sign = rand_obj.nextInt(2);

        // Generate an hours offset from the current system time
        int time_hours_offset = rand_obj.nextInt(48);

        // Get the current system time

        long currTimeMillis = System.currentTimeMillis();

        // Determine the random flight time in ms

        if (time_hours_sign > 0)
            currTimeMillis += time_hours_offset * 3.6e+6;
        else currTimeMillis -= time_hours_offset * 3.6e+6;

        // Instantinate and return flight item data object based on the

        // data previously generated

        return new AirportDataModel(currTimeMillis,
                flight_status, destCity, destResId, airlines);
    }

    public ArrayList<AirportDataModel> InitModel(int numOfItems)
    {

        // Init model by generated a list of random flight items
        ArrayList<AirportDataModel> newDataModel = new ArrayList<>();
        for (int index = 0; index < numOfItems; index++) {
            newDataModel.add(this.getRandomFlight());
        }

        return newDataModel;
    }

    public ArrayList<AirportDataModel> Simulate(ArrayList<AirportDataModel> dataSet)
    {

        // Get current system time in ms
        long currTimeMillis = System.currentTimeMillis();

        // Get a random current time being simulated
        currTimeMillis += new Random().nextInt(48) * 3.6e+6;

        // Perform a linear search to filter out all flights that already have taken place
        for (int index = 0; index < dataSet.size(); index++) {
            AirportDataModel item = dataSet.get(index);
            if (item.m_Time <= currTimeMillis) {

                // Remove current flight item
                dataSet.remove(item);

                // Generate and add new flight item
                dataSet.add(new Random().nextInt(dataSet.size()), getRandomFlight());
            }
        }

        return dataSet;
    }

    public ArrayList<AirportDataModel> filterByTime(
            ArrayList<AirportDataModel> dataSet, long time_start, long time_end) {
        ArrayList<AirportDataModel> targetDataSet = new ArrayList<>();

        // Perform a linear search to filter out flights which time belongs to a given range
        for (int index = 0; index < dataSet.size(); index++) {
            AirportDataModel item = dataSet.get(index);
            if (item.m_Time > time_start && item.m_Time < time_end)
                targetDataSet.add(item);
        }

        return targetDataSet;
    }
}

 

 
在本課程中,咱們實現了生成和操縱航班數據所需的全部方法。咱們須要作的第一件事就是實現一個getRandomFlight(...)生成隨機飛行數據的方法。如下方法基本上依賴於使用靜態聲明的數據。爲此,咱們建立了另外一個類,它還定義和操做特定於航班的數據:
 
AirportFlightsDestData.java:
 
package com.epsilon.arthurvratz.airportapp;

import java.util.Arrays;
import java.util.List;

public class AirportFlightsDestData
{
    public class CountryCityRel
    {
        public CountryCityRel(int countryId, int[] cityIds)
        {
            this.m_cityIds = cityIds;
            this.m_countryId = countryId;
        }

        private int m_countryId;
        private int[] m_cityIds;
    }

    public String getFlagResourceByDestCity(String destCity)
    {
        int countryId = -1;

        // Performing a linear search to find the dest city index
        for (int index = 0; index < m_DestCities.size(); index++) {
            if (m_DestCities.get(index) == destCity) {

                // Performing a linear search to find the dest country and return country-id
                for (int country = 0; country < m_CountryCityRelTable.size(); country++) {
                    int[] cityIds = m_CountryCityRelTable.get(country).m_cityIds;
                    for (int city = 0; city < cityIds.length && cityIds != null; city++)
                        countryId = (cityIds[city] == index) ?
                                m_CountryCityRelTable.get(country).m_countryId : countryId;
                }
            }
        }

        return m_countryResName.get(countryId);
    }

    public List<String> m_DestCities = Arrays.asList(
            "Atlanta", "Beijing", "Dubai", "Tokyo", "Los Angeles", "Chicago", "London", "Hong Kong",
            "Shanghai", "Paris", "Amsterdam", "Dallas", "Guangdong", "Frankfurt", "Istanbul", "Delhi", "Tangerang",
            "Changi", "Incheon", "Denver", "New York", "San Francisco", "Madrid", "Las Vegas", "Barcelona", "Mumbai", "Torronto");

    public List<String> m_countryResName = Arrays.asList(
        "peoplesrepublicofchina", "unitedstates", "unitedarabemirates", "japan", "unitedkingdom",
        "hongkong", "france", "netherlands", "germany", "turkey", "india", "indonesia",
        "singapore", "southkorea", "spain", "canada");

    public List<String> m_airlinesResName = Arrays.asList(
            "aa2", "aeromexico", "airberlin", "aircanada", "airfrance2", "airindia2", "airmadagascar",
            "airphillipines", "airtran", "alaskaairlines3", "alitalia", "austrian2", "avianca1",
            "ba2", "brusselsairlines2", "cathaypacific21", "china_airlines", "continental",
            "croatia2", "dagonair", "delta3", "elal2", "emirates_logo2", "ethiopianairlines4",
            "garudaindonesia", "hawaiian2", "iberia2", "icelandair", "jal2", "klm2", "korean",
            "lan2", "lot2", "lufthansa4", "malaysia", "midweat", "newzealand", "nwa1", "oceanic",
            "qantas2", "sabena2", "singaporeairlines", "southafricanairways2", "southwest2",
            "spirit", "srilankan", "swiss", "swissair3", "tap", "tarom", "thai4", "turkish",
            "united", "varig", "vietnamairlines", "virgin4", "wideroe1");

    public List<CountryCityRel> m_CountryCityRelTable =
            Arrays.asList(new CountryCityRel(0, new int[] { 1, 8, 12, }),
                          new CountryCityRel(1, new int[] { 0, 4, 5, 11, 19, 20, 21,23 }),
                          new CountryCityRel(2, new int[] { 2 }),
                          new CountryCityRel(3, new int[] { 3 }),
                          new CountryCityRel(4, new int[] { 6 }),
                          new CountryCityRel(5, new int[] { 7 }),
                          new CountryCityRel(6, new int[] { 9 }),
                          new CountryCityRel(7, new int[] { 10 }),
                          new CountryCityRel(8, new int[] { 13 }),
                          new CountryCityRel(9, new int[] { 14 }),
                          new CountryCityRel(10, new int[] { 15, 22, 25 }),
                          new CountryCityRel(11, new int[] { 16 }),
                          new CountryCityRel(12, new int[] { 17 }),
                          new CountryCityRel(13, new int[] { 18 }),
                          new CountryCityRel(14, new int[] { 21, 24 }),
                          new CountryCityRel(15, new int[] { 26 }));

    public List<String> m_Status = Arrays.asList("Check-In", "Canceled", "Expected", "Delayed");
}

 

如下類包含一組List聲明和靜態初始化的通用「 對象」,用於保存航班目的地城市的各類數據,以及包含航空公司徽標和國家/地區標誌的資源名稱列表。此外,如下類具備' getFlagResourceByDestCity'方法的聲明,該方法用於經過目標城市的名稱檢索特定資源名稱上的數據。
 
在機場數據模型類中,咱們必須聲明特定的數據字段變量來保存每一個航班上的數據:
 
long m_Time;
String m_Status;
String m_Destination;
String m_DestResId;
Airlines m_Airlines;

public class Airlines
{
    public Airlines(String logoResId, String flight)
    {
        this.m_Flight = flight;
        this.m_logoResId = logoResId;
    }

    public String m_logoResId;
    public String m_Flight;
}

 

此外,在這個類中,咱們實現下面的方法包括getRandomFlight(...),InitModel(...),Simulate(...)和filterByTime(...)。正如咱們已經討論的那樣,getRandomFlight方法用於爲隨機飛行生成隨機數據,稍後將添加到燈光列表中。爲此,咱們分別在ArrivalsFragment和DeparturesFragment類構造函數中調用InitModel方法,以便這些構造函數中的每個都將當即啓動機場數據模型類對象,調用此方法並接收包含到達列表的數組列表對象的本身的副本或者出發航班:
 
public ArrivalsFragment() {
    m_ArrivalsDataSet = new AirportDataModel().InitModel(20);
}

 

要在航班時刻表模擬過程當中提供航班動態更新列表,咱們必須覆蓋應用程序活動類的默認onResume方法,以下所示:
 
@Override
protected void onResume() {
    super.onResume();
    this.findViewById(R.id.search_bar).requestFocus();

    startSimulation();
}

 

在這個方法中,咱們調用另外一個Simulate(...)方法來啓動模擬過程:
 
public void Simulate() {
      simTask = new TimerTask() {
          @Override
          public void run() {
              handler.post(new Runnable() {
                  @Override
                  public void run() {

                     // Instantinate flights fragment object

                     FlightsFragment flightsFragment = (FlightsFragment)
                              m_FragmentMgr.findFragmentById(R.id.airport_fragment_container);

                      m_flightsNavigationView.setSelectedItemId(R.id.flights_now);

                      ArrayList<AirportDataModel> dataSet = null;
                      RecyclerView recyclerView = null;

                      // Determine the currently selected tab
                      TabLayout tabLayout = findViewById(R.id.flights_destination_tabs);

                      if (tabLayout.getTabAt(0).isSelected()) {
                          dataSet = flightsFragment.m_ArrivalsFragment.m_ArrivalsDataSet;
                          recyclerView = flightsFragment.m_ArrivalsFragment.m_ArrivalsRecyclerView;
                      }

                      else if (tabLayout.getTabAt(1).isSelected()) {
                          dataSet = flightsFragment.m_DeparturesFragment.m_DeparturesDataSet;
                          recyclerView = flightsFragment.m_DeparturesFragment.m_DeparturesRecyclerView;
                      }


                      // Invoking airport data model's Simulate method

                      m_AirportDataModel.Simulate(dataSet);

                      // Instantinating the new object for FlightsRecyclerAdapter class and

                      // pass the dataset object as one of the adapter's constructor parameters

                      FlightsRecyclerAdapter recyclerAdapter
                              = new FlightsRecyclerAdapter(dataSet, getBaseContext());


                      // For the current recycler view object setting the new adapter

                      recyclerView.setAdapter(recyclerAdapter);


                      // Updating the data bound to the new recycler adapter

                      recyclerAdapter.notifyDataSetChanged();
                      recyclerAdapter.notifyItemRangeChanged(0, dataSet.size());
                  }
              });
          }
      };
  }

 

在上面列出的這個方法中,咱們建立了一個由timer實例生成的計時器任務線程:
private void startSimulation()
{
    this.Simulate(); new Timer().schedule(simTask, 50, 10000);
}

 

每次系統計時器調度時,都會調用run(...)方法。在下面的方法中,咱們調用上面列出的機場數據模型的Simulate(...)方法。如下方法肯定系統時間並過濾掉時間值小於當前系統時間的全部航班項目。以後咱們建立一個前面討論過的Recycler視圖控制器的新實例,並將新的航班列表做爲其構造函數的參數傳遞給它。以後,咱們最終調用適配器的notifyDataSetChange(...)和notifyItemRangeChanged(...)方法,使正在更新的數據無效並在Recycler視圖中反映其更改。

添加自定義搜索視圖功能

正如咱們上面已經討論過的,咱們的機場應用程序實現了在應用程序主窗口最頂層呈現的搜索視圖,以經過部分匹配執行航班數據的索引搜索。此時,咱們要作的就是將搜索功能添加到如下自定義搜索視圖中。爲此,咱們onTextChanged在主應用程序的活動類中實現方法,可使用按鈕監聽器進行搜索:

public class SearchableWithButtonListener implements View.OnClickListener, TextWatcher
    {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
        }

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            // Instantinating the flights fragment object
            FlightsFragment flightsFragment = (FlightsFragment)
                    m_FragmentMgr.findFragmentById(R.id.airport_fragment_container);

            RecyclerView flightsRecyclerView = null;
            ArrayList<AirportDataModel> DataSet, oldDataSet = null;

            // Determining the currently select tab
            TabLayout tabLayout = findViewById(R.id.flights_destination_tabs);

            if (tabLayout.getTabAt(0).isSelected()) {

                // Instantinating the currently active recycler view's object
                flightsRecyclerView = flightsFragment.m_ArrivalsFragment.m_ArrivalsRecyclerView;

                // Retriving a list of arrival flights
                oldDataSet = flightsFragment.m_ArrivalsFragment.m_ArrivalsDataSet;

            }

            else if (tabLayout.getTabAt(1).isSelected()) {
<span Segoe UI",Arial,Sans-Serif; font-size: 14px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-shadow: none; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">                
                            // Instantinating the currently active recycler view's object</span>
                flightsRecyclerView = flightsFragment.m_DeparturesFragment.m_DeparturesRecyclerView;

<span Segoe UI",Arial,Sans-Serif; font-size: 14px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-shadow: none; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">                            // Retriving a list of departure flights</span>
                oldDataSet = flightsFragment.m_DeparturesFragment.m_DeparturesDataSet;
            }

            // Perform a check if the string is not empty

            if (!charSequence.toString().isEmpty()) {

                // If so instantinate the flights indexed search object and invoke doSearch method

                // to obtain the list flights which data matches by the partial match
                DataSet = new FlightsIndexedSearch().
                       doSearch(charSequence.toString(), oldDataSet);

                if (DataSet.size() == 0) {
                    DataSet = oldDataSet;
                }
            }

            else DataSet = oldDataSet;

            // Instantinate the new adapter object and pass the new filtered dataset as argument

            FlightsRecyclerAdapter recyclerAdapter
                   = new FlightsRecyclerAdapter(DataSet, getBaseContext());

<span Segoe UI",Arial,Sans-Serif; font-size: 14px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; line-height: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-shadow: none; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">                     // Setting up the new recycler view's adapter</span>

            flightsRecyclerView.setAdapter(recyclerAdapter);

<span Segoe UI",Arial,Sans-Serif; font-size: 14px; font-style: normal; font-variant: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: left; text-decoration: none; text-indent: 0px; text-shadow: none; text-transform: none; -webkit-text-stroke-width: 0px; white-space: normal; word-spacing: 0px;">                     // Reflect changes in recycler view</span>

            recyclerAdapter.notifyDataSetChanged();

            recyclerAdapter.notifyItemRangeChanged(0, DataSet.size());
        }

        @Override
        public void afterTextChanged(Editable editable) {

        }

        @Override
        public void onClick(View view) {
            if (!m_DrawerLayout.isDrawerOpen(GravityCompat.START))
                m_DrawerLayout.openDrawer(GravityCompat.START);
        }
    }

 

當用戶在搜索視圖中鍵入文本時,將onTextChanged 調用overriden事件處理方法。在此方法中,咱們肯定當前活動的回收器視圖並執行doSearch方法以經過部分匹配得到已過濾的航班物品列表。以後,咱們即時啓動新適配器並將獲取的數據集傳遞給如下適配器。最後,咱們在當前活動的回收站視圖中使這些數據無效。下面列出的代碼片斷包含doSearch方法的實現:
 
package com.epsilon.arthurvratz.airportapp;

import java.util.ArrayList;
import java.util.regex.Pattern;

public class FlightsIndexedSearch {

    public ArrayList<AirportDataModel> doSearch(String text,
               ArrayList<AirportDataModel> dataSet) {

        // Instantinating the empty flights array list object

        ArrayList<AirportDataModel> targetDataset = new ArrayList<>();

        // Performing a linear search to find all flight items which data values

        // match the specific pattern

        for (int index = 0; index < dataSet.size(); index++) {
            AirportDataModel currItem = dataSet.get(index);

            // Applying search pattern to the flight destination string value

            boolean dest = Pattern.compile(".*" + text + ".*",
                    Pattern.CASE_INSENSITIVE).matcher(currItem.m_Destination).matches();

            // Applying search pattern to the airlines flight code string value

            boolean flight = Pattern.compile(".*" + text + ".*",
                    Pattern.CASE_INSENSITIVE).matcher(currItem.m_Airlines.m_Flight).matches();

            // Applying search pattern to the flight status string value

            boolean status = Pattern.compile(".*" + text + ".*",
                    Pattern.CASE_INSENSITIVE).matcher(currItem.m_Status).matches();

            // If one of these values matches the pattern add the current item to the

            // target dataset

            if (dest != false || flight != false || status != false) {
                targetDataset.add(currItem);
            }
        }

        return targetDataset;
    }
}

 

添加底部導航欄功能

底部導航欄的功能與爲執行航班索引搜索而提供的功能很是類似。要提供此功能,咱們必須在主應用程序的活動類中設置選定偵聽器的導航項:

m_flightsNavigationView.setOnNavigationItemSelectedListener(

            new BottomNavigationView.OnNavigationItemSelectedListener() {
        @Override
        public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) {
            FlightsFragment flightsFragment = (FlightsFragment)
                    m_FragmentMgr.findFragmentById(R.id.airport_fragment_container);

            RecyclerView recyclerView = null;
            ArrayList<AirportDataModel> dataSet = null;
            FlightsRecyclerAdapter recyclerAdapter = null;

            // Determining the currently selected tab
            TabLayout tabLayout = findViewById(R.id.flights_destination_tabs);

            if (tabLayout.getTabAt(0).isSelected()) {

                // Getting the currently active recycler view object
                recyclerView = flightsFragment.m_ArrivalsFragment.m_ArrivalsRecyclerView;

                // Getting the dataset of the currently active recycle view (e.g. arrival flights dataset)
                dataSet = flightsFragment.m_ArrivalsFragment.m_ArrivalsDataSet;
            }

            else if (tabLayout.getTabAt(1).isSelected()) {
                // Getting the currently active recycler view object
                recyclerView = flightsFragment.m_DeparturesFragment.m_DeparturesRecyclerView;

                // Getting the dataset of the currently active recycle view (e.g. departure flights dataset
                dataSet = flightsFragment.m_DeparturesFragment.m_DeparturesDataSet;

            }

            // Get current system time value

            long curr_time = System.currentTimeMillis();

            if (menuItem.getItemId() == R.id.flights_prev)
            {

                // Instantinating the new recycler adapter object and pass the filtered list of previous flights items

                // returned by the filterByTime method
                recyclerAdapter = new FlightsRecyclerAdapter(m_AirportDataModel.filterByTime(dataSet,
                            curr_time - (long)3.6e+6 * 48, curr_time), getBaseContext());
            }

            else if (menuItem.getItemId() == R.id.flights_now)
            {

                // Instantinating the new recycler adapter object and pass the filtered list of current flights items

                // returned by the filterByTime method

                recyclerAdapter = new FlightsRecyclerAdapter(m_AirportDataModel.filterByTime(dataSet,
                        curr_time - (long)3.6e+6 * 24, curr_time + (long)3.6e+6 * 24), getBaseContext());

            else if (menuItem.getItemId() == R.id.flights_next)
            {

                // Instantinating the new recycler adapter object and pass the filtered list of next flights items

                // returned by the filterByTime method

                recyclerAdapter = new FlightsRecyclerAdapter(m_AirportDataModel.filterByTime(dataSet,
                        curr_time, curr_time + (long)3.6e+6 * 48), getBaseContext());
            }

            // Setting up the new recycler view's adapter

            recyclerView.setAdapter(recyclerAdapter);

            // Reflect changes in recycler view

            recyclerAdapter.notifyDataSetChanged();
            recyclerAdapter.notifyItemRangeChanged(0, dataSet.size());

            return true;
        }
    });

 

在這種方法中,咱們首先肯定當前活動的回收器視圖並接收其對象。以後,咱們正在檢查用戶是否切換了特定的底部導航按鈕,並經過調用filterByTime方法過濾掉與給定時間線條件匹配的全部航班。最後,咱們建立一個新的回收器視圖適配器並將數據集傳遞給它的構造函數,使當前活動的回收器視圖無效。

興趣點

在本文中,咱們討論了使用各類Android和Java編程語言技術建立和開發高級Android應用程序的幾個方面,包括建立自定義視圖和佈局,提供基於導航抽屜的應用程序,處理片斷和回收器視圖,實現自定義數據適配器和控制器等

相關文章
相關標籤/搜索