在本文中,咱們將演示如何使用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 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 Studio IDE主窗口左上角的應用程序解決方案樹。一般,解決方案樹顯示正在建立的項目的內容,該內容與保存到特定位置的目錄結構徹底對應(例如,「 D:\ AirportApp 」)。
文件夾「清單」是顯示在應用解決方案樹頂部的第一個文件夾。它基本上只包含一個文件' 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 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 }
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' }
<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>
#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
## 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
<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>
<?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>
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); } }
此時,咱們的主要目標是建立機場計劃模擬應用程序的主要佈局設計草圖。更具體地說,主應用程序的佈局將具備如下外觀:
<?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>
<?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>
<?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>
<?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>
最後,由' NavigationView'及其藍圖呈現的應用程序的抽屜佈局將具備如下外觀:
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>
public class SearchableWithButtonView extends View { // SearchView basic functionality implementation java-code goes here... }
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()); }
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>
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) { } }
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); } }
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(); } }
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(); } }
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(); //... }
在回收站視圖中渲染航班列表是咱們即將在本文中討論的最終機場應用程序的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>
<?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>
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); } }
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); }
<?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>
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()); } }
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; } }
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"); }
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 ArrivalsFragment() { m_ArrivalsDataSet = new AirportDataModel().InitModel(20); }
@Override protected void onResume() { super.onResume(); this.findViewById(R.id.search_bar).requestFocus(); startSimulation(); }
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()); } }); } }; }
private void startSimulation() { this.Simulate(); new Timer().schedule(simTask, 50, 10000); }
正如咱們上面已經討論過的,咱們的機場應用程序實現了在應用程序主窗口最頂層呈現的搜索視圖,以經過部分匹配執行航班數據的索引搜索。此時,咱們要作的就是將搜索功能添加到如下自定義搜索視圖中。爲此,咱們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); } }
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應用程序的幾個方面,包括建立自定義視圖和佈局,提供基於導航抽屜的應用程序,處理片斷和回收器視圖,實現自定義數據適配器和控制器等