我們知道在android的世界各種優(yōu)美而又炫酷控件和動(dòng)畫精彩紛呈,這個(gè)得益于大Google將android開源,市場(chǎng)上android智能手機(jī)也是大方色彩,不僅有華為、聯(lián)想、三星、小米,魅族還有現(xiàn)在異軍突起的vivo、oppo,他們基于android系統(tǒng)定制屬于自己的系統(tǒng),這使得android在不到十年的時(shí)間瘋狂掠奪手機(jī)市場(chǎng)份額,預(yù)計(jì)未來andriod智能手機(jī)仍然占領(lǐng)智能手機(jī)主要市場(chǎng),android系統(tǒng)的快速普及使得Google對(duì)Android系統(tǒng)變革步伐加快,拿幾年前和現(xiàn)在的android手機(jī)對(duì)比使用,不難發(fā)現(xiàn)老版本的android手機(jī)真是卡到家了,這是Google被吐槽最多的缺憾,而如今的android已經(jīng)在很多方面都進(jìn)行過幾次優(yōu)化,例如2012年的黃油計(jì)劃,以及針對(duì)4.4版本對(duì)電池性能的優(yōu)化,Android Runtim虛擬機(jī),Material Design風(fēng)格,運(yùn)行時(shí)權(quán)限,以及針對(duì)未來智能設(shè)備的Android Go系統(tǒng)!!!
納悶,說了那么多,好像跟本節(jié)要講的內(nèi)容有毛線干系,,,(不假思索 ) 好像是耶,說了那么多把a(bǔ)ndroid幾次改變捋一下下而已,那我們就此入題吧....? (廢話那么多!!!---抱怨 ? ? ? ? 大蝦,見諒,見諒)
自定義控件是每個(gè)android工程師成長的必經(jīng)之路,狹路相逢勇者勝,看到自定義控件不要怕,抽出神刀果斷亮劍,多次交鋒下來,你會(huì)發(fā)現(xiàn)你越來越厲害了,自定義View需要練習(xí),現(xiàn)在的我也經(jīng)常看相關(guān)的內(nèi)容,時(shí)間長了不用不寫就忘了,跟大家目前一塊學(xué)習(xí)自定義View,請(qǐng)大蝦以后多多關(guān)照....
自定義一般分為三種,難易程度由易到難,首先難度系數(shù)較低的是自定義擴(kuò)展控件,就是對(duì)已有控件進(jìn)行再次封裝,以滿足自己特殊“癖好”,接著就是自定義組合控件,這個(gè)有的時(shí)候還是比較容易,有的需要多動(dòng)動(dòng)腦筋,那么高潮來了,最難的就是完全自定義View,我們通過直接繼承View/ViewGroup(其實(shí)也是繼承View),對(duì)控件進(jìn)行完全自定義,那么今天我們一塊分享難度系數(shù)比較低的自定義組合控件吧,后面跟大家在分享一些對(duì)已有控件進(jìn)行拓展,以及最后一起學(xué)習(xí)和探討姿勢(shì)高難度的完全自定義View.
假設(shè)一個(gè)應(yīng)用場(chǎng)景,我們需要一個(gè)控件,這個(gè)控件能夠輸入內(nèi)容并且根據(jù)接口校驗(yàn)輸入數(shù)據(jù)正確與否,并對(duì)結(jié)果不同提示也不同,大家有時(shí)候會(huì)用到的輸入校驗(yàn)框,不說話上圖
整個(gè)過程如上圖,接下來我們一點(diǎn)一點(diǎn)分析:
首先自定義一個(gè)控件繼承自RelativeLayout,然后自定義屬性,在values目錄下創(chuàng)建attrs資源文件,自定義屬性根節(jié)點(diǎn)為
定義的屬性如下,如圖:
我們來看一下比較重要的屬性,
input_hintText:提示文字,跟EditText屬性hint一樣
input_hintColor:提示文字顏色
inputType:輸入內(nèi)容類型
input_state:枚舉輸入狀態(tài)
接著看如何自定義控件:
接觸過自定義控件的同學(xué)都知道,構(gòu)造函數(shù)有多個(gè)重載,
public WmsInputView(Context context) {? ? super(context);? ? this.context = context;? ? init(null, 0);}
public WmsInputView(Context context, AttributeSet attrs) {? ? super(context, attrs);? ? this.context = context;? ? init(attrs, 0);}
public WmsInputView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);? this.context = context;? ? init(attrs, defStyle);}
第一個(gè)構(gòu)造是在代碼中直接創(chuàng)建而調(diào)用,第二個(gè)以及第三個(gè)是在布局文件中創(chuàng)建調(diào)用,這里分attrs是我們?cè)诓季治募卸x的屬性集,后面我們要從這個(gè)屬性集里面取出屬性,defStyleAttr是定義在theme中的一個(gè)引用,這個(gè)引用指向一個(gè)style資源,而這個(gè)style資源包含了一些TypedArray的默認(rèn)值,一般我們默認(rèn)就行。
注意,我們分別在三個(gè)構(gòu)造函數(shù)中init()來初始化屬性,
private void init(AttributeSet attrs, int defStyle) {
LayoutInflater.from(context).inflate(R.layout.layout_wms_input_view, this, true);
rlInputView = (RelativeLayout) findViewById(R.id.rl_input);
iconInner = (ImageView) findViewById(R.id.icon_checked_inner);
progressBar = (ProgressBar) findViewById(R.id.progress_bar);
edtInput = (EditText) findViewById(R.id.et_input);? ? // Load attributes
final TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.WmsInputView, defStyle, 0);
inputState = a.getInt(R.styleable.WmsInputView_input_state, STATE_INPUT);
hintText = a.getString(R.styleable.WmsInputView_input_hintText);
hintColor = a.getColor(R.styleable.WmsInputView_input_hintColor, hintColor);
inputColor = a.getColor(R.styleable.WmsInputView_inputColor, getResources().getColor(R.color.home_header_check_store));
inputSize = a.getDimensionPixelSize(R.styleable.WmsInputView_inputSize, inputSize);
inputType = a.getInteger(R.styleable.WmsInputView_android_inputType, InputType.TYPE_CLASS_TEXT);
maxLength = a.getInt(R.styleable.WmsInputView_android_maxLength, maxLength);
maxLines = a.getInt(R.styleable.WmsInputView_android_maxLines, maxLines);
inputText = a.getString(R.styleable.WmsInputView_android_text);
a.recycle();
initViews();
}
這里我們?cè)诓季治募懥艘粋€(gè)該控件的布局,該控件是由textView,EditText,ProgressBar和imageView組成,然后通過布局加載器加載該布局,
LayoutInflater.from(context).inflate(R.layout.layout_wms_input_view, this, true);
然后我們將布局里面定義的屬性通過getContext().obtainStyledAttributes()拿到TypedArray,再從這個(gè)屬性集中拿出對(duì)應(yīng)的屬性。
記得后面要recycler一下,那為什么呢??
仁兄注意:使用recycle過后,是將我們之前創(chuàng)建的attrs屬性進(jìn)行回收等待下一次復(fù)用,這樣,每次引用到我們自定義View的組件重新創(chuàng)建的時(shí)候,我們的自定義屬性就不會(huì)重新的重建,GC就不用頻繁的操作這個(gè)對(duì)象,防止了OOM的出現(xiàn)。
接著把我們自定義控件xml文件中對(duì)應(yīng)屬性值設(shè)置個(gè)控件中各個(gè)組件進(jìn)行初始化,這里強(qiáng)行貼圖,這樣大家必須要?jiǎng)邮植僮髁耍葸輣~~(動(dòng)作要快,姿勢(shì)要對(duì))
這里我們進(jìn)行簡(jiǎn)單分析內(nèi)容,大家看到很多-1,到底是啥,我們?cè)匍_始對(duì)變量進(jìn)行了初始化是設(shè)置了默認(rèn)值,當(dāng)我們?cè)賦ml文件中沒有設(shè)置對(duì)應(yīng)的屬性時(shí)候,我們get的值就是這個(gè)默認(rèn)值,要回舉一反三哦,
inputFilters.add(new InputFilter.LengthFilter(maxLength));
edtInput.setFilters(inputFilters.toArray(new InputFilter[inputFilters.size()]));
這里的InputFilter是過濾器,我們對(duì)EditText要多輸入框進(jìn)行長度控制時(shí)就需要?jiǎng)?chuàng)建這個(gè)LengthFilter過濾器,當(dāng)然過濾器有很多,這里就不一一列舉,
最后,我們對(duì)editText設(shè)置了鍵盤輸入監(jiān)聽器(OnEditorActionListener),也就當(dāng)我們按下確認(rèn)的時(shí)候會(huì)進(jìn)行回調(diào),我們?cè)倩卣{(diào)中對(duì)輸入的內(nèi)容進(jìn)行校驗(yàn),所以這個(gè)監(jiān)聽很重要,想要對(duì)這個(gè)監(jiān)聽器了解更多的可以自行搜索,“多動(dòng)動(dòng)手哦,老司機(jī)都是擼出來的,,,,”(咻咻~~)
重點(diǎn)來了,墻裂敲黑板!!!
switchState(inputState);
這又是什么鬼??? 還記得·····那年大明湖畔~~~
oh myGod? 串詞了, 事情的經(jīng)過是這樣的,,, 還記得前面幾張圖片嗎?
我們的控件有很多種狀態(tài),正常輸入狀態(tài),不能輸入狀態(tài),加載狀態(tài),和加載完成以及加載錯(cuò)誤狀態(tài),沒錯(cuò),就是這個(gè)方法“惹的禍”,那我接下來就來“一一拷問”它究竟是如何作案的,肯定也沒那么神奇,畢竟我們都猜到結(jié)局~~
立刻上碼,揚(yáng)鞭奔騰起來,當(dāng)我們?cè)O(shè)置STATE_INPUT狀態(tài)時(shí)都有哪些操作,我只分析一種情況后面的大家應(yīng)該都能理解,首先我們對(duì)控件的背景顏色進(jìn)行設(shè)置,例如控件是輸入狀態(tài)時(shí)背景設(shè)置成藍(lán)色的,這個(gè)rlInputView是啥呢?
原來是個(gè)布局呀,也就是我們整個(gè)控件的外面的布局,
當(dāng)控件狀態(tài)是STATE_INPUT時(shí),我們把其他驗(yàn)證成功和進(jìn)度都設(shè)置為GONE,輸入框通過postDelayed發(fā)送一個(gè)延時(shí)操作獲取焦點(diǎn),
edtInput.postDelayed(new Runnable() {
@Override
public void run() {
edtInput.requestFocus();
}}, 200);
接下來的狀態(tài)設(shè)置跟這個(gè)差別不是很大,就是對(duì)布局種各組件進(jìn)行狀態(tài)設(shè)置,隱藏還是顯示,顏色和背景以及字體等等的設(shè)置,大家細(xì)看就會(huì)的,
不過我們?cè)僭O(shè)置狀態(tài)為STATE_ERROR時(shí)候用到了高亮和全選,
void setErrorText(String errorText) {
if (TextUtils.isEmpty(errorText)) {
return;
}
edtInput.setTextColor(ContextCompat.getColor(context, R.color.text_error));
edtInput.selectAll();
edtInput.setHighlightColor(context.getResources().getColor(R.color.bg_text_error));}
這個(gè)作用時(shí)給用戶一個(gè)顯眼的提示,并且自動(dòng)全選后可以點(diǎn)刪除鍵一鍵全刪,用戶體驗(yàn)還是很好的,還是看圖吧,
是不是看起來還不錯(cuò)呢,當(dāng)你刪除的時(shí)候就可以全部刪掉,不用再長按選擇全選或者不停的“抖手”,當(dāng)然,我們這樣在布局里面吧所有屬性寫死是不是感覺擴(kuò)展性太差,我們對(duì)外也暴露了一些方法用來修改控件狀態(tài)和屬性值,
public void setText(String text) {
edtInput.setText(TextUtils.isEmpty(text) ? "" : text);
edtInput.setSelection(text.length());}/** * 獲取輸入文本 * * @return */
public String getInputText() {
return edtInput.getText().toString().trim();}/** * 獲取輸入控件 * @return */
public EditText getEditInputView() {
if (edtInput != null) return edtInput;
return null;
}
public interface OnCompleteListener {
void onComplete(String inputText);
}
看到這里我們控件基本說完了,是不是感覺沒那么復(fù)雜吧,“真相”終究會(huì)大告天下,講解的過程中我只是將一些重要的地方拿出來說了一下,當(dāng)然還有其他地方?jīng)]有說明,如果你還有點(diǎn)疑惑就看看代碼吧,我會(huì)把代碼放到github上去,本人是個(gè)github新人,分析的過程有的地方并不完美,希望大蝦諒解,如有疑問和意見歡迎指正和提出,讓我們?cè)龠M(jìn)階的道路上一塊進(jìn)步,一起提高~~
github地址:github.com/Scus5761/-View-
祝大家周末愉快!!!