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