├── InteractiveInfoWindowAndroid ├── .gitignore ├── .idea │ ├── caches │ │ ├── build_file_checksums.ser │ │ └── gradle_models.ser │ ├── compiler.xml │ ├── copyright │ │ └── profiles_settings.xml │ ├── encodings.xml │ ├── gradle.xml │ ├── jarRepositories.xml │ ├── misc.xml │ ├── modules.xml │ ├── modules │ │ ├── interactive-info-window │ │ │ └── InteractiveInfoWindowAndroid.interactive-info-window.iml │ │ └── sample │ │ │ └── InteractiveInfoWindowAndroid.sample.iml │ ├── runConfigurations.xml │ └── vcs.xml ├── InteractiveInfoWindowAndroid.iml ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── interactive-info-window │ ├── .gitignore │ ├── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── proguard-rules.pro │ ├── src │ │ ├── androidTest │ │ │ └── java │ │ │ │ └── com │ │ │ │ └── appolica │ │ │ │ └── mapanimations │ │ │ │ └── ApplicationTest.java │ │ └── main │ │ │ ├── AndroidManifest.xml │ │ │ ├── java │ │ │ └── com │ │ │ │ └── appolica │ │ │ │ └── interactiveinfowindow │ │ │ │ ├── InfoWindow.java │ │ │ │ ├── InfoWindowManager.java │ │ │ │ ├── animation │ │ │ │ ├── SimpleAnimationListener.java │ │ │ │ └── SimpleAnimatorListener.java │ │ │ │ ├── customview │ │ │ │ ├── DisallowInterceptLayout.java │ │ │ │ └── TouchInterceptFrameLayout.java │ │ │ │ └── fragment │ │ │ │ └── MapInfoWindowFragment.java │ │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── infowindow_background.9.png │ │ │ ├── drawable-mdpi │ │ │ └── infowindow_background.9.png │ │ │ ├── drawable-xhdpi │ │ │ └── infowindow_background.9.png │ │ │ ├── drawable-xxhdpi │ │ │ └── infowindow_background.9.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── infowindow_background.9.png │ │ │ ├── layout │ │ │ └── fragment_map_infowindow.xml │ │ │ └── values │ │ │ ├── dimens.xml │ │ │ ├── ids.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ └── versioning.gradle ├── sample │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── com │ │ │ └── appolica │ │ │ └── sample │ │ │ └── ExampleInstrumentedTest.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── java │ │ │ └── com │ │ │ │ └── appolica │ │ │ │ └── sample │ │ │ │ ├── activities │ │ │ │ ├── ActivitiesAdapter.java │ │ │ │ ├── MainActivity.java │ │ │ │ ├── MapActivity.java │ │ │ │ ├── MapFragmentActivity.java │ │ │ │ └── MapViewActivity.java │ │ │ │ ├── dummy │ │ │ │ └── DummyContent.java │ │ │ │ └── fragments │ │ │ │ ├── FormFragment.java │ │ │ │ ├── InfoWindowRVAdapter.java │ │ │ │ ├── MapFragment.java │ │ │ │ ├── MapViewFragment.java │ │ │ │ └── RecyclerViewFragment.java │ │ └── res │ │ │ ├── layout │ │ │ ├── activity_main.xml │ │ │ ├── activity_map.xml │ │ │ ├── activity_sample_with_map_fragment.xml │ │ │ ├── activity_sample_with_map_view.xml │ │ │ ├── fragment_sample_map.xml │ │ │ ├── fragment_sample_map_view.xml │ │ │ ├── info_window_form_fragment.xml │ │ │ ├── info_window_recyclerview_fragment.xml │ │ │ └── list_item.xml │ │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ │ ├── navigation │ │ │ └── navigation.xml │ │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── dimens.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ └── test │ │ └── java │ │ └── com │ │ └── appolica │ │ └── sample │ │ └── ExampleUnitTest.java └── settings.gradle ├── README.md └── gifs ├── Sample1.gif ├── Sample2.gif ├── Sample3.gif └── Sample4.gif /InteractiveInfoWindowAndroid/.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Appolica/InteractiveInfoWindowAndroid/1566be134b7443f1485ce3e3f1e4df759229cbb2/InteractiveInfoWindowAndroid/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/.idea/caches/gradle_models.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Appolica/InteractiveInfoWindowAndroid/1566be134b7443f1485ce3e3f1e4df759229cbb2/InteractiveInfoWindowAndroid/.idea/caches/gradle_models.ser -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 20 | 21 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/.idea/jarRepositories.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 26 | 45 | 46 | 47 | 48 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 1.8 57 | 58 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/.idea/modules/interactive-info-window/InteractiveInfoWindowAndroid.interactive-info-window.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 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 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/.idea/modules/sample/InteractiveInfoWindowAndroid.sample.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 12 | 13 | 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 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/InteractiveInfoWindowAndroid.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | mavenCentral() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:4.2.1' 10 | 11 | classpath 'org.jetbrains.dokka:dokka-gradle-plugin:1.4.10.2' // NEW 12 | 13 | classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5") 14 | 15 | // NOTE: Do not place your application dependencies here; they belong 16 | // in the individual module build.gradle files 17 | } 18 | } 19 | 20 | allprojects { 21 | repositories { 22 | google() 23 | mavenCentral() 24 | } 25 | } -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 10 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 11 | # When configured, Gradle will run in incubating parallel mode. 12 | # This option should only be used with decoupled projects. More details, visit 13 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 14 | # org.gradle.parallel=true 15 | LIB_VERSION=1.0.8-SNAPSHOT 16 | android.useAndroidX=true 17 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Appolica/InteractiveInfoWindowAndroid/1566be134b7443f1485ce3e3f1e4df759229cbb2/InteractiveInfoWindowAndroid/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Jul 08 10:43:25 EEST 2019 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-6.7.1-all.zip 7 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/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 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/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 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | gradle.properties -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Appolica Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. 6 | * 7 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under 10 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | apply plugin: 'com.android.library' 16 | apply plugin: 'maven-publish' 17 | apply plugin: 'signing' 18 | 19 | apply from: 'versioning.gradle' 20 | 21 | android { 22 | compileSdkVersion 30 23 | 24 | defaultConfig { 25 | minSdkVersion 15 26 | targetSdkVersion 30 27 | versionCode 7 28 | versionName getVersionName() 29 | } 30 | buildTypes { 31 | release { 32 | minifyEnabled false 33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 34 | } 35 | } 36 | compileOptions { 37 | sourceCompatibility JavaVersion.VERSION_1_7 38 | targetCompatibility JavaVersion.VERSION_1_7 39 | } 40 | } 41 | 42 | dependencies { 43 | implementation fileTree(dir: 'libs', include: ['*.jar']) 44 | implementation "androidx.appcompat:appcompat:1.3.0" 45 | implementation 'com.google.android.gms:play-services-maps:17.0.1' 46 | } 47 | 48 | afterEvaluate { 49 | publishing { 50 | publications { 51 | aar(MavenPublication) { 52 | from components.release 53 | artifactId = 'interactive-info-window-android' 54 | groupId = "com.appolica" 55 | version = getVersionName() 56 | } 57 | } 58 | } 59 | } 60 | 61 | publishing { 62 | repositories { 63 | maven { 64 | url = 'https://s01.oss.sonatype.org/content/repositories/releases/' 65 | // url = 'https://s01.oss.sonatype.org/content/repositories/snapshots/' // Repository for snapshot versions of library. 66 | credentials { 67 | username = ossrhUsername 68 | password = ossrhPassword 69 | } 70 | } 71 | } 72 | } 73 | 74 | signing { 75 | def signingKeyId = findProperty("signing.keyId") 76 | def signingPassword = findProperty("signing.password") 77 | def signingKey = findProperty("signing.secretKeyRingFile") 78 | useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) 79 | } -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Appolica/InteractiveInfoWindowAndroid/1566be134b7443f1485ce3e3f1e4df759229cbb2/InteractiveInfoWindowAndroid/interactive-info-window/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Dec 28 10:00:20 PST 2015 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip 7 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/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 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/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/dido/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 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/androidTest/java/com/appolica/mapanimations/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Appolica Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. 6 | * 7 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under 10 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | package com.appolica.mapanimations; 16 | 17 | import android.app.Application; 18 | import android.test.ApplicationTestCase; 19 | 20 | /** 21 | * Testing Fundamentals 22 | */ 23 | public class ApplicationTest extends ApplicationTestCase { 24 | public ApplicationTest() { 25 | super(Application.class); 26 | } 27 | } -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/java/com/appolica/interactiveinfowindow/InfoWindow.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Appolica Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. 6 | * 7 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under 10 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | package com.appolica.interactiveinfowindow; 16 | 17 | 18 | import androidx.fragment.app.Fragment; 19 | 20 | import com.google.android.gms.maps.model.LatLng; 21 | import com.google.android.gms.maps.model.Marker; 22 | 23 | import java.io.Serializable; 24 | 25 | /** 26 | * This class contains everything needed for an InfoWindow to be shown. 27 | * It also provides a windowState that shows whether the window is already shown/hidden 28 | * or is in the middle of showing/hiding. 29 | */ 30 | public class InfoWindow { 31 | private LatLng position; 32 | private MarkerSpecification markerSpec; 33 | 34 | private Fragment windowFragment; 35 | 36 | private State windowState = State.HIDDEN; 37 | 38 | /** 39 | * @param marker The marker which determines the window's position on the screen. 40 | * @param markerSpec Provides the marker's offsetX and offsetY. 41 | * @param fragment The actual window that is displayed on the screen. 42 | */ 43 | public InfoWindow( 44 | Marker marker, 45 | MarkerSpecification markerSpec, 46 | Fragment fragment) { 47 | 48 | this(marker.getPosition(), markerSpec, fragment); 49 | } 50 | 51 | /** 52 | * @param position The {@link com.google.android.gms.maps.model.LatLng} which determines the window's position on the screen. 53 | * @param markerSpec Provides the marker's offsetX and offsetY. 54 | * @param fragment The actual window that is displayed on the screen. 55 | */ 56 | public InfoWindow( 57 | LatLng position, 58 | MarkerSpecification markerSpec, 59 | Fragment fragment) { 60 | 61 | this.position = position; 62 | this.markerSpec = markerSpec; 63 | this.windowFragment = fragment; 64 | } 65 | 66 | public LatLng getPosition() { return position; } 67 | 68 | public void setPosition(LatLng position) { 69 | this.position = position; 70 | } 71 | 72 | public MarkerSpecification getMarkerSpec() { 73 | return markerSpec; 74 | } 75 | 76 | public void setMarkerSpec(MarkerSpecification markerSpec) { 77 | this.markerSpec = markerSpec; 78 | } 79 | 80 | public Fragment getWindowFragment() { 81 | return windowFragment; 82 | } 83 | 84 | /** 85 | * Get window's windowState which could be one of the following: 86 | *
87 | * {@link State#SHOWING}, {@link State#SHOWN}, 88 | * {@link State#HIDING}, {@link State#HIDDEN} 89 | * 90 | * @return The InfoWindow's windowState. 91 | */ 92 | public State getWindowState() { 93 | return windowState; 94 | } 95 | 96 | public void setWindowState(State windowState) { 97 | this.windowState = windowState; 98 | } 99 | 100 | public enum State { 101 | SHOWING, SHOWN, HIDING, HIDDEN 102 | } 103 | 104 | public static class MarkerSpecification implements Serializable { 105 | private int offsetX; 106 | private int offsetY; 107 | 108 | private boolean centerByX = true; 109 | private boolean centerByY = false; 110 | 111 | /** 112 | * Create marker specification by providing InfoWindow's x and y offsets from marker's 113 | * screen location. 114 | * 115 | *

116 | * Note: By default offsetX will be ignored, so in order for it to take effect, you 117 | * must call setCenterByX(false). 118 | * 119 | * Also if you want to use dp, you should convert the values to px by yourself. 120 | * The constructor expects absolute pixel values. 121 | *

122 | * 123 | * @param offsetX InfoWindow's offset by x from marker's screen location. 124 | * Value must be in px. 125 | * @param offsetY InfoWindow's offset by y from marker's screen location. 126 | * Value must be in px. 127 | * 128 | * @see #setCenterByX(boolean) 129 | * @see #setCenterByY(boolean) 130 | */ 131 | public MarkerSpecification(int offsetX, int offsetY) { 132 | this.offsetX = offsetX; 133 | this.offsetY = offsetY; 134 | } 135 | 136 | public int getOffsetX() { 137 | return offsetX; 138 | } 139 | 140 | public void setOffsetX(int offsetX) { 141 | this.offsetX = offsetX; 142 | } 143 | 144 | public int getOffsetY() { 145 | return offsetY; 146 | } 147 | 148 | public void setOffsetY(int offsetY) { 149 | this.offsetY = offsetY; 150 | } 151 | 152 | public boolean centerByX() { 153 | return centerByX; 154 | } 155 | 156 | /** 157 | * Set whether the InfoWindow's center by x should be the same as the marker's 158 | * screen x coordinate. 159 | * If false, offsetX will be used and applied on the x position of the view. Default value is true. 160 | * 161 | * @param centerByX Pass true if you want InfoWindow's x center to be the 162 | * same as the marker's screen x coordinate. Pass false if you want 163 | * offsetX to be used instead. 164 | * 165 | * @see com.appolica.interactiveinfowindow.InfoWindowManager#centerInfoWindow(InfoWindow, android.view.View) 166 | */ 167 | public void setCenterByX(boolean centerByX) { 168 | this.centerByX = centerByX; 169 | } 170 | 171 | public boolean centerByY() { 172 | return centerByY; 173 | } 174 | 175 | /** 176 | * Set whether the InfoWindow's center by y should be the same as the marker's 177 | * screen y coordinate. 178 | * If false, offsetY will be used and applied on the y position of the view. Default value is false. 179 | * 180 | * @param centerByY Pass true if you want InfoWindow's y center to be the 181 | * same as the marker's screen y coordinate. Pass false if you want 182 | * offsetX to be used instead. 183 | * 184 | * @see com.appolica.interactiveinfowindow.InfoWindowManager#centerInfoWindow(InfoWindow, android.view.View) 185 | */ 186 | public void setCenterByY(boolean centerByY) { 187 | this.centerByY = centerByY; 188 | } 189 | 190 | @Override 191 | public boolean equals(Object o) { 192 | 193 | if (o instanceof MarkerSpecification) { 194 | final MarkerSpecification markerSpecification = (MarkerSpecification) o; 195 | 196 | final boolean offsetCheck = markerSpecification.getOffsetY() == offsetY; 197 | 198 | return offsetCheck; 199 | } 200 | 201 | return super.equals(o); 202 | } 203 | } 204 | 205 | @Override 206 | public boolean equals(Object o) { 207 | 208 | if (o instanceof InfoWindow) { 209 | final InfoWindow queryWindow = (InfoWindow) o; 210 | 211 | final boolean markerCheck = queryWindow.getPosition().equals(position); 212 | final boolean specCheck = queryWindow.getMarkerSpec().equals(markerSpec); 213 | final boolean fragmentCheck = queryWindow.getWindowFragment() == windowFragment; 214 | 215 | return markerCheck && specCheck && fragmentCheck; 216 | } 217 | 218 | return super.equals(o); 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/java/com/appolica/interactiveinfowindow/InfoWindowManager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Appolica Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. 6 | * 7 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under 10 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | package com.appolica.interactiveinfowindow; 16 | 17 | import android.content.Context; 18 | import android.graphics.Point; 19 | import android.graphics.Rect; 20 | import android.graphics.drawable.Drawable; 21 | import android.os.Build; 22 | import android.os.Bundle; 23 | import android.view.GestureDetector; 24 | import android.view.MotionEvent; 25 | import android.view.View; 26 | import android.view.ViewGroup; 27 | import android.view.ViewTreeObserver; 28 | import android.view.animation.Animation; 29 | import android.view.animation.DecelerateInterpolator; 30 | import android.view.animation.ScaleAnimation; 31 | import android.widget.FrameLayout; 32 | 33 | import androidx.annotation.NonNull; 34 | import androidx.annotation.Nullable; 35 | import androidx.core.content.ContextCompat; 36 | import androidx.core.view.ViewCompat; 37 | import androidx.fragment.app.Fragment; 38 | import androidx.fragment.app.FragmentManager; 39 | 40 | import com.appolica.interactiveinfowindow.animation.SimpleAnimationListener; 41 | import com.appolica.interactiveinfowindow.customview.DisallowInterceptLayout; 42 | import com.appolica.interactiveinfowindow.customview.TouchInterceptFrameLayout; 43 | import com.appolica.mapanimations.R; 44 | import com.google.android.gms.maps.CameraUpdate; 45 | import com.google.android.gms.maps.CameraUpdateFactory; 46 | import com.google.android.gms.maps.GoogleMap; 47 | import com.google.android.gms.maps.Projection; 48 | import com.google.android.gms.maps.model.LatLng; 49 | import com.google.android.gms.maps.model.Polygon; 50 | import com.google.android.gms.maps.model.Polyline; 51 | 52 | /** 53 | * This is where all the magic happens. Use this class to show your interactive {@link InfoWindow} 54 | * above your {@link com.google.android.gms.maps.model.Marker}. 55 | */ 56 | public class InfoWindowManager 57 | implements GoogleMap.OnCameraIdleListener, 58 | GoogleMap.OnCameraMoveStartedListener, 59 | GoogleMap.OnCameraMoveListener, 60 | GoogleMap.OnCameraMoveCanceledListener, 61 | GoogleMap.OnMapClickListener, 62 | GoogleMap.OnPolylineClickListener, 63 | GoogleMap.OnPolygonClickListener{ 64 | 65 | public static final String FRAGMENT_TAG_INFO = "InfoWindow"; 66 | 67 | private static final String TAG = "InfoWindowManager"; 68 | 69 | public static final int DURATION_WINDOW_ANIMATION = 200; 70 | public static final int DURATION_CAMERA_ENSURE_VISIBLE_ANIMATION = 500; 71 | 72 | private GoogleMap googleMap; 73 | 74 | private FragmentManager fragmentManager; 75 | 76 | private InfoWindow currentWindow; 77 | private ViewGroup parent; 78 | private View currentContainer; 79 | 80 | private ContainerSpecification containerSpec; 81 | 82 | private FragmentContainerIdProvider idProvider; 83 | 84 | private GoogleMap.OnMapClickListener onMapClickListener; 85 | 86 | private GoogleMap.OnCameraIdleListener onCameraIdleListener; 87 | private GoogleMap.OnCameraMoveStartedListener onCameraMoveStartedListener; 88 | private GoogleMap.OnCameraMoveListener onCameraMoveListener; 89 | private GoogleMap.OnCameraMoveCanceledListener onCameraMoveCanceledListener; 90 | private GoogleMap.OnPolylineClickListener onPolylineClickListener; 91 | private GoogleMap.OnPolygonClickListener onPolygonClickListener; 92 | 93 | private Animation showAnimation; 94 | private Animation hideAnimation; 95 | 96 | private WindowShowListener windowShowListener; 97 | 98 | private boolean hideOnFling = false; 99 | 100 | public InfoWindowManager(@NonNull final FragmentManager fragmentManager) { 101 | 102 | this.fragmentManager = fragmentManager; 103 | } 104 | 105 | /** 106 | * Call this method if you are not using 107 | * {@link com.appolica.interactiveinfowindow.fragment.MapInfoWindowFragment}. If you are calling 108 | * it from a Fragment we suggest you to call it in {@link Fragment#onViewCreated(View, Bundle)} 109 | * and if you are calling it from an Activity you should call it in 110 | * {@link android.app.Activity#onCreate(Bundle)}. 111 | * 112 | * @param parent The parent of your {@link com.google.android.gms.maps.MapView} or 113 | * {@link com.google.android.gms.maps.SupportMapFragment}. 114 | * @param savedInstanceState The saved state Bundle from your Fragment/Activity. 115 | */ 116 | public void onParentViewCreated( 117 | @NonNull final TouchInterceptFrameLayout parent, 118 | @Nullable final Bundle savedInstanceState) { 119 | 120 | this.parent = parent; 121 | this.idProvider = new FragmentContainerIdProvider(savedInstanceState); 122 | this.containerSpec = generateDefaultContainerSpecs(parent.getContext()); 123 | 124 | parent.setDetector( 125 | new GestureDetector( 126 | parent.getContext(), 127 | new GestureDetector.SimpleOnGestureListener() { 128 | 129 | @Override 130 | public boolean onScroll( 131 | MotionEvent e1, MotionEvent e2, 132 | float distanceX, float distanceY) { 133 | 134 | if (isOpen()) { 135 | centerInfoWindow(currentWindow, currentContainer); 136 | } 137 | 138 | return true; 139 | } 140 | 141 | @Override 142 | public boolean onFling( 143 | MotionEvent e1, MotionEvent e2, 144 | float velocityX, float velocityY) { 145 | 146 | if (isOpen()) { 147 | if (hideOnFling) { 148 | hide(currentWindow); 149 | } else { 150 | centerInfoWindow(currentWindow, currentContainer); 151 | } 152 | } 153 | 154 | return true; 155 | } 156 | 157 | @Override 158 | public boolean onDoubleTap(MotionEvent e) { 159 | 160 | if (isOpen()) { 161 | centerInfoWindow(currentWindow, currentContainer); 162 | } 163 | 164 | return true; 165 | } 166 | })); 167 | 168 | currentContainer = parent.findViewById(idProvider.currentId); 169 | 170 | if (currentContainer == null) { 171 | currentContainer = createContainer(parent); 172 | 173 | parent.addView(currentContainer); 174 | } 175 | 176 | final Fragment oldFragment = fragmentManager.findFragmentByTag(FRAGMENT_TAG_INFO); 177 | if (oldFragment != null) { 178 | fragmentManager.beginTransaction() 179 | .remove(oldFragment) 180 | .commitNow(); 181 | } 182 | 183 | } 184 | 185 | private View createContainer(@NonNull final ViewGroup parent) { 186 | final DisallowInterceptLayout container = new DisallowInterceptLayout(parent.getContext()); 187 | 188 | container.setDisallowParentIntercept(true); 189 | container.setLayoutParams(generateDefaultLayoutParams()); 190 | container.setId(idProvider.getNewId()); 191 | container.setVisibility(View.INVISIBLE); 192 | 193 | return container; 194 | } 195 | 196 | private FrameLayout.LayoutParams generateDefaultLayoutParams() { 197 | 198 | return generateLayoutParams( 199 | ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT); 200 | } 201 | 202 | private FrameLayout.LayoutParams generateLayoutParams( 203 | final int infoWindowWidth, final int infoWindowHeight) { 204 | 205 | return new FrameLayout.LayoutParams(infoWindowWidth, infoWindowHeight); 206 | } 207 | 208 | /** 209 | * Same as calling toggle(currentWindow, true); 210 | * 211 | * @param infoWindow The {@link InfoWindow} that is to be shown/hidden. 212 | * @see #toggle(InfoWindow, boolean) 213 | */ 214 | public void toggle(@NonNull final InfoWindow infoWindow) { 215 | toggle(infoWindow, true); 216 | } 217 | 218 | /** 219 | * Open/hide the given {@link InfoWindow}. 220 | * 221 | * @param infoWindow The {@link InfoWindow} that is to be shown/hidden. 222 | * @param animated true if you want to toggle it with animation, 223 | * false otherwise. 224 | */ 225 | public void toggle(@NonNull final InfoWindow infoWindow, final boolean animated) { 226 | 227 | if (isOpen()) { 228 | // If the toggled window is tha same as the already opened one, close it. 229 | // Otherwise close the currently opened window and open the new one. 230 | if (infoWindow.equals(currentWindow)) { 231 | hide(infoWindow, animated); 232 | } else { 233 | show(infoWindow, animated); 234 | } 235 | 236 | } else { 237 | show(infoWindow, animated); 238 | } 239 | 240 | } 241 | 242 | /** 243 | * Same as calling show(currentWindow, true); 244 | * 245 | * @param infoWindow The {@link InfoWindow} that is to be shown. 246 | * @see #show(InfoWindow, boolean) 247 | */ 248 | public void show(@NonNull final InfoWindow infoWindow) { 249 | show(infoWindow, true); 250 | } 251 | 252 | /** 253 | * Show the given {@link InfoWindow}. Pass true if you want this action 254 | * to be animated, false otherwise. If another window has been already opened 255 | * it will be closed while opening the new one. 256 | * 257 | * @param window The {@link InfoWindow} that is to be shown. 258 | * @param animated true if you want to show it with animation, 259 | * false otherwise. 260 | */ 261 | public void show(@NonNull final InfoWindow window, final boolean animated) { 262 | // Check if already opened 263 | if (isOpen()) { 264 | 265 | internalHide(currentContainer, currentWindow); 266 | 267 | currentContainer = createContainer(parent); 268 | parent.addView(currentContainer); 269 | } 270 | 271 | setCurrentWindow(window); 272 | 273 | internalShow(window, currentContainer, animated); 274 | } 275 | 276 | private void internalShow(@NonNull final InfoWindow infoWindow, 277 | @NonNull final View container, 278 | final boolean animated) { 279 | 280 | addFragment(infoWindow.getWindowFragment(), container); 281 | prepareView(container, infoWindow); 282 | 283 | if (animated) { 284 | 285 | animateWindowOpen(infoWindow, container); 286 | 287 | } else { 288 | propagateShowEvent(infoWindow, InfoWindow.State.SHOWN); 289 | container.setVisibility(View.VISIBLE); 290 | 291 | } 292 | } 293 | 294 | private void prepareView(final View view, final InfoWindow infoWindow) { 295 | 296 | updateWithContainerSpec(view); 297 | 298 | view.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { 299 | @Override 300 | public boolean onPreDraw() { 301 | 302 | centerInfoWindow(infoWindow, view); 303 | ensureVisible(view); 304 | 305 | view.getViewTreeObserver().removeOnPreDrawListener(this); 306 | return true; 307 | } 308 | }); 309 | } 310 | 311 | private void updateWithContainerSpec(final View view) { 312 | ViewCompat.setBackground(view, containerSpec.background); 313 | } 314 | 315 | private void animateWindowOpen(@NonNull final InfoWindow infoWindow, 316 | @NonNull final View container) { 317 | 318 | final SimpleAnimationListener animationListener = new SimpleAnimationListener() { 319 | 320 | @Override 321 | public void onAnimationStart(Animation animation) { 322 | 323 | container.setVisibility(View.VISIBLE); 324 | propagateShowEvent(infoWindow, InfoWindow.State.SHOWING); 325 | 326 | } 327 | 328 | @Override 329 | public void onAnimationEnd(Animation animation) { 330 | 331 | propagateShowEvent(infoWindow, InfoWindow.State.SHOWN); 332 | setCurrentWindow(infoWindow); 333 | 334 | } 335 | }; 336 | 337 | if (showAnimation == null) { 338 | 339 | container.getViewTreeObserver().addOnPreDrawListener( 340 | new ViewTreeObserver.OnPreDrawListener() { 341 | 342 | @Override 343 | public boolean onPreDraw() { 344 | final int containerWidth = container.getWidth(); 345 | final int containerHeight = container.getHeight(); 346 | 347 | final float pivotX = container.getTranslationX() + containerWidth / 2; 348 | final float pivotY = container.getTranslationY() + containerHeight; 349 | 350 | final ScaleAnimation scaleAnimation = new ScaleAnimation( 351 | 0f, 1f, 352 | 0f, 1f, 353 | pivotX, pivotY); 354 | 355 | scaleAnimation.setDuration(DURATION_WINDOW_ANIMATION); 356 | scaleAnimation.setInterpolator(new DecelerateInterpolator()); 357 | scaleAnimation.setAnimationListener(animationListener); 358 | 359 | container.startAnimation(scaleAnimation); 360 | 361 | container.getViewTreeObserver().removeOnPreDrawListener(this); 362 | return true; 363 | } 364 | }); 365 | } else { 366 | showAnimation.setAnimationListener(animationListener); 367 | container.startAnimation(showAnimation); 368 | } 369 | } 370 | 371 | /** 372 | * Same as calling hide(currentWindow, true); 373 | * 374 | * @param infoWindow The {@link InfoWindow} that is to be hidden. 375 | * @see #hide(InfoWindow, boolean) 376 | */ 377 | public void hide(@NonNull final InfoWindow infoWindow) { 378 | hide(infoWindow, true); 379 | } 380 | 381 | /** 382 | * Hides the given {@link InfoWindow}. Pass true if you want this action 383 | * to be animated, false otherwise. 384 | * 385 | * @param infoWindow The {@link InfoWindow} that is to be hidden. 386 | * @param animated true if you want to hide it with animation, 387 | * false otherwise. 388 | */ 389 | public void hide(@NonNull final InfoWindow infoWindow, final boolean animated) { 390 | internalHide(currentContainer, infoWindow, animated); 391 | } 392 | 393 | private void internalHide(@NonNull final View container, @NonNull final InfoWindow infoWindow) { 394 | internalHide(container, infoWindow, true); 395 | } 396 | 397 | private void internalHide( 398 | @NonNull final View container, 399 | @NonNull final InfoWindow toHideWindow, 400 | final boolean animated) { 401 | 402 | if (animated) { 403 | 404 | final Animation animation; 405 | 406 | if (hideAnimation == null) { 407 | 408 | final int containerWidth = container.getWidth(); 409 | final int containerHeight = container.getHeight(); 410 | 411 | final float pivotX = container.getTranslationX() + containerWidth / 2; 412 | final float pivotY = container.getTranslationY() + containerHeight; 413 | 414 | animation = new ScaleAnimation( 415 | 1f, 0f, 416 | 1f, 0f, 417 | pivotX, pivotY); 418 | 419 | animation.setDuration(DURATION_WINDOW_ANIMATION); 420 | animation.setInterpolator(new DecelerateInterpolator()); 421 | 422 | 423 | } else { 424 | animation = hideAnimation; 425 | } 426 | 427 | animation.setAnimationListener(new SimpleAnimationListener() { 428 | 429 | @Override 430 | public void onAnimationStart(Animation animation) { 431 | toHideWindow.setWindowState(InfoWindow.State.HIDING); 432 | propagateShowEvent(toHideWindow, InfoWindow.State.HIDING); 433 | } 434 | 435 | @Override 436 | public void onAnimationEnd(Animation animation) { 437 | removeWindow(toHideWindow, container); 438 | 439 | if (container.getId() != InfoWindowManager.this.currentContainer.getId()) { 440 | parent.removeView(container); 441 | } 442 | 443 | toHideWindow.setWindowState(InfoWindow.State.HIDDEN); 444 | propagateShowEvent(toHideWindow, InfoWindow.State.HIDDEN); 445 | } 446 | }); 447 | 448 | this.currentContainer.startAnimation(animation); 449 | 450 | } else { 451 | 452 | removeWindow(toHideWindow, container); 453 | propagateShowEvent(toHideWindow, InfoWindow.State.HIDDEN); 454 | 455 | } 456 | } 457 | 458 | private void propagateShowEvent( 459 | @NonNull final InfoWindow infoWindow, 460 | @NonNull final InfoWindow.State state) { 461 | 462 | infoWindow.setWindowState(state); 463 | 464 | if (windowShowListener != null) { 465 | switch (state) { 466 | case SHOWING: 467 | windowShowListener.onWindowShowStarted(infoWindow); 468 | break; 469 | case SHOWN: 470 | windowShowListener.onWindowShown(infoWindow); 471 | break; 472 | case HIDING: 473 | windowShowListener.onWindowHideStarted(infoWindow); 474 | break; 475 | case HIDDEN: 476 | windowShowListener.onWindowHidden(infoWindow); 477 | break; 478 | } 479 | } 480 | } 481 | 482 | private void centerInfoWindow(@NonNull final InfoWindow infoWindow, 483 | @NonNull final View container) { 484 | final InfoWindow.MarkerSpecification markerSpec = infoWindow.getMarkerSpec(); 485 | final Projection projection = googleMap.getProjection(); 486 | 487 | final Point windowScreenLocation = projection.toScreenLocation(infoWindow.getPosition()); 488 | 489 | final int containerWidth = container.getWidth(); 490 | final int containerHeight = container.getHeight(); 491 | 492 | final int x; 493 | if (markerSpec.centerByX()) { 494 | x = windowScreenLocation.x - containerWidth / 2; 495 | } else { 496 | x = windowScreenLocation.x + markerSpec.getOffsetX(); 497 | } 498 | 499 | final int y; 500 | if (markerSpec.centerByY()) { 501 | y = windowScreenLocation.y - containerHeight / 2; 502 | } else { 503 | y = windowScreenLocation.y - containerHeight - markerSpec.getOffsetY(); 504 | } 505 | 506 | final int pivotX = containerWidth / 2; 507 | final int pivotY = containerHeight; 508 | 509 | container.setPivotX(pivotX); 510 | container.setPivotY(pivotY); 511 | 512 | container.setX(x); 513 | container.setY(y); 514 | } 515 | 516 | private boolean ensureVisible(@NonNull final View infoWindowContainer) { 517 | 518 | final int[] infoWindowLocation = new int[2]; 519 | infoWindowContainer.getLocationOnScreen(infoWindowLocation); 520 | 521 | final boolean visible = true; 522 | final Rect infoWindowRect = new Rect(); 523 | infoWindowContainer.getHitRect(infoWindowRect); 524 | 525 | final int[] parentPosition = new int[2]; 526 | parent.getLocationOnScreen(parentPosition); 527 | 528 | final Rect parentRect = new Rect(); 529 | parent.getGlobalVisibleRect(parentRect); 530 | 531 | infoWindowContainer.getGlobalVisibleRect(infoWindowRect); 532 | 533 | final int visibleWidth = infoWindowRect.width(); 534 | final int actualWidth = infoWindowContainer.getWidth(); 535 | 536 | final int visibleHeight = infoWindowRect.height(); 537 | final int actualHeight = infoWindowContainer.getHeight(); 538 | 539 | int scrollX = (visibleWidth - actualWidth); 540 | int scrollY = (visibleHeight - actualHeight); 541 | 542 | if (scrollX != 0) { 543 | if (infoWindowRect.left == parentRect.left) { 544 | scrollX = -Math.abs(scrollX); 545 | } else { 546 | scrollX = Math.abs(scrollX); 547 | } 548 | } 549 | 550 | if (scrollY != 0) { 551 | if (infoWindowRect.top < parentRect.top) { 552 | scrollY = Math.abs(scrollY); 553 | } else { 554 | scrollY = -Math.abs(scrollY); 555 | } 556 | } 557 | 558 | final CameraUpdate cameraUpdate = CameraUpdateFactory.scrollBy(scrollX, scrollY); 559 | googleMap.animateCamera(cameraUpdate, DURATION_CAMERA_ENSURE_VISIBLE_ANIMATION, null); 560 | 561 | return visible; 562 | } 563 | 564 | private void removeWindow(@NonNull final InfoWindow window, @NonNull final View container) { 565 | 566 | container.setVisibility(View.INVISIBLE); 567 | container.setScaleY(1f); 568 | container.setScaleX(1f); 569 | container.clearAnimation(); 570 | 571 | removeWindowFragment(window.getWindowFragment()); 572 | } 573 | 574 | private void addFragment(@NonNull final Fragment fragment, @NonNull final View container) { 575 | fragmentManager.beginTransaction() 576 | .replace(container.getId(), fragment, FRAGMENT_TAG_INFO) 577 | .commitNow(); 578 | } 579 | 580 | private void removeWindowFragment(final Fragment windowFragment) { 581 | fragmentManager.beginTransaction() 582 | .remove(windowFragment) 583 | .commitNow(); 584 | } 585 | 586 | /** 587 | * Generate default {@link ContainerSpecification} for the container view. 588 | * 589 | * @param context used to work with Resources. 590 | * @return New instance of the generated default container specs. 591 | */ 592 | public ContainerSpecification generateDefaultContainerSpecs(Context context) { 593 | final Drawable drawable = 594 | ContextCompat.getDrawable(context, R.drawable.infowindow_background); 595 | 596 | return new ContainerSpecification(drawable); 597 | } 598 | 599 | private boolean isOpen() { 600 | return currentContainer != null && currentContainer.getVisibility() == View.VISIBLE; 601 | } 602 | 603 | /** 604 | * Set a callback which will be invoked when an {@link InfoWindow} is changing its state. 605 | * 606 | * @param windowShowListener The callback that will run. 607 | * @see WindowShowListener 608 | */ 609 | public void setWindowShowListener(WindowShowListener windowShowListener) { 610 | this.windowShowListener = windowShowListener; 611 | } 612 | 613 | private void setCurrentWindow(InfoWindow currentWindow) { 614 | this.currentWindow = currentWindow; 615 | } 616 | 617 | /** 618 | * Set the container specifications. These specifications are global for all 619 | * {@link InfoWindow}s. 620 | * 621 | * @param containerSpec The container specifications used for the InfoWindow container view. 622 | */ 623 | public void setContainerSpec(ContainerSpecification containerSpec) { 624 | this.containerSpec = containerSpec; 625 | } 626 | 627 | /** 628 | * Get the specification of the {@link InfoWindow}'s container. 629 | * 630 | * @return {@link InfoWindow}'s container specification. 631 | * @see ContainerSpecification 632 | */ 633 | public ContainerSpecification getContainerSpec() { 634 | return containerSpec; 635 | } 636 | 637 | private class FragmentContainerIdProvider { 638 | private final static String BUNDLE_KEY_ID = "BundleKeyFragmentContainerIdProvider"; 639 | private int currentId; 640 | 641 | public FragmentContainerIdProvider(@Nullable final Bundle savedInstanceState) { 642 | if (savedInstanceState != null) { 643 | currentId = savedInstanceState.getInt(BUNDLE_KEY_ID, R.id.infoWindowContainer1); 644 | } else { 645 | currentId = R.id.infoWindowContainer1; 646 | } 647 | } 648 | 649 | public int getCurrentId() { 650 | return currentId; 651 | } 652 | 653 | public int getNewId() { 654 | if (currentId == R.id.infoWindowContainer1) { 655 | currentId = R.id.infoWindowContainer2; 656 | } else { 657 | currentId = R.id.infoWindowContainer1; 658 | } 659 | 660 | return currentId; 661 | } 662 | 663 | public void onSaveInstanceState(@NonNull final Bundle outState) { 664 | outState.putInt(BUNDLE_KEY_ID, currentId); 665 | } 666 | } 667 | 668 | /** 669 | * This method must be called from activity's or fragment's onSaveInstanceState(Bundle outState). 670 | * There is no need of calling this method if you are using 671 | * {@link com.appolica.interactiveinfowindow.fragment.MapInfoWindowFragment} 672 | * 673 | * @param outState Bundle from activity's of fragment's onSaveInstanceState(Bundle outState). 674 | */ 675 | public void onSaveInstanceState(@NonNull final Bundle outState) { 676 | idProvider.onSaveInstanceState(outState); 677 | } 678 | 679 | /** 680 | * This method must be called from activity's or fragment's onDestroy(). 681 | * There is no need of calling this method if you are using 682 | * {@link com.appolica.interactiveinfowindow.fragment.MapInfoWindowFragment} 683 | */ 684 | public void onDestroy() { 685 | 686 | currentContainer = null; 687 | parent = null; 688 | 689 | } 690 | 691 | /** 692 | * Call this method in your onMapReady(GoogleMap googleMap) callback if you are not using 693 | * {@link com.appolica.interactiveinfowindow.fragment.MapInfoWindowFragment}. 694 | *

695 | *

Keep in mind that this method sets all camera listeners and map click listener 696 | * to the googleMap object and you shouldn't set them by yourself. However if you want 697 | * to listen for these events you can use the methods below:

698 | *

699 | * {@link #setOnCameraMoveStartedListener(GoogleMap.OnCameraMoveStartedListener)} 700 | *
701 | * {@link #setOnCameraMoveCanceledListener(GoogleMap.OnCameraMoveCanceledListener)} 702 | *
703 | * {@link #setOnCameraMoveListener(GoogleMap.OnCameraMoveListener)} 704 | *
705 | * {@link #setOnCameraIdleListener(GoogleMap.OnCameraIdleListener)} 706 | *

707 | *
708 | * 709 | * @param googleMap The GoogleMap object from onMapReady callback. 710 | * @see #setOnMapClickListener(GoogleMap.OnMapClickListener) 711 | * @see #setOnCameraMoveStartedListener(GoogleMap.OnCameraMoveStartedListener) 712 | * @see #setOnCameraMoveCanceledListener(GoogleMap.OnCameraMoveCanceledListener) 713 | * @see #setOnCameraMoveListener(GoogleMap.OnCameraMoveListener) 714 | * @see #setOnCameraIdleListener(GoogleMap.OnCameraIdleListener) 715 | */ 716 | public void onMapReady(@NonNull final GoogleMap googleMap) { 717 | this.googleMap = googleMap; 718 | 719 | googleMap.setOnMapClickListener(this); 720 | 721 | googleMap.setOnCameraIdleListener(this); 722 | googleMap.setOnCameraMoveStartedListener(this); 723 | googleMap.setOnCameraMoveListener(this); 724 | googleMap.setOnCameraMoveCanceledListener(this); 725 | googleMap.setOnPolylineClickListener(this); 726 | googleMap.setOnPolygonClickListener(this); 727 | } 728 | 729 | @Override 730 | public void onMapClick(LatLng latLng) { 731 | if (onMapClickListener != null) { 732 | onMapClickListener.onMapClick(latLng); 733 | } 734 | 735 | if (isOpen()) { 736 | internalHide(currentContainer, currentWindow); 737 | } 738 | 739 | } 740 | 741 | @Override 742 | public void onCameraIdle() { 743 | if (onCameraIdleListener != null) { 744 | onCameraIdleListener.onCameraIdle(); 745 | } 746 | } 747 | 748 | @Override 749 | public void onCameraMoveStarted(int i) { 750 | if (onCameraMoveStartedListener != null) { 751 | onCameraMoveStartedListener.onCameraMoveStarted(i); 752 | } 753 | } 754 | 755 | @Override 756 | public void onCameraMove() { 757 | if (onCameraMoveListener != null) { 758 | onCameraMoveListener.onCameraMove(); 759 | } 760 | 761 | if (isOpen()) { 762 | centerInfoWindow(currentWindow, currentContainer); 763 | } 764 | } 765 | 766 | @Override 767 | public void onCameraMoveCanceled() { 768 | if (onCameraMoveCanceledListener != null) { 769 | onCameraMoveCanceledListener.onCameraMoveCanceled(); 770 | } 771 | } 772 | 773 | @Override 774 | public void onPolylineClick(@NonNull Polyline polyline) { 775 | onPolylineClickListener.onPolylineClick(polyline); 776 | } 777 | 778 | @Override 779 | public void onPolygonClick(@NonNull Polygon polygon) { 780 | onPolygonClickListener.onPolygonClick(polygon); 781 | } 782 | 783 | /** 784 | * Set onMapClickListener. 785 | * 786 | * @param onMapClickListener The callback that will run. 787 | */ 788 | public void setOnMapClickListener(GoogleMap.OnMapClickListener onMapClickListener) { 789 | 790 | this.onMapClickListener = onMapClickListener; 791 | } 792 | 793 | /** 794 | * Set onCameraIdleListener. 795 | * 796 | * @param onCameraIdleListener The callback that will run. 797 | */ 798 | public void setOnCameraIdleListener(GoogleMap.OnCameraIdleListener onCameraIdleListener) { 799 | 800 | this.onCameraIdleListener = onCameraIdleListener; 801 | } 802 | 803 | /** 804 | * Set onCameraMoveStartedListener. 805 | * 806 | * @param onCameraMoveStartedListener The callback that will run. 807 | */ 808 | public void setOnCameraMoveStartedListener( 809 | final GoogleMap.OnCameraMoveStartedListener onCameraMoveStartedListener) { 810 | 811 | this.onCameraMoveStartedListener = onCameraMoveStartedListener; 812 | } 813 | 814 | /** 815 | * Set onCameraMoveListener 816 | * 817 | * @param onCameraMoveListener The callback that will run. 818 | */ 819 | public void setOnCameraMoveListener(final GoogleMap.OnCameraMoveListener onCameraMoveListener) { 820 | 821 | this.onCameraMoveListener = onCameraMoveListener; 822 | } 823 | 824 | /** 825 | * Set onCameraMoveCanceledListener. 826 | * 827 | * @param onCameraMoveCanceledListener The callback that will run. 828 | */ 829 | public void setOnCameraMoveCanceledListener( 830 | final GoogleMap.OnCameraMoveCanceledListener onCameraMoveCanceledListener) { 831 | 832 | this.onCameraMoveCanceledListener = onCameraMoveCanceledListener; 833 | } 834 | 835 | /** 836 | * Set onPolylineClickListener. 837 | * 838 | * @param onPolylineClickListener The callback that will run. 839 | */ 840 | public void setOnPolylineClickListener(final GoogleMap.OnPolylineClickListener onPolylineClickListener) { 841 | 842 | this.onPolylineClickListener = onPolylineClickListener; 843 | } 844 | 845 | /** 846 | * Set onPolygonClickListener. 847 | * 848 | * @param onPolygonClickListener The callback that will run. 849 | */ 850 | public void setOnPolygonClickListener(final GoogleMap.OnPolygonClickListener onPolygonClickListener) { 851 | 852 | this.onPolygonClickListener = onPolygonClickListener; 853 | } 854 | 855 | /** 856 | * Provide your own animation for showing the {@link InfoWindow}. 857 | * 858 | * @param showAnimation Show animation. 859 | */ 860 | public void setShowAnimation(Animation showAnimation) { 861 | this.showAnimation = showAnimation; 862 | } 863 | 864 | /** 865 | * Provide your own animation for hiding the {@link InfoWindow}. 866 | * 867 | * @param hideAnimation Hide animation. 868 | */ 869 | public void setHideAnimation(Animation hideAnimation) { 870 | this.hideAnimation = hideAnimation; 871 | } 872 | 873 | /** 874 | * Determine whether your {@link InfoWindow} should be closed after the user flings the map 875 | * or should move with it. 876 | * 877 | * @param hideOnFling Pass true if you want to hide your {@link InfoWindow} 878 | * when fling event occurs, pass false if you want your window 879 | * to move with the map. 880 | */ 881 | public void setHideOnFling(final boolean hideOnFling) { 882 | this.hideOnFling = hideOnFling; 883 | } 884 | 885 | /** 886 | * Interface definition for callbacks to be invoked when an {@link InfoWindow}'s 887 | * state has been changed. 888 | */ 889 | public interface WindowShowListener { 890 | void onWindowShowStarted(@NonNull final InfoWindow infoWindow); 891 | 892 | void onWindowShown(@NonNull final InfoWindow infoWindow); 893 | 894 | void onWindowHideStarted(@NonNull final InfoWindow infoWindow); 895 | 896 | void onWindowHidden(@NonNull final InfoWindow infoWindow); 897 | } 898 | 899 | /** 900 | * Class containing {@link InfoWindow}'s container details. 901 | */ 902 | public static class ContainerSpecification { 903 | private Drawable background; 904 | 905 | /** 906 | * Create a new instance of ContainerSpecification by providing the container background. 907 | * 908 | * @param background the background of the container. 909 | */ 910 | public ContainerSpecification(Drawable background) { 911 | this.background = background; 912 | } 913 | 914 | /** 915 | * This is what is called to set the background of the container view. 916 | * 917 | * @return the background of the container view. 918 | */ 919 | public Drawable getBackground() { 920 | return background; 921 | } 922 | 923 | public void setBackground(Drawable background) { 924 | this.background = background; 925 | } 926 | } 927 | } 928 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/java/com/appolica/interactiveinfowindow/animation/SimpleAnimationListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Appolica Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. 6 | * 7 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under 10 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | package com.appolica.interactiveinfowindow.animation; 16 | 17 | import android.view.animation.Animation; 18 | 19 | /** 20 | * Created by Bogomil Kolarov on 02.09.16. 21 | */ 22 | public class SimpleAnimationListener implements Animation.AnimationListener { 23 | @Override 24 | public void onAnimationStart(Animation animation) { 25 | 26 | } 27 | 28 | @Override 29 | public void onAnimationEnd(Animation animation) { 30 | 31 | } 32 | 33 | @Override 34 | public void onAnimationRepeat(Animation animation) { 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/java/com/appolica/interactiveinfowindow/animation/SimpleAnimatorListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Appolica Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. 6 | * 7 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under 10 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | package com.appolica.interactiveinfowindow.animation; 16 | 17 | import android.animation.Animator; 18 | 19 | /** 20 | * Created by Bogomil Kolarov on 02.09.16. 21 | */ 22 | public class SimpleAnimatorListener implements Animator.AnimatorListener { 23 | @Override 24 | public void onAnimationStart(Animator animator) { 25 | 26 | } 27 | 28 | @Override 29 | public void onAnimationEnd(Animator animator) { 30 | 31 | } 32 | 33 | @Override 34 | public void onAnimationCancel(Animator animator) { 35 | 36 | } 37 | 38 | @Override 39 | public void onAnimationRepeat(Animator animator) { 40 | 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/java/com/appolica/interactiveinfowindow/customview/DisallowInterceptLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Appolica Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. 6 | * 7 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under 10 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | package com.appolica.interactiveinfowindow.customview; 16 | 17 | import android.annotation.TargetApi; 18 | import android.content.Context; 19 | import android.os.Build; 20 | import android.util.AttributeSet; 21 | import android.view.MotionEvent; 22 | import android.view.ViewParent; 23 | import android.widget.FrameLayout; 24 | 25 | /** 26 | * This class simply calls {@link android.view.ViewGroup#requestDisallowInterceptTouchEvent(boolean)} 27 | * in {@link android.view.ViewGroup#dispatchTouchEvent(MotionEvent)} and passes to it 28 | * {@link #disallowParentIntercept}. 29 | */ 30 | public class DisallowInterceptLayout extends FrameLayout { 31 | 32 | private boolean disallowParentIntercept = false; 33 | 34 | public DisallowInterceptLayout(Context context) { 35 | super(context); 36 | } 37 | 38 | public DisallowInterceptLayout(Context context, AttributeSet attrs) { 39 | super(context, attrs); 40 | } 41 | 42 | public DisallowInterceptLayout(Context context, AttributeSet attrs, int defStyleAttr) { 43 | super(context, attrs, defStyleAttr); 44 | } 45 | 46 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 47 | public DisallowInterceptLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 48 | super(context, attrs, defStyleAttr, defStyleRes); 49 | } 50 | 51 | @Override 52 | public boolean dispatchTouchEvent(MotionEvent ev) { 53 | if (disallowParentIntercept) { 54 | final ViewParent parent = getParent(); 55 | if (parent != null) { 56 | parent.requestDisallowInterceptTouchEvent(disallowParentIntercept); 57 | } 58 | } 59 | return super.dispatchTouchEvent(ev); 60 | } 61 | 62 | public void setDisallowParentIntercept(boolean disallowParentIntercept) { 63 | this.disallowParentIntercept = disallowParentIntercept; 64 | } 65 | 66 | public boolean willDisallowParentIntercept() { 67 | return disallowParentIntercept; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/java/com/appolica/interactiveinfowindow/customview/TouchInterceptFrameLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Appolica Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. 6 | * 7 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under 10 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | package com.appolica.interactiveinfowindow.customview; 16 | 17 | import android.annotation.TargetApi; 18 | import android.content.Context; 19 | import android.os.Build; 20 | import android.util.AttributeSet; 21 | import android.view.GestureDetector; 22 | import android.view.MotionEvent; 23 | import android.widget.FrameLayout; 24 | 25 | public class TouchInterceptFrameLayout extends FrameLayout { 26 | 27 | private GestureDetector detector; 28 | 29 | public TouchInterceptFrameLayout(Context context) { 30 | super(context); 31 | } 32 | 33 | public TouchInterceptFrameLayout(Context context, AttributeSet attrs) { 34 | super(context, attrs); 35 | } 36 | 37 | public TouchInterceptFrameLayout(Context context, AttributeSet attrs, int defStyleAttr) { 38 | super(context, attrs, defStyleAttr); 39 | } 40 | 41 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 42 | public TouchInterceptFrameLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 43 | super(context, attrs, defStyleAttr, defStyleRes); 44 | } 45 | 46 | @Override 47 | public boolean onInterceptTouchEvent(MotionEvent ev) { 48 | if (detector != null) { 49 | detector.onTouchEvent(ev); 50 | } 51 | return super.onInterceptTouchEvent(ev); 52 | } 53 | 54 | @Override 55 | public boolean onTouchEvent(MotionEvent event) { 56 | if (detector != null) { 57 | detector.onTouchEvent(event); 58 | } 59 | return super.onTouchEvent(event); 60 | } 61 | 62 | public void setDetector(GestureDetector detector) { 63 | this.detector = detector; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/java/com/appolica/interactiveinfowindow/fragment/MapInfoWindowFragment.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2016 Appolica Ltd. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file 5 | * except in compliance with the License. 6 | * 7 | * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software distributed under 10 | * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, 11 | * either express or implied. See the License for the specific language governing permissions and 12 | * limitations under the License. 13 | */ 14 | 15 | package com.appolica.interactiveinfowindow.fragment; 16 | 17 | import android.os.Bundle; 18 | import android.view.LayoutInflater; 19 | import android.view.View; 20 | import android.view.ViewGroup; 21 | 22 | import androidx.annotation.Nullable; 23 | import androidx.fragment.app.Fragment; 24 | 25 | import com.appolica.interactiveinfowindow.InfoWindow; 26 | import com.appolica.interactiveinfowindow.InfoWindowManager; 27 | import com.appolica.interactiveinfowindow.customview.TouchInterceptFrameLayout; 28 | import com.appolica.mapanimations.R; 29 | import com.google.android.gms.maps.GoogleMap; 30 | import com.google.android.gms.maps.OnMapReadyCallback; 31 | import com.google.android.gms.maps.SupportMapFragment; 32 | 33 | public class MapInfoWindowFragment extends Fragment { 34 | 35 | private static final String TAG = "MapInfoWindowFragment"; 36 | 37 | private GoogleMap googleMap; 38 | private InfoWindowManager infoWindowManager; 39 | 40 | @Nullable 41 | @Override 42 | public View onCreateView( 43 | LayoutInflater inflater, 44 | @Nullable ViewGroup container, 45 | @Nullable Bundle savedInstanceState) { 46 | 47 | final View view = inflater.inflate(R.layout.fragment_map_infowindow, container, false); 48 | 49 | return view; 50 | } 51 | 52 | @Override 53 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 54 | super.onViewCreated(view, savedInstanceState); 55 | 56 | infoWindowManager = new InfoWindowManager(getChildFragmentManager()); 57 | infoWindowManager.onParentViewCreated((TouchInterceptFrameLayout) view, savedInstanceState); 58 | } 59 | 60 | private void setUpMapIfNeeded() { 61 | // Do a null check to confirm that we have not already instantiated the map. 62 | if (googleMap == null) { 63 | // Try to obtain the map from the SupportMapFragment. 64 | 65 | final SupportMapFragment mapFragment = 66 | (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.mapFragment); 67 | 68 | mapFragment 69 | .getMapAsync(new OnMapReadyCallback() { 70 | @Override 71 | public void onMapReady(GoogleMap googleMap) { 72 | MapInfoWindowFragment.this.googleMap = googleMap; 73 | setUpMap(); 74 | } 75 | }); 76 | } 77 | } 78 | 79 | private void setUpMap() { 80 | infoWindowManager.onMapReady(googleMap); 81 | } 82 | 83 | /** 84 | * Get the {@link InfoWindowManager}, used for showing/hiding and positioning the 85 | * {@link InfoWindow}. 86 | * 87 | * @return The {@link InfoWindowManager} 88 | */ 89 | public InfoWindowManager infoWindowManager() { 90 | return infoWindowManager; 91 | } 92 | 93 | @Override 94 | public void onResume() { 95 | super.onResume(); 96 | setUpMapIfNeeded(); 97 | } 98 | 99 | @Override 100 | public void onSaveInstanceState(Bundle outState) { 101 | super.onSaveInstanceState(outState); 102 | infoWindowManager.onSaveInstanceState(outState); 103 | } 104 | 105 | @Override 106 | public void onDestroyView() { 107 | super.onDestroyView(); 108 | infoWindowManager.onDestroy(); 109 | } 110 | 111 | /** 112 | * Use this method to get the {@link GoogleMap} object asynchronously from our fragment. 113 | * 114 | * @param onMapReadyCallback The callback that will be called providing you the GoogleMap 115 | * object. 116 | */ 117 | public void getMapAsync(OnMapReadyCallback onMapReadyCallback) { 118 | final SupportMapFragment mapFragment = 119 | (SupportMapFragment) getChildFragmentManager().findFragmentById(R.id.mapFragment); 120 | 121 | mapFragment.getMapAsync(onMapReadyCallback); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/drawable-hdpi/infowindow_background.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Appolica/InteractiveInfoWindowAndroid/1566be134b7443f1485ce3e3f1e4df759229cbb2/InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/drawable-hdpi/infowindow_background.9.png -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/drawable-mdpi/infowindow_background.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Appolica/InteractiveInfoWindowAndroid/1566be134b7443f1485ce3e3f1e4df759229cbb2/InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/drawable-mdpi/infowindow_background.9.png -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/drawable-xhdpi/infowindow_background.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Appolica/InteractiveInfoWindowAndroid/1566be134b7443f1485ce3e3f1e4df759229cbb2/InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/drawable-xhdpi/infowindow_background.9.png -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/drawable-xxhdpi/infowindow_background.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Appolica/InteractiveInfoWindowAndroid/1566be134b7443f1485ce3e3f1e4df759229cbb2/InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/drawable-xxhdpi/infowindow_background.9.png -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/drawable-xxxhdpi/infowindow_background.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Appolica/InteractiveInfoWindowAndroid/1566be134b7443f1485ce3e3f1e4df759229cbb2/InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/drawable-xxxhdpi/infowindow_background.9.png -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/layout/fragment_map_infowindow.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16 | 17 | 22 | 23 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | 100dp 18 | 120dp 19 | 20 | 16dp 21 | 16dp 22 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/values/ids.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | InteractiveInfoWindowAndroid 17 | 18 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 14 | 15 | 16 | 17 | 18 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/interactive-info-window/versioning.gradle: -------------------------------------------------------------------------------- 1 | def final staticVerName = "1.1.0" 2 | ext.getVersionName = { -> 3 | if (project.hasProperty("buildNumber")) { 4 | return "1.0.$buildNumber" 5 | } else { 6 | return staticVerName 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'androidx.navigation.safeargs' 3 | 4 | android { 5 | compileSdkVersion 30 6 | 7 | defaultConfig { 8 | applicationId "com.appolica.sample" 9 | minSdkVersion 19 10 | targetSdkVersion 30 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | compileOptions { 24 | sourceCompatibility JavaVersion.VERSION_1_7 25 | targetCompatibility JavaVersion.VERSION_1_7 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation fileTree(dir: 'libs', include: ['*.jar']) 31 | 32 | implementation "androidx.appcompat:appcompat:1.3.0" 33 | implementation 'androidx.recyclerview:recyclerview:1.2.1' 34 | implementation 'androidx.cardview:cardview:1.0.0' 35 | implementation 'androidx.legacy:legacy-support-v4:1.0.0' 36 | 37 | // implementation project (':interactive-info-window') 38 | 39 | implementation 'com.google.android.material:material:1.3.0' 40 | implementation 'androidx.constraintlayout:constraintlayout:2.0.4' 41 | implementation 'androidx.navigation:navigation-fragment:2.3.5' 42 | implementation 'androidx.navigation:navigation-ui:2.3.5' 43 | 44 | testImplementation 'junit:junit:4.13.2' 45 | implementation 'com.google.android.gms:play-services-maps:17.0.1' 46 | 47 | implementation 'com.appolica:interactive-info-window-android:1.1.0' 48 | } 49 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /home/bogomil/Development/SDK/android-sdk-linux/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 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/androidTest/java/com/appolica/sample/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.appolica.sample; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.assertEquals; 11 | 12 | /** 13 | * Instrumentation test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.appolica.sample", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 11 | 12 | 13 | 16 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/java/com/appolica/sample/activities/ActivitiesAdapter.java: -------------------------------------------------------------------------------- 1 | package com.appolica.sample.activities; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | 11 | import androidx.recyclerview.widget.RecyclerView; 12 | 13 | import com.appolica.sample.R; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | 18 | /** 19 | * Created by Bogomil Kolarov on 21.09.16. 20 | * Copyright © 2016 Appolica. All rights reserved. 21 | */ 22 | public class ActivitiesAdapter extends RecyclerView.Adapter { 23 | 24 | private List> data = new ArrayList<>(); 25 | 26 | class ItemViewHolder extends RecyclerView.ViewHolder { 27 | 28 | private TextView textView; 29 | 30 | ItemViewHolder(View itemView) { 31 | super(itemView); 32 | 33 | textView = itemView.findViewById(R.id.listItemTextView); 34 | } 35 | } 36 | 37 | @Override 38 | public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 39 | final View view = 40 | LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false); 41 | 42 | return new ItemViewHolder(view); 43 | } 44 | 45 | @Override 46 | public void onBindViewHolder(ItemViewHolder holder, final int position) { 47 | holder.textView.setText(data.get(position).getSimpleName()); 48 | 49 | final int pos = position; 50 | holder.itemView.setOnClickListener(new View.OnClickListener() { 51 | @Override 52 | public void onClick(View v) { 53 | final Context context = v.getContext(); 54 | 55 | context.startActivity(new Intent(context, data.get(pos))); 56 | } 57 | }); 58 | } 59 | 60 | @Override 61 | public int getItemCount() { 62 | return data.size(); 63 | } 64 | 65 | public void addData(List> data) { 66 | this.data.addAll(data); 67 | notifyDataSetChanged(); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/java/com/appolica/sample/activities/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.appolica.sample.activities; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | 6 | import androidx.appcompat.app.AppCompatActivity; 7 | import androidx.recyclerview.widget.LinearLayoutManager; 8 | import androidx.recyclerview.widget.RecyclerView; 9 | 10 | import com.appolica.sample.R; 11 | 12 | import java.util.ArrayList; 13 | 14 | public class MainActivity extends AppCompatActivity { 15 | 16 | private RecyclerView recyclerView; 17 | private ActivitiesAdapter adapter; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_main); 23 | 24 | recyclerView = findViewById(R.id.recyclerViewActivities); 25 | 26 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 27 | recyclerView.setHasFixedSize(true); 28 | 29 | adapter = new ActivitiesAdapter(); 30 | recyclerView.setAdapter(adapter); 31 | 32 | final ArrayList> activityClasses = new ArrayList<>(); 33 | activityClasses.add(MapFragmentActivity.class); 34 | activityClasses.add(MapViewActivity.class); 35 | activityClasses.add(MapActivity.class); 36 | 37 | adapter.addData(activityClasses); 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/java/com/appolica/sample/activities/MapActivity.java: -------------------------------------------------------------------------------- 1 | package com.appolica.sample.activities; 2 | 3 | import androidx.appcompat.app.AppCompatActivity; 4 | import androidx.navigation.Navigation; 5 | 6 | import android.os.Bundle; 7 | 8 | import com.appolica.sample.R; 9 | 10 | public class MapActivity extends AppCompatActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | setContentView(R.layout.activity_map); 16 | } 17 | } -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/java/com/appolica/sample/activities/MapFragmentActivity.java: -------------------------------------------------------------------------------- 1 | package com.appolica.sample.activities; 2 | 3 | import android.os.Bundle; 4 | import android.util.Log; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.fragment.app.FragmentActivity; 8 | 9 | import com.appolica.interactiveinfowindow.InfoWindow; 10 | import com.appolica.interactiveinfowindow.InfoWindowManager; 11 | import com.appolica.interactiveinfowindow.fragment.MapInfoWindowFragment; 12 | import com.appolica.sample.R; 13 | import com.appolica.sample.fragments.FormFragment; 14 | import com.appolica.sample.fragments.RecyclerViewFragment; 15 | import com.google.android.gms.maps.GoogleMap; 16 | import com.google.android.gms.maps.OnMapReadyCallback; 17 | import com.google.android.gms.maps.model.LatLng; 18 | import com.google.android.gms.maps.model.Marker; 19 | import com.google.android.gms.maps.model.MarkerOptions; 20 | 21 | public class MapFragmentActivity 22 | extends FragmentActivity 23 | implements InfoWindowManager.WindowShowListener, 24 | GoogleMap.OnMarkerClickListener { 25 | 26 | private static final String RECYCLER_VIEW = "RECYCLER_VIEW_MARKER"; 27 | private static final String FORM_VIEW = "FORM_VIEW_MARKER"; 28 | 29 | private InfoWindow recyclerWindow; 30 | private InfoWindow formWindow; 31 | private InfoWindowManager infoWindowManager; 32 | 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | setContentView(R.layout.activity_sample_with_map_fragment); 37 | 38 | final MapInfoWindowFragment mapInfoWindowFragment = 39 | (MapInfoWindowFragment) getSupportFragmentManager().findFragmentById(R.id.infoWindowMap); 40 | 41 | infoWindowManager = mapInfoWindowFragment.infoWindowManager(); 42 | infoWindowManager.setHideOnFling(true); 43 | 44 | mapInfoWindowFragment.getMapAsync(new OnMapReadyCallback() { 45 | @Override 46 | public void onMapReady(GoogleMap googleMap) { 47 | final Marker marker1 = googleMap.addMarker(new MarkerOptions().position(new LatLng(5, 5)).snippet(RECYCLER_VIEW)); 48 | final Marker marker2 = googleMap.addMarker(new MarkerOptions().position(new LatLng(1, 1)).snippet(FORM_VIEW)); 49 | 50 | final int offsetX = (int) getResources().getDimension(R.dimen.marker_offset_x); 51 | final int offsetY = (int) getResources().getDimension(R.dimen.marker_offset_y); 52 | 53 | final InfoWindow.MarkerSpecification markerSpec = 54 | new InfoWindow.MarkerSpecification(offsetX, offsetY); 55 | 56 | recyclerWindow = new InfoWindow(marker1, markerSpec, new RecyclerViewFragment()); 57 | formWindow = new InfoWindow(marker2, markerSpec, new FormFragment()); 58 | 59 | googleMap.setOnMarkerClickListener(MapFragmentActivity.this); 60 | } 61 | }); 62 | 63 | infoWindowManager.setWindowShowListener(MapFragmentActivity.this); 64 | 65 | } 66 | 67 | @Override 68 | public boolean onMarkerClick(Marker marker) { 69 | InfoWindow infoWindow = null; 70 | switch (marker.getSnippet()) { 71 | case RECYCLER_VIEW: 72 | infoWindow = recyclerWindow; 73 | break; 74 | case FORM_VIEW: 75 | infoWindow = formWindow; 76 | break; 77 | } 78 | 79 | if (infoWindow != null) { 80 | infoWindowManager.toggle(infoWindow, true); 81 | } 82 | 83 | return true; 84 | } 85 | 86 | @Override 87 | public void onWindowShowStarted(@NonNull InfoWindow infoWindow) { 88 | Log.d("debug", "onWindowShowStarted: " + infoWindow); 89 | } 90 | 91 | @Override 92 | public void onWindowShown(@NonNull InfoWindow infoWindow) { 93 | Log.d("debug", "onWindowShown: " + infoWindow); 94 | } 95 | 96 | @Override 97 | public void onWindowHideStarted(@NonNull InfoWindow infoWindow) { 98 | Log.d("debug", "onWindowHideStarted: " + infoWindow); 99 | } 100 | 101 | @Override 102 | public void onWindowHidden(@NonNull InfoWindow infoWindow) { 103 | Log.d("debug", "onWindowHidden: " + infoWindow); 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/java/com/appolica/sample/activities/MapViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.appolica.sample.activities; 2 | 3 | import android.os.Bundle; 4 | import android.os.PersistableBundle; 5 | 6 | import androidx.fragment.app.FragmentActivity; 7 | 8 | import com.appolica.interactiveinfowindow.InfoWindow; 9 | import com.appolica.interactiveinfowindow.InfoWindowManager; 10 | import com.appolica.interactiveinfowindow.customview.TouchInterceptFrameLayout; 11 | import com.appolica.sample.R; 12 | import com.appolica.sample.fragments.FormFragment; 13 | import com.appolica.sample.fragments.RecyclerViewFragment; 14 | import com.google.android.gms.maps.GoogleMap; 15 | import com.google.android.gms.maps.MapView; 16 | import com.google.android.gms.maps.OnMapReadyCallback; 17 | import com.google.android.gms.maps.model.LatLng; 18 | import com.google.android.gms.maps.model.Marker; 19 | import com.google.android.gms.maps.model.MarkerOptions; 20 | 21 | public class MapViewActivity 22 | extends FragmentActivity 23 | implements OnMapReadyCallback, 24 | GoogleMap.OnMarkerClickListener { 25 | 26 | private static final String RECYCLER_VIEW = "RECYCLER_VIEW_MARKER"; 27 | private static final String FORM_VIEW = "FORM_VIEW_MARKER"; 28 | 29 | private MapView mapView; 30 | 31 | private InfoWindowManager infoWindowManager; 32 | 33 | private InfoWindow recyclerWindow; 34 | private InfoWindow formWindow; 35 | 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | setContentView(R.layout.activity_sample_with_map_view); 40 | mapView = findViewById(R.id.mapView); 41 | mapView.onCreate(savedInstanceState); 42 | final TouchInterceptFrameLayout mapViewContainer = 43 | findViewById(R.id.mapViewContainer); 44 | 45 | mapView.getMapAsync(this); 46 | 47 | infoWindowManager = new InfoWindowManager(getSupportFragmentManager()); 48 | infoWindowManager.onParentViewCreated(mapViewContainer, savedInstanceState); 49 | } 50 | 51 | @Override 52 | protected void onResume() { 53 | super.onResume(); 54 | mapView.onResume(); 55 | } 56 | 57 | @Override 58 | protected void onPause() { 59 | super.onPause(); 60 | mapView.onPause(); 61 | } 62 | 63 | @Override 64 | public void onSaveInstanceState(Bundle outState, PersistableBundle outPersistentState) { 65 | super.onSaveInstanceState(outState, outPersistentState); 66 | mapView.onSaveInstanceState(outState); 67 | infoWindowManager.onSaveInstanceState(outState); 68 | } 69 | 70 | @Override 71 | protected void onDestroy() { 72 | super.onDestroy(); 73 | infoWindowManager.onDestroy(); 74 | mapView.onDestroy(); 75 | } 76 | 77 | @Override 78 | public void onLowMemory() { 79 | super.onLowMemory(); 80 | mapView.onLowMemory(); 81 | } 82 | 83 | @Override 84 | public void onMapReady(final GoogleMap googleMap) { 85 | infoWindowManager.onMapReady(googleMap); 86 | 87 | final Marker marker1 = googleMap.addMarker(new MarkerOptions().position(new LatLng(5, 5)).snippet(RECYCLER_VIEW)); 88 | final Marker marker2 = googleMap.addMarker(new MarkerOptions().position(new LatLng(1, 1)).snippet(FORM_VIEW)); 89 | 90 | final int offsetX = (int) getResources().getDimension(R.dimen.marker_offset_x); 91 | final int offsetY = (int) getResources().getDimension(R.dimen.marker_offset_y); 92 | 93 | final InfoWindow.MarkerSpecification markerSpec = 94 | new InfoWindow.MarkerSpecification(offsetX, offsetY); 95 | 96 | recyclerWindow = new InfoWindow(marker1, markerSpec, new RecyclerViewFragment()); 97 | formWindow = new InfoWindow(marker2, markerSpec, new FormFragment()); 98 | 99 | googleMap.setOnMarkerClickListener(this); 100 | } 101 | 102 | @Override 103 | public boolean onMarkerClick(Marker marker) { 104 | InfoWindow infoWindow = null; 105 | switch (marker.getSnippet()) { 106 | case RECYCLER_VIEW: 107 | infoWindow = recyclerWindow; 108 | break; 109 | case FORM_VIEW: 110 | infoWindow = formWindow; 111 | break; 112 | } 113 | 114 | if (infoWindow != null) { 115 | infoWindowManager.toggle(infoWindow, true); 116 | } 117 | 118 | return true; 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/java/com/appolica/sample/dummy/DummyContent.java: -------------------------------------------------------------------------------- 1 | package com.appolica.sample.dummy; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * Helper class for providing sample content for user interfaces created by 10 | * Android template wizards. 11 | *

12 | * TODO: Replace all uses of this class before publishing your app. 13 | */ 14 | public class DummyContent { 15 | 16 | /** 17 | * An array of sample (dummy) items. 18 | */ 19 | public static List ITEMS = new ArrayList(); 20 | 21 | /** 22 | * A map of sample (dummy) items, by ID. 23 | */ 24 | public static Map ITEM_MAP = new HashMap(); 25 | 26 | static { 27 | // Add 3 sample items. 28 | addItem(new DummyItem("1", "Item 1")); 29 | addItem(new DummyItem("2", "Item 2")); 30 | addItem(new DummyItem("3", "Item 3")); 31 | } 32 | 33 | private static void addItem(DummyItem item) { 34 | ITEMS.add(item); 35 | ITEM_MAP.put(item.id, item); 36 | } 37 | 38 | /** 39 | * A dummy item representing a piece of content. 40 | */ 41 | public static class DummyItem { 42 | public String id; 43 | public String content; 44 | 45 | public DummyItem(String id, String content) { 46 | this.id = id; 47 | this.content = content; 48 | } 49 | 50 | @Override 51 | public String toString() { 52 | return content; 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/java/com/appolica/sample/fragments/FormFragment.java: -------------------------------------------------------------------------------- 1 | package com.appolica.sample.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.Toast; 8 | 9 | import androidx.annotation.Nullable; 10 | import androidx.fragment.app.Fragment; 11 | 12 | import com.appolica.sample.R; 13 | 14 | public class FormFragment extends Fragment { 15 | @Nullable 16 | @Override 17 | public View onCreateView( 18 | LayoutInflater inflater, 19 | @Nullable ViewGroup container, 20 | @Nullable Bundle savedInstanceState) { 21 | 22 | final View view = inflater.inflate(R.layout.info_window_form_fragment, container, false); 23 | 24 | return view; 25 | } 26 | 27 | @Override 28 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 29 | super.onViewCreated(view, savedInstanceState); 30 | 31 | final View.OnClickListener onClickListener = new View.OnClickListener() { 32 | @Override 33 | public void onClick(View v) { 34 | Toast.makeText(getContext(), "Well I don't have any...", Toast.LENGTH_SHORT).show(); 35 | } 36 | }; 37 | 38 | view.findViewById(R.id.button1).setOnClickListener(onClickListener); 39 | view.findViewById(R.id.button2).setOnClickListener(onClickListener); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/java/com/appolica/sample/fragments/InfoWindowRVAdapter.java: -------------------------------------------------------------------------------- 1 | package com.appolica.sample.fragments; 2 | 3 | import android.view.LayoutInflater; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.TextView; 7 | 8 | import androidx.recyclerview.widget.RecyclerView; 9 | 10 | import com.appolica.sample.R; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | /** 16 | * Created by Bogomil Kolarov on 22.09.16. 17 | * Copyright © 2016 Appolica. All rights reserved. 18 | */ 19 | public class InfoWindowRVAdapter extends RecyclerView.Adapter { 20 | 21 | private List data = new ArrayList<>(); 22 | 23 | public class ItemViewHolder extends RecyclerView.ViewHolder { 24 | 25 | private TextView textView; 26 | 27 | public ItemViewHolder(View itemView) { 28 | super(itemView); 29 | 30 | textView = (TextView) itemView.findViewById(R.id.listItemTextView); 31 | } 32 | } 33 | 34 | @Override 35 | public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 36 | 37 | final View view = 38 | LayoutInflater.from(parent.getContext()).inflate(R.layout.list_item, parent, false); 39 | 40 | return new ItemViewHolder(view); 41 | 42 | } 43 | 44 | @Override 45 | public void onBindViewHolder(ItemViewHolder holder, int position) { 46 | holder.textView.setText(data.get(position)); 47 | } 48 | 49 | @Override 50 | public int getItemCount() { 51 | return data.size(); 52 | } 53 | 54 | public void addData(List data) { 55 | this.data.addAll(data); 56 | notifyDataSetChanged(); 57 | } 58 | 59 | public void addItem(String item) { 60 | data.add(item); 61 | notifyDataSetChanged(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/java/com/appolica/sample/fragments/MapFragment.java: -------------------------------------------------------------------------------- 1 | package com.appolica.sample.fragments; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.fragment.app.Fragment; 7 | 8 | import android.util.Log; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import com.appolica.interactiveinfowindow.InfoWindow; 14 | import com.appolica.interactiveinfowindow.InfoWindowManager; 15 | import com.appolica.interactiveinfowindow.fragment.MapInfoWindowFragment; 16 | import com.appolica.sample.R; 17 | import com.google.android.gms.maps.GoogleMap; 18 | import com.google.android.gms.maps.OnMapReadyCallback; 19 | import com.google.android.gms.maps.model.LatLng; 20 | import com.google.android.gms.maps.model.Marker; 21 | import com.google.android.gms.maps.model.MarkerOptions; 22 | 23 | public class MapFragment 24 | extends Fragment 25 | implements InfoWindowManager.WindowShowListener, 26 | GoogleMap.OnMarkerClickListener { 27 | 28 | private static final String RECYCLER_VIEW = "RECYCLER_VIEW_MARKER"; 29 | private static final String FORM_VIEW = "FORM_VIEW_MARKER"; 30 | 31 | private InfoWindow recyclerWindow; 32 | private InfoWindow formWindow; 33 | private InfoWindowManager infoWindowManager; 34 | 35 | @Override 36 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 37 | Bundle savedInstanceState) { 38 | // Inflate the layout for this fragment 39 | View view = inflater.inflate(R.layout.fragment_sample_map, container, false); 40 | 41 | final MapInfoWindowFragment mapInfoWindowFragment = 42 | (MapInfoWindowFragment) getChildFragmentManager().findFragmentById(R.id.infoWindowMapFragment); 43 | 44 | infoWindowManager = mapInfoWindowFragment.infoWindowManager(); 45 | infoWindowManager.setHideOnFling(true); 46 | 47 | mapInfoWindowFragment.getMapAsync(new OnMapReadyCallback() { 48 | @Override 49 | public void onMapReady(GoogleMap googleMap) { 50 | final Marker marker1 = googleMap.addMarker(new MarkerOptions().position(new LatLng(5, 5)).snippet(RECYCLER_VIEW)); 51 | final Marker marker2 = googleMap.addMarker(new MarkerOptions().position(new LatLng(1, 1)).snippet(FORM_VIEW)); 52 | 53 | final int offsetX = (int) getResources().getDimension(R.dimen.marker_offset_x); 54 | final int offsetY = (int) getResources().getDimension(R.dimen.marker_offset_y); 55 | 56 | final InfoWindow.MarkerSpecification markerSpec = 57 | new InfoWindow.MarkerSpecification(offsetX, offsetY); 58 | 59 | recyclerWindow = new InfoWindow(marker1, markerSpec, new RecyclerViewFragment()); 60 | formWindow = new InfoWindow(marker2, markerSpec, new FormFragment()); 61 | 62 | googleMap.setOnMarkerClickListener(MapFragment.this); 63 | } 64 | }); 65 | 66 | infoWindowManager.setWindowShowListener(MapFragment.this); 67 | 68 | return view; 69 | } 70 | 71 | @Override 72 | public boolean onMarkerClick(Marker marker) { 73 | InfoWindow infoWindow = null; 74 | switch (marker.getSnippet()) { 75 | case RECYCLER_VIEW: 76 | infoWindow = recyclerWindow; 77 | break; 78 | case FORM_VIEW: 79 | infoWindow = formWindow; 80 | break; 81 | } 82 | 83 | if (infoWindow != null) { 84 | infoWindowManager.toggle(infoWindow, true); 85 | } 86 | 87 | return true; 88 | } 89 | 90 | @Override 91 | public void onWindowShowStarted(@NonNull InfoWindow infoWindow) { 92 | Log.d("debug", "onWindowShowStarted: " + infoWindow); 93 | } 94 | 95 | @Override 96 | public void onWindowShown(@NonNull InfoWindow infoWindow) { 97 | Log.d("debug", "onWindowShown: " + infoWindow); 98 | } 99 | 100 | @Override 101 | public void onWindowHideStarted(@NonNull InfoWindow infoWindow) { 102 | Log.d("debug", "onWindowHideStarted: " + infoWindow); 103 | } 104 | 105 | @Override 106 | public void onWindowHidden(@NonNull InfoWindow infoWindow) { 107 | Log.d("debug", "onWindowHidden: " + infoWindow); 108 | } 109 | } -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/java/com/appolica/sample/fragments/MapViewFragment.java: -------------------------------------------------------------------------------- 1 | package com.appolica.sample.fragments; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.fragment.app.Fragment; 7 | 8 | import android.os.PersistableBundle; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | 13 | import com.appolica.interactiveinfowindow.InfoWindow; 14 | import com.appolica.interactiveinfowindow.InfoWindowManager; 15 | import com.appolica.interactiveinfowindow.customview.TouchInterceptFrameLayout; 16 | import com.appolica.sample.R; 17 | import com.google.android.gms.maps.GoogleMap; 18 | import com.google.android.gms.maps.MapView; 19 | import com.google.android.gms.maps.OnMapReadyCallback; 20 | import com.google.android.gms.maps.model.LatLng; 21 | import com.google.android.gms.maps.model.Marker; 22 | import com.google.android.gms.maps.model.MarkerOptions; 23 | 24 | public class MapViewFragment 25 | extends Fragment 26 | implements OnMapReadyCallback, 27 | GoogleMap.OnMarkerClickListener { 28 | 29 | private static final String RECYCLER_VIEW = "RECYCLER_VIEW_MARKER"; 30 | private static final String FORM_VIEW = "FORM_VIEW_MARKER"; 31 | 32 | private MapView mapView; 33 | 34 | private InfoWindowManager infoWindowManager; 35 | 36 | private InfoWindow recyclerWindow; 37 | private InfoWindow formWindow; 38 | 39 | @Override 40 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 41 | Bundle savedInstanceState) { 42 | // Inflate the layout for this fragment 43 | View view = inflater.inflate(R.layout.fragment_sample_map_view, container, false); 44 | 45 | mapView = view.findViewById(R.id.mapViewFragment); 46 | mapView.onCreate(savedInstanceState); 47 | final TouchInterceptFrameLayout mapViewContainer = 48 | view.findViewById(R.id.mapViewFragmentContainer); 49 | 50 | mapView.getMapAsync(this); 51 | 52 | infoWindowManager = new InfoWindowManager(getParentFragmentManager()); 53 | infoWindowManager.onParentViewCreated(mapViewContainer, savedInstanceState); 54 | 55 | return view; 56 | } 57 | 58 | @Override 59 | public void onSaveInstanceState(@NonNull Bundle outState) { 60 | super.onSaveInstanceState(outState); 61 | mapView.onSaveInstanceState(outState); 62 | infoWindowManager.onSaveInstanceState(outState); 63 | } 64 | 65 | @Override 66 | public void onResume() { 67 | super.onResume(); 68 | mapView.onResume(); 69 | } 70 | 71 | @Override 72 | public void onPause() { 73 | super.onPause(); 74 | mapView.onPause(); 75 | } 76 | 77 | @Override 78 | public void onDestroy() { 79 | super.onDestroy(); 80 | infoWindowManager.onDestroy(); 81 | mapView.onDestroy(); 82 | } 83 | 84 | @Override 85 | public void onLowMemory() { 86 | super.onLowMemory(); 87 | mapView.onLowMemory(); 88 | } 89 | 90 | @Override 91 | public boolean onMarkerClick(@NonNull Marker marker) { 92 | InfoWindow infoWindow = null; 93 | switch (marker.getSnippet()) { 94 | case RECYCLER_VIEW: 95 | infoWindow = recyclerWindow; 96 | break; 97 | case FORM_VIEW: 98 | infoWindow = formWindow; 99 | break; 100 | } 101 | 102 | if (infoWindow != null) { 103 | infoWindowManager.toggle(infoWindow, true); 104 | } 105 | 106 | return true; 107 | } 108 | 109 | @Override 110 | public void onMapReady(@NonNull GoogleMap googleMap) { 111 | infoWindowManager.onMapReady(googleMap); 112 | 113 | final Marker marker1 = googleMap.addMarker(new MarkerOptions().position(new LatLng(5, 5)).snippet(RECYCLER_VIEW)); 114 | final Marker marker2 = googleMap.addMarker(new MarkerOptions().position(new LatLng(1, 1)).snippet(FORM_VIEW)); 115 | 116 | final int offsetX = (int) getResources().getDimension(R.dimen.marker_offset_x); 117 | final int offsetY = (int) getResources().getDimension(R.dimen.marker_offset_y); 118 | 119 | final InfoWindow.MarkerSpecification markerSpec = 120 | new InfoWindow.MarkerSpecification(offsetX, offsetY); 121 | 122 | recyclerWindow = new InfoWindow(marker1, markerSpec, new RecyclerViewFragment()); 123 | formWindow = new InfoWindow(marker2, markerSpec, new FormFragment()); 124 | 125 | googleMap.setOnMarkerClickListener(this); 126 | } 127 | } -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/java/com/appolica/sample/fragments/RecyclerViewFragment.java: -------------------------------------------------------------------------------- 1 | package com.appolica.sample.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | 8 | import androidx.annotation.Nullable; 9 | import androidx.fragment.app.Fragment; 10 | import androidx.recyclerview.widget.LinearLayoutManager; 11 | import androidx.recyclerview.widget.RecyclerView; 12 | 13 | import com.appolica.sample.R; 14 | 15 | public class RecyclerViewFragment extends Fragment { 16 | 17 | @Nullable 18 | @Override 19 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 20 | final View view = inflater.inflate(R.layout.info_window_recyclerview_fragment, container, false); 21 | 22 | return view; 23 | } 24 | 25 | @Override 26 | public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { 27 | super.onViewCreated(view, savedInstanceState); 28 | 29 | final RecyclerView recyclerView = view.findViewById(R.id.infoWindowRecyclerView); 30 | final InfoWindowRVAdapter adapter = new InfoWindowRVAdapter(); 31 | 32 | recyclerView.setHasFixedSize(true); 33 | recyclerView.setLayoutManager(new LinearLayoutManager(getContext())); 34 | recyclerView.setAdapter(adapter); 35 | 36 | for (int i = 0; i < 20; i++) { 37 | adapter.addItem("Item " + i); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/res/layout/activity_map.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/res/layout/activity_sample_with_map_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/res/layout/activity_sample_with_map_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/res/layout/fragment_sample_map.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/res/layout/fragment_sample_map_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /InteractiveInfoWindowAndroid/sample/src/main/res/layout/info_window_form_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 |