├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── mycover.png │ │ │ │ ├── icon_like.png │ │ │ │ ├── icon_next.png │ │ │ │ └── icon_previous.png │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── values-v21 │ │ │ │ └── styles.xml │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── strings.xml │ │ │ │ ├── dimens.xml │ │ │ │ └── styles.xml │ │ │ ├── menu │ │ │ │ └── menu_main.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ └── layout │ │ │ │ └── activity_main.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── androgeek │ │ │ └── material │ │ │ └── musicplayer │ │ │ └── MainActivity.java │ └── androidTest │ │ └── java │ │ └── androgeek │ │ └── material │ │ └── musicplayer │ │ └── ApplicationTest.java ├── build.gradle ├── proguard-rules.pro └── app.iml ├── library ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ └── attrs.xml │ │ │ └── drawable │ │ │ │ ├── icon_play.png │ │ │ │ └── icon_pause.png │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── androgeek │ │ │ └── material │ │ │ └── library │ │ │ └── MaterialMusicPlayerView.java │ └── androidTest │ │ └── java │ │ └── androgeek │ │ └── material │ │ └── library │ │ └── ApplicationTest.java ├── build.gradle ├── proguard-rules.pro └── library.iml ├── .idea ├── .name ├── copyright │ └── profiles_settings.xml ├── encodings.xml ├── vcs.xml ├── modules.xml ├── runConfigurations.xml ├── gradle.xml ├── compiler.xml └── misc.xml ├── settings.gradle ├── library-release.aar ├── art └── MaterialScreenshot.png ├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── MusicPlayerView.iml ├── Material_MusicPlayerView.iml ├── gradlew.bat ├── README.md └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | Material_MusicPlayerView -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', ':library' 2 | -------------------------------------------------------------------------------- /library-release.aar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amineghabi/Material_MusicPlayerView/HEAD/library-release.aar -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /art/MaterialScreenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amineghabi/Material_MusicPlayerView/HEAD/art/MaterialScreenshot.png -------------------------------------------------------------------------------- /library/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | library 3 | 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amineghabi/Material_MusicPlayerView/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable/mycover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amineghabi/Material_MusicPlayerView/HEAD/app/src/main/res/drawable/mycover.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_like.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amineghabi/Material_MusicPlayerView/HEAD/app/src/main/res/drawable/icon_like.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_next.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amineghabi/Material_MusicPlayerView/HEAD/app/src/main/res/drawable/icon_next.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/icon_previous.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amineghabi/Material_MusicPlayerView/HEAD/app/src/main/res/drawable/icon_previous.png -------------------------------------------------------------------------------- /library/src/main/res/drawable/icon_play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amineghabi/Material_MusicPlayerView/HEAD/library/src/main/res/drawable/icon_play.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amineghabi/Material_MusicPlayerView/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amineghabi/Material_MusicPlayerView/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amineghabi/Material_MusicPlayerView/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amineghabi/Material_MusicPlayerView/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /library/src/main/res/drawable/icon_pause.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/amineghabi/Material_MusicPlayerView/HEAD/library/src/main/res/drawable/icon_pause.png -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #ff0028 4 | #ff333333 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Material_MusicPlayerView 3 | 4 | Hello world! 5 | Settings 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Apr 10 15:27:10 PDT 2013 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 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/androidTest/java/androgeek/material/musicplayer/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package androgeek.material.musicplayer; 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 | } -------------------------------------------------------------------------------- /library/src/androidTest/java/androgeek/material/library/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package androgeek.material.library; 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 | } -------------------------------------------------------------------------------- /library/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | minSdkVersion 14 9 | targetSdkVersion 22 10 | versionCode 1 11 | versionName "1.0" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | compile fileTree(dir: 'libs', include: ['*.jar']) 23 | compile 'com.squareup.picasso:picasso:2.5.2' 24 | } 25 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 22 5 | buildToolsVersion "22.0.1" 6 | 7 | defaultConfig { 8 | applicationId "androgeek.material.musicplayer" 9 | minSdkVersion 14 10 | targetSdkVersion 22 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.+" 25 | compile project(':library') 26 | } 27 | -------------------------------------------------------------------------------- /app/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/mertsimsek/Library/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 | -------------------------------------------------------------------------------- /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 /Users/mertsimsek/Library/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 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /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 -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /MusicPlayerView.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /Material_MusicPlayerView.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/java/androgeek/material/musicplayer/MainActivity.java: -------------------------------------------------------------------------------- 1 | package androgeek.material.musicplayer; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.view.View; 6 | 7 | import androgeek.material.library.MaterialMusicPlayerView; 8 | 9 | 10 | public class MainActivity extends Activity { 11 | 12 | MaterialMusicPlayerView mpv; 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.activity_main); 18 | 19 | mpv = (MaterialMusicPlayerView) findViewById(R.id.mpv); 20 | mpv.setCoverURL("http://i.ytimg.com/vi/y7HvUEPjbzU/hqdefault.jpg"); 21 | 22 | mpv.setOnClickListener(new View.OnClickListener() { 23 | @Override 24 | public void onClick(View v) { 25 | if (mpv.isRotating()) 26 | mpv.stop(); 27 | else 28 | mpv.start(); 29 | } 30 | }); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 17 | 18 | 19 | 29 | 30 | 31 | 42 | 43 | 51 | 52 | 53 | 61 | 62 | 63 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | 47 | 48 | 49 | 50 | 1.7 51 | 52 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MaterialMusicPlayerView 2 | 3 | Android Material view and progress for music player. 4 | 5 | # Screen (without animation) 6 | 7 | 8 | # Usage 9 | 10 | You can define values on you XML file or you can make it programmatically. We have 5 values to customize 11 | our player view. 12 | 13 | 14 | ```java 15 | mpv = (MaterialMusicPlayerView) findViewById(R.id.mpv); 16 | ``` 17 | 18 | ```buttonColor``` play/pause button background. 19 | ```progressEmptyColor``` progress bar color(Left Seconds) 20 | ```progressLoadedColor``` progress bar color(Passed Seconds) 21 | ```textColor``` music minutes(Left and passed time) color 22 | ```textSize``` music minutes(Left and passed time) size 23 | 24 | ## XML Usage 25 | 26 | ```xml 27 | 37 | ``` 38 | 39 | ## Java Usage 40 | 41 | You can customize UI view programmatically 42 | ```java 43 | mpv.setButtonColor(Color.DKGRAY); 44 | mpv.setCoverDrawable(R.drawable.mycover); 45 | mpv.setProgressEmptyColor(Color.GRAY); 46 | mpv.setProgressLoadedColor(Color.BLUE); 47 | mpv.setTimeColor(Color.WHITE); 48 | ``` 49 | 50 | ## Methods 51 | 52 | You can also load image from URL 53 | ```java 54 | mpv.setCoverURL("YOUR_IMAGE_URL"); 55 | ``` 56 | 57 | You need to set music time in seconds otherwise default value 100 seconds will be used. 58 | ```java 59 | mpv.setMax(320); 60 | ``` 61 | 62 | Progress will start from 0. But you can also set progress. 63 | ```java 64 | mpv.setProgress(10); 65 | ``` 66 | 67 | To start playing 68 | ```java 69 | mpv.start(); 70 | ``` 71 | 72 | To stop playing 73 | ```java 74 | mpv.stop(); 75 | ``` 76 | 77 | Check if it is rotating(Playing) 78 | ```java 79 | mpv.isRotating(); 80 | ``` 81 | 82 | When you call ```start()``` method, image will start rotating and progress(seconds) will start counting 83 | automatically. when you call ```stop()``` method, rotating will be stopped, time too. You may want to handle 84 | progress yourself. You can disable progress thread. 85 | ```java 86 | mpv.setAutoProgress(false); 87 | ``` 88 | 89 | You can also change velocity of turning album cover.(Default value is 1 which is ideal -my idea-) 90 | ```java 91 | mpv.setVelocity(2); 92 | ``` 93 | 94 | If you have live player then you don't have progress. You can also disable progress view. 95 | ```java 96 | mpv.setProgressVisibility(false); 97 | ``` 98 | 99 | # Libraries Used 100 | 101 | [Picasso by Square](http://square.github.io/picasso/) 102 | 103 | # Design Owner 104 | 105 | Design is created by [Amin Ghabi] (https://www.facebook.com/amin.duferme) 106 | 107 | License 108 | -------- 109 | 110 | 111 | Copyright 2015 Amin Ghabi. 112 | 113 | Licensed under the Apache License, Version 2.0 (the "License"); 114 | you may not use this file except in compliance with the License. 115 | You may obtain a copy of the License at 116 | 117 | http://www.apache.org/licenses/LICENSE-2.0 118 | 119 | Unless required by applicable law or agreed to in writing, software 120 | distributed under the License is distributed on an "AS IS" BASIS, 121 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 122 | See the License for the specific language governing permissions and 123 | limitations under the License. 124 | 125 | 126 | 127 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /library/library.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 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 | 94 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 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 | 94 | 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /library/src/main/java/androgeek/material/library/MaterialMusicPlayerView.java: -------------------------------------------------------------------------------- 1 | package androgeek.material.library; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.graphics.BitmapShader; 8 | import android.graphics.Canvas; 9 | import android.graphics.Color; 10 | import android.graphics.Matrix; 11 | import android.graphics.Paint; 12 | import android.graphics.Rect; 13 | import android.graphics.RectF; 14 | import android.graphics.Region; 15 | import android.graphics.Shader; 16 | import android.graphics.drawable.BitmapDrawable; 17 | import android.graphics.drawable.Drawable; 18 | import android.os.Handler; 19 | import android.util.AttributeSet; 20 | import android.view.MotionEvent; 21 | import android.view.View; 22 | 23 | import com.squareup.picasso.Picasso; 24 | import com.squareup.picasso.Target; 25 | 26 | /** 27 | * Created by Amin Ghabi on 12/07/15. 28 | */ 29 | public class MaterialMusicPlayerView extends View { 30 | 31 | 32 | private Rect mRectText; 33 | 34 | /** 35 | * Paint for drawing left and passed time. 36 | */ 37 | private Paint mPaintTime; 38 | 39 | /** 40 | * RectF for draw circle progress. 41 | */ 42 | private RectF rectF; 43 | 44 | /** 45 | * Paint for circle progress left 46 | */ 47 | private Paint mPaintProgressEmpty; 48 | 49 | /** 50 | * Paint for circle progress loaded 51 | */ 52 | private Paint mPaintProgressLoaded; 53 | 54 | /** 55 | * Modified OnClickListener. We do not want all view click. 56 | * notify onClick() only button area touched. 57 | */ 58 | private OnClickListener onClickListener; 59 | 60 | /** 61 | * Button paint for play/pause control button 62 | */ 63 | private Paint mPaintButton; 64 | 65 | /** 66 | * Play/Pause button region for handle onTouch 67 | */ 68 | private Region mButtonRegion; 69 | 70 | /** 71 | * Play icon will be converted to Bitmap 72 | */ 73 | private Bitmap mBitmapPlay; 74 | 75 | /** 76 | * Pause icon will be converted to Bitmap 77 | */ 78 | private Bitmap mBitmapPause; 79 | 80 | /** 81 | * Paint for drawing play/pause icons to canvas. 82 | */ 83 | private Paint mPaintPlayPause; 84 | 85 | /** 86 | * Paint to draw cover photo to canvas 87 | */ 88 | private Paint mPaintCover; 89 | 90 | /** 91 | * Bitmap for shader. 92 | */ 93 | private Bitmap mBitmapCover; 94 | 95 | /** 96 | * Shader for make drawable circle 97 | */ 98 | private BitmapShader mShader; 99 | 100 | /** 101 | * Scale image to view width/height 102 | */ 103 | private float mCoverScale; 104 | 105 | /** 106 | * Image Height and Width values. 107 | */ 108 | private int mHeight; 109 | private int mWidth; 110 | 111 | /** 112 | * Center values for cover image. 113 | */ 114 | private float mCenterX; 115 | private float mCenterY; 116 | 117 | /** 118 | * Cover image is rotating. That is why we hold that value. 119 | */ 120 | private int mRotateDegrees; 121 | 122 | /** 123 | * Handler for posting runnable object 124 | */ 125 | private Handler mHandlerRotate; 126 | 127 | /** 128 | * Runnable for turning image (default velocity is 10) 129 | */ 130 | private Runnable mRunnableRotate; 131 | 132 | /** 133 | * Handler for posting runnable object 134 | */ 135 | private Handler mHandlerProgress; 136 | 137 | /** 138 | * Runnable for turning image (default velocity is 10) 139 | */ 140 | private Runnable mRunnableProgress; 141 | 142 | /** 143 | * isRotating 144 | */ 145 | private boolean isRotating; 146 | 147 | /** 148 | * Handler will post runnable object every @ROTATE_DELAY seconds. 149 | */ 150 | private static int ROTATE_DELAY = 10; 151 | 152 | /** 153 | * 1 sn = 1000 ms 154 | */ 155 | private static int PROGRESS_SECOND_MS = 1000; 156 | 157 | /** 158 | * mRotateDegrees count increase 1 by 1 default. 159 | * I used that parameter as velocity. 160 | */ 161 | private static int VELOCITY = 1; 162 | 163 | /** 164 | * Default color code for cover 165 | */ 166 | private int mCoverColor = Color.GRAY; 167 | 168 | /** 169 | * Play/Pause button radius.(default = 120) 170 | */ 171 | private float mButtonRadius = 120f; 172 | 173 | /** 174 | * Play/Pause button color(Default = dark gray) 175 | */ 176 | private int mButtonColor = Color.DKGRAY; 177 | 178 | /** 179 | * Color code for progress left. 180 | */ 181 | private int mProgressEmptyColor = 0x20FFFFFF; 182 | 183 | /** 184 | * Color code for progress loaded. 185 | */ 186 | private int mProgressLoadedColor = 0xFF00815E; 187 | 188 | /** 189 | * Time text size 190 | */ 191 | private int mTextSize = 40; 192 | 193 | /** 194 | * Default text color 195 | */ 196 | private int mTextColor = 0xFFFFFFFF; 197 | 198 | /** 199 | * Current progress value 200 | */ 201 | private int currentProgress = 0; 202 | 203 | /** 204 | * Max progress value 205 | */ 206 | private int maxProgress = 100; 207 | 208 | /** 209 | * Auto progress value start progressing when 210 | * cover image start rotating. 211 | */ 212 | private boolean isAutoProgress = true; 213 | 214 | /** 215 | * Progressview and time will be visible/invisible depends on this 216 | */ 217 | private boolean mProgressVisibility = true; 218 | 219 | /** 220 | * Constructor 221 | * 222 | * @param context 223 | */ 224 | public MaterialMusicPlayerView(Context context) { 225 | super(context); 226 | init(context, null); 227 | } 228 | 229 | /** 230 | * Constructor 231 | * 232 | * @param context 233 | * @param attrs 234 | */ 235 | public MaterialMusicPlayerView(Context context, AttributeSet attrs) { 236 | super(context, attrs); 237 | init(context, attrs); 238 | } 239 | 240 | /** 241 | * Constructor 242 | * 243 | * @param context 244 | * @param attrs 245 | * @param defStyleAttr 246 | */ 247 | public MaterialMusicPlayerView(Context context, AttributeSet attrs, int defStyleAttr) { 248 | super(context, attrs, defStyleAttr); 249 | init(context, attrs); 250 | } 251 | 252 | /** 253 | * Constructor 254 | * 255 | * @param context 256 | * @param attrs 257 | * @param defStyleAttr 258 | * @param defStyleRes 259 | */ 260 | public MaterialMusicPlayerView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 261 | super(context, attrs, defStyleAttr, defStyleRes); 262 | init(context, attrs); 263 | } 264 | 265 | /** 266 | * Initializes resource values, create objects which we need them later. 267 | * Object creation must not called onDraw() method, otherwise it won't be 268 | * smooth. 269 | * 270 | * @param context 271 | * @param attrs 272 | */ 273 | private void init(Context context, AttributeSet attrs) { 274 | 275 | //Get Image resource from xml 276 | TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.playerview); 277 | Drawable mDrawableCover = a.getDrawable(R.styleable.playerview_cover); 278 | if (mDrawableCover != null) 279 | mBitmapCover = drawableToBitmap(mDrawableCover); 280 | 281 | mButtonColor = a.getColor(R.styleable.playerview_buttonColor, mButtonColor); 282 | mProgressEmptyColor = a.getColor(R.styleable.playerview_progressEmptyColor, mProgressEmptyColor); 283 | mProgressLoadedColor = a.getColor(R.styleable.playerview_progressLoadedColor, mProgressLoadedColor); 284 | mTextColor = a.getColor(R.styleable.playerview_textColor, mTextColor); 285 | mTextSize = a.getDimensionPixelSize(R.styleable.playerview_textSize, mTextSize); 286 | a.recycle(); 287 | 288 | mRotateDegrees = 0; 289 | 290 | //Handler and Runnable object for turn cover image by updating rotation degrees 291 | mHandlerRotate = new Handler(); 292 | 293 | mRunnableRotate = new Runnable() { 294 | @Override 295 | public void run() { 296 | if (isRotating) { 297 | 298 | if(currentProgress > maxProgress){ 299 | currentProgress = 0; 300 | setProgress(currentProgress); 301 | stop(); 302 | } 303 | 304 | updateCoverRotate(); 305 | mHandlerRotate.postDelayed(mRunnableRotate, ROTATE_DELAY); 306 | } 307 | } 308 | }; 309 | 310 | //Handler and Runnable object for progressing. 311 | mHandlerProgress = new Handler(); 312 | 313 | mRunnableProgress = new Runnable() { 314 | @Override 315 | public void run() { 316 | if(isRotating){ 317 | currentProgress += 1; 318 | mHandlerProgress.postDelayed(mRunnableProgress, PROGRESS_SECOND_MS); 319 | } 320 | } 321 | }; 322 | 323 | //Play/Pause button circle paint 324 | mPaintButton = new Paint(); 325 | mPaintButton.setAntiAlias(true); 326 | mPaintButton.setStyle(Paint.Style.FILL); 327 | mPaintButton.setColor(mButtonColor); 328 | 329 | //Play/Pause button icons paint and bitmaps 330 | mPaintPlayPause = new Paint(); 331 | mPaintPlayPause.setAntiAlias(true); 332 | mBitmapPlay = BitmapFactory.decodeResource(getResources(), R.drawable.icon_play); 333 | mBitmapPause = BitmapFactory.decodeResource(getResources(), R.drawable.icon_pause); 334 | 335 | //Progress paint object creation 336 | mPaintProgressEmpty = new Paint(); 337 | mPaintProgressEmpty.setAntiAlias(true); 338 | mPaintProgressEmpty.setColor(mProgressEmptyColor); 339 | mPaintProgressEmpty.setStyle(Paint.Style.STROKE); 340 | mPaintProgressEmpty.setStrokeWidth(12.0f); 341 | 342 | mPaintProgressLoaded = new Paint(); 343 | mPaintProgressEmpty.setAntiAlias(true); 344 | mPaintProgressLoaded.setColor(mProgressLoadedColor); 345 | mPaintProgressLoaded.setStyle(Paint.Style.STROKE); 346 | mPaintProgressLoaded.setStrokeWidth(12.0f); 347 | 348 | mPaintTime = new Paint(); 349 | mPaintTime.setColor(mTextColor); 350 | mPaintTime.setAntiAlias(true); 351 | mPaintTime.setTextSize(mTextSize); 352 | 353 | //rectF and rect initializes 354 | rectF = new RectF(); 355 | mRectText = new Rect(); 356 | 357 | } 358 | 359 | /** 360 | * Calculate mWidth, mHeight, mCenterX, mCenterY values and 361 | * scale resource bitmap. Create shader. This is not called multiple times. 362 | * 363 | * @param widthMeasureSpec 364 | * @param heightMeasureSpec 365 | */ 366 | @Override 367 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 368 | 369 | mWidth = MeasureSpec.getSize(widthMeasureSpec); 370 | mHeight = MeasureSpec.getSize(heightMeasureSpec); 371 | 372 | int minSide = Math.min(mWidth,mHeight); 373 | mWidth = minSide; 374 | mHeight = minSide; 375 | 376 | this.setMeasuredDimension(mWidth, mHeight); 377 | 378 | mCenterX = mWidth / 2f; 379 | mCenterY = mHeight / 2f; 380 | 381 | //set RectF left, top, right, bottom coordiantes 382 | rectF.set(20.0f, 20.0f, mWidth - 20.0f, mHeight - 20.0f); 383 | 384 | //button size is about to 1/4 of image size then we divide it to 8. 385 | mButtonRadius = mWidth / 8.0f; 386 | 387 | //We resize icons with button radius. icons need to be inside circle. 388 | mBitmapPlay = getResizedBitmap(mBitmapPlay, mButtonRadius - 20.0f, mButtonRadius - 20.0f); 389 | mBitmapPause = getResizedBitmap(mBitmapPause, mButtonRadius - 20.0f, mButtonRadius - 20.0f); 390 | 391 | mButtonRegion = new Region((int) (mCenterX - mButtonRadius), 392 | (int) (mCenterY - mButtonRadius), 393 | (int) (mCenterX + mButtonRadius), 394 | (int) (mCenterY + mButtonRadius)); 395 | 396 | createShader(); 397 | 398 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 399 | } 400 | 401 | /** 402 | * This is where magic happens as you know. 403 | * 404 | * @param canvas 405 | */ 406 | @Override 407 | protected void onDraw(Canvas canvas) { 408 | super.onDraw(canvas); 409 | 410 | if (mShader == null) 411 | return; 412 | 413 | //Draw cover image 414 | float radius = mCenterX <= mCenterY ? mCenterX - 75.0f: mCenterY - 75.0f; 415 | canvas.rotate(mRotateDegrees, mCenterX, mCenterY); 416 | canvas.drawCircle(mCenterX, mCenterY, radius, mPaintCover); 417 | 418 | //Rotate back to make play/pause button stable(No turn) 419 | canvas.rotate(-mRotateDegrees, mCenterX, mCenterY); 420 | 421 | //Draw Play/Pause button 422 | canvas.drawCircle(mCenterX, mCenterY, mButtonRadius, mPaintButton); 423 | canvas.drawBitmap(isRotating() ? mBitmapPause : mBitmapPlay, 424 | mCenterX - mBitmapPause.getWidth() / 2f, 425 | mCenterY - mBitmapPause.getHeight() / 2f, 426 | mPaintPlayPause); 427 | 428 | if(mProgressVisibility){ 429 | //Draw empty progress 430 | canvas.drawArc(rectF, 145, 250, false, mPaintProgressEmpty); 431 | 432 | //Draw loaded progress 433 | canvas.drawArc(rectF, 145, calculatePastProgressDegree(), false, mPaintProgressLoaded); 434 | 435 | //Draw left time text 436 | String leftTime = secondsToTime(calculateLeftSeconds()); 437 | mPaintTime.getTextBounds(leftTime, 0, leftTime.length(), mRectText); 438 | 439 | canvas.drawText(leftTime, 440 | (float) (mCenterX * Math.cos(Math.toRadians(35.0))) + mWidth / 2.0f - mRectText.width() / 1.5f, 441 | (float) (mCenterX * Math.sin(Math.toRadians(35.0))) + mHeight / 2.0f + mRectText.height() + 15.0f, 442 | mPaintTime); 443 | 444 | //Draw passed time text 445 | String passedTime = secondsToTime(calculatePassedSeconds()); 446 | mPaintTime.getTextBounds(passedTime, 0, passedTime.length(), mRectText); 447 | 448 | canvas.drawText(passedTime, 449 | (float) (mCenterX * -Math.cos(Math.toRadians(35.0))) + mWidth / 2.0f - mRectText.width() / 3.0f, 450 | (float) (mCenterX * Math.sin(Math.toRadians(35.0))) + mHeight / 2.0f + mRectText.height() + 15.0f, 451 | mPaintTime); 452 | 453 | } 454 | } 455 | 456 | /** 457 | * We need to convert drawable (which we get from attributes) to bitmap 458 | * to prepare if for BitmapShader 459 | * 460 | * @param drawable 461 | * @return 462 | */ 463 | private Bitmap drawableToBitmap(Drawable drawable) { 464 | if (drawable instanceof BitmapDrawable) { 465 | return ((BitmapDrawable) drawable).getBitmap(); 466 | } 467 | 468 | int width = drawable.getIntrinsicWidth(); 469 | width = width > 0 ? width : 1; 470 | int height = drawable.getIntrinsicHeight(); 471 | height = height > 0 ? height : 1; 472 | 473 | Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 474 | Canvas canvas = new Canvas(bitmap); 475 | drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); 476 | drawable.draw(canvas); 477 | 478 | return bitmap; 479 | } 480 | 481 | /** 482 | * Create shader and set shader to mPaintCover 483 | */ 484 | private void createShader() { 485 | 486 | if (mWidth == 0) 487 | return; 488 | 489 | //if mBitmapCover is null then create default colored cover 490 | if (mBitmapCover == null) { 491 | mBitmapCover = Bitmap.createBitmap(mWidth, mHeight, Bitmap.Config.ARGB_8888); 492 | mBitmapCover.eraseColor(mCoverColor); 493 | } 494 | 495 | mCoverScale = ((float) mWidth) / (float) mBitmapCover.getWidth(); 496 | 497 | mBitmapCover = Bitmap.createScaledBitmap(mBitmapCover, 498 | (int) (mBitmapCover.getWidth() * mCoverScale), 499 | (int) (mBitmapCover.getHeight() * mCoverScale), 500 | true); 501 | 502 | mShader = new BitmapShader(mBitmapCover, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP); 503 | mPaintCover = new Paint(); 504 | mPaintCover.setAntiAlias(true); 505 | mPaintCover.setShader(mShader); 506 | 507 | } 508 | 509 | /** 510 | * Update rotate degree of cover and invalide onDraw(); 511 | */ 512 | public void updateCoverRotate() { 513 | mRotateDegrees += VELOCITY; 514 | mRotateDegrees = mRotateDegrees % 360; 515 | postInvalidate(); 516 | } 517 | 518 | /** 519 | * Checks is rotating 520 | * 521 | * @return 522 | */ 523 | public boolean isRotating() { 524 | return isRotating; 525 | } 526 | 527 | /** 528 | * Start turning image 529 | */ 530 | public void start() { 531 | isRotating = true; 532 | mHandlerRotate.removeCallbacksAndMessages(null); 533 | mHandlerRotate.postDelayed(mRunnableRotate, ROTATE_DELAY); 534 | if(isAutoProgress){ 535 | mHandlerProgress.removeCallbacksAndMessages(null); 536 | mHandlerProgress.postDelayed(mRunnableProgress, PROGRESS_SECOND_MS); 537 | } 538 | } 539 | 540 | /** 541 | * Stop turning image 542 | */ 543 | public void stop() { 544 | isRotating = false; 545 | } 546 | 547 | /** 548 | * Set velocity.When updateCoverRotate() method called, 549 | * increase degree by velocity value. 550 | * 551 | * @param velocity 552 | */ 553 | public void setVelocity(int velocity) { 554 | if (velocity > 0) 555 | VELOCITY = velocity; 556 | } 557 | 558 | /** 559 | * set cover image resource 560 | * 561 | * @param coverDrawable 562 | */ 563 | public void setCoverDrawable(int coverDrawable) { 564 | Drawable drawable = getContext().getDrawable(coverDrawable); 565 | mBitmapCover = drawableToBitmap(drawable); 566 | createShader(); 567 | postInvalidate(); 568 | } 569 | 570 | /** 571 | * gets image URL and load it to cover image.It uses Picasso Library. 572 | * 573 | * @param imageUrl 574 | */ 575 | public void setCoverURL(String imageUrl) { 576 | Picasso.with(getContext()).load(imageUrl).into(target); 577 | } 578 | 579 | /** 580 | * When picasso load into target. Overrider methods are called. 581 | * Invalidate view onBitmapLoaded. 582 | */ 583 | private Target target = new Target() { 584 | @Override 585 | public void onBitmapLoaded(Bitmap bitmap, Picasso.LoadedFrom from) { 586 | mBitmapCover = bitmap; 587 | createShader(); 588 | postInvalidate(); 589 | } 590 | 591 | @Override 592 | public void onBitmapFailed(Drawable errorDrawable) { 593 | 594 | } 595 | 596 | @Override 597 | public void onPrepareLoad(Drawable placeHolderDrawable) { 598 | 599 | } 600 | }; 601 | 602 | /** 603 | * This is detect when mButtonRegion is clicked. Which means 604 | * play/pause action happened. 605 | * 606 | * @param event 607 | * @return 608 | */ 609 | @Override 610 | public boolean onTouchEvent(MotionEvent event) { 611 | 612 | float x = event.getX(); 613 | float y = event.getY(); 614 | 615 | switch (event.getAction()) { 616 | case MotionEvent.ACTION_DOWN: { 617 | return true; 618 | } 619 | case MotionEvent.ACTION_UP: { 620 | if (mButtonRegion.contains((int) x, (int) y)) { 621 | if (onClickListener != null) 622 | onClickListener.onClick(this); 623 | } 624 | } 625 | break; 626 | } 627 | 628 | return super.onTouchEvent(event); 629 | } 630 | 631 | /** 632 | * onClickListener.onClick will be called when button clicked. 633 | * We dont want all view click. We only want button area click. 634 | * That is why we override it. 635 | * 636 | * @param l 637 | */ 638 | @Override 639 | public void setOnClickListener(OnClickListener l) { 640 | onClickListener = l; 641 | } 642 | 643 | /** 644 | * Resize bitmap with @newHeight and @newWidth parameters 645 | * 646 | * @param bm 647 | * @param newHeight 648 | * @param newWidth 649 | * @return 650 | */ 651 | private Bitmap getResizedBitmap(Bitmap bm, float newHeight, float newWidth) { 652 | int width = bm.getWidth(); 653 | int height = bm.getHeight(); 654 | float scaleWidth = ((float) newWidth) / width; 655 | float scaleHeight = ((float) newHeight) / height; 656 | Matrix matrix = new Matrix(); 657 | matrix.postScale(scaleWidth, scaleHeight); 658 | Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false); 659 | return resizedBitmap; 660 | } 661 | 662 | /** 663 | * Sets button color 664 | * 665 | * @param color 666 | */ 667 | public void setButtonColor(int color) { 668 | mButtonColor = color; 669 | mPaintButton.setColor(mButtonColor); 670 | postInvalidate(); 671 | } 672 | 673 | /** 674 | * sets progress empty color 675 | * @param color 676 | */ 677 | public void setProgressEmptyColor(int color){ 678 | mProgressEmptyColor = color; 679 | mPaintProgressEmpty.setColor(mProgressEmptyColor); 680 | postInvalidate(); 681 | } 682 | 683 | /** 684 | * sets progress loaded color 685 | * @param color 686 | */ 687 | public void setProgressLoadedColor(int color){ 688 | mProgressLoadedColor = color; 689 | mPaintProgressLoaded.setColor(mProgressLoadedColor); 690 | postInvalidate(); 691 | } 692 | 693 | /** 694 | * Sets total seconds of music 695 | * @param maxProgress 696 | */ 697 | public void setMax(int maxProgress){ 698 | this.maxProgress = maxProgress; 699 | postInvalidate(); 700 | } 701 | 702 | /** 703 | * Sets current seconds of music 704 | * @param currentProgress 705 | */ 706 | public void setProgress(int currentProgress){ 707 | if(0 <= currentProgress && currentProgress<= maxProgress){ 708 | this.currentProgress = currentProgress; 709 | postInvalidate(); 710 | } 711 | } 712 | 713 | /** 714 | * Get current progress seconds 715 | * @return 716 | */ 717 | public int getProgress(){ 718 | return currentProgress; 719 | } 720 | 721 | /** 722 | * Calculate left seconds 723 | * @return 724 | */ 725 | private int calculateLeftSeconds(){ 726 | return maxProgress - currentProgress; 727 | } 728 | 729 | /** 730 | * Return passed seconds 731 | * @return 732 | */ 733 | private int calculatePassedSeconds(){ 734 | return currentProgress; 735 | 736 | } 737 | 738 | /** 739 | * Convert seconds to time 740 | * @param seconds 741 | * @return 742 | */ 743 | private String secondsToTime(int seconds){ 744 | String time = ""; 745 | 746 | String minutesText = String.valueOf(seconds / 60); 747 | if(minutesText.length() == 1) 748 | minutesText = "0" + minutesText; 749 | 750 | String secondsText = String.valueOf(seconds % 60); 751 | if(secondsText.length() == 1) 752 | secondsText = "0" + secondsText; 753 | 754 | time = minutesText + ":" + secondsText; 755 | 756 | return time; 757 | 758 | } 759 | /** 760 | * Calculate passed progress degree 761 | * @return 762 | */ 763 | private int calculatePastProgressDegree(){ 764 | return (250 * currentProgress) / maxProgress; 765 | } 766 | 767 | /** 768 | * If you do not want to automatic progress, you can disable it 769 | * and implement your own handler by using setProgress method repeatedly. 770 | * @param isAutoProgress 771 | */ 772 | public void setAutoProgress(boolean isAutoProgress){ 773 | this.isAutoProgress = isAutoProgress; 774 | } 775 | 776 | /** 777 | * Sets time text color 778 | * @param color 779 | */ 780 | public void setTimeColor(int color){ 781 | mTextColor = color; 782 | mPaintTime.setColor(mTextColor); 783 | postInvalidate(); 784 | } 785 | 786 | public void setProgressVisibility(boolean mProgressVisibility){ 787 | this.mProgressVisibility = mProgressVisibility; 788 | postInvalidate(); 789 | } 790 | } 791 | --------------------------------------------------------------------------------