diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0d9283c --- /dev/null +++ b/LICENSE @@ -0,0 +1,8 @@ +The MIT License (MIT) +Copyright (c) 2016 https://github.com/zcweng/SwitchButton + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md index d061c5d..ece6522 100644 --- a/README.md +++ b/README.md @@ -1,41 +1,53 @@ # SwitchButton -具有与IOS开关控件相似样式的Android控件 +------- +SwitchButton.具有与IOS开关控件相似样式和行为的Android控件 ``` - 阴影半径 - 阴影偏移 - 阴影颜色 - 关闭颜色 - 开启颜色 - 边框宽度 - 开启指示器颜色 - 开启指示器线宽 - 关闭指示器颜色 - 关闭指示器线宽 - 关闭指示器半径 - 是否选中 - 是否启用阴影 - 动画时间,默认300ms - 按钮颜色 - 是否显示指示器,默认true:显示 - 背景色,默认白色 - 是否启用特效,默认true - - - + 阴影半径 + 阴影偏移 + 阴影颜色 + 关闭颜色 + 开启颜色 + 边框宽度 + 开启指示器颜色 + 开启指示器线宽 + 关闭指示器颜色 + 关闭指示器线宽 + 关闭指示器半径 + 是否选中 + 是否启用阴影 + 动画时间,默认300ms + 按钮颜色 + 是否显示指示器,默认true:显示 + 背景色,默认白色 + 是否启用特效,默认true + + SwitchButton switchButton = (SwitchButton) findViewById(R.id.switch_button); switchButton.setChecked(true); + switchButton.isChecked(); + switchButton.toggle(); //切换开关 + switchButton.toggle(false);//无动画切换 + //禁用按钮 + switchButton.setEnabled(false); + switchButton.setOnCheckedChangeListener(new SwitchButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(SwitchButton view, boolean isChecked) { //TODO do your job } }); - //禁用按钮 - switchButton.setEnabled(false); + ``` + + + + +License +------- +MIT, See the [LICENSE] file for details. diff --git a/build.gradle b/build.gradle index 74b2ab0..eb09adb 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,10 @@ buildscript { // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files + + classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3' + classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' +// classpath 'com.novoda:bintray-release:0.4.0' } } diff --git a/device-capture.png b/device-capture.png index 5cbbd40..ce66039 100644 Binary files a/device-capture.png and b/device-capture.png differ diff --git a/library/build.gradle b/library/build.gradle index 1d12413..8076a2c 100644 --- a/library/build.gradle +++ b/library/build.gradle @@ -1,4 +1,11 @@ apply plugin: 'com.android.library' +apply plugin: 'com.github.dcendents.android-maven' +apply plugin: 'com.jfrog.bintray' +//apply plugin: 'com.novoda.bintray-release' + +def MODULE_NAME = "SwitchButton" +def VERSION_CODE = 1 +def VERSION_NAME = "0.0.1" android { compileSdkVersion 23 @@ -7,15 +14,121 @@ android { defaultConfig { minSdkVersion 11 targetSdkVersion 23 + versionCode VERSION_CODE + versionName VERSION_NAME } - sourceSets { - main { - } + lintOptions { + abortOnError false } + } dependencies{ } + +def SITE_URL = "https://github.com/zcweng/SwitchButton" +def GIT_URL = "https://github.com/zcweng/SwitchButton.git" +def ISSUE_URL = "https://github.com/zcweng/SwitchButton/issues" +def LICENSE_URL = "https://github.com/zcweng/SwitchButton/LICENSE" +def DESCRIPTION = "SwitchButton.具有与IOS开关控件相似样式和行为的Android控件" +group = "com.suke.widget" + +//Properties properties = new Properties() +//properties.load(project.rootProject.file('local.properties').newDataInputStream()) +//publish { +// userOrg = properties.getProperty("bintray.user") +// groupId = group +// artifactId = MODULE_NAME +// publishVersion = VERSION_NAME +// desc = DESCRIPTION +// website = SITE_URL +//} + + +install { + repositories.mavenInstaller { + pom { + project { + packaging 'aar' + name DESCRIPTION + url SITE_URL + licenses { + license { + name 'The MIT License (MIT)' + url LICENSE_URL + } + } + developers { + developer { + id 'suke' + name 'suke' + email 'zhouchangwen@gmail.com' + } + } + scm { + connection GIT_URL + developerConnection GIT_URL + url SITE_URL + } + } + } + } +} +task sourcesJar(type: Jar) { + from android.sourceSets.main.java.srcDirs + classifier = 'sources' +} +task javadoc(type: Javadoc) { + source = android.sourceSets.main.java.srcDirs + classpath += project.files(android.getBootClasspath().join(File.pathSeparator)) +} +task javadocJar(type: Jar, dependsOn: javadoc) { + classifier = 'javadoc' + from javadoc.destinationDir +} +artifacts { + archives javadocJar + archives sourcesJar +} + + +Properties properties = new Properties() +properties.load(project.rootProject.file('local.properties').newDataInputStream()) +def BINTRAY_USER = properties.getProperty("bintray.user") +def BINTRAY_KEY = properties.getProperty("bintray.apikey") + +bintray { + user = BINTRAY_USER + key = BINTRAY_KEY + configurations = ['archives'] + filesSpec { + from 'build/libs' + into 'standalone_files/level1' + } + dryRun = false + publish = true + override = true + pkg { + repo = "maven" + name = "switch-button" + desc = DESCRIPTION + websiteUrl = SITE_URL + vcsUrl = GIT_URL + userOrg = BINTRAY_USER + issueTrackerUrl = ISSUE_URL + licenses = ["MIT"] + labels = ['android'] //标签 + publish = true + publicDownloadNumbers = true + } + version { + name = VERSION_NAME + desc = DESCRIPTION + released = new Date() + vcsTag = VERSION_NAME + attributes = ['gradle-plugin': 'com.use.less:com.use.less.gradle:gradle-useless-plugin'] + } +} diff --git a/library/src/main/java/com/suke/widget/SwitchButton.java b/library/src/main/java/com/suke/widget/SwitchButton.java index 89290ce..82c7d2d 100644 --- a/library/src/main/java/com/suke/widget/SwitchButton.java +++ b/library/src/main/java/com/suke/widget/SwitchButton.java @@ -20,26 +20,6 @@ /** * SwitchButton. - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * */ public class SwitchButton extends View implements Checkable { private static final int DEFAULT_WIDTH = dp2pxInt(58); @@ -205,10 +185,8 @@ private void init(Context context, AttributeSet attrs) { valueAnimator.addUpdateListener(animatorUpdateListener); valueAnimator.addListener(animatorListener); - + super.setClickable(true); super.setPadding(0,0,0,0); - super.setOnClickListener(innerOnClickListener); - super.setOnLongClickListener(innerOnLongClickListener); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { setLayerType(LAYER_TYPE_SOFTWARE, null); } @@ -231,20 +209,6 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } - class ViewState { - float buttonX; - int checkStateColor; - int checkLineColor; - float radius; - ViewState(){} - private void copy(ViewState source){ - this.buttonX = source.buttonX; - this.checkStateColor = source.checkStateColor; - this.checkLineColor = source.checkLineColor; - this.radius = source.radius; - } - } - @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); @@ -281,17 +245,23 @@ protected void onSizeChanged(int w, int h, int oldw, int oldh) { } + /** + * @param viewState + */ private void setUncheckViewState(ViewState viewState){ viewState.radius = 0; viewState.checkStateColor = uncheckColor; - viewState.checkLineColor = Color.TRANSPARENT; + viewState.checkedLineColor = Color.TRANSPARENT; viewState.buttonX = buttonMinX; } + /** + * @param viewState + */ private void setCheckedViewState(ViewState viewState){ viewState.radius = viewRadius; viewState.checkStateColor = checkedColor; - viewState.checkLineColor = checkLineColor; + viewState.checkedLineColor = checkLineColor; viewState.buttonX = buttonMaxX; } @@ -355,7 +325,7 @@ protected void onDraw(Canvas canvas) { */ protected void drawCheckedIndicator(Canvas canvas) { drawCheckedIndicator(canvas, - viewState.checkLineColor, + viewState.checkedLineColor, checkLineWidth, left + viewRadius - checkedLineOffsetX, centerY - checkLineLength, left + viewRadius - checkedLineOffsetY, centerY + checkLineLength, @@ -423,6 +393,16 @@ protected void drawUncheckIndicator(Canvas canvas, canvas.drawCircle(centerX, centerY, radius, paint); } + /** + * @param canvas + * @param left + * @param top + * @param right + * @param bottom + * @param startAngle + * @param sweepAngle + * @param paint + */ private void drawArc(Canvas canvas, float left, float top, float right, float bottom, @@ -438,6 +418,15 @@ private void drawArc(Canvas canvas, } } + /** + * @param canvas + * @param left + * @param top + * @param right + * @param bottom + * @param backgroundRadius + * @param paint + */ private void drawRoundRect(Canvas canvas, float left, float top, float right, float bottom, @@ -487,6 +476,10 @@ public void toggle() { toggle(true); } + /** + * 切换状态 + * @param animate + */ public void toggle(boolean animate) { if(!isEnabled()){return;} @@ -527,6 +520,9 @@ public void toggle(boolean animate) { valueAnimator.start(); } + /** + * + */ private void broadcastEvent() { if(onCheckedChangeListener != null){ isEventBroadcast = true; @@ -535,6 +531,7 @@ private void broadcastEvent() { isEventBroadcast = false; } + @Override public boolean onTouchEvent(MotionEvent event) { int actionMasked = event.getActionMasked(); @@ -542,12 +539,26 @@ public boolean onTouchEvent(MotionEvent event) { switch (actionMasked){ case MotionEvent.ACTION_DOWN:{ isTouchingDown = true; + touchDownTime = System.currentTimeMillis(); + //取消准备进入拖动状态 + removeCallbacks(postPendingDrag); + //预设100ms进入拖动状态 + postDelayed(postPendingDrag, 100); break; } case MotionEvent.ACTION_MOVE:{ float eventX = event.getX(); - if(isDragState()){ + if(isPendingDragState()){ + //在准备进入拖动状态过程中,可以拖动按钮位置 + float fraction = eventX / getWidth(); + fraction = Math.max(0f, Math.min(1f, fraction)); + + viewState.buttonX = buttonMinX + + (buttonMaxX - buttonMinX) + * fraction; + }else if(isDragState()){ + //拖动按钮位置,同时改变对应的背景颜色 float fraction = eventX / getWidth(); fraction = Math.max(0f, Math.min(1f, fraction)); @@ -560,15 +571,21 @@ public boolean onTouchEvent(MotionEvent event) { uncheckColor, checkedColor ); - postInvalidate(); + } break; } - case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP:{ isTouchingDown = false; - if(isDragState()){ + //取消准备进入拖动状态 + removeCallbacks(postPendingDrag); + + if(System.currentTimeMillis() - touchDownTime <= 300){ + //点击时间小于300ms,认为是点击操作 + toggle(); + }else if(isDragState()){ + //在拖动状态,计算按钮位置,设置是否切换状态 float eventX = event.getX(); float fraction = eventX / getWidth(); fraction = Math.max(0f, Math.min(1f, fraction)); @@ -580,26 +597,57 @@ public boolean onTouchEvent(MotionEvent event) { pendingSettleState(); } }else if(isPendingDragState()){ + //在准备进入拖动状态过程中,取消之,复位 + pendingCancelDragState(); + } + break; + } + case MotionEvent.ACTION_CANCEL:{ + isTouchingDown = false; + + removeCallbacks(postPendingDrag); + + if(isPendingDragState() + || isDragState()){ + //复位 pendingCancelDragState(); } break; } } - return super.onTouchEvent(event); + return true; } + /** + * 是否在动画状态 + * @return + */ private boolean isInAnimating(){ return animateState != ANIMATE_STATE_NONE; } + + /** + * 是否在进入拖动或离开拖动状态 + * @return + */ private boolean isPendingDragState(){ return animateState == ANIMATE_STATE_PENDING_DRAG || animateState == ANIMATE_STATE_PENDING_RESET; } + + /** + * 是否在手指拖动状态 + * @return + */ private boolean isDragState(){ return animateState == ANIMATE_STATE_DRAGING; } + /** + * 设置是否启用阴影效果 + * @param shadowEffect true.启用 + */ public void setShadowEffect(boolean shadowEffect) { if(this.shadowEffect == shadowEffect){return;} this.shadowEffect = shadowEffect; @@ -618,7 +666,7 @@ public void setShadowEffect(boolean shadowEffect) { } /** - * 80-50 + * 开始进入拖动状态 */ private void pendingDragState() { if(isInAnimating()){return;} @@ -636,7 +684,7 @@ private void pendingDragState() { if(isChecked()){ afterState.checkStateColor = checkedColor; afterState.buttonX = buttonMaxX; - afterState.checkLineColor = checkedColor; + afterState.checkedLineColor = checkedColor; }else{ afterState.checkStateColor = uncheckColor; afterState.buttonX = buttonMinX; @@ -647,6 +695,9 @@ private void pendingDragState() { } + /** + * 取消拖动状态 + */ private void pendingCancelDragState() { if(isDragState() || isPendingDragState()){ if(valueAnimator.isRunning()){ @@ -666,6 +717,9 @@ private void pendingCancelDragState() { } + /** + * 动画-设置新的状态 + */ private void pendingSettleState() { if(valueAnimator.isRunning()){ valueAnimator.cancel(); @@ -684,14 +738,10 @@ private void pendingSettleState() { @Override - public void setOnClickListener(OnClickListener l) { - delegateOnClickListener = l; - } + public final void setOnClickListener(OnClickListener l) {} @Override - public void setOnLongClickListener(OnLongClickListener l) { - delegateOnLongClickListener = l; - } + public final void setOnLongClickListener(OnLongClickListener l) {} public void setOnCheckedChangeListener(OnCheckedChangeListener l){ onCheckedChangeListener = l; @@ -914,26 +964,19 @@ private static boolean optBoolean(TypedArray typedArray, private OnCheckedChangeListener onCheckedChangeListener; - private OnClickListener - delegateOnClickListener, - innerOnClickListener = new OnClickListener() { - @Override - public void onClick(View v) { - toggle(); - if(delegateOnClickListener != null){ - delegateOnClickListener.onClick(v); - } - } - }; - - private OnLongClickListener delegateOnLongClickListener, - innerOnLongClickListener = new OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - pendingDragState(); - return true; - } - }; + /** + * 手势按下的时刻 + */ + private long touchDownTime; + + private Runnable postPendingDrag = new Runnable() { + @Override + public void run() { + if(!isInAnimating()){ + pendingDragState(); + } + } + }; private ValueAnimator.AnimatorUpdateListener animatorUpdateListener = new ValueAnimator.AnimatorUpdateListener() { @@ -941,22 +984,24 @@ public boolean onLongClick(View v) { public void onAnimationUpdate(ValueAnimator animation) { float value = (Float) animation.getAnimatedValue(); switch (animateState) { - case ANIMATE_STATE_PENDING_RESET: { - } case ANIMATE_STATE_PENDING_SETTLE: { } + case ANIMATE_STATE_PENDING_RESET: { + } case ANIMATE_STATE_PENDING_DRAG: { - viewState.checkLineColor = (int) argbEvaluator.evaluate( + viewState.checkedLineColor = (int) argbEvaluator.evaluate( value, - beforeState.checkLineColor, - afterState.checkLineColor + beforeState.checkedLineColor, + afterState.checkedLineColor ); viewState.radius = beforeState.radius + (afterState.radius - beforeState.radius) * value; - viewState.buttonX = beforeState.buttonX - + (afterState.buttonX - beforeState.buttonX) * value; + if(animateState != ANIMATE_STATE_PENDING_DRAG){ + viewState.buttonX = beforeState.buttonX + + (afterState.buttonX - beforeState.buttonX) * value; + } viewState.checkStateColor = (int) argbEvaluator.evaluate( value, @@ -979,7 +1024,7 @@ public void onAnimationUpdate(ValueAnimator animation) { ); viewState.radius = fraction * viewRadius; - viewState.checkLineColor = (int) argbEvaluator.evaluate( + viewState.checkedLineColor = (int) argbEvaluator.evaluate( fraction, Color.TRANSPARENT, checkLineColor @@ -1011,7 +1056,7 @@ public void onAnimationEnd(Animator animation) { } case ANIMATE_STATE_PENDING_DRAG: { animateState = ANIMATE_STATE_DRAGING; - viewState.checkLineColor = Color.TRANSPARENT; + viewState.checkedLineColor = Color.TRANSPARENT; viewState.radius = viewRadius; postInvalidate(); @@ -1051,4 +1096,35 @@ public void onAnimationRepeat(Animator animation) { } }; + + /*******************************************************/ + /** + * 保存动画状态 + * */ + private static class ViewState { + /** + * 按钮x位置[buttonMinX-buttonMaxX] + */ + float buttonX; + /** + * 状态背景颜色 + */ + int checkStateColor; + /** + * 选中线的颜色 + */ + int checkedLineColor; + /** + * 状态背景的半径 + */ + float radius; + ViewState(){} + private void copy(ViewState source){ + this.buttonX = source.buttonX; + this.checkStateColor = source.checkStateColor; + this.checkedLineColor = source.checkedLineColor; + this.radius = source.radius; + } + } + } diff --git a/sample/src/main/java/com/suke/widget/sample/MainActivity.java b/sample/src/main/java/com/suke/widget/sample/MainActivity.java index c482c22..a6654ad 100644 --- a/sample/src/main/java/com/suke/widget/sample/MainActivity.java +++ b/sample/src/main/java/com/suke/widget/sample/MainActivity.java @@ -12,6 +12,15 @@ protected void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_main); SwitchButton switchButton = (SwitchButton) findViewById(R.id.switch_button); + + switchButton.setChecked(true); + switchButton.isChecked(); + switchButton.toggle(); //切换开关 + switchButton.toggle(false);//无动画切换 + switchButton.setShadowEffect(true);//设置是否启用阴影效果 + //禁用按钮 + switchButton.setEnabled(false); + switchButton.setOnCheckedChangeListener(new SwitchButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(SwitchButton view, boolean isChecked) { @@ -19,5 +28,7 @@ public void onCheckedChanged(SwitchButton view, boolean isChecked) { } }); + + } }