自定義開關按鈕控件SwitchView

在講解自定義SwitchView之前? 先講解一下自定義View的基本步驟

1 .有些自定義需要定義View的屬性如:背景顏色? 字體大小? 字體顏色? 需要用到typeArray 大概步驟是在value包下創建attrs文件定義 declare-styleable 名稱 主要是定義屬性的format 主要知道自定義屬性對應的format就可以了 dimension 尺寸值 color 顏色? integer整數值 ? boolean布爾值 reference 資源文件ID float浮點值 string字符串值 fraction百分數 enum 枚舉值 flag 位或運算 因為我這里沒用用到自定義屬性所以就不過多的介紹了 有興趣的同學可以下來了解具體的使用

2.onMeasure定義View的大小? 這里介紹下MeasureSpec中的三種模式:分別是UNSPECIFIED ? AT_MOST ?? EXACTLY

1).精確模式MeasureSpec.EXACTLY 這種情況下是確定該控件的具體大小值? 如10dp

2).最大模式 MeasureSpec.AT_MOST 這個也就是父組件,能夠給出最大的控件,當前組件的寬高大小只能為這么大,當然也可以比這個小 如wrap_content

3).未指定模式 MeasureSpec.EXACTLY 當前組件,可以隨便使用控件,不受限制 MATCH_PARENT?

3.onSizeChanged 用以顯示當前View的位置和寬高設置 w h 分別表示當前的寬和高 oldw oldh分別表示改變之前的寬和高

4 onDraw繪制View? 定義畫布canvas 然后定義paint 最后就是在畫布上繪制View等一些邏輯上的操作?

5.onTouchEvent觸摸事件操作?

講了這么多 還不如直接上代碼自行研究

public class SwitchViewextends View {

private final Paintpaint =new Paint();

? private final PathsPath =new Path();

? private final PathbPath =new Path();

? private final RectFbRectF =new RectF();

? private float sAnim, bAnim;

? private RadialGradientshadowGradient;

? private final AccelerateInterpolatoraInterpolator =new AccelerateInterpolator(2);

? /**

* state switch on

*/

? public static final int STATE_SWITCH_ON =4;

? /**

* state prepare to off

*/

? public static final int STATE_SWITCH_ON2 =3;

? /**

* state prepare to on

*/

? public static final int STATE_SWITCH_OFF2 =2;

? /**

* state prepare to off

*/

? public static final int STATE_SWITCH_OFF =1;

? /**

* current state

*/

? private int state =STATE_SWITCH_OFF;

? /**

* last state

*/

? private int lastState =state;

? private boolean isOpened =false;

? private int mWidth, mHeight;

? private float sWidth, sHeight;

? private float sLeft, sTop, sRight, sBottom;

? private float sCenterX, sCenterY;

? private float sScale;

? private float bOffset;

? private float bRadius, bStrokeWidth;

? private float bWidth;

? private float bLeft, bTop, bRight, bBottom;

? private float bOnLeftX, bOn2LeftX, bOff2LeftX, bOffLeftX;

? private float shadowHeight;

? public SwitchView(Context context) {

this(context, null);

? }

public SwitchView(Context context, AttributeSet attrs) {

super(context, attrs);

? ? ? setLayerType(LAYER_TYPE_SOFTWARE, null);

? }

@Override

? protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

int widthSize = MeasureSpec.getSize(widthMeasureSpec);

? ? ? int heightSize = (int) (widthSize *0.65f);

? ? ? setMeasuredDimension(widthSize, heightSize);

? }

@Override

? protected void onSizeChanged(int w, int h, int oldw, int oldh) {

super.onSizeChanged(w, h, oldw, oldh);

? ? ? mWidth = w;

? ? ? mHeight = h;

? ? ? sLeft =sTop =0;

? ? ? sRight =mWidth;

? ? ? sBottom =mHeight *0.91f;

? ? ? sWidth =sRight -sLeft;

? ? ? sHeight =sBottom -sTop;

? ? ? sCenterX = (sRight +sLeft) /2;

? ? ? sCenterY = (sBottom +sTop) /2;

? ? ? shadowHeight =mHeight -sBottom;

? ? ? bLeft =bTop =0;

? ? ? bRight =bBottom =sBottom;

? ? ? bWidth =bRight -bLeft;

? ? ? final float halfHeightOfS = (sBottom -sTop) /2;

? ? ? bRadius = halfHeightOfS *0.95f;

? ? ? bOffset =bRadius *0.2f;

? ? ? bStrokeWidth = (halfHeightOfS -bRadius) *2;

? ? ? bOnLeftX =sWidth -bWidth;

? ? ? bOn2LeftX =bOnLeftX -bOffset;

? ? ? bOffLeftX =0;

? ? ? bOff2LeftX =0;

? ? ? sScale =1 -bStrokeWidth /sHeight;

? ? ? RectF sRectF =new RectF(sLeft, sTop, sBottom, sBottom);

? ? ? sPath.arcTo(sRectF, 90, 180);

? ? ? sRectF.left =sRight -sBottom;

? ? ? sRectF.right =sRight;

? ? ? sPath.arcTo(sRectF, 270, 180);

? ? ? sPath.close();

? ? ? bRectF.left =bLeft;

? ? ? bRectF.right =bRight;

? ? ? bRectF.top =bTop +bStrokeWidth /2;

? ? ? bRectF.bottom =bBottom -bStrokeWidth /2;

? ? ? shadowGradient =new RadialGradient(bWidth /2, bWidth /2, bWidth /2, 0xff000000, 0x00000000, Shader.TileMode.CLAMP);

? }

private void calcBPath(float percent) {

bPath.reset();

? ? ? bRectF.left =bLeft +bStrokeWidth /2;

? ? ? bRectF.right =bRight -bStrokeWidth /2;

? ? ? bPath.arcTo(bRectF, 90, 180);

? ? ? bRectF.left =bLeft + percent *bOffset +bStrokeWidth /2;

? ? ? bRectF.right =bRight + percent *bOffset -bStrokeWidth /2;

? ? ? bPath.arcTo(bRectF, 270, 180);

? ? ? bPath.close();

? }

private float calcBTranslate(float percent) {

float result =0;

? ? ? int wich =state -lastState;

? ? ? switch (wich) {

case 1:

// off -> off2

? ? ? ? ? ? if (state ==STATE_SWITCH_OFF2) {

result =bOff2LeftX - (bOff2LeftX -bOffLeftX) * percent;

? ? ? ? ? ? }

// on2 -> on

? ? ? ? ? ? else if (state ==STATE_SWITCH_ON) {

result =bOnLeftX - (bOnLeftX -bOn2LeftX) * percent;

? ? ? ? ? ? }

break;

? ? ? ? case 2:

// off2 -> on

? ? ? ? ? ? if (state ==STATE_SWITCH_ON) {

result =bOnLeftX - (bOnLeftX -bOff2LeftX) * percent;

? ? ? ? ? ? }

// off -> on2

? ? ? ? ? ? else if (state ==STATE_SWITCH_ON) {

result =bOn2LeftX - (bOn2LeftX -bOffLeftX) * percent;

? ? ? ? ? ? }

break;

? ? ? ? case 3:// off -> on

? ? ? ? ? ? result =bOnLeftX - (bOnLeftX -bOffLeftX) * percent;

break;

? ? ? ? case -1:

// on -> on2

? ? ? ? ? ? if (state ==STATE_SWITCH_ON2) {

result =bOn2LeftX + (bOnLeftX -bOn2LeftX) * percent;

? ? ? ? ? ? }

// off2 -> off

? ? ? ? ? ? else if (state ==STATE_SWITCH_OFF) {

result =bOffLeftX + (bOff2LeftX -bOffLeftX) * percent;

? ? ? ? ? ? }

break;

? ? ? ? case -2:

// on2 -> off

? ? ? ? ? ? if (state ==STATE_SWITCH_OFF) {

result =bOffLeftX + (bOn2LeftX -bOffLeftX) * percent;

? ? ? ? ? ? }

// on -> off2

? ? ? ? ? ? else if (state ==STATE_SWITCH_OFF2) {

result =bOff2LeftX + (bOnLeftX -bOff2LeftX) * percent;

? ? ? ? ? ? }

break;

? ? ? ? case -3:// on -> off

? ? ? ? ? ? result =bOffLeftX + (bOnLeftX -bOffLeftX) * percent;

break;

? ? ? }

return result -bOffLeftX;

? }

@Override

? protected void onDraw(Canvas canvas) {

super.onDraw(canvas);

? ? ? paint.setAntiAlias(true);

? ? ? final boolean isOn = (state ==STATE_SWITCH_ON ||state ==STATE_SWITCH_ON2);

? ? ? // draw background

? ? ? paint.setStyle(Style.FILL);

? ? ? paint.setColor(isOn ?0xff4bd763 :0xffe3e3e3);

? ? ? canvas.drawPath(sPath, paint);

? ? ? sAnim =sAnim -0.1f >0 ?sAnim -0.1f :0;

? ? ? bAnim =bAnim -0.1f >0 ?bAnim -0.1f :0;

? ? ? final float dsAnim =aInterpolator.getInterpolation(sAnim);

? ? ? final float dbAnim =aInterpolator.getInterpolation(bAnim);

? ? ? // draw background animation

? ? ? final float scale =sScale * (isOn ? dsAnim :1 - dsAnim);

? ? ? final float scaleOffset = (bOnLeftX +bRadius -sCenterX) * (isOn ?1 - dsAnim : dsAnim);

? ? ? canvas.save();

? ? ? canvas.scale(scale, scale, sCenterX + scaleOffset, sCenterY);

? ? ? paint.setColor(0xffffffff);

? ? ? canvas.drawPath(sPath, paint);

? ? ? canvas.restore();

? ? ? // draw center bar

? ? ? canvas.save();

? ? ? canvas.translate(calcBTranslate(dbAnim), shadowHeight);

? ? ? final boolean isState2 = (state ==STATE_SWITCH_ON2 ||state ==STATE_SWITCH_OFF2);

? ? ? calcBPath(isState2 ?1 - dbAnim : dbAnim);

? ? ? // draw shadow

? ? ? paint.setStyle(Style.FILL);

? ? ? paint.setColor(0xff333333);

? ? ? paint.setShader(shadowGradient);

? ? ? canvas.drawPath(bPath, paint);

? ? ? paint.setShader(null);

? ? ? canvas.translate(0, -shadowHeight);

? ? ? canvas.scale(0.98f, 0.98f, bWidth /2, bWidth /2);

? ? ? paint.setStyle(Style.FILL);

? ? ? paint.setColor(0xffffffff);

? ? ? canvas.drawPath(bPath, paint);

? ? ? paint.setStyle(Style.STROKE);

? ? ? paint.setStrokeWidth(bStrokeWidth *0.5f);

? ? ? paint.setColor(isOn ?0xff4ada60 :0xffbfbfbf);

? ? ? canvas.drawPath(bPath, paint);

? ? ? canvas.restore();

? ? ? paint.reset();

? ? ? if (sAnim >0 ||bAnim >0) invalidate();

? }

@Override

? public boolean onTouchEvent(MotionEvent event) {

if ((state ==STATE_SWITCH_ON ||state ==STATE_SWITCH_OFF) && (sAnim *bAnim ==0)) {

switch (event.getAction()) {

case MotionEvent.ACTION_DOWN:

return true;

? ? ? ? ? ? case MotionEvent.ACTION_UP:

lastState =state;

? ? ? ? ? ? ? if (state ==STATE_SWITCH_OFF) {

refreshState(STATE_SWITCH_OFF2);

? ? ? ? ? ? ? }else if (state ==STATE_SWITCH_ON) {

refreshState(STATE_SWITCH_ON2);

? ? ? ? ? ? ? }

bAnim =1;

? ? ? ? ? ? ? invalidate();

? ? ? ? ? ? ? if (state ==STATE_SWITCH_OFF2) {

listener.toggleToOn(this);

? ? ? ? ? ? ? }else if (state ==STATE_SWITCH_ON2) {

listener.toggleToOff(this);

? ? ? ? ? ? ? }

break;

? ? ? ? }

}

return super.onTouchEvent(event);

? }

private void refreshState(int newState) {

if (!isOpened && newState ==STATE_SWITCH_ON) {

isOpened =true;

? ? ? }else if (isOpened && newState ==STATE_SWITCH_OFF) {

isOpened =false;

? ? ? }

lastState =state;

? ? ? state = newState;

? ? ? postInvalidate();

? }

/**

? ? * @return the state of switch view

*/

? public boolean isOpened() {

return isOpened;

? }

/**

* if set true , the state change to on;

* if set false, the state change to off

*

? ? * @param isOpened

? ? */

? public void setOpened(boolean isOpened) {

refreshState(isOpened ?STATE_SWITCH_ON :STATE_SWITCH_OFF);

? }

/**

* if set true , the state change to on;

* if set false, the state change to off

? ? *
change state with animation

? ? *

? ? * @param isOpened

? ? */

? public void toggleSwitch(final boolean isOpened) {

this.isOpened = isOpened;

? ? ? postDelayed(new Runnable() {

@Override

? ? ? ? public void run() {

toggleSwitch(isOpened ?STATE_SWITCH_ON :STATE_SWITCH_OFF);

? ? ? ? }

}, 300);

? }

private synchronized void toggleSwitch(int wich) {

if (wich ==STATE_SWITCH_ON || wich ==STATE_SWITCH_OFF) {

if ((wich ==STATE_SWITCH_ON && (lastState ==STATE_SWITCH_OFF ||lastState ==STATE_SWITCH_OFF2))

|| (wich ==STATE_SWITCH_OFF && (lastState ==STATE_SWITCH_ON ||lastState ==STATE_SWITCH_ON2))) {

sAnim =1;

? ? ? ? }

bAnim =1;

? ? ? ? refreshState(wich);

? ? ? }

}

public interface OnStateChangedListener {

void toggleToOn(View view);

? ? ? void toggleToOff(View view);

? }

private OnStateChangedListenerlistener =new OnStateChangedListener() {

@Override

? ? ? public void toggleToOn(View view) {

toggleSwitch(STATE_SWITCH_ON);

? ? ? }

@Override

? ? ? public void toggleToOff(View view) {

toggleSwitch(STATE_SWITCH_OFF);

? ? ? }

};

? public void setOnStateChangedListener(OnStateChangedListener listener) {

if (listener ==null)throw new IllegalArgumentException("empty listener");

? ? ? this.listener = listener;

? }

@Override

? public ParcelableonSaveInstanceState() {

Parcelable superState =super.onSaveInstanceState();

? ? ? SavedState ss =new SavedState(superState);

? ? ? ss.isOpened =isOpened;

? ? ? return ss;

? }

@Override

? public void onRestoreInstanceState(Parcelable state) {

SavedState ss = (SavedState) state;

? ? ? super.onRestoreInstanceState(ss.getSuperState());

? ? ? this.isOpened = ss.isOpened;

? ? ? this.state =this.isOpened ?STATE_SWITCH_ON :STATE_SWITCH_OFF;

? }

static final class SavedStateextends BaseSavedState {

private boolean isOpened;

? ? ? SavedState(Parcelable superState) {

super(superState);

? ? ? }

private SavedState(Parcel in) {

super(in);

? ? ? ? isOpened =1 == in.readInt();

? ? ? }

@Override

? ? ? public void writeToParcel(Parcel out, int flags) {

super.writeToParcel(out, flags);

? ? ? ? out.writeInt(isOpened ?1 :0);

? ? ? }

}

}

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,431評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,637評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 178,555評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,900評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,629評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,976評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,976評論 3 448
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,139評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,686評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,411評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,641評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,129評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,820評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,233評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,567評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,362評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,604評論 2 380

推薦閱讀更多精彩內容