自定义开关按钮控件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阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容