├── .gitignore ├── .idea ├── .name ├── compiler.xml ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── gradle.xml ├── libraries │ ├── appcompat_v7_21_0_2.xml │ ├── support_annotations_21_0_2.xml │ └── support_v4_21_0_2.xml ├── misc.xml ├── modules.xml ├── scopes │ └── scope_settings.xml └── vcs.xml ├── .travis.yml ├── README.md ├── build.gradle ├── demoapp ├── .gitignore ├── build.gradle ├── demoapp.iml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── cardiomood │ │ └── andoid │ │ └── demo │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── cardiomood │ │ └── andoid │ │ └── demo │ │ └── MainActivity.java │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── layout │ └── activity_main.xml │ ├── menu │ └── menu_main.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── main ├── .gitignore ├── build.gradle ├── gradle.properties ├── main.iml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── cardiomood │ │ └── android │ │ └── controls │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── cardiomood │ │ └── android │ │ └── controls │ │ ├── CircledTextView.java │ │ ├── gauge │ │ ├── BatteryIndicatorGauge.java │ │ └── SpeedometerGauge.java │ │ └── progress │ │ └── CircularProgressBar.java │ └── res │ ├── drawable │ ├── img_empty_battery_horisontal.png │ ├── img_empty_battery_vertical.png │ └── spot_mask.png │ └── values │ └── attrs.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 18 | 19 | # Local configuration file (sdk path, etc) 20 | local.properties 21 | 22 | # Proguard folder generated by Eclipse 23 | proguard/ 24 | 25 | # Log Files 26 | *.log 27 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | AndroidWidgets -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /.idea/libraries/appcompat_v7_21_0_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/libraries/support_annotations_21_0_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/libraries/support_v4_21_0_2.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /.idea/misc.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 | 31 | 32 | 33 | 51 | 58 | 59 | 72 | 73 | 74 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 96 | 97 | localhost 98 | 5050 99 | 100 | 101 | 102 | 103 | 104 | 105 | Android API 19 Platform 106 | 107 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/scopes/scope_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | env: 3 | matrix: 4 | - ANDROID_SDKS=android-21 ANDROID_TARGET=android-21 5 | 6 | android: 7 | components: 8 | - build-tools-20.0.0 9 | - android-L 10 | 11 | script: 12 | - ./gradlew clean build 13 | 14 | #before_install: 15 | # - echo no | android create avd --force -n test -t $ANDROID_TARGET --abi $ANDROID_ABI 16 | # - emulator -avd test -no-skin -no-audio -no-window & 17 | 18 | #before_script: 19 | # - adb wait-for-device 20 | # - adb shell input keyevent 82 & 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](http://img.shields.io/travis/ntoskrnl/AndroidWidgets/master.svg?style=flat)](https://travis-ci.org/ntoskrnl/AndroidWidgets) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.cardiomood.android/android-widgets/badge.svg?style=flat)](https://maven-badges.herokuapp.com/maven-central/com.cardiomood.android/android-widgets) AndroidWidgets 2 | ============== 3 | 4 | Custom Android widgets used in cardiomood.com projects. 5 | 6 | ## Import to your project 7 | 8 | The library is available in maven repository. 9 | 10 | You can include your library in your **build.gradle**: 11 | 12 | ``` 13 | compile 'com.cardiomood.android:android-widgets:0.1.1' 14 | ``` 15 | 16 | If you are using Maven, add the following dependency to **pom.xml**: 17 | ```xml 18 | 19 | com.cardiomood.android 20 | android-widgets 21 | 0.1.1 22 | aar 23 | 24 | ``` 25 | 26 | ## Widgets 27 | 28 | At the moment, the library includes the following components: 29 | - **SpeedometerGauge** - a simple needle gauge that looks like speedometer 30 | - **BatteryIndicatorGauge** - an iPhone like pseudo-3d battery indicator 31 | - **CircularProgressBar** - a siple circular progress bar with a text inside 32 | - **CircledTextView** - a TextView that has a circle around it 33 | 34 | 35 | ## SpeedometerGauge 36 | 37 | This was moved from the previous repository: https://github.com/ntoskrnl/SpeedometerView 38 | 39 | Simple speedometer-like gauge with needle for Android. 40 | 41 | ![speedometerview-v1](https://f.cloud.github.com/assets/1446492/2292440/175bd3a8-a059-11e3-8f1e-67624fc92349.png) 42 | 43 | 44 | ### Usage 45 | 46 | Import the library to your project. 47 | 48 | In your layout xml-file add SpeedometerGauge as shown: 49 | 50 | ```xml 51 | 56 | ``` 57 | 58 | Configure SpeedometerGuge: 59 | 60 | ```java 61 | private SpeedometerGauge speedometer; 62 | 63 | // Customize SpeedometerGauge 64 | speedometer = (SpeedometerGauge) v.findViewById(R.id.speedometer); 65 | 66 | // Add label converter 67 | speedometer.setLabelConverter(new SpeedometerView.LabelConverter() { 68 | @Override 69 | public String getLabelFor(double progress, double maxProgress) { 70 | return String.valueOf((int) Math.round(progress)); 71 | } 72 | }); 73 | 74 | // configure value range and ticks 75 | speedometer.setMaxSpeed(300); 76 | speedometer.setMajorTickStep(30); 77 | speedometer.setMinorTicks(2); 78 | 79 | // Configure value range colors 80 | speedometer.addColoredRange(30, 140, Color.GREEN); 81 | speedometer.addColoredRange(140, 180, Color.YELLOW); 82 | speedometer.addColoredRange(180, 400, Color.RED); 83 | 84 | ``` 85 | -------------------------------------------------------------------------------- /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:1.0.0' 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 | } 19 | } 20 | -------------------------------------------------------------------------------- /demoapp/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /demoapp/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion "20.0.0" 6 | 7 | defaultConfig { 8 | applicationId "com.cardiomood.andoid.demo" 9 | minSdkVersion 9 10 | targetSdkVersion 21 11 | versionCode 1 12 | versionName "1.0" 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 | compile 'com.android.support:appcompat-v7:21.0.2' 25 | compile project(":main") 26 | } 27 | -------------------------------------------------------------------------------- /demoapp/demoapp.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /demoapp/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 /Users/antondanhsin/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 | -------------------------------------------------------------------------------- /demoapp/src/androidTest/java/com/cardiomood/andoid/demo/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.cardiomood.andoid.demo; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /demoapp/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /demoapp/src/main/java/com/cardiomood/andoid/demo/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.cardiomood.andoid.demo; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.support.v7.app.ActionBarActivity; 6 | import android.view.Menu; 7 | import android.view.MenuItem; 8 | 9 | import com.cardiomood.android.controls.gauge.BatteryIndicatorGauge; 10 | import com.cardiomood.android.controls.gauge.SpeedometerGauge; 11 | import com.cardiomood.android.controls.progress.CircularProgressBar; 12 | 13 | 14 | public class MainActivity extends ActionBarActivity { 15 | 16 | private SpeedometerGauge speedometer; 17 | private BatteryIndicatorGauge batteryindicator; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | 24 | speedometer = (SpeedometerGauge) findViewById(R.id.speedometer); 25 | speedometer.setMaxSpeed(50); 26 | speedometer.setLabelConverter(new SpeedometerGauge.LabelConverter() { 27 | @Override 28 | public String getLabelFor(double progress, double maxProgress) { 29 | return String.valueOf((int) Math.round(progress)); 30 | } 31 | }); 32 | speedometer.setMaxSpeed(50); 33 | speedometer.setMajorTickStep(5); 34 | speedometer.setMinorTicks(4); 35 | speedometer.addColoredRange(0, 30, Color.GREEN); 36 | speedometer.addColoredRange(30, 45, Color.YELLOW); 37 | speedometer.addColoredRange(45, 50, Color.RED); 38 | speedometer.setSpeed(33, 1000, 300); 39 | 40 | batteryindicator = (BatteryIndicatorGauge) findViewById(R.id.batteryindicator); 41 | batteryindicator.setValue(80, 1000, 300); 42 | 43 | CircularProgressBar circ = (CircularProgressBar) findViewById(R.id.circularprogress); 44 | circ.setProgress(90, 1000); 45 | } 46 | 47 | 48 | @Override 49 | public boolean onCreateOptionsMenu(Menu menu) { 50 | // Inflate the menu; this adds items to the action bar if it is present. 51 | getMenuInflater().inflate(R.menu.menu_main, menu); 52 | return true; 53 | } 54 | 55 | @Override 56 | public boolean onOptionsItemSelected(MenuItem item) { 57 | // Handle action bar item clicks here. The action bar will 58 | // automatically handle clicks on the Home/Up button, so long 59 | // as you specify a parent activity in AndroidManifest.xml. 60 | int id = item.getItemId(); 61 | 62 | //noinspection SimplifiableIfStatement 63 | if (id == R.id.action_settings) { 64 | return true; 65 | } 66 | 67 | return super.onOptionsItemSelected(item); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /demoapp/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntoskrnl/AndroidWidgets/278fcb93dce444ace248e8746e3f3e964d3c23ea/demoapp/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /demoapp/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntoskrnl/AndroidWidgets/278fcb93dce444ace248e8746e3f3e964d3c23ea/demoapp/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /demoapp/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntoskrnl/AndroidWidgets/278fcb93dce444ace248e8746e3f3e964d3c23ea/demoapp/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demoapp/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntoskrnl/AndroidWidgets/278fcb93dce444ace248e8746e3f3e964d3c23ea/demoapp/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /demoapp/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 11 | 12 | 17 | 18 | 24 | 25 | 31 | 32 | -------------------------------------------------------------------------------- /demoapp/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 4 | 6 | 7 | -------------------------------------------------------------------------------- /demoapp/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /demoapp/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /demoapp/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DemoApp 5 | Hello world! 6 | Settings 7 | 8 | 9 | -------------------------------------------------------------------------------- /demoapp/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /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 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | 20 | VERSION_NAME=0.1.1 21 | VERSION_CODE=1 22 | GROUP=com.cardiomood.android 23 | 24 | POM_DESCRIPTION=CardioMood Android Widgets library 25 | POM_URL=https://github.com/ntoskrnl/AndroidWidgets 26 | POM_SCM_URL=https://github.com/ntoskrnl/AndroidWidgets 27 | POM_SCM_CONNECTION=scm:git@github.com:ntoskrnl/AndroidWidgets.git 28 | POM_SCM_DEV_CONNECTION=scm:git@github.com:ntoskrnl/AndroidWidgets.git 29 | POM_LICENCE_NAME=The Apache Software License, Version 2.0 30 | POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt 31 | POM_LICENCE_DIST=repo 32 | POM_DEVELOPER_ID=ntoskrnl 33 | POM_DEVELOPER_NAME=Anton Danshin -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntoskrnl/AndroidWidgets/278fcb93dce444ace248e8746e3f3e964d3c23ea/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Dec 23 08:24:21 MSK 2014 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.2.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 | # For Cygwin, ensure paths are in UNIX format before anything is touched. 46 | if $cygwin ; then 47 | [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 48 | fi 49 | 50 | # Attempt to set APP_HOME 51 | # Resolve links: $0 may be a link 52 | PRG="$0" 53 | # Need this for relative symlinks. 54 | while [ -h "$PRG" ] ; do 55 | ls=`ls -ld "$PRG"` 56 | link=`expr "$ls" : '.*-> \(.*\)$'` 57 | if expr "$link" : '/.*' > /dev/null; then 58 | PRG="$link" 59 | else 60 | PRG=`dirname "$PRG"`"/$link" 61 | fi 62 | done 63 | SAVED="`pwd`" 64 | cd "`dirname \"$PRG\"`/" >&- 65 | APP_HOME="`pwd -P`" 66 | cd "$SAVED" >&- 67 | 68 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 69 | 70 | # Determine the Java command to use to start the JVM. 71 | if [ -n "$JAVA_HOME" ] ; then 72 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 73 | # IBM's JDK on AIX uses strange locations for the executables 74 | JAVACMD="$JAVA_HOME/jre/sh/java" 75 | else 76 | JAVACMD="$JAVA_HOME/bin/java" 77 | fi 78 | if [ ! -x "$JAVACMD" ] ; then 79 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 80 | 81 | Please set the JAVA_HOME variable in your environment to match the 82 | location of your Java installation." 83 | fi 84 | else 85 | JAVACMD="java" 86 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 87 | 88 | Please set the JAVA_HOME variable in your environment to match the 89 | location of your Java installation." 90 | fi 91 | 92 | # Increase the maximum file descriptors if we can. 93 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 94 | MAX_FD_LIMIT=`ulimit -H -n` 95 | if [ $? -eq 0 ] ; then 96 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 97 | MAX_FD="$MAX_FD_LIMIT" 98 | fi 99 | ulimit -n $MAX_FD 100 | if [ $? -ne 0 ] ; then 101 | warn "Could not set maximum file descriptor limit: $MAX_FD" 102 | fi 103 | else 104 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 105 | fi 106 | fi 107 | 108 | # For Darwin, add options to specify how the application appears in the dock 109 | if $darwin; then 110 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 111 | fi 112 | 113 | # For Cygwin, switch paths to Windows format before running java 114 | if $cygwin ; then 115 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 116 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /main/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /main/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 21 5 | buildToolsVersion "20.0.0" 6 | 7 | defaultConfig { 8 | minSdkVersion 9 9 | targetSdkVersion 21 10 | versionCode 1 11 | versionName "0.1.1" 12 | } 13 | buildTypes { 14 | release { 15 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 16 | } 17 | } 18 | 19 | lintOptions { 20 | abortOnError false 21 | } 22 | } 23 | 24 | dependencies { 25 | compile fileTree(dir: 'libs', include: ['*.jar']) 26 | } 27 | 28 | apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle' 29 | -------------------------------------------------------------------------------- /main/gradle.properties: -------------------------------------------------------------------------------- 1 | POM_NAME=CardioMood Android Widgets library 2 | POM_ARTIFACT_ID=android-widgets 3 | POM_PACKAGING=aar -------------------------------------------------------------------------------- /main/main.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /main/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 /Applications/Android Studio.app/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 | -------------------------------------------------------------------------------- /main/src/androidTest/java/com/cardiomood/android/controls/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.cardiomood.android.controls; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /main/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /main/src/main/java/com/cardiomood/android/controls/CircledTextView.java: -------------------------------------------------------------------------------- 1 | package com.cardiomood.android.controls; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.Rect; 8 | import android.util.AttributeSet; 9 | import android.widget.TextView; 10 | 11 | /** 12 | * Created by danon on 08.05.2014. 13 | */ 14 | public class CircledTextView extends TextView { 15 | 16 | private int circleColor = Color.LTGRAY; 17 | private float density = 1; 18 | 19 | public CircledTextView(Context context) { 20 | super(context); 21 | density = getResources().getDisplayMetrics().density; 22 | } 23 | 24 | public CircledTextView(Context context, AttributeSet attrs, int defStyle) { 25 | super(context, attrs, defStyle); 26 | density = getResources().getDisplayMetrics().density; 27 | } 28 | 29 | public CircledTextView(Context context, AttributeSet attrs) { 30 | super(context, attrs); 31 | density = getResources().getDisplayMetrics().density; 32 | } 33 | 34 | // @Override 35 | // protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 36 | // super.onMeasure(widthMeasureSpec, heightMeasureSpec); 37 | // 38 | // int measuredHeight = getMeasuredHeight(); 39 | // int measuredWidth = getMeasuredWidth(); 40 | // 41 | // int w = Math.max(measuredHeight, measuredWidth); 42 | // setMeasuredDimension(w, w); 43 | // } 44 | 45 | @Override 46 | protected void onDraw(Canvas canvas) { 47 | super.onDraw(canvas); 48 | 49 | Rect rect = canvas.getClipBounds(); 50 | Paint paint = new Paint(Paint.ANTI_ALIAS_FLAG); 51 | paint.setColor(circleColor); 52 | paint.setStrokeWidth(3*density); 53 | paint.setStyle(Paint.Style.STROKE); 54 | canvas.drawCircle(rect.centerX(), rect.centerY(), Math.max(rect.width(), rect.height())/2.0f - 1.5f*density, paint); 55 | } 56 | 57 | public void setCircleColor(int circleColor) { 58 | this.circleColor = circleColor; 59 | invalidate(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /main/src/main/java/com/cardiomood/android/controls/gauge/BatteryIndicatorGauge.java: -------------------------------------------------------------------------------- 1 | package com.cardiomood.android.controls.gauge; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.annotation.TargetApi; 5 | import android.content.Context; 6 | import android.graphics.Bitmap; 7 | import android.graphics.BitmapFactory; 8 | import android.graphics.BlurMaskFilter; 9 | import android.graphics.Canvas; 10 | import android.graphics.Color; 11 | import android.graphics.Paint; 12 | import android.graphics.RectF; 13 | import android.util.AttributeSet; 14 | import android.view.View; 15 | 16 | import com.cardiomood.android.controls.R; 17 | 18 | /** 19 | * Created by danon on 03.04.2014. 20 | */ 21 | public class BatteryIndicatorGauge extends View { 22 | 23 | public static final int HORIZONTAL = 0; 24 | public static final int VERTICAL = 1; 25 | public static final int DEFAULT_ORIENTATION = HORIZONTAL; 26 | 27 | public static final float DEFAULT_MAX = 100; 28 | public static final float DEFAULT_MIN = 0; 29 | public static final float DEFAULT_VALUE = 0; 30 | public static final ValueToColorConverter DEFAULT_CONVERTER = new ValueToColorConverter() { 31 | @Override 32 | public int getColorOf(BatteryIndicatorGauge view, float value) { 33 | float percentage = view.getPercentage(); 34 | if (percentage < 10.0f) { 35 | return Color.RED; 36 | } 37 | if (percentage < 20.0f) { 38 | return Color.rgb(255, 127, 0); 39 | } 40 | if (percentage < 40.0f) { 41 | return Color.rgb(255, 191, 0); 42 | } 43 | if (percentage < 50.0f) { 44 | return Color.rgb(255, 216, 0); 45 | } 46 | if (percentage < 70.0f) { 47 | return Color.rgb(255, 247, 0); 48 | } 49 | return Color.rgb(102, 255, 0); 50 | } 51 | }; 52 | 53 | private float max = DEFAULT_MAX; 54 | private float value = 0; 55 | private float min = DEFAULT_MIN; 56 | private int orientation = DEFAULT_ORIENTATION; 57 | private ValueToColorConverter valueToColorConverter; 58 | 59 | private float density = 1.0f; 60 | 61 | private Paint mPaint; 62 | private Paint bmpPaint; 63 | private Paint ovalPaint; 64 | private Bitmap batteryBitmap; 65 | 66 | public BatteryIndicatorGauge(Context context) { 67 | super(context); 68 | density = getResources().getDisplayMetrics().density; 69 | 70 | init(); 71 | } 72 | 73 | public BatteryIndicatorGauge(Context context, AttributeSet attrs) { 74 | super(context, attrs); 75 | density = getResources().getDisplayMetrics().density; 76 | 77 | // TypedArray attributes = context.getTheme().obtainStyledAttributes( 78 | // attrs, 79 | // R.styleable.BatteryIndicatorGauge, 80 | // 0, 0); 81 | // 82 | // try { 83 | // // read attributes 84 | // setMax(attributes.getFloat(R.styleable.BatteryIndicatorGauge_max, (float) DEFAULT_MAX)); 85 | // setMin(attributes.getFloat(R.styleable.BatteryIndicatorGauge_min, (float) DEFAULT_MIN)); 86 | // setValue(attributes.getFloat(R.styleable.BatteryIndicatorGauge_value, DEFAULT_VALUE)); 87 | // setOrientation(attributes.getInt(R.styleable.BatteryIndicatorGauge_orientation, DEFAULT_ORIENTATION)); 88 | // } finally { 89 | // attributes.recycle(); 90 | // } 91 | 92 | init(); 93 | } 94 | 95 | public float getMax() { 96 | return max; 97 | } 98 | 99 | public void setMax(float max) { 100 | if (max < min) { 101 | throw new IllegalArgumentException("Illegal value: max < min"); 102 | } 103 | 104 | this.max = max; 105 | invalidate(); 106 | } 107 | 108 | public float getMin() { 109 | return min; 110 | } 111 | 112 | public void setMin(float min) { 113 | if (max < min) { 114 | throw new IllegalArgumentException("Illegal value: min > max"); 115 | } 116 | this.min = min; 117 | invalidate(); 118 | } 119 | 120 | public float getValue() { 121 | return value; 122 | } 123 | 124 | public void setValue(float value) { 125 | if (value <= getMin()) 126 | value = getMin(); 127 | if (value >= getMax()) 128 | value = getMax(); 129 | this.value = value; 130 | invalidate(); 131 | } 132 | 133 | @TargetApi(11) 134 | public ValueAnimator setValue(float value, long duration, long delay) { 135 | if (value <= getMin()) 136 | value = getMin(); 137 | if (value >= getMax()) 138 | value = getMax(); 139 | 140 | ValueAnimator va = ValueAnimator.ofFloat(getValue(), value).setDuration(duration); 141 | va.setStartDelay(delay); 142 | va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 143 | @Override 144 | public void onAnimationUpdate(ValueAnimator animation) { 145 | float value = (Float) animation.getAnimatedValue(); 146 | setValue(value); 147 | } 148 | }); 149 | va.start(); 150 | return va; 151 | } 152 | 153 | public ValueToColorConverter getValueToColorConverter() { 154 | return valueToColorConverter; 155 | } 156 | 157 | public void setValueToColorConverter(ValueToColorConverter valueToColorConverter) { 158 | this.valueToColorConverter = valueToColorConverter; 159 | invalidate(); 160 | } 161 | 162 | private int getColorForValue(float value) { 163 | return valueToColorConverter == null ? DEFAULT_CONVERTER.getColorOf(this, value) : valueToColorConverter.getColorOf(this, value); 164 | } 165 | 166 | public int getOrientation() { 167 | return orientation; 168 | } 169 | 170 | public void setOrientation(int orientation) { 171 | if (orientation == VERTICAL || orientation == HORIZONTAL) 172 | this.orientation = orientation; 173 | else throw new IllegalArgumentException("Invalid orientation: " + orientation); 174 | 175 | if (getOrientation() == HORIZONTAL) 176 | batteryBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img_empty_battery_horisontal); 177 | else 178 | batteryBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img_empty_battery_vertical); 179 | } 180 | 181 | public float getPercentage() { 182 | return 100 * getValue() / (getMax() - getMin()); 183 | } 184 | 185 | @Override 186 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 187 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 188 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 189 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 190 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 191 | 192 | int width; 193 | int height; 194 | 195 | //Measure Width 196 | if (widthMode == MeasureSpec.EXACTLY) { 197 | //Must be this size 198 | width = widthSize; 199 | } else { 200 | width = -1; 201 | } 202 | 203 | //Measure Height 204 | if (heightMode == MeasureSpec.EXACTLY) { 205 | //Must be this size 206 | height = heightSize; 207 | } else { 208 | height = -1; 209 | } 210 | 211 | if (height >= 0 && width >= 0) { 212 | // it's ok 213 | } else if (width >= 0) { 214 | height = Math.round(width / getOriginalWidth() * getOriginalHeight()); 215 | } else if (height >= 0) { 216 | width = Math.round(height / getOriginalHeight() * getOriginalWidth()); 217 | } else { 218 | width = 0; 219 | height = 0; 220 | } 221 | 222 | //MUST CALL THIS 223 | setMeasuredDimension(width, height); 224 | } 225 | 226 | @Override 227 | protected void onDraw(Canvas canvas) { 228 | super.onDraw(canvas); 229 | 230 | // mPaint.setColor(Color.TRANSPARENT); 231 | // canvas.drawPaint(mPaint); 232 | 233 | if (getWidth() == 0 || getHeight() == 0) 234 | return; 235 | 236 | RectF rect = getRect(getWidth(), getHeight()); 237 | float alpha = rect.width()/getOriginalWidth(); 238 | mPaint.setColor(getColorForValue(getValue())); 239 | if (!isInEditMode()) 240 | mPaint.setMaskFilter(new BlurMaskFilter(3*alpha, BlurMaskFilter.Blur.NORMAL)); 241 | RectF contentRect = getBatteryContentRect(rect); 242 | float percentage = getPercentage(); 243 | if (getOrientation() == HORIZONTAL) { 244 | contentRect.right = contentRect.left + contentRect.width() * percentage/100; 245 | } else { 246 | contentRect.top = contentRect.top + contentRect.height() * (100 - percentage)/100; 247 | } 248 | canvas.drawRect(contentRect, mPaint); 249 | 250 | if (percentage > 0.5f && percentage < 99.0f) { 251 | ovalPaint.setColor(getColorForValue(getValue())); 252 | if (getOrientation() == HORIZONTAL) { 253 | canvas.drawOval(new RectF(contentRect.right - alpha * 20, contentRect.top, contentRect.right + alpha * 20, contentRect.bottom), ovalPaint); 254 | ovalPaint.setColor(Color.argb(6, 0, 0, 0)); 255 | canvas.drawOval(new RectF(contentRect.right - alpha * 20, contentRect.top, contentRect.right + alpha * 20, contentRect.bottom), ovalPaint); 256 | } else { 257 | canvas.drawOval(new RectF(contentRect.left, contentRect.top - alpha * 25, contentRect.right, contentRect.top + alpha * 25), ovalPaint); 258 | ovalPaint.setColor(Color.argb(6, 0, 0, 0)); 259 | canvas.drawOval(new RectF(contentRect.left, contentRect.top - alpha * 25, contentRect.right, contentRect.top + alpha * 25), ovalPaint); 260 | } 261 | 262 | mPaint.setColor(getColorForValue(getValue())); 263 | if (getOrientation() == HORIZONTAL) { 264 | canvas.drawOval(new RectF(contentRect.right - alpha * 12, contentRect.top + alpha * 20, contentRect.right + alpha * 12, contentRect.bottom - alpha * 20), mPaint); 265 | mPaint.setColor(Color.argb(80, 255, 255, 255)); 266 | if (!isInEditMode()) 267 | mPaint.setMaskFilter(new BlurMaskFilter(6 * alpha, BlurMaskFilter.Blur.NORMAL)); 268 | canvas.drawOval(new RectF(contentRect.right - alpha * 12, contentRect.top + alpha * 20, contentRect.right + alpha * 12, contentRect.bottom - alpha * 20), mPaint); 269 | } else { 270 | canvas.drawOval(new RectF(contentRect.left + alpha * 20, contentRect.top - alpha * 12, contentRect.right - alpha * 20, contentRect.top + alpha * 12), mPaint); 271 | mPaint.setColor(Color.argb(80, 255, 255, 255)); 272 | if (!isInEditMode()) 273 | mPaint.setMaskFilter(new BlurMaskFilter(6 * alpha, BlurMaskFilter.Blur.NORMAL)); 274 | canvas.drawOval(new RectF(contentRect.left + alpha * 20, contentRect.top - alpha * 12, contentRect.right - alpha * 20, contentRect.top + alpha * 12), mPaint); 275 | } 276 | } 277 | 278 | Bitmap bmp = Bitmap.createScaledBitmap(batteryBitmap, (int) rect.width(), (int) rect.height(), true); 279 | canvas.drawBitmap(bmp, rect.left, rect.top, bmpPaint); 280 | } 281 | 282 | private void init() { 283 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 284 | mPaint.setStyle(Paint.Style.FILL); 285 | 286 | bmpPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 287 | bmpPaint.setStyle(Paint.Style.FILL); 288 | 289 | ovalPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 290 | ovalPaint.setStyle(Paint.Style.FILL); 291 | 292 | if (getOrientation() == HORIZONTAL) 293 | batteryBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img_empty_battery_horisontal); 294 | else if (getOrientation() == VERTICAL) 295 | batteryBitmap = BitmapFactory.decodeResource(getResources(), R.drawable.img_empty_battery_vertical); 296 | else throw new IllegalStateException("Invalid orientation value: " + getOrientation()); 297 | } 298 | 299 | private float getOriginalWidth() { 300 | if (!isInEditMode()) { 301 | return batteryBitmap.getWidth(); 302 | } else { 303 | return (getOrientation() == HORIZONTAL) ? 467 : 216; 304 | } 305 | } 306 | 307 | private float getOriginalHeight() { 308 | if (!isInEditMode()) { 309 | return batteryBitmap.getHeight(); 310 | } else { 311 | return (getOrientation() == HORIZONTAL) ? 216 : 467; 312 | } 313 | } 314 | 315 | private RectF getRect(float w, float h) { 316 | float availableWidth = w - getPaddingLeft() - getPaddingRight(); 317 | float availableHeight = h - getPaddingTop() - getPaddingBottom(); 318 | if (availableWidth / availableHeight >= getOriginalWidth() / getOriginalHeight()) { 319 | // we have abundant space horizontally 320 | float alpha = availableHeight / getOriginalHeight(); 321 | float x = getPaddingLeft() + (availableWidth - alpha*getOriginalWidth()) / 2.0f; 322 | float y = getPaddingTop(); 323 | return new RectF(x, y, x + alpha*getOriginalWidth(), h - getPaddingBottom()); 324 | } else { 325 | // too much space vertically 326 | float alpha = availableWidth / getOriginalWidth(); 327 | float x = getPaddingLeft(); 328 | float y = getPaddingTop() + (availableHeight - alpha*getOriginalHeight()) / 2.0f; 329 | return new RectF(x, y, w - getPaddingRight(), y + alpha*getOriginalHeight()); 330 | } 331 | } 332 | 333 | private RectF getBatteryContentRect(RectF rect) { 334 | if (getOrientation() == HORIZONTAL) { 335 | float alpha = rect.width() / getOriginalWidth(); 336 | return new RectF( 337 | rect.left + 20*alpha, 338 | rect.top + 3*alpha, 339 | rect.right - 40*alpha, 340 | rect.bottom - 3*alpha 341 | ); 342 | } else if (getOrientation() == VERTICAL) { 343 | float alpha = rect.height() / getOriginalHeight(); 344 | return new RectF( 345 | rect.left + 4*alpha, 346 | rect.top + 40*alpha, 347 | rect.right - 4*alpha, 348 | rect.bottom - 20*alpha 349 | ); 350 | } else throw new IllegalStateException("Invalid orientation value: " + getOrientation()); 351 | } 352 | 353 | public static interface ValueToColorConverter { 354 | 355 | int getColorOf(BatteryIndicatorGauge view, float value); 356 | 357 | } 358 | } 359 | -------------------------------------------------------------------------------- /main/src/main/java/com/cardiomood/android/controls/gauge/SpeedometerGauge.java: -------------------------------------------------------------------------------- 1 | package com.cardiomood.android.controls.gauge; 2 | 3 | import android.animation.TypeEvaluator; 4 | import android.animation.ValueAnimator; 5 | import android.annotation.TargetApi; 6 | import android.content.Context; 7 | import android.graphics.Bitmap; 8 | import android.graphics.BitmapFactory; 9 | import android.graphics.Canvas; 10 | import android.graphics.Color; 11 | import android.graphics.Paint; 12 | import android.graphics.RectF; 13 | import android.os.Build; 14 | import android.util.AttributeSet; 15 | import android.view.View; 16 | 17 | import com.cardiomood.android.controls.R; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | 22 | /** 23 | * Speedometer with needle. 24 | * 25 | * Created by danon on 26.02.14. 26 | * @version 1.0 27 | * @author Anton Danshin anton.danshin@frtk.ru 28 | */ 29 | public class SpeedometerGauge extends View { 30 | 31 | private static final String TAG = SpeedometerGauge.class.getSimpleName(); 32 | 33 | public static final double DEFAULT_MAX_SPEED = 100.0; 34 | public static final double DEFAULT_MAJOR_TICK_STEP = 20.0; 35 | public static final int DEFAULT_MINOR_TICKS = 1; 36 | public static final int DEFAULT_LABEL_TEXT_SIZE_DP = 12; 37 | public static final int DEFAULT_UNITS_TEXT_SIZE_DP = 24; 38 | 39 | private double maxSpeed = DEFAULT_MAX_SPEED; 40 | private double speed = 0; 41 | private int defaultColor = Color.rgb(180, 180, 180); 42 | private double majorTickStep = DEFAULT_MAJOR_TICK_STEP; 43 | private int minorTicks = DEFAULT_MINOR_TICKS; 44 | private LabelConverter labelConverter; 45 | private String unitsText = "km/h"; 46 | 47 | private List ranges = new ArrayList(); 48 | 49 | private Paint backgroundPaint; 50 | private Paint backgroundInnerPaint; 51 | private Paint maskPaint; 52 | private Paint needlePaint; 53 | private Paint ticksPaint; 54 | private Paint txtPaint; 55 | private Paint unitsPaint; 56 | private Paint colorLinePaint; 57 | private int labelTextSize; 58 | private int unitsTextSize; 59 | 60 | private Bitmap mMask; 61 | 62 | public SpeedometerGauge(Context context) { 63 | super(context); 64 | init(); 65 | 66 | float density = getResources().getDisplayMetrics().density; 67 | setLabelTextSize(Math.round(DEFAULT_LABEL_TEXT_SIZE_DP * density)); 68 | setUnitsTextSize(Math.round(DEFAULT_UNITS_TEXT_SIZE_DP * density)); 69 | } 70 | 71 | public SpeedometerGauge(Context context, AttributeSet attrs) { 72 | super(context, attrs); 73 | init(); 74 | 75 | float density = getResources().getDisplayMetrics().density; 76 | setLabelTextSize(Math.round(DEFAULT_LABEL_TEXT_SIZE_DP * density)); 77 | setUnitsTextSize(Math.round(DEFAULT_UNITS_TEXT_SIZE_DP * density)); 78 | } 79 | 80 | public double getMaxSpeed() { 81 | return maxSpeed; 82 | } 83 | 84 | public void setMaxSpeed(double maxSpeed) { 85 | if (maxSpeed <= 0) 86 | throw new IllegalArgumentException("Non-positive value specified as max speed."); 87 | this.maxSpeed = maxSpeed; 88 | invalidate(); 89 | } 90 | 91 | public double getSpeed() { 92 | return speed; 93 | } 94 | 95 | public void setSpeed(double speed) { 96 | if (speed < 0) 97 | throw new IllegalArgumentException("Non-positive value specified as a speed."); 98 | if (speed > maxSpeed) 99 | speed = maxSpeed; 100 | this.speed = speed; 101 | invalidate(); 102 | } 103 | 104 | public String getUnitsText() { 105 | return unitsText; 106 | } 107 | 108 | public void setUnitsText(String unitsText) { 109 | this.unitsText = unitsText; 110 | invalidate(); 111 | } 112 | 113 | @TargetApi(11) 114 | public ValueAnimator setSpeed(double progress, long duration, long startDelay) { 115 | if (progress < 0) 116 | throw new IllegalArgumentException("Negative value specified as a speed."); 117 | 118 | if (progress > maxSpeed) 119 | progress = maxSpeed; 120 | 121 | ValueAnimator va = ValueAnimator.ofObject(new TypeEvaluator() { 122 | @Override 123 | public Double evaluate(float fraction, Double startValue, Double endValue) { 124 | return startValue + fraction * (endValue - startValue); 125 | } 126 | }, Double.valueOf(getSpeed()), Double.valueOf(progress)); 127 | 128 | va.setDuration(duration); 129 | va.setStartDelay(startDelay); 130 | va.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 131 | public void onAnimationUpdate(ValueAnimator animation) { 132 | Double value = (Double) animation.getAnimatedValue(); 133 | if (value != null) 134 | setSpeed(value); 135 | } 136 | }); 137 | va.start(); 138 | return va; 139 | } 140 | 141 | @TargetApi(11) 142 | public ValueAnimator setSpeed(double progress, boolean animate) { 143 | return setSpeed(progress, 1500, 200); 144 | } 145 | 146 | public int getDefaultColor() { 147 | return defaultColor; 148 | } 149 | 150 | public void setDefaultColor(int defaultColor) { 151 | this.defaultColor = defaultColor; 152 | invalidate(); 153 | } 154 | 155 | public double getMajorTickStep() { 156 | return majorTickStep; 157 | } 158 | 159 | public void setMajorTickStep(double majorTickStep) { 160 | if (majorTickStep <= 0) 161 | throw new IllegalArgumentException("Non-positive value specified as a major tick step."); 162 | this.majorTickStep = majorTickStep; 163 | invalidate(); 164 | } 165 | 166 | public int getMinorTicks() { 167 | return minorTicks; 168 | } 169 | 170 | public void setMinorTicks(int minorTicks) { 171 | this.minorTicks = minorTicks; 172 | invalidate(); 173 | } 174 | 175 | public LabelConverter getLabelConverter() { 176 | return labelConverter; 177 | } 178 | 179 | public void setLabelConverter(LabelConverter labelConverter) { 180 | this.labelConverter = labelConverter; 181 | invalidate(); 182 | } 183 | 184 | public void clearColoredRanges() { 185 | ranges.clear(); 186 | invalidate(); 187 | } 188 | 189 | public void addColoredRange(double begin, double end, int color) { 190 | if (begin >= end) 191 | throw new IllegalArgumentException("Incorrect number range specified!"); 192 | if (begin < - 5.0/160* maxSpeed) 193 | begin = - 5.0/160* maxSpeed; 194 | if (end > maxSpeed * (5.0/160 + 1)) 195 | end = maxSpeed * (5.0/160 + 1); 196 | ranges.add(new ColoredRange(color, begin, end)); 197 | invalidate(); 198 | } 199 | 200 | public int getLabelTextSize() { 201 | return labelTextSize; 202 | } 203 | 204 | public void setLabelTextSize(int labelTextSize) { 205 | this.labelTextSize = labelTextSize; 206 | if (txtPaint != null) { 207 | txtPaint.setTextSize(labelTextSize); 208 | invalidate(); 209 | } 210 | } 211 | 212 | public int getUnitsTextSize() { 213 | return unitsTextSize; 214 | } 215 | 216 | public void setUnitsTextSize(int unitsTextSize) { 217 | this.unitsTextSize = unitsTextSize; 218 | if (unitsPaint != null) { 219 | unitsPaint.setTextSize(unitsTextSize); 220 | invalidate(); 221 | } 222 | } 223 | 224 | @Override 225 | protected void onDraw(Canvas canvas) { 226 | super.onDraw(canvas); 227 | 228 | // Clear canvas 229 | canvas.drawColor(Color.TRANSPARENT); 230 | 231 | // Draw Metallic Arc and background 232 | drawBackground(canvas); 233 | 234 | // Draw Ticks and colored arc 235 | drawTicks(canvas); 236 | 237 | // Draw Needle 238 | drawNeedle(canvas); 239 | } 240 | 241 | @Override 242 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 243 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 244 | int widthSize = MeasureSpec.getSize(widthMeasureSpec); 245 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 246 | int heightSize = MeasureSpec.getSize(heightMeasureSpec); 247 | 248 | int width; 249 | int height; 250 | 251 | //Measure Width 252 | if (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST) { 253 | //Must be this size 254 | width = widthSize; 255 | } else { 256 | width = -1; 257 | } 258 | 259 | //Measure Height 260 | if (heightMode == MeasureSpec.EXACTLY || heightMode == MeasureSpec.AT_MOST) { 261 | //Must be this size 262 | height = heightSize; 263 | } else { 264 | height = -1; 265 | } 266 | 267 | if (height >= 0 && width >= 0) { 268 | width = Math.min(height, width); 269 | height = width/2; 270 | } else if (width >= 0) { 271 | height = width/2; 272 | } else if (height >= 0) { 273 | width = height*2; 274 | } else { 275 | width = 0; 276 | height = 0; 277 | } 278 | 279 | //MUST CALL THIS 280 | setMeasuredDimension(width, height); 281 | } 282 | 283 | private void drawNeedle(Canvas canvas) { 284 | RectF oval = getOval(canvas, 1); 285 | float radius = oval.width()*0.35f + 10; 286 | RectF smallOval = getOval(canvas, 0.2f); 287 | 288 | float angle = 10 + (float) (getSpeed()/ getMaxSpeed()*160); 289 | canvas.drawLine( 290 | (float) (oval.centerX() + Math.cos((180 - angle) / 180 * Math.PI) * smallOval.width()*0.5f), 291 | (float) (oval.centerY() - Math.sin(angle / 180 * Math.PI) * smallOval.width()*0.5f), 292 | (float) (oval.centerX() + Math.cos((180 - angle) / 180 * Math.PI) * (radius)), 293 | (float) (oval.centerY() - Math.sin(angle / 180 * Math.PI) * (radius)), 294 | needlePaint 295 | ); 296 | 297 | 298 | canvas.drawArc(smallOval, 180, 180, true, backgroundPaint); 299 | } 300 | 301 | private void drawTicks(Canvas canvas) { 302 | float availableAngle = 160; 303 | float majorStep = (float) (majorTickStep/ maxSpeed *availableAngle); 304 | float minorStep = majorStep / (1 + minorTicks); 305 | 306 | float majorTicksLength = 30; 307 | float minorTicksLength = majorTicksLength/2; 308 | 309 | RectF oval = getOval(canvas, 1); 310 | float radius = oval.width()*0.35f; 311 | 312 | float currentAngle = 10; 313 | double curProgress = 0; 314 | while (currentAngle <= 170) { 315 | 316 | canvas.drawLine( 317 | (float) (oval.centerX() + Math.cos((180 - currentAngle) / 180 * Math.PI)*(radius-majorTicksLength/2)), 318 | (float) (oval.centerY() - Math.sin(currentAngle / 180 * Math.PI)*(radius-majorTicksLength/2)), 319 | (float) (oval.centerX() + Math.cos((180 - currentAngle) / 180 * Math.PI)*(radius+majorTicksLength/2)), 320 | (float) (oval.centerY() - Math.sin(currentAngle / 180 * Math.PI)*(radius+majorTicksLength/2)), 321 | ticksPaint 322 | ); 323 | 324 | for (int i=1; i<=minorTicks; i++) { 325 | float angle = currentAngle + i*minorStep; 326 | if (angle >= 170 + minorStep/2) { 327 | break; 328 | } 329 | canvas.drawLine( 330 | (float) (oval.centerX() + Math.cos((180 - angle) / 180 * Math.PI) * radius), 331 | (float) (oval.centerY() - Math.sin(angle / 180 * Math.PI) * radius), 332 | (float) (oval.centerX() + Math.cos((180 - angle) / 180 * Math.PI) * (radius + minorTicksLength)), 333 | (float) (oval.centerY() - Math.sin(angle / 180 * Math.PI) * (radius + minorTicksLength)), 334 | ticksPaint 335 | ); 336 | } 337 | 338 | if (labelConverter != null) { 339 | 340 | canvas.save(); 341 | canvas.rotate(180 + currentAngle, oval.centerX(), oval.centerY()); 342 | float txtX = oval.centerX() + radius + majorTicksLength/2 + 8; 343 | float txtY = oval.centerY(); 344 | canvas.rotate(+90, txtX, txtY); 345 | canvas.drawText(labelConverter.getLabelFor(curProgress, maxSpeed), txtX, txtY, txtPaint); 346 | canvas.restore(); 347 | } 348 | 349 | currentAngle += majorStep; 350 | curProgress += majorTickStep; 351 | } 352 | 353 | RectF smallOval = getOval(canvas, 0.7f); 354 | colorLinePaint.setColor(defaultColor); 355 | canvas.drawArc(smallOval, 185, 170, false, colorLinePaint); 356 | 357 | for (ColoredRange range: ranges) { 358 | colorLinePaint.setColor(range.getColor()); 359 | canvas.drawArc(smallOval, (float) (190 + range.getBegin()/ maxSpeed *160), (float) ((range.getEnd() - range.getBegin())/ maxSpeed *160), false, colorLinePaint); 360 | } 361 | } 362 | 363 | private RectF getOval(Canvas canvas, float factor) { 364 | RectF oval; 365 | final int canvasWidth = canvas.getWidth() - getPaddingLeft() - getPaddingRight(); 366 | final int canvasHeight = canvas.getHeight() - getPaddingTop() - getPaddingBottom(); 367 | 368 | if (canvasHeight*2 >= canvasWidth) { 369 | oval = new RectF(0, 0, canvasWidth*factor, canvasWidth*factor); 370 | } else { 371 | oval = new RectF(0, 0, canvasHeight*2*factor, canvasHeight*2*factor); 372 | } 373 | 374 | oval.offset((canvasWidth-oval.width())/2 + getPaddingLeft(), (canvasHeight*2-oval.height())/2 + getPaddingTop()); 375 | 376 | return oval; 377 | } 378 | 379 | private void drawBackground(Canvas canvas) { 380 | RectF oval = getOval(canvas, 1); 381 | canvas.drawArc(oval, 180, 180, true, backgroundPaint); 382 | 383 | RectF innerOval = getOval(canvas, 0.9f); 384 | canvas.drawArc(innerOval, 180, 180, true, backgroundInnerPaint); 385 | 386 | Bitmap mask = Bitmap.createScaledBitmap(mMask, (int) (oval.width() * 1.1), (int) (oval.height() * 1.1) / 2, true); 387 | canvas.drawBitmap(mask, oval.centerX() - oval.width()*1.1f/2, oval.centerY()-oval.width()*1.1f/2, maskPaint); 388 | 389 | canvas.drawText(unitsText, oval.centerX(), oval.centerY()/1.5f, unitsPaint); 390 | } 391 | 392 | @SuppressWarnings("NewApi") 393 | private void init() { 394 | if (Build.VERSION.SDK_INT >= 11 && !isInEditMode()) { 395 | setLayerType(View.LAYER_TYPE_HARDWARE, null); 396 | } 397 | 398 | backgroundPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 399 | backgroundPaint.setStyle(Paint.Style.FILL); 400 | backgroundPaint.setColor(Color.rgb(127, 127, 127)); 401 | 402 | backgroundInnerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 403 | backgroundInnerPaint.setStyle(Paint.Style.FILL); 404 | backgroundInnerPaint.setColor(Color.rgb(150, 150, 150)); 405 | 406 | txtPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 407 | txtPaint.setColor(Color.WHITE); 408 | txtPaint.setTextSize(labelTextSize); 409 | txtPaint.setTextAlign(Paint.Align.CENTER); 410 | txtPaint.setLinearText(true); 411 | 412 | unitsPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 413 | unitsPaint.setColor(Color.WHITE); 414 | unitsPaint.setTextSize(unitsTextSize); 415 | unitsPaint.setTextAlign(Paint.Align.CENTER); 416 | unitsPaint.setLinearText(true); 417 | 418 | mMask = BitmapFactory.decodeResource(getResources(), R.drawable.spot_mask); 419 | mMask = Bitmap.createBitmap(mMask, 0, 0, mMask.getWidth(), mMask.getHeight() / 2); 420 | 421 | maskPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 422 | maskPaint.setDither(true); 423 | 424 | ticksPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 425 | ticksPaint.setStrokeWidth(3.0f); 426 | ticksPaint.setStyle(Paint.Style.STROKE); 427 | ticksPaint.setColor(defaultColor); 428 | 429 | colorLinePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 430 | colorLinePaint.setStyle(Paint.Style.STROKE); 431 | colorLinePaint.setStrokeWidth(5); 432 | colorLinePaint.setColor(defaultColor); 433 | 434 | needlePaint = new Paint(Paint.ANTI_ALIAS_FLAG); 435 | needlePaint.setStrokeWidth(5); 436 | needlePaint.setStyle(Paint.Style.STROKE); 437 | needlePaint.setColor(Color.argb(200, 255, 0, 0)); 438 | } 439 | 440 | 441 | public static interface LabelConverter { 442 | 443 | String getLabelFor(double progress, double maxProgress); 444 | 445 | } 446 | 447 | public static class ColoredRange { 448 | 449 | private int color; 450 | private double begin; 451 | private double end; 452 | 453 | public ColoredRange(int color, double begin, double end) { 454 | this.color = color; 455 | this.begin = begin; 456 | this.end = end; 457 | } 458 | 459 | public int getColor() { 460 | return color; 461 | } 462 | 463 | public void setColor(int color) { 464 | this.color = color; 465 | } 466 | 467 | public double getBegin() { 468 | return begin; 469 | } 470 | 471 | public void setBegin(double begin) { 472 | this.begin = begin; 473 | } 474 | 475 | public double getEnd() { 476 | return end; 477 | } 478 | 479 | public void setEnd(double end) { 480 | this.end = end; 481 | } 482 | } 483 | 484 | } 485 | -------------------------------------------------------------------------------- /main/src/main/java/com/cardiomood/android/controls/progress/CircularProgressBar.java: -------------------------------------------------------------------------------- 1 | package com.cardiomood.android.controls.progress; 2 | 3 | import android.animation.ValueAnimator; 4 | import android.annotation.TargetApi; 5 | import android.content.Context; 6 | import android.graphics.Canvas; 7 | import android.graphics.Color; 8 | import android.graphics.Paint; 9 | import android.graphics.RectF; 10 | import android.util.AttributeSet; 11 | import android.view.View; 12 | 13 | /** 14 | * Created by danon on 01.03.14. 15 | */ 16 | public class CircularProgressBar extends View { 17 | 18 | public static final float DEFAULT_MAX = 100; 19 | public static final float DEFAULT_MIN = 0; 20 | public static final int DEFAULT_LINE_WIDTH = 60; 21 | public static final int DEFAULT_COLOR = Color.RED; 22 | public static final int DEFAULT_TEXT_SIZE = 20; 23 | 24 | private float max = DEFAULT_MAX; 25 | private float progress = 0; 26 | private float min = DEFAULT_MIN; 27 | private float lineWidth = DEFAULT_LINE_WIDTH; 28 | private int color = DEFAULT_COLOR; 29 | private float textSize = 0; 30 | private int textColor = Color.BLACK; 31 | private LabelConverter labelConverter = null; 32 | 33 | private float density = 1.0f; 34 | 35 | private Paint mPaint; 36 | private Paint txtPaint; 37 | 38 | 39 | public CircularProgressBar(Context context) { 40 | super(context); 41 | density = getResources().getDisplayMetrics().density; 42 | textSize = DEFAULT_TEXT_SIZE*density; 43 | init(); 44 | } 45 | 46 | public CircularProgressBar(Context context, AttributeSet attrs) { 47 | super(context, attrs); 48 | 49 | density = getResources().getDisplayMetrics().density; 50 | textSize = DEFAULT_TEXT_SIZE*density; 51 | 52 | // density = getResources().getDisplayMetrics().density; 53 | // TypedArray attributes = context.getTheme().obtainStyledAttributes( 54 | // attrs, 55 | // R.styleable.CircularProgressBar, 56 | // 0, 0); 57 | // 58 | // try { 59 | // // read attributes 60 | // setMax(attributes.getFloat(R.styleable.CircularProgressBar_max, (float) DEFAULT_MAX)); 61 | // setMin(attributes.getFloat(R.styleable.CircularProgressBar_min, (float) DEFAULT_MIN)); 62 | // setProgress(attributes.getFloat(R.styleable.CircularProgressBar_progress, 0)); 63 | // setLineWidth(attributes.getDimensionPixelSize(R.styleable.CircularProgressBar_lineWidth, DEFAULT_LINE_WIDTH)); 64 | // setColor(attributes.getColor(R.styleable.CircularProgressBar_color, DEFAULT_COLOR)); 65 | // setTextSize(attributes.getDimensionPixelSize(R.styleable.CircularProgressBar_textSize, Math.round(DEFAULT_TEXT_SIZE * density))); 66 | // } finally { 67 | // attributes.recycle(); 68 | // } 69 | 70 | init(); 71 | } 72 | 73 | public CircularProgressBar(Context context, AttributeSet attrs, int defStyleAttr) { 74 | super(context, attrs, defStyleAttr); 75 | 76 | density = getResources().getDisplayMetrics().density; 77 | textSize = DEFAULT_TEXT_SIZE*density; 78 | 79 | init(); 80 | } 81 | 82 | public float getMax() { 83 | return max; 84 | } 85 | 86 | public void setMax(float max) { 87 | if (max < min) { 88 | throw new IllegalArgumentException("Illegal value: max < min"); 89 | } 90 | 91 | this.max = max; 92 | invalidate(); 93 | } 94 | 95 | public float getProgress() { 96 | return progress; 97 | } 98 | 99 | public void setProgress(float progress) { 100 | if (progress > max || progress < min) { 101 | throw new IllegalArgumentException("Illegal value: progress is outside range [min, max]"); 102 | } 103 | this.progress = progress; 104 | invalidate(); 105 | } 106 | 107 | @TargetApi(11) 108 | public ValueAnimator setProgress(float progress, long duration) { 109 | if (progress > max || progress < min) { 110 | throw new IllegalArgumentException("Illegal value: progress is outside range [min, max]"); 111 | } 112 | ValueAnimator animator = ValueAnimator.ofFloat(getProgress(), progress).setDuration(duration); 113 | animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { 114 | @Override 115 | public void onAnimationUpdate(ValueAnimator animation) { 116 | float f = (Float) animation.getAnimatedValue(); 117 | setProgress(f); 118 | } 119 | }); 120 | animator.start(); 121 | return animator; 122 | } 123 | 124 | public float getMin() { 125 | return min; 126 | } 127 | 128 | public void setMin(float min) { 129 | if (max < min) { 130 | throw new IllegalArgumentException("Illegal value: min > max"); 131 | } 132 | this.min = min; 133 | invalidate(); 134 | } 135 | 136 | public float getLineWidth() { 137 | return lineWidth; 138 | } 139 | 140 | public void setLineWidth(float lineWidth) { 141 | if (lineWidth < 0) { 142 | throw new IllegalArgumentException("Illegal value: lineWidth < 0"); 143 | } 144 | this.lineWidth = lineWidth; 145 | invalidate(); 146 | } 147 | 148 | public int getColor() { 149 | return color; 150 | } 151 | 152 | public void setColor(int color) { 153 | this.color = color; 154 | invalidate(); 155 | } 156 | 157 | public int getTextColor() { 158 | return textColor; 159 | } 160 | 161 | public void setTextColor(int color) { 162 | this.textColor = textColor; 163 | if (txtPaint != null) 164 | txtPaint.setColor(color); 165 | invalidate(); 166 | } 167 | 168 | public float getTextSize() { 169 | return textSize; 170 | } 171 | 172 | public void setTextSize(float textSize) { 173 | this.textSize = textSize; 174 | if (txtPaint != null) 175 | txtPaint.setTextSize(textSize); 176 | invalidate(); 177 | } 178 | 179 | public LabelConverter getLabelConverter() { 180 | return labelConverter; 181 | } 182 | 183 | public void setLabelConverter(LabelConverter labelConverter) { 184 | this.labelConverter = labelConverter; 185 | invalidate(); 186 | } 187 | 188 | @Override 189 | protected void onDraw(Canvas canvas) { 190 | super.onDraw(canvas); 191 | 192 | // Clear canvas 193 | canvas.drawColor(Color.TRANSPARENT); 194 | 195 | RectF oval = getOval(getWidth(), getHeight(), 1); 196 | 197 | // background circle 198 | mPaint.setStrokeWidth(lineWidth); 199 | mPaint.setColor(Color.LTGRAY); 200 | canvas.drawArc(oval, 0, 360, false, mPaint); 201 | 202 | // current progress arc 203 | float angle = 360 * Math.abs((progress) / (max - min)); 204 | mPaint.setColor(color); 205 | canvas.drawArc(oval, -90, angle, false, mPaint); 206 | 207 | if (labelConverter != null) { 208 | String text = labelConverter.getLabelFor(progress, max, txtPaint); 209 | 210 | if (text != null) { 211 | // Rect bounds = new Rect(); 212 | // txtPaint.getTextBounds(text, 0, text.length(), bounds); 213 | canvas.drawText(text, oval.centerX(), oval.centerY() - (txtPaint.descent() + txtPaint.ascent()) / 2, txtPaint); 214 | } 215 | } 216 | } 217 | 218 | private RectF getOval(int w, int h, float factor) { 219 | RectF oval; 220 | final int canvasWidth = w - getPaddingLeft() - getPaddingRight() - (int) getLineWidth(); 221 | final int canvasHeight = h - getPaddingTop() - getPaddingBottom() - (int) getLineWidth(); 222 | 223 | if (canvasHeight >= canvasWidth) { 224 | oval = new RectF(0, 0, canvasWidth*factor, canvasWidth*factor); 225 | } else { 226 | oval = new RectF(0, 0, canvasHeight*factor, canvasHeight*factor); 227 | } 228 | 229 | oval.offset((canvasWidth-oval.width())/2 + getPaddingLeft()+getLineWidth()/2, (canvasHeight-oval.height())/2 + getPaddingTop()+getLineWidth()/2); 230 | 231 | return oval; 232 | } 233 | 234 | private void init() { 235 | mPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 236 | mPaint.setStyle(Paint.Style.STROKE); 237 | 238 | txtPaint = new Paint(Paint.ANTI_ALIAS_FLAG); 239 | txtPaint.setTextSize(textSize); 240 | txtPaint.setColor(textColor); 241 | txtPaint.setTextAlign(Paint.Align.CENTER); 242 | } 243 | 244 | public static interface LabelConverter { 245 | String getLabelFor(float progress, float max, Paint paint); 246 | } 247 | } 248 | -------------------------------------------------------------------------------- /main/src/main/res/drawable/img_empty_battery_horisontal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntoskrnl/AndroidWidgets/278fcb93dce444ace248e8746e3f3e964d3c23ea/main/src/main/res/drawable/img_empty_battery_horisontal.png -------------------------------------------------------------------------------- /main/src/main/res/drawable/img_empty_battery_vertical.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntoskrnl/AndroidWidgets/278fcb93dce444ace248e8746e3f3e964d3c23ea/main/src/main/res/drawable/img_empty_battery_vertical.png -------------------------------------------------------------------------------- /main/src/main/res/drawable/spot_mask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ntoskrnl/AndroidWidgets/278fcb93dce444ace248e8746e3f3e964d3c23ea/main/src/main/res/drawable/spot_mask.png -------------------------------------------------------------------------------- /main/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':main', ':demoapp' --------------------------------------------------------------------------------