android自定義drawable的state屬性
在drawable中使用selector在開發中太常用了,不過用的最多是根據系統提供的一些狀態來選擇圖片,比如:android:state_checked
、android:state_pressed
等等,其實這些狀態也是可以自定義的,比方說天氣狀況有很多種情況,天晴、多雲、下雨等等,那能不能根據這些狀況來顯示不同的示意圖呢?這裏是指通過 selector
的方式來實現。答案是肯定的,下面的效果圖就是用selector
來實現的。
[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-R1Zgi0aC-1584662459361)(https://raw.githubusercontent.com/MingHuang1024/illustrations/master/drawable_state.gif)]
上面動畫中的天氣圖標、背景、文字顏色都是用selector
來實現的,沒有在代碼中根據不同天氣設置圖片等,不過文字是在代碼中改變的。
下面來看下怎麼實現:
一、定義表示狀態的屬性
在res-values目錄中新建一個attrs.xml文件(已有則不必新建),定義以下三個屬性,代表天氣中的天晴、多雲、下雨
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="Weather">
<attr name="state_sunny" format="boolean"/>
<attr name="state_raining" format="boolean"/>
<attr name="state_cloudy" format="boolean"/>
</declare-styleable>
</resources>
二、使用selector定義drawable
在res-drawable中新建一個weather_icon.xml文件,根據天氣狀態來顯示相應的圖片
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:example="http://schemas.android.com/apk/res-auto">
<item android:drawable="@drawable/sunny" example:state_sunny="true" />
<item android:drawable="@drawable/raining" example:state_raining="true" />
<item android:drawable="@drawable/cloudy" example:state_cloudy="true" />
</selector>
或者根據天氣情況顯示不同的顏色,如下:
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:example="http://schemas.android.com/apk/res-auto">
<item android:drawable="@color/sunnyColor" example:state_sunny="true" />
<item android:drawable="@color/rainingColor" example:state_raining="true" />
<item android:drawable="@color/cloudyColor" example:state_cloudy="true" />
</selector>
三、定義組件
接下來定義一個組件,這個組件用於接收不同的天氣狀況,如果該組件或其子view使用到了以上定義的圖片選擇器,這個組件或者其子view就會根據不同的天氣狀況顯示出不的圖片或顏色
爲了達到這個目的,該組件一定要重寫View的onCreateDrawableState()方法,如下:
override fun onCreateDrawableState(extraSpace: Int): IntArray? {
Log.d("WeatherView", "onCreateDrawableState...")
// 注意,這裏要給數組添加一個容量,用於儲存自定義的狀態
val drawableState = super.onCreateDrawableState(extraSpace + 1)
when (mWeather) {
Weather.SUNNY -> {
mergeDrawableStates(drawableState, STATE_SUNNY)
}
Weather.RAINING -> {
mergeDrawableStates(drawableState, STATE_RAINING)
}
Weather.CLOUDY -> {
mergeDrawableStates(drawableState, STATE_CLOUDY)
}
}
Log.d("WeatherView", mWeather.name)
return drawableState
}
其作用是創建圖片狀態,在這個方法中將自定義的狀態添加到圖片狀態列表中去,這樣系統就能識別自定義的狀態,使得自定義的圖片選擇器生效。
以上代碼中的STATE_SUNNY等狀態是一個常量,就是第一步中自定義的屬性,不過它是一個數組
private val STATE_SUNNY = intArrayOf(R.attr.state_sunny)
private val STATE_CLOUDY = intArrayOf(R.attr.state_cloudy)
private val STATE_RAINING = intArrayOf(R.attr.state_raining)
Weather
是一個枚舉類,代表着天氣的狀況,
enum class Weather {
SUNNY,
RAINING,
CLOUDY
}
可以通過重置mWeather的值來改變組件的狀態。
fun setWeather(weather: Weather) {
Log.d("WeatherView", "setWeather...")
if (mWeather!=weather) {
mWeather = weather
// 刷新狀態
refreshDrawableState()
}
}
再看一下這個組件的佈局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/weather_bg"
android:duplicateParentState="true"
android:orientation="horizontal"
android:padding="50dp">
<ImageView
android:id="@+id/ivIcon"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_gravity="center"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:background="@drawable/weather_icon"
android:duplicateParentState="true" />
<TextView
android:id="@+id/tv"
android:layout_gravity="center_vertical"
android:layout_marginLeft="30dp"
android:gravity="center"
android:textSize="30sp"
android:layout_width="100dp"
android:layout_height="80dp"
android:background="#ffffff"
android:duplicateParentState="true"
android:text="天氣"
android:textColor="@color/text_color" />
</LinearLayout>
這裏有幾個地方需要注意:
1、根佈局一定要設置android:duplicateParentState="true"
,表示複製父控件的狀態,這樣才能感受到自定義狀態的變化。容易忽略的一點是,以爲xml中的根佈局就是自定義組件的根佈局,如通過以下方式加載佈局:
inflate(context, R.layout.weather_view, this)
其實這裏inflate出來的view是控件的子view,要想子view接受父控件的狀態就要設置duplicateParentState爲true
建議自定義控件的根佈局使用merge,可以減少一層佈局,提高性能。另,使用merge的話不需要設置duplicateParentState爲true。
2、在需要根據狀態來改變背景、src、或文字顏色等的子view中設置前面定義的圖片選擇器,並且這些子view都要設置android:duplicateParentState="true"
,如:
android:background="@drawable/weather_bg"
android:duplicateParentState="true"
或
android:duplicateParentState="true"
android:text="天氣"
android:textColor="@color/text_color"
四、使用控件
接下來看一下效果,在activity中引入自定義組件,並用一個按鈕來改變組件的狀態,看看組件內的子view能不能根據狀態選擇不同的背景或文字顏色
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="60dp"
android:textSize="25sp"
android:padding="40dp"
android:text="點擊改變天氣"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.example.multiplestateview.WeatherView
android:id="@+id/weatherView"
android:padding="20dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView" />
activity中的點擊事件:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView.setOnClickListener {
when (weatherView.getWeather()){
Weather.SUNNY->{
weatherView.setWeather(Weather.CLOUDY)
}
Weather.CLOUDY->{
weatherView.setWeather(Weather.RAINING)
}
Weather.RAINING->{
weatherView.setWeather(Weather.SUNNY)
}
}
}
}
好了,這樣就實現了根據自定義的狀態來選擇不同的圖片了,還是挺簡單的,用上的話可以讓代碼看起來清爽不少。
源碼:
demo源碼:https://github.com/MingHuang1024/CustomDrawableState
由於水平有限,如果文中存在錯誤之處,請大家批評指正,歡迎大家一起來分享、探討!
博客:http://blog.csdn.net/MingHuang2017
GitHub:https://github.com/MingHuang1024
Email: [email protected]
誤之處,請大家批評指正,歡迎大家一起來分享、探討!
博客:http://blog.csdn.net/MingHuang2017
GitHub:https://github.com/MingHuang1024
Email: [email protected]
微信:724360018