diff --git a/.gitignore b/.gitignore index b0fce84..7654e9d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,5 +18,3 @@ hs_err_pid* bin/ gen/ .DS_Store - -SwitchButton/gradle.properties diff --git a/CHANGELOG.md b/CHANGELOG.md index 054529d..18a269c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,4 @@ + Change Log ============ @@ -10,29 +11,102 @@ This project provides you a convenient way to use and customise a SwitchButton w Now we get the biggest movement since SwitchButton published. v1.3.0 comes with totally reconsitution and more convenient API. A wholly new demo can give you a tour in it. *** -update 1.4.4 (Latest) + +## 2.1.0 (Latest) + +**ENG** + +1. Migrate to maven central + + +**CHN** + +1. 迁移到 maven central + +## 2.0.3 + +**ENG** + +1. Fix [#103](https://github.com/kyleduo/SwitchButton/issues/103). Determin whether state change is triggered manully using `isPressed` in OnCheckedChangeListener is supported. +2. Remove targetSdkVersion declaration. +3. Upgrate compileSdkVersion to 29. + +**CHN** + +1. 修复 [#103](https://github.com/kyleduo/SwitchButton/issues/103)。支持在OnCheckedChangeListener中使用`isPressed`判断是否是手动触发。 +2. 移除targetSdkVersion声明 +3. 更新compileSdkVersion到29 + +## 2.0.2 + +**ENG** + +1. Fix [#122](https://github.com/kyleduo/SwitchButton/issues/122). Support sub-class extending from SwitchButton. +2. Fix bug which move faster than expected while touch and move vertically. +3. Remove dependency of AppCompat library. + + +**CHN** + +1. 修复 [#122](https://github.com/kyleduo/SwitchButton/issues/122)。支持从SwitchButton集成子类。 +2. 修复纵向滑动导致移动过快的Bug。 +3. 移除对AppCompat库的依赖。 + + +## 2.0.0 + +**ENG** + +1. Re-clarify the meaning of some params. +2. Update the measurement of SwitchButton and the logic becomes more clear, especially the text part. +3. Support config SwitchButton's size by setting a exact width and height. There are now TWO mainly method to control it's size. + + +**CHN** + +1. 重新明确了参数的含义。 +2. 更新了SwitchButton的测量机制,逻辑更加清晰;尤其是文字部分。 +3. 支持设置确定的宽高,来确定SwitchButton的View大小。现在有两种方式可以控制SwitchButton的大小了。 + + + +## 1.4.6 + +- Fixed [#89](https://github.com/kyleduo/SwitchButton/issues/89) . + +## 1.4.5 + +- Fixed [#75](https://github.com/kyleduo/SwitchButton/issues/75) [#78](https://github.com/kyleduo/SwitchButton/issues/78) [#85](https://github.com/kyleduo/SwitchButton/issues/85). + + + +1.4.4 --- * Fixed [#65](https://github.com/kyleduo/SwitchButton/issues/65). * Update text layout, tests looks like center. -update 1.4.3 + +1.4.3 --- * Fixed [#64](https://github.com/kyleduo/SwitchButton/issues/64). Respect to clickable and focusable attributes. -update 1.4.2 + +1.4.2 --- * Support [#49](https://github.com/kyleduo/SwitchButton/issues/60). By default SwitchButton found accentColor of your theme for tintColor. (**accentColor** is used for controls according to [Material Design guideline](https://material.google.com/style/color.html#color-color-schemes). ) -update 1.4.1 + +1.4.1 --- * fixed [#49](https://github.com/kyleduo/SwitchButton/issues/49). * Support operation without onCheckedChanged callback. -update 1.4.0 + +1.4.0 --- * Add text feature. You can set text for either checked or unchecked status with __kswTextOn__ and __kswTextOff__ attrs. And you can set the margin of text in horizontal direction using __kswTextMarginH__ attr. @@ -40,91 +114,103 @@ update 1.4.0 * Thanks [@lpmfilho](https://github.com/lpmfilho) *** -update 1.3.4 + +1.3.4 --- * Fix [#40](https://github.com/kyleduo/SwitchButton/issues/40) *** -update 1.3.3 + +1.3.3 --- * Fix SwitchButtonMD style bug in RecyclerView and add page for test. -* Fix bug in setCheckedImmediately() in onCheckedChanged() method. **(setChecked in onChecked)** + *Fix bug in setCheckedImmediately() in onCheckedChanged() method. **(setChecked in onChecked)** *** -update 1.3.2 + +1.3.2 --- * **setClickable(boolean)** support. -* Bug fix. + * Bug fix. *** -update 1.3.1 + +1.3.1 --- * Remove shadow of MD style to support under 5.0. *** -update 1.3.0 + +1.3.0 --- * Reconstructe the whole library. -* More convenient customization way by __tintColor__. -* New design demo. All APIs in ONE. -* Fix issue [#23](https://github.com/kyleduo/SwitchButton/issues/23) [#25](https://github.com/kyleduo/SwitchButton/issues/25) [#26](https://github.com/kyleduo/SwitchButton/issues/26) -* Just exciting!!! + * More convenient customization way by __tintColor__. + * New design demo. All APIs in ONE. + *Fix issue [#23](https://github.com/kyleduo/SwitchButton/issues/23) [#25](https://github.com/kyleduo/SwitchButton/issues/25) [#26](https://github.com/kyleduo/SwitchButton/issues/26) + *Just exciting!!! *** -update 1.2.10 + +1.2.10 --- * Fix issue [#22](https://github.com/kyleduo/SwitchButton/issues/22) by change the attributes' name to prevent conflict; *** -update 1.2.9 +1.2.9 --- * Fix issue [#19](https://github.com/kyleduo/SwitchButton/issues/19). *** -update 1.2.8 + +1.2.8 --- * Fix stretch bug while using higher API. -* Add Gradle support. -* Built in Android Studio. + *Add Gradle support. + *Built in Android Studio. *** -update 1.2.7 + +1.2.7 --- * Fix rendering bug on some devices. -* Fix states bug. + *Fix states bug. *** -update 1.2.6 + +1.2.6 --- * With calling the method ___setChecked(boolean, false);___, you can change the status without invoking the listener. *** -update 1.2.5 + +1.2.5 --- * Fix shrink bug in Android 5.0 (the problem is same like it is in Android 4.4, which has been fixed in 1.2.4). -* More available to setup Material Design style SwitchButton using ___@style/MD___ in xml layout. -* Fix Demo Project bug + *More available to setup Material Design style SwitchButton using ___@style/MD___ in xml layout. + *Fix Demo Project bug *** -update 1.2.4 + +1.2.4 --- * fix shrink bug(that will cause the content out of bounds not disapper, on Android 4.4) * upload .pad resource, whitch I forgot to upload before.(My fault.) *** -update 1.2.3 + +1.2.3 --- * bug fix * upgrade demo apk @@ -142,7 +228,8 @@ When toggle, call ___toggle();___ with change the status immediately and ___togg ![easy_to_use](https://raw.githubusercontent.com/kyleduo/SwitchButton/master/preview/easy_to_use_128.png) *** -update 1.2 + +1.2 --- (11/08/2014) @@ -163,7 +250,8 @@ To use shrink feature, you can easily add these attributes in your xml file. It *** -update 1.1 + +1.1 --- (10/08/2014) @@ -182,6 +270,7 @@ new default style and demo apk looks like this: *** -Update 1.0 + +1.0 --- -Add an attr of radius, now you can change the radius when configure the button's face! \ No newline at end of file +Add an attr of radius, now you can change the radius when configure the button's face! diff --git a/README.md b/README.md index 7710883..a55840a 100644 --- a/README.md +++ b/README.md @@ -1,46 +1,89 @@ + SwitchButton ============ [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-SwitchButton-brightgreen.svg?style=flat)](https://android-arsenal.com/details/1/1119) -**To get a quick preview, you can get Demo apk in [Google Play](https://play.google.com/store/apps/details?id=com.kyleduo.switchbutton.demo) or [Directly download](./demo/switchbutton_demo_142.apk).** - -This project provides you a convenient way to use and customise a SwitchButton widget in Android. With just resources changed and attrs set, you can create a lifelike SwitchButton of Android 5.0+, iOS, MIUI, or Flyme and so on. +**To get a quick preview, you can get Demo apk in [Google Play](https://play.google.com/store/apps/details?id=com.kyleduo.switchbutton.demo) or [Directly download](./demo/switchbutton_demo_200.apk).** -Now we get the biggest movement since SwitchButton published. v1.3.0 comes with totally reconsitution and more convenient API. A wholly new demo can give you a tour in it. +This project provides you a convenient way to use and customise a SwitchButton widget in Android. *** + + Change Log --- -Latested Change Log: +[All Change Log](./CHANGELOG.md) + +**Some attributes are changed in 2.0.0 and you need to update them to the new ones, since the measurement logic has been totally changed. (Default behavior and style does not change.)** -> 1. Fixed [#65](https://github.com/kyleduo/SwitchButton/issues/65). -> 2. Update text layout, tests looks like center. +**因为2.0.0测量逻辑的改变,部分属性已经做了修改,请更新到新的属性。(默认行为和样式并没有变化)** + +> 1. Re-clarify the meaning of some params. +> 2. Update the measurement of SwitchButton and the logic becomes more clear, especially the text part. +> 3. Support config SwitchButton's size by setting a exact width and height. There are now TWO mainly method to control it's size. +> 4. When use SwitchButon in a scrollable view, SwitchButton consume scroll gestures only horizontal. This means the when you want to scroll the scrollable view vertically and start your touch on a SwitchButton, it will not stop you now. +> 5. Bug fix. + +>1. 重新明确了参数的含义。 +>2. 更新了SwitchButton的测量机制,逻辑更加清晰;尤其是文字部分。 +>3. 支持设置确定的宽高,来确定SwitchButton的View大小。现在有两种方式可以控制SwitchButton的大小了。 +>4. 可滚动的View中的SwitchButton只会消费横向滚动事件。这意味着你可以从SwitchButton开始按下并纵向滚动可滚动View,SwitchButton现在不会阻止你滚动了。 +>5. Bug 修复。 -[Change History.](https://github.com/kyleduo/SwitchButton/blob/master/CHANGELOG.md) Using SwitchButton in your application --- -~~ADT~~ - No more support - -__Gradle__ - +**In Gradle** +​ Add dependencies in build.gradle of your module - dependencies { - compile 'com.kyleduo.switchbutton:library:1.4.4' - } +```groovy +dependencies { + implementation 'com.kyleduo.switchbutton:library:2.1.0' +} +``` + +> Version 2.1.0 can be pulled from `mavenCentral()` + +#### Migrate to 2.0.0 (迁移到2.0.0) + +**ENG** + +**There is a big diagram below to show how SwitchButton measure it self in 2.0.0. It is strongly recommended that you should check it out.** + +1. **kswBackMeasureRatio** has been removed from SwitchButton attributes since it has an ambiguous meaning. I've add the new **kswThumbRangeRatio** attribute to represent how much multiple the scroll range of thumb than the width of thumb. +2. **kswTextMarginH** and **kswAutoAdjustTextPosition** have been removed from SwitchButton attributes since I updated the measurement logic of text part. And these two attributes do not represent the back meaning well. **kswTextThumbInset**, **kswTextExtra** and **kswTextAdjust** was introduced to represent "how much the text go under thumb", "how much extra space do you want around the text" and "how much to move the text from the center of text area to adjust the text's position". There are all shown on the diagram. +3. Setters and getters are also changed due to the change of attributes. + +**CHN** + +下面有一张图表来解释SwitchButton在2.0.0版本中是如何进行测量的,非常建议你看一看。 + +1. **kswBackMeasureRatio** 属性被移除了,因为名称有歧义。新增加的 **kswThumbRangeRatio** 属性表示thumb移动区域和thumb宽度的比值。 +2. **kswTextMarginH** 和 **kswAutoAdjustTextPosition** 属性被移除了,因为我更新了对文字的测量逻辑,而且这两个属性名称表意不明确。我增加了**kswTextThumbInset**, **kswTextExtra** 和 **kswTextAdjust** 这三个新属性来分别表示“文字在thumb下面的距离”,“额外文字空间”和”文字调节距离“。这些都在图表中有所体现。 +3. setter和getter都跟随属性名称的改变而进行了改变。 + +### Diagram: How SwitchButton Measure + +This diagram shows how SwitchButton measure itself and what does those nouns mean. To measure width is much complex than the height, so if you know how to measure width, you know how to measure height. And text measurement and location increase the complexity. + +![demo_preview](./images/how_switchbutton_measure.png) + *** + + Demo --- I create a new demo apk to show you how to style the cute widget and use it. There's some screenshots of the new demo. -![demo_preview](./preview/demo_140.jpg) +![demo_preview](./images/demo_preview.png) *** + + Usage --- @@ -63,6 +106,8 @@ From **version 1.4.1** on, SwitchButton support operation without onCheckedChang * __toggleImmediatelyNoEvent()__ *** + + Style --- @@ -84,17 +129,21 @@ In __xml__ layout file, you can configure the face of switch button using these * __kswBackDrawable__: drawable for background * __kswBackColor__: color for background * __kswFadeBack__: fade background color/drawable when drag or animate between on/off status or not -* __kswBackMeasureRatio__: (background's width / thumb's width). float value. * __kswAnimationDuration__: duration of animation between 2 status * __kswTintColor__: change SwitchButton's style just by __one__ property, all relevant color will be generate automatically. Do not support `SwitchButtonMD` or other style created by xml resources. * __kswTextOn__: text for checked status. * __kswTextOff__: text for unchecked status. -* __kswTextMarginH__: horizontal margin of text. -* __kswAutoAdjustTextPosition__: **(Since 1.4.4)** whether auto adjust text position to make them looks centered (NOT really centered) when there are round corners. You should set this to false when you don't need this feature. +* __kswTextThumbInset (since 2.0.0)__: length of the part of text under the thumb. +* __kswTextExtra (since 2.0.0)__: extra space needed by background besides the actual text width. +* __kswTextAdjust (since 2.0.0)__: move the text after position text on the center of text area. +* **kswThumbRangeRatio (since 2.0.0)**: (thumb move range width / thumb's width). float value. *see measure diagram* +* ~~__kswBackMeasureRatio__: (background's width / thumb's width). float value.~~ *Removed since 2.0.0* +* ~~**kswTextMarginH**: horizontal margin of text.~~ *Removed since 2.0.0* +* ~~__kswAutoAdjustTextPosition__: **(since 1.4.4)** whether auto adjust text position to make them looks centered (NOT really centered) when there are round corners. You should set this to false when you don't need this feature.~~ *Removed since 2.0.0* You can alse change the configuration of SwitchButton ___in code___. You can find the api from Demo apk. There's a glance. -``` +```java private String[] opts = new String[]{ "setThumbColorRes/setThumbColor", "setThumbDrawableRes/setThumbDrawable", @@ -113,11 +162,17 @@ private String[] opts = new String[]{ }; ``` + Beautiful Apps --- -If you're using SwitchButton in your app, wish you can email me the name and link of your app and I'll create a list here. And that should be an utmost encouragement to me. :-) [kyleduo@gmail.com](mailto:kyleduo@gmail.com) +If you're using SwitchButton in your app, wish you can email me these infomation of your app and I'll create a list here. And that should be an utmost encouragement to me. :-) [kyleduo@gmail.com](mailto:kyleduo@gmail.com) +| App | Name | Description | Markets | Developer | +| ---------------------------------------- | ----------------------- | ---------------------------------------- | ---------------------------------------- | ---------------------------------------- | +| ![headlines.png](https://static.kyleduo.com/headlines.png?imageView/2/w/80/) | Headlines | Headlines is a news dashboard for your smart TV. | [![market_logo_google_play.png](https://static.kyleduo.com/market_logo_google_play.png?imageView/2/w/80)](https://play.google.com/store/apps/details?id=com.mystraldesign.headlines)[![amazon-underground-app-us-black.png](https://static.kyleduo.com/amazon-underground-app-us-black.png?imageView/2/w/80)](https://www.amazon.com/Headlines-news-your-big-screen/dp/B01G93GRGC/ref=sr_1_1?ie=UTF8&qid=1487843182&sr=8-1&keywords=mystral+design) | [MYSTRAL](http://www.mystraldesign.com/) | +| ![lantouzi.png](https://static.kyleduo.com/lantouzi.png?imageView/2/w/80) | 懒投资 | 专业安全的投资理财平台 | [官方网站](https://lantouzi.com/mobile/download) | [懒投资 lantouzi.com](https://lantouzi.com) | +| ![notifications_in_bubble.png](https://static.kyleduo.com/project/switchbutton/apps/logo/notifications_in_bubble.png) | Notifications in bubble | Access all notifications from a floating bubble. | [![market_logo_google_play.png](https://static.kyleduo.com/market_logo_google_play.png?imageView/2/w/80)](https://play.google.com/store/apps/details?id=com.ram.chocolate.nm.premium) | [BestAppzz](https://play.google.com/store/apps/developer?id=BestAppzz) | License @@ -126,9 +181,9 @@ License Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - + http://www.apache.org/licenses/LICENSE-2.0 - + Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. diff --git a/SwitchButton/build.gradle b/SwitchButton/build.gradle index 8e64d9d..b3fed8f 100644 --- a/SwitchButton/build.gradle +++ b/SwitchButton/build.gradle @@ -2,20 +2,18 @@ buildscript { repositories { - jcenter() + google() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.3' - classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1' - classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.0' - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath 'com.android.tools.build:gradle:8.0.0' + classpath "com.vanniktech:gradle-maven-publish-plugin:0.25.3" } } allprojects { repositories { - jcenter() + google() + mavenCentral() } } diff --git a/SwitchButton/demo/build.gradle b/SwitchButton/demo/build.gradle index 6ddf6a0..e3f1192 100644 --- a/SwitchButton/demo/build.gradle +++ b/SwitchButton/demo/build.gradle @@ -1,16 +1,20 @@ -apply plugin: 'com.android.application' +plugins { + id('com.android.application') +} android { - compileSdkVersion 23 - buildToolsVersion "22.0.1" + compileSdk 33 + + namespace "com.kyleduo.switchbutton.demo" defaultConfig { applicationId "com.kyleduo.switchbutton.demo" - minSdkVersion 11 - targetSdkVersion 23 - versionCode 20 - versionName "1.4.4" + minSdkVersion 14 + targetSdk 33 + versionCode Integer.parseInt(VERSION_CODE) + versionName VERSION_NAME } + buildTypes { release { minifyEnabled true @@ -20,8 +24,9 @@ android { } dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:23.3.0' - compile 'com.android.support:recyclerview-v7:23.3.0' - compile project(':library') + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation("androidx.appcompat:appcompat:1.6.1") + implementation("androidx.recyclerview:recyclerview:1.3.2") + implementation project(':library') +// implementation 'com.kyleduo.switchbutton:library:2.0.3' } diff --git a/SwitchButton/demo/demo-release.apk b/SwitchButton/demo/demo-release.apk deleted file mode 100644 index 7902d85..0000000 Binary files a/SwitchButton/demo/demo-release.apk and /dev/null differ diff --git a/SwitchButton/demo/src/main/AndroidManifest.xml b/SwitchButton/demo/src/main/AndroidManifest.xml index 970e2f2..7801699 100644 --- a/SwitchButton/demo/src/main/AndroidManifest.xml +++ b/SwitchButton/demo/src/main/AndroidManifest.xml @@ -1,59 +1,55 @@ - + - - - - + + + + - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + - + diff --git a/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/MainActivity.java b/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/MainActivity.java index 29c4487..6a3f330 100644 --- a/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/MainActivity.java +++ b/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/MainActivity.java @@ -3,13 +3,14 @@ import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ListView; +import androidx.appcompat.app.AppCompatActivity; + public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { @@ -32,17 +33,14 @@ public boolean onCreateOptionsMenu(Menu menu) { public boolean onOptionsItemSelected(MenuItem item) { Intent intent = new Intent(Intent.ACTION_VIEW); int id = item.getItemId(); - switch (id) { - case R.id.action_github: - intent.setData(Uri.parse("https://github.com/kyleduo/SwitchButton")); - startActivity(intent); - return true; - case R.id.action_blog: - intent.setData(Uri.parse("http://kyleduo.com")); - startActivity(intent); - return true; - default: - break; + if (id == R.id.action_github) { + intent.setData(Uri.parse("https://github.com/kyleduo/SwitchButton")); + startActivity(intent); + return true; + } else if (id == R.id.action_blog) { + intent.setData(Uri.parse("https://kyleduo.com")); + startActivity(intent); + return true; } return super.onOptionsItemSelected(item); } @@ -63,15 +61,15 @@ private void jumpToRecycler() { startActivity(new Intent(this, RecyclerActivity.class)); } - private void gotoWeibo() { + private void gotoBlog() { Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse("http://kyleduo.com")); + intent.setData(Uri.parse("https://kyleduo.com")); startActivity(intent); } private void gotoLicense() { Intent intent = new Intent(Intent.ACTION_VIEW); - intent.setData(Uri.parse("http://www.apache.org/licenses/LICENSE-2.0")); + intent.setData(Uri.parse("https://www.apache.org/licenses/LICENSE-2.0")); startActivity(intent); } @@ -92,7 +90,7 @@ public void onItemClick(AdapterView parent, View view, int position, long id) jumpToRecycler(); break; case 4: - gotoWeibo(); + gotoBlog(); break; case 5: gotoLicense(); diff --git a/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/RecyclerActivity.java b/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/RecyclerActivity.java index 556f99a..ce73170 100644 --- a/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/RecyclerActivity.java +++ b/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/RecyclerActivity.java @@ -1,9 +1,6 @@ package com.kyleduo.switchbutton.demo; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -15,6 +12,10 @@ import java.util.ArrayList; import java.util.List; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + /** * Created by kyle on 16/1/8. */ diff --git a/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/StyleActivity.java b/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/StyleActivity.java index 5d55e34..37b91ce 100644 --- a/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/StyleActivity.java +++ b/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/StyleActivity.java @@ -1,49 +1,54 @@ package com.kyleduo.switchbutton.demo; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; import android.widget.CompoundButton; import com.kyleduo.switchbutton.SwitchButton; +import androidx.appcompat.app.AppCompatActivity; + public class StyleActivity extends AppCompatActivity { - private SwitchButton mFlymeSb, mMiuiSb, mCustomSb, mDefaultSb, mSB; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_style); - - SwitchButton disableSb = (SwitchButton) findViewById(R.id.sb_disable_control); - SwitchButton disableNoEventSb = (SwitchButton) findViewById(R.id.sb_disable_control_no_event); - mFlymeSb = (SwitchButton) findViewById(R.id.sb_custom_flyme); - mMiuiSb = (SwitchButton) findViewById(R.id.sb_custom_miui); - mCustomSb = (SwitchButton) findViewById(R.id.sb_custom); - mDefaultSb = (SwitchButton) findViewById(R.id.sb_default); - mSB = (SwitchButton) findViewById(R.id.sb_ios); - - disableSb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - mFlymeSb.setEnabled(isChecked); - mMiuiSb.setEnabled(isChecked); - mCustomSb.setEnabled(isChecked); - mDefaultSb.setEnabled(isChecked); - mSB.setEnabled(isChecked); - } - }); - disableNoEventSb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - mFlymeSb.setEnabled(isChecked); - mMiuiSb.setEnabled(isChecked); - mCustomSb.setEnabled(isChecked); - mDefaultSb.setEnabled(isChecked); - mSB.setEnabled(isChecked); - } - }); - disableNoEventSb.setCheckedImmediatelyNoEvent(false); - } + private SwitchButton mFlymeSb, mMIUISb, mCustomSb, mDefaultSb, mSB; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_style); + + SwitchButton disableSb = (SwitchButton) findViewById(R.id.sb_disable_control); + SwitchButton disableNoEventSb = (SwitchButton) findViewById(R.id.sb_disable_control_no_event); + mFlymeSb = (SwitchButton) findViewById(R.id.sb_custom_flyme); + mMIUISb = (SwitchButton) findViewById(R.id.sb_custom_miui); + mCustomSb = (SwitchButton) findViewById(R.id.sb_custom); + mDefaultSb = (SwitchButton) findViewById(R.id.sb_default); + mSB = (SwitchButton) findViewById(R.id.sb_ios); + + if (disableSb != null) { + disableSb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mFlymeSb.setEnabled(isChecked); + mMIUISb.setEnabled(isChecked); + mCustomSb.setEnabled(isChecked); + mDefaultSb.setEnabled(isChecked); + mSB.setEnabled(isChecked); + } + }); + } + if (disableNoEventSb != null) { + disableNoEventSb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mFlymeSb.setEnabled(isChecked); + mMIUISb.setEnabled(isChecked); + mCustomSb.setEnabled(isChecked); + mDefaultSb.setEnabled(isChecked); + mSB.setEnabled(isChecked); + } + }); + disableNoEventSb.setCheckedImmediatelyNoEvent(false); + } + } } diff --git a/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/StyleInCodeActivity.java b/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/StyleInCodeActivity.java index c498899..ab2ce83 100644 --- a/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/StyleInCodeActivity.java +++ b/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/StyleInCodeActivity.java @@ -1,13 +1,10 @@ package com.kyleduo.switchbutton.demo; -import android.graphics.PointF; import android.graphics.RectF; -import android.graphics.drawable.Drawable; import android.os.Bundle; -import android.support.v7.app.ActionBarActivity; import android.text.Spannable; import android.text.SpannableString; -import android.text.SpannableStringBuilder; +import android.text.TextUtils; import android.text.style.ImageSpan; import android.view.View; import android.widget.AdapterView; @@ -16,109 +13,114 @@ import com.kyleduo.switchbutton.SwitchButton; -public class StyleInCodeActivity extends ActionBarActivity implements AdapterView.OnItemClickListener { +import androidx.appcompat.app.AppCompatActivity; - private SwitchButton mChangeSb; - private boolean mThumbMarginFlag, mThumbSizeFlag, mThumbRadiusFlag, mBackRadiusFlag, mBackMeasureRatioFlag, mAnimationDurationFlag; - private String[] opts = new String[]{ - "setThumbColorRes/setThumbColor", - "setThumbDrawableRes/setThumbDrawable", - "setBackColorRes/setBackColor", - "setBackDrawableRes/setBackDrawable", - "setTintColor", - "setThumbMargin", - "setThumbSize", - "setThumbRadius (color-mode only)", - "setBackRadius (color-mode only)", - "setFadeBack", - "setBackMeasureRatio", - "setAnimationDuration", - "setText", - "setDrawDebugRect", - }; +public class StyleInCodeActivity extends AppCompatActivity implements AdapterView.OnItemClickListener { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_style_in_code); + private SwitchButton mChangeSb; + private boolean mThumbMarginFlag, mThumbSizeFlag, mThumbRadiusFlag, mBackRadiusFlag, mBackMeasureRatioFlag, mAnimationDurationFlag; + private String[] opts = new String[]{ + "setThumbColorRes/setThumbColor", + "setThumbDrawableRes/setThumbDrawable", + "setBackColorRes/setBackColor", + "setBackDrawableRes/setBackDrawable", + "setTintColor", + "setThumbMargin", + "setThumbSize", + "setThumbRadius (color-mode only)", + "setBackRadius (color-mode only)", + "setFadeBack", + "setThumbRangeRatio", + "setAnimationDuration", + "setText", + "setDrawDebugRect", + }; - mChangeSb = (SwitchButton) findViewById(R.id.sb_code); - ListView optLv = (ListView) findViewById(R.id.opt_lv); + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_style_in_code); - optLv.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, android.R.id.text1, opts)); - optLv.setOnItemClickListener(this); - } + mChangeSb = (SwitchButton) findViewById(R.id.sb_code); + ListView optLv = (ListView) findViewById(R.id.opt_lv); - @Override - public void onItemClick(AdapterView parent, View view, int position, long id) { - switch (position) { - case 0: - mChangeSb.setThumbColorRes(R.color.custom_thumb_color); - break; - case 1: - mChangeSb.setThumbDrawableRes(R.drawable.miui_thumb_drawable); - break; - case 2: - mChangeSb.setBackColorRes(R.color.custom_back_color); - break; - case 3: - mChangeSb.setBackDrawableRes(R.drawable.miui_back_drawable); - break; - case 4: - mChangeSb.setTintColor(0x9F6C66); - break; - case 5: { - float margin = 10 * getResources().getDisplayMetrics().density; - float defaultMargin = SwitchButton.DEFAULT_THUMB_MARGIN_DP * getResources().getDisplayMetrics().density; - mChangeSb.setThumbMargin(mThumbMarginFlag ? new RectF(defaultMargin, defaultMargin, defaultMargin, defaultMargin) : new RectF(margin, margin, margin, margin)); - mThumbMarginFlag = !mThumbMarginFlag; - } - break; - case 6: { - float size = 30 * getResources().getDisplayMetrics().density; - mChangeSb.setThumbSize(mThumbSizeFlag ? null : new PointF(size, size)); - mThumbSizeFlag = !mThumbSizeFlag; - } - break; - case 7: { - float r = 2 * getResources().getDisplayMetrics().density; - mChangeSb.setThumbRadius(mThumbRadiusFlag ? Math.min(mChangeSb.getThumbSizeF().x, mChangeSb.getThumbSizeF().y) / 2f : r); - mThumbRadiusFlag = !mThumbRadiusFlag; - } - break; - case 8: { - float r = 2 * getResources().getDisplayMetrics().density; - mChangeSb.setBackRadius(mBackRadiusFlag ? Math.min(mChangeSb.getBackSizeF().x, mChangeSb.getBackSizeF().y) / 2f : r); - mBackRadiusFlag = !mBackRadiusFlag; - } - break; - case 9: - mChangeSb.setFadeBack(!mChangeSb.isFadeBack()); - break; - case 10: - mChangeSb.setBackMeasureRatio(mBackMeasureRatioFlag ? SwitchButton.DEFAULT_BACK_MEASURE_RATIO : 2.4f); - mBackMeasureRatioFlag = !mBackMeasureRatioFlag; - break; - case 11: - mChangeSb.setAnimationDuration(mAnimationDurationFlag ? SwitchButton.DEFAULT_ANIMATION_DURATION : 1000); - mAnimationDurationFlag = !mAnimationDurationFlag; - break; - case 12: { - SpannableString ss = new SpannableString("abc"); - Drawable d = getResources().getDrawable(R.drawable.icon_blog); - if (d != null) { - d.setBounds(0, d.getIntrinsicWidth() / 4, d.getIntrinsicWidth() / 2, d.getIntrinsicHeight() * 3 / 4); - ImageSpan span = new ImageSpan(d, ImageSpan.ALIGN_BASELINE); - ss.setSpan(span, 0, 3, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - mChangeSb.setText(ss, "OFF"); - } - } - break; - case 13: - mChangeSb.setDrawDebugRect(!mChangeSb.isDrawDebugRect()); - break; - default: - break; - } - } + optLv.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, android.R.id.text1, opts)); + optLv.setOnItemClickListener(this); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + switch (position) { + case 0: + mChangeSb.setThumbColorRes(R.color.custom_thumb_color); + break; + case 1: + mChangeSb.setThumbDrawableRes(R.drawable.miui_thumb_drawable); + break; + case 2: + mChangeSb.setBackColorRes(R.color.custom_back_color); + break; + case 3: + mChangeSb.setBackDrawableRes(R.drawable.miui_back_drawable); + break; + case 4: + mChangeSb.setTintColor(0x9F6C66); + break; + case 5: { + float margin = 10 * getResources().getDisplayMetrics().density; + float defaultMargin = SwitchButton.DEFAULT_THUMB_MARGIN_DP * getResources().getDisplayMetrics().density; + mChangeSb.setThumbMargin(mThumbMarginFlag ? new RectF(defaultMargin, defaultMargin, defaultMargin, defaultMargin) : new RectF(margin, margin, margin, margin)); + mThumbMarginFlag = !mThumbMarginFlag; + } + break; + case 6: { + int size = (int) (30 * getResources().getDisplayMetrics().density); + mChangeSb.setThumbSize(size, size); + mThumbSizeFlag = !mThumbSizeFlag; + } + break; + case 7: { + float r = 2 * getResources().getDisplayMetrics().density; + mChangeSb.setThumbRadius(mThumbRadiusFlag ? Math.min(mChangeSb.getThumbWidth(), mChangeSb.getThumbHeight()) / 2f : r); + mThumbRadiusFlag = !mThumbRadiusFlag; + } + break; + case 8: { + float r = 2 * getResources().getDisplayMetrics().density; + mChangeSb.setBackRadius(mBackRadiusFlag ? Math.min(mChangeSb.getBackSizeF().x, mChangeSb.getBackSizeF().y) / 2f : r); + mBackRadiusFlag = !mBackRadiusFlag; + } + break; + case 9: + mChangeSb.setFadeBack(!mChangeSb.isFadeBack()); + break; + case 10: + mChangeSb.setThumbRangeRatio(mBackMeasureRatioFlag ? SwitchButton.DEFAULT_THUMB_RANGE_RATIO : 4f); + mBackMeasureRatioFlag = !mBackMeasureRatioFlag; + break; + case 11: + mChangeSb.setAnimationDuration(mAnimationDurationFlag ? SwitchButton.DEFAULT_ANIMATION_DURATION : 1000); + mAnimationDurationFlag = !mAnimationDurationFlag; + break; + case 12: { + CharSequence on = mChangeSb.getTextOn(); + CharSequence off = mChangeSb.getTextOff(); + if (TextUtils.isEmpty(on) || TextUtils.isEmpty(off)) { + SpannableString ss = new SpannableString("[icon]"); + ImageSpan span = new ImageSpan(this, R.drawable.icon_blog_small, ImageSpan.ALIGN_BOTTOM); + ss.setSpan(span, 0, ss.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + mChangeSb.setText(ss, "OFF"); + mChangeSb.setTextExtra((int) (getResources().getDisplayMetrics().density * 4)); + } else { + mChangeSb.setText("", ""); + } + } + break; + case 13: + mChangeSb.setDrawDebugRect(!mChangeSb.isDrawDebugRect()); + break; + default: + break; + } + } } diff --git a/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/UseActivity.java b/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/UseActivity.java index 80024aa..fcaf276 100644 --- a/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/UseActivity.java +++ b/SwitchButton/demo/src/main/java/com/kyleduo/switchbutton/demo/UseActivity.java @@ -3,7 +3,6 @@ import android.animation.Animator; import android.animation.ObjectAnimator; import android.os.Bundle; -import android.support.v7.app.AppCompatActivity; import android.view.View; import android.widget.Button; import android.widget.CompoundButton; @@ -14,168 +13,176 @@ import com.kyleduo.switchbutton.SwitchButton; +import androidx.appcompat.app.AppCompatActivity; + public class UseActivity extends AppCompatActivity implements View.OnClickListener { - private SwitchButton mListenerSb, mLongSb, mToggleSb, mCheckedSb, mDelaySb, mForceOpenSb, mForceOpenControlSb; - private ProgressBar mPb; - private Button mStartBt; - private TextView mListenerFinish; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_use); - - findView(); - - LinearLayout toggleWrapper = (LinearLayout) findViewById(R.id.toggle_wrapper); - for (int i = 0; i < toggleWrapper.getChildCount(); i++) { - toggleWrapper.getChildAt(i).setOnClickListener(this); - } - - LinearLayout checkWrapper = (LinearLayout) findViewById(R.id.check_wrapper); - for (int i = 0; i < checkWrapper.getChildCount(); i++) { - checkWrapper.getChildAt(i).setOnClickListener(this); - } - - - // work with listener - mListenerSb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - mListenerFinish.setVisibility(isChecked ? View.VISIBLE : View.INVISIBLE); - } - }); - - // work with delay - mDelaySb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - mDelaySb.setEnabled(false); - mDelaySb.postDelayed(new Runnable() { - @Override - public void run() { - mDelaySb.setEnabled(true); - } - }, 1500); - } - }); - - // work with stuff takes long - mStartBt.setOnClickListener(new View.OnClickListener() { - - @Override - public void onClick(View v) { - ObjectAnimator animator = ObjectAnimator.ofInt(mPb, "progress", 0, 1000); - animator.setDuration(1000); - animator.addListener(new Animator.AnimatorListener() { - @Override - public void onAnimationStart(Animator animation) { - mStartBt.setEnabled(false); - mLongSb.setChecked(false); - } - - @Override - public void onAnimationEnd(Animator animation) { - mStartBt.setEnabled(true); - mLongSb.setChecked(true); - } - - @Override - public void onAnimationCancel(Animator animation) { - mStartBt.setEnabled(true); - } - - @Override - public void onAnimationRepeat(Animator animation) { - - } - }); - animator.start(); - } - }); - - // check in check - mForceOpenSb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (mForceOpenControlSb.isChecked()) { - toast("Call mForceOpenSb.setChecked(true); in on CheckedChanged"); - mForceOpenSb.setChecked(true); - } - } - }); - } - - private void findView() { - mListenerSb = (SwitchButton) findViewById(R.id.sb_use_listener); - mLongSb = (SwitchButton) findViewById(R.id.sb_use_long); - mToggleSb = (SwitchButton) findViewById(R.id.sb_use_toggle); - mCheckedSb = (SwitchButton) findViewById(R.id.sb_use_checked); - mDelaySb = (SwitchButton) findViewById(R.id.sb_use_delay); - - mPb = (ProgressBar) findViewById(R.id.pb); - mPb.setProgress(0); - mPb.setMax(1000); - - mStartBt = (Button) findViewById(R.id.long_start); - - mListenerFinish = (TextView) findViewById(R.id.listener_finish); - mListenerFinish.setVisibility(mListenerSb.isChecked() ? View.VISIBLE : View.INVISIBLE); - - mForceOpenSb = (SwitchButton) findViewById(R.id.use_focus_open); - mForceOpenControlSb = (SwitchButton) findViewById(R.id.use_focus_open_control); - - mToggleSb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - toast("Toggle SwitchButton new check state: " + (isChecked ? "Checked" : "Unchecked")); - } - }); - - mCheckedSb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - toast("Check SwitchButton new check state: " + (isChecked ? "Checked" : "Unchecked")); - } - }); - } - - @Override - public void onClick(View v) { - int id = v.getId(); - switch (id) { - case R.id.toggle_ani: - mToggleSb.toggle(); - break; - case R.id.toggle_ani_no_event: - mToggleSb.toggleNoEvent(); - break; - case R.id.toggle_not_ani: - mToggleSb.toggleImmediately(); - break; - case R.id.toggle_not_ani_no_event: - mToggleSb.toggleImmediatelyNoEvent(); - break; - case R.id.checked_ani: - mCheckedSb.setChecked(!mCheckedSb.isChecked()); - break; - case R.id.checked_ani_no_event: - mCheckedSb.setCheckedNoEvent(!mCheckedSb.isChecked()); - break; - case R.id.checked_not_ani: - mCheckedSb.setCheckedImmediately(!mCheckedSb.isChecked()); - break; - case R.id.checked_not_ani_no_event: - mCheckedSb.setCheckedImmediatelyNoEvent(!mCheckedSb.isChecked()); - break; - default: - break; - } - } - - private void toast(String text) { - Toast.makeText(UseActivity.this, text, Toast.LENGTH_SHORT).show(); - } + private SwitchButton mListenerSb, mListenerDistinguishSb, mLongSb, mToggleSb, mCheckedSb, mDelaySb, mForceOpenSb, mForceOpenControlSb; + private ProgressBar mPb; + private Button mStartBt; + private TextView mListenerFinish; + private TextView mTriggerTv; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_use); + + findView(); + + LinearLayout toggleWrapper = (LinearLayout) findViewById(R.id.toggle_wrapper); + for (int i = 0; i < toggleWrapper.getChildCount(); i++) { + toggleWrapper.getChildAt(i).setOnClickListener(this); + } + + LinearLayout checkWrapper = (LinearLayout) findViewById(R.id.check_wrapper); + for (int i = 0; i < checkWrapper.getChildCount(); i++) { + checkWrapper.getChildAt(i).setOnClickListener(this); + } + + + // work with listener + mListenerSb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mListenerFinish.setVisibility(isChecked ? View.VISIBLE : View.INVISIBLE); + if (mListenerDistinguishSb.isChecked() != isChecked) { + mListenerDistinguishSb.setChecked(isChecked); + } + } + }); + + // listener with distinguish + mListenerDistinguishSb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mTriggerTv.setVisibility(isChecked ? View.VISIBLE : View.GONE); + if (isChecked) { + mTriggerTv.setText(buttonView.isPressed() ? R.string.use_trigger_manually : R.string.use_trigger_by_code); + } + } + }); + + // work with delay + mDelaySb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + mDelaySb.setEnabled(false); + mDelaySb.postDelayed(new Runnable() { + @Override + public void run() { + mDelaySb.setEnabled(true); + } + }, 1500); + } + }); + + // work with stuff takes long + mStartBt.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + ObjectAnimator animator = ObjectAnimator.ofInt(mPb, "progress", 0, 1000); + animator.setDuration(1000); + animator.addListener(new Animator.AnimatorListener() { + @Override + public void onAnimationStart(Animator animation) { + mStartBt.setEnabled(false); + mLongSb.setChecked(false); + } + + @Override + public void onAnimationEnd(Animator animation) { + mStartBt.setEnabled(true); + mLongSb.setChecked(true); + } + + @Override + public void onAnimationCancel(Animator animation) { + mStartBt.setEnabled(true); + } + + @Override + public void onAnimationRepeat(Animator animation) { + + } + }); + animator.start(); + } + }); + + // check in check + mForceOpenSb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (mForceOpenControlSb.isChecked()) { + toast("Call mForceOpenSb.setChecked(true); in on CheckedChanged"); + mForceOpenSb.setChecked(true); + } + } + }); + } + + private void findView() { + mListenerSb = (SwitchButton) findViewById(R.id.sb_use_listener); + mListenerDistinguishSb = (SwitchButton) findViewById(R.id.sb_listener_distinguish); + mLongSb = (SwitchButton) findViewById(R.id.sb_use_long); + mToggleSb = (SwitchButton) findViewById(R.id.sb_use_toggle); + mCheckedSb = (SwitchButton) findViewById(R.id.sb_use_checked); + mDelaySb = (SwitchButton) findViewById(R.id.sb_use_delay); + + mPb = (ProgressBar) findViewById(R.id.pb); + mPb.setProgress(0); + mPb.setMax(1000); + + mStartBt = (Button) findViewById(R.id.long_start); + + mListenerFinish = (TextView) findViewById(R.id.listener_finish); + mListenerFinish.setVisibility(mListenerSb.isChecked() ? View.VISIBLE : View.INVISIBLE); + mTriggerTv = (TextView) findViewById(R.id.listener_trigger); + + mForceOpenSb = (SwitchButton) findViewById(R.id.use_focus_open); + mForceOpenControlSb = (SwitchButton) findViewById(R.id.use_focus_open_control); + + mToggleSb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + toast("Toggle SwitchButton new check state: " + (isChecked ? "Checked" : "Unchecked")); + } + }); + + mCheckedSb.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + toast("Check SwitchButton new check state: " + (isChecked ? "Checked" : "Unchecked")); + } + }); + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.toggle_ani) { + mToggleSb.toggle(); + } else if (id == R.id.toggle_ani_no_event) { + mToggleSb.toggleNoEvent(); + } else if (id == R.id.toggle_not_ani) { + mToggleSb.toggleImmediately(); + } else if (id == R.id.toggle_not_ani_no_event) { + mToggleSb.toggleImmediatelyNoEvent(); + } else if (id == R.id.checked_ani) { + mCheckedSb.setChecked(!mCheckedSb.isChecked()); + } else if (id == R.id.checked_ani_no_event) { + mCheckedSb.setCheckedNoEvent(!mCheckedSb.isChecked()); + } else if (id == R.id.checked_not_ani) { + mCheckedSb.setCheckedImmediately(!mCheckedSb.isChecked()); + } else if (id == R.id.checked_not_ani_no_event) { + mCheckedSb.setCheckedImmediatelyNoEvent(!mCheckedSb.isChecked()); + } + } + + private void toast(String text) { + Toast.makeText(UseActivity.this, text, Toast.LENGTH_SHORT).show(); + } } diff --git a/SwitchButton/demo/src/main/res/drawable-xxhdpi/icon_blog_small.png b/SwitchButton/demo/src/main/res/drawable-xxhdpi/icon_blog_small.png new file mode 100644 index 0000000..0d0fa2f Binary files /dev/null and b/SwitchButton/demo/src/main/res/drawable-xxhdpi/icon_blog_small.png differ diff --git a/SwitchButton/demo/src/main/res/layout/activity_recycler.xml b/SwitchButton/demo/src/main/res/layout/activity_recycler.xml index 79f7f53..af4a141 100644 --- a/SwitchButton/demo/src/main/res/layout/activity_recycler.xml +++ b/SwitchButton/demo/src/main/res/layout/activity_recycler.xml @@ -1,8 +1,8 @@ - - \ No newline at end of file + \ No newline at end of file diff --git a/SwitchButton/demo/src/main/res/layout/activity_style.xml b/SwitchButton/demo/src/main/res/layout/activity_style.xml index 041cacd..8a0d55c 100644 --- a/SwitchButton/demo/src/main/res/layout/activity_style.xml +++ b/SwitchButton/demo/src/main/res/layout/activity_style.xml @@ -26,6 +26,128 @@ android:layout_height="wrap_content" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -55,6 +179,8 @@ android:layout_height="wrap_content" android:textColor="@color/custom_text_state_color" android:textSize="16dp" + app:kswTextAdjust="-2dp" + app:kswTextExtra="8dp" app:kswTextOff="Off" app:kswTextOn="On" /> @@ -111,9 +237,9 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - app:kswBackMeasureRatio="2.2" app:kswBackRadius="2dp" app:kswThumbRadius="2dp" + app:kswThumbRangeRatio="2.2" app:kswTintColor="#49416E" /> @@ -124,9 +250,9 @@ android:layout_height="wrap_content" android:layout_gravity="center" app:kswBackDrawable="@drawable/flyme_back_drawable" - app:kswBackMeasureRatio="2.2" app:kswThumbDrawable="@drawable/flyme_thumb_drawable" app:kswThumbHeight="16dp" + app:kswThumbRangeRatio="2.2" app:kswThumbWidth="16dp" /> @@ -137,9 +263,9 @@ android:layout_height="wrap_content" android:layout_gravity="center" app:kswBackDrawable="@drawable/miui_back_drawable" - app:kswBackMeasureRatio="2" app:kswThumbDrawable="@drawable/miui_thumb_drawable" app:kswThumbHeight="18dp" + app:kswThumbRangeRatio="2" app:kswThumbWidth="18dp" /> @@ -208,12 +334,12 @@ android:layout_height="wrap_content" app:kswAnimationDuration="300" app:kswBackDrawable="@drawable/ios_back_drawable" - app:kswBackMeasureRatio="1.4" app:kswThumbDrawable="@drawable/ios_thumb_selector" app:kswThumbMarginBottom="-8dp" app:kswThumbMarginLeft="-5dp" app:kswThumbMarginRight="-5dp" - app:kswThumbMarginTop="-2.5dp"/> + app:kswThumbMarginTop="-2.5dp" + app:kswThumbRangeRatio="1.4"/> - \ No newline at end of file diff --git a/SwitchButton/demo/src/main/res/layout/activity_use.xml b/SwitchButton/demo/src/main/res/layout/activity_use.xml index e6fd29d..a9619c0 100644 --- a/SwitchButton/demo/src/main/res/layout/activity_use.xml +++ b/SwitchButton/demo/src/main/res/layout/activity_use.xml @@ -41,6 +41,49 @@ android:text="@string/use_listener_finish"/> + + + + + + + + + + + + + - + - + - + - \ No newline at end of file + \ No newline at end of file diff --git a/SwitchButton/demo/src/main/res/values-v21/styles.xml b/SwitchButton/demo/src/main/res/values-v21/styles.xml index 3cdaf57..a884624 100644 --- a/SwitchButton/demo/src/main/res/values-v21/styles.xml +++ b/SwitchButton/demo/src/main/res/values-v21/styles.xml @@ -1,8 +1,9 @@ - + + \ No newline at end of file diff --git a/SwitchButton/demo/src/main/res/values/strings.xml b/SwitchButton/demo/src/main/res/values/strings.xml index 1224885..d9e62de 100644 --- a/SwitchButton/demo/src/main/res/values/strings.xml +++ b/SwitchButton/demo/src/main/res/values/strings.xml @@ -4,11 +4,12 @@ Hello world! Settings - version 1.4.4\nby kyleduo + version 2.0.3\nby kyleduo Apache License 2.0 Default Style + Specific exact width. \n1. 80dp width;\n2. 80dp width & 20dp thumb width;\n3. 20dp width & 20dp thumb width, no enough room so do NOT display and no event;\n4. 2nd line negative thumbMargin;\n5. 3rd line. with text. With Status Text Default Style without background fade effect. Custom by tint color @@ -22,10 +23,13 @@ Work with listener - Same as above, but not trigger listener + Distinguish between manually and by code + Use the SwitchButton above to control this one. Work with delay call setChecked(boolean)/setCheckedImmediately(boolean) in OnCheckedChangeListener\nIf the second SwitchButton is checked, the first one was "locked" to checked state. You did it! + Manually + By Code work with stuff takes long Click "Start" button to kick it off. Click "Start" button to kick it off. diff --git a/SwitchButton/gradle.properties b/SwitchButton/gradle.properties new file mode 100644 index 0000000..7f389c5 --- /dev/null +++ b/SwitchButton/gradle.properties @@ -0,0 +1,41 @@ +## Project-wide Gradle settings. +# +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# +# Specifies the JVM arguments used for the daemon progress. +# The setting is particularly useful for tweaking memory settings. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 +# +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +#Wed Aug 17 15:39:14 CST 2016 + +android.useAndroidX=true + +#Gradle properties: https://docs.gradle.org/current/userguide/build_environment.html +org.gradle.caching=true +org.gradle.configureondemand=true +org.gradle.parallel=true + +# maven publish +GROUP=com.kyleduo.switchbutton +VERSION_NAME=2.1.0 +VERSION_CODE=27 + +POM_DESCRIPTION=SwitchButton + +POM_URL=https://github.com/kyleduo/SwitchButton +POM_SCM_URL=https://github.com/kyleduo/SwitchButton +POM_SCM_CONNECTION=scm:git:https://github.com/kyleduo/SwitchButton.git +POM_SCM_DEV_CONNECTION=scm:git:git@github.com:kyleduo/SwitchButton.git + +POM_LICENCE_NAME=The Apache Software License, Version 2.0 +POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt +POM_LICENCE_DIST=repo + +POM_DEVELOPER_ID=kyleduo +POM_DEVELOPER_NAME=kyleduo diff --git a/SwitchButton/gradle/wrapper/gradle-wrapper.properties b/SwitchButton/gradle/wrapper/gradle-wrapper.properties index f51ee3e..bb2f015 100644 --- a/SwitchButton/gradle/wrapper/gradle-wrapper.properties +++ b/SwitchButton/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Tue May 24 21:48:46 CST 2016 +#Sat Dec 02 17:34:44 CST 2023 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/SwitchButton/library/build.gradle b/SwitchButton/library/build.gradle index 66a7b04..e57948e 100644 --- a/SwitchButton/library/build.gradle +++ b/SwitchButton/library/build.gradle @@ -1,105 +1,16 @@ -apply plugin: 'com.android.library' -apply plugin: 'com.github.dcendents.android-maven' -apply plugin: 'com.jfrog.bintray' +plugins { + id("com.android.library") + id("com.vanniktech.maven.publish") +} -version = "1.4.4" +android { + compileSdk 33 -def siteUrl = 'https://github.com/kyleduo/SwitchButton' -def gitUrl = 'https://github.com/kyleduo/SwitchButton.git' -group = "com.kyleduo.switchbutton" + namespace "com.kyleduo.switchbutton" -android { - compileSdkVersion 23 - buildToolsVersion "22.0.1" - resourcePrefix "SwitchButton__" + resourcePrefix "ksw" defaultConfig { minSdkVersion 11 - targetSdkVersion 23 - versionCode 21 - versionName "1.4.4" } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } -} - -install { - repositories.mavenInstaller { - pom { - //noinspection GroovyAssignabilityCheck - project { - packaging 'aar' - - // Add your description here - name 'SwitchButton' - url siteUrl - - // Set your license - licenses { - license { - name 'The Apache Software License, Version 2.0' - url 'http://www.apache.org/licenses/LICENSE-2.0.txt' - } - } - developers { - developer { - id 'kyleduo' - name 'kyleduo' - email 'kyleduo@gmail.com' - } - } - scm { - connection gitUrl - developerConnection gitUrl - url siteUrl - - } - } - } - } -} - -dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - compile 'com.android.support:appcompat-v7:23.1.0' -} - -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()) -bintray { - user = properties.getProperty("bintray.user") - key = properties.getProperty("bintray.apikey") - configurations = ['archives'] - pkg { - repo = "maven" - name = "SwitchButton" // project name in jcenter - websiteUrl = siteUrl - vcsUrl = gitUrl - licenses = ["Apache-2.0"] - publish = true - } -} \ No newline at end of file diff --git a/SwitchButton/library/gradle.properties b/SwitchButton/library/gradle.properties new file mode 100644 index 0000000..ff9730f --- /dev/null +++ b/SwitchButton/library/gradle.properties @@ -0,0 +1,3 @@ +POM_ARTIFACT_ID=library +POM_NAME=SwitchButton +POM_PACKAGING=aar \ No newline at end of file diff --git a/SwitchButton/library/src/main/AndroidManifest.xml b/SwitchButton/library/src/main/AndroidManifest.xml index ae2f54e..cc947c5 100644 --- a/SwitchButton/library/src/main/AndroidManifest.xml +++ b/SwitchButton/library/src/main/AndroidManifest.xml @@ -1,3 +1 @@ - - - + diff --git a/SwitchButton/library/src/main/java/com/kyleduo/switchbutton/ColorUtils.java b/SwitchButton/library/src/main/java/com/kyleduo/switchbutton/ColorUtils.java index 4342b89..4becf63 100644 --- a/SwitchButton/library/src/main/java/com/kyleduo/switchbutton/ColorUtils.java +++ b/SwitchButton/library/src/main/java/com/kyleduo/switchbutton/ColorUtils.java @@ -7,49 +7,49 @@ * Created by kyle on 15/11/4. */ public class ColorUtils { - private static final int ENABLE_ATTR = android.R.attr.state_enabled; - private static final int CHECKED_ATTR = android.R.attr.state_checked; - private static final int PRESSED_ATTR = android.R.attr.state_pressed; + private static final int ENABLE_ATTR = android.R.attr.state_enabled; + private static final int CHECKED_ATTR = android.R.attr.state_checked; + private static final int PRESSED_ATTR = android.R.attr.state_pressed; - public static ColorStateList generateThumbColorWithTintColor(final int tintColor) { - int[][] states = new int[][]{ - {-ENABLE_ATTR, CHECKED_ATTR}, - {-ENABLE_ATTR}, - {PRESSED_ATTR, -CHECKED_ATTR}, - {PRESSED_ATTR, CHECKED_ATTR}, - {CHECKED_ATTR}, - {-CHECKED_ATTR} - }; + static ColorStateList generateThumbColorWithTintColor(final int tintColor) { + int[][] states = new int[][]{ + {-ENABLE_ATTR, CHECKED_ATTR}, + {-ENABLE_ATTR}, + {PRESSED_ATTR, -CHECKED_ATTR}, + {PRESSED_ATTR, CHECKED_ATTR}, + {CHECKED_ATTR}, + {-CHECKED_ATTR} + }; - int[] colors = new int[] { - tintColor - 0xAA000000, - 0xFFBABABA, - tintColor - 0x99000000, - tintColor - 0x99000000, - tintColor | 0xFF000000, - 0xFFEEEEEE - }; - return new ColorStateList(states, colors); - } + int[] colors = new int[]{ + tintColor - 0xAA000000, + 0xFFBABABA, + tintColor - 0x99000000, + tintColor - 0x99000000, + tintColor | 0xFF000000, + 0xFFEEEEEE + }; + return new ColorStateList(states, colors); + } - public static ColorStateList generateBackColorWithTintColor(final int tintColor) { - int[][] states = new int[][]{ - {-ENABLE_ATTR, CHECKED_ATTR}, - {-ENABLE_ATTR}, - {CHECKED_ATTR, PRESSED_ATTR}, - {-CHECKED_ATTR, PRESSED_ATTR}, - {CHECKED_ATTR}, - {-CHECKED_ATTR} - }; + static ColorStateList generateBackColorWithTintColor(final int tintColor) { + int[][] states = new int[][]{ + {-ENABLE_ATTR, CHECKED_ATTR}, + {-ENABLE_ATTR}, + {CHECKED_ATTR, PRESSED_ATTR}, + {-CHECKED_ATTR, PRESSED_ATTR}, + {CHECKED_ATTR}, + {-CHECKED_ATTR} + }; - int[] colors = new int[] { - tintColor - 0xE1000000, - 0x10000000, - tintColor - 0xD0000000, - 0x20000000, - tintColor - 0xD0000000, - 0x20000000 - }; - return new ColorStateList(states, colors); - } + int[] colors = new int[]{ + tintColor - 0xE1000000, + 0x10000000, + tintColor - 0xD0000000, + 0x20000000, + tintColor - 0xD0000000, + 0x20000000 + }; + return new ColorStateList(states, colors); + } } diff --git a/SwitchButton/library/src/main/java/com/kyleduo/switchbutton/SwitchButton.java b/SwitchButton/library/src/main/java/com/kyleduo/switchbutton/SwitchButton.java index 4ca8b1f..a0065cf 100644 --- a/SwitchButton/library/src/main/java/com/kyleduo/switchbutton/SwitchButton.java +++ b/SwitchButton/library/src/main/java/com/kyleduo/switchbutton/SwitchButton.java @@ -1,7 +1,7 @@ package com.kyleduo.switchbutton; -import android.animation.ObjectAnimator; -import android.annotation.TargetApi; +import android.animation.ValueAnimator; +import android.annotation.SuppressLint; import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -16,13 +16,11 @@ import android.os.Build; import android.os.Parcel; import android.os.Parcelable; -import android.support.v4.content.ContextCompat; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.util.AttributeSet; -import android.util.Log; import android.util.TypedValue; import android.view.MotionEvent; import android.view.SoundEffectConstants; @@ -39,910 +37,1164 @@ * @since 2014-09-24 */ +@SuppressWarnings("unused") public class SwitchButton extends CompoundButton { - public static final float DEFAULT_BACK_MEASURE_RATIO = 1.8f; - public static final int DEFAULT_THUMB_SIZE_DP = 20; - public static final int DEFAULT_THUMB_MARGIN_DP = 2; - public static final int DEFAULT_TEXT_MARGIN_DP = 2; - public static final int DEFAULT_ANIMATION_DURATION = 250; - public static final int DEFAULT_TINT_COLOR = 0x327FC2; - - private static int[] CHECKED_PRESSED_STATE = new int[]{android.R.attr.state_checked, android.R.attr.state_enabled, android.R.attr.state_pressed}; - private static int[] UNCHECKED_PRESSED_STATE = new int[]{-android.R.attr.state_checked, android.R.attr.state_enabled, android.R.attr.state_pressed}; - - private Drawable mThumbDrawable, mBackDrawable; - private ColorStateList mBackColor, mThumbColor; - private float mThumbRadius, mBackRadius; - private RectF mThumbMargin; - private float mBackMeasureRatio; - private long mAnimationDuration; - // fade back drawable or color when dragging or animating - private boolean mFadeBack; - private int mTintColor; - private PointF mThumbSizeF; - - private int mCurrThumbColor, mCurrBackColor, mNextBackColor, mOnTextColor, mOffTextColor; - private Drawable mCurrentBackDrawable, mNextBackDrawable; - private RectF mThumbRectF, mBackRectF, mSafeRectF, mTextOnRectF, mTextOffRectF; - private Paint mPaint; - // whether using Drawable for thumb or back - private boolean mIsThumbUseDrawable, mIsBackUseDrawable; - private boolean mDrawDebugRect = false; - private ObjectAnimator mProcessAnimator; - // animation control - private float mProcess; - // temp position of thumb when dragging or animating - private RectF mPresentThumbRectF; - private float mStartX, mStartY, mLastX; - private int mTouchSlop; - private int mClickTimeout; - private Paint mRectPaint; - private CharSequence mTextOn; - private CharSequence mTextOff; - private TextPaint mTextPaint; - private Layout mOnLayout; - private Layout mOffLayout; - private float mTextWidth; - private float mTextHeight; - private float mTextMarginH; - private boolean mAutoAdjustTextPosition = true; - - private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener; - - public SwitchButton(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(attrs); - } - - public SwitchButton(Context context, AttributeSet attrs) { - super(context, attrs); - init(attrs); - } - - public SwitchButton(Context context) { - super(context); - init(null); - } - - private void init(AttributeSet attrs) { - mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); - mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); - - mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mRectPaint.setStyle(Paint.Style.STROKE); - mRectPaint.setStrokeWidth(getResources().getDisplayMetrics().density); - - mTextPaint = getPaint(); - - mThumbRectF = new RectF(); - mBackRectF = new RectF(); - mSafeRectF = new RectF(); - mThumbSizeF = new PointF(); - mThumbMargin = new RectF(); - mTextOnRectF = new RectF(); - mTextOffRectF = new RectF(); - - mProcessAnimator = ObjectAnimator.ofFloat(this, "process", 0, 0).setDuration(DEFAULT_ANIMATION_DURATION); - mProcessAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); - - mPresentThumbRectF = new RectF(); - - Resources res = getResources(); - float density = res.getDisplayMetrics().density; - - Drawable thumbDrawable = null; - ColorStateList thumbColor = null; - float margin = density * DEFAULT_THUMB_MARGIN_DP; - float marginLeft = 0; - float marginRight = 0; - float marginTop = 0; - float marginBottom = 0; - float thumbWidth = density * DEFAULT_THUMB_SIZE_DP; - float thumbHeight = density * DEFAULT_THUMB_SIZE_DP; - float thumbRadius = density * DEFAULT_THUMB_SIZE_DP / 2; - float backRadius = thumbRadius; - Drawable backDrawable = null; - ColorStateList backColor = null; - float backMeasureRatio = DEFAULT_BACK_MEASURE_RATIO; - int animationDuration = DEFAULT_ANIMATION_DURATION; - boolean fadeBack = true; - int tintColor = 0; - String textOn = null; - String textOff = null; - float textMarginH = density * DEFAULT_TEXT_MARGIN_DP; - boolean autoAdjustTextPosition = true; - - TypedArray ta = attrs == null ? null : getContext().obtainStyledAttributes(attrs, R.styleable.SwitchButton); - if (ta != null) { - thumbDrawable = ta.getDrawable(R.styleable.SwitchButton_kswThumbDrawable); - thumbColor = ta.getColorStateList(R.styleable.SwitchButton_kswThumbColor); - margin = ta.getDimension(R.styleable.SwitchButton_kswThumbMargin, margin); - marginLeft = ta.getDimension(R.styleable.SwitchButton_kswThumbMarginLeft, margin); - marginRight = ta.getDimension(R.styleable.SwitchButton_kswThumbMarginRight, margin); - marginTop = ta.getDimension(R.styleable.SwitchButton_kswThumbMarginTop, margin); - marginBottom = ta.getDimension(R.styleable.SwitchButton_kswThumbMarginBottom, margin); - thumbWidth = ta.getDimension(R.styleable.SwitchButton_kswThumbWidth, thumbWidth); - thumbHeight = ta.getDimension(R.styleable.SwitchButton_kswThumbHeight, thumbHeight); - thumbRadius = ta.getDimension(R.styleable.SwitchButton_kswThumbRadius, Math.min(thumbWidth, thumbHeight) / 2.f); - backRadius = ta.getDimension(R.styleable.SwitchButton_kswBackRadius, thumbRadius + density * 2f); - backDrawable = ta.getDrawable(R.styleable.SwitchButton_kswBackDrawable); - backColor = ta.getColorStateList(R.styleable.SwitchButton_kswBackColor); - backMeasureRatio = ta.getFloat(R.styleable.SwitchButton_kswBackMeasureRatio, backMeasureRatio); - animationDuration = ta.getInteger(R.styleable.SwitchButton_kswAnimationDuration, animationDuration); - fadeBack = ta.getBoolean(R.styleable.SwitchButton_kswFadeBack, true); - tintColor = ta.getColor(R.styleable.SwitchButton_kswTintColor, tintColor); - textOn = ta.getString(R.styleable.SwitchButton_kswTextOn); - textOff = ta.getString(R.styleable.SwitchButton_kswTextOff); - textMarginH = Math.max(textMarginH, backRadius / 2); - textMarginH = ta.getDimension(R.styleable.SwitchButton_kswTextMarginH, textMarginH); - autoAdjustTextPosition = ta.getBoolean(R.styleable.SwitchButton_kswAutoAdjustTextPosition, autoAdjustTextPosition); - ta.recycle(); - } - - // click - ta = attrs == null ? null : getContext().obtainStyledAttributes(attrs, new int[]{android.R.attr.focusable, android.R.attr.clickable}); - if (ta != null) { - boolean focusable = ta.getBoolean(0, true); - //noinspection ResourceType - boolean clickable = ta.getBoolean(1, focusable); - setFocusable(focusable); - setClickable(clickable); - ta.recycle(); - } - - // text - mTextOn = textOn; - mTextOff = textOff; - mTextMarginH = textMarginH; - mAutoAdjustTextPosition = autoAdjustTextPosition; - - // thumb drawable and color - mThumbDrawable = thumbDrawable; - mThumbColor = thumbColor; - mIsThumbUseDrawable = mThumbDrawable != null; - mTintColor = tintColor; - if (mTintColor == 0) { - TypedValue typedValue = new TypedValue(); - boolean found = getContext().getTheme().resolveAttribute(R.attr.colorAccent, typedValue, true); - if (found) { - mTintColor = typedValue.data; - } else { - mTintColor = DEFAULT_TINT_COLOR; - } - } - if (!mIsThumbUseDrawable && mThumbColor == null) { - mThumbColor = ColorUtils.generateThumbColorWithTintColor(mTintColor); - mCurrThumbColor = mThumbColor.getDefaultColor(); - } - if (mIsThumbUseDrawable) { - thumbWidth = Math.max(thumbWidth, mThumbDrawable.getMinimumWidth()); - thumbHeight = Math.max(thumbHeight, mThumbDrawable.getMinimumHeight()); - } - mThumbSizeF.set(thumbWidth, thumbHeight); - - // back drawable and color - mBackDrawable = backDrawable; - mBackColor = backColor; - mIsBackUseDrawable = mBackDrawable != null; - if (!mIsBackUseDrawable && mBackColor == null) { - mBackColor = ColorUtils.generateBackColorWithTintColor(mTintColor); - mCurrBackColor = mBackColor.getDefaultColor(); - mNextBackColor = mBackColor.getColorForState(CHECKED_PRESSED_STATE, mCurrBackColor); - } - - // margin - mThumbMargin.set(marginLeft, marginTop, marginRight, marginBottom); - - // size & measure params must larger than 1 - mBackMeasureRatio = mThumbMargin.width() >= 0 ? Math.max(backMeasureRatio, 1) : backMeasureRatio; - - mThumbRadius = thumbRadius; - mBackRadius = backRadius; - mAnimationDuration = animationDuration; - mFadeBack = fadeBack; - - mProcessAnimator.setDuration(mAnimationDuration); - - // sync checked status - if (isChecked()) { - setProcess(1); - } - } - - - private Layout makeLayout(CharSequence text) { - return new StaticLayout(text, mTextPaint, (int) Math.ceil(Layout.getDesiredWidth(text, mTextPaint)), Layout.Alignment.ALIGN_CENTER, 1.f, 0, false); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - if (mOnLayout == null && mTextOn != null) { - mOnLayout = makeLayout(mTextOn); - } - if (mOffLayout == null && mTextOff != null) { - mOffLayout = makeLayout(mTextOff); - } - setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); - } - - private int measureWidth(int widthMeasureSpec) { - int widthSize = MeasureSpec.getSize(widthMeasureSpec); - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int measuredWidth; - - int minWidth = ceil(mThumbSizeF.x * mBackMeasureRatio); - if (mIsBackUseDrawable) { - minWidth = Math.max(minWidth, mBackDrawable.getMinimumWidth()); - } - float onWidth = mOnLayout != null ? mOnLayout.getWidth() : 0; - float offWidth = mOffLayout != null ? mOffLayout.getWidth() : 0; - if (onWidth != 0 || offWidth != 0) { - mTextWidth = Math.max(onWidth, offWidth) + mTextMarginH * 2; - float left = minWidth - mThumbSizeF.x; - if (left < mTextWidth) { - minWidth += mTextWidth - left; - } - } else { - mTextWidth = 0; - } - minWidth = Math.max(minWidth, ceil(minWidth + mThumbMargin.left + mThumbMargin.right)); - minWidth = Math.max(minWidth, minWidth + getPaddingLeft() + getPaddingRight()); - minWidth = Math.max(minWidth, getSuggestedMinimumWidth()); - - if (widthMode == MeasureSpec.EXACTLY) { - measuredWidth = Math.max(minWidth, widthSize); - } else { - measuredWidth = minWidth; - if (widthMode == MeasureSpec.AT_MOST) { - measuredWidth = Math.min(measuredWidth, widthSize); - } - } - - return measuredWidth; - } - - private int measureHeight(int heightMeasureSpec) { - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - int measuredHeight; - - int minHeight = ceil(Math.max(mThumbSizeF.y, mThumbSizeF.y + mThumbMargin.top + mThumbMargin.right)); - float onHeight = mOnLayout != null ? mOnLayout.getHeight() : 0; - float offHeight = mOffLayout != null ? mOffLayout.getHeight() : 0; - if (onHeight != 0 || offHeight != 0) { - mTextHeight = Math.max(onHeight, offHeight); - minHeight = ceil(Math.max(minHeight, mTextHeight)); - } else { - mTextHeight = 0; - } - minHeight = Math.max(minHeight, getSuggestedMinimumHeight()); - minHeight = Math.max(minHeight, minHeight + getPaddingTop() + getPaddingBottom()); - - if (heightMode == MeasureSpec.EXACTLY) { - measuredHeight = Math.max(minHeight, heightSize); - } else { - measuredHeight = minHeight; - if (heightMode == MeasureSpec.AT_MOST) { - measuredHeight = Math.min(measuredHeight, heightSize); - } - } - - return measuredHeight; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - if (w != oldw || h != oldh) { - setup(); - } - } - - private int ceil(double dimen) { - return (int) Math.ceil(dimen); - } - - /** - * set up the rect of back and thumb - */ - private void setup() { - float thumbTop = getPaddingTop() + Math.max(0, mThumbMargin.top); - float thumbLeft = getPaddingLeft() + Math.max(0, mThumbMargin.left); - - if (mOnLayout != null && mOffLayout != null) { - if (mThumbMargin.top + mThumbMargin.bottom > 0) { - // back is higher than thumb - float addition = (getMeasuredHeight() - getPaddingBottom() - getPaddingTop() - mThumbSizeF.y - mThumbMargin.top - mThumbMargin.bottom) / 2; - thumbTop += addition; - } - } - - if (mIsThumbUseDrawable) { - mThumbSizeF.x = Math.max(mThumbSizeF.x, mThumbDrawable.getMinimumWidth()); - mThumbSizeF.y = Math.max(mThumbSizeF.y, mThumbDrawable.getMinimumHeight()); - } - - mThumbRectF.set(thumbLeft, thumbTop, thumbLeft + mThumbSizeF.x, thumbTop + mThumbSizeF.y); - - float backLeft = mThumbRectF.left - mThumbMargin.left; - float textDiffWidth = Math.min(0, (Math.max(mThumbSizeF.x * mBackMeasureRatio, mThumbSizeF.x + mTextWidth) - mThumbRectF.width() - mTextWidth) / 2); - float textDiffHeight = Math.min(0, (mThumbRectF.height() + mThumbMargin.top + mThumbMargin.bottom - mTextHeight) / 2); - mBackRectF.set(backLeft + textDiffWidth, - mThumbRectF.top - mThumbMargin.top + textDiffHeight, - backLeft + mThumbMargin.left + Math.max(mThumbSizeF.x * mBackMeasureRatio, mThumbSizeF.x + mTextWidth) + mThumbMargin.right - textDiffWidth, - mThumbRectF.bottom + mThumbMargin.bottom - textDiffHeight); - - mSafeRectF.set(mThumbRectF.left, 0, mBackRectF.right - mThumbMargin.right - mThumbRectF.width(), 0); - - float minBackRadius = Math.min(mBackRectF.width(), mBackRectF.height()) / 2.f; - mBackRadius = Math.min(minBackRadius, mBackRadius); - - if (mBackDrawable != null) { - mBackDrawable.setBounds((int) mBackRectF.left, (int) mBackRectF.top, ceil(mBackRectF.right), ceil(mBackRectF.bottom)); - } - - if (mOnLayout != null) { - float marginOnX = mBackRectF.left + (mBackRectF.width() - mThumbRectF.width() - mThumbMargin.right - mOnLayout.getWidth()) / 2 + (mThumbMargin.left < 0 ? mThumbMargin.left * -0.5f : 0); - if (!mIsBackUseDrawable && mAutoAdjustTextPosition) { - marginOnX += mBackRadius / 4; - } - float marginOnY = mBackRectF.top + (mBackRectF.height() - mOnLayout.getHeight()) / 2; - mTextOnRectF.set(marginOnX, marginOnY, marginOnX + mOnLayout.getWidth(), marginOnY + mOnLayout.getHeight()); - } - - if (mOffLayout != null) { - float marginOffX = mBackRectF.right - (mBackRectF.width() - mThumbRectF.width() - mThumbMargin.left - mOffLayout.getWidth()) / 2 - mOffLayout.getWidth() + (mThumbMargin.right < 0 ? mThumbMargin.right * 0.5f : 0); - if (!mIsBackUseDrawable && mAutoAdjustTextPosition) { - marginOffX -= mBackRadius / 4; - } - float marginOffY = mBackRectF.top + (mBackRectF.height() - mOffLayout.getHeight()) / 2; - mTextOffRectF.set(marginOffX, marginOffY, marginOffX + mOffLayout.getWidth(), marginOffY + mOffLayout.getHeight()); - } - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - // fade back - if (mIsBackUseDrawable) { - if (mFadeBack && mCurrentBackDrawable != null && mNextBackDrawable != null) { - int alpha = (int) (255 * (isChecked() ? getProcess() : (1 - getProcess()))); - mCurrentBackDrawable.setAlpha(alpha); - mCurrentBackDrawable.draw(canvas); - alpha = 255 - alpha; - mNextBackDrawable.setAlpha(alpha); - mNextBackDrawable.draw(canvas); - } else { - mBackDrawable.setAlpha(255); - mBackDrawable.draw(canvas); - } - } else { - if (mFadeBack) { - int alpha; - int colorAlpha; - - // curr back - alpha = (int) (255 * (isChecked() ? getProcess() : (1 - getProcess()))); - colorAlpha = Color.alpha(mCurrBackColor); - colorAlpha = colorAlpha * alpha / 255; - mPaint.setARGB(colorAlpha, Color.red(mCurrBackColor), Color.green(mCurrBackColor), Color.blue(mCurrBackColor)); - canvas.drawRoundRect(mBackRectF, mBackRadius, mBackRadius, mPaint); - - // next back - alpha = 255 - alpha; - colorAlpha = Color.alpha(mNextBackColor); - colorAlpha = colorAlpha * alpha / 255; - mPaint.setARGB(colorAlpha, Color.red(mNextBackColor), Color.green(mNextBackColor), Color.blue(mNextBackColor)); - canvas.drawRoundRect(mBackRectF, mBackRadius, mBackRadius, mPaint); - - mPaint.setAlpha(255); - } else { - mPaint.setColor(mCurrBackColor); - canvas.drawRoundRect(mBackRectF, mBackRadius, mBackRadius, mPaint); - } - } - - // text - Layout switchText = getProcess() > 0.5 ? mOnLayout : mOffLayout; - RectF textRectF = getProcess() > 0.5 ? mTextOnRectF : mTextOffRectF; - if (switchText != null && textRectF != null) { - int alpha = (int) (255 * (getProcess() >= 0.75 ? getProcess() * 4 - 3 : (getProcess() < 0.25 ? 1 - getProcess() * 4 : 0))); - int textColor = getProcess() > 0.5 ? mOnTextColor : mOffTextColor; - int colorAlpha = Color.alpha(textColor); - colorAlpha = colorAlpha * alpha / 255; - switchText.getPaint().setARGB(colorAlpha, Color.red(textColor), Color.green(textColor), Color.blue(textColor)); - canvas.save(); - canvas.translate(textRectF.left, textRectF.top); - switchText.draw(canvas); - canvas.restore(); - } - - // thumb - mPresentThumbRectF.set(mThumbRectF); - mPresentThumbRectF.offset(mProcess * mSafeRectF.width(), 0); - if (mIsThumbUseDrawable) { - mThumbDrawable.setBounds((int) mPresentThumbRectF.left, (int) mPresentThumbRectF.top, ceil(mPresentThumbRectF.right), ceil(mPresentThumbRectF.bottom)); - mThumbDrawable.draw(canvas); - } else { - mPaint.setColor(mCurrThumbColor); - canvas.drawRoundRect(mPresentThumbRectF, mThumbRadius, mThumbRadius, mPaint); - } - - if (mDrawDebugRect) { - mRectPaint.setColor(Color.parseColor("#AA0000")); - canvas.drawRect(mBackRectF, mRectPaint); - mRectPaint.setColor(Color.parseColor("#0000FF")); - canvas.drawRect(mPresentThumbRectF, mRectPaint); - mRectPaint.setColor(Color.parseColor("#00CC00")); - canvas.drawRect(getProcess() > 0.5 ? mTextOnRectF : mTextOffRectF, mRectPaint); - } - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - - if (!mIsThumbUseDrawable && mThumbColor != null) { - mCurrThumbColor = mThumbColor.getColorForState(getDrawableState(), mCurrThumbColor); - } else { - setDrawableState(mThumbDrawable); - } - - int[] nextState = isChecked() ? UNCHECKED_PRESSED_STATE : CHECKED_PRESSED_STATE; - ColorStateList textColors = getTextColors(); - if (textColors != null) { - int defaultTextColor = textColors.getDefaultColor(); - mOnTextColor = textColors.getColorForState(CHECKED_PRESSED_STATE, defaultTextColor); - mOffTextColor = textColors.getColorForState(UNCHECKED_PRESSED_STATE, defaultTextColor); - } - if (!mIsBackUseDrawable && mBackColor != null) { - mCurrBackColor = mBackColor.getColorForState(getDrawableState(), mCurrBackColor); - mNextBackColor = mBackColor.getColorForState(nextState, mCurrBackColor); - } else { - if (mBackDrawable instanceof StateListDrawable && mFadeBack) { - mBackDrawable.setState(nextState); - mNextBackDrawable = mBackDrawable.getCurrent().mutate(); - } else { - mNextBackDrawable = null; - } - setDrawableState(mBackDrawable); - if (mBackDrawable != null) { - mCurrentBackDrawable = mBackDrawable.getCurrent().mutate(); - } - } - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - - if (!isEnabled() || !isClickable() || !isFocusable()) { - return false; - } - - int action = event.getAction(); - - float deltaX = event.getX() - mStartX; - float deltaY = event.getY() - mStartY; - - // status the view going to change to when finger released - boolean nextStatus; - - switch (action) { - case MotionEvent.ACTION_DOWN: - catchView(); - mStartX = event.getX(); - mStartY = event.getY(); - mLastX = mStartX; - setPressed(true); - break; - - case MotionEvent.ACTION_MOVE: - float x = event.getX(); - setProcess(getProcess() + (x - mLastX) / mSafeRectF.width()); - mLastX = x; - break; - - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - setPressed(false); - nextStatus = getStatusBasedOnPos(); - float time = event.getEventTime() - event.getDownTime(); - if (deltaX < mTouchSlop && deltaY < mTouchSlop && time < mClickTimeout) { - performClick(); - } else { - if (nextStatus != isChecked()) { - playSoundEffect(SoundEffectConstants.CLICK); - setChecked(nextStatus); - } else { - animateToState(nextStatus); - } - } - break; - - default: - break; - } - return true; - } - - - /** - * return the status based on position of thumb - * - * @return - */ - private boolean getStatusBasedOnPos() { - return getProcess() > 0.5f; - } - - public final float getProcess() { - return mProcess; - } - - public final void setProcess(final float process) { - float tp = process; - if (tp > 1) { - tp = 1; - } else if (tp < 0) { - tp = 0; - } - this.mProcess = tp; - invalidate(); - } - - @Override - public boolean performClick() { - return super.performClick(); - } - - /** - * processing animation - * - * @param checked checked or unChecked - */ - protected void animateToState(boolean checked) { - if (mProcessAnimator == null) { - return; - } - if (mProcessAnimator.isRunning()) { - mProcessAnimator.cancel(); - } - mProcessAnimator.setDuration(mAnimationDuration); - if (checked) { - mProcessAnimator.setFloatValues(mProcess, 1f); - } else { - mProcessAnimator.setFloatValues(mProcess, 0); - } - mProcessAnimator.start(); - } - - private void catchView() { - ViewParent parent = getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } - } - - @Override - public void setChecked(final boolean checked) { - // animate before super.setChecked() become user may call setChecked again in OnCheckedChangedListener - if (isChecked() != checked) { - animateToState(checked); - } - super.setChecked(checked); - } - - public void setCheckedNoEvent(final boolean checked) { - if (mChildOnCheckedChangeListener == null) { - setChecked(checked); - } else { - super.setOnCheckedChangeListener(null); - setChecked(checked); - setOnCheckedChangeListener(mChildOnCheckedChangeListener); - } - } - - public void setCheckedImmediatelyNoEvent(boolean checked) { - if (mChildOnCheckedChangeListener == null) { - setCheckedImmediately(checked); - } else { - super.setOnCheckedChangeListener(null); - setCheckedImmediately(checked); - setOnCheckedChangeListener(mChildOnCheckedChangeListener); - } - } - - public void toggleNoEvent() { - if (mChildOnCheckedChangeListener == null) { - toggle(); - } else { - super.setOnCheckedChangeListener(null); - toggle(); - setOnCheckedChangeListener(mChildOnCheckedChangeListener); - } - } - - public void toggleImmediatelyNoEvent() { - if (mChildOnCheckedChangeListener == null) { - toggleImmediately(); - } else { - super.setOnCheckedChangeListener(null); - toggleImmediately(); - setOnCheckedChangeListener(mChildOnCheckedChangeListener); - } - } - - @Override - public void setOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) { - super.setOnCheckedChangeListener(onCheckedChangeListener); - mChildOnCheckedChangeListener = onCheckedChangeListener; - } - - public void setCheckedImmediately(boolean checked) { - super.setChecked(checked); - if (mProcessAnimator != null && mProcessAnimator.isRunning()) { - mProcessAnimator.cancel(); - } - setProcess(checked ? 1 : 0); - invalidate(); - } - - public void toggleImmediately() { - setCheckedImmediately(!isChecked()); - } - - private void setDrawableState(Drawable drawable) { - if (drawable != null) { - int[] myDrawableState = getDrawableState(); - drawable.setState(myDrawableState); - invalidate(); - } - } - - public boolean isDrawDebugRect() { - return mDrawDebugRect; - } - - public void setDrawDebugRect(boolean drawDebugRect) { - mDrawDebugRect = drawDebugRect; - invalidate(); - } - - public long getAnimationDuration() { - return mAnimationDuration; - } - - public void setAnimationDuration(long animationDuration) { - mAnimationDuration = animationDuration; - } - - public Drawable getThumbDrawable() { - return mThumbDrawable; - } - - public void setThumbDrawable(Drawable thumbDrawable) { - mThumbDrawable = thumbDrawable; - mIsThumbUseDrawable = mThumbDrawable != null; - setup(); - refreshDrawableState(); - requestLayout(); - invalidate(); - } - - public void setThumbDrawableRes(int thumbDrawableRes) { - setThumbDrawable(ContextCompat.getDrawable(getContext(), thumbDrawableRes)); - } - - public Drawable getBackDrawable() { - return mBackDrawable; - } - - public void setBackDrawable(Drawable backDrawable) { - mBackDrawable = backDrawable; - mIsBackUseDrawable = mBackDrawable != null; - setup(); - refreshDrawableState(); - requestLayout(); - invalidate(); - } - - public void setBackDrawableRes(int backDrawableRes) { - setBackDrawable(ContextCompat.getDrawable(getContext(), backDrawableRes)); - } - - public ColorStateList getBackColor() { - return mBackColor; - } - - public void setBackColor(ColorStateList backColor) { - mBackColor = backColor; - if (mBackColor != null) { - setBackDrawable(null); - } - invalidate(); - } - - public void setBackColorRes(int backColorRes) { - setBackColor(ContextCompat.getColorStateList(getContext(), backColorRes)); - } - - public ColorStateList getThumbColor() { - return mThumbColor; - } - - public void setThumbColor(ColorStateList thumbColor) { - mThumbColor = thumbColor; - if (mThumbColor != null) { - setThumbDrawable(null); - } - } - - public void setThumbColorRes(int thumbColorRes) { - setThumbColor(ContextCompat.getColorStateList(getContext(), thumbColorRes)); - } - - public float getBackMeasureRatio() { - return mBackMeasureRatio; - } - - public void setBackMeasureRatio(float backMeasureRatio) { - mBackMeasureRatio = backMeasureRatio; - requestLayout(); - } - - public RectF getThumbMargin() { - return mThumbMargin; - } - - public void setThumbMargin(RectF thumbMargin) { - if (thumbMargin == null) { - setThumbMargin(0, 0, 0, 0); - } else { - setThumbMargin(thumbMargin.left, thumbMargin.top, thumbMargin.right, thumbMargin.bottom); - } - } - - public void setThumbMargin(float left, float top, float right, float bottom) { - mThumbMargin.set(left, top, right, bottom); - requestLayout(); - } - - public void setThumbSize(float width, float height) { - mThumbSizeF.set(width, height); - setup(); - requestLayout(); - } - - public float getThumbWidth() { - return mThumbSizeF.x; - } - - public float getThumbHeight() { - return mThumbSizeF.y; - } - - public void setThumbSize(PointF size) { - if (size == null) { - float defaultSize = getResources().getDisplayMetrics().density * DEFAULT_THUMB_SIZE_DP; - setThumbSize(defaultSize, defaultSize); - } else { - setThumbSize(size.x, size.y); - } - } - - public PointF getThumbSizeF() { - return mThumbSizeF; - } - - public float getThumbRadius() { - return mThumbRadius; - } - - public void setThumbRadius(float thumbRadius) { - mThumbRadius = thumbRadius; - if (!mIsThumbUseDrawable) { - invalidate(); - } - } - - public PointF getBackSizeF() { - return new PointF(mBackRectF.width(), mBackRectF.height()); - } - - public float getBackRadius() { - return mBackRadius; - } - - public void setBackRadius(float backRadius) { - mBackRadius = backRadius; - if (!mIsBackUseDrawable) { - invalidate(); - } - } - - public boolean isFadeBack() { - return mFadeBack; - } - - public void setFadeBack(boolean fadeBack) { - mFadeBack = fadeBack; - } - - public int getTintColor() { - return mTintColor; - } - - public void setTintColor(int tintColor) { - mTintColor = tintColor; - mThumbColor = ColorUtils.generateThumbColorWithTintColor(mTintColor); - mBackColor = ColorUtils.generateBackColorWithTintColor(mTintColor); - mIsBackUseDrawable = false; - mIsThumbUseDrawable = false; - // call this method to refresh color states - refreshDrawableState(); - invalidate(); - } - - public void setText(CharSequence onText, CharSequence offText) { - mTextOn = onText; - mTextOff = offText; - - mOnLayout = null; - mOffLayout = null; - - requestLayout(); - invalidate(); - } - - - @Override - public Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - SavedState ss = new SavedState(superState); - ss.onText = mTextOn; - ss.offText = mTextOff; - return ss; - } - - @Override - public void onRestoreInstanceState(Parcelable state) { - SavedState ss = (SavedState) state; - setText(ss.onText, ss.offText); - super.onRestoreInstanceState(ss.getSuperState()); - } - - static class SavedState extends BaseSavedState { - CharSequence onText; - CharSequence offText; - - SavedState(Parcelable superState) { - super(superState); - } - - private SavedState(Parcel in) { - super(in); - onText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); - offText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); - } - - @Override - public void writeToParcel(Parcel out, int flags) { - super.writeToParcel(out, flags); - TextUtils.writeToParcel(onText, out, flags); - TextUtils.writeToParcel(offText, out, flags); - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } + public static final float DEFAULT_THUMB_RANGE_RATIO = 1.8f; + public static final int DEFAULT_THUMB_SIZE_DP = 20; + public static final int DEFAULT_THUMB_MARGIN_DP = 2; + public static final int DEFAULT_ANIMATION_DURATION = 250; + public static final int DEFAULT_TINT_COLOR = 0x327FC2; + + private static final int[] CHECKED_PRESSED_STATE = new int[]{android.R.attr.state_checked, android.R.attr.state_enabled, android.R.attr.state_pressed}; + private static final int[] UNCHECKED_PRESSED_STATE = new int[]{-android.R.attr.state_checked, android.R.attr.state_enabled, android.R.attr.state_pressed}; + + private Drawable mThumbDrawable, mBackDrawable; + private ColorStateList mBackColor, mThumbColor; + private float mThumbRadius, mBackRadius; + private RectF mThumbMargin; + private float mThumbRangeRatio; + private long mAnimationDuration; + // fade back drawable or color when dragging or animating + private boolean mFadeBack; + private int mTintColor; + private int mThumbWidth; + private int mThumbHeight; + private int mBackWidth; + private int mBackHeight; + + private int mCurrThumbColor, mCurrBackColor, mNextBackColor, mOnTextColor, mOffTextColor; + private Drawable mCurrentBackDrawable, mNextBackDrawable; + private RectF mThumbRectF, mBackRectF, mSafeRectF, mTextOnRectF, mTextOffRectF; + private Paint mPaint; + // whether using Drawable for thumb or back + private boolean mIsThumbUseDrawable, mIsBackUseDrawable; + private boolean mDrawDebugRect = false; + private ValueAnimator mProgressAnimator; + // animation control + private float mProgress; + // temp position of thumb when dragging or animating + private RectF mPresentThumbRectF; + private float mStartX, mStartY, mLastX; + private int mTouchSlop; + private int mClickTimeout; + private Paint mRectPaint; + private CharSequence mTextOn; + private CharSequence mTextOff; + private TextPaint mTextPaint; + private Layout mOnLayout; + private Layout mOffLayout; + private float mTextWidth; + private float mTextHeight; + private int mTextThumbInset; + private int mTextExtra; + private int mTextAdjust; + // FIX #78,#85 : When restoring saved states, setChecked() called by super. So disable + // animation and event listening when restoring. + private boolean mRestoring = false; + private boolean mReady = false; + private boolean mCatch = false; + private UnsetPressedState mUnsetPressedState; + + private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener; + + public SwitchButton(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(attrs); + } + + public SwitchButton(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs); + } + + public SwitchButton(Context context) { + super(context); + init(null); + } + + private void init(AttributeSet attrs) { + mTouchSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop(); + mClickTimeout = ViewConfiguration.getPressedStateDuration() + ViewConfiguration.getTapTimeout(); + + mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mRectPaint = new Paint(Paint.ANTI_ALIAS_FLAG); + mRectPaint.setStyle(Paint.Style.STROKE); + mRectPaint.setStrokeWidth(getResources().getDisplayMetrics().density); + + mTextPaint = getPaint(); + + mThumbRectF = new RectF(); + mBackRectF = new RectF(); + mSafeRectF = new RectF(); + mThumbMargin = new RectF(); + mTextOnRectF = new RectF(); + mTextOffRectF = new RectF(); + + mProgressAnimator = ValueAnimator.ofFloat(0, 0).setDuration(DEFAULT_ANIMATION_DURATION); + mProgressAnimator.setInterpolator(new AccelerateDecelerateInterpolator()); + mProgressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator valueAnimator) { + setProgress((float) valueAnimator.getAnimatedValue()); + } + }); + + mPresentThumbRectF = new RectF(); + + Resources res = getResources(); + float density = res.getDisplayMetrics().density; + + Drawable thumbDrawable = null; + ColorStateList thumbColor = null; + float margin = density * DEFAULT_THUMB_MARGIN_DP; + float marginLeft = 0; + float marginRight = 0; + float marginTop = 0; + float marginBottom = 0; + float thumbWidth = 0; + float thumbHeight = 0; + float thumbRadius = -1; + float backRadius = -1; + Drawable backDrawable = null; + ColorStateList backColor = null; + float thumbRangeRatio = DEFAULT_THUMB_RANGE_RATIO; + int animationDuration = DEFAULT_ANIMATION_DURATION; + boolean fadeBack = true; + int tintColor = 0; + String textOn = null; + String textOff = null; + int textThumbInset = 0; + int textExtra = 0; + int textAdjust = 0; + + TypedArray ta = attrs == null ? null : getContext().obtainStyledAttributes(attrs, R.styleable.SwitchButton); + if (ta != null) { + thumbDrawable = ta.getDrawable(R.styleable.SwitchButton_kswThumbDrawable); + thumbColor = ta.getColorStateList(R.styleable.SwitchButton_kswThumbColor); + margin = ta.getDimension(R.styleable.SwitchButton_kswThumbMargin, margin); + marginLeft = ta.getDimension(R.styleable.SwitchButton_kswThumbMarginLeft, margin); + marginRight = ta.getDimension(R.styleable.SwitchButton_kswThumbMarginRight, margin); + marginTop = ta.getDimension(R.styleable.SwitchButton_kswThumbMarginTop, margin); + marginBottom = ta.getDimension(R.styleable.SwitchButton_kswThumbMarginBottom, margin); + thumbWidth = ta.getDimension(R.styleable.SwitchButton_kswThumbWidth, thumbWidth); + thumbHeight = ta.getDimension(R.styleable.SwitchButton_kswThumbHeight, thumbHeight); + thumbRadius = ta.getDimension(R.styleable.SwitchButton_kswThumbRadius, thumbRadius); + backRadius = ta.getDimension(R.styleable.SwitchButton_kswBackRadius, backRadius); + backDrawable = ta.getDrawable(R.styleable.SwitchButton_kswBackDrawable); + backColor = ta.getColorStateList(R.styleable.SwitchButton_kswBackColor); + thumbRangeRatio = ta.getFloat(R.styleable.SwitchButton_kswThumbRangeRatio, thumbRangeRatio); + animationDuration = ta.getInteger(R.styleable.SwitchButton_kswAnimationDuration, animationDuration); + fadeBack = ta.getBoolean(R.styleable.SwitchButton_kswFadeBack, true); + tintColor = ta.getColor(R.styleable.SwitchButton_kswTintColor, tintColor); + textOn = ta.getString(R.styleable.SwitchButton_kswTextOn); + textOff = ta.getString(R.styleable.SwitchButton_kswTextOff); + textThumbInset = ta.getDimensionPixelSize(R.styleable.SwitchButton_kswTextThumbInset, 0); + textExtra = ta.getDimensionPixelSize(R.styleable.SwitchButton_kswTextExtra, 0); + textAdjust = ta.getDimensionPixelSize(R.styleable.SwitchButton_kswTextAdjust, 0); + ta.recycle(); + } + + // click + ta = attrs == null ? null : getContext().obtainStyledAttributes(attrs, new int[]{android.R.attr.focusable, android.R.attr.clickable}); + if (ta != null) { + boolean focusable = ta.getBoolean(0, true); + //noinspection ResourceType + @SuppressLint("ResourceType") + boolean clickable = ta.getBoolean(1, focusable); + setFocusable(focusable); + setClickable(clickable); + ta.recycle(); + } else { + setFocusable(true); + setClickable(true); + } + + // text + mTextOn = textOn; + mTextOff = textOff; + mTextThumbInset = textThumbInset; + mTextExtra = textExtra; + mTextAdjust = textAdjust; + + // thumb drawable and color + mThumbDrawable = thumbDrawable; + mThumbColor = thumbColor; + mIsThumbUseDrawable = mThumbDrawable != null; + mTintColor = tintColor; + if (mTintColor == 0) { + mTintColor = getThemeAccentColorOrDefault(getContext(), DEFAULT_TINT_COLOR); + } + if (!mIsThumbUseDrawable && mThumbColor == null) { + mThumbColor = ColorUtils.generateThumbColorWithTintColor(mTintColor); + mCurrThumbColor = mThumbColor.getDefaultColor(); + } + + // thumbSize + mThumbWidth = ceil(thumbWidth); + mThumbHeight = ceil(thumbHeight); + + // back drawable and color + mBackDrawable = backDrawable; + mBackColor = backColor; + mIsBackUseDrawable = mBackDrawable != null; + if (!mIsBackUseDrawable && mBackColor == null) { + mBackColor = ColorUtils.generateBackColorWithTintColor(mTintColor); + mCurrBackColor = mBackColor.getDefaultColor(); + mNextBackColor = mBackColor.getColorForState(CHECKED_PRESSED_STATE, mCurrBackColor); + } + + // margin + mThumbMargin.set(marginLeft, marginTop, marginRight, marginBottom); + + // size & measure params must larger than 1 + mThumbRangeRatio = mThumbMargin.width() >= 0 ? Math.max(thumbRangeRatio, 1) : thumbRangeRatio; + + mThumbRadius = thumbRadius; + mBackRadius = backRadius; + mAnimationDuration = animationDuration; + mFadeBack = fadeBack; + + mProgressAnimator.setDuration(mAnimationDuration); + + // sync checked status + if (isChecked()) { + setProgress(1); + } + } + + private static int getThemeAccentColorOrDefault(Context context, @SuppressWarnings("SameParameterValue") int defaultColor) { + int colorAttr; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + colorAttr = android.R.attr.colorAccent; + } else { + //Get colorAccent defined for AppCompat + colorAttr = context.getResources().getIdentifier("colorAccent", "attr", context.getPackageName()); + } + TypedValue outValue = new TypedValue(); + boolean resolved = context.getTheme().resolveAttribute(colorAttr, outValue, true); + return resolved ? outValue.data : defaultColor; + } + + private Layout makeLayout(CharSequence text) { + return new StaticLayout(text, mTextPaint, (int) Math.ceil(Layout.getDesiredWidth(text, mTextPaint)), Layout.Alignment.ALIGN_CENTER, 1.f, 0, false); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + /* + * ensure textLayout + */ + if (mOnLayout == null && !TextUtils.isEmpty(mTextOn)) { + mOnLayout = makeLayout(mTextOn); + } + if (mOffLayout == null && !TextUtils.isEmpty(mTextOff)) { + mOffLayout = makeLayout(mTextOff); + } + + float onWidth = mOnLayout != null ? mOnLayout.getWidth() : 0; + float offWidth = mOffLayout != null ? mOffLayout.getWidth() : 0; + if (onWidth != 0 || offWidth != 0) { + mTextWidth = Math.max(onWidth, offWidth); + } else { + mTextWidth = 0; + } + + float onHeight = mOnLayout != null ? mOnLayout.getHeight() : 0; + float offHeight = mOffLayout != null ? mOffLayout.getHeight() : 0; + if (onHeight != 0 || offHeight != 0) { + mTextHeight = Math.max(onHeight, offHeight); + } else { + mTextHeight = 0; + } + + setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); + } + + /** + * SwitchButton use this formula to determine the final size of thumb, background and itself. + *

+ * textWidth = max(onWidth, offWidth) + * thumbRange = thumbWidth * rangeRatio + * textExtraSpace = textWidth + textExtra - (moveRange - thumbWidth + max(thumbMargin.left, thumbMargin.right) + textThumbInset) + * backWidth = thumbRange + thumbMargin.left + thumbMargin.right + max(textExtraSpace, 0) + * contentSize = thumbRange + max(thumbMargin.left, 0) + max(thumbMargin.right, 0) + max(textExtraSpace, 0) + * + * @param widthMeasureSpec widthMeasureSpec + * @return measuredWidth + */ + private int measureWidth(int widthMeasureSpec) { + int widthSize = MeasureSpec.getSize(widthMeasureSpec); + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int measuredWidth = widthSize; + + if (mThumbWidth == 0 && mIsThumbUseDrawable) { + mThumbWidth = mThumbDrawable.getIntrinsicWidth(); + } + + int moveRange; + int textWidth = ceil(mTextWidth); + // how much the background should extend to fit text. + int textExtraSpace; + int contentSize; + + if (mThumbRangeRatio == 0) { + mThumbRangeRatio = DEFAULT_THUMB_RANGE_RATIO; + } + + if (widthMode == MeasureSpec.EXACTLY) { + contentSize = widthSize - getPaddingLeft() - getPaddingRight(); + + if (mThumbWidth != 0) { + moveRange = ceil(mThumbWidth * mThumbRangeRatio); + textExtraSpace = textWidth + mTextExtra - (moveRange - mThumbWidth + ceil(Math.max(mThumbMargin.left, mThumbMargin.right))); + mBackWidth = ceil(moveRange + mThumbMargin.left + mThumbMargin.right + Math.max(textExtraSpace, 0)); + if (mBackWidth < 0) { + mThumbWidth = 0; + } + if (moveRange + Math.max(mThumbMargin.left, 0) + Math.max(mThumbMargin.right, 0) + Math.max(textExtraSpace, 0) > contentSize) { + mThumbWidth = 0; + } + } + + if (mThumbWidth == 0) { + contentSize = widthSize - getPaddingLeft() - getPaddingRight(); + moveRange = ceil(contentSize - Math.max(mThumbMargin.left, 0) - Math.max(mThumbMargin.right, 0)); + if (moveRange < 0) { + mThumbWidth = 0; + mBackWidth = 0; + return measuredWidth; + } + mThumbWidth = ceil(moveRange / mThumbRangeRatio); + mBackWidth = ceil(moveRange + mThumbMargin.left + mThumbMargin.right); + if (mBackWidth < 0) { + mThumbWidth = 0; + mBackWidth = 0; + return measuredWidth; + } + textExtraSpace = textWidth + mTextExtra - (moveRange - mThumbWidth + ceil(Math.max(mThumbMargin.left, mThumbMargin.right))); + if (textExtraSpace > 0) { + // since backWidth is determined by view width, so we can only reduce thumbSize. + mThumbWidth = mThumbWidth - textExtraSpace; + } + if (mThumbWidth < 0) { + mThumbWidth = 0; + mBackWidth = 0; + return measuredWidth; + } + } + } else { + /* + If parent view want SwitchButton to determine it's size itself, we calculate the minimal + size of it's content. Further more, we ignore the limitation of widthSize since we want + to display SwitchButton in its actual size rather than compress the shape. + */ + if (mThumbWidth == 0) { + /* + If thumbWidth is not set, use the default one. + */ + mThumbWidth = ceil(getResources().getDisplayMetrics().density * DEFAULT_THUMB_SIZE_DP); + } + if (mThumbRangeRatio == 0) { + mThumbRangeRatio = DEFAULT_THUMB_RANGE_RATIO; + } + + moveRange = ceil(mThumbWidth * mThumbRangeRatio); + textExtraSpace = ceil(textWidth + mTextExtra - (moveRange - mThumbWidth + Math.max(mThumbMargin.left, mThumbMargin.right) + mTextThumbInset)); + mBackWidth = ceil(moveRange + mThumbMargin.left + mThumbMargin.right + Math.max(0, textExtraSpace)); + if (mBackWidth < 0) { + mThumbWidth = 0; + mBackWidth = 0; + return measuredWidth; + } + contentSize = ceil(moveRange + Math.max(0, mThumbMargin.left) + Math.max(0, mThumbMargin.right) + Math.max(0, textExtraSpace)); + + measuredWidth = Math.max(contentSize, contentSize + getPaddingLeft() + getPaddingRight()); + } + return measuredWidth; + } + + private int measureHeight(int heightMeasureSpec) { + int heightSize = MeasureSpec.getSize(heightMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + int measuredHeight = heightSize; + + if (mThumbHeight == 0 && mIsThumbUseDrawable) { + mThumbHeight = mThumbDrawable.getIntrinsicHeight(); + } + int contentSize; + int textExtraSpace; + if (heightMode == MeasureSpec.EXACTLY) { + if (mThumbHeight != 0) { + /* + If thumbHeight has been set, we calculate backHeight and check if there is enough room. + */ + mBackHeight = ceil(mThumbHeight + mThumbMargin.top + mThumbMargin.bottom); + mBackHeight = ceil(Math.max(mBackHeight, mTextHeight)); + if (mBackHeight + getPaddingTop() + getPaddingBottom() - Math.min(0, mThumbMargin.top) - Math.min(0, mThumbMargin.bottom) > heightSize) { + // No enough room, we set thumbHeight to zero to calculate these value again. + mThumbHeight = 0; + } + } + + if (mThumbHeight == 0) { + mBackHeight = ceil(heightSize - getPaddingTop() - getPaddingBottom() + Math.min(0, mThumbMargin.top) + Math.min(0, mThumbMargin.bottom)); + if (mBackHeight < 0) { + mBackHeight = 0; + mThumbHeight = 0; + return measuredHeight; + } + mThumbHeight = ceil(mBackHeight - mThumbMargin.top - mThumbMargin.bottom); + } + if (mThumbHeight < 0) { + mBackHeight = 0; + mThumbHeight = 0; + return measuredHeight; + } + } else { + if (mThumbHeight == 0) { + mThumbHeight = ceil(getResources().getDisplayMetrics().density * DEFAULT_THUMB_SIZE_DP); + } + mBackHeight = ceil(mThumbHeight + mThumbMargin.top + mThumbMargin.bottom); + if (mBackHeight < 0) { + mBackHeight = 0; + mThumbHeight = 0; + return measuredHeight; + } + textExtraSpace = ceil(mTextHeight - mBackHeight); + if (textExtraSpace > 0) { + mBackHeight += textExtraSpace; + mThumbHeight += textExtraSpace; + } + contentSize = Math.max(mThumbHeight, mBackHeight); + + measuredHeight = Math.max(contentSize, contentSize + getPaddingTop() + getPaddingBottom()); + measuredHeight = Math.max(measuredHeight, getSuggestedMinimumHeight()); + } + + return measuredHeight; + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (w != oldw || h != oldh) { + setup(); + } + } + + private int ceil(double dimen) { + return (int) Math.ceil(dimen); + } + + /** + * set up the rect of back and thumb + */ + private void setup() { + if (mThumbWidth == 0 || mThumbHeight == 0 || mBackWidth == 0 || mBackHeight == 0) { + return; + } + + if (mThumbRadius == -1) { + mThumbRadius = Math.min(mThumbWidth, mThumbHeight) / 2f; + } + if (mBackRadius == -1) { + mBackRadius = Math.min(mBackWidth, mBackHeight) / 2f; + } + + int contentWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); + int contentHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom(); + + // max range of drawing content, when thumbMargin is negative, drawing range is larger than backWidth + int drawingWidth = ceil(mBackWidth - Math.min(0, mThumbMargin.left) - Math.min(0, mThumbMargin.right)); + int drawingHeight = ceil(mBackHeight - Math.min(0, mThumbMargin.top) - Math.min(0, mThumbMargin.bottom)); + + float thumbTop; + if (contentHeight <= drawingHeight) { + thumbTop = getPaddingTop() + Math.max(0, mThumbMargin.top); + } else { + // center vertical in content area + thumbTop = getPaddingTop() + Math.max(0, mThumbMargin.top) + (contentHeight - drawingHeight + 1) / 2f; + } + + float thumbLeft; + if (contentWidth <= mBackWidth) { + thumbLeft = getPaddingLeft() + Math.max(0, mThumbMargin.left); + } else { + thumbLeft = getPaddingLeft() + Math.max(0, mThumbMargin.left) + (contentWidth - drawingWidth + 1) / 2f; + } + + mThumbRectF.set(thumbLeft, thumbTop, thumbLeft + mThumbWidth, thumbTop + mThumbHeight); + + float backLeft = mThumbRectF.left - mThumbMargin.left; + mBackRectF.set(backLeft, + mThumbRectF.top - mThumbMargin.top, + backLeft + mBackWidth, + mThumbRectF.top - mThumbMargin.top + mBackHeight); + + mSafeRectF.set(mThumbRectF.left, 0, mBackRectF.right - mThumbMargin.right - mThumbRectF.width(), 0); + + float minBackRadius = Math.min(mBackRectF.width(), mBackRectF.height()) / 2.f; + mBackRadius = Math.min(minBackRadius, mBackRadius); + + if (mBackDrawable != null) { + mBackDrawable.setBounds((int) mBackRectF.left, (int) mBackRectF.top, ceil(mBackRectF.right), ceil(mBackRectF.bottom)); + } + + if (mOnLayout != null) { + float onLeft = mBackRectF.left + (mBackRectF.width() + mTextThumbInset - mThumbWidth - mThumbMargin.right - mOnLayout.getWidth()) / 2f - mTextAdjust; + float onTop = mBackRectF.top + (mBackRectF.height() - mOnLayout.getHeight()) / 2; + mTextOnRectF.set(onLeft, onTop, onLeft + mOnLayout.getWidth(), onTop + mOnLayout.getHeight()); + } + + if (mOffLayout != null) { + float offLeft = mBackRectF.right - (mBackRectF.width() + mTextThumbInset - mThumbWidth - mThumbMargin.left - mOffLayout.getWidth()) / 2f - mOffLayout.getWidth() + mTextAdjust; + float offTop = mBackRectF.top + (mBackRectF.height() - mOffLayout.getHeight()) / 2; + mTextOffRectF.set(offLeft, offTop, offLeft + mOffLayout.getWidth(), offTop + mOffLayout.getHeight()); + } + + mReady = true; + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + if (!mReady) { + setup(); + } + if (!mReady) { + return; + } + + // fade back + if (mIsBackUseDrawable) { + if (mFadeBack && mCurrentBackDrawable != null && mNextBackDrawable != null) { + // fix #75, 70%A + 30%B != 30%B + 70%A, order matters when mix two layer of different alpha. + // So make sure the order of on/off layers never change during slide from one endpoint to another. + Drawable below = isChecked() ? mCurrentBackDrawable : mNextBackDrawable; + Drawable above = isChecked() ? mNextBackDrawable : mCurrentBackDrawable; + + int alpha = (int) (255 * getProgress()); + below.setAlpha(alpha); + below.draw(canvas); + alpha = 255 - alpha; + above.setAlpha(alpha); + above.draw(canvas); + } else { + mBackDrawable.setAlpha(255); + mBackDrawable.draw(canvas); + } + } else { + if (mFadeBack) { + int alpha; + int colorAlpha; + + // fix #75 + int belowColor = isChecked() ? mCurrBackColor : mNextBackColor; + int aboveColor = isChecked() ? mNextBackColor : mCurrBackColor; + + // curr back + alpha = (int) (255 * getProgress()); + colorAlpha = Color.alpha(belowColor); + colorAlpha = colorAlpha * alpha / 255; + mPaint.setARGB(colorAlpha, Color.red(belowColor), Color.green(belowColor), Color.blue(belowColor)); + canvas.drawRoundRect(mBackRectF, mBackRadius, mBackRadius, mPaint); + + // next back + alpha = 255 - alpha; + colorAlpha = Color.alpha(aboveColor); + colorAlpha = colorAlpha * alpha / 255; + mPaint.setARGB(colorAlpha, Color.red(aboveColor), Color.green(aboveColor), Color.blue(aboveColor)); + canvas.drawRoundRect(mBackRectF, mBackRadius, mBackRadius, mPaint); + + mPaint.setAlpha(255); + } else { + mPaint.setColor(mCurrBackColor); + canvas.drawRoundRect(mBackRectF, mBackRadius, mBackRadius, mPaint); + } + } + + // text + Layout switchText = getProgress() > 0.5 ? mOnLayout : mOffLayout; + RectF textRectF = getProgress() > 0.5 ? mTextOnRectF : mTextOffRectF; + if (switchText != null && textRectF != null) { + int alpha = (int) (255 * (getProgress() >= 0.75 ? getProgress() * 4 - 3 : (getProgress() < 0.25 ? 1 - getProgress() * 4 : 0))); + int textColor = getProgress() > 0.5 ? mOnTextColor : mOffTextColor; + int colorAlpha = Color.alpha(textColor); + colorAlpha = colorAlpha * alpha / 255; + switchText.getPaint().setARGB(colorAlpha, Color.red(textColor), Color.green(textColor), Color.blue(textColor)); + canvas.save(); + canvas.translate(textRectF.left, textRectF.top); + switchText.draw(canvas); + canvas.restore(); + } + + // thumb + mPresentThumbRectF.set(mThumbRectF); + mPresentThumbRectF.offset(mProgress * mSafeRectF.width(), 0); + if (mIsThumbUseDrawable) { + mThumbDrawable.setBounds((int) mPresentThumbRectF.left, (int) mPresentThumbRectF.top, ceil(mPresentThumbRectF.right), ceil(mPresentThumbRectF.bottom)); + mThumbDrawable.draw(canvas); + } else { + mPaint.setColor(mCurrThumbColor); + canvas.drawRoundRect(mPresentThumbRectF, mThumbRadius, mThumbRadius, mPaint); + } + + if (mDrawDebugRect) { + mRectPaint.setColor(Color.parseColor("#AA0000")); + canvas.drawRect(mBackRectF, mRectPaint); + mRectPaint.setColor(Color.parseColor("#0000FF")); + canvas.drawRect(mPresentThumbRectF, mRectPaint); + mRectPaint.setColor(Color.parseColor("#000000")); + canvas.drawLine(mSafeRectF.left, mThumbRectF.top, mSafeRectF.right, mThumbRectF.top, mRectPaint); + mRectPaint.setColor(Color.parseColor("#00CC00")); + canvas.drawRect(getProgress() > 0.5 ? mTextOnRectF : mTextOffRectF, mRectPaint); + } + } + + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + if (!mIsThumbUseDrawable && mThumbColor != null) { + mCurrThumbColor = mThumbColor.getColorForState(getDrawableState(), mCurrThumbColor); + } else { + setDrawableState(mThumbDrawable); + } + + int[] nextState = isChecked() ? UNCHECKED_PRESSED_STATE : CHECKED_PRESSED_STATE; + ColorStateList textColors = getTextColors(); + if (textColors != null) { + int defaultTextColor = textColors.getDefaultColor(); + mOnTextColor = textColors.getColorForState(CHECKED_PRESSED_STATE, defaultTextColor); + mOffTextColor = textColors.getColorForState(UNCHECKED_PRESSED_STATE, defaultTextColor); + } + if (!mIsBackUseDrawable && mBackColor != null) { + mCurrBackColor = mBackColor.getColorForState(getDrawableState(), mCurrBackColor); + mNextBackColor = mBackColor.getColorForState(nextState, mCurrBackColor); + } else { + if (mBackDrawable instanceof StateListDrawable && mFadeBack) { + mBackDrawable.setState(nextState); + mNextBackDrawable = mBackDrawable.getCurrent().mutate(); + } else { + mNextBackDrawable = null; + } + setDrawableState(mBackDrawable); + if (mBackDrawable != null) { + mCurrentBackDrawable = mBackDrawable.getCurrent().mutate(); + } + } + } + + @Override + public boolean onTouchEvent(MotionEvent event) { + + if (!isEnabled() || !isClickable() || !isFocusable() || !mReady) { + return false; + } + + int action = event.getAction(); + + float deltaX = event.getX() - mStartX; + float deltaY = event.getY() - mStartY; + + switch (action) { + case MotionEvent.ACTION_DOWN: + mStartX = event.getX(); + mStartY = event.getY(); + mLastX = mStartX; + setPressed(true); + break; + + case MotionEvent.ACTION_MOVE: + float x = event.getX(); + setProgress(getProgress() + (x - mLastX) / mSafeRectF.width()); + mLastX = x; + if (!mCatch && (Math.abs(deltaX) > mTouchSlop / 2f || Math.abs(deltaY) > mTouchSlop / 2f)) { + if (deltaY == 0 || Math.abs(deltaX) > Math.abs(deltaY)) { + catchView(); + } else if (Math.abs(deltaY) > Math.abs(deltaX)) { + return false; + } + } + break; + + case MotionEvent.ACTION_CANCEL: + case MotionEvent.ACTION_UP: + mCatch = false; + float time = event.getEventTime() - event.getDownTime(); + if (Math.abs(deltaX) < mTouchSlop && Math.abs(deltaY) < mTouchSlop && time < mClickTimeout) { + performClick(); + } else { + boolean nextStatus = getStatusBasedOnPos(); + if (nextStatus != isChecked()) { + playSoundEffect(SoundEffectConstants.CLICK); + setChecked(nextStatus); + } else { + animateToState(nextStatus); + } + } + if (isPressed()) { + if (mUnsetPressedState == null) { + mUnsetPressedState = new UnsetPressedState(); + } + if (!post(mUnsetPressedState)) { + mUnsetPressedState.run(); + } + } + break; + + default: + break; + } + return true; + } + + + /** + * return the status based on position of thumb + * + * @return whether checked or not + */ + private boolean getStatusBasedOnPos() { + return getProgress() > 0.5f; + } + + private float getProgress() { + return mProgress; + } + + private void setProgress(final float progress) { + float tempProgress = progress; + if (tempProgress > 1) { + tempProgress = 1; + } else if (tempProgress < 0) { + tempProgress = 0; + } + this.mProgress = tempProgress; + invalidate(); + } + + @Override + public boolean performClick() { + return super.performClick(); + } + + /** + * processing animation + * + * @param checked checked or unChecked + */ + protected void animateToState(boolean checked) { + if (mProgressAnimator == null) { + return; + } + if (mProgressAnimator.isRunning()) { + mProgressAnimator.cancel(); + } + mProgressAnimator.setDuration(mAnimationDuration); + if (checked) { + mProgressAnimator.setFloatValues(mProgress, 1f); + } else { + mProgressAnimator.setFloatValues(mProgress, 0); + } + mProgressAnimator.start(); + } + + private void catchView() { + ViewParent parent = getParent(); + if (parent != null) { + parent.requestDisallowInterceptTouchEvent(true); + } + mCatch = true; + } + + @Override + public void setChecked(final boolean checked) { + // animate before super.setChecked() become user may call setChecked again in OnCheckedChangedListener + if (isChecked() != checked) { + animateToState(checked); + } + if (mRestoring) { + setCheckedImmediatelyNoEvent(checked); + } else { + super.setChecked(checked); + } + } + + public void setCheckedNoEvent(final boolean checked) { + if (mChildOnCheckedChangeListener == null) { + setChecked(checked); + } else { + super.setOnCheckedChangeListener(null); + setChecked(checked); + super.setOnCheckedChangeListener(mChildOnCheckedChangeListener); + } + } + + public void setCheckedImmediatelyNoEvent(boolean checked) { + if (mChildOnCheckedChangeListener == null) { + setCheckedImmediately(checked); + } else { + super.setOnCheckedChangeListener(null); + setCheckedImmediately(checked); + super.setOnCheckedChangeListener(mChildOnCheckedChangeListener); + } + } + + public void toggleNoEvent() { + if (mChildOnCheckedChangeListener == null) { + toggle(); + } else { + super.setOnCheckedChangeListener(null); + toggle(); + super.setOnCheckedChangeListener(mChildOnCheckedChangeListener); + } + } + + public void toggleImmediatelyNoEvent() { + if (mChildOnCheckedChangeListener == null) { + toggleImmediately(); + } else { + super.setOnCheckedChangeListener(null); + toggleImmediately(); + super.setOnCheckedChangeListener(mChildOnCheckedChangeListener); + } + } + + @Override + public void setOnCheckedChangeListener(OnCheckedChangeListener onCheckedChangeListener) { + super.setOnCheckedChangeListener(onCheckedChangeListener); + mChildOnCheckedChangeListener = onCheckedChangeListener; + } + + public void setCheckedImmediately(boolean checked) { + super.setChecked(checked); + if (mProgressAnimator != null && mProgressAnimator.isRunning()) { + mProgressAnimator.cancel(); + } + setProgress(checked ? 1 : 0); + invalidate(); + } + + public void toggleImmediately() { + setCheckedImmediately(!isChecked()); + } + + private void setDrawableState(Drawable drawable) { + if (drawable != null) { + int[] myDrawableState = getDrawableState(); + drawable.setState(myDrawableState); + invalidate(); + } + } + + public boolean isDrawDebugRect() { + return mDrawDebugRect; + } + + public void setDrawDebugRect(boolean drawDebugRect) { + mDrawDebugRect = drawDebugRect; + invalidate(); + } + + public long getAnimationDuration() { + return mAnimationDuration; + } + + public void setAnimationDuration(long animationDuration) { + mAnimationDuration = animationDuration; + } + + public Drawable getThumbDrawable() { + return mThumbDrawable; + } + + public void setThumbDrawable(Drawable thumbDrawable) { + mThumbDrawable = thumbDrawable; + mIsThumbUseDrawable = mThumbDrawable != null; + refreshDrawableState(); + mReady = false; + requestLayout(); + invalidate(); + } + + public void setThumbDrawableRes(int thumbDrawableRes) { + setThumbDrawable(getDrawableCompat(getContext(), thumbDrawableRes)); + } + + public Drawable getBackDrawable() { + return mBackDrawable; + } + + public void setBackDrawable(Drawable backDrawable) { + mBackDrawable = backDrawable; + mIsBackUseDrawable = mBackDrawable != null; + refreshDrawableState(); + mReady = false; + requestLayout(); + invalidate(); + } + + public void setBackDrawableRes(int backDrawableRes) { + setBackDrawable(getDrawableCompat(getContext(), backDrawableRes)); + } + + public ColorStateList getBackColor() { + return mBackColor; + } + + public void setBackColor(ColorStateList backColor) { + mBackColor = backColor; + if (mBackColor != null) { + setBackDrawable(null); + } + invalidate(); + } + + public void setBackColorRes(int backColorRes) { + setBackColor(getColorStateListCompat(getContext(), backColorRes)); + } + + public ColorStateList getThumbColor() { + return mThumbColor; + } + + public void setThumbColor(ColorStateList thumbColor) { + mThumbColor = thumbColor; + if (mThumbColor != null) { + setThumbDrawable(null); + } + invalidate(); + } + + public void setThumbColorRes(int thumbColorRes) { + setThumbColor(getColorStateListCompat(getContext(), thumbColorRes)); + } + + public float getThumbRangeRatio() { + return mThumbRangeRatio; + } + + public void setThumbRangeRatio(float thumbRangeRatio) { + mThumbRangeRatio = thumbRangeRatio; + // We need to mark "ready" to false since requestLayout may not cause size changed. + mReady = false; + requestLayout(); + } + + public RectF getThumbMargin() { + return mThumbMargin; + } + + public void setThumbMargin(RectF thumbMargin) { + if (thumbMargin == null) { + setThumbMargin(0, 0, 0, 0); + } else { + setThumbMargin(thumbMargin.left, thumbMargin.top, thumbMargin.right, thumbMargin.bottom); + } + } + + public void setThumbMargin(float left, float top, float right, float bottom) { + mThumbMargin.set(left, top, right, bottom); + mReady = false; + requestLayout(); + } + + public void setThumbSize(int width, int height) { + mThumbWidth = width; + mThumbHeight = height; + mReady = false; + requestLayout(); + } + + public float getThumbWidth() { + return mThumbWidth; + } + + public float getThumbHeight() { + return mThumbHeight; + } + + public float getThumbRadius() { + return mThumbRadius; + } + + public void setThumbRadius(float thumbRadius) { + mThumbRadius = thumbRadius; + if (!mIsThumbUseDrawable) { + invalidate(); + } + } + + public PointF getBackSizeF() { + return new PointF(mBackRectF.width(), mBackRectF.height()); + } + + public float getBackRadius() { + return mBackRadius; + } + + public void setBackRadius(float backRadius) { + mBackRadius = backRadius; + if (!mIsBackUseDrawable) { + invalidate(); + } + } + + public boolean isFadeBack() { + return mFadeBack; + } + + public void setFadeBack(boolean fadeBack) { + mFadeBack = fadeBack; + } + + public int getTintColor() { + return mTintColor; + } + + public void setTintColor(@SuppressWarnings("SameParameterValue") int tintColor) { + mTintColor = tintColor; + mThumbColor = ColorUtils.generateThumbColorWithTintColor(mTintColor); + mBackColor = ColorUtils.generateBackColorWithTintColor(mTintColor); + mIsBackUseDrawable = false; + mIsThumbUseDrawable = false; + // call this method to refresh color states + refreshDrawableState(); + invalidate(); + } + + public void setText(CharSequence onText, CharSequence offText) { + mTextOn = onText; + mTextOff = offText; + + mOnLayout = null; + mOffLayout = null; + + mReady = false; + requestLayout(); + invalidate(); + } + + public CharSequence getTextOn() { + return mTextOn; + } + + public CharSequence getTextOff() { + return mTextOff; + } + + public void setTextThumbInset(int textThumbInset) { + mTextThumbInset = textThumbInset; + mReady = false; + requestLayout(); + invalidate(); + } + + public void setTextExtra(int textExtra) { + mTextExtra = textExtra; + mReady = false; + requestLayout(); + invalidate(); + } + + public void setTextAdjust(int textAdjust) { + mTextAdjust = textAdjust; + mReady = false; + requestLayout(); + invalidate(); + } + + @Override + public Parcelable onSaveInstanceState() { + Parcelable superState = super.onSaveInstanceState(); + SavedState ss = new SavedState(superState); + ss.onText = mTextOn; + ss.offText = mTextOff; + return ss; + } + + @Override + public void onRestoreInstanceState(Parcelable state) { + SavedState ss = (SavedState) state; + setText(ss.onText, ss.offText); + mRestoring = true; + super.onRestoreInstanceState(ss.getSuperState()); + mRestoring = false; + } + + /** + * Copied from compat library + * + * @param context context + * @param id id + * @return Drawable + */ + private Drawable getDrawableCompat(Context context, int id) { + final int version = Build.VERSION.SDK_INT; + if (version >= 21) { + return context.getDrawable(id); + } else { + //noinspection deprecation + return context.getResources().getDrawable(id); + } + } + + /** + * Copied from compat library + * + * @param context context + * @param id id + * @return ColorStateList + */ + private ColorStateList getColorStateListCompat(Context context, int id) { + final int version = Build.VERSION.SDK_INT; + if (version >= 23) { + return context.getColorStateList(id); + } else { + //noinspection deprecation + return context.getResources().getColorStateList(id); + } + } + + static class SavedState extends BaseSavedState { + CharSequence onText; + CharSequence offText; + + SavedState(Parcelable superState) { + super(superState); + } + + private SavedState(Parcel in) { + super(in); + onText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + offText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + } + + @Override + public void writeToParcel(Parcel out, int flags) { + super.writeToParcel(out, flags); + TextUtils.writeToParcel(onText, out, flags); + TextUtils.writeToParcel(offText, out, flags); + } + + public static final Parcelable.Creator CREATOR + = new Parcelable.Creator() { + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; + } + + private final class UnsetPressedState implements Runnable { + @Override + public void run() { + setPressed(false); + } + } } \ No newline at end of file diff --git a/SwitchButton/library/src/main/res/values/attrs.xml b/SwitchButton/library/src/main/res/values/attrs.xml index d7fae4e..8fe44a5 100644 --- a/SwitchButton/library/src/main/res/values/attrs.xml +++ b/SwitchButton/library/src/main/res/values/attrs.xml @@ -1,7 +1,7 @@ - + - + @@ -16,13 +16,14 @@ - + - - + + + \ No newline at end of file diff --git a/SwitchButton/library/src/main/res/values/styles.xml b/SwitchButton/library/src/main/res/values/styles.xml index 166f2ab..adf37f3 100644 --- a/SwitchButton/library/src/main/res/values/styles.xml +++ b/SwitchButton/library/src/main/res/values/styles.xml @@ -1,9 +1,9 @@ - + -