Android開發者們!是時候讓 Android Tools 屬性拯救你了

日常開發過程中,我們都會遇到這樣一種場景:

我們寫出的 UI 效果在對接數據之前需要提前進行預覽,進而調整 UI 細節和排版問題。

我們一般的做法是什麼樣的?

如果存在像 TextView 或者 ImageView 這種基礎控件,你是不是還在通過

android:text="xxx"android:class="lazy" src="//p2.ttnews.xyz/loading.gif" data-original="@drawable/xxx"

的方式來測試和預覽UI效果?當然你肯定也會遇到這些“髒數據”給你帶來的困擾:測試的時候某些地方出現了本不該出現的數據,事後可能一拍腦門才發現,原來是佈局中控件預覽數據沒有清除導致的。

如果是 RecyclerView,在後臺接口尚能測試的情況下,你是否又要自己生成“假數據”並手寫 Adapter 呢?

這時候你不禁會問:有沒有一種方法,既能夠做到佈局時預覽數據方便排版,又能夠在對接真實數據運行後動態替換和移除這些無關數據呢?

噹噹噹當!Android的 Tools attributes 應運而生。老規矩,我們先來看一個效果:

Android開發者們!是時候讓 Android Tools 屬性拯救你了

What?你在耍我嗎?這麼簡單的列表拿出來幹嘛?哈哈,客觀不要著急。這個並不難實現,倘若我說這裡並沒有寫一行 Java 或者 Kotlin 代碼就實現了此效果,而只是在佈局頁面預覽,你敢信嗎?上圖只是冰山一角,下面這張圖才是全貌:

Android開發者們!是時候讓 Android Tools 屬性拯救你了

下面會帶大家一步步實現上述功能,首先,讓我們從頭說起。

認識 Tools attributes

Tools attributes 即以 tools 開始的命名空間,舉個我們最常見到的例子:


<android.support.constraint.constraintlayout> 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
/<android.support.constraint.constraintlayout>
複製代碼

大家肯定平時都會見到 tools:context=".XXXActivity 這個系統默認為我們生成的配置。一般來說,只有根視圖才能使用這個屬性,它指定了當前佈局默認是與哪個 Activity 相關聯,使得佈局能夠獲取到綁定 Activity 的一些信息,比如 Theme 等等,而且當你在佈局中給子 View 添加 onClick 事件時,相應的方法代碼會插入到這個 Activity 中。 Android studio 支持很多在 XML 文件中以 tools 為命名空間的屬性,當構建 App 時這些屬性會被擦除,對 APK 的大小和運行時行為沒有任何影響,這也就是我們文章最初想要的結果。

細說 Tools attributes

在具體介紹 Tools attributes 之前,我們需要先了解如何引入 Tools 的命名空間並使用,很簡單,只需要在 XML 佈局文件的根元素中添加即可:

<roottag> xmlns:tools="http://schemas.android.com/tools" >
/<roottag>

這些工具屬性大概可以分為以下三類:

Error handling attributes

錯誤和警告處理屬性。這類屬性常被用來規避被 lint 檢查出的一些錯誤提示以及警告。下面讓我們看一些常見的例子:

  • tools:ignore
  • 主要用來忽略一些 lint 產生的警告信息,並支持一些屬性,例如:
<resources>
<string>ConstraintSample/<string>
<string>header image/<string>
/<resources>
  • 這個對於 Android studio 升級到 3.0 以上的小夥伴來說應該是很常見了,如果我們項目中涉及到國際化支持,那麼編譯器就會提示我們為每一種語言做適配,不能“厚此薄彼”,如果我們某些 string 只需要支持一種語言,只需要像上面那樣添加
    tools:ignore="MissingTranslation 即可。相似的例子還可以在使用 ImageView 的時候看到:
<imageview> android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:class="lazy" data-original="@drawable/ic_person_off"
tools:ignore="contentDescription" />
/<imageview>
  • tools:targetApi
  • 這個屬性的功能和 Java 代碼中的註解 @TargetApi 是一樣的:它指定了當前控件或元素支持的 API 級別,屬性值可以是 API Code 名或者 API 常數值,它支持一切屬性。如:我們都知道,android:elevation 屬性是在 API 21 版本以上才支持的,所以我們可以通過以下代碼規避 lint 的警告:
<button> android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="4dp"
tools:targetApi="lollipop"/>
/<button>
  • tools:locale
  • 這個屬性主要用在 <resource> 標籤內,用來指定當前資源中默認為哪種語言和區域,進而規避語言拼寫的檢測,比如你可以指定
    values/strings.xml 文件的默認語言是西班牙語而不是英語:
<resources> tools:locale="es">
/<resources>

Resource shrinking attributes

資源壓縮屬性。關於此類屬性的用法我們已經在之前的[一篇文章帶你領略Android混淆的魅力]

我們可以通過 tools:shrinkModetools:keep 屬性來分別指定資源壓縮的模式和需要保留的不被壓縮的資源 ,還可以通過 tools:discard 屬性來指定需要保留的資源,與 keep 功能類似:


<resources> tools:shrinkMode="strict"
tools:keep="@layout/activity_video*,@layout/dialog_update_v2"
tools:discard="@layout/unused_layout,@drawable/unused_selector" />
/<resources>

下面就到本篇文章的重頭戲了,注意,前方高能來襲!

Design-time View Attributes

這就是我們先前效果圖中的重要功臣了,即:佈局設計時的控件屬性。這類屬性主要作用於 View 控件,如上文所說的 tools:context 就是“成員”之一,下面我們來介紹其他重要成員。

在此之前,我們需要先揭開 tools 命名空間的另一層神秘面紗:tools: 可以替換任何以 android: 為前綴的屬性,併為其設置樣例數據sample data)。當然,正如我們前面所說,tools 屬性只能在佈局編輯期間有效,App真正運行後就毫無意義了,所以,我們就可以像下面這樣來在運行前預覽佈局效果

Android開發者們!是時候讓 Android Tools 屬性拯救你了

上圖對應的佈局文件為:

Card_item_layout.xml


<android.support.constraint.constraintlayout> xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="@android:color/white"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackground"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:layout_marginEnd="2dp"
tools:targetApi="m"
tools:ignore="UnusedAttribute">
<imageview> android:id="@+id/card_item_avatar"
android:layout_width="38dp"
android:layout_height="38dp"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp"
app:layout_constraintVertical_bias="0.0"
tools:ignore="ContentDescription"
tools:srcCompat="@drawable/user_other"/>
<textview> android:id="@+id/card_item_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="@+id/card_item_title"
app:layout_constraintEnd_toEndOf="@+id/card_item_title"
app:layout_constraintHorizontal_bias="0.0"
android:textSize="12sp"
android:textColor="@color/username_text_color"
android:layout_marginEnd="16dp"
android:paddingEnd="16dp"
tools:text="水月沐風" />
<textview> android:id="@+id/card_item_title"

android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="@color/title_text_color"
app:layout_constraintStart_toEndOf="@+id/card_item_avatar"
android:layout_marginStart="12dp"
app:layout_constraintTop_toBottomOf="@+id/card_item_username"
android:layout_marginTop="8dp"
android:maxLines="1"
tools:text="今天上海的夜色真美!"/>
<textview> android:id="@+id/card_item_content"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="24dp"
app:layout_constraintTop_toBottomOf="@+id/card_item_avatar"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp"
app:layout_constraintVertical_bias="1.0"
android:maxLines="3"
android:ellipsize="end"
android:textColor="@color/content_text_color"
android:textStyle="normal"
app:layout_constraintEnd_toEndOf="@+id/card_item_bottom_border"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
app:layout_constraintHorizontal_bias="0.0"
tools:text="人生若只如初見,何事秋風悲畫扇..."/>
<imageview> android:id="@+id/card_item_poster"
android:layout_width="0dp"
android:layout_height="200dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/card_item_content"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp"
android:scaleType="centerCrop"
android:layout_marginTop="8dp"
android:layout_marginStart="16dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp"
app:layout_constraintVertical_bias="0.0"
tools:ignore="ContentDescription"
android:visibility="visible"
tools:class="lazy" data-original="@drawable/shanghai_night"/>
<view> android:id="@+id/card_item_bottom_border"
android:layout_width="0dp"
android:layout_height="2dp"
app:layout_constraintTop_toBottomOf="@+id/card_item_poster"

android:background="#ffededfe"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"/>
<textview> android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/card_item_date"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginEnd="16dp"
android:textColor="@color/date_text_color"
android:textSize="12sp"
tools:text="2019-08-10"/>
/<textview>/<view>/<imageview>/<textview>/<textview>/<textview>/<imageview>/<android.support.constraint.constraintlayout>

通過上面代碼我們可以發現:通過 對 TextView 使用 tools:text 屬性代替 android:text就可以實現文本具體效果的預覽,然而這項設置並不會對我們 App 實際運行效果產生影響。同理,通過將 tools:src 作用於 ImageView 也可以達到預覽圖片的效果。此外。我們還可以對其他以 android: 為前綴的屬性進行預覽而不影響實際運行的效果,例如:上面佈局代碼中的底部分割線 <view>,我們想將其在 App 實際運行的時候隱藏掉,但我們還是需要知道它的預覽效果和所佔高度:/<view>

<view> android:id="@+id/card_item_bottom_border"
android:layout_width="0dp"

android:layout_height="2dp"
android:visibility="gone"
tools:visibility="visible"
tools:layout_height="8dp"
app:layout_constraintTop_toBottomOf="@+id/card_item_poster"
android:background="#ffededfe"
app:layout_constraintEnd_toEndOf="parent"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"/>
/<view>

如上所示,通過 tools:visibilitytools:layout_height 就可以僅在佈局預覽情況下改變 View 的狀態和高度。雖然上述情況比較少用,但是希望大家也能夠知道,tools: 可以替代所有 android: 修飾的屬性。

下面再列舉一些其他會常用到的屬性。

  • tools:layout
  • 這個屬性只能用於 fragment 控件中,如果我們的 activity 佈局文件中聲明瞭 <fragment> 控件,我們就可以通過 tools:layout=”@layout/fragment_main” 來在當前 activity
    佈局中預覽 fragment 中的佈局效果。
  • tools:showIn
  • 這個屬性就比較好玩了,它可以指定其他佈局文件像 <include> 組件一樣在當前佈局文件中使用和預覽 <include> 控件的實際效果。例如,我們 card_item_layout.xml 作為 showIn 的對象給 show_in_layout.xml 佈局使用,然後我就可以看到 show_in_layout.xml 中如下效果:
Android開發者們!是時候讓 Android Tools 屬性拯救你了

tools:menu

  • 這個屬性可以給當前佈局預覽器的 Toolbar 添加多個菜單項,但僅限於佈局文件的根節點元素。如:

<linearlayout> xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:menu="menu1,menu2" />
/<linearlayout>
  • tools:maxValue | tools:minValue
  • 這兩個屬性僅用於 <numberpicker>,可以在預覽時指定其最大值和最小值:
<numberpicker> xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/numberPicker"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:minValue="0"
tools:maxValue="10" />
/<numberpicker>
  • tools:listitem | tools:listheader | tools:listfooter | tools:listCount
  • 下面來講一下列表相關組件的 tools 屬性。上面四個屬性僅用於
    <adapterview> 及其子類(如:ListViewRecyclerView)。然而,它們內部仍有一些使用限制:tools:listCount 僅用於 RecyclerViewtools:listheadertools:listfooter僅限於 ListView;至於 tools:listitem 屬性二者皆可用。之前的效果圖就是藉助於此屬性:

activity_main.xml:


<android.support.constraint.constraintlayout> 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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:background="#ffEBEBEF">
<android.support.v7.widget.toolbar> android:id="@+id/toolbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="?attr/colorPrimary"
android:theme="?attr/actionBarTheme"
android:minHeight="?attr/actionBarSize"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:elevation="1dp"
app:title="@string/app_name"
app:layout_constraintTop_toTopOf="parent"

app:layout_constraintHorizontal_bias="0.0"/>
<android.support.v7.widget.recyclerview> android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:scrollbars="vertical"
app:layout_constraintTop_toBottomOf="@+id/toolbar"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="0.0"
tools:listitem="@layout/card_item_layout"/>
/<android.support.v7.widget.recyclerview>/<android.support.v7.widget.toolbar>/<android.support.constraint.constraintlayout>

sample data

樣本數據 功能,可以通過 @tools:sample 來使用該屬性,也屬於 design-time view attributes。但它並非只是一個屬性那麼簡單,更應該算是一個“工具利器”,所以會將其單獨拿出來詳細介紹。這個工具是本年度 Google 大會上 Android 開發團隊特別介紹的一個新推屬性。它有什麼用呢?用處大了!先前的佈局預覽使用的數據都是我們直接在佈局控件中註明或者在 strings.xml 文件中給出的,這一就會產生一些髒數據,不利於我們後期的處理。而有了 sample data,我們就可以對佈局預覽器中的

“樣本數據”進行集中保存和管理了。

一、sample data 的使用

Android studio 已為我們提供了以下樣本數據,我可以直接拿來使用:

Attribute valueDescription of placeholder data@tools:sample/full_namesFull names that are randomly generated from the combination of@tools:sample/first_names and @tools:sample/last_names.@tools:sample/first_namesCommon first names.@tools:sample/last_namesCommon last names.@tools:sample/citiesNames of cities from across the world.@tools:sample/us_zipcodesRandomly generated US zipcodes.@tools:sample/us_phonesRandomly generated phone numbers with the following format: (800) 555-xxxx.@tools:sample/loremPlaceholder text that is derived from Latin.@tools:sample/date/day_of_weekRandomized dates and times for the specified format.@tools:sample/date/ddmmyy@tools:sample/date/mmddyy@tools:sample/date/hhmm@tools:sample/date/hhmmss@tools:sample/avatarsVector drawables that you can use as profile avatars.@tools:sample/backgrounds/scenicImages that you can use as backgrounds.

上述表格中不僅有常用文本數據和日期等數據,還提供了一些圖片樣本數據,那麼該如何使用呢?很簡單,只需要切換到佈局預覽界面,並拖動一個 ImageView 到面板上,然後 Android studio 就會彈出如下界面:

Android開發者們!是時候讓 Android Tools 屬性拯救你了

然後選擇 avatars 或者 background/scenic 數據源就可以了。當然你也可以通過 xml 代碼形式來設置:


<android.support.constraint.constraintlayout> xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@android:color/white"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<imageview> android:layout_width="36dp"
android:layout_height="36dp"
android:id="@+id/imageView"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="16dp"
tools:srcCompat="@tools:sample/avatars"/>
<textview> android:layout_width="0dp"
android:layout_height="wrap_content"
android:id="@+id/textView" app:layout_constraintStart_toEndOf="@+id/imageView"
android:layout_marginStart="8dp" app:layout_constraintEnd_toEndOf="parent" android:layout_marginEnd="16dp"
app:layout_constraintTop_toTopOf="parent"
tools:text="@tools:sample/lorem/random"
tools:maxLines="8"
android:ellipsize="end"
android:textSize="14sp"
android:textColor="@color/title_color" android:layout_marginTop="16dp"
app:layout_constraintHorizontal_bias="0.0"/>
/<textview>/<imageview>/<android.support.constraint.constraintlayout>

同樣地,TextView 也可以通過 @tools:sample/lorem/random 來添加樣本數據,如此一來,效果如下:

Android開發者們!是時候讓 Android Tools 屬性拯救你了

哈哈,還不錯吧。那麼問題來了,如果我們想要用自己的樣本數據源呢?

二、自定義 sample data

如果我們想要是用自己定製化的樣例數據,該如何做呢?其實很簡單,只需要在 app 目錄下創建 sample data directory 就可以了:

Android開發者們!是時候讓 Android Tools 屬性拯救你了

接下來,我們就可以在裡面定製我們自己的數據了,在剛建好的 sampledata 目錄下新建一個 txt 格式的數據文件,如 users,然後在裡面創建如下數據:

Android開發者們!是時候讓 Android Tools 屬性拯救你了

如此這般,同理創建我們的其他數據: titlesdescriptions,然後在上述 card_item_layout.xml 佈局文件中替換並使用自己的數據源:

<textview> android:id="@+id/card_item_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="@+id/card_item_title"
app:layout_constraintEnd_toEndOf="@+id/card_item_title"
app:layout_constraintHorizontal_bias="0.0"
android:textSize="12sp"
android:textColor="#8989ae"
android:layout_marginEnd="16dp"
android:paddingEnd="16dp"
tools:text="@sample/users" />
/<textview>

這裡僅以其中一個 TextView 舉例說明,其他同理。

什麼?你以為到這裡就講完了?哈哈,少年,看你骨骼驚奇,再教你一招來上天入地:通過自定義 Json 格式的 數據來為控件綁定數據

Android開發者們!是時候讓 Android Tools 屬性拯救你了

打完收工,還是上面的例子,來看看如何通過 json 數據來綁定:

<textview> android:id="@+id/card_item_username"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="@+id/card_item_title"
app:layout_constraintEnd_toEndOf="@+id/card_item_title"
app:layout_constraintHorizontal_bias="0.0"
android:textSize="12sp"
android:textColor="#8989ae"
android:layout_marginEnd="16dp"
android:paddingEnd="16dp"
tools:text="@sample/sample.json/data/username" />
/<textview>

以上操作的時候 Android studio 都會自動提示 sampledata 路徑下的數據文件,Json 格式亦會提示到具體字段。

最後

從 Google Android 官方的進一步動向來看,後續Android更新過程中,佈局編輯器將更加強大。從 Tools attributes 到 ConstraintLayout1.1 再到即將到來的 ConstraintLayout2.0 中的 MotionLayout,我們可以預見:Android 將在 UI 渲染和動畫實現方面進一步解放我們的雙手。後續我將繼續為大家帶來系列文章,敬請期待。

最後

如果你也想提升自己,升職加薪,不如在下班時間花點時間來自我學習吧。

在我學習的過程中,最開始是在網上找了很多資料,畢竟這些資料是我們開始最快速的學習方法,這裡我放上我這些年在網上收集到的資料,然後再以我的工作經驗給大家總結一下,讓你們少走些彎路,提取一些目前互聯網公司最主流的Android開發架構技術,希望能幫助到大家,需要的讀者可以關注私信我免費【架構】獲取。

Android開發者們!是時候讓 Android Tools 屬性拯救你了

Android開發者們!是時候讓 Android Tools 屬性拯救你了


分享到:


相關文章: