11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TriState Toggle Button
2 | A fully customizable and super-easy tri-state toggle button (switch button if you prefer) for Android, based on iOS look and feel.
3 | Can act with three independent states, or with two states like a standard checkbox, or with two states plus one undefined.
4 |
5 |
6 |
7 |
8 |
9 | * Out-of-the-box working 3-state toggle
10 | * Fully customizable and styleable
11 | * Can become a classic 2-state toggle returning booleans
12 | * Can become a 2.5-state toggle: on/off and an unselectable mid button
13 | * Can be enabled / disabled
14 | * Can be programmatically controlled
15 | * Works both with clicks and with swipes
16 |
17 |
18 |
19 | ### Setup (Gradle)
20 | In your project's build.gradle file:
21 |
22 | allprojects {
23 | repositories {
24 | ...
25 | maven { url "https://jitpack.io" }
26 | ...
27 | }
28 | }
29 |
30 | In your Application's or Module's build.gradle file:
31 |
32 | dependencies {
33 | ...
34 | compile 'com.github.BeppiMenozzi:TriStateToggleButton:1.1.4'
35 | ...
36 | }
37 |
38 | ### Setup (Eclipse)
39 | Whaaaaat?
40 |
41 | ### Minimal usage
42 | Layout:
43 |
44 | ...
45 | xmlns:app="http://schemas.android.com/apk/res-auto"
46 | ...
47 |
52 |
53 | Listener:
54 |
55 | ...
56 | TriStateToggleButton tstb_1 = (TriStateToggleButton) findViewById(R.id.tstb_1);
57 | tstb_1.setOnToggleChanged(new TriStateToggleButton.OnToggleChanged() {
58 | @Override
59 | public void onToggle(TriStateToggleButton.ToggleStatus toggleStatus, boolean booleanToggleStatus, int toggleIntValue) {
60 | switch (toggleStatus) {
61 | case off: break;
62 | case mid: break;
63 | case on: break;
64 | }
65 | }
66 | });
67 | ...
68 |
69 | Inside onToggle() you can use the ToggleStatus type values, or limit yourself to use booleans or integers (0, 1, 2) if you want it easy.
70 |
71 | To have a two-states toggle button:
72 |
73 |
79 |
80 | To have a two-states toggle button, with an undefined starting value:
81 |
82 |
89 |
90 | Browse the full example here:
91 | Example
92 |
93 | ### Attributes description
94 | List of attributes with description:
95 |
96 |
tbBorderWidth
Width of the border of the widget
97 |
tdOffBorderColor
Color of the width that appears with the button in state off. Used also for animations.
98 |
tbOffColor
Color of the background of the toggle when off
99 |
tbMidColor
Color of the background of the toggle with in mid position
100 |
tbOnColor
Color of the background of the toggle when on
101 |
tbSpotColor
Color of the handle of the toggle
102 |
tbAnimate
True for animation
103 |
tbDefaultStatus
Starting value for the toggle
104 |
tbIsMidSelectable
If false, the toggle becomes a standard two-states toggle, but can still assume the mid value if forced programmatically or set as default
105 |
tbSwipeSensitivityPixels
Number of pixels a swipe must travel to fire a toggle event. Default is 200. If set to zero, swipes are disabled
106 |
107 |
108 | ### New in 1.1.3
109 | * Fixed: error drawing toggle in mid position when visibility passed from GONE to VISIBLE
110 |
111 | ### New in 1.1.0
112 | * Added swipe gesture management together with normal click
113 | * Gradle update
114 | * More documentation
115 |
116 | ### New in 1.0.5
117 | * Fixed: added super in setEnabled()
118 |
119 | ### New in 1.0.4
120 | * Fixed: setting a boolean value programmatically sometimes didn't update status
121 |
122 | ### New in 1.0.3
123 | * Toggle now can set and return integer values (0, 1, 2)
124 | * Warning: onToggle() changed to include integer values
125 | * Added static functions to convert from/to booleans and integers to/from toggleStatus
126 |
127 | Used in
128 | -------
129 | * https://play.google.com/store/apps/details?id=it.beppi.bootwithlogo
130 |
131 | Credits
132 | -------
133 | This project is strongly based on (and contains parts of code of) the very beautiful Toggle Button by zcweng.
134 |
135 | Author
136 | -------
137 | * Beppi Menozzi
138 |
139 | License
140 | -------
141 | The MIT License (MIT)
142 |
143 | Copyright (c) 2016 Beppi Menozzi
144 |
145 | Permission is hereby granted, free of charge, to any person obtaining a copy
146 | of this software and associated documentation files (the "Software"), to deal
147 | in the Software without restriction, including without limitation the rights
148 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
149 | copies of the Software, and to permit persons to whom the Software is
150 | furnished to do so, subject to the following conditions:
151 |
152 | The above copyright notice and this permission notice shall be included in all
153 | copies or substantial portions of the Software.
154 |
155 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
156 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
157 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
158 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
159 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
160 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
161 | SOFTWARE.
162 |
163 |
164 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.2.3'
9 |
10 | // NOTE: Do not place your application dependencies here; they belong
11 | // in the individual module build.gradle files
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | jcenter()
18 | maven { url "https://jitpack.io" }
19 | }
20 | }
21 |
22 | task clean(type: Delete) {
23 | delete rootProject.buildDir
24 | }
25 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeppiMenozzi/TriStateToggleButton/3b21708073403039f8b3199805173a9b987bd95c/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Dec 28 10:00:20 PST 2015
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/images/tstb.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeppiMenozzi/TriStateToggleButton/3b21708073403039f8b3199805173a9b987bd95c/images/tstb.gif
--------------------------------------------------------------------------------
/images/tstb1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeppiMenozzi/TriStateToggleButton/3b21708073403039f8b3199805173a9b987bd95c/images/tstb1.gif
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':tristatetogglebutton_sample', ':tristatetogglebutton_library'
2 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.0"
6 |
7 | defaultConfig {
8 | minSdkVersion 9
9 | targetSdkVersion 25
10 | versionCode 8
11 | versionName "1.1.4"
12 |
13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
14 |
15 | }
16 | buildTypes {
17 | release {
18 | minifyEnabled false
19 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
20 | }
21 | }
22 | }
23 |
24 | dependencies {
25 | compile fileTree(dir: 'libs', include: ['*.jar'])
26 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
27 | exclude group: 'com.android.support', module: 'support-annotations'
28 | })
29 | compile 'com.android.support:appcompat-v7:25.0.1'
30 | testCompile 'junit:junit:4.12'
31 | }
32 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in F:\Android\android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/androidTest/java/it/beppi/tristatetogglebutton_library/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package it.beppi.tristatetogglebutton_library;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("it.beppi.tristatetogglebutton_library.test", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/AndroidSpringLooperFactory.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | */
10 |
11 | package com.facebook.rebound;
12 |
13 | import android.annotation.TargetApi;
14 | import android.os.Build;
15 | import android.os.Handler;
16 | import android.os.SystemClock;
17 | import android.view.Choreographer;
18 |
19 | /**
20 | * Android version of the spring looper that uses the most appropriate frame callback mechanism
21 | * available. It uses Android's {@link Choreographer} when available, otherwise it uses a
22 | * {@link Handler}.
23 | */
24 | abstract class AndroidSpringLooperFactory {
25 |
26 | /**
27 | * Create an Android {@link com.facebook.rebound.SpringLooper} for the detected Android platform.
28 | * @return a SpringLooper
29 | */
30 | public static SpringLooper createSpringLooper() {
31 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
32 | return ChoreographerAndroidSpringLooper.create();
33 | } else {
34 | return LegacyAndroidSpringLooper.create();
35 | }
36 | }
37 |
38 | /**
39 | * The base implementation of the Android spring looper, using a {@link Handler} for the
40 | * frame callbacks.
41 | */
42 | private static class LegacyAndroidSpringLooper extends SpringLooper {
43 |
44 | private final Handler mHandler;
45 | private final Runnable mLooperRunnable;
46 | private boolean mStarted;
47 | private long mLastTime;
48 |
49 | /**
50 | * @return an Android spring looper using a new {@link Handler} instance
51 | */
52 | public static SpringLooper create() {
53 | return new LegacyAndroidSpringLooper(new Handler());
54 | }
55 |
56 | public LegacyAndroidSpringLooper(Handler handler) {
57 | mHandler = handler;
58 | mLooperRunnable = new Runnable() {
59 | @Override
60 | public void run() {
61 | if (!mStarted || mSpringSystem == null) {
62 | return;
63 | }
64 | long currentTime = SystemClock.uptimeMillis();
65 | mSpringSystem.loop(currentTime - mLastTime);
66 | mHandler.post(mLooperRunnable);
67 | }
68 | };
69 | }
70 |
71 | @Override
72 | public void start() {
73 | if (mStarted) {
74 | return;
75 | }
76 | mStarted = true;
77 | mLastTime = SystemClock.uptimeMillis();
78 | mHandler.removeCallbacks(mLooperRunnable);
79 | mHandler.post(mLooperRunnable);
80 | }
81 |
82 | @Override
83 | public void stop() {
84 | mStarted = false;
85 | mHandler.removeCallbacks(mLooperRunnable);
86 | }
87 | }
88 |
89 | /**
90 | * The Jelly Bean and up implementation of the spring looper that uses Android's
91 | * {@link Choreographer} instead of a {@link Handler}
92 | */
93 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
94 | private static class ChoreographerAndroidSpringLooper extends SpringLooper {
95 |
96 | private final Choreographer mChoreographer;
97 | private final Choreographer.FrameCallback mFrameCallback;
98 | private boolean mStarted;
99 | private long mLastTime;
100 |
101 | /**
102 | * @return an Android spring choreographer using the system {@link Choreographer}
103 | */
104 | public static ChoreographerAndroidSpringLooper create() {
105 | return new ChoreographerAndroidSpringLooper(Choreographer.getInstance());
106 | }
107 |
108 | public ChoreographerAndroidSpringLooper(Choreographer choreographer) {
109 | mChoreographer = choreographer;
110 | mFrameCallback = new Choreographer.FrameCallback() {
111 | @Override
112 | public void doFrame(long frameTimeNanos) {
113 | if (!mStarted || mSpringSystem == null) {
114 | return;
115 | }
116 | long currentTime = SystemClock.uptimeMillis();
117 | mSpringSystem.loop(currentTime - mLastTime);
118 | mLastTime = currentTime;
119 | mChoreographer.postFrameCallback(mFrameCallback);
120 | }
121 | };
122 | }
123 |
124 | @Override
125 | public void start() {
126 | if (mStarted) {
127 | return;
128 | }
129 | mStarted = true;
130 | mLastTime = SystemClock.uptimeMillis();
131 | mChoreographer.removeFrameCallback(mFrameCallback);
132 | mChoreographer.postFrameCallback(mFrameCallback);
133 | }
134 |
135 | @Override
136 | public void stop() {
137 | mStarted = false;
138 | mChoreographer.removeFrameCallback(mFrameCallback);
139 | }
140 | }
141 | }
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/BaseSpringSystem.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | */
10 |
11 | package com.facebook.rebound;
12 |
13 | import java.util.ArrayList;
14 | import java.util.Collection;
15 | import java.util.Collections;
16 | import java.util.HashMap;
17 | import java.util.List;
18 | import java.util.Map;
19 | import java.util.Set;
20 | import java.util.concurrent.CopyOnWriteArraySet;
21 |
22 | /**
23 | * BaseSpringSystem maintains the set of springs within an Application context. It is responsible for
24 | * Running the spring integration loop and maintains a registry of all the Springs it solves for.
25 | * In addition to listening to physics events on the individual Springs in the system, listeners
26 | * can be added to the BaseSpringSystem itself to provide pre and post integration setup.
27 | */
28 | public class BaseSpringSystem {
29 |
30 | private final Map mSpringRegistry = new HashMap();
31 | private final Set mActiveSprings = new CopyOnWriteArraySet();
32 | private final SpringLooper mSpringLooper;
33 | private final CopyOnWriteArraySet mListeners = new CopyOnWriteArraySet();
34 | private boolean mIdle = true;
35 |
36 | /**
37 | * create a new BaseSpringSystem
38 | * @param springLooper parameterized springLooper to allow testability of the
39 | * physics loop
40 | */
41 | public BaseSpringSystem(SpringLooper springLooper) {
42 | if (springLooper == null) {
43 | throw new IllegalArgumentException("springLooper is required");
44 | }
45 | mSpringLooper = springLooper;
46 | mSpringLooper.setSpringSystem(this);
47 | }
48 |
49 | /**
50 | * check if the system is idle
51 | * @return is the system idle
52 | */
53 | public boolean getIsIdle() {
54 | return mIdle;
55 | }
56 |
57 | /**
58 | * create a spring with a random uuid for its name.
59 | * @return the spring
60 | */
61 | public Spring createSpring() {
62 | Spring spring = new Spring(this);
63 | registerSpring(spring);
64 | return spring;
65 | }
66 |
67 | /**
68 | * get a spring by name
69 | * @param id id of the spring to retrieve
70 | * @return Spring with the specified key
71 | */
72 | public Spring getSpringById(String id) {
73 | if (id == null) {
74 | throw new IllegalArgumentException("id is required");
75 | }
76 | return mSpringRegistry.get(id);
77 | }
78 |
79 | /**
80 | * return all the springs in the simulator
81 | * @return all the springs
82 | */
83 | public List getAllSprings() {
84 | Collection collection = mSpringRegistry.values();
85 | List list;
86 | if (collection instanceof List) {
87 | list = (List)collection;
88 | } else {
89 | list = new ArrayList(collection);
90 | }
91 | return Collections.unmodifiableList(list);
92 | }
93 |
94 | /**
95 | * Registers a Spring to this BaseSpringSystem so it can be iterated if active.
96 | * @param spring the Spring to register
97 | */
98 | void registerSpring(Spring spring) {
99 | if (spring == null) {
100 | throw new IllegalArgumentException("spring is required");
101 | }
102 | if (mSpringRegistry.containsKey(spring.getId())) {
103 | throw new IllegalArgumentException("spring is already registered"); }
104 | mSpringRegistry.put(spring.getId(), spring);
105 | }
106 |
107 | /**
108 | * Deregisters a Spring from this BaseSpringSystem, so it won't be iterated anymore. The Spring should
109 | * not be used anymore after doing this.
110 | *
111 | * @param spring the Spring to deregister
112 | */
113 | void deregisterSpring(Spring spring) {
114 | if (spring == null) {
115 | throw new IllegalArgumentException("spring is required");
116 | }
117 | mActiveSprings.remove(spring);
118 | mSpringRegistry.remove(spring.getId());
119 | }
120 |
121 | /**
122 | * update the springs in the system
123 | * @param deltaTime delta since last update in millis
124 | */
125 | void advance(double deltaTime) {
126 | for (Spring spring : mActiveSprings) {
127 | // advance time in seconds
128 | if (spring.systemShouldAdvance()) {
129 | spring.advance(deltaTime / 1000.0);
130 | } else {
131 | mActiveSprings.remove(spring);
132 | }
133 | }
134 | }
135 |
136 | /**
137 | * loop the system until idle
138 | * @param ellapsedMillis = =
139 | */
140 | public void loop(double ellapsedMillis) {
141 | for (SpringSystemListener listener : mListeners) {
142 | listener.onBeforeIntegrate(this);
143 | }
144 | advance(ellapsedMillis);
145 | if (mActiveSprings.isEmpty()) {
146 | mIdle = true;
147 | }
148 | for (SpringSystemListener listener : mListeners) {
149 | listener.onAfterIntegrate(this);
150 | }
151 | if (mIdle) {
152 | mSpringLooper.stop();
153 | }
154 | }
155 |
156 | /**
157 | * This is used internally by the {@link Spring}s created by this {@link BaseSpringSystem} to notify
158 | * it has reached a state where it needs to be iterated. This will add the spring to the list of
159 | * active springs on this system and start the iteration if the system was idle before this call.
160 | * @param springId the id of the Spring to be activated
161 | */
162 | void activateSpring(String springId) {
163 | Spring spring = mSpringRegistry.get(springId);
164 | if (spring == null) {
165 | throw new IllegalArgumentException("springId " + springId + " does not reference a registered spring");
166 | }
167 | mActiveSprings.add(spring);
168 | if (getIsIdle()) {
169 | mIdle = false;
170 | mSpringLooper.start();
171 | }
172 | }
173 |
174 | /** listeners
175 | * @param newListener = =
176 | **/
177 | public void addListener(SpringSystemListener newListener) {
178 | if (newListener == null) {
179 | throw new IllegalArgumentException("newListener is required");
180 | }
181 | mListeners.add(newListener);
182 | }
183 |
184 | public void removeListener(SpringSystemListener listenerToRemove) {
185 | if (listenerToRemove == null) {
186 | throw new IllegalArgumentException("listenerToRemove is required");
187 | }
188 | mListeners.remove(listenerToRemove);
189 | }
190 |
191 | public void removeAllListeners() {
192 | mListeners.clear();
193 | }
194 | }
195 |
196 |
197 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/OrigamiValueConverter.java:
--------------------------------------------------------------------------------
1 | package com.facebook.rebound;
2 |
3 | public class OrigamiValueConverter {
4 |
5 | public static double tensionFromOrigamiValue(double oValue) {
6 | return oValue == 0 ? 0 : (oValue - 30.0) * 3.62 + 194.0;
7 | }
8 |
9 | public static double origamiValueFromTension(double tension) {
10 | return tension == 0 ? 0 : (tension - 194.0) / 3.62 + 30.0;
11 | }
12 |
13 | public static double frictionFromOrigamiValue(double oValue) {
14 | return oValue == 0 ? 0 : (oValue - 8.0) * 3.0 + 25.0;
15 | }
16 |
17 | public static double origamiValueFromFriction(double friction) {
18 | return friction == 0 ? 0 : (friction - 25.0) / 3.0 + 8.0;
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/SimpleSpringListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | */
10 |
11 | package com.facebook.rebound;
12 |
13 | public class SimpleSpringListener implements SpringListener {
14 | @Override
15 | public void onSpringUpdate(Spring spring) {
16 | }
17 |
18 | @Override
19 | public void onSpringAtRest(Spring spring) {
20 | }
21 |
22 | @Override
23 | public void onSpringActivate(Spring spring) {
24 | }
25 |
26 | @Override
27 | public void onSpringEndStateChange(Spring spring) {
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/Spring.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | */
10 |
11 | package com.facebook.rebound;
12 |
13 | import java.util.concurrent.CopyOnWriteArraySet;
14 |
15 | /**
16 | * Classical spring implementing Hooke's law with configurable friction and tension.
17 | */
18 | public class Spring {
19 |
20 | // unique incrementer id for springs
21 | private static int ID = 0;
22 |
23 | // maximum amount of time to simulate per physics iteration in seconds (4 frames at 60 FPS)
24 | private static final double MAX_DELTA_TIME_SEC = 0.064;
25 | // fixed timestep to use in the physics solver in seconds
26 | private static final double SOLVER_TIMESTEP_SEC = 0.001;
27 | private SpringConfig mSpringConfig;
28 | private boolean mOvershootClampingEnabled;
29 |
30 | // storage for the current and prior physics state while integration is occurring
31 | private static class PhysicsState {
32 | double position;
33 | double velocity;
34 | }
35 |
36 | // unique id for the spring in the system
37 | private final String mId;
38 | // all physics simulation objects are final and reused in each processing pass
39 | private final PhysicsState mCurrentState = new PhysicsState();
40 | private final PhysicsState mPreviousState = new PhysicsState();
41 | private final PhysicsState mTempState = new PhysicsState();
42 | private double mStartValue;
43 | private double mEndValue;
44 | private boolean mWasAtRest = true;
45 | // thresholds for determining when the spring is at rest
46 | private double mRestSpeedThreshold = 0.005;
47 | private double mDisplacementFromRestThreshold = 0.005;
48 | private CopyOnWriteArraySet mListeners = new CopyOnWriteArraySet();
49 | private double mTimeAccumulator = 0;
50 |
51 | private final BaseSpringSystem mSpringSystem;
52 |
53 | /**
54 | * create a new spring
55 | */
56 | Spring(BaseSpringSystem springSystem) {
57 | if (springSystem == null) {
58 | throw new IllegalArgumentException("Spring cannot be created outside of a BaseSpringSystem");
59 | }
60 | mSpringSystem = springSystem;
61 | mId = "spring:" + ID++;
62 | setSpringConfig(SpringConfig.defaultConfig);
63 | }
64 |
65 | /**
66 | * Destroys this Spring, meaning that it will be deregistered from its BaseSpringSystem so it won't be
67 | * iterated anymore and will clear its set of listeners. Do not use the Spring after calling this,
68 | * doing so may just cause an exception to be thrown.
69 | */
70 | public void destroy() {
71 | mListeners.clear();
72 | mSpringSystem.deregisterSpring(this);
73 | }
74 |
75 | /**
76 | * get the unique id for this spring
77 | * @return the unique id
78 | */
79 | public String getId() {
80 | return mId;
81 | }
82 |
83 | /**
84 | * set the config class
85 | * @param springConfig config class for the spring
86 | * @return this Spring instance for chaining
87 | */
88 | public Spring setSpringConfig(SpringConfig springConfig) {
89 | if (springConfig == null) {
90 | throw new IllegalArgumentException("springConfig is required");
91 | }
92 | mSpringConfig = springConfig;
93 | return this;
94 | }
95 |
96 | /**
97 | * retrieve the spring config for this spring
98 | * @return the SpringConfig applied to this spring
99 | */
100 | public SpringConfig getSpringConfig() {
101 | return mSpringConfig;
102 | }
103 |
104 | /**
105 | * Set the displaced value to determine the displacement for the spring from the rest value.
106 | * This value is retained and used to calculate the displacement ratio.
107 | * This also updates the start value of the Spring.
108 | * @param currentValue the new start and current value for the spring
109 | * @return the spring for chaining
110 | */
111 | public Spring setCurrentValue(double currentValue) {
112 | mStartValue = currentValue;
113 | mCurrentState.position = currentValue;
114 | mSpringSystem.activateSpring(this.getId());
115 | for (SpringListener listener : mListeners) {
116 | listener.onSpringUpdate(this);
117 | }
118 | return this;
119 | }
120 |
121 | /**
122 | * Get the displacement value from the last time setCurrentValue was called.
123 | * @return displacement value
124 | */
125 | public double getStartValue() {
126 | return mStartValue;
127 | }
128 |
129 | /**
130 | * Get the current
131 | * @return current value
132 | */
133 | public double getCurrentValue() {
134 | return mCurrentState.position;
135 | }
136 |
137 | /**
138 | * get the displacement of the springs current value from its rest value.
139 | * @return the distance displaced by
140 | */
141 | public double getCurrentDisplacementDistance() {
142 | return getDisplacementDistanceForState(mCurrentState);
143 | }
144 |
145 | /**
146 | * get the displacement from rest for a given physics state
147 | * @param state the state to measure from
148 | * @return the distance displaced by
149 | */
150 | private double getDisplacementDistanceForState(PhysicsState state) {
151 | return Math.abs(mEndValue - state.position);
152 | }
153 |
154 | /**
155 | * set the rest value to determine the displacement for the spring
156 | * @param endValue the endValue for the spring
157 | * @return the spring for chaining
158 | */
159 | public Spring setEndValue(double endValue) {
160 | if (mEndValue == endValue && isAtRest()) {
161 | return this;
162 | }
163 | mStartValue = getCurrentValue();
164 | mEndValue = endValue;
165 | mSpringSystem.activateSpring(this.getId());
166 | for (SpringListener listener : mListeners) {
167 | listener.onSpringEndStateChange(this);
168 | }
169 | return this;
170 | }
171 |
172 | /**
173 | * get the rest value used for determining the displacement of the spring
174 | * @return the rest value for the spring
175 | */
176 | public double getEndValue() {
177 | return mEndValue;
178 | }
179 |
180 | /**
181 | * set the velocity on the spring in pixels per second
182 | * @param velocity = =
183 | * @return the spring for chaining
184 | */
185 | public Spring setVelocity(double velocity) {
186 | mCurrentState.velocity = velocity;
187 | mSpringSystem.activateSpring(this.getId());
188 | return this;
189 | }
190 |
191 | /**
192 | * get the velocity of the spring
193 | * @return the current velocity
194 | */
195 | public double getVelocity() {
196 | return mCurrentState.velocity;
197 | }
198 |
199 | /**
200 | * Sets the speed at which the spring should be considered at rest.
201 | * @param restSpeedThreshold speed pixels per second
202 | * @return the spring for chaining
203 | */
204 | public Spring setRestSpeedThreshold(double restSpeedThreshold) {
205 | mRestSpeedThreshold = restSpeedThreshold;
206 | return this;
207 | }
208 |
209 | /**
210 | * Returns the speed at which the spring should be considered at rest in pixels per second
211 | * @return speed in pixels per second
212 | */
213 | public double getRestSpeedThreshold() {
214 | return mRestSpeedThreshold;
215 | }
216 |
217 | /**
218 | * set the threshold of displacement from rest below which the spring should be considered at rest
219 | * @param displacementFromRestThreshold displacement to consider resting below
220 | * @return the spring for chaining
221 | */
222 | public Spring setRestDisplacementThreshold(double displacementFromRestThreshold) {
223 | mDisplacementFromRestThreshold = displacementFromRestThreshold;
224 | return this;
225 | }
226 |
227 | /**
228 | * get the threshold of displacement from rest below which the spring should be considered at rest
229 | * @return displacement to consider resting below
230 | */
231 | public double getRestDisplacementThreshold() {
232 | return mDisplacementFromRestThreshold;
233 | }
234 |
235 | /**
236 | * Force the spring to clamp at its end value to avoid overshooting the target value.
237 | * @param overshootClampingEnabled whether or not to enable overshoot clamping
238 | * @return the spring for chaining
239 | */
240 | public Spring setOvershootClampingEnabled(boolean overshootClampingEnabled) {
241 | mOvershootClampingEnabled = overshootClampingEnabled;
242 | return this;
243 | }
244 |
245 | /**
246 | * Check if overshoot clamping is enabled.
247 | * @return is overshoot clamping enabled
248 | */
249 | public boolean isOvershootClampingEnabled() {
250 | return mOvershootClampingEnabled;
251 | }
252 |
253 | /**
254 | * Check if the spring is overshooting beyond its target.
255 | * @return true if the spring is overshooting its target
256 | */
257 | public boolean isOvershooting() {
258 | return (mStartValue < mEndValue && getCurrentValue() > mEndValue) ||
259 | (mStartValue > mEndValue && getCurrentValue() < mEndValue);
260 | }
261 |
262 | /**
263 | * advance the physics simulation in SOLVER_TIMESTEP_SEC sized chunks to fulfill the required
264 | * realTimeDelta.
265 | * The math is inlined inside the loop since it made a huge performance impact when there are
266 | * several springs being advanced.
267 | * @param time clock time
268 | * @param realDeltaTime clock drift
269 | */
270 | void advance(double realDeltaTime) {
271 |
272 | boolean isAtRest = isAtRest();
273 |
274 | if (isAtRest && mWasAtRest) {
275 | /* begin debug
276 | Log.d(TAG, "bailing out because we are at rest:" + getName());
277 | end debug */
278 | return;
279 | }
280 |
281 | // clamp the amount of realTime to simulate to avoid stuttering in the UI. We should be able
282 | // to catch up in a subsequent advance if necessary.
283 | double adjustedDeltaTime = realDeltaTime;
284 | if (realDeltaTime > MAX_DELTA_TIME_SEC) {
285 | adjustedDeltaTime = MAX_DELTA_TIME_SEC;
286 | }
287 |
288 | /* begin debug
289 | long startTime = System.currentTimeMillis();
290 | int iterations = 0;
291 | end debug */
292 |
293 | mTimeAccumulator += adjustedDeltaTime;
294 |
295 | double tension = mSpringConfig.tension;
296 | double friction = mSpringConfig.friction;
297 |
298 | double position = mCurrentState.position;
299 | double velocity = mCurrentState.velocity;
300 | double tempPosition = mTempState.position;
301 | double tempVelocity = mTempState.velocity;
302 |
303 | double aVelocity, aAcceleration;
304 | double bVelocity, bAcceleration;
305 | double cVelocity, cAcceleration;
306 | double dVelocity, dAcceleration;
307 |
308 | double dxdt, dvdt;
309 |
310 | // iterate over the true time
311 | while (mTimeAccumulator >= SOLVER_TIMESTEP_SEC) {
312 | /* begin debug
313 | iterations++;
314 | end debug */
315 | mTimeAccumulator -= SOLVER_TIMESTEP_SEC;
316 |
317 | if (mTimeAccumulator < SOLVER_TIMESTEP_SEC) {
318 | // This will be the last iteration. Remember the previous state in case we need to
319 | // interpolate
320 | mPreviousState.position = position;
321 | mPreviousState.velocity = velocity;
322 | }
323 |
324 | // Perform an RK4 integration to provide better detection of the acceleration curve via
325 | // sampling of Euler integrations at 4 intervals feeding each derivative into the calculation
326 | // of the next and taking a weighted sum of the 4 derivatives as the final output.
327 |
328 | // This math was inlined since it made for big performance improvements when advancing several
329 | // springs in one pass of the BaseSpringSystem.
330 |
331 | // The initial derivative is based on the current velocity and the calculated acceleration
332 | aVelocity = velocity;
333 | aAcceleration = (tension * (mEndValue - tempPosition)) - friction * velocity;
334 |
335 | // Calculate the next derivatives starting with the last derivative and integrating over the
336 | // timestep
337 | tempPosition = position + aVelocity * SOLVER_TIMESTEP_SEC * 0.5;
338 | tempVelocity = velocity + aAcceleration * SOLVER_TIMESTEP_SEC * 0.5;
339 | bVelocity = tempVelocity;
340 | bAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity;
341 |
342 | tempPosition = position + bVelocity * SOLVER_TIMESTEP_SEC * 0.5;
343 | tempVelocity = velocity + bAcceleration * SOLVER_TIMESTEP_SEC * 0.5;
344 | cVelocity = tempVelocity;
345 | cAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity;
346 |
347 | tempPosition = position + cVelocity * SOLVER_TIMESTEP_SEC;
348 | tempVelocity = velocity + cAcceleration * SOLVER_TIMESTEP_SEC;
349 | dVelocity = tempVelocity;
350 | dAcceleration = (tension * (mEndValue - tempPosition)) - friction * tempVelocity;
351 |
352 | // Take the weighted sum of the 4 derivatives as the final output.
353 | dxdt = 1.0/6.0 * (aVelocity + 2.0 * (bVelocity + cVelocity) + dVelocity);
354 | dvdt = 1.0/6.0 * (aAcceleration + 2.0 * (bAcceleration + cAcceleration) + dAcceleration);
355 |
356 | position += dxdt * SOLVER_TIMESTEP_SEC;
357 | velocity += dvdt * SOLVER_TIMESTEP_SEC;
358 | }
359 |
360 | mTempState.position = tempPosition;
361 | mTempState.velocity = tempVelocity;
362 |
363 | mCurrentState.position = position;
364 | mCurrentState.velocity = velocity;
365 |
366 | if (mTimeAccumulator > 0) {
367 | interpolate(mTimeAccumulator / SOLVER_TIMESTEP_SEC);
368 | }
369 |
370 | // End the spring immediately if it is overshooting and overshoot clamping is enabled.
371 | // Also make sure that if the spring was considered within a resting threshold that it's now
372 | // snapped to its end value.
373 | if (isAtRest() || (mOvershootClampingEnabled && isOvershooting())) {
374 | // Don't call setCurrentValue because that forces a call to onSpringUpdate
375 | mStartValue = mEndValue;
376 | mCurrentState.position = mEndValue;
377 | setVelocity(0);
378 | isAtRest = true;
379 | }
380 |
381 | /* begin debug
382 | long endTime = System.currentTimeMillis();
383 | long elapsedMillis = endTime - startTime;
384 | Log.d(TAG,
385 | "iterations:" + iterations +
386 | " iterationTime:" + elapsedMillis +
387 | " position:" + mCurrentState.position +
388 | " velocity:" + mCurrentState.velocity +
389 | " realDeltaTime:" + realDeltaTime +
390 | " adjustedDeltaTime:" + adjustedDeltaTime +
391 | " isAtRest:" + isAtRest +
392 | " wasAtRest:" + mWasAtRest);
393 | end debug */
394 |
395 | // NB: do these checks outside the loop so all listeners are properly notified of the state
396 | // transition
397 | boolean notifyActivate = false;
398 | if (mWasAtRest) {
399 | mWasAtRest = false;
400 | notifyActivate = true;
401 | }
402 | boolean notifyAtRest = false;
403 | if (isAtRest) {
404 | mWasAtRest = true;
405 | notifyAtRest = true;
406 | }
407 | for (SpringListener listener : mListeners) {
408 | // starting to move
409 | if (notifyActivate) {
410 | listener.onSpringActivate(this);
411 | }
412 |
413 | // updated
414 | listener.onSpringUpdate(this);
415 |
416 | // coming to rest
417 | if (notifyAtRest) {
418 | listener.onSpringAtRest(this);
419 | }
420 | }
421 | }
422 |
423 | /**
424 | * Check if this spring should be advanced by the system. * The rule is if the spring is
425 | * currently at rest and it was at rest in the previous advance, the system can skip this spring
426 | * @return should the system process this spring
427 | */
428 | public boolean systemShouldAdvance() {
429 | return !isAtRest() || !wasAtRest();
430 | }
431 |
432 | /**
433 | * Check if the spring was at rest in the prior iteration. This is used for ensuring the ending
434 | * callbacks are fired as the spring comes to a rest.
435 | * @return true if the spring was at rest in the prior iteration
436 | */
437 | public boolean wasAtRest() {
438 | return mWasAtRest;
439 | }
440 |
441 | /**
442 | * check if the current state is at rest
443 | * @return is the spring at rest
444 | */
445 | public boolean isAtRest() {
446 | return Math.abs(mCurrentState.velocity) <= mRestSpeedThreshold &&
447 | getDisplacementDistanceForState(mCurrentState) <= mDisplacementFromRestThreshold;
448 | }
449 |
450 | /**
451 | * Set the spring to be at rest by making its end value equal to its current value and setting
452 | * velocity to 0.
453 | * @return = =
454 | */
455 | public Spring setAtRest() {
456 | mEndValue = mCurrentState.position;
457 | mTempState.position = mCurrentState.position;
458 | mCurrentState.velocity = 0;
459 | return this;
460 | }
461 |
462 | /**
463 | * linear interpolation between the previous and current physics state based on the amount of
464 | * timestep remaining after processing the rendering delta time in timestep sized chunks.
465 | * @param alpha from 0 to 1, where 0 is the previous state, 1 is the current state
466 | */
467 | private void interpolate(double alpha) {
468 | mCurrentState.position = mCurrentState.position * alpha + mPreviousState.position *(1-alpha);
469 | mCurrentState.velocity = mCurrentState.velocity * alpha + mPreviousState.velocity *(1-alpha);
470 | }
471 |
472 | /** listeners **/
473 |
474 | /**
475 | * add a listener
476 | * @param newListener to add
477 | * @return the spring for chaining
478 | */
479 | public Spring addListener(SpringListener newListener) {
480 | if (newListener == null) {
481 | throw new IllegalArgumentException("newListener is required");
482 | }
483 | mListeners.add(newListener);
484 | return this;
485 | }
486 |
487 | /**
488 | * remove a listener
489 | * @param listenerToRemove to remove
490 | * @return the spring for chaining
491 | */
492 | public Spring removeListener(SpringListener listenerToRemove) {
493 | if (listenerToRemove == null) {
494 | throw new IllegalArgumentException("listenerToRemove is required");
495 | }
496 | mListeners.remove(listenerToRemove);
497 | return this;
498 | }
499 |
500 | /**
501 | * remove all of the listeners
502 | * @return the spring for chaining
503 | */
504 | public Spring removeAllListeners() {
505 | mListeners.clear();
506 | return this;
507 | }
508 |
509 | /**
510 | * This method checks to see that the current spring displacement value is equal to the input,
511 | * accounting for the spring's rest displacement threshold.
512 | * @param value The value to compare the spring value to
513 | * @return Whether the displacement value from the spring is within the bounds of the compare
514 | * value, accounting for threshold
515 | */
516 | public boolean currentValueIsApproximately(double value) {
517 | return Math.abs(getCurrentValue() - value) <= getRestDisplacementThreshold();
518 | }
519 |
520 | }
521 |
522 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/SpringConfig.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | */
10 |
11 | package com.facebook.rebound;
12 |
13 | /**
14 | * Data structure for storing spring configuration.
15 | */
16 | public class SpringConfig {
17 | public double friction;
18 | public double tension;
19 |
20 | public static SpringConfig defaultConfig = SpringConfig.fromOrigamiTensionAndFriction(40, 7);
21 |
22 | /**
23 | * constructor for the SpringConfig
24 | * @param tension tension value for the SpringConfig
25 | * @param friction friction value for the SpringConfig
26 | */
27 | public SpringConfig(double tension, double friction) {
28 | this.tension = tension;
29 | this.friction = friction;
30 | }
31 |
32 | /**
33 | * A helper to make creating a SpringConfig easier with values mapping to the Origami values.
34 | * @param qcTension tension as defined in the Quartz Composition
35 | * @param qcFriction friction as defined in the Quartz Composition
36 | * @return a SpringConfig that maps to these values
37 | */
38 | public static SpringConfig fromOrigamiTensionAndFriction(double qcTension, double qcFriction) {
39 | return new SpringConfig(
40 | OrigamiValueConverter.tensionFromOrigamiValue(qcTension),
41 | OrigamiValueConverter.frictionFromOrigamiValue(qcFriction)
42 | );
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/SpringConfigRegistry.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | */
10 |
11 | package com.facebook.rebound;
12 |
13 | import java.util.Collections;
14 | import java.util.HashMap;
15 | import java.util.Map;
16 |
17 | /**
18 | * class for maintaining a registry of all spring configs
19 | */
20 | public class SpringConfigRegistry {
21 |
22 | private static final SpringConfigRegistry INSTANCE = new SpringConfigRegistry(true);
23 |
24 | public static SpringConfigRegistry getInstance() {
25 | return INSTANCE;
26 | }
27 |
28 | private final Map mSpringConfigMap;
29 |
30 | /**
31 | * constructor for the SpringConfigRegistry
32 | */
33 | SpringConfigRegistry(boolean includeDefaultEntry) {
34 | mSpringConfigMap = new HashMap();
35 | if (includeDefaultEntry) {
36 | addSpringConfig(SpringConfig.defaultConfig, "default config");
37 | }
38 | }
39 |
40 | /**
41 | * add a SpringConfig to the registry
42 | *
43 | * @param springConfig SpringConfig to add to the registry
44 | * @param configName name to give the SpringConfig in the registry
45 | * @return true if the SpringConfig was added, false if a config with that name is already
46 | * present.
47 | */
48 | public boolean addSpringConfig(SpringConfig springConfig, String configName) {
49 | if (springConfig == null) {
50 | throw new IllegalArgumentException("springConfig is required");
51 | }
52 | if (configName == null) {
53 | throw new IllegalArgumentException("configName is required");
54 | }
55 | if (mSpringConfigMap.containsKey(springConfig)) {
56 | return false;
57 | }
58 | mSpringConfigMap.put(springConfig, configName);
59 | return true;
60 | }
61 |
62 | /**
63 | * remove a specific SpringConfig from the registry
64 | * @param springConfig the of the SpringConfig to remove
65 | * @return true if the SpringConfig was removed, false if it was not present.
66 | */
67 | public boolean removeSpringConfig(SpringConfig springConfig) {
68 | if (springConfig == null) {
69 | throw new IllegalArgumentException("springConfig is required");
70 | }
71 | return mSpringConfigMap.remove(springConfig) != null;
72 | }
73 |
74 | /**
75 | * retrieve all SpringConfig in the registry
76 | * @return a list of all SpringConfig
77 | */
78 | public Map getAllSpringConfig() {
79 | return Collections.unmodifiableMap(mSpringConfigMap);
80 | }
81 |
82 | /**
83 | * clear all SpringConfig in the registry
84 | */
85 | public void removeAllSpringConfig() {
86 | mSpringConfigMap.clear();
87 | }
88 | }
89 |
90 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/SpringListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | */
10 |
11 | package com.facebook.rebound;
12 |
13 | public interface SpringListener {
14 |
15 | /**
16 | * called whenever the spring is updated
17 | * @param spring the Spring sending the update
18 | */
19 | void onSpringUpdate(Spring spring);
20 |
21 | /**
22 | * called whenever the spring achieves a resting state
23 | * @param spring the spring that's now resting
24 | */
25 | void onSpringAtRest(Spring spring);
26 |
27 | /**
28 | * called whenever the spring leaves its resting state
29 | * @param spring the spring that has left its resting state
30 | */
31 | void onSpringActivate(Spring spring);
32 |
33 | /**
34 | * called whenever the spring notifies of displacement state changes
35 | * @param spring the spring whose end state has changed
36 | */
37 | void onSpringEndStateChange(Spring spring);
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/SpringLooper.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | */
10 |
11 | package com.facebook.rebound;
12 |
13 | /**
14 | * The spring looper is an interface for implementing platform-dependent run loops.
15 | */
16 | public abstract class SpringLooper {
17 |
18 | protected BaseSpringSystem mSpringSystem;
19 |
20 | /**
21 | * Set the BaseSpringSystem that the SpringLooper will call back to.
22 | * @param springSystem the spring system to call loop on.
23 | */
24 | public void setSpringSystem(BaseSpringSystem springSystem) {
25 | mSpringSystem = springSystem;
26 | }
27 |
28 | /**
29 | * The BaseSpringSystem has requested that the looper begins running this {@link Runnable}
30 | * on every frame. The {@link Runnable} will continue running on every frame until
31 | * {@link #stop()} is called.
32 | * If an existing {@link Runnable} had been started on this looper, it will be cancelled.
33 | */
34 | public abstract void start();
35 |
36 | /**
37 | * The looper will no longer run the {@link Runnable}.
38 | */
39 | public abstract void stop();
40 | }
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/SpringSystem.java:
--------------------------------------------------------------------------------
1 | package com.facebook.rebound;
2 |
3 | /**
4 | * This is a wrapper for BaseSpringSystem that provides the convenience of automatically providing
5 | * the AndroidSpringLooper dependency in {@link SpringSystem#create}.
6 | */
7 | public class SpringSystem extends BaseSpringSystem {
8 |
9 | /**
10 | * Create a new SpringSystem providing the appropriate constructor parameters to work properly
11 | * in an Android environment.
12 | * @return the SpringSystem
13 | */
14 | public static SpringSystem create() {
15 | return new SpringSystem(AndroidSpringLooperFactory.createSpringLooper());
16 | }
17 |
18 | private SpringSystem(SpringLooper springLooper) {
19 | super(springLooper);
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/SpringSystemListener.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | */
10 |
11 | package com.facebook.rebound;
12 |
13 | /**
14 | * SpringSystemListener provides an interface for listening to events before and after each Physics
15 | * solving loop the BaseSpringSystem runs.
16 | */
17 | public interface SpringSystemListener {
18 |
19 | /**
20 | * Runs before each pass through the physics integration loop providing an opportunity to do any
21 | * setup or alterations to the Physics state before integrating.
22 | * @param springSystem the BaseSpringSystem listened to
23 | */
24 | void onBeforeIntegrate(BaseSpringSystem springSystem);
25 |
26 | /**
27 | * Runs after each pass through the physics integration loop providing an opportunity to do any
28 | * setup or alterations to the Physics state after integrating.
29 | * @param springSystem the BaseSpringSystem listened to
30 | */
31 | void onAfterIntegrate(BaseSpringSystem springSystem);
32 | }
33 |
34 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/SpringUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | */
10 |
11 | package com.facebook.rebound;
12 |
13 | public class SpringUtil {
14 |
15 | /**
16 | * Map a value within a given range to another range.
17 | * @param value the value to map
18 | * @param fromLow the low end of the range the value is within
19 | * @param fromHigh the high end of the range the value is within
20 | * @param toLow the low end of the range to map to
21 | * @param toHigh the high end of the range to map to
22 | * @return the mapped value
23 | */
24 | public static double mapValueFromRangeToRange(
25 | double value,
26 | double fromLow,
27 | double fromHigh,
28 | double toLow,
29 | double toHigh) {
30 | double fromRangeSize = fromHigh - fromLow;
31 | double toRangeSize = toHigh - toLow;
32 | double valueScale = (value - fromLow) / fromRangeSize;
33 | return toLow + (valueScale * toRangeSize);
34 | }
35 |
36 | /**
37 | * Clamp a value to be within the provided range.
38 | * @param value the value to clamp
39 | * @param low the low end of the range
40 | * @param high the high end of the range
41 | * @return the clamped value
42 | */
43 | public static double clamp(double value, double low, double high) {
44 | return Math.min(Math.max(value, low), high);
45 | }
46 |
47 | public static int clamp(int value, int low, int high) {
48 | return Math.min(Math.max(value, low), high);
49 | }
50 |
51 |
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/SteppingLooper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | */
9 |
10 | package com.facebook.rebound;
11 |
12 | public class SteppingLooper extends SpringLooper {
13 |
14 | private boolean mStarted;
15 | private long mLastTime;
16 |
17 | @Override
18 | public void start() {
19 | mStarted = true;
20 | mLastTime = 0;
21 | }
22 |
23 | public boolean step(long interval) {
24 | if (mSpringSystem == null || !mStarted) {
25 | return false;
26 | }
27 | long currentTime = mLastTime + interval;
28 | mSpringSystem.loop(currentTime);
29 | mLastTime = currentTime;
30 | return mSpringSystem.getIsIdle();
31 | }
32 |
33 | @Override
34 | public void stop() {
35 | mStarted = false;
36 | }
37 | }
38 |
39 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/SynchronousLooper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2013, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | */
9 |
10 | package com.facebook.rebound;
11 |
12 | public class SynchronousLooper extends SpringLooper {
13 |
14 | public static double SIXTY_FPS = 16.6667;
15 | private double mTimeStep;
16 | private boolean mRunning;
17 |
18 | public SynchronousLooper() {
19 | mTimeStep = SIXTY_FPS;
20 | }
21 |
22 | public double getTimeStep() {
23 | return mTimeStep;
24 | }
25 |
26 | public void setTimeStep(double timeStep) {
27 | mTimeStep = timeStep;
28 | }
29 |
30 | @Override
31 | public void start() {
32 | mRunning = true;
33 | while (!mSpringSystem.getIsIdle()) {
34 | if (mRunning == false) {
35 | break;
36 | }
37 | mSpringSystem.loop(mTimeStep);
38 | }
39 | }
40 |
41 | @Override
42 | public void stop() {
43 | mRunning = false;
44 | }
45 | }
46 |
47 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/ui/SpringConfiguratorView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2013, Facebook, Inc.
3 | * All rights reserved.
4 | *
5 | * This source code is licensed under the BSD-style license found in the
6 | * LICENSE file in the root directory of this source tree. An additional grant
7 | * of patent rights can be found in the PATENTS file in the same directory.
8 | *
9 | */
10 |
11 | package com.facebook.rebound.ui;
12 |
13 | import android.annotation.TargetApi;
14 | import android.content.Context;
15 | import android.content.res.Resources;
16 | import android.graphics.Color;
17 | import android.os.Build;
18 | import android.util.AttributeSet;
19 | import android.view.Gravity;
20 | import android.view.MotionEvent;
21 | import android.view.View;
22 | import android.view.ViewGroup;
23 | import android.widget.AbsListView;
24 | import android.widget.AdapterView;
25 | import android.widget.BaseAdapter;
26 | import android.widget.FrameLayout;
27 | import android.widget.LinearLayout;
28 | import android.widget.SeekBar;
29 | import android.widget.Spinner;
30 | import android.widget.TableLayout;
31 | import android.widget.TextView;
32 |
33 | import com.facebook.rebound.OrigamiValueConverter;
34 | import com.facebook.rebound.Spring;
35 | import com.facebook.rebound.SpringConfig;
36 | import com.facebook.rebound.SpringConfigRegistry;
37 | import com.facebook.rebound.SpringListener;
38 | import com.facebook.rebound.SpringSystem;
39 |
40 | import java.text.DecimalFormat;
41 | import java.util.ArrayList;
42 | import java.util.List;
43 | import java.util.Map;
44 |
45 | import static com.facebook.rebound.ui.Util.*;
46 |
47 | /**
48 | * The SpringConfiguratorView provides a reusable view for live-editing all registered springs
49 | * within an Application. Each registered Spring can be accessed by its id and its tension and
50 | * friction properties can be edited while the user tests the effected UI live.
51 | */
52 | public class SpringConfiguratorView extends FrameLayout {
53 |
54 | private static final int MAX_SEEKBAR_VAL = 100000;
55 | private static final float MIN_TENSION = 0;
56 | private static final float MAX_TENSION = 200;
57 | private static final float MIN_FRICTION = 0;
58 | private static final float MAX_FRICTION = 50;
59 | private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#.#");
60 |
61 | private final SpinnerAdapter spinnerAdapter;
62 | private final List mSpringConfigs = new ArrayList();
63 | private final Spring mRevealerSpring;
64 | private final float mStashPx;
65 | private final float mRevealPx;
66 | private final SpringConfigRegistry springConfigRegistry;
67 | private final int mTextColor = Color.argb(255, 225, 225, 225);
68 | private SeekBar mTensionSeekBar;
69 | private SeekBar mFrictionSeekBar;
70 | private Spinner mSpringSelectorSpinner;
71 | private TextView mFrictionLabel;
72 | private TextView mTensionLabel;
73 | private SpringConfig mSelectedSpringConfig;
74 |
75 | public SpringConfiguratorView(Context context) {
76 | this(context, null);
77 | }
78 |
79 | public SpringConfiguratorView(Context context, AttributeSet attrs) {
80 | this(context, attrs, 0);
81 | }
82 |
83 | @TargetApi(Build.VERSION_CODES.HONEYCOMB)
84 | public SpringConfiguratorView(Context context, AttributeSet attrs, int defStyle) {
85 | super(context, attrs, defStyle);
86 |
87 | SpringSystem springSystem = SpringSystem.create();
88 | springConfigRegistry = SpringConfigRegistry.getInstance();
89 | spinnerAdapter = new SpinnerAdapter(context);
90 |
91 | Resources resources = getResources();
92 | mRevealPx = dpToPx(40, resources);
93 | mStashPx = dpToPx(280, resources);
94 |
95 | mRevealerSpring = springSystem.createSpring();
96 | SpringListener revealerSpringListener = new RevealerSpringListener();
97 | mRevealerSpring
98 | .setCurrentValue(1)
99 | .setEndValue(1)
100 | .addListener(revealerSpringListener);
101 |
102 | addView(generateHierarchy(context));
103 |
104 | SeekbarListener seekbarListener = new SeekbarListener();
105 | mTensionSeekBar.setMax(MAX_SEEKBAR_VAL);
106 | mTensionSeekBar.setOnSeekBarChangeListener(seekbarListener);
107 |
108 | mFrictionSeekBar.setMax(MAX_SEEKBAR_VAL);
109 | mFrictionSeekBar.setOnSeekBarChangeListener(seekbarListener);
110 |
111 | mSpringSelectorSpinner.setAdapter(spinnerAdapter);
112 | mSpringSelectorSpinner.setOnItemSelectedListener(new SpringSelectedListener());
113 | refreshSpringConfigurations();
114 |
115 | this.setTranslationY(mStashPx);
116 | }
117 |
118 | /**
119 | * Programmatically build up the view hierarchy to avoid the need for resources.
120 | * @return View hierarchy
121 | */
122 | private View generateHierarchy(Context context) {
123 | Resources resources = getResources();
124 |
125 | FrameLayout.LayoutParams params;
126 | int fivePx = dpToPx(5, resources);
127 | int tenPx = dpToPx(10, resources);
128 | int twentyPx = dpToPx(20, resources);
129 | TableLayout.LayoutParams tableLayoutParams = new TableLayout.LayoutParams(
130 | 0,
131 | ViewGroup.LayoutParams.WRAP_CONTENT,
132 | 1f);
133 | tableLayoutParams.setMargins(0, 0, fivePx, 0);
134 | LinearLayout seekWrapper;
135 |
136 | FrameLayout root = new FrameLayout(context);
137 | params = createLayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, dpToPx(300, resources));
138 | root.setLayoutParams(params);
139 |
140 | FrameLayout container = new FrameLayout(context);
141 | params = createMatchParams();
142 | params.setMargins(0, twentyPx, 0, 0);
143 | container.setLayoutParams(params);
144 | container.setBackgroundColor(Color.argb(100, 0, 0, 0));
145 | root.addView(container);
146 |
147 | mSpringSelectorSpinner = new Spinner(context, Spinner.MODE_DIALOG);
148 | params = createMatchWrapParams();
149 | params.gravity = Gravity.TOP;
150 | params.setMargins(tenPx, tenPx, tenPx, 0);
151 | mSpringSelectorSpinner.setLayoutParams(params);
152 | container.addView(mSpringSelectorSpinner);
153 |
154 | LinearLayout linearLayout = new LinearLayout(context);
155 | params = createMatchWrapParams();
156 | params.setMargins(0, 0, 0, dpToPx(80, resources));
157 | params.gravity = Gravity.BOTTOM;
158 | linearLayout.setLayoutParams(params);
159 | linearLayout.setOrientation(LinearLayout.VERTICAL);
160 | container.addView(linearLayout);
161 |
162 | seekWrapper = new LinearLayout(context);
163 | params = createMatchWrapParams();
164 | params.setMargins(tenPx, tenPx, tenPx, twentyPx);
165 | seekWrapper.setPadding(tenPx, tenPx, tenPx, tenPx);
166 | seekWrapper.setLayoutParams(params);
167 | seekWrapper.setOrientation(LinearLayout.HORIZONTAL);
168 | linearLayout.addView(seekWrapper);
169 |
170 | mTensionSeekBar = new SeekBar(context);
171 | mTensionSeekBar.setLayoutParams(tableLayoutParams);
172 | seekWrapper.addView(mTensionSeekBar);
173 |
174 | mTensionLabel = new TextView(getContext());
175 | mTensionLabel.setTextColor(mTextColor);
176 | params = createLayoutParams(
177 | dpToPx(50, resources),
178 | ViewGroup.LayoutParams.MATCH_PARENT);
179 | mTensionLabel.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
180 | mTensionLabel.setLayoutParams(params);
181 | mTensionLabel.setMaxLines(1);
182 | seekWrapper.addView(mTensionLabel);
183 |
184 | seekWrapper = new LinearLayout(context);
185 | params = createMatchWrapParams();
186 | params.setMargins(tenPx, tenPx, tenPx, twentyPx);
187 | seekWrapper.setPadding(tenPx, tenPx, tenPx, tenPx);
188 | seekWrapper.setLayoutParams(params);
189 | seekWrapper.setOrientation(LinearLayout.HORIZONTAL);
190 | linearLayout.addView(seekWrapper);
191 |
192 | mFrictionSeekBar = new SeekBar(context);
193 | mFrictionSeekBar.setLayoutParams(tableLayoutParams);
194 | seekWrapper.addView(mFrictionSeekBar);
195 |
196 | mFrictionLabel = new TextView(getContext());
197 | mFrictionLabel.setTextColor(mTextColor);
198 | params = createLayoutParams(dpToPx(50, resources), ViewGroup.LayoutParams.MATCH_PARENT);
199 | mFrictionLabel.setGravity(Gravity.CENTER_VERTICAL | Gravity.LEFT);
200 | mFrictionLabel.setLayoutParams(params);
201 | mFrictionLabel.setMaxLines(1);
202 | seekWrapper.addView(mFrictionLabel);
203 |
204 | View nub = new View(context);
205 | params = createLayoutParams(dpToPx(60, resources), dpToPx(40, resources));
206 | params.gravity = Gravity.TOP | Gravity.CENTER;
207 | nub.setLayoutParams(params);
208 | nub.setOnTouchListener(new OnNubTouchListener());
209 | nub.setBackgroundColor(Color.argb(255, 0, 164, 209));
210 | root.addView(nub);
211 |
212 | return root;
213 | }
214 |
215 | /**
216 | * remove the configurator from its parent and clean up springs and listeners
217 | */
218 | public void destroy() {
219 | ViewGroup parent = (ViewGroup) getParent();
220 | if (parent != null) {
221 | parent.removeView(this);
222 | }
223 | mRevealerSpring.destroy();
224 | }
225 |
226 | /**
227 | * reload the springs from the registry and update the UI
228 | */
229 | public void refreshSpringConfigurations() {
230 | Map springConfigMap = springConfigRegistry.getAllSpringConfig();
231 |
232 | spinnerAdapter.clear();
233 | mSpringConfigs.clear();
234 |
235 | for (Map.Entry entry : springConfigMap.entrySet()) {
236 | if (entry.getKey() == SpringConfig.defaultConfig) {
237 | continue;
238 | }
239 | mSpringConfigs.add(entry.getKey());
240 | spinnerAdapter.add(entry.getValue());
241 | }
242 | // Add the default config in last.
243 | mSpringConfigs.add(SpringConfig.defaultConfig);
244 | spinnerAdapter.add(springConfigMap.get(SpringConfig.defaultConfig));
245 | spinnerAdapter.notifyDataSetChanged();
246 | if (mSpringConfigs.size() > 0) {
247 | mSpringSelectorSpinner.setSelection(0);
248 | }
249 | }
250 |
251 | private class SpringSelectedListener implements AdapterView.OnItemSelectedListener {
252 |
253 | @Override
254 | public void onItemSelected(AdapterView> adapterView, View view, int i, long l) {
255 | mSelectedSpringConfig = mSpringConfigs.get(i);
256 | updateSeekBarsForSpringConfig(mSelectedSpringConfig);
257 | }
258 |
259 | @Override
260 | public void onNothingSelected(AdapterView> adapterView) {
261 | }
262 | }
263 |
264 | /**
265 | * listen to events on seekbars and update registered springs accordingly
266 | */
267 | private class SeekbarListener implements SeekBar.OnSeekBarChangeListener {
268 |
269 | @Override
270 | public void onProgressChanged(SeekBar seekBar, int val, boolean b) {
271 | float tensionRange = MAX_TENSION - MIN_TENSION;
272 | float frictionRange = MAX_FRICTION - MIN_FRICTION;
273 |
274 | if (seekBar == mTensionSeekBar) {
275 | float scaledTension = ((val) * tensionRange) / MAX_SEEKBAR_VAL + MIN_TENSION;
276 | mSelectedSpringConfig.tension =
277 | OrigamiValueConverter.tensionFromOrigamiValue(scaledTension);
278 | String roundedTensionLabel = DECIMAL_FORMAT.format(scaledTension);
279 | mTensionLabel.setText("T:" + roundedTensionLabel);
280 | }
281 |
282 | if (seekBar == mFrictionSeekBar) {
283 | float scaledFriction = ((val) * frictionRange) / MAX_SEEKBAR_VAL + MIN_FRICTION;
284 | mSelectedSpringConfig.friction =
285 | OrigamiValueConverter.frictionFromOrigamiValue(scaledFriction);
286 | String roundedFrictionLabel = DECIMAL_FORMAT.format(scaledFriction);
287 | mFrictionLabel.setText("F:" + roundedFrictionLabel);
288 | }
289 | }
290 |
291 | @Override
292 | public void onStartTrackingTouch(SeekBar seekBar) {
293 | }
294 |
295 | @Override
296 | public void onStopTrackingTouch(SeekBar seekBar) {
297 | }
298 | }
299 |
300 | /**
301 | * update the position of the seekbars based on the spring value;
302 | * @param springConfig current editing spring
303 | */
304 | private void updateSeekBarsForSpringConfig(SpringConfig springConfig) {
305 | float tension = (float) OrigamiValueConverter.origamiValueFromTension(springConfig.tension);
306 | float tensionRange = MAX_TENSION - MIN_TENSION;
307 | int scaledTension = Math.round(((tension - MIN_TENSION) * MAX_SEEKBAR_VAL) / tensionRange);
308 |
309 | float friction = (float) OrigamiValueConverter.origamiValueFromFriction(springConfig.friction);
310 | float frictionRange = MAX_FRICTION - MIN_FRICTION;
311 | int scaledFriction = Math.round(((friction - MIN_FRICTION) * MAX_SEEKBAR_VAL) / frictionRange);
312 |
313 | mTensionSeekBar.setProgress(scaledTension);
314 | mFrictionSeekBar.setProgress(scaledFriction);
315 | }
316 |
317 | /**
318 | * toggle visibility when the nub is tapped.
319 | */
320 | private class OnNubTouchListener implements View.OnTouchListener {
321 | @Override
322 | public boolean onTouch(View view, MotionEvent motionEvent) {
323 | if (motionEvent.getAction() == MotionEvent.ACTION_DOWN) {
324 | togglePosition();
325 | }
326 | return true;
327 | }
328 | }
329 |
330 | private void togglePosition() {
331 | double currentValue = mRevealerSpring.getEndValue();
332 | mRevealerSpring
333 | .setEndValue(currentValue == 1 ? 0 : 1);
334 | }
335 |
336 | private class RevealerSpringListener implements SpringListener {
337 |
338 | @Override
339 | public void onSpringUpdate(Spring spring) {
340 | float val = (float) spring.getCurrentValue();
341 | float minTranslate = mRevealPx;
342 | float maxTranslate = mStashPx;
343 | float range = maxTranslate - minTranslate;
344 | float yTranslate = (val * range) + minTranslate;
345 | SpringConfiguratorView.this.setTranslationY(yTranslate);
346 | }
347 |
348 | @Override
349 | public void onSpringAtRest(Spring spring) {
350 | }
351 |
352 | @Override
353 | public void onSpringActivate(Spring spring) {
354 | }
355 |
356 | @Override
357 | public void onSpringEndStateChange(Spring spring) {
358 | }
359 | }
360 |
361 | private class SpinnerAdapter extends BaseAdapter {
362 |
363 | private final Context mContext;
364 | private final List mStrings;
365 |
366 | public SpinnerAdapter(Context context) {
367 | mContext = context;
368 | mStrings = new ArrayList();
369 | }
370 |
371 | @Override
372 | public int getCount() {
373 | return mStrings.size();
374 | }
375 |
376 | @Override
377 | public Object getItem(int position) {
378 | return mStrings.get(position);
379 | }
380 |
381 | @Override
382 | public long getItemId(int position) {
383 | return position;
384 | }
385 |
386 | public void add(String string) {
387 | mStrings.add(string);
388 | notifyDataSetChanged();
389 | }
390 |
391 | /**
392 | * Remove all elements from the list.
393 | */
394 | public void clear() {
395 | mStrings.clear();
396 | notifyDataSetChanged();
397 | }
398 |
399 | @Override
400 | public View getView(int position, View convertView, ViewGroup parent) {
401 | TextView textView;
402 | if (convertView == null) {
403 | textView = new TextView(mContext);
404 | AbsListView.LayoutParams params = new AbsListView.LayoutParams(
405 | ViewGroup.LayoutParams.MATCH_PARENT,
406 | ViewGroup.LayoutParams.MATCH_PARENT);
407 | textView.setLayoutParams(params);
408 | int twelvePx = dpToPx(12, getResources());
409 | textView.setPadding(twelvePx, twelvePx, twelvePx, twelvePx);
410 | textView.setTextColor(mTextColor);
411 | } else {
412 | textView = (TextView) convertView;
413 | }
414 | textView.setText(mStrings.get(position));
415 | return textView;
416 | }
417 | }
418 | }
419 |
420 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/com/facebook/rebound/ui/Util.java:
--------------------------------------------------------------------------------
1 | package com.facebook.rebound.ui;
2 |
3 | import android.content.res.Resources;
4 | import android.util.TypedValue;
5 | import android.view.ViewGroup;
6 | import android.widget.FrameLayout;
7 | import android.widget.RelativeLayout;
8 |
9 | /**
10 | * Utilities for generating view hierarchies without using resources.
11 | */
12 | public abstract class Util {
13 |
14 | public static final int dpToPx(float dp, Resources res) {
15 | return (int) TypedValue.applyDimension(
16 | TypedValue.COMPLEX_UNIT_DIP,
17 | dp,
18 | res.getDisplayMetrics());
19 | }
20 |
21 | public static final FrameLayout.LayoutParams createLayoutParams(int width, int height) {
22 | return new FrameLayout.LayoutParams(width, height);
23 | }
24 |
25 | public static final FrameLayout.LayoutParams createMatchParams() {
26 | return createLayoutParams(
27 | ViewGroup.LayoutParams.MATCH_PARENT,
28 | ViewGroup.LayoutParams.MATCH_PARENT);
29 | }
30 |
31 | public static final FrameLayout.LayoutParams createWrapParams() {
32 | return createLayoutParams(
33 | ViewGroup.LayoutParams.WRAP_CONTENT,
34 | ViewGroup.LayoutParams.WRAP_CONTENT);
35 | }
36 |
37 | public static final FrameLayout.LayoutParams createWrapMatchParams() {
38 | return createLayoutParams(
39 | ViewGroup.LayoutParams.WRAP_CONTENT,
40 | ViewGroup.LayoutParams.MATCH_PARENT);
41 | }
42 |
43 | public static final FrameLayout.LayoutParams createMatchWrapParams() {
44 | return createLayoutParams(
45 | ViewGroup.LayoutParams.MATCH_PARENT,
46 | ViewGroup.LayoutParams.WRAP_CONTENT);
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/java/it/beppi/tristatetogglebutton_library/TriStateToggleButton.java:
--------------------------------------------------------------------------------
1 | package it.beppi.tristatetogglebutton_library;
2 |
3 | import android.content.Context;
4 | import android.content.res.Resources;
5 | import android.content.res.TypedArray;
6 | import android.graphics.Canvas;
7 | import android.graphics.Color;
8 | import android.graphics.Paint;
9 | import android.graphics.Paint.Cap;
10 | import android.graphics.Paint.Style;
11 | import android.graphics.RectF;
12 | import android.util.AttributeSet;
13 | import android.util.TypedValue;
14 | import android.view.MotionEvent;
15 | import android.view.View;
16 |
17 | import com.facebook.rebound.SimpleSpringListener;
18 | import com.facebook.rebound.Spring;
19 | import com.facebook.rebound.SpringConfig;
20 | import com.facebook.rebound.SpringSystem;
21 | import com.facebook.rebound.SpringUtil;
22 |
23 | import static it.beppi.tristatetogglebutton_library.TriStateToggleButton.ToggleStatus.mid;
24 | import static it.beppi.tristatetogglebutton_library.TriStateToggleButton.ToggleStatus.off;
25 | import static it.beppi.tristatetogglebutton_library.TriStateToggleButton.ToggleStatus.on;
26 |
27 |
28 | /**
29 | * @author ThinkPad
30 | * @author modified by BeppiMenozzi
31 | */
32 | public class TriStateToggleButton extends View{
33 | // Beppi: added a three state enumerator
34 | public enum ToggleStatus {
35 | on, mid, off
36 | }
37 | // Beppi: convert from the string values defined in the xml attributes to an usable value
38 | private ToggleStatus attrToStatus(String attr) {
39 | if (attr == null) return off;
40 | if (attr.equals("0")) return off;
41 | else if (attr.equals("1")) return mid;
42 | else return on;
43 | }
44 |
45 | // Beppi: static shortcuts for handling boolean values with 2-state. mid = false.
46 | public static boolean toggleStatusToBoolean(ToggleStatus toggleStatus) {
47 | if (toggleStatus == on) return true;
48 | else return false;
49 | }
50 | public static ToggleStatus booleanToToggleStatus(boolean toggleStatus) {
51 | if (toggleStatus) return on;
52 | else return off;
53 | }
54 | // Beppi: same with integers
55 | public static int toggleStatusToInt(ToggleStatus toggleStatus) {
56 | switch (toggleStatus) {
57 | case off: return 0;
58 | case mid: return 1;
59 | case on:
60 | default: return 2;
61 | }
62 | }
63 | public static ToggleStatus intToToggleStatus(int toggleIntValue) {
64 | if (toggleIntValue == 0) return off;
65 | else if (toggleIntValue == 1) return mid;
66 | else return on;
67 | }
68 |
69 |
70 | private SpringSystem springSystem;
71 | private Spring spring ;
72 | /** */
73 | private float radius;
74 | /** 开启颜色*/ // Turn on color
75 | // Beppi: Modified color to match material design
76 | // private int onColor = Color.parseColor("#4ebb7f");
77 | private int onColor = Color.parseColor("#42bd41"); // green 300
78 | /** 关闭颜色*/ // Turn off color
79 | // Beppi: Modified color to match material design
80 | // private int offBorderColor = Color.parseColor("#dadbda");
81 | private int offBorderColor = Color.parseColor("#bdbdbd"); // grey 400
82 | /** 灰色带颜色*/ // Gray color
83 | private int offColor = Color.parseColor("#ffffff");
84 | /** 手柄颜色*/ // Handle color
85 | // Beppi: Added third mid color
86 | private int midColor = Color.parseColor("#ffca28"); // amber 400
87 | private int spotColor = Color.parseColor("#ffffff");
88 | /** 边框颜色*/ // Border color
89 | private int borderColor = offBorderColor;
90 | /** 画笔*/ // brush
91 | private Paint paint ;
92 | /** 开关状态*/ // switch status
93 | // Beppi: changed the type of toggleOn from boolean to ToggleStatus
94 | // Beppi: refactored the variable name from toggleOn to toggleStatus
95 | // private boolean toggleOn = false;
96 | private ToggleStatus toggleStatus = off;
97 | // Beppi: added previousToggleStatus to manage transitions correctly
98 | private ToggleStatus previousToggleStatus = off;
99 | /** 边框大小*/ // Border size
100 | private int borderWidth = 2;
101 | /** 垂直中心*/ // Vertical center
102 | private float centerY;
103 | /** 按钮的开始和结束位置*/ // The start and end positions of the button
104 | // Beppi: added midX position
105 | // private float startX, endX;
106 | private float startX, midX, endX;
107 | /** 手柄X位置的最小和最大值*/ // The minimum and maximum values for the X position of the handle
108 | // Beppi: added spotMidX
109 | // private float spotMinX, spotMaxX;
110 | private float spotMinX, spotMidX, spotMaxX;
111 | /**手柄大小 */ // Handle size
112 | private int spotSize ;
113 | /** 手柄X位置*/ // Handle X position
114 | private float spotX;
115 | /** 关闭时内部灰色带高度*/ // Off Internal gray band height
116 | private float offLineWidth;
117 | /** */
118 | private RectF rect = new RectF();
119 | /** 默认使用动画*/ // Animation is used by default
120 | private boolean defaultAnimate = true;
121 | // Beppi: added midSelectable
122 | private boolean midSelectable = true;
123 |
124 | // Beppi: swipe management
125 | private int swipeSensitivityPixels = 200;
126 | private int swipeX = 0;
127 |
128 | /** 是否默认处于打开状态*/ // Whether it is on by default
129 | // Beppi: changed the type of isDefaultOn from boolean to ToggleStatus
130 | // Beppi: refactored the variable name from isDefaultOn to defaultStatus
131 | // private boolean isDefaultOn = false;
132 | private ToggleStatus defaultStatus = off;
133 | // Beppi: enabled && disabledColor
134 | private boolean enabled = true;
135 | private int disabledColor = Color.parseColor("#bdbdbd"); // grey 400
136 |
137 | private boolean swipeing = false;
138 |
139 | private OnToggleChanged listener;
140 |
141 | private TriStateToggleButton(Context context) {
142 | super(context);
143 | }
144 | public TriStateToggleButton(Context context, AttributeSet attrs, int defStyleAttr) {
145 | super(context, attrs, defStyleAttr);
146 | setup(attrs);
147 | }
148 | public TriStateToggleButton(Context context, AttributeSet attrs) {
149 | super(context, attrs);
150 | setup(attrs);
151 | }
152 |
153 | @Override
154 | protected void onDetachedFromWindow() {
155 | super.onDetachedFromWindow();
156 | spring.removeListener(springListener);
157 | }
158 |
159 | public void onAttachedToWindow() {
160 | super.onAttachedToWindow();
161 | spring.addListener(springListener);
162 | }
163 |
164 | public void setup(AttributeSet attrs) {
165 | paint = new Paint(Paint.ANTI_ALIAS_FLAG);
166 | paint.setStyle(Style.FILL);
167 | paint.setStrokeCap(Cap.ROUND);
168 |
169 | springSystem = SpringSystem.create();
170 | spring = springSystem.createSpring();
171 | spring.setSpringConfig(SpringConfig.fromOrigamiTensionAndFriction(50, 7));
172 |
173 | this.setOnClickListener(new OnClickListener() {
174 | @Override
175 | public void onClick(View arg0) {
176 | toggle(defaultAnimate);
177 | }
178 | });
179 |
180 | // Beppi: swipe management
181 | this.setOnTouchListener(new OnTouchListener() {
182 | @Override
183 | public boolean onTouch(View view, MotionEvent motionEvent) {
184 | int x = (int) motionEvent.getX();
185 | int action = motionEvent.getAction();
186 | if (action == MotionEvent.ACTION_DOWN) {
187 | swipeX = x;
188 | swipeing = false;
189 | }
190 | else if (action == MotionEvent.ACTION_MOVE) {
191 | if (swipeSensitivityPixels == 0) return false;
192 | else if (x - swipeX > swipeSensitivityPixels) {
193 | swipeX = x;
194 | swipeing = true;
195 | increaseValue();
196 | return true;
197 | }
198 | else if (swipeX - x > swipeSensitivityPixels) {
199 | swipeX = x;
200 | swipeing = true;
201 | decreaseValue();
202 | return true;
203 | }
204 | }
205 | else if (action == MotionEvent.ACTION_UP) {
206 | if (!swipeing) toggle(defaultAnimate); // here simple clicks are managed.
207 | return true;
208 | }
209 | return false;
210 | }
211 | });
212 |
213 | TypedArray typedArray = getContext().obtainStyledAttributes(attrs, R.styleable.TriStateToggleButton);
214 | offBorderColor = typedArray.getColor(R.styleable.TriStateToggleButton_tbOffBorderColor, offBorderColor);
215 | onColor = typedArray.getColor(R.styleable.TriStateToggleButton_tbOnColor, onColor);
216 | spotColor = typedArray.getColor(R.styleable.TriStateToggleButton_tbSpotColor, spotColor);
217 | offColor = typedArray.getColor(R.styleable.TriStateToggleButton_tbOffColor, offColor);
218 | // Beppi: added midColor attribute
219 | midColor = typedArray.getColor(R.styleable.TriStateToggleButton_tbMidColor, midColor);
220 | borderWidth = typedArray.getDimensionPixelSize(R.styleable.TriStateToggleButton_tbBorderWidth, borderWidth);
221 | defaultAnimate = typedArray.getBoolean(R.styleable.TriStateToggleButton_tbAnimate, defaultAnimate);
222 | // Beppi: modified defaultStatus from boolean to DefaultStatus
223 | // defaultStatus = typedArray.getBoolean(R.styleable.ToggleButton_tbDefaultStatus, defaultStatus);
224 | defaultStatus = attrToStatus(typedArray.getString(R.styleable.TriStateToggleButton_tbDefaultStatus));
225 | // Beppi: added tbIsMidSelectable
226 | midSelectable = typedArray.getBoolean(R.styleable.TriStateToggleButton_tbIsMidSelectable, midSelectable);
227 | // Beppi: added enabled
228 | enabled = typedArray.getBoolean(R.styleable.TriStateToggleButton_enabled, enabled);
229 |
230 | // Beppi: swipe
231 | swipeSensitivityPixels = typedArray.getInt(R.styleable.TriStateToggleButton_tbSwipeSensitivityPixels, swipeSensitivityPixels);
232 | // 0 == off
233 |
234 | typedArray.recycle();
235 |
236 | borderColor = offBorderColor;
237 |
238 | // Beppi: changed the usage of defaultStatus to match ToggleStatus type
239 | // if (defaultStatus) { toggleOn(); }
240 | switch (defaultStatus) {
241 | case off: toggleOff(); break; // actually not needed, added for clearness
242 | case mid: toggleMid(); break;
243 | case on: toggleOn(); break;
244 | }
245 | }
246 |
247 | public void toggle() {
248 | toggle(true);
249 | }
250 |
251 | // Beppi: modified to iterate on the 3 values instead of switching between two
252 | public void toggle(boolean animate) {
253 | // toggleStatus = !toggleStatus;
254 | if (midSelectable)
255 | switch (toggleStatus) {
256 | case off: putValueInToggleStatus(mid); break;
257 | case mid: putValueInToggleStatus(on); break;
258 | case on: putValueInToggleStatus(off); break;
259 | }
260 | else
261 | switch (toggleStatus) {
262 | case off:
263 | case mid: putValueInToggleStatus(on); break;
264 | case on: putValueInToggleStatus(off); break;
265 | }
266 | takeEffect(animate);
267 |
268 | if(listener != null){
269 | listener.onToggle(toggleStatus, toggleStatusToBoolean(toggleStatus), toggleStatusToInt(toggleStatus));
270 | }
271 | }
272 |
273 | public void toggleOn() {
274 | setToggleOn();
275 | if(listener != null){
276 | listener.onToggle(toggleStatus, toggleStatusToBoolean(toggleStatus), toggleStatusToInt(toggleStatus));
277 | }
278 | }
279 |
280 | public void toggleOff() {
281 | setToggleOff();
282 | if(listener != null){
283 | listener.onToggle(toggleStatus, toggleStatusToBoolean(toggleStatus), toggleStatusToInt(toggleStatus));
284 | }
285 | }
286 |
287 | // Beppi: added method to handle the mid value
288 | public void toggleMid() {
289 | setToggleMid();
290 | if(listener != null){
291 | listener.onToggle(toggleStatus, toggleStatusToBoolean(toggleStatus), toggleStatusToInt(toggleStatus));
292 | }
293 | }
294 |
295 | private void putValueInToggleStatus(ToggleStatus value) {
296 | if (!enabled) return;
297 | previousToggleStatus = toggleStatus;
298 | toggleStatus = value;
299 | }
300 |
301 | /**
302 | * 设置显示成打开样式,不会触发toggle事件 // Setting the display to open style does not fire the toggle event
303 | */
304 | public void setToggleOn() {
305 | setToggleOn(true);
306 | }
307 |
308 | /**
309 | * @param animate asd
310 | */
311 | public void setToggleOn(boolean animate){
312 | // Beppi: changed toggleStatus value from true to on
313 | // toggleStatus = true;
314 | putValueInToggleStatus(on);
315 | takeEffect(animate);
316 | }
317 |
318 | /**
319 | * 设置显示成关闭样式,不会触发toggle事件 // Settings are shown as off styles, and the toggle event is not fired
320 | */
321 | public void setToggleOff() {
322 | setToggleOff(true);
323 | }
324 |
325 | public void setToggleOff(boolean animate) {
326 | // Beppi: changed toggleStatus value from false to off
327 | // toggleStatus = false;
328 | putValueInToggleStatus(off);
329 | takeEffect(animate);
330 | }
331 |
332 | // Beppi: added method for Mid value management
333 | public void setToggleMid(boolean animate) {
334 | putValueInToggleStatus(mid);
335 | takeEffect(animate);
336 | }
337 | public void setToggleMid() {
338 | setToggleMid(true);
339 | }
340 |
341 | //Beppi: added setToggleStatus() method, that imho was missing and needed
342 | public void setToggleStatus(ToggleStatus toggleStatus, boolean animate) {
343 | putValueInToggleStatus(toggleStatus);
344 | takeEffect(animate);
345 | }
346 | public void setToggleStatus(ToggleStatus toggleStatus) {
347 | setToggleStatus(toggleStatus, true);
348 | }
349 | public void setToggleStatus(boolean toggleStatus) {
350 | setToggleStatus(toggleStatus, true);
351 | }
352 | public void setToggleStatus(boolean toggleStatus, boolean animate) {
353 | if (toggleStatus) putValueInToggleStatus(on);
354 | else putValueInToggleStatus(off);
355 | takeEffect(animate);
356 | }
357 | public void setToggleStatus(int toggleIntValue) {
358 | setToggleStatus(toggleIntValue, true);
359 | }
360 | public void setToggleStatus(int toggleIntValue, boolean animate) {
361 | setToggleStatus(intToToggleStatus(toggleIntValue), animate);
362 | }
363 |
364 | public void increaseValue(boolean animate) { // same as toggle, but after on does not rewind to off
365 | switch (toggleStatus) {
366 | case off: if (midSelectable) putValueInToggleStatus(mid); else putValueInToggleStatus(on); break;
367 | case mid: putValueInToggleStatus(on); break;
368 | case on: break;
369 | }
370 | takeEffect(animate);
371 | if(listener != null){
372 | listener.onToggle(toggleStatus, toggleStatusToBoolean(toggleStatus), toggleStatusToInt(toggleStatus));
373 | }
374 | }
375 | public void increaseValue() {
376 | increaseValue(true);
377 | }
378 | public void decreaseValue(boolean animate) {
379 | switch (toggleStatus) {
380 | case on: if (midSelectable) putValueInToggleStatus(mid); else putValueInToggleStatus(off); break;
381 | case mid: putValueInToggleStatus(off); break;
382 | case off: break;
383 | }
384 | takeEffect(animate);
385 | if(listener != null){
386 | listener.onToggle(toggleStatus, toggleStatusToBoolean(toggleStatus), toggleStatusToInt(toggleStatus));
387 | }
388 | }
389 | public void decreaseValue() {
390 | decreaseValue(true);
391 | }
392 |
393 | // Beppi: rewritten takeEffect() method to manage 3 states
394 | /*
395 | private void takeEffect(boolean animate) {
396 | if(animate){
397 | spring.setEndValue(toggleStatus ? 1 : 0);
398 | }else{
399 | //这里没有调用spring,所以spring里的当前值没有变更,这里要设置一下,同步两边的当前值
400 | // There is no call spring, so the current value of the spring has not changed, here to set it, the current value on both sides of synchronization
401 | spring.setCurrentValue(toggleStatus ? 1 : 0);
402 | calculateEffect(toggleStatus ? 1 : 0);
403 | }
404 | }
405 | */
406 | private void takeEffect(boolean animate) {
407 | if(animate){
408 | spring.setEndValue(toggleStatus == on ? 1 : toggleStatus == off ? 0 : 0.5);
409 | }else{
410 | //这里没有调用spring,所以spring里的当前值没有变更,这里要设置一下,同步两边的当前值
411 | // There is no call spring, so the current value of the spring has not changed, here to set it, the current value on both sides of synchronization
412 | spring.setCurrentValue(toggleStatus == on ? 1 : toggleStatus == off ? 0 : 0.5);
413 | if (toggleStatus == on) calculateEffect(1);
414 | else if (toggleStatus == mid) calculateEffect(0.5);
415 | else calculateEffect(0);
416 |
417 | // calculateEffect(toggleStatus == on ? 1 : toggleStatus == off ? 0 : 0.5);
418 | }
419 | }
420 |
421 |
422 | @Override
423 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
424 | final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
425 | final int heightMode = MeasureSpec.getMode(heightMeasureSpec);
426 |
427 | int widthSize = MeasureSpec.getSize(widthMeasureSpec);
428 | int heightSize = MeasureSpec.getSize(heightMeasureSpec);
429 |
430 | Resources r = Resources.getSystem();
431 | if(widthMode == MeasureSpec.UNSPECIFIED || widthMode == MeasureSpec.AT_MOST){
432 | widthSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 50, r.getDisplayMetrics());
433 | widthMeasureSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.EXACTLY);
434 | }
435 |
436 | if(heightMode == MeasureSpec.UNSPECIFIED || heightSize == MeasureSpec.AT_MOST){
437 | heightSize = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 30, r.getDisplayMetrics());
438 | heightMeasureSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.EXACTLY);
439 | }
440 |
441 |
442 | super.onMeasure(widthMeasureSpec, heightMeasureSpec);
443 | }
444 |
445 |
446 | @Override
447 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
448 | super.onLayout(changed, left, top, right, bottom);
449 |
450 | final int width = getWidth();
451 | final int height = getHeight();
452 |
453 | radius = Math.min(width, height) * 0.5f;
454 | centerY = radius;
455 | startX = radius;
456 | endX = width - radius;
457 | spotMinX = startX + borderWidth;
458 | spotMaxX = endX - borderWidth;
459 | spotMidX = (startX + endX) / 2;
460 | spotSize = height - 4 * borderWidth;
461 | // Beppi: changed management of the position according to 3 states
462 | // spotX = toggleStatus ? spotMaxX : spotMinX;
463 | spotX = toggleStatus == on ? spotMaxX : toggleStatus == off ? spotMinX : spotMidX;
464 | offLineWidth = 0;
465 | }
466 |
467 |
468 | SimpleSpringListener springListener = new SimpleSpringListener(){
469 | @Override
470 | public void onSpringUpdate(Spring spring) {
471 | final double value = spring.getCurrentValue();
472 | calculateEffect(value);
473 | }
474 | };
475 |
476 | private int clamp(int value, int low, int high) {
477 | return Math.min(Math.max(value, low), high);
478 | }
479 |
480 |
481 | @Override
482 | public void draw(Canvas canvas) {
483 | rect.set(0, 0, getWidth(), getHeight());
484 | paint.setColor(borderColor);
485 | canvas.drawRoundRect(rect, radius, radius, paint);
486 |
487 | if(offLineWidth > 0){
488 | final float cy = offLineWidth * 0.5f;
489 | rect.set(spotX - cy, centerY - cy, endX + cy, centerY + cy);
490 | paint.setColor(enabled ? (toggleStatus == mid ? midColor : offColor) : disabledColor);
491 | canvas.drawRoundRect(rect, cy, cy, paint);
492 | }
493 |
494 | rect.set(spotX - 1 - radius, centerY - radius, spotX + 1.1f + radius, centerY + radius);
495 | paint.setColor(enabled ? borderColor : disabledColor);
496 | canvas.drawRoundRect(rect, radius, radius, paint);
497 |
498 | final float spotR = spotSize * 0.5f;
499 | rect.set(spotX - spotR, centerY - spotR, spotX + spotR, centerY + spotR);
500 | paint.setColor(enabled ? spotColor : disabledColor);
501 | canvas.drawRoundRect(rect, spotR, spotR, paint);
502 |
503 | }
504 |
505 | /**
506 | * @param value
507 | */
508 | /*
509 | private void calculateEffect(final double value) {
510 | final float mapToggleX = (float) SpringUtil.mapValueFromRangeToRange(value, 0, 1, spotMinX, spotMaxX);
511 | spotX = mapToggleX;
512 |
513 | float mapOffLineWidth = (float) SpringUtil.mapValueFromRangeToRange(1 - value, 0, 1, 10, spotSize);
514 |
515 | offLineWidth = mapOffLineWidth;
516 |
517 | final int fromB = Color.blue(onColor);
518 | final int fromR = Color.red(onColor);
519 | final int fromG = Color.green(onColor);
520 |
521 | final int toB = Color.blue(offBorderColor);
522 | final int toR = Color.red(offBorderColor);
523 | final int toG = Color.green(offBorderColor);
524 |
525 | int springB = (int) SpringUtil.mapValueFromRangeToRange(1 - value, 0, 1, fromB, toB);
526 | int springR = (int) SpringUtil.mapValueFromRangeToRange(1 - value, 0, 1, fromR, toR);
527 | int springG = (int) SpringUtil.mapValueFromRangeToRange(1 - value, 0, 1, fromG, toG);
528 |
529 | springB = clamp(springB, 0, 255);
530 | springR = clamp(springR, 0, 255);
531 | springG = clamp(springG, 0, 255);
532 |
533 | borderColor = Color.rgb(springR, springG, springB);
534 |
535 | postInvalidate();
536 | }
537 | */
538 | private void calculateEffect(final double value) {
539 | final float mapToggleX = (float) SpringUtil.mapValueFromRangeToRange(value, 0, 1, spotMinX, spotMaxX);
540 | spotX = mapToggleX;
541 | double min = 0, max = 0;
542 | int fromColor, toColor;
543 | if (previousToggleStatus == off && toggleStatus == mid) {
544 | toColor = offBorderColor; fromColor = midColor;
545 | } else
546 | if (previousToggleStatus == off && toggleStatus == on) {
547 | toColor = offBorderColor; fromColor = onColor;
548 | } else
549 | if (previousToggleStatus == mid && toggleStatus == on) {
550 | toColor = midColor; fromColor = onColor;
551 | } else
552 | if (previousToggleStatus == on && toggleStatus == off) {
553 | toColor = offBorderColor; fromColor = onColor;
554 | } else
555 | if (previousToggleStatus == on && toggleStatus == mid) {
556 | toColor = midColor; fromColor = onColor;
557 | } else
558 | {
559 | toColor = offBorderColor; fromColor = onColor;
560 | }
561 |
562 | if (previousToggleStatus == off) min = 0;
563 | else if (previousToggleStatus == mid) min = 0.5;
564 | else min = 1;
565 | if (toggleStatus == off) max = 0;
566 | else if (toggleStatus == mid) max = 0.5;
567 | else max = 1;
568 |
569 | if (min == max) { min = 0; max = 1; }
570 | else if (min > max) { double temp = min; min = max; max = temp; }
571 |
572 | offLineWidth = (float) SpringUtil.mapValueFromRangeToRange(min + max - value, min, max, 0, spotSize);
573 |
574 | final int fromB = Color.blue(fromColor);
575 | final int fromR = Color.red(fromColor);
576 | final int fromG = Color.green(fromColor);
577 | final int toB = Color.blue(toColor);
578 | final int toR = Color.red(toColor);
579 | final int toG = Color.green(toColor);
580 |
581 | int springB = (int) SpringUtil.mapValueFromRangeToRange(min + max - value, min, max, fromB, toB);
582 | int springR = (int) SpringUtil.mapValueFromRangeToRange(min + max - value, min, max, fromR, toR);
583 | int springG = (int) SpringUtil.mapValueFromRangeToRange(min + max - value, min, max, fromG, toG);
584 |
585 | springB = clamp(springB, 0, 255);
586 | springR = clamp(springR, 0, 255);
587 | springG = clamp(springG, 0, 255);
588 |
589 | borderColor = Color.rgb(springR, springG, springB);
590 |
591 | postInvalidate();
592 | }
593 |
594 | /**
595 | * @author ThinkPad
596 | *
597 | */
598 | public interface OnToggleChanged{
599 | /**
600 | * @param toggleStatus = =
601 | */
602 | // Beppi: changed according to 3 states value
603 | // public void onToggle(boolean on);
604 | public void onToggle(ToggleStatus toggleStatus, boolean booleanToggleStatus, int toggleIntValue);
605 | }
606 |
607 | public void setOnToggleChanged(OnToggleChanged onToggleChanged) {
608 | listener = onToggleChanged;
609 | }
610 |
611 | public boolean isAnimate() {
612 | return defaultAnimate;
613 | }
614 | public void setAnimate(boolean animate) {
615 | this.defaultAnimate = animate;
616 | }
617 |
618 | // Beppi: added all next methods, getters and setters
619 |
620 |
621 | public int getOnColor() {
622 | return onColor;
623 | }
624 |
625 | public void setOnColor(int onColor) {
626 | this.onColor = onColor;
627 | postInvalidate();
628 | }
629 |
630 | public int getOffBorderColor() {
631 | return offBorderColor;
632 | }
633 |
634 | public void setOffBorderColor(int offBorderColor) {
635 | this.offBorderColor = offBorderColor;
636 | postInvalidate();
637 | }
638 |
639 | public int getOffColor() {
640 | return offColor;
641 | }
642 |
643 | public void setOffColor(int offColor) {
644 | this.offColor = offColor;
645 | postInvalidate();
646 | }
647 |
648 | public int getMidColor() {
649 | return midColor;
650 | }
651 |
652 | public void setMidColor(int midColor) {
653 | this.midColor = midColor;
654 | postInvalidate();
655 | }
656 |
657 | public int getSpotColor() {
658 | return spotColor;
659 | }
660 |
661 | public void setSpotColor(int spotColor) {
662 | this.spotColor = spotColor;
663 | postInvalidate();
664 | }
665 |
666 | public int getBorderColor() {
667 | return borderColor;
668 | }
669 |
670 | public void setBorderColor(int borderColor) {
671 | this.borderColor = borderColor;
672 | postInvalidate();
673 | }
674 |
675 | public ToggleStatus getToggleStatus() {
676 | return toggleStatus;
677 | }
678 |
679 | public int getBorderWidth() {
680 | return borderWidth;
681 | }
682 |
683 | public void setBorderWidth(int borderWidth) {
684 | this.borderWidth = borderWidth;
685 | postInvalidate();
686 | }
687 |
688 | public int getSpotSize() {
689 | return spotSize;
690 | }
691 |
692 | public void setSpotSize(int spotSize) {
693 | this.spotSize = spotSize;
694 | postInvalidate();
695 | }
696 |
697 | public boolean isMidSelectable() {
698 | return midSelectable;
699 | }
700 |
701 | public void setMidSelectable(boolean midSelectable) {
702 | this.midSelectable = midSelectable;
703 | }
704 |
705 | @Override
706 | public boolean isEnabled() {
707 | return enabled;
708 | }
709 |
710 | @Override
711 | public void setEnabled(boolean enabled) {
712 | this.enabled = enabled;
713 | postInvalidate();
714 | super.setEnabled(enabled);
715 | }
716 | }
717 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TriStateToggleButton
3 |
4 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/main/res/values/toggle_button_attrs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/tristatetogglebutton_library/src/test/java/it/beppi/tristatetogglebutton_library/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package it.beppi.tristatetogglebutton_library;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.0"
6 | defaultConfig {
7 | applicationId "it.beppi.tristatetogglebutton"
8 | minSdkVersion 9
9 | targetSdkVersion 25
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:25.0.1'
28 | testCompile 'junit:junit:4.12'
29 | compile project(':tristatetogglebutton_library')
30 | }
31 |
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in F:\Android\android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/androidTest/java/it/beppi/tristatetogglebutton/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package it.beppi.tristatetogglebutton;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("it.beppi.tristatetogglebutton", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
7 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/main/java/it/beppi/tristatetogglebuttonsample/SampleActivity.java:
--------------------------------------------------------------------------------
1 | package it.beppi.tristatetogglebuttonsample;
2 |
3 | import android.graphics.Color;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.os.Bundle;
6 | import android.view.View;
7 | import android.widget.Button;
8 | import android.widget.TextView;
9 |
10 | import java.util.Random;
11 |
12 | import it.beppi.tristatetogglebutton_library.TriStateToggleButton;
13 |
14 | public class SampleActivity extends AppCompatActivity {
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | setContentView(R.layout.activity_sample);
20 |
21 | final TriStateToggleButton tstb_1 = (TriStateToggleButton) findViewById(R.id.tstb_1);
22 | final TextView tstb_1_text = (TextView) findViewById(R.id.tstb_1_text);
23 |
24 | final TriStateToggleButton tstb_2 = (TriStateToggleButton) findViewById(R.id.tstb_2);
25 | final TextView tstb_2_text = (TextView) findViewById(R.id.tstb_2_text);
26 |
27 | final TriStateToggleButton tstb_3 = (TriStateToggleButton) findViewById(R.id.tstb_3);
28 | final TextView tstb_3_text = (TextView) findViewById(R.id.tstb_3_text);
29 |
30 | final TriStateToggleButton tstb_4 = (TriStateToggleButton) findViewById(R.id.tstb_4);
31 |
32 | // Example 1: a default tristate toggle without any customization
33 |
34 | tstb_1_text.setText("Off");
35 | tstb_1.setOnToggleChanged(new TriStateToggleButton.OnToggleChanged() {
36 | @Override
37 | public void onToggle(TriStateToggleButton.ToggleStatus toggleStatus, boolean booleanToggleStatus, int toggleIntValue) {
38 | switch (toggleStatus) {
39 | case off: tstb_1_text.setText("Off"); break;
40 | case mid: tstb_1_text.setText("Half way"); break;
41 | case on: tstb_1_text.setText("On"); break;
42 | }
43 | }
44 | });
45 |
46 | // Example 2: a default tristate toggle that starts in the middle status and never gets back again to it
47 |
48 | tstb_2_text.setText("Almost...");
49 | tstb_2.setOnToggleChanged(new TriStateToggleButton.OnToggleChanged() {
50 | @Override
51 | public void onToggle(TriStateToggleButton.ToggleStatus toggleStatus, boolean booleanToggleStatus, int toggleIntValue) {
52 | switch (toggleStatus) {
53 | case off: tstb_2_text.setText("Off"); break;
54 | case mid: tstb_2_text.setText("Almost..."); break;
55 | case on: tstb_2_text.setText("On"); break;
56 | }
57 | }
58 | });
59 |
60 | // Example 3: a customized tristate toggle that is undefined in the middle and enables / disables another toggle
61 |
62 | tstb_3_text.setText("undefined");
63 | tstb_3.setOnToggleChanged(new TriStateToggleButton.OnToggleChanged() {
64 | @Override
65 | public void onToggle(TriStateToggleButton.ToggleStatus toggleStatus, boolean booleanToggleStatus, int toggleIntValue) {
66 | switch (toggleStatus) {
67 | case off: tstb_3_text.setText("False"); break;
68 | case mid: tstb_3_text.setText("undefined"); break;
69 | case on: tstb_3_text.setText("True"); break;
70 | }
71 | }
72 | });
73 |
74 | // Example 4: an out of the box classic 2-state toggle, using booleans, controls toggle 3
75 |
76 | tstb_4.setToggleStatus(true);
77 | tstb_4.setOnToggleChanged(new TriStateToggleButton.OnToggleChanged() {
78 | @Override
79 | public void onToggle(TriStateToggleButton.ToggleStatus toggleStatus, boolean booleanToggleStatus, int toggleIntValue) {
80 | tstb_3.setEnabled(booleanToggleStatus);
81 | }
82 | });
83 |
84 | // Example 5: random restyle of toggle 3
85 |
86 | ((Button) findViewById(R.id.button_restyle)).setOnClickListener(new View.OnClickListener() {
87 | @Override
88 | public void onClick(View view) {
89 | restyle(tstb_3);
90 | }
91 | });
92 |
93 | }
94 |
95 | void restyle(TriStateToggleButton toggleButton) {
96 | Random rnd = new Random();
97 | for (int w = 0; w < 7; w++) {
98 | int randomColor = Color.argb(255, rnd.nextInt(256), rnd.nextInt(256), rnd.nextInt(256));
99 | switch (w) {
100 | case 0: toggleButton.setMidColor(randomColor); break;
101 | case 1: toggleButton.setBorderColor(randomColor); break;
102 | case 2: toggleButton.setOffBorderColor(randomColor); break;
103 | case 3: toggleButton.setOffColor(randomColor); break;
104 | case 4: toggleButton.setOnColor(randomColor); break;
105 | case 5: toggleButton.setSpotColor(randomColor); break;
106 | case 6: toggleButton.setSpotSize(rnd.nextInt(20) + 30); break;
107 | }
108 | }
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/main/res/layout/activity_sample.xml:
--------------------------------------------------------------------------------
1 |
2 |
14 |
15 |
20 |
27 |
33 |
34 |
41 |
42 |
43 |
48 |
55 |
62 |
63 |
70 |
71 |
72 |
77 |
84 |
97 |
104 |
105 |
106 |
107 |
112 |
119 |
125 |
126 |
127 |
128 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeppiMenozzi/TriStateToggleButton/3b21708073403039f8b3199805173a9b987bd95c/tristatetogglebutton_sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeppiMenozzi/TriStateToggleButton/3b21708073403039f8b3199805173a9b987bd95c/tristatetogglebutton_sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeppiMenozzi/TriStateToggleButton/3b21708073403039f8b3199805173a9b987bd95c/tristatetogglebutton_sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeppiMenozzi/TriStateToggleButton/3b21708073403039f8b3199805173a9b987bd95c/tristatetogglebutton_sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/BeppiMenozzi/TriStateToggleButton/3b21708073403039f8b3199805173a9b987bd95c/tristatetogglebutton_sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 |
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TriStateToggleButton Sample
3 |
4 |
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/tristatetogglebutton_sample/src/test/java/it/beppi/tristatetogglebutton/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package it.beppi.tristatetogglebutton;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------