這個(gè)系列是老外寫的,干貨哟楷!翻譯出來一起學(xué)習(xí)瘤载。如有不妥,不吝賜教卖擅!
- Android自定義視圖一:擴(kuò)展現(xiàn)有的視圖鸣奔,添加新的XML屬性
- Android自定義視圖二:如何繪制內(nèi)容
- Android自定義視圖三:給自定義視圖添加“流暢”的動(dòng)畫
- Android自定義視圖四:定制onMeasure強(qiáng)制顯示為方形
簡(jiǎn)介
這個(gè)系列詳細(xì)的介紹了如何穿件Android自定義視圖。主要涉及的內(nèi)容有如何繪制內(nèi)容惩阶,layout和measure的原理挎狸,如何繼承實(shí)現(xiàn)view group以及如何給其子視圖添加動(dòng)畫。第一篇主要講述如何擴(kuò)展和使用現(xiàn)有的視圖断楷,以及如何添加特有的XML屬性锨匆。
特定的任務(wù)使用特定的視圖
Android提供的view都是比較通用的,哪里都可以用冬筒。但是在開發(fā)應(yīng)用的過程中需要對(duì)這些通用的view加以修改恐锣。很多時(shí)候這些代碼都添加到了Activity中茅主,這樣是的Activity的代碼雜亂,影響維護(hù)侥蒙。
假設(shè)你在開發(fā)一個(gè)最用用戶訓(xùn)練數(shù)據(jù)的應(yīng)用暗膜。比如用戶的總運(yùn)動(dòng)時(shí)長(zhǎng),總運(yùn)動(dòng)距離以及不同的運(yùn)動(dòng)類型等鞭衩。為了友好的把數(shù)據(jù)展現(xiàn)給用戶学搜,你需要根據(jù)用戶運(yùn)動(dòng)的時(shí)間長(zhǎng)度做不同的處理。比如他運(yùn)動(dòng)了2378秒论衍,那么顯示的肯定是48分鐘瑞佩。18550秒,那顯示的肯定是5小時(shí)9分鐘坯台。
創(chuàng)建一個(gè)自定義視圖
處理上面的問題最好就是定義一個(gè)view炬丸。這個(gè)view里包含了處理上面時(shí)間的功能。我們來創(chuàng)建一個(gè)自定義view:DurationTextView
蜒蕾。
class DurationTextView : TextView {
constructor(context: Context, attributeSet: AttributeSet) : super(context, attributeSet) {
}
}
TextView
和其他的view一樣有三個(gè)構(gòu)造函數(shù):一個(gè)是只需要Context
的稠炬,一個(gè)是上面給出的需要兩個(gè)參數(shù)Context
和AttributeSet
,還有一個(gè)需要這兩個(gè)參數(shù)之外的關(guān)于style的參數(shù)咪啡。一般來說首启,上面給出的構(gòu)造方法就可以滿足。下面在布局中使用上面定義的view撤摸。
<demo.customview.customviewdemo.Views.DurationTextView
android:id="@+id/duration_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
注意毅桃,這里需要給出自定義視圖的全名稱。
添加展示邏輯
現(xiàn)在這個(gè)視圖和標(biāo)準(zhǔn)的TextView
沒什么太大的區(qū)別准夷。我們?yōu)檫@個(gè)view添加一個(gè)duration
屬性來設(shè)置和讀取duration值钥飞。
var duration: Float
get() = _duration
set(value) {
_duration = value
var durationInMinutes: Int = Math.round(_duration / 60)
var hours: Int = durationInMinutes / 60
var minutes: Int = durationInMinutes % 60
var hourText: String = ""
var minuteText: String = ""
if (hours > 0) {
hourText = "$hours ${if (hours == 1) "hour" else "hours"}"
}
if (minutes > 0) {
minuteText = "$minutes ${if (minutes == 1) "minute" else "minutes"}"
}
if (hours == 0 && minutes == 0) {
minuteText = "Less than 1 minute"
}
var durationText = TEXT_TEMPLATE.format(hourText, minuteText)
text = durationText
}
companion object {
val TEXT_TEMPLATE = "Duration: %s %s"
}
這個(gè)方法接受一個(gè)Float
型的參數(shù),訓(xùn)練時(shí)間的秒數(shù)衫嵌。然后通過上面的轉(zhuǎn)換邏輯把這個(gè)秒數(shù)轉(zhuǎn)成用戶友好的文字值读宙。最后賦值給TextView
的text。轉(zhuǎn)換的邏輯非常簡(jiǎn)單渐扮,就是把秒數(shù)轉(zhuǎn)為分鐘數(shù)论悴,最后轉(zhuǎn)為小時(shí)和分鐘的值。分鐘和小時(shí)數(shù)如果大于1的時(shí)候單位就顯示為minutes和hours墓律,等于1的時(shí)候?yàn)閙inute和hour膀估。
用起來
現(xiàn)在就可以試試效果了。在Activity的onCreate()
方法里添加下面的代碼:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var durationTextView1 = findViewById(R.id.duration_view1) as DurationTextView
durationTextView1.duration = 2378.0f
var durationTextView2 = findViewById(R.id.duration_view2) as DurationTextView
durationTextView2.duration = 3670.0f
var durationTextView3 = findViewById(R.id.duration_view3) as DurationTextView
durationTextView3.duration = 18550.0f
}
運(yùn)行結(jié)果如下圖:
看起來還不錯(cuò)吧耻讽。
添加XML屬性
如果我們能夠這個(gè)view添加一個(gè)方法來設(shè)置文本展示的模板察纯,就像設(shè)定duration一樣。但是,這個(gè)template其實(shí)并不會(huì)想duration一樣需要經(jīng)常的設(shè)置饼记,更多的是一個(gè)類似于常數(shù)一樣的存在香伴。所以對(duì)于添加一個(gè)方法來說,添加一個(gè)XML屬性更加合適具则。
首先添加values/attrs.xml文件即纲,XML屬性就在這個(gè)文件中定義。我們只需要添加一個(gè)字符串類型的博肋,名稱為template的屬性低斋。添加之后attrs.xml文件看起來是這樣的:
<resources>
<declare-styleable name="TemplateTextView">
<attr name="template" format="string" />
</declare-styleable>
</resources>
我們聲明了一個(gè)名稱為TemplateTextView的declare-styleable節(jié)點(diǎn)。名字可以任意起匪凡,不過最好還是在哪個(gè)view里使用就叫做什么膊畴。這里叫做TemplateTextView是因?yàn)楹竺孢@個(gè)XML屬性會(huì)用與很多其他的自定義view中。來看看是怎么使用這個(gè)屬性的病游。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="demo.customview.customviewdemo.MainActivity">
<demo.customview.customviewdemo.Views.DurationTextView
android:id="@+id/duration_view1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Customized"
android:textSize="20sp"
app:template="%s was spent running" />
<!-- ...... -->
</LinearLayout>
在格式化代碼(快捷鍵Ctrl+Alt+L唇跨,或者Cmd+Alt+L)后在LinearLayout
中會(huì)增加一行xmlns:app="http://schemas.android.com/apk/res-auto"
這一句聲明了一個(gè)namespace,這樣APP就可以理解我們剛剛在自定義view的布局中添加的屬性了衬衬。當(dāng)然需要使用這條聲明里指定的app為前綴:app:template="%s was spent running"
买猖。
現(xiàn)在就需要我們?cè)诖a里讀取并應(yīng)用新添加的屬性值了。首先添加一個(gè)var template:String? = null
的屬性滋尉,準(zhǔn)備接收解析的字符串模板政勃。
var attributes = context.obtainStyledAttributes(attributeSet, R.styleable.TemplateTextView)
template = attributes.getString(R.styleable.TemplateTextView_template)
if (template == null || !(template?.contains("%s", ignoreCase = true) ?: false)) {
template = TEXT_TEMPLATE
}
attributes.recycle()
第一行使用了AttributeSet
類型的參數(shù)attributeSet
,這個(gè)參數(shù)包含了在attrs.xml文件中定義的全部的屬性兼砖。 方法obtainStyledAttributes()
主要做了兩件事:
- 過濾全部的自定義屬性,并把這些屬性的定義和給定的值關(guān)聯(lián)起來既棺。
- 返回你在第二個(gè)參數(shù)所指定的屬性組合讽挟。
R.styleable.TemplateTextView
就是我們自己定義的屬性數(shù)組,這里只有一個(gè)元素丸冕。R.styleable.TemplateTextView_template
是我們自定義屬性數(shù)組里的那個(gè)叫做template的元素耽梅。然后我們使用typed array來獲取這個(gè)屬性的值。如果沒有設(shè)置模板胖烛,或者模板中不包含處理字符串的%s的話就是用我們之前定義的默認(rèn)的文字模板來代替眼姐。
但是有一點(diǎn)需要格外注意:不管有沒有獲取到屬性值,都要回收TypedArray對(duì)象佩番,attributes.recycle()
众旗。
上下兩個(gè)分別設(shè)定了不同的template,中間的不設(shè)定趟畏,運(yùn)行一下看看修改后的效果如何:
對(duì)于大多數(shù)的Android視圖XML屬性都有對(duì)應(yīng)的方法來通過代碼的方式設(shè)置對(duì)應(yīng)的值贡歧。對(duì)于自定義視圖是否需要這么做,主要取決于你打算怎么用。添加一個(gè)方法也沒什么問題利朵。
下一篇主要簡(jiǎn)述如何繪制視圖的內(nèi)容律想,并自定義一個(gè)顯示折線圖的視圖。