Kotlin Android Extensions: 與 findViewById 說再見 (KAD 04) -- 更新版


作者:Antonio Leiva

時間:Aug 16, 2017

原文鏈接:https://antonioleiva.com/kotlin-android-extensions/

 

Kotlin1.1.4版本 發布后,原作者依據 Kotlin 新版本的一系列新特性,以及有讀者關於如何在 Fragment 和 custom view 中使用Kotlin 等等向他提問,原作者決定針對這些內容進行更新、重寫幾個月的文章。

 

在這篇重寫的文章中,他涵蓋了所有KAE(1.1.4版本前后)可以完成的事情。現在你會喜歡在任何類(不只是activity, fragment 或 view)使用它們,包括一個新的注釋來實現Parcelable。

 

 

你可能會厭倦日復一日地使用findViewById來恢復Androidview。或許你已放棄了,並開始使用著名的Butterknife庫。那么你會喜歡上Kotlin Android Extensions

 

Kotlin Android Extensions:這是什么?

 

 

Kotlin Android ExtensionsKotlin的一個插件,它包含在普通的那個插件中,這就允許以驚人的無縫方式從ActivitieFragmentView中恢復View

 

該插件將生成一些額外的代碼,允許你訪問布局XMLView,就像它們是在布局中定義的屬性一樣,你可以使用 id 的名稱。

 

 

它還構建本地視圖緩存。所以首次使用一個屬性時,它會做一個普通的findViewById。而接下來,View則是從緩存中恢復,因此訪問速度更快。

 

 

怎樣使用它們

 

讓我們看看它的使用是多么容易。我會以一個Activity做第一個例子:

 

Kotlin Android Extensions集成到我們的代碼中

 

雖然這個插件被集成到普通的插件(你不需要安裝新的插件)中,但是,你要使用它,還必須在Android模塊中添加額外的應用:

1 apply plugin: 'com.android.application'
2 apply plugin: 'kotlin-android'
3 apply plugin: 'kotlin-android-extensions'

 

只需要做這些,可以開始使用它了。

 

XML中恢復 View

 

從這一刻起,恢復View就像將你在XML中定義的View ID直接用於你的Activity一樣簡單。

 

假設你有一個這樣的XML

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <FrameLayout
 3     xmlns:android="http://schemas.android.com/apk/res/android"
 4     android:layout_width="match_parent"
 5     android:layout_height="match_parent">
 6  
 7     <TextView
 8         android:id="@+id/welcomeMessage"
 9         android:layout_width="wrap_content"
10         android:layout_height="wrap_content"
11         android:layout_gravity="center"
12         android:text="Hello World!"/>
13  
14 </FrameLayout>

 

如你所見,TextViewwelcomeMessage ID。

 

在你的 MainActivity 中,僅僅需要這樣編寫:

1 override fun onCreate(savedInstanceState: Bundle?) {
2     super.onCreate(savedInstanceState)
3     setContentView(R.layout.activity_main)
4  
5     welcomeMessage.text = "Hello Kotlin!"
6 }

 

為了能夠使用它,你需要如下的import,而IDE能夠自動加入它。不是很容易嗎?

1 import kotlinx.android.synthetic.main.activity_main.*

 

如上所述,生成的代碼將包括View緩存,因此再次詢問視圖,就不需要另一個findViewById

 

讓我們看看其背后都是什么。

 

Kotlin Android Extensions背后的魔力

 

 

在你開始使用Kotlin時,理解所使用特性時生成的字節碼是非常有趣。這也有助於你了解在你的決定背后隱藏的成本

 

Tools-->Kotlin下,有一個強大的功能,稱為顯示Kotlin字節碼(Show Kotlin Bytecode。如果你點擊它,你將看到當你打開的類文件編譯后生成的字節碼。

 

 

對大多數人來說,字節碼並不是很有用,但是還有另一個選擇:Decompile(反編譯)

 

 

這將顯示Kotlin生成的字節碼的Java表示所以你可以或多或少地了解你寫的Kotlin代碼對應Java的代碼。

 

 

在我生成的Activity中使用它,並查看由Kotlin Android Extensions生成的代碼。

 

有趣的是這一個:

 

 1 private HashMap _$_findViewCache;
 2 ...
 3 public View _$_findCachedViewById(int var1) {
 4    if(this._$_findViewCache == null) {
 5       this._$_findViewCache = new HashMap();
 6    }
 7  
 8    View var2 = (View)this._$_findViewCache.get(Integer.valueOf(var1));
 9    if(var2 == null) {
10       var2 = this.findViewById(var1);
11       this._$_findViewCache.put(Integer.valueOf(var1), var2);
12    }
13  
14    return var2;
15 }
16  
17 public void _$_clearFindViewByIdCache() {
18    if(this._$_findViewCache != null) {
19       this._$_findViewCache.clear();
20    }
21  
22 }

 

 

這就是我們正在談論的視圖緩存(View Cache

 

 

在要求查看時,首先會嘗試在緩存中找。如果它不存在,它會查找它,並將其添加到緩存。非常簡單。

 

 

此外,它還添加了一個清除緩存的功能:clearFindViewByIdCache。如果你必須重建視圖,因為舊視圖將不再有效,就可以使用它。

 

 

那么這一行:

 

1 welcomeMessage.text = "Hello Kotlin!"

 

 

則轉換為:

 

1 ((TextView)this._$_findCachedViewById(id.welcomeMessage)).setText((CharSequence)"Hello Kotlin!");

 

 

因此,屬性不是真實的,插件不會為每個視圖生成屬性。在編譯期間,只需要替換代碼即可訪問視圖緩存,將其轉換為正確的類型並調用該方法。

 

Fragment的 Kotlin Android Extensions

 

 

這個插件也能夠用於Fragment

 

Fragment的問題是View可以重新創建,而Fragment實例卻保持有效。這會怎么樣?這就意味着緩存中的視圖將不再有效。

 

 

 

如果我們把View移動到Fragment中,我們來看看生成的代碼。這是我創建這個簡單的Fragment,它使用與上面寫的相同的XML

 1 class Fragment : Fragment() {
 2  
 3     override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
 4         return inflater.inflate(R.layout.fragment, container, false)
 5     }
 6  
 7     override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
 8         super.onViewCreated(view, savedInstanceState)
 9         welcomeMessage.text = "Hello Kotlin!"
10     }
11 }

 

onViewCreated中,我再次更改TextView的文本。生成怎樣的字節碼呢?

 

 

一切都與Activity中的一樣,僅有一個微小的區別:

1 // $FF: synthetic method
2 public void onDestroyView() {
3    super.onDestroyView();
4    this._$_clearFindViewByIdCache();
5 }

 

View被銷毀時,此方法將調用clearFindViewByIdCache,所以我們是安全的!

 

自定義View的Kotlin Android extensions

 

在自定義視圖下,Kotlin Android extensions非常類似。假設我們有這樣的View

 1 <merge xmlns:android="http://schemas.android.com/apk/res/android"
 2               android:orientation="vertical"
 3               android:layout_width="match_parent"
 4               android:layout_height="match_parent">
 5     
 6     <ImageView
 7         android:id="@+id/itemImage"
 8         android:layout_width="match_parent"
 9         android:layout_height="200dp"/>
10     
11     <TextView
12         android:id="@+id/itemTitle"
13         android:layout_width="match_parent"
14         android:layout_height="wrap_content"/>
15  
16 </merge>

 

我創建一個非常簡單的自定義視圖,並使用@JvmOverloads注釋的新Intent生成構造函數:

 

 

1 class CustomView @JvmOverloads constructor(
2         context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
3 ) : LinearLayout(context, attrs, defStyleAttr) {
4  
5     init {
6         LayoutInflater.from(context).inflate(R.layout.view_custom, this, true)
7         itemTitle.text = "Hello Kotlin!"
8     }
9 }

 

在上面的示例中,我將文本更改為itemTitle。生成的代碼應該嘗試從緩存中查找View。再次復制所有相同的代碼已無意義了,但是你可以在更改文本的行中看到這一點:

1  ((TextView)this._$_findCachedViewById(id.itemTitle)).setText((CharSequence)"Hello Kotlin!");

 

太棒了!在自定義View中,我們只是首次調用findViewById

 

從另一個View中恢復View

 

Kotlin Android Extensions提供的最后一個選擇是直接從另一個視圖使用屬性

 

 

我用與上節非常相似的布局。假設一下這是在適配器(Adapter)中對實例進行inflate

 

使用此插件,你還可以直接訪問子視圖(subview):

1 val itemView = ...
2 itemView.itemImage.setImageResource(R.mipmap.ic_launcher)
3 itemView.itemTitle.text = "My Text"

 

雖然這個插件會幫你填寫import,但是在這方面還是有點不同:

 

1 import kotlinx.android.synthetic.main.view_item.view.*

 

 

這里有幾件事情,你需要了解:

  • 在編譯時,你可以從任何其他視圖(View)引用任何視圖。 這意味着你可以引用一個視圖,該視圖不是其的直接子節點。 但是,當試圖嘗試恢復不存在的視圖時,執行會失敗。
  • 在這種情況下,視圖不像ActivityFragment那樣被緩存。

 

 

為什么會這樣?與之前的情況相反,這里的插件沒有地方可以為緩存生成所需的代碼。

 

 

 

 

 

 

如果你再次查看代碼,它是當從視圖調用屬性時由插件生成的,你會看到:

 

1 ((TextView)itemView.findViewById(id.itemTitle)).setText((CharSequence)"My Text");

 

 

如你所見,沒有調用緩存。如果你的視圖很復雜,而且你是在適配器中使用,請注意,它可能會影響性能。

 

 

或者你可以選擇:Kotlin 1.1.4

 

版本1.1.4中的Kotlin Android Extensions

 

 

 

 

 

Kotlin新版本中,Android Extensions已經引入了一些新的有趣的功能:任何類中的緩存(有趣的包括ViewHolder)和一個新的@Parcelize注釋。還有一種方法可以自定義生成的緩存。

 

稍后,我們會看到它們,但你需要知道這些功能並不是最終的版本所以你需要將它們添加到build.gradle中來啟動它們

 

1 androidExtensions {
2     experimental = true
3 }

 

 

ViewHolder(或任何自定義類)中使用它

現在,你可以通過簡單的方式在任何類中構建緩存。唯一要求是你的類要實現LayoutContainer接口。該接口將提供一個視圖,它是由插件查找的子視圖。假設,我們有一個ViewHolder,它保持前面示例中講述的布局視圖。你只需要做:

 

1 class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), 
2         LayoutContainer {
3  
4     fun bind(title: String) {
5         itemTitle.text = "Hello Kotlin!"
6     }
7 }

 

 

containerView是我們從LayoutContainer接口覆蓋的。但這就是你需要的。

 

 

之后,你就可以直接訪問視圖,無需使用前置itemView來訪問子視圖。

 

 

另外,如果你檢查生成代碼,你會看到它從緩存中獲取視圖:

 

1 ((TextView)this._$_findCachedViewById(id.itemTitle)).setText((CharSequence)"Hello Kotlin!");

 

 

這里,我已經在ViewHolder上使用了它,但是你可以看到這是通用的,完全可以在任何類中使用。

 

Kotlin Android Extension實現Parcelable

 

用新的@Parcelize注釋,你可以以非常簡單的方式使任何類都能實現Parcelable

 

 

你只需要添加注釋,插件就會完成所有復雜的工作:

 

1 @Parcelize
2 class Model(val title: String, val amount: Int) : Parcelable

 

 

然后,如你所知,你可以將對象添加到任何intent中:

1 val intent = Intent(this, DetailActivity::class.java)
2 intent.putExtra(DetailActivity.EXTRA, model)
3 startActivity(intent)

 

並且在任何位置(在這種情況下是在在目標Activity)上,從intent恢復對象:

1 val model: Model = intent.getParcelableExtra(EXTRA)

 

自定義構建緩存

 

在這組實驗中包含的新功能是一個新注釋@ContainerOptions。這允許你以自定義方式構建緩存,甚至阻止類創建它

 

 

默認情況下,它將用Hashmap,如我們之前看到的那樣。但是,這可以用Android框架的SparseArray來改變,這在某些情況下可能會更有效率。如果由於某種原因,你不需要一個類的緩存,你也可以使用該選項。

 

這是它的用法:

 

1 @ContainerOptions(CacheImplementation.SPARSE_ARRAY)
2 class MainActivity : AppCompatActivity() {
3 ...
4 }

 

 

目前,已有的選擇是:

 

1 public enum class CacheImplementation {
2     SPARSE_ARRAY,
3     HASH_MAP,
4     NO_CACHE;
5  
6     ...
7 }

 

 

結論

 

 

 

 

 

 

 

 

 

 

你已經看到Kotlin處理Android視圖的如此簡易。通過一個簡單的插件,我們可以拋棄那些在inflation后視圖恢復的所有可怕的代碼。此插件將為我們創建所需的屬性,而不會出現任何問題。

 

此外,Kotlin 1.1.4增加了一些有趣的特性,這些特性在以前的插件沒有覆蓋的情況下,是非常有用。

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM