上一篇文章我們利用View進(jìn)行自定義UI护糖,這篇我們將利用Android現(xiàn)有的UI進(jìn)行自定義UI晶疼。我們利用現(xiàn)有的UI控件,主要是利用它們的一些屬性吧恃,并且根據(jù)這些屬性的改變可以達(dá)到我們預(yù)期的效果虾啦。還是看看今天我們實(shí)現(xiàn)的效果吧,No picture痕寓,it's so hard傲醉。效果圖如下所示,就是我們常見的Tab和SeekBar呻率,看看今天怎么用現(xiàn)有的UI控件實(shí)現(xiàn)它硬毕;老樣子我們還是來一步一步分析吧。
分解效果圖
我們看到Horizontal和Vertical兩個(gè)Tab是不可以滑動(dòng)的礼仗,只有通過點(diǎn)擊來觸發(fā)左右切換吐咳,在Tab的下面會(huì)有一個(gè)帶顏色的bar標(biāo)志當(dāng)前選中的位置。想這樣的UI目前現(xiàn)有的Android控件應(yīng)該沒有可以直接使用的(據(jù)我的了解)元践。一般我們?cè)趯?shí)現(xiàn)這樣的UI時(shí)挪丢,我們會(huì)用TextView和一個(gè)帶顏色的View組合實(shí)現(xiàn)慎恒。這樣好像也可以趋距,但是還是有一定的代碼量的摘盆。那么我們?cè)趺从米钌俚拇a實(shí)現(xiàn)這樣的需求呢狼渊!首先我們看到左右切換的選中米苹,類似于RadioButton這樣的控件——在多個(gè)選項(xiàng)中只能選取其中一個(gè)亏较,但是RadioButton這樣的控件底部好像也沒有這樣的帶顏色的bar啊巡通,難道還是要利用一個(gè)View和它組合使用嗎弥锄,那這樣還是太low了戒悠。我們想啊绸狐,既然底部沒有這樣一個(gè)bar卤恳,那我們可以讓RadioButton在底部畫一個(gè)啊。怎么畫寒矿?那當(dāng)然是繼承它突琳,在onDraw()方法里面畫啦。具體怎么畫劫窒,我相信畫一個(gè)矩形應(yīng)該很簡單吧_本今。
上面分析完了Tab的部分拆座,這下我們來看看這個(gè)Seekbar吧主巍,我們先看看下面分解的4張圖片。
從上面的四張圖片可以看出挪凑,這個(gè)Seekbar是通過前面三張一個(gè)一個(gè)疊加過后達(dá)到第四張圖片的效果孕索。當(dāng)我們拖動(dòng)小thumb(小圓球)滑動(dòng)的時(shí)候,不斷的改變第二層上圓角矩形的寬度就可以達(dá)到想要的效果了躏碳。那么具體怎么實(shí)現(xiàn)搞旭,當(dāng)然還是上代碼啦。
實(shí)現(xiàn)分解效果圖
1.實(shí)現(xiàn)Tab的切換效果
上面我們分析了菇绵,需要實(shí)現(xiàn)Tab底部的效果主要是繼承RadioButton肄渗,并在onDraw()方法中繪制一個(gè)矩形就可以了,還是直接上代碼吧咬最。
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
int height = getMeasuredHeight();
//the indicator's height
int indicatorHeight = getResources().getDimensionPixelSize(R.dimen.radio_button_indicator_height);
if (isChecked()){
mPaint.setColor(getCurrentTextColor());
}else {
mPaint.setColor(Color.TRANSPARENT);
}
canvas.drawRect(0, height - indicatorHeight, getMeasuredWidth(), height, mPaint);
}
這里我們?cè)O(shè)置了選中色塊的高度翎嫡,再調(diào)用drawRect()方法繪制矩形(選中色塊)。這里簡單介紹一個(gè)這個(gè)方法
drawRect(float left, float top, float right, float bottom, @NonNull Paint paint)
先看看下面的圖解吧永乌。
結(jié)合上面的圖解惑申,我們這里把top的值設(shè)為height - indicatorHeight,這個(gè)height為整個(gè)RadioButton的高度翅雏,由圖應(yīng)該可以很清楚的知道top的坐標(biāo)計(jì)算方法了圈驼。我們?cè)赗adioButton選中的時(shí)候通過Paint的值設(shè)置選中顏色,當(dāng)處于未選中狀態(tài)時(shí)設(shè)置為透明色望几,并且繪制同一塊區(qū)域绩脆,達(dá)到切換的效果。
2.實(shí)現(xiàn)Seekbar
在分析Seekbar的時(shí)候,我們把它分解成了幾個(gè)層次的疊加靴迫,那接下來我們的任務(wù)就是實(shí)現(xiàn)這些層次的疊加祈坠。我們這部分是通過現(xiàn)有的UI控件實(shí)現(xiàn)這個(gè)效果的,那么我們用哪一個(gè)UI控件可以實(shí)現(xiàn)層次的疊加呢矢劲,那當(dāng)然只有FrameLayou和RelativeLayout啦赦拘!那到底用FrameLayout還是RelativeLayout呢?這里我們使用RelativeLayout芬沉,對(duì)于FrameLayout的使用可以自行實(shí)踐躺同。
(1)實(shí)現(xiàn)第一層圓角——progressbar
那我們這一層用什么實(shí)現(xiàn)呢,這里我們可以使用Andorid UI控件中的LinearLayout丸逸、FrameLayou等都可以蹋艺,但是我們這里使用ViewGroup的子類——LinearLayout(當(dāng)然也可以選取其他的),這樣我們可以對(duì)這一層進(jìn)行擴(kuò)展黄刚,在里面添加TextView捎谨、ImageView等。那么怎么實(shí)現(xiàn)圓角呢憔维,當(dāng)時(shí)是使用drawble進(jìn)行配置的涛救,但是對(duì)于使用drawable進(jìn)行配置是有問題的——不能代碼控制圓角的大小,這個(gè)問題導(dǎo)致可擴(kuò)展性太差业扒。那怎么解決呢检吆,當(dāng)然得想辦法啊,最后找到了使用GradientDrawable程储,這里我想說的就是蹭沛,我們?cè)谧远x的過程中總會(huì)出現(xiàn)問題,然后不停的找到具體的解決方法章鲤,一步一步實(shí)現(xiàn)摊灭。其實(shí)其他的編程問題都是這樣的。好了败徊,我們這里還是直接看看具體代碼吧帚呼,如下:
mFirstBar = new LinearLayout(context);
GradientDrawable drawable = new GradientDrawable();
drawable.setColor(Color.parseColor("#FFBB33"));
drawable.setCornerRadius((float) mProgressHeight / 2);
mFirstBar.setBackgroundDrawable(drawable);
//mFirstBar.setBackgroundResource(R.drawable.firtbar_bkg);
mFirstBarLp = new RelativeLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, mProgressHeight);
mFirstBarLp.addRule(CENTER_IN_PARENT);
mFirstBar.setClickable(false);
addView(mFirstBar, mFirstBarLp);
好了,第一層的實(shí)現(xiàn)應(yīng)該很簡單吧集嵌。
(2)實(shí)現(xiàn)第二層圓角——secondProgressbar
其實(shí)secondProgressbar的實(shí)現(xiàn)基本上同progressbar的實(shí)現(xiàn)類似萝挤,還是直接上代碼吧
mSecondBar = new LinearLayout(context);
GradientDrawable secondDrawable = new GradientDrawable();
secondDrawable.setColor(Color.parseColor("#99CC00"));
secondDrawable.setCornerRadius((float)mProgressHeight/2);
mSecondBar.setBackgroundDrawable(secondDrawable);
mSecondBarLp = new RelativeLayout.LayoutParams(mThumbRadius, mProgressHeight);
//mSecondBarLp.leftMargin = 0;
mSecondBarLp.addRule(CENTER_VERTICAL);
//mSecondBarLp.addRule(LEFT_OF, THUMB_ID);
mSecondBarLp.addRule(ALIGN_PARENT_LEFT);
addView(mSecondBar, 1, mSecondBarLp);
mSecondBar.setClickable(false);
這里我們看到了,在創(chuàng)建LayoutParams的時(shí)候根欧,我們?cè)O(shè)置的width的大小為圓角半徑的大小怜珍,為什么要這么做?這里還是先解釋一下吧凤粗,這樣做的目的主要是將secondProgressbar的最右端總是在thumb的中心位置酥泛,這里記住這一點(diǎn)后面我們?cè)诮榻B今豆。
(3)實(shí)現(xiàn)游標(biāo)——thumb
其實(shí)thumb很簡單啦,就是一個(gè)圓形的View柔袁。這里直接使用TextView呆躲,不要問我為什么——我任性$_$。直接看代碼吧捶索。
mThumb = new TextView(context);
GradientDrawable thumb = new GradientDrawable();
thumb.setColor(Color.parseColor("#33b5e5"));
thumb.setCornerRadius((float)mThumbRadius);
mThumb.setBackgroundDrawable(thumb);
mThumbLp = new RelativeLayout.LayoutParams(mThumbRadius*2, mThumbRadius*2);
mThumbLp.addRule(CENTER_VERTICAL);
mThumbLp.addRule(ALIGN_PARENT_LEFT);
mThumbLp.leftMargin = 0;
addView(mThumb, mThumbLp);
mThumb.setId(THUMB_ID);
mThumb.setGravity(Gravity.CENTER);
if (mThumbTextSize != 0)
mThumb.setTextSize(mThumbTextSize);
這里在我們使用TextView插掂,我們還可以在上面顯示一些信息,擴(kuò)展性更好腥例,當(dāng)然你也可以利用其他UI控件辅甥,設(shè)置你需要的UI!
(4)實(shí)現(xiàn)滑動(dòng)——Seekbar
好了燎竖,上面我們分解的圖都實(shí)現(xiàn)了璃弄,現(xiàn)在應(yīng)該要實(shí)現(xiàn)滑動(dòng)了吧!那么怎么實(shí)現(xiàn)按住Thumb就會(huì)滑動(dòng)构回,并且secondProgressbar和滑動(dòng)夏块,同時(shí)顯示當(dāng)前的進(jìn)度。當(dāng)然是使用監(jiān)聽OnTouchEvent事件啦纤掸,根據(jù)滑動(dòng)的distance不斷更新thumb和secondProgressbar的參數(shù)脐供,讓他們動(dòng)起來。那這里就直接實(shí)現(xiàn)RelativeLayout的OnTouchEvent方法茁肠,還是先看代碼吧患民,如下:
@Override
public boolean onTouchEvent(MotionEvent event) {
int action = MotionEventCompat.getActionMasked(event);
boolean isDraged = false;
Rect rect = new Rect();
mThumb.getHitRect(rect);
switch (action){
case MotionEvent.ACTION_DOWN:
float x = event.getX();
float y = event.getY();
boolean contain = rect.contains((int)x, (int)y);
if (contain){
mLastMotionX = event.getX();
isDraged = true;
}
break;
case MotionEvent.ACTION_MOVE:
dragThumb(event.getX());
break;
case MotionEvent.ACTION_UP:
break;
}
return isDraged;
}
private void dragThumb(float x){
float distance = (x - mLastMotionX);
mLastMotionX = x;
mThumbLp.leftMargin = (int) (mThumbLp.leftMargin + distance);
mSecondBarLp.width = (int) (mSecondBarLp.width + distance);
LogUtils.LogD(TAG, " horizontal current distance == " + distance);
//confirm this thumb is show, no anywhere is hide
if (mThumbLp.leftMargin <= 0) {
mThumbLp.leftMargin = 0;
mSecondBarLp.width = mThumbRadius;
} else if (mThumbLp.leftMargin >= getMeasuredWidth() - mThumbRadius * 2) {
mThumbLp.leftMargin = getMeasuredWidth() - mThumbRadius * 2;
mSecondBarLp.width = getMeasuredWidth() - mThumbRadius;
}
updateViewLayout(mThumb, mThumbLp);
updateViewLayout(mSecondBar, mSecondBarLp);
}
這里我們實(shí)現(xiàn)的是整個(gè)RelativeLayout的OnTouchEvent方法缩举,所以它的touch事件是針對(duì)整個(gè)RelativeLayout的垦梆。所以這里我們要做一下過濾,點(diǎn)擊范圍在不在thumb上面仅孩,只有點(diǎn)擊和拖動(dòng)都在thumb上面這次的touch對(duì)thumb才有效托猩。當(dāng)確定拖動(dòng)有效的時(shí)候,在開始初始化的時(shí)候辽慕,設(shè)置了thumb相對(duì)于父控件為ALIGN_PARENT_LEFT京腥,所以通過改變mThumbLp.leftMargin就可以改變thumb于左邊的距離啦。對(duì)于secondProgressbar溅蛉,只要改變mSecondBarLp.width的大小就可以改變它的寬度公浪,最后調(diào)用updateViewLayout()方法更新UI。這里我們要注意兩點(diǎn):
1.防止thumb滑動(dòng)到最右端時(shí)超出邊界船侧。
2.防止thumb滑動(dòng)回來到最左端時(shí)超出邊界欠气。
好了,到這里通過簡單的介紹镜撩,我們將這個(gè)Seekbar的基本功能完成预柒。
總結(jié)
上面我們利用Android控件實(shí)現(xiàn)了一個(gè)簡單的Seekbar,現(xiàn)在簡單的總結(jié)一下:
1.和實(shí)踐自定UI—View的時(shí)候一樣,我們還是把這個(gè)Seekbar進(jìn)行了分解宜鸯,然后一步一步實(shí)現(xiàn)憔古。
2.在自定義的過程中我們會(huì)遇到很多問題,我在這里遇到了這些問題:圓角怎么可以用代碼控制淋袖、在開始的時(shí)候我沒有利用RelativeLayou的onTouchEvent方法實(shí)現(xiàn)滑動(dòng)鸿市,而是將onTouchEvent事件直接set在thumb上面,結(jié)果滑動(dòng)的時(shí)候出現(xiàn)了問題(有興趣的可以自己試試看看是什么問題)....即碗。這里面遇到了很多問題灸芳,但都一個(gè)一個(gè)擊破,所以我們?cè)谧远║I的時(shí)候不要心急拜姿,一點(diǎn)一點(diǎn)將沒有問題解決烙样,最后就會(huì)實(shí)現(xiàn)你想要的效果。
3.這里只是通過這個(gè)例子分析怎樣去利用Android UI 自定義我們自己需要的UI蕊肥。這里只是引導(dǎo)谒获,更多的還是靠實(shí)踐、實(shí)踐壁却、實(shí)踐...批狱。重要的事說三遍_
好了,國際慣例展东,可以自己練習(xí)一下垂直方向的Seekbar赔硫,如下圖:
最后還是放上代碼地址吧
如何利用View自定義UI請(qǐng)閱讀實(shí)踐自定UI—View
如何利用ViewGroup自定義UI請(qǐng)閱讀實(shí)踐自定義UI-ViewGroup
希望在Android學(xué)習(xí)的路上,大家共同成長盐肃!
如果你也在簡書上撰寫Android相關(guān)的內(nèi)容爪膊,或者也準(zhǔn)備寫可以加入我們的寫作交流群:196537830