Android 5.0以下XML定義的drawable不識別?attr/屬性的解決思路

ifadai發表於2017-02-23

最近寫個小專案,之前一直使用5.0 和7.0系統的手機測試正常,換到4.4系統卻報Resources$NotFoundException錯誤,很是疑惑,因為錯誤指向的drawable檔案正常啊,把log複製到搜尋引擎查了一下,也沒什麼收穫,但查到有個類似的問題,是:TextView.setText()時,直接給了一個int型的引數,導致系統把這個引數當做Resource id去處理了。 根據這個思路,我開啟我的drawable檔案,試著把裡面的"?attr/colorPrimary"換成“@color/顏色資源”,結果沒有報錯。網上查了一下,果然是5.0以下,在drawable中無法正確引用?attr/的值(我的想法是:系統本應該取到顏色值,可卻把這個值做resource id處理了,然後根據id去尋找資源時,沒有找到資源)。

可因為我在軟體中加入了主題更換的功能,所以要獲取實時的?attr/colorPrimary,而不是固定的color資源,於是想了些解決辦法:

 1、新建drawable-21 資料夾,對於5.0和5.0 以下系統的drawable資源分別定義,這樣可以解決,但是分別定義的drawable因為使用的資源的不同可能導致效果不同。

2、邏輯上避開在drawable中引用?attr/資源。比如我在某一個按鈕上的效果:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <Button
        android:layout_width="96dp"
        android:layout_height="40dp"
        android:layout_centerInParent="true"
        android:background="@drawable/btn_pressed"
        android:text="按鈕"
        android:textColor="#FFFFFF"/>

</RelativeLayout>

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="false">
        <shape android:shape="rectangle">
            <solid android:color="?colorPrimary"/>
        </shape>
    </item>
    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <solid android:color="?colorPrimaryDark"/>
        </shape>
    </item>
</selector>
很簡單,就不上圖了,96*48的按鈕,觸控時和不觸控時引用不同的顏色。

可在5.0 以下系統,肯定會報Resources$NotFoundException,怎麼解決呢?

我們可以在這個按鈕下面放一個同樣大小的控制元件,任何設定該控制元件的background為?colorPrimary。然後在按鈕的drawable檔案中,將不觸控時的顏色設為全透明,觸控時設為一定透明度的黑色。如下:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <FrameLayout
        android:layout_width="96dp"
        android:layout_height="40dp"
        android:layout_centerInParent="true"
        android:background="?colorPrimary">
        <Button
            android:layout_width="96dp"
            android:layout_height="40dp"
            android:background="@drawable/btn_pressed"
            android:text="按鈕"
            android:textColor="#FFFFFF"/>
    </FrameLayout>
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:state_pressed="false">
        <shape android:shape="rectangle">
            <!--全透明色-->
            <solid android:color="#00000000"/>
        </shape>
    </item>
    <item android:state_pressed="true">
        <shape android:shape="rectangle">
            <!--透明度為12的黑色-->
            <solid android:color="#1E000000"/>
        </shape>
    </item>
</selector>
兩次的效果是差不多的,通過調整透明色,可以達到一樣的效果。這樣,我們避開了在drawable中引用?attr的資源,而且到達預期的效果了。
3、在java程式碼中定義drawable。這也是最好的一種解決辦法,我們可以在程式碼中獲取到?attr/資源的值,然後在定義的drawable中引用就行了,

public class TestActivity extends AppCompatActivity{
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_test);
        Utils.context=this;
        Button button=(Button)findViewById(R.id.btn);
        button.setBackground(getStateListDrawable());
    }
//    對應drawable 中的selector
    private StateListDrawable getStateListDrawable(){
        StateListDrawable stateListDrawable=new StateListDrawable();
        stateListDrawable.addState(new int[]{-android.R.attr.state_pressed},getGradientDrawable(false));
        stateListDrawable.addState(new int[]{android.R.attr.state_pressed},getGradientDrawable(true));
        return stateListDrawable;
    }

//    對應drawable 中的 shape
    private GradientDrawable getGradientDrawable(boolean isPressed){
        GradientDrawable gradientDrawable=new GradientDrawable();
        gradientDrawable.setShape(GradientDrawable.RECTANGLE);
        // 獲取顏色
        TypedValue primaryValue=new TypedValue();
        TypedValue primaryDarkValue=new TypedValue();
        this.getTheme().resolveAttribute(R.attr.colorPrimary,primaryValue,true);
        this.getTheme().resolveAttribute(R.attr.colorPrimaryDark,primaryDarkValue,true);
//        背景顏色
        if(isPressed){
            gradientDrawable.setColor(primaryDarkValue.data);
        } else {
            gradientDrawable.setColor(primaryValue.data);
        }
        gradientDrawable.setBounds(0,0,SizeUtils.dp2px(96),SizeUtils.dp2px(48));
        return gradientDrawable;
    }
}
程式碼很簡單,可以對應在xml中定義drawable的方式來看,實現的效果和之前的一樣。關於上面用到的
StateListDrawable  
GradientDrawable

兩個類,大家可以去詳細學習一下,我這裡知識提一下解決思路,如果你有更好的方法,可以在下面評論告訴我。
還是上傳個圖片吧,上面實現的效果:



相關文章