Android style點繼承與parent繼承踩坑

Android style定義中,咱們能夠給一個新的style樣式指定一個父style。衆所周知,能夠經過兩種方式實現:
1,新的style name中經過.將父style name與新的style name鏈接,如常見的:java

<style name="Theme.AppCompat.Light.NoActionBar">
    <item name="windowActionBar">false</item>
    <item name="windowNoTitle">true</item>
</style>
<style name="Theme.AppCompat.NoActionBar">
    <item name="windowActionBar">false</item>
    <item name="windowNoTitle">true</item>
</style>
複製代碼

2,新的style name中經過parent指定特定的父style name,如Android源碼中的:android

<style name="aerr_list_item" parent="Widget.Material.Light.Button.Borderless">
    <item name="minHeight">?attr/listPreferredItemHeightSmall</item>
    <item name="textAppearance">?attr/textAppearanceListItemSmall</item>
    <item name="textColor">?attr/textColorAlertDialogListItem</item>
    <item name="gravity">center_vertical</item>
    <item name="paddingStart">?attr/dialogPreferredPadding</item>
    <item name="paddingEnd">?attr/dialogPreferredPadding</item>
    <item name="background">?attr/selectableItemBackground</item>
    <item name="drawablePadding">32dp</item>
    <item name="drawableTint">?android:attr/colorAccent</item>
    <item name="drawableTintMode">src_atop</item>
</style>
複製代碼

以上兩種狀況,子style最終都會從父styel中繼承屬性,同時,若是子style中存在同名屬性,則覆蓋之。markdown

可是,現實中咱們每每看到另一種寫法,咋一看上去是雜糅了上述兩種方式,如appcompat中常見的:app

<style name="RtlOverlay.Widget.AppCompat.Search.DropDown" parent="android:Widget">
    <item name="android:paddingLeft">@dimen/abc_dropdownitem_text_padding_left</item>
    <item name="android:paddingRight">4dp</item>
</style>
<style name="RtlOverlay.Widget.AppCompat.Search.DropDown.Icon1" parent="android:Widget">
    <item name="android:layout_alignParentLeft">true</item>
</style>
<style name="RtlOverlay.Widget.AppCompat.Search.DropDown.Icon2" parent="android:Widget">
    <item name="android:layout_toLeftOf">@id/edit_query</item>
</style>
<style name="RtlOverlay.Widget.AppCompat.Search.DropDown.Query" parent="android:Widget">
    <item name="android:layout_alignParentRight">true</item>
</style>
複製代碼

咱們發現,這種寫法仍是很廣泛的,包括咱們項目本身在定義style時,也有很多是此類寫法。less

那麼,問題來了,這種寫法下,style最終是怎麼樣的一個規則呢? 以.寫法做爲父style,仍是以parent做爲父style,抑或二者都做爲父style,而後按照其餘規則處理屬性優先級?ide

最近正好遇到一個相關的Bug。主工程接上UI組件庫後,測試效果,寫了個簡單的TextView,其中textColor使用?attr/brand_color_primary形式指向自定義屬性。天然的,須要將當前項目Activity theme(style名是以.鏈接),指定UI組件庫中的一個基礎style做爲parent,具體經過parent指定,爲描述方便,簡單將其簡化爲:oop

<style name="A.B.C" parent="D">
....
</style>
複製代碼

但運行時出現閃退。錯誤信息以下:佈局

Process: com.corn.debug, PID: 16205
    android.view.InflateException: Binary XML file line #144 in com.corn.debug:layout/fragment_home_me: Failed to resolve attribute at index 5: TypedValue{t=0x2/d=0x7f040271 a=-1}
    Caused by: java.lang.UnsupportedOperationException: Failed to resolve attribute at index 5: TypedValue{t=0x2/d=0x7f040271 a=-1}
        at android.content.res.TypedArray.getDimensionPixelSize(TypedArray.java:783)
        at android.view.ViewGroup$MarginLayoutParams.<init>(ViewGroup.java:8226)
        at androidx.constraintlayout.widget.ConstraintLayout$LayoutParams.<init>(ConstraintLayout.java:2691)
        at androidx.constraintlayout.widget.ConstraintLayout.generateLayoutParams(ConstraintLayout.java:1916)
        at androidx.constraintlayout.widget.ConstraintLayout.generateLayoutParams(ConstraintLayout.java:482)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:1129)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1088)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:1130)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1088)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:1130)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1088)
        at android.view.LayoutInflater.rInflate(LayoutInflater.java:1130)
        at android.view.LayoutInflater.rInflateChildren(LayoutInflater.java:1088)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:686)
        at android.view.LayoutInflater.inflate(LayoutInflater.java:538)
        at com.corn.base.BaseFragment.onCreateView(BaseFragment.java:29)
        at androidx.fragment.app.Fragment.performCreateView(Fragment.java:2698)
        at androidx.fragment.app.FragmentStateManager.createView(FragmentStateManager.java:310)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1185)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1354)
        at androidx.fragment.app.FragmentManager.moveFragmentToExpectedState(FragmentManager.java:1432)
        at androidx.fragment.app.FragmentManager.moveToState(FragmentManager.java:1495)
        at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:447)
        at androidx.fragment.app.FragmentManager.executeOps(FragmentManager.java:2167)
        at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:1990)
        at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:1945)
        at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:1847)
        at androidx.fragment.app.FragmentManager$4.run(FragmentManager.java:413)
        at android.os.Handler.handleCallback(Handler.java:883)
        at android.os.Handler.dispatchMessage(Handler.java:100)
        at android.os.Looper.loop(Looper.java:224)
        at android.app.ActivityThread.main(ActivityThread.java:7520)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
複製代碼

一開始一直覺得是自定義屬性寫法有問題,最終運行時沒有找到其對應的屬性值。但最終發現,錯誤信息指示位置還沒有來到新增的測試佈局位置,在以前的其餘頁面就已經發生了閃退。測試

從報錯信息上看,應該是自定義屬性沒有取到值,經過查看閃退佈局,發現有以下寫法:ui

<ImageView
  android:id="@id/iv_arrow"
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:layout_marginEnd="?attr/insetRight"
  android:layout_marginRight="?attr/insetRight"
  android:src="@drawable/icon_arrow_right"
  app:layout_constraintBottom_toBottomOf="parent"
  app:layout_constraintEnd_toEndOf="parent"
  app:layout_constraintTop_toTopOf="parent" />
複製代碼

很顯然,應該是?attr/insetRight處出了問題。

Activity的主題是在Manifest中進行的配置,追查發現,insetRight是一個自定義屬性。

<attr name="insetRight" format="dimension" />
複製代碼

其賦值具體是在<style name="A.B.C" parent="D">....</style>中的B中。也就是說,新指定parent="D"後,最終style沒有繼承到來自B的特有屬性。

從新爲insetLeft賦值後,一切運行正常。

<style name="A.B.C" parent="D">
    <item name="insetRight">14dp</item>
   	....
</style>
複製代碼

從結果上看,咱們得出的結論以下:
style爲單繼承,parent方式優先級高於.繼承,同時存在時,前綴的做用只做爲名字。

也能夠本身寫自定義幾個style再次測試下:

<style name="MyStyle1">
    <item name="android:textColor">#00f</item>
</style>

<style name="MyStyle2">
    <item name="android:textSize">18sp</item>
</style>

<style name="MyStyle2.SubStyle" parent="MyStyle1">
    <item name="android:textStyle">bold</item>
</style>
複製代碼

從提示上看,MyStyle2是沒有生效的。同時,運行後,也確實只有SubStyleMyStyle1的效果。此時,咱們直接將MyStyle2.SubStyle理解成新的style name便可。

Google官方文檔上,其實也有相似描述,平時沒太注意,本覺得只是同時存在.命名與parent時,是二者屬性的都會繼承,而後再按照parent的爲準,最後纔是以本身的屬性爲最終標準的。如今看確實理解錯了。

附上官網文檔。
Styles and Themes

其中關鍵的描述爲:

踩坑留念。

end~

相關文章
相關標籤/搜索