上周同事問有沒有多個Item的Switch控件,我想這也不是什么難事,這么多第三方庫,直接挑一個就行。找了半天,雖說大部分Switch都很炫,不過都只支持兩個Item,唯一找到的支持多Item的,但是是用在ios的,如圖:

自定義Switch倒也不是什么難事,這個圖里面唯一的難點也就是滑塊懸浮在文字上方的時候文字會變色,不過一點難度都沒有確實也沒什么意思,這是我實現(xiàn)的版本:

可以從圖中看出,特點是:
- 支持多個Item
- 支持文字和圖標
- 形狀可以多選擇
- 滑塊滑動的時候覆蓋的文字和圖標會變色
Github地址:https://github.com/mCyp/Orient-Ui
一、思路
制作這個Switch挺簡單,過程是:
- 繪制底層的背景
- 繪制底層的文字或者Icon
- 繪制滑塊
- 滑塊如果覆蓋到了文字或者Icon,就繪制滑塊覆蓋到的文字或者Icon
這也就是本Switch的難點了,如何繪制部分文字和圖標呢,答案就見標題了,使用畫布裁剪的方法Canvas.clipRect(Rect rect),我如果將畫布Canvas裁剪成一個滑塊大小,這個時候,我再繪制滑塊覆蓋到的文字到原有的位置,超出滑塊的部分就不會顯示了。
聰明的同學(xué)這個時候可能會有這樣的疑問?你現(xiàn)在將畫布裁剪了,那豈不是只能顯示滑塊了,道理是這樣的,可是我們還有Canvas.save()和Canvas.restore()方法,它們對應(yīng)的作用分別是將當前的畫布保存到對應(yīng)的畫布棧中和取出畫布棧中頂層的畫布,并進行恢復(fù)。
二、核心代碼
在這里,我假設(shè)大家已經(jīng)對基本的自定View已經(jīng)很熟悉了,直接展示繪制滑塊的代碼:
/**
* 繪制Switch滑動塊
*/
private void drawThumb(Canvas canvas) {
// 滑塊的左右邊界
int left = mItemCoordinate[mThumbState.pos] + mThumbState.offset;
int right = mItemCoordinate[mThumbState.pos + 1] + mThumbState.offset;
// 1. 保存當前圖層
canvas.save();
Rect rect = new Rect(left + mThumbMargin, top + mThumbMargin, right - mThumbMargin, bottom - mThumbMargin);
// 2. 根據(jù)滑塊的設(shè)定大小裁剪畫布
canvas.clipRect(rect);
// 3. 繪制滑塊
int padding = mThumbMargin + mThumbBorderWidth;
if (mShape == SwitchShape.RECT) {
drawRoundRect(canvas, left + padding, top + padding
, right - padding, bottom - padding, CORNER_RADIUS, mThumbColorPaint);
if (mThumbBorderWidth != 0)
drawRoundRect(canvas, left + mThumbMargin, top + mThumbMargin
, right - mThumbMargin, bottom - mThumbMargin, CORNER_RADIUS, mThumbBorderPaint);
} else {
drawRoundRect(canvas, left + padding, top + padding
, right - padding, bottom - padding, (bottom - top) / 2 - padding, mThumbColorPaint);
if (mThumbBorderWidth != 0)
drawRoundRect(canvas, left + mThumbMargin, top + mThumbMargin
, right - mThumbMargin, bottom - mThumbMargin, (bottom - top) / 2 - mThumbMargin, mThumbBorderPaint);
}
int first, second;
//... 省略 獲取位置
// 4. 繪制文字orIcon
if (mType == SwitchType.TEXT) {
drawText(canvas, mItems[first], mItemCoordinate[first], top, mItemCoordinate[first + 1], bottom, mThumbTextPaint);
if (second != -1 && second <= mItemCount - 1) {
drawText(canvas, mItems[second], mItemCoordinate[second], top, mItemCoordinate[second + 1], bottom, mThumbTextPaint);
}
} else {
drawIcon(canvas, mIconRes[first], mItemCoordinate[first], top, mItemCoordinate[first + 1], bottom, mThumbTextPaint);
if (second >= 0) {
drawIcon(canvas, mIconRes[second], mItemCoordinate[second], top, mItemCoordinate[second + 1], bottom, mThumbTextPaint);
}
}
// 5. 底層的畫布恢復(fù)
canvas.restore();
}
注釋也都在上面了,對源碼感興趣的同學(xué)可以直接看Github,這個控件的代碼也就600行,處理好觸摸事件和使用好屬性動畫即可。
三、使用
可能有的同學(xué)不想關(guān)注原理,只想知道如何使用。
開始
implementation 'com.orient:Orient-Ui:2.1.1'
第一步 添加進xml布局文件
<com.orient.me.widget.sw.MultiSwitch
android:id="@+id/ms_weak"
android:layout_width="match_parent"
android:layout_marginStart="@dimen/len_10"
android:layout_marginEnd="@dimen/len_10"
android:layout_height="60dp"
android:layout_gravity="center"
android:layout_marginTop="@dimen/len_20"
app:msBackgroundColor="@color/teal_300"
app:msTextSize="@dimen/font_18"
app:msNormalTextColor="@color/white_alpha_192"
app:msShape="rect"
app:msThumbColor="@color/white"
app:msThumbMargin="@dimen/len_6"
app:msThumbTextColor="@color/teal_300"
app:msType="text" />
解釋一下各個屬性的用法:
| 屬性 | 說明 | 類型 |
|---|---|---|
| msBackgroundColor | 背景顏色 | reference|color |
| msNormalTextColor | 非選中狀態(tài)文本或者Icon顏色 | reference|color |
| msThumbTextColor | 滑塊中文本或者Icon顏色 | reference|color |
| msTextSize | 文本大小 | reference|dimension |
| msIconSize | 圖標大小 | reference|dimension |
| msThumbMargin | 滑塊的外邊距 | reference|dimension |
| msShape | 選擇的形狀 | rect or oval |
| msType | 選擇的類型 | text or icon |
| msThumbColor | 滑塊背景色 | reference |
第二步 獲取MultiSwitch
使用findViewById獲取MultiSwitch對象
第三步 設(shè)置選項內(nèi)容
設(shè)置字符串數(shù)組或者Icon數(shù)組
mHead.setItemsArray(new String[]{"Dark","Light"});
// or
mIconSwitch.setIconArray(new int[]{R.drawable.grid_ic_play,R.drawable.ic_camera,R.drawable.common_ic_back});
第四步 設(shè)置監(jiān)聽器
提供了位置選擇的回調(diào)以及滑塊移動百分比的回調(diào),如我的效果圖,設(shè)置背景的黑夜模式和白天模式的時候,利用百分比回調(diào)可以用來設(shè)置背景色的漸變效果。
mHead.setMultiSwitchListener(new MultiSwitchListener() {
@Override
public void onPositionSelected(int pos) {
// when pos selected, it will call back
}
@Override
public void onPositionOffsetPercent(int pos, float percent) {
// current page move offset percent when drag
}
});
除此以外,你還可以設(shè)置默認位置:
// 設(shè)置默認位置
mHead.setCurrentItem(2);
四、總結(jié)
進行自定View的時候,大家大可不必聞自定View色變,有的時候,某個自定義View的難點可能就是大家不常用到的那一到兩個Api,這個時候,就需要大家熟悉官方提供的Api了。
個人說明
本人最近打算找工作了,在上?;蛘邿o錫,如果有好的公司推薦或者內(nèi)推,可以聯(lián)系我哈,微信:Jw_19951030,感激不盡~
如果大家對Oreint-Ui系列的其他控件感興趣,可以查看:
表格: 《Orient-Ui | 單RecyclerView實現(xiàn)花式表格》
時間軸:《花式實現(xiàn)時間軸,樣式由你來定!》