├── .github └── workflows │ └── build.yml ├── .gitignore ├── LICENSE.txt ├── README.md ├── build.gradle ├── checkstyle.xml ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── maven_publish.gradle └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── alexvasilkov │ │ └── gestures │ │ ├── GestureController.java │ │ ├── GestureControllerForPager.java │ │ ├── Settings.java │ │ ├── State.java │ │ ├── StateController.java │ │ ├── animation │ │ ├── ImageViewHelper.java │ │ ├── ViewPosition.java │ │ ├── ViewPositionAnimator.java │ │ └── ViewPositionHolder.java │ │ ├── commons │ │ ├── CropAreaView.java │ │ ├── DepthPageTransformer.java │ │ ├── RecyclePagerAdapter.java │ │ ├── circle │ │ │ ├── CircleGestureImageView.java │ │ │ └── CircleImageView.java │ │ └── package-info.java │ │ ├── internal │ │ ├── AnimationEngine.java │ │ ├── DebugOverlay.java │ │ ├── ExitController.java │ │ ├── Fps.java │ │ ├── GestureDebug.java │ │ ├── MovementBounds.java │ │ ├── UnitsUtils.java │ │ ├── ZoomBounds.java │ │ ├── detectors │ │ │ ├── RotationGestureDetector.java │ │ │ └── ScaleGestureDetectorFixed.java │ │ └── package-info.java │ │ ├── transition │ │ ├── GestureTransitions.java │ │ ├── ViewsCoordinator.java │ │ ├── ViewsTransitionAnimator.java │ │ ├── internal │ │ │ ├── FromBaseListener.java │ │ │ ├── FromListViewListener.java │ │ │ ├── FromRecyclerViewListener.java │ │ │ ├── IntoViewPager2Listener.java │ │ │ ├── IntoViewPagerListener.java │ │ │ └── package-info.java │ │ └── tracker │ │ │ ├── AbstractTracker.java │ │ │ ├── FromTracker.java │ │ │ ├── IntoTracker.java │ │ │ └── SimpleTracker.java │ │ ├── utils │ │ ├── ClipHelper.java │ │ ├── CropUtils.java │ │ ├── FloatScroller.java │ │ ├── GravityUtils.java │ │ └── MathUtils.java │ │ └── views │ │ ├── GestureFrameLayout.java │ │ ├── GestureImageView.java │ │ └── interfaces │ │ ├── AnimatorView.java │ │ ├── ClipBounds.java │ │ ├── ClipView.java │ │ └── GestureView.java │ └── res │ └── values │ └── attrs.xml ├── sample ├── .gitignore ├── art │ ├── demo.gif │ ├── ic_launcher.png │ ├── ic_launcher.psd │ ├── logo.png │ ├── logo.psd │ └── logo_small.png ├── build.gradle ├── commons.gradle ├── release.gradle ├── signing │ ├── .gitignore │ ├── debug.jks │ ├── play_account.json.enc │ └── upload.jks.enc └── src │ └── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ └── com │ │ └── alexvasilkov │ │ └── gestures │ │ └── sample │ │ ├── SampleApplication.java │ │ ├── base │ │ ├── BaseActivity.java │ │ ├── BaseSettingsActivity.java │ │ ├── StartActivity.java │ │ └── settings │ │ │ ├── SettingsController.java │ │ │ └── SettingsMenu.java │ │ ├── demo │ │ ├── DemoActivity.java │ │ ├── adapter │ │ │ ├── DefaultEndlessRecyclerAdapter.java │ │ │ ├── EndlessRecyclerAdapter.java │ │ │ ├── PhotoListAdapter.java │ │ │ └── PhotoPagerAdapter.java │ │ └── utils │ │ │ ├── AspectImageView.java │ │ │ ├── DecorUtils.java │ │ │ ├── DemoGlideHelper.java │ │ │ ├── FlickrApi.java │ │ │ └── RecyclerAdapterHelper.java │ │ └── ex │ │ ├── ExamplesActivity.java │ │ ├── animations │ │ ├── ImageAnimationActivity.java │ │ ├── RoundImageAnimationActivity.java │ │ └── cross │ │ │ ├── CrossEvents.java │ │ │ ├── FullImageActivity.java │ │ │ └── ImageCrossAnimationActivity.java │ │ ├── image │ │ ├── control │ │ │ └── ImageControlActivity.java │ │ ├── crop │ │ │ └── ImageCropActivity.java │ │ ├── pager │ │ │ ├── ViewPagerActivity.java │ │ │ └── ViewPagerAdapter.java │ │ └── viewer │ │ │ └── ImageViewerActivity.java │ │ ├── layout │ │ ├── pager │ │ │ ├── LayoutsInPagerActivity.java │ │ │ └── LayoutsPagerAdapter.java │ │ └── viewer │ │ │ └── LayoutViewerActivity.java │ │ ├── other │ │ ├── markers │ │ │ ├── ImageMarkersActivity.java │ │ │ ├── Marker.java │ │ │ └── MarkersOverlay.java │ │ ├── scene │ │ │ ├── Item.java │ │ │ ├── SceneActivity.java │ │ │ └── SceneView.java │ │ └── text │ │ │ ├── CustomViewActivity.java │ │ │ └── GestureTextView.java │ │ ├── transitions │ │ ├── complex │ │ │ ├── BaseComplexListActivity.java │ │ │ ├── ListAdapter.java │ │ │ ├── ListAnyToAllActivity.java │ │ │ ├── ListAnyToAnyActivity.java │ │ │ ├── ListItem.java │ │ │ └── PagerAdapter.java │ │ └── recycler │ │ │ ├── PagerAdapter.java │ │ │ ├── RecyclerAdapter.java │ │ │ └── RecyclerToPagerActivity.java │ │ └── utils │ │ ├── GlideHelper.java │ │ └── Painting.java │ └── res │ ├── drawable-hdpi │ ├── ic_arrow_back_white_24dp.png │ ├── ic_check_white_24dp.png │ ├── ic_crop_16_9_black_24dp.png │ ├── ic_crop_circle_black_24dp.png │ ├── ic_crop_original_black_24dp.png │ ├── ic_crop_square_black_24dp.png │ ├── ic_ex_complex_list.png │ ├── ic_ex_crop.png │ ├── ic_ex_custom_text.png │ ├── ic_ex_image.png │ ├── ic_ex_image_animation.png │ ├── ic_ex_layout.png │ ├── ic_ex_list.png │ ├── ic_ex_markers.png │ ├── ic_ex_objects_control.png │ ├── ic_ex_pager.png │ ├── ic_ex_state_control.png │ ├── ic_grid_on_black_24dp.png │ ├── ic_info_outline_white_24dp.png │ ├── ic_place_white_24dp.png │ ├── ic_restore_black_24dp.png │ ├── ic_rotate_90_degrees_ccw_black_24dp.png │ ├── ic_zoom_in_black_24dp.png │ └── ic_zoom_out_black_24dp.png │ ├── drawable-night-nodpi │ └── start_background.jpg │ ├── drawable-nodpi │ ├── demo_top.jpg │ ├── item_ball.png │ ├── item_pin.png │ ├── painting_01.jpg │ ├── painting_02.jpg │ ├── painting_03.jpg │ ├── painting_04.jpg │ ├── painting_05.jpg │ ├── painting_thumb_01.jpg │ ├── painting_thumb_02.jpg │ ├── painting_thumb_03.jpg │ ├── painting_thumb_04.jpg │ ├── painting_thumb_05.jpg │ ├── start_background.jpg │ ├── start_text.png │ └── world_map.png │ ├── drawable-xhdpi │ ├── ic_arrow_back_white_24dp.png │ ├── ic_check_white_24dp.png │ ├── ic_crop_16_9_black_24dp.png │ ├── ic_crop_circle_black_24dp.png │ ├── ic_crop_original_black_24dp.png │ ├── ic_crop_square_black_24dp.png │ ├── ic_ex_complex_list.png │ ├── ic_ex_crop.png │ ├── ic_ex_custom_text.png │ ├── ic_ex_image.png │ ├── ic_ex_image_animation.png │ ├── ic_ex_layout.png │ ├── ic_ex_list.png │ ├── ic_ex_markers.png │ ├── ic_ex_objects_control.png │ ├── ic_ex_pager.png │ ├── ic_ex_state_control.png │ ├── ic_grid_on_black_24dp.png │ ├── ic_info_outline_white_24dp.png │ ├── ic_place_white_24dp.png │ ├── ic_restore_black_24dp.png │ ├── ic_rotate_90_degrees_ccw_black_24dp.png │ ├── ic_zoom_in_black_24dp.png │ └── ic_zoom_out_black_24dp.png │ ├── drawable-xxhdpi │ ├── ic_arrow_back_white_24dp.png │ ├── ic_check_white_24dp.png │ ├── ic_crop_16_9_black_24dp.png │ ├── ic_crop_circle_black_24dp.png │ ├── ic_crop_original_black_24dp.png │ ├── ic_crop_square_black_24dp.png │ ├── ic_ex_complex_list.png │ ├── ic_ex_crop.png │ ├── ic_ex_custom_text.png │ ├── ic_ex_image.png │ ├── ic_ex_image_animation.png │ ├── ic_ex_layout.png │ ├── ic_ex_list.png │ ├── ic_ex_markers.png │ ├── ic_ex_objects_control.png │ ├── ic_ex_pager.png │ ├── ic_ex_state_control.png │ ├── ic_grid_on_black_24dp.png │ ├── ic_info_outline_white_24dp.png │ ├── ic_place_white_24dp.png │ ├── ic_restore_black_24dp.png │ ├── ic_rotate_90_degrees_ccw_black_24dp.png │ ├── ic_zoom_in_black_24dp.png │ └── ic_zoom_out_black_24dp.png │ ├── drawable-xxxhdpi │ ├── ic_arrow_back_white_24dp.png │ ├── ic_check_white_24dp.png │ ├── ic_crop_16_9_black_24dp.png │ ├── ic_crop_circle_black_24dp.png │ ├── ic_crop_original_black_24dp.png │ ├── ic_crop_square_black_24dp.png │ ├── ic_ex_complex_list.png │ ├── ic_ex_crop.png │ ├── ic_ex_custom_text.png │ ├── ic_ex_image.png │ ├── ic_ex_image_animation.png │ ├── ic_ex_layout.png │ ├── ic_ex_list.png │ ├── ic_ex_markers.png │ ├── ic_ex_objects_control.png │ ├── ic_ex_pager.png │ ├── ic_ex_state_control.png │ ├── ic_grid_on_black_24dp.png │ ├── ic_info_outline_white_24dp.png │ ├── ic_place_white_24dp.png │ ├── ic_restore_black_24dp.png │ ├── ic_rotate_90_degrees_ccw_black_24dp.png │ ├── ic_zoom_in_black_24dp.png │ └── ic_zoom_out_black_24dp.png │ ├── drawable │ ├── demo_circle_border.xml │ ├── demo_photo_border.xml │ ├── item_left_background.xml │ ├── item_right_background.xml │ └── shadow_bottom.xml │ ├── layout │ ├── complex_list_item_images.xml │ ├── complex_list_item_text.xml │ ├── complex_list_screen.xml │ ├── custom_view_screen.xml │ ├── demo_item_extra_error.xml │ ├── demo_item_extra_loading.xml │ ├── demo_item_photo.xml │ ├── demo_item_photo_full.xml │ ├── demo_screen.xml │ ├── examples_list_header.xml │ ├── examples_list_item.xml │ ├── examples_screen.xml │ ├── image_animation_round_screen.xml │ ├── image_animation_screen.xml │ ├── image_control_screen.xml │ ├── image_crop_screen.xml │ ├── image_cross_animation_from_screen.xml │ ├── image_cross_animation_to_screen.xml │ ├── image_markers_screen.xml │ ├── image_pager_screen.xml │ ├── image_viewer_screen.xml │ ├── info_dialog.xml │ ├── layout_pager_item.xml │ ├── layout_pager_screen.xml │ ├── layout_viewer_screen.xml │ ├── list_image_item.xml │ ├── list_recycler_screen.xml │ └── start_screen.xml │ ├── mipmap-anydpi-v26 │ └── ic_launcher.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_foreground.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_foreground.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_foreground.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_foreground.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_foreground.png │ ├── values-land │ └── integers.xml │ ├── values-night │ ├── colors.xml │ └── themes.xml │ └── values │ ├── attrs.xml │ ├── colors.xml │ ├── dimens.xml │ ├── ids.xml │ ├── integers.xml │ ├── paintings.xml │ ├── strings.xml │ ├── strings_ex_info.xml │ ├── styles.xml │ └── themes.xml └── settings.gradle /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | 18 | - name: Set up JDK 11 19 | uses: actions/setup-java@v1 20 | with: 21 | java-version: 11 22 | 23 | - name: Build and test 24 | run: | 25 | ./gradlew assembleDebug checkstyle lintDebug --stacktrace 26 | 27 | - name: Publish sample app 28 | if: github.event_name == 'push' 29 | env: 30 | ENCRYPT_KEY: ${{ secrets.ENCRYPT_KEY }} 31 | UPLOAD_KEYSTORE_PWD: ${{ secrets.UPLOAD_KEYSTORE_PWD }} 32 | run: | 33 | git fetch --unshallow # Build number will be incorrect for shallow copy 34 | ./gradlew publishSample --stacktrace 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | *.iml 4 | build 5 | local.properties 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | GestureViews 2 | ============ 3 | 4 | [![Maven][mvn-img]][mvn-url] 5 | [![Build][build-img]][build-url] 6 | 7 | ImageView and FrameLayout with gestures control and position animation. 8 | 9 | Main goal of this library is to make images viewing process as smooth as possible and to make it 10 | easier for developers to integrate it into their apps. 11 | 12 | #### Features #### 13 | 14 | - Gestures support: pan, zoom, quick scale, fling, double tap, rotation. 15 | - [Seamless integration](https://github.com/alexvasilkov/GestureViews/wiki/Usage#viewpager) with ViewPager (panning smoothly turns into ViewPager flipping and vise versa). 16 | - [View position animation](https://github.com/alexvasilkov/GestureViews/wiki/Basic-animations) ("opening" animation). Useful to animate into full image view mode. 17 | - [Advanced animation](https://github.com/alexvasilkov/GestureViews/wiki/Advanced-animations) from RecyclerView (or ListView) into ViewPager. 18 | - Exit full image mode by scroll and scale gestures. 19 | - Rounded images with animations support. 20 | - [Image cropping](https://github.com/alexvasilkov/GestureViews/wiki/Image-cropping) (supports rotation). 21 | - [Lots of settings](https://github.com/alexvasilkov/GestureViews/wiki/Settings). 22 | - [Gestures listener](https://github.com/alexvasilkov/GestureViews/wiki/Usage#listeners): down (touch), up (touch), single tap, double tap, long press. 23 | - Custom state animation (animating position, zoom, rotation). 24 | - Supports both ImageView and FrameLayout out of the box, also supports [custom views](https://github.com/alexvasilkov/GestureViews/wiki/Custom-views). 25 | 26 | #### Sample app #### 27 | 28 | 29 | Get it on Google Play 30 | 31 | 32 | #### Demo video #### 33 | 34 | [YouTube](https://youtu.be/KDJj08qN7n4) 35 | 36 | [![Demo video](https://github.com/alexvasilkov/GestureViews/raw/master/sample/art/demo.gif)](https://youtu.be/KDJj08qN7n4) 37 | 38 | #### Usage #### 39 | 40 | Add dependency to your `build.gradle` file: 41 | 42 | implementation 'com.alexvasilkov:gesture-views:2.8.3' 43 | 44 | [Usage wiki](https://github.com/alexvasilkov/GestureViews/wiki/Usage) 45 | 46 | [Javadoc][javadoc-url] 47 | 48 | [Sample app sources](https://github.com/alexvasilkov/GestureViews/tree/master/sample) 49 | 50 | #### License #### 51 | 52 | Licensed under the Apache License, Version 2.0 (the "License"); 53 | you may not use this file except in compliance with the License. 54 | You may obtain a copy of the License at 55 | 56 | http://www.apache.org/licenses/LICENSE-2.0 57 | 58 | Unless required by applicable law or agreed to in writing, software 59 | distributed under the License is distributed on an "AS IS" BASIS, 60 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 61 | See the License for the specific language governing permissions and 62 | limitations under the License. 63 | 64 | [mvn-url]: https://maven-badges.herokuapp.com/maven-central/com.alexvasilkov/gesture-views 65 | [mvn-img]: https://img.shields.io/maven-central/v/com.alexvasilkov/gesture-views.svg?style=flat-square 66 | 67 | [build-url]: https://actions-badge.atrox.dev/alexvasilkov/GestureViews/goto?ref=master 68 | [build-img]: https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Falexvasilkov%2FGestureViews%2Fbadge%3Fref%3Dmaster&style=flat-square 69 | 70 | [javadoc-url]: http://javadoc.io/doc/com.alexvasilkov/gesture-views 71 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | buildscript { 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:7.0.4' 9 | } 10 | } 11 | 12 | allprojects { 13 | buildscript { 14 | repositories { 15 | mavenCentral() 16 | } 17 | } 18 | repositories { 19 | google() 20 | mavenCentral() 21 | } 22 | 23 | // Enabling checkstyle for all sub projects 24 | project == rootProject || project.afterEvaluate { 25 | project.apply plugin: 'checkstyle' 26 | 27 | project.extensions.getByName('checkstyle').with { 28 | toolVersion = '8.32' 29 | configFile file("${rootDir}/checkstyle.xml") 30 | } 31 | 32 | task checkstyle(type: Checkstyle) { 33 | source 'src' 34 | include '**/*.java', '**/*.xml' 35 | classpath = files() 36 | } 37 | 38 | project.tasks.getByName('check').dependsOn 'checkstyle' 39 | 40 | project.extensions.getByName('android').with { 41 | lintOptions { 42 | ignore 'GoogleAppIndexingWarning', 'ContentDescription', 43 | 'RtlHardcoded', 'IconMissingDensityFolder' 44 | } 45 | } 46 | } 47 | 48 | gradle.projectsEvaluated { 49 | tasks.withType(JavaCompile) { 50 | options.compilerArgs << "-Xlint:deprecation" 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # More memory to build on Travis 2 | org.gradle.jvmargs=-Xmx1024m 3 | 4 | # AndroidX transformation of external libs 5 | android.enableJetifier=true 6 | android.useAndroidX=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 6 | -------------------------------------------------------------------------------- /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 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 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 Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /library/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml -------------------------------------------------------------------------------- /library/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.library' 3 | } 4 | 5 | ext.group = 'com.alexvasilkov' 6 | ext.artifactId = 'gesture-views' 7 | ext.version = '2.8.3' 8 | ext.name = 'GestureViews' 9 | ext.description = 'ImageView and FrameLayout with gestures control and position animation' 10 | ext.github = 'https://github.com/alexvasilkov/GestureViews' 11 | ext.githubScm = 'scm:git@github.com:alexvasilkov/GestureViews.git' 12 | 13 | android { 14 | compileSdkVersion 31 15 | 16 | defaultConfig { 17 | minSdkVersion 14 18 | targetSdkVersion 31 19 | versionName project.ext.version 20 | } 21 | 22 | compileOptions { 23 | sourceCompatibility JavaVersion.VERSION_1_7 24 | targetCompatibility JavaVersion.VERSION_1_7 25 | } 26 | } 27 | 28 | dependencies { 29 | compileOnly 'androidx.annotation:annotation:1.3.0' 30 | compileOnly 'androidx.viewpager:viewpager:1.0.0' 31 | compileOnly 'androidx.viewpager2:viewpager2:1.0.0' 32 | compileOnly 'androidx.recyclerview:recyclerview:1.2.1' 33 | } 34 | 35 | // New version can be uploaded with './gradlew clean :library:publish' 36 | apply from: 'maven_publish.gradle' 37 | -------------------------------------------------------------------------------- /library/maven_publish.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'maven-publish' 2 | apply plugin: 'signing' 3 | 4 | task javadocs(type: Javadoc) { 5 | source = android.sourceSets.main.java.sourceFiles 6 | classpath = files(android.bootClasspath) 7 | 8 | android.libraryVariants.all { variant -> 9 | owner.classpath += variant.javaCompileProvider.get().classpath 10 | } 11 | 12 | exclude '**/BuildConfig.java' 13 | exclude '**/R.java' 14 | 15 | title "${project.ext.name} ${project.ext.version} API" 16 | options { 17 | windowTitle "${project.ext.name} ${project.ext.version} API" 18 | } 19 | } 20 | 21 | task javadocsJar(type: Jar, dependsOn: javadocs) { 22 | archiveClassifier.set('javadoc') 23 | from javadocs.destinationDir 24 | } 25 | 26 | task sourcesJar(type: Jar) { 27 | archiveClassifier.set('sources') 28 | from android.sourceSets.main.java.sourceFiles 29 | } 30 | 31 | afterEvaluate { 32 | publishing { 33 | publications { 34 | release(MavenPublication) { 35 | from components.release 36 | artifact source: javadocsJar, classifier: 'javadoc' 37 | artifact source: sourcesJar, classifier: 'sources' 38 | 39 | groupId = project.ext.group 40 | artifactId = project.ext.artifactId 41 | version = project.ext.version 42 | 43 | pom { 44 | name = project.ext.name 45 | description = project.ext.description 46 | packaging = 'aar' 47 | url = project.ext.github 48 | licenses { 49 | license { 50 | name = 'The Apache License, Version 2.0' 51 | url = 'http://www.apache.org/licenses/LICENSE-2.0.txt' 52 | } 53 | } 54 | developers { 55 | developer { 56 | id = 'alexvasilkov' 57 | name = 'Alex Vasilkov' 58 | } 59 | } 60 | scm { 61 | connection = project.ext.githubScm 62 | developerConnection = project.ext.githubScm 63 | url = project.ext.github 64 | } 65 | } 66 | } 67 | } 68 | repositories { 69 | maven { 70 | def releasesRepoUrl = 'https://oss.sonatype.org/service/local/staging/deploy/maven2/' 71 | def snapshotsRepoUrl = 'https://oss.sonatype.org/content/repositories/snapshots/' 72 | url = project.ext.version.contains('SNAPSHOT') ? snapshotsRepoUrl : releasesRepoUrl 73 | credentials { 74 | username = findProperty('maven.alexvasilkov.username') 75 | password = findProperty('maven.alexvasilkov.password') 76 | } 77 | } 78 | } 79 | } 80 | 81 | signing { 82 | required { gradle.taskGraph.hasTask('publish') } 83 | 84 | sign publishing.publications.release 85 | 86 | // We need to re-map singing properties to be able to mix properties for different projects 87 | project.ext.'signing.keyId' = findProperty('signing.alexvasilkov.keyId') 88 | project.ext.'signing.password' = findProperty('signing.alexvasilkov.password') 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /library/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/animation/ImageViewHelper.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.animation; 2 | 3 | import android.graphics.Matrix; 4 | import android.graphics.RectF; 5 | import android.widget.ImageView; 6 | 7 | class ImageViewHelper { 8 | 9 | private static final RectF tmpSrc = new RectF(); 10 | private static final RectF tmpDst = new RectF(); 11 | 12 | private ImageViewHelper() {} 13 | 14 | /** 15 | * Helper method to calculate drawing matrix. Based on ImageView source code. 16 | */ 17 | @SuppressWarnings("SameParameterValue") 18 | static void applyScaleType(ImageView.ScaleType type, 19 | int dwidth, int dheight, 20 | int vwidth, int vheight, 21 | Matrix imageMatrix, 22 | Matrix outMatrix) { 23 | 24 | if (ImageView.ScaleType.CENTER == type) { 25 | // Center bitmap in view, no scaling. 26 | outMatrix.setTranslate((vwidth - dwidth) * 0.5f, 27 | (vheight - dheight) * 0.5f); 28 | } else if (ImageView.ScaleType.CENTER_CROP == type) { 29 | float scale; 30 | float dx = 0; 31 | float dy = 0; 32 | 33 | if (dwidth * vheight > vwidth * dheight) { 34 | scale = (float) vheight / (float) dheight; 35 | dx = (vwidth - dwidth * scale) * 0.5f; 36 | } else { 37 | scale = (float) vwidth / (float) dwidth; 38 | dy = (vheight - dheight * scale) * 0.5f; 39 | } 40 | 41 | outMatrix.setScale(scale, scale); 42 | outMatrix.postTranslate(dx, dy); 43 | } else if (ImageView.ScaleType.CENTER_INSIDE == type) { 44 | float scale; 45 | float dx; 46 | float dy; 47 | 48 | if (dwidth <= vwidth && dheight <= vheight) { 49 | scale = 1.0f; 50 | } else { 51 | scale = Math.min((float) vwidth / (float) dwidth, 52 | (float) vheight / (float) dheight); 53 | } 54 | 55 | dx = (vwidth - dwidth * scale) * 0.5f; 56 | dy = (vheight - dheight * scale) * 0.5f; 57 | 58 | outMatrix.setScale(scale, scale); 59 | outMatrix.postTranslate(dx, dy); 60 | } else { 61 | Matrix.ScaleToFit scaleToFit = scaleTypeToScaleToFit(type); 62 | if (scaleToFit == null) { 63 | outMatrix.set(imageMatrix); 64 | } else { 65 | // Generate the required transform. 66 | tmpSrc.set(0, 0, dwidth, dheight); 67 | tmpDst.set(0, 0, vwidth, vheight); 68 | outMatrix.setRectToRect(tmpSrc, tmpDst, scaleToFit); 69 | } 70 | } 71 | } 72 | 73 | private static Matrix.ScaleToFit scaleTypeToScaleToFit(ImageView.ScaleType type) { 74 | switch (type) { 75 | case FIT_XY: 76 | return Matrix.ScaleToFit.FILL; 77 | case FIT_START: 78 | return Matrix.ScaleToFit.START; 79 | case FIT_CENTER: 80 | return Matrix.ScaleToFit.CENTER; 81 | case FIT_END: 82 | return Matrix.ScaleToFit.END; 83 | default: 84 | return null; 85 | } 86 | } 87 | 88 | } 89 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/animation/ViewPositionHolder.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.animation; 2 | 3 | import android.os.Build; 4 | import android.view.View; 5 | import android.view.ViewTreeObserver; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | /** 10 | * Helper class that monitors {@link View} position on screen and notifies 11 | * {@link OnViewPositionChangeListener} if any changes were detected. 12 | */ 13 | class ViewPositionHolder implements ViewTreeObserver.OnPreDrawListener { 14 | 15 | private final ViewPosition pos = ViewPosition.newInstance(); 16 | 17 | private OnViewPositionChangeListener listener; 18 | private View view; 19 | private View.OnAttachStateChangeListener attachListener; 20 | private boolean isPaused; 21 | 22 | @Override 23 | public boolean onPreDraw() { 24 | update(); 25 | return true; 26 | } 27 | 28 | void init(@NonNull View view, @NonNull OnViewPositionChangeListener listener) { 29 | clear(); // Cleaning up old listeners, just in case 30 | 31 | this.view = view; 32 | this.listener = listener; 33 | 34 | attachListener = new View.OnAttachStateChangeListener() { 35 | @Override 36 | public void onViewAttachedToWindow(View view) { 37 | onViewAttached(view, true); 38 | } 39 | 40 | @Override 41 | public void onViewDetachedFromWindow(View view) { 42 | onViewAttached(view, false); 43 | } 44 | }; 45 | view.addOnAttachStateChangeListener(attachListener); 46 | 47 | onViewAttached(view, isAttached(view)); 48 | 49 | if (isLaidOut(view)) { 50 | update(); 51 | } 52 | } 53 | 54 | private void onViewAttached(View view, boolean attached) { 55 | view.getViewTreeObserver().removeOnPreDrawListener(this); 56 | if (attached) { 57 | view.getViewTreeObserver().addOnPreDrawListener(this); 58 | } 59 | } 60 | 61 | void clear() { 62 | if (view != null) { 63 | view.removeOnAttachStateChangeListener(attachListener); 64 | onViewAttached(view, false); 65 | } 66 | 67 | pos.view.setEmpty(); 68 | pos.viewport.setEmpty(); 69 | pos.image.setEmpty(); 70 | 71 | view = null; 72 | attachListener = null; 73 | listener = null; 74 | isPaused = false; 75 | } 76 | 77 | void pause(boolean paused) { 78 | if (isPaused == paused) { 79 | return; 80 | } 81 | 82 | isPaused = paused; 83 | update(); 84 | } 85 | 86 | private void update() { 87 | if (view != null && listener != null && !isPaused) { 88 | boolean changed = ViewPosition.apply(pos, view); 89 | if (changed) { 90 | listener.onViewPositionChanged(pos); 91 | } 92 | } 93 | } 94 | 95 | private static boolean isLaidOut(View view) { 96 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 97 | return view.isLaidOut(); 98 | } else { 99 | return view.getWidth() > 0 && view.getHeight() > 0; 100 | } 101 | } 102 | 103 | private static boolean isAttached(View view) { 104 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 105 | return view.isAttachedToWindow(); 106 | } else { 107 | return view.getWindowToken() != null; 108 | } 109 | } 110 | 111 | interface OnViewPositionChangeListener { 112 | void onViewPositionChanged(@NonNull ViewPosition position); 113 | } 114 | 115 | } 116 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/commons/DepthPageTransformer.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.commons; 2 | 3 | import android.os.Build; 4 | import android.view.View; 5 | 6 | import androidx.annotation.NonNull; 7 | import androidx.viewpager.widget.ViewPager; 8 | import androidx.viewpager2.widget.ViewPager2; 9 | 10 | /** 11 | * Page transformer which will scroll previous page as usual and will scale next page with alpha. 12 | *

13 | * Usage: {@link ViewPager#setPageTransformer(boolean, ViewPager.PageTransformer) 14 | * ViewPager.setPageTransformer(true, new DepthPageTransformer())} or 15 | * {@link ViewPager2#setPageTransformer(ViewPager2.PageTransformer) 16 | * ViewPager2.setPageTransformer(new DepthPageTransformer())}. 17 | */ 18 | public class DepthPageTransformer implements ViewPager.PageTransformer, ViewPager2.PageTransformer { 19 | 20 | private static final float MIN_SCALE = 0.75f; 21 | 22 | @Override 23 | public void transformPage(@NonNull View view, float position) { 24 | if (0 < position && position < 1f) { 25 | // Fade the page out 26 | view.setAlpha(1f - position); 27 | 28 | // Counteract the default slide transition 29 | view.setTranslationX(-view.getWidth() * position); 30 | 31 | // Scale the page down (between MIN_SCALE and 1) 32 | float scaleFactor = 1f - (1f - MIN_SCALE) * position; 33 | view.setScaleX(scaleFactor); 34 | view.setScaleY(scaleFactor); 35 | if (Build.VERSION.SDK_INT >= 21) { 36 | view.setTranslationZ(scaleFactor - 1f); 37 | } 38 | } else { 39 | view.setAlpha(1f); 40 | view.setTranslationX(0f); 41 | view.setScaleX(1f); 42 | view.setScaleY(1f); 43 | if (Build.VERSION.SDK_INT >= 21) { 44 | view.setTranslationZ(0f); 45 | } 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/commons/RecyclePagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.commons; 2 | 3 | import android.util.SparseArray; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | import androidx.recyclerview.widget.RecyclerView; 10 | import androidx.viewpager.widget.PagerAdapter; 11 | 12 | import java.util.LinkedList; 13 | import java.util.Queue; 14 | 15 | /** 16 | * {@link PagerAdapter} implementation where each page is a regular view. Supports views recycling. 17 | *

18 | * Inspired by {@link RecyclerView.Adapter}. 19 | */ 20 | public abstract class RecyclePagerAdapter 21 | extends PagerAdapter { 22 | 23 | private final Queue cache = new LinkedList<>(); 24 | private final SparseArray attached = new SparseArray<>(); 25 | 26 | public abstract VH onCreateViewHolder(@NonNull ViewGroup container); 27 | 28 | public abstract void onBindViewHolder(@NonNull VH holder, int position); 29 | 30 | public void onRecycleViewHolder(@NonNull VH holder) { 31 | } 32 | 33 | /** 34 | * Returns ViewHolder for given position if it exists within ViewPager, or null otherwise. 35 | * 36 | * @param position Item position 37 | * @return View holder for given position 38 | */ 39 | @Nullable 40 | public VH getViewHolder(int position) { 41 | return attached.get(position); 42 | } 43 | 44 | @NonNull 45 | @Override 46 | public Object instantiateItem(@NonNull ViewGroup container, int position) { 47 | VH holder = cache.poll(); 48 | if (holder == null) { 49 | holder = onCreateViewHolder(container); 50 | } 51 | attached.put(position, holder); 52 | 53 | // We should not use previous layout params, since ViewPager stores 54 | // important information there which cannot be reused 55 | container.addView(holder.itemView, null); 56 | 57 | onBindViewHolder(holder, position); 58 | return holder; 59 | } 60 | 61 | @SuppressWarnings("unchecked") 62 | @Override 63 | public void destroyItem(ViewGroup container, int position, @NonNull Object object) { 64 | VH holder = (VH) object; 65 | attached.remove(position); 66 | container.removeView(holder.itemView); 67 | cache.offer(holder); 68 | onRecycleViewHolder(holder); 69 | } 70 | 71 | @Override 72 | public boolean isViewFromObject(@NonNull View view, @NonNull Object object) { 73 | ViewHolder holder = (ViewHolder) object; 74 | return holder.itemView == view; 75 | } 76 | 77 | @Override 78 | public int getItemPosition(@NonNull Object object) { 79 | // Forcing all views reinitialization when data set changed. 80 | // It should be safe because we're using views recycling logic. 81 | return POSITION_NONE; 82 | } 83 | 84 | public static class ViewHolder { 85 | public final View itemView; 86 | 87 | public ViewHolder(@NonNull View itemView) { 88 | this.itemView = itemView; 89 | } 90 | } 91 | 92 | } 93 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/commons/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * This package contains common classes which are not directly related to library but still can be 3 | * useful. 4 | *

5 | * Keep in mind, that classes in this package can have breaking changes every time minor 6 | * part of library's version is changed (i.e. from 2.0.3 to 2.1.0). 7 | */ 8 | package com.alexvasilkov.gestures.commons; 9 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/internal/AnimationEngine.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.internal; 2 | 3 | import android.os.Build; 4 | import android.view.View; 5 | 6 | import androidx.annotation.NonNull; 7 | 8 | public abstract class AnimationEngine implements Runnable { 9 | 10 | private static final long FRAME_TIME = 10L; 11 | 12 | private final View view; 13 | private final Fps fps; 14 | 15 | public AnimationEngine(@NonNull View view) { 16 | this.view = view; 17 | this.fps = GestureDebug.isDebugFps() ? new Fps() : null; 18 | } 19 | 20 | @Override 21 | public final void run() { 22 | boolean continueAnimation = onStep(); 23 | 24 | if (fps != null) { 25 | fps.step(); 26 | if (!continueAnimation) { 27 | fps.stop(); 28 | } 29 | } 30 | 31 | if (continueAnimation) { 32 | scheduleNextStep(); 33 | } 34 | } 35 | 36 | public abstract boolean onStep(); 37 | 38 | private void scheduleNextStep() { 39 | view.removeCallbacks(this); 40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { 41 | view.postOnAnimationDelayed(this, FRAME_TIME); 42 | } else { 43 | view.postDelayed(this, FRAME_TIME); 44 | } 45 | } 46 | 47 | public void start() { 48 | if (fps != null) { 49 | fps.start(); 50 | } 51 | 52 | scheduleNextStep(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/internal/Fps.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.internal; 2 | 3 | import android.os.SystemClock; 4 | import android.util.Log; 5 | 6 | class Fps { 7 | 8 | private static final String TAG = "GestureFps"; 9 | 10 | private static final long WARNING_TIME = 20L; // Dropping less than 60 fps in average 11 | private static final long ERROR_TIME = 40L; // Dropping less than 30 fps in average 12 | 13 | private long frameStart; 14 | private long animationStart; 15 | private int framesCount; 16 | 17 | void start() { 18 | if (GestureDebug.isDebugFps()) { 19 | animationStart = frameStart = SystemClock.uptimeMillis(); 20 | framesCount = 0; 21 | } 22 | } 23 | 24 | void stop() { 25 | if (GestureDebug.isDebugFps() && framesCount > 0) { 26 | int time = (int) (SystemClock.uptimeMillis() - animationStart); 27 | Log.d(TAG, "Average FPS: " + Math.round(1000f * framesCount / time)); 28 | } 29 | } 30 | 31 | void step() { 32 | if (GestureDebug.isDebugFps()) { 33 | long frameTime = SystemClock.uptimeMillis() - frameStart; 34 | if (frameTime > ERROR_TIME) { 35 | Log.e(TAG, "Frame time: " + frameTime); 36 | } else if (frameTime > WARNING_TIME) { 37 | Log.w(TAG, "Frame time: " + frameTime); 38 | } 39 | 40 | framesCount++; 41 | frameStart = SystemClock.uptimeMillis(); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/internal/GestureDebug.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.internal; 2 | 3 | public class GestureDebug { 4 | 5 | private static boolean debugFps; 6 | private static boolean debugAnimator; 7 | private static boolean drawDebugOverlay; 8 | 9 | private GestureDebug() {} 10 | 11 | @SuppressWarnings("WeakerAccess") // Public API (kinda) 12 | public static boolean isDebugFps() { 13 | return debugFps; 14 | } 15 | 16 | public static void setDebugFps(boolean debug) { 17 | debugFps = debug; 18 | } 19 | 20 | public static boolean isDebugAnimator() { 21 | return debugAnimator; 22 | } 23 | 24 | public static void setDebugAnimator(boolean debug) { 25 | debugAnimator = debug; 26 | } 27 | 28 | public static boolean isDrawDebugOverlay() { 29 | return drawDebugOverlay; 30 | } 31 | 32 | public static void setDrawDebugOverlay(boolean draw) { 33 | drawDebugOverlay = draw; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/internal/UnitsUtils.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.internal; 2 | 3 | import android.content.Context; 4 | import android.util.TypedValue; 5 | 6 | public class UnitsUtils { 7 | 8 | private UnitsUtils() {} 9 | 10 | public static float toPixels(Context context, float value) { 11 | return toPixels(context, TypedValue.COMPLEX_UNIT_DIP, value); 12 | } 13 | 14 | public static float toPixels(Context context, int type, float value) { 15 | return TypedValue.applyDimension(type, value, 16 | context.getResources().getDisplayMetrics()); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/internal/detectors/ScaleGestureDetectorFixed.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.internal.detectors; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.view.MotionEvent; 7 | import android.view.ScaleGestureDetector; 8 | 9 | import androidx.annotation.NonNull; 10 | 11 | /** 12 | * 'Double tap and swipe' mode works bad for fast gestures. This class tries to fix this issue. 13 | */ 14 | public class ScaleGestureDetectorFixed extends ScaleGestureDetector { 15 | 16 | private float currY; 17 | private float prevY; 18 | 19 | public ScaleGestureDetectorFixed(Context context, OnScaleGestureListener listener) { 20 | super(context, listener); 21 | warmUpScaleDetector(); 22 | } 23 | 24 | /** 25 | * Scale detector is a little buggy when first time scale is occurred. 26 | * So we will feed it with fake motion event to warm it up. 27 | */ 28 | private void warmUpScaleDetector() { 29 | long time = System.currentTimeMillis(); 30 | MotionEvent event = MotionEvent.obtain(time, time, MotionEvent.ACTION_CANCEL, 0f, 0f, 0); 31 | onTouchEvent(event); 32 | event.recycle(); 33 | } 34 | 35 | @Override 36 | public boolean onTouchEvent(@NonNull MotionEvent event) { 37 | final boolean result = super.onTouchEvent(event); 38 | 39 | prevY = currY; 40 | currY = event.getY(); 41 | 42 | if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { 43 | prevY = event.getY(); 44 | } 45 | 46 | return result; 47 | } 48 | 49 | @TargetApi(Build.VERSION_CODES.KITKAT) 50 | private boolean isInDoubleTapMode() { 51 | // Indirectly determine double tap mode 52 | return Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT 53 | && isQuickScaleEnabled() && getCurrentSpan() == getCurrentSpanY(); 54 | } 55 | 56 | @Override 57 | public float getScaleFactor() { 58 | float factor = super.getScaleFactor(); 59 | 60 | if (isInDoubleTapMode()) { 61 | // We will filter buggy factors which may appear when crossing focus point. 62 | // We will also filter factors which are too far from 1, to make scaling smoother. 63 | return (currY > prevY && factor > 1f) || (currY < prevY && factor < 1f) 64 | ? Math.max(0.8f, Math.min(factor, 1.25f)) : 1f; 65 | } else { 66 | return factor; 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/internal/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal classes. 3 | */ 4 | package com.alexvasilkov.gestures.internal; 5 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/transition/internal/FromListViewListener.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.transition.internal; 2 | 3 | import android.view.View; 4 | import android.widget.AbsListView; 5 | import android.widget.ListView; 6 | 7 | import com.alexvasilkov.gestures.transition.tracker.FromTracker; 8 | 9 | public class FromListViewListener extends FromBaseListener { 10 | 11 | public FromListViewListener(ListView list, final FromTracker tracker, boolean autoScroll) { 12 | super(list, tracker, autoScroll); 13 | 14 | if (!autoScroll) { 15 | // No need to track list view scrolling if auto scroll is disabled 16 | return; 17 | } 18 | 19 | // Tracking list view scrolling to pick up newly visible views 20 | list.setOnScrollListener(new AbsListView.OnScrollListener() { 21 | @Override 22 | public void onScroll(AbsListView view, int firstVisible, int visibleCount, int total) { 23 | final ID id = getAnimator() == null ? null : getAnimator().getRequestedId(); 24 | 25 | // If view was requested and list is scrolled we should try to find the view again 26 | if (id != null) { 27 | int position = tracker.getPositionById(id); 28 | if (position >= firstVisible && position < firstVisible + visibleCount) { 29 | View from = tracker.getViewById(id); 30 | if (from != null) { 31 | // View is found, we can set up 'from' view position now 32 | getAnimator().setFromView(id, from); 33 | } 34 | } 35 | } 36 | } 37 | 38 | @Override 39 | public void onScrollStateChanged(AbsListView view, int scrollState) { 40 | // No-op 41 | } 42 | }); 43 | } 44 | 45 | @Override 46 | boolean isShownInList(ListView list, int pos) { 47 | return pos >= list.getFirstVisiblePosition() && pos <= list.getLastVisiblePosition(); 48 | } 49 | 50 | @Override 51 | void scrollToPosition(ListView list, int pos) { 52 | list.setSelection(pos); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/transition/internal/package-info.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Internal classes. 3 | */ 4 | package com.alexvasilkov.gestures.transition.internal; 5 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/transition/tracker/AbstractTracker.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.transition.tracker; 2 | 3 | import android.view.View; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | interface AbstractTracker { 9 | 10 | int NO_POSITION = -1; 11 | 12 | /** 13 | * @param id Item ID 14 | * @return Position of list item which contains element with given ID, 15 | * or {@link #NO_POSITION} if element with given ID is not part of the list.
16 | * Note, that there can be several elements inside single list item, but we only need to know 17 | * list item position, so we can scroll to it if required. 18 | */ 19 | int getPositionById(@NonNull ID id); 20 | 21 | /** 22 | * @param id Item ID 23 | * @return View for given element ID, or {@code null} if view is not found.
24 | * Note, that it is safe to return {@code null} if view is not found on the screen, list view 25 | * will be automatically scrolled to needed position (as returned by 26 | * {@link #getPositionById(Object)}) and this method will be called again. 27 | */ 28 | @Nullable 29 | View getViewById(@NonNull ID id); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/transition/tracker/FromTracker.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.transition.tracker; 2 | 3 | public interface FromTracker extends AbstractTracker { 4 | } 5 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/transition/tracker/IntoTracker.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.transition.tracker; 2 | 3 | import androidx.annotation.Nullable; 4 | 5 | public interface IntoTracker extends AbstractTracker { 6 | 7 | /** 8 | * @param position List position 9 | * @return Item's id at given position, or {@code null} if position is invalid. 10 | * Note, that only one id per position should be possible for "To" view. 11 | */ 12 | @Nullable 13 | ID getIdByPosition(int position); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/transition/tracker/SimpleTracker.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.transition.tracker; 2 | 3 | import android.view.View; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.annotation.Nullable; 7 | 8 | /** 9 | * Class implementing both {@link FromTracker} and {@link IntoTracker} assuming that positions will 10 | * be used as items ids. 11 | *

12 | * Note, that it will only work correctly if both "from" and "to" lists are the same and there will 13 | * be no changes to them which will change existing items' positions. So you can't remove items, or 14 | * add items into the middle, but you can add new items to the end of the list, as long as both 15 | * lists are updated simultaneously. 16 | *

17 | * If you need to handle more advanced cases you should manually implement {@link FromTracker} and 18 | * {@link IntoTracker}, and use items ids instead of their positions. 19 | */ 20 | public abstract class SimpleTracker implements FromTracker, IntoTracker { 21 | 22 | @Override 23 | public Integer getIdByPosition(int position) { 24 | return position; 25 | } 26 | 27 | @Override 28 | public int getPositionById(@NonNull Integer id) { 29 | return id; 30 | } 31 | 32 | @Override 33 | public View getViewById(@NonNull Integer id) { 34 | return getViewAt(id); 35 | } 36 | 37 | @Nullable 38 | protected abstract View getViewAt(int position); 39 | 40 | } 41 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/utils/ClipHelper.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.utils; 2 | 3 | import android.graphics.Canvas; 4 | import android.graphics.RectF; 5 | import android.view.View; 6 | 7 | import androidx.annotation.NonNull; 8 | import androidx.annotation.Nullable; 9 | 10 | import com.alexvasilkov.gestures.State; 11 | import com.alexvasilkov.gestures.views.interfaces.ClipView; 12 | 13 | /** 14 | * Helper class to implement view clipping (with {@link ClipView} interface). 15 | * 16 | * Usage: call {@link #clipView(RectF, float)} method when needed and override 17 | * {@link View#draw(Canvas)} method: 18 | *

{@code
19 |  *   public void draw(Canvas canvas) {
20 |  *       clipHelper.onPreDraw(canvas);
21 |  *       super.draw(canvas);
22 |  *       clipHelper.onPostDraw(canvas);
23 |  *   }
24 |  * }
25 | */ 26 | public class ClipHelper implements ClipView { 27 | 28 | private final View view; 29 | 30 | private boolean isClipping; 31 | 32 | private final RectF clipRect = new RectF(); 33 | private float clipRotation; 34 | 35 | public ClipHelper(@NonNull View view) { 36 | this.view = view; 37 | } 38 | 39 | /** 40 | * {@inheritDoc} 41 | */ 42 | @Override 43 | public void clipView(@Nullable RectF rect, float rotation) { 44 | if (rect == null) { 45 | if (isClipping) { 46 | isClipping = false; 47 | view.invalidate(); 48 | } 49 | } else { 50 | isClipping = true; 51 | 52 | clipRect.set(rect); 53 | clipRotation = rotation; 54 | view.invalidate(); 55 | } 56 | } 57 | 58 | public void onPreDraw(@NonNull Canvas canvas) { 59 | if (isClipping) { 60 | canvas.save(); 61 | 62 | if (State.equals(clipRotation, 0f)) { 63 | canvas.clipRect(clipRect); 64 | } else { 65 | // Note, that prior Android 4.3 (18) canvas matrix is not correctly applied to 66 | // clip rect, clip rect will be set to its upper bound, which is good enough for us. 67 | canvas.rotate(clipRotation, clipRect.centerX(), clipRect.centerY()); 68 | canvas.clipRect(clipRect); 69 | canvas.rotate(-clipRotation, clipRect.centerX(), clipRect.centerY()); 70 | } 71 | } 72 | } 73 | 74 | public void onPostDraw(@NonNull Canvas canvas) { 75 | if (isClipping) { 76 | canvas.restore(); 77 | } 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/utils/CropUtils.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.utils; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.Canvas; 5 | import android.graphics.Matrix; 6 | import android.graphics.Rect; 7 | import android.graphics.drawable.Drawable; 8 | 9 | import androidx.annotation.Nullable; 10 | 11 | import com.alexvasilkov.gestures.GestureController; 12 | import com.alexvasilkov.gestures.Settings; 13 | import com.alexvasilkov.gestures.State; 14 | 15 | public class CropUtils { 16 | 17 | private CropUtils() {} 18 | 19 | /** 20 | * Crops image drawable into bitmap according to current image position. 21 | * 22 | * @param drawable Image drawable 23 | * @param controller Image controller 24 | * @return Cropped image part 25 | */ 26 | @Nullable 27 | public static Bitmap crop(Drawable drawable, GestureController controller) { 28 | if (drawable == null) { 29 | return null; 30 | } 31 | 32 | controller.stopAllAnimations(); 33 | controller.updateState(); // Applying state restrictions 34 | 35 | final Settings settings = controller.getSettings(); 36 | final State state = controller.getState(); 37 | final float zoom = state.getZoom(); 38 | 39 | // Computing crop size for base zoom level (zoom == 1) 40 | int width = Math.round(settings.getMovementAreaW() / zoom); 41 | int height = Math.round(settings.getMovementAreaH() / zoom); 42 | 43 | // Crop area coordinates within viewport 44 | Rect pos = new Rect(); 45 | GravityUtils.getMovementAreaPosition(settings, pos); 46 | 47 | Matrix matrix = new Matrix(); 48 | state.get(matrix); 49 | // Scaling to base zoom level (zoom == 1) 50 | matrix.postScale(1f / zoom, 1f / zoom, pos.left, pos.top); 51 | // Positioning crop area 52 | matrix.postTranslate(-pos.left, -pos.top); 53 | 54 | try { 55 | // Draw drawable into bitmap 56 | Bitmap dst = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 57 | 58 | Canvas canvas = new Canvas(dst); 59 | canvas.concat(matrix); 60 | drawable.draw(canvas); 61 | 62 | return dst; 63 | } catch (OutOfMemoryError e) { 64 | return null; // Not enough memory for cropped bitmap 65 | } 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/utils/GravityUtils.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.utils; 2 | 3 | import android.graphics.Matrix; 4 | import android.graphics.Point; 5 | import android.graphics.Rect; 6 | import android.graphics.RectF; 7 | import android.view.Gravity; 8 | 9 | import androidx.annotation.NonNull; 10 | 11 | import com.alexvasilkov.gestures.Settings; 12 | import com.alexvasilkov.gestures.State; 13 | 14 | public class GravityUtils { 15 | 16 | private static final Matrix tmpMatrix = new Matrix(); 17 | private static final RectF tmpRectF = new RectF(); 18 | 19 | private static final Rect tmpRect1 = new Rect(); 20 | private static final Rect tmpRect2 = new Rect(); 21 | 22 | 23 | private GravityUtils() {} 24 | 25 | /** 26 | * Calculates image position (scaled and rotated) within viewport area with gravity applied. 27 | * 28 | * @param state Image state 29 | * @param settings Image settings 30 | * @param out Output rectangle 31 | */ 32 | public static void getImagePosition( 33 | @NonNull State state, 34 | @NonNull Settings settings, 35 | @NonNull Rect out 36 | ) { 37 | state.get(tmpMatrix); 38 | getImagePosition(tmpMatrix, settings, out); 39 | } 40 | 41 | /** 42 | * Calculates image position (scaled and rotated) within viewport area with gravity applied. 43 | * 44 | * @param matrix Image matrix 45 | * @param settings Image settings 46 | * @param out Output rectangle 47 | */ 48 | public static void getImagePosition( 49 | @NonNull Matrix matrix, 50 | @NonNull Settings settings, 51 | @NonNull Rect out 52 | ) { 53 | tmpRectF.set(0, 0, settings.getImageW(), settings.getImageH()); 54 | 55 | matrix.mapRect(tmpRectF); 56 | 57 | final int w = Math.round(tmpRectF.width()); 58 | final int h = Math.round(tmpRectF.height()); 59 | 60 | // Calculating image position basing on gravity 61 | tmpRect1.set(0, 0, settings.getViewportW(), settings.getViewportH()); 62 | Gravity.apply(settings.getGravity(), w, h, tmpRect1, out); 63 | } 64 | 65 | /** 66 | * Calculates movement area position within viewport area with gravity applied. 67 | * 68 | * @param settings Image settings 69 | * @param out Output rectangle 70 | */ 71 | public static void getMovementAreaPosition(@NonNull Settings settings, @NonNull Rect out) { 72 | tmpRect1.set(0, 0, settings.getViewportW(), settings.getViewportH()); 73 | Gravity.apply(settings.getGravity(), 74 | settings.getMovementAreaW(), settings.getMovementAreaH(), tmpRect1, out); 75 | } 76 | 77 | /** 78 | * Calculates default pivot point for scale and rotation. 79 | * 80 | * @param settings Image settings 81 | * @param out Output point 82 | */ 83 | public static void getDefaultPivot(@NonNull Settings settings, @NonNull Point out) { 84 | getMovementAreaPosition(settings, tmpRect2); 85 | Gravity.apply(settings.getGravity(), 0, 0, tmpRect2, tmpRect1); 86 | out.set(tmpRect1.left, tmpRect1.top); 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/views/interfaces/AnimatorView.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.views.interfaces; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.alexvasilkov.gestures.animation.ViewPositionAnimator; 6 | 7 | /** 8 | * Common interface for views supporting position animation. 9 | */ 10 | public interface AnimatorView { 11 | 12 | /** 13 | * @return {@link ViewPositionAnimator} instance to control animation from other view position. 14 | */ 15 | @NonNull 16 | ViewPositionAnimator getPositionAnimator(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/views/interfaces/ClipBounds.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.views.interfaces; 2 | 3 | import android.graphics.RectF; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | public interface ClipBounds { 8 | 9 | /** 10 | * Clips view so only {@code rect} part will be drawn. 11 | * 12 | * @param rect Rectangle to clip view bounds, or {@code null} to turn clipping off 13 | */ 14 | void clipBounds(@Nullable RectF rect); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/views/interfaces/ClipView.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.views.interfaces; 2 | 3 | import android.graphics.RectF; 4 | 5 | import androidx.annotation.Nullable; 6 | 7 | public interface ClipView { 8 | 9 | /** 10 | * Clips view so only {@code rect} part (modified by view's state) will be drawn. 11 | * 12 | * @param rect Clip rectangle or {@code null} to turn clipping off 13 | * @param rotation Clip rectangle rotation 14 | */ 15 | void clipView(@Nullable RectF rect, float rotation); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /library/src/main/java/com/alexvasilkov/gestures/views/interfaces/GestureView.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.views.interfaces; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.alexvasilkov.gestures.GestureController; 6 | 7 | /** 8 | * Common interface for all Gesture* views. 9 | *

10 | * All classes implementing this interface should be descendants of {@link android.view.View}. 11 | */ 12 | public interface GestureView { 13 | 14 | /** 15 | * Returns {@link GestureController} which is a main engine for all gestures interactions. 16 | *

17 | * Use it to apply settings, access and modify image state and so on. 18 | * 19 | * @return {@link GestureController}. 20 | */ 21 | @NonNull 22 | GestureController getController(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /sample/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml 3 | -------------------------------------------------------------------------------- /sample/art/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/art/demo.gif -------------------------------------------------------------------------------- /sample/art/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/art/ic_launcher.png -------------------------------------------------------------------------------- /sample/art/ic_launcher.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/art/ic_launcher.psd -------------------------------------------------------------------------------- /sample/art/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/art/logo.png -------------------------------------------------------------------------------- /sample/art/logo.psd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/art/logo.psd -------------------------------------------------------------------------------- /sample/art/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/art/logo_small.png -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'com.android.application' 3 | id 'com.github.triplet.play' version '3.7.0' 4 | } 5 | 6 | apply from: 'commons.gradle' 7 | apply from: 'release.gradle' 8 | 9 | android { 10 | compileSdkVersion 31 11 | 12 | defaultConfig { 13 | minSdkVersion 23 14 | targetSdkVersion 31 15 | 16 | setupVersion '2.8.3' 17 | setOutputFileName 'gesture-views' 18 | 19 | resConfigs 'en' 20 | } 21 | 22 | buildTypes { 23 | debug { 24 | applicationIdSuffix '.debug' 25 | } 26 | } 27 | 28 | compileOptions { 29 | sourceCompatibility JavaVersion.VERSION_1_8 30 | targetCompatibility JavaVersion.VERSION_1_8 31 | } 32 | } 33 | 34 | dependencies { 35 | implementation project(':library') 36 | 37 | implementation 'androidx.appcompat:appcompat:1.4.0' 38 | implementation 'androidx.recyclerview:recyclerview:1.2.1' 39 | implementation 'androidx.viewpager2:viewpager2:1.0.0' 40 | implementation 'com.google.android.material:material:1.4.0' 41 | 42 | implementation 'com.github.bumptech.glide:glide:4.12.0' 43 | 44 | implementation 'com.alexvasilkov:android-commons:2.0.2' 45 | implementation 'com.alexvasilkov:events:1.0.0' 46 | 47 | implementation 'com.googlecode.flickrj-android:flickrj-android:2.1.0' 48 | implementation 'org.slf4j:slf4j-android:1.7.7' // Required by Flickr library 49 | } 50 | -------------------------------------------------------------------------------- /sample/commons.gradle: -------------------------------------------------------------------------------- 1 | /** 2 | * Sets app version name and version code. 3 | * 4 | * Provided that version name is in form 'X.Y.Z', these version will be parsed and version code will 5 | * be generated from it as '(X * 10000 + Y * 100 + Z) * 10000 + build_number'. 6 | * E.g. if version name is '1.2.3' and build number is 456 then resulting code will be: '102030456'. 7 | * 8 | * Build number is a total number of commits from the branch root. 9 | * 10 | * Should be called instead of 'versionName' / 'versionCode' within defaultConfig closure: 11 | * setupVersion 'X.Y.Z' 12 | */ 13 | ext.setupVersion = { version -> 14 | def final buildNumber = getBuildNumber() 15 | def final baseCode = getBaseVersionCode(version) 16 | 17 | println "VERSION: BASE NAME = ${version}\n" + 18 | "VERSION: BASE CODE = ${baseCode}\n" + 19 | "VERSION: BUILD = ${buildNumber}" 20 | 21 | android.defaultConfig.versionCode (baseCode + buildNumber) 22 | android.defaultConfig.versionName version 23 | } 24 | 25 | ext.getBaseVersionCode = { version -> 26 | // Version code has next format: XYYZZBBBB, 27 | // where X is a major version, Y is minor, Z is patch and B is build number (optional). 28 | // Since version code is an integer we are limited with 21.47.48.3647 (max int). 29 | def (major, minor, patch) = version.tokenize('.') 30 | if (major.toInteger() > 20) { 31 | throw new GradleException("Major part of version name cannot be larger than 20") 32 | } else if (minor.toInteger() > 99) { 33 | throw new GradleException("Minor part of version name cannot be larger than 99") 34 | } else if (patch.toInteger() > 99) { 35 | throw new GradleException("Patch part of version name cannot be larger than 99") 36 | } 37 | return (major.toInteger() * 10000 + minor.toInteger() * 100 + patch.toInteger()) * 10000 38 | } 39 | 40 | ext.getBuildNumber = { 41 | def build 42 | 43 | try { 44 | def final buildStr = 'git rev-list --count HEAD'.execute().text.trim() 45 | build = buildStr.toInteger() 46 | } catch (Exception ignored) { 47 | System.err.println 'Build number is not available' 48 | build = 1 49 | } 50 | 51 | if (build >= 10000) { 52 | throw new GradleException( 53 | "Build number ($build) exceeded 10000, version code should be adjusted to fit it") 54 | } 55 | 56 | return build 57 | } 58 | 59 | 60 | /** 61 | * Sets better apk file name using format: 'appName'-'buildType'-'flavor'-'versionName'.apk 62 | * 63 | * Should be called after call to `setupVersion`, e.g.: 64 | * setOutputFileName 'app-name' 65 | */ 66 | ext.setOutputFileName = { appName -> 67 | android.applicationVariants.whenObjectAdded { variant -> 68 | variant.outputs.each { output -> 69 | output.outputFileName = "${appName}-${output.baseName}-${variant.versionName}.apk" 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /sample/release.gradle: -------------------------------------------------------------------------------- 1 | android { 2 | signingConfigs { 3 | debug { 4 | storeFile file('signing/debug.jks') 5 | } 6 | upload { 7 | storeFile file('signing/upload.jks') 8 | storePassword System.env.UPLOAD_KEYSTORE_PWD ?: '' 9 | keyAlias 'upload' 10 | keyPassword System.env.UPLOAD_KEYSTORE_PWD ?: '' 11 | } 12 | } 13 | 14 | buildTypes { 15 | debug { 16 | signingConfig signingConfigs.debug 17 | } 18 | release { 19 | signingConfig signingConfigs.upload 20 | } 21 | } 22 | } 23 | 24 | play { 25 | serviceAccountCredentials.set(file('signing/play_account.json')) 26 | track.set('alpha') 27 | } 28 | 29 | project.afterEvaluate { 30 | // Setting up 'publishSample' task 31 | // Note, that in order to work this task requires ENCRYPT_KEY and UPLOAD_KEYSTORE_PWD env vars 32 | 33 | final publish = 'publishSample' 34 | 35 | project.task publish 36 | project.tasks[publish].dependsOn 'decryptUploadKeystore' 37 | project.tasks[publish].dependsOn 'decryptPlayAccount' 38 | project.tasks[publish].dependsOn 'publishReleaseBundle' 39 | project.tasks[publish].finalizedBy 'cleanUploadKeystore' 40 | project.tasks[publish].finalizedBy 'cleanPlayAccount' 41 | } 42 | 43 | task decryptUploadKeystore { 44 | doFirst { 45 | println 'Decrypt upload keystore' 46 | decode('upload.jks') 47 | } 48 | } 49 | 50 | task cleanUploadKeystore { 51 | doLast { 52 | println 'Clean up upload keystore' 53 | cleanup('upload.jks') 54 | } 55 | } 56 | 57 | task decryptPlayAccount { 58 | doFirst { 59 | println 'Decrypt Play account' 60 | decode('play_account.json') 61 | } 62 | } 63 | 64 | task cleanPlayAccount { 65 | doLast { 66 | println 'Clean up Play account' 67 | cleanup('play_account.json') 68 | } 69 | } 70 | 71 | private static void decode(String file) { 72 | def command = "openssl enc -aes-256-cbc -d -md sha512 -pbkdf2 -iter 100000 -salt" + 73 | " -in sample/signing/${file}.enc -out sample/signing/$file -k $System.env.ENCRYPT_KEY" 74 | def process = command.execute() 75 | def error = process.err.text 76 | if (error != null && !error.isEmpty()) { 77 | println "Error:\n" + error 78 | throw new RuntimeException("Error deconding $file",) 79 | } 80 | } 81 | 82 | private static void cleanup(String file) { 83 | "rm sample/signing/$file".execute() 84 | } 85 | -------------------------------------------------------------------------------- /sample/signing/.gitignore: -------------------------------------------------------------------------------- 1 | upload.jks 2 | play_account.json -------------------------------------------------------------------------------- /sample/signing/debug.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/signing/debug.jks -------------------------------------------------------------------------------- /sample/signing/play_account.json.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/signing/play_account.json.enc -------------------------------------------------------------------------------- /sample/signing/upload.jks.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/signing/upload.jks.enc -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 14 | 15 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 33 | 34 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /sample/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/SampleApplication.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample; 2 | 3 | import android.app.Application; 4 | import android.os.Build; 5 | 6 | import androidx.appcompat.app.AppCompatDelegate; 7 | 8 | import com.alexvasilkov.events.Events; 9 | import com.alexvasilkov.gestures.internal.GestureDebug; 10 | import com.alexvasilkov.gestures.sample.demo.utils.FlickrApi; 11 | 12 | public class SampleApplication extends Application { 13 | 14 | @Override 15 | public void onCreate() { 16 | super.onCreate(); 17 | 18 | Events.register(FlickrApi.class); 19 | 20 | GestureDebug.setDebugFps(BuildConfig.DEBUG); 21 | GestureDebug.setDebugAnimator(BuildConfig.DEBUG); 22 | 23 | if (Build.VERSION.SDK_INT <= 28) { 24 | // It looks like day night theme does not work well in old Android versions 25 | AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_NO); 26 | } 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/base/BaseSettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.base; 2 | 3 | import android.os.Bundle; 4 | import android.view.Menu; 5 | import android.view.MenuItem; 6 | 7 | import androidx.annotation.NonNull; 8 | 9 | import com.alexvasilkov.gestures.Settings; 10 | import com.alexvasilkov.gestures.sample.base.settings.SettingsController; 11 | import com.alexvasilkov.gestures.sample.base.settings.SettingsMenu; 12 | 13 | public abstract class BaseSettingsActivity extends BaseActivity { 14 | 15 | private final SettingsMenu settingsMenu = new SettingsMenu(); 16 | 17 | @Override 18 | protected void onCreate(Bundle savedInstanceState) { 19 | super.onCreate(savedInstanceState); 20 | settingsMenu.onRestoreInstanceState(savedInstanceState); 21 | } 22 | 23 | @Override 24 | protected void onPostCreate(Bundle savedInstanceState) { 25 | super.onPostCreate(savedInstanceState); 26 | getSupportActionBarNotNull().setDisplayHomeAsUpEnabled(true); 27 | } 28 | 29 | @Override 30 | protected void onSaveInstanceState(@NonNull Bundle outState) { 31 | settingsMenu.onSaveInstanceState(outState); 32 | super.onSaveInstanceState(outState); 33 | } 34 | 35 | @Override 36 | public boolean onCreateOptionsMenu(Menu menu) { 37 | super.onCreateOptionsMenu(menu); 38 | settingsMenu.onCreateOptionsMenu(menu); 39 | return true; 40 | } 41 | 42 | @Override 43 | public boolean onOptionsItemSelected(MenuItem item) { 44 | if (settingsMenu.onOptionsItemSelected(item)) { 45 | supportInvalidateOptionsMenu(); 46 | onSettingsChanged(); 47 | return true; 48 | } else { 49 | return super.onOptionsItemSelected(item); 50 | } 51 | } 52 | 53 | protected SettingsController getSettingsController() { 54 | return settingsMenu; 55 | } 56 | 57 | protected abstract void onSettingsChanged(); 58 | 59 | protected void setDefaultSettings(Settings settings) { 60 | settingsMenu.setValuesFrom(settings); 61 | invalidateOptionsMenu(); 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/base/StartActivity.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.base; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.widget.TextView; 6 | 7 | import com.alexvasilkov.gestures.sample.BuildConfig; 8 | import com.alexvasilkov.gestures.sample.R; 9 | import com.alexvasilkov.gestures.sample.demo.DemoActivity; 10 | import com.alexvasilkov.gestures.sample.ex.ExamplesActivity; 11 | 12 | public class StartActivity extends BaseActivity { 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | setContentView(R.layout.start_screen); 18 | 19 | findViewById(R.id.start_demo) 20 | .setOnClickListener(v -> startActivity(new Intent(this, DemoActivity.class))); 21 | findViewById(R.id.start_examples) 22 | .setOnClickListener(v -> startActivity(new Intent(this, ExamplesActivity.class))); 23 | 24 | final String version = "v" + BuildConfig.VERSION_NAME; 25 | final TextView versionView = findViewById(R.id.start_version); 26 | versionView.setText(version); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/base/settings/SettingsController.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.base.settings; 2 | 3 | import com.alexvasilkov.gestures.views.interfaces.GestureView; 4 | 5 | public interface SettingsController { 6 | void apply(GestureView view); 7 | } 8 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/demo/utils/AspectImageView.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.demo.utils; 2 | 3 | import android.content.Context; 4 | import android.content.res.TypedArray; 5 | import android.util.AttributeSet; 6 | import android.util.Log; 7 | 8 | import androidx.appcompat.widget.AppCompatImageView; 9 | 10 | import com.alexvasilkov.gestures.sample.R; 11 | 12 | public class AspectImageView extends AppCompatImageView { 13 | 14 | public static final float DEFAULT_ASPECT = 16f / 9f; 15 | 16 | private static final int VERTICAL = 0; 17 | private static final int HORIZONTAL = 0; 18 | 19 | private float aspect = DEFAULT_ASPECT; 20 | 21 | public AspectImageView(Context context) { 22 | super(context); 23 | } 24 | 25 | public AspectImageView(Context context, AttributeSet attrs) { 26 | super(context, attrs); 27 | 28 | TypedArray arr = context.obtainStyledAttributes(attrs, new int[] { R.attr.aspect }); 29 | aspect = arr.getFloat(0, aspect); 30 | arr.recycle(); 31 | } 32 | 33 | @Override 34 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 35 | int width = MeasureSpec.getSize(widthMeasureSpec); 36 | int widthMode = MeasureSpec.getMode(widthMeasureSpec); 37 | int height = MeasureSpec.getSize(heightMeasureSpec); 38 | int heightMode = MeasureSpec.getMode(heightMeasureSpec); 39 | 40 | if (widthMode == MeasureSpec.EXACTLY || widthMode == MeasureSpec.AT_MOST) { 41 | height = calculate(width, aspect, VERTICAL); 42 | } else if (heightMode == MeasureSpec.EXACTLY || heightMode == MeasureSpec.AT_MOST) { 43 | width = calculate(height, aspect, HORIZONTAL); 44 | } else if (width != 0) { 45 | height = calculate(width, aspect, VERTICAL); 46 | } else if (height != 0) { 47 | width = calculate(height, aspect, HORIZONTAL); 48 | } else { 49 | Log.e(AspectImageView.class.getSimpleName(), 50 | "Either width or height should have exact value"); 51 | } 52 | 53 | int specWidth = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); 54 | int specHeight = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); 55 | 56 | super.onMeasure(specWidth, specHeight); 57 | } 58 | 59 | private int calculate(int size, float aspect, int direction) { 60 | int wp = getPaddingLeft() + getPaddingRight(); 61 | int hp = getPaddingTop() + getPaddingBottom(); 62 | return direction == VERTICAL 63 | ? Math.round((size - wp) / aspect) + hp 64 | : Math.round((size - hp) * aspect) + wp; 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/demo/utils/FlickrApi.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.demo.utils; 2 | 3 | import com.alexvasilkov.events.EventResult; 4 | import com.alexvasilkov.events.Events.Background; 5 | import com.alexvasilkov.events.Events.Subscribe; 6 | import com.googlecode.flickrjandroid.Flickr; 7 | import com.googlecode.flickrjandroid.photos.Photo; 8 | import com.googlecode.flickrjandroid.photos.PhotoList; 9 | import com.googlecode.flickrjandroid.photos.PhotosInterface; 10 | import com.googlecode.flickrjandroid.photos.SearchParameters; 11 | 12 | import java.util.ArrayList; 13 | import java.util.HashSet; 14 | import java.util.List; 15 | import java.util.Set; 16 | 17 | public class FlickrApi { 18 | 19 | public static final String LOAD_IMAGES_EVENT = "LOAD_IMAGES_EVENT"; 20 | 21 | private static final String API_KEY = "7f6035774a01a39f9056d6d7bde60002"; 22 | private static final String SEARCH_QUERY = "landscape"; 23 | private static final int PER_PAGE = 30; 24 | private static final int MAX_PAGES = 5; 25 | 26 | private static final Set photoParams = new HashSet<>(); 27 | 28 | static { 29 | photoParams.add("url_t"); 30 | photoParams.add("url_m"); 31 | photoParams.add("url_h"); 32 | photoParams.add("owner_name"); 33 | } 34 | 35 | private static final List photos = new ArrayList<>(); 36 | private static final List pages = new ArrayList<>(); 37 | 38 | private FlickrApi() {} 39 | 40 | @Background(singleThread = true) 41 | @Subscribe(LOAD_IMAGES_EVENT) 42 | private static synchronized EventResult loadImages(int count) throws Exception { 43 | SearchParameters params = new SearchParameters(); 44 | params.setText(SEARCH_QUERY); 45 | params.setSafeSearch(Flickr.SAFETYLEVEL_SAFE); 46 | params.setSort(SearchParameters.INTERESTINGNESS_DESC); 47 | params.setExtras(photoParams); 48 | 49 | boolean hasNext = hasNext(); 50 | 51 | final PhotosInterface flickrPhotos = new Flickr(API_KEY).getPhotosInterface(); 52 | while (photos.size() < count && hasNext) { 53 | final PhotoList loaded = flickrPhotos.search(params, PER_PAGE, pages.size() + 1); 54 | pages.add(loaded); 55 | photos.addAll(loaded); 56 | 57 | hasNext = hasNext(); 58 | } 59 | 60 | int resultSize = Math.min(photos.size(), count); 61 | 62 | List result = new ArrayList<>(photos.subList(0, resultSize)); 63 | if (!hasNext) { 64 | hasNext = photos.size() > count; 65 | } 66 | 67 | return EventResult.create().result(result, hasNext).build(); 68 | } 69 | 70 | private static boolean hasNext() { 71 | if (pages.isEmpty()) { 72 | return true; 73 | } else if (pages.size() >= MAX_PAGES) { 74 | return false; 75 | } else { 76 | PhotoList page = pages.get(pages.size() - 1); 77 | return page.getPage() * page.getPerPage() < page.getTotal(); 78 | } 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/demo/utils/RecyclerAdapterHelper.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.demo.utils; 2 | 3 | import androidx.recyclerview.widget.DiffUtil; 4 | import androidx.recyclerview.widget.RecyclerView; 5 | 6 | import java.util.List; 7 | 8 | public class RecyclerAdapterHelper { 9 | 10 | private RecyclerAdapterHelper() {} 11 | 12 | /** 13 | * Calls adapter's notify* methods when items are added / removed / moved / updated. 14 | */ 15 | public static void notifyChanges(RecyclerView.Adapter adapter, 16 | final List oldList, final List newList) { 17 | 18 | DiffUtil.calculateDiff(new DiffUtil.Callback() { 19 | @Override 20 | public int getOldListSize() { 21 | return oldList == null ? 0 : oldList.size(); 22 | } 23 | 24 | @Override 25 | public int getNewListSize() { 26 | return newList == null ? 0 : newList.size(); 27 | } 28 | 29 | @Override 30 | public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { 31 | return oldList.get(oldItemPosition).equals(newList.get(newItemPosition)); 32 | } 33 | 34 | @Override 35 | public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { 36 | return areItemsTheSame(oldItemPosition, newItemPosition); 37 | } 38 | }).dispatchUpdatesTo(adapter); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/animations/ImageAnimationActivity.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.animations; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.widget.ImageView; 6 | 7 | import com.alexvasilkov.gestures.sample.R; 8 | import com.alexvasilkov.gestures.sample.base.BaseSettingsActivity; 9 | import com.alexvasilkov.gestures.sample.ex.utils.GlideHelper; 10 | import com.alexvasilkov.gestures.sample.ex.utils.Painting; 11 | import com.alexvasilkov.gestures.transition.GestureTransitions; 12 | import com.alexvasilkov.gestures.transition.ViewsTransitionAnimator; 13 | import com.alexvasilkov.gestures.views.GestureImageView; 14 | 15 | /** 16 | * This example demonstrates image animation from small mode into a full one. 17 | */ 18 | public class ImageAnimationActivity extends BaseSettingsActivity { 19 | 20 | private static final int PAINTING_ID = 2; 21 | 22 | private ImageView image; 23 | private GestureImageView fullImage; 24 | private View fullBackground; 25 | private ViewsTransitionAnimator animator; 26 | 27 | private Painting painting; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | 33 | initContentView(); 34 | 35 | image = findViewById(R.id.single_image); 36 | fullImage = findViewById(R.id.single_image_full); 37 | fullBackground = findViewById(R.id.single_image_back); 38 | 39 | // Loading image 40 | painting = Painting.list(getResources())[PAINTING_ID]; 41 | GlideHelper.loadThumb(image, painting.thumbId); 42 | 43 | // We will expand image on click 44 | image.setOnClickListener(view -> openFullImage()); 45 | 46 | // Initializing image animator 47 | animator = GestureTransitions.from(image).into(fullImage); 48 | animator.addPositionUpdateListener(this::applyImageAnimationState); 49 | } 50 | 51 | /** 52 | * Override this method if you want to provide slightly different layout. 53 | */ 54 | protected void initContentView() { 55 | setContentView(R.layout.image_animation_screen); 56 | setTitle(R.string.example_image_animation); 57 | } 58 | 59 | @Override 60 | public void onBackPressed() { 61 | // We should leave full image mode instead of closing the screen 62 | if (!animator.isLeaving()) { 63 | animator.exit(true); 64 | } else { 65 | super.onBackPressed(); 66 | } 67 | } 68 | 69 | @Override 70 | protected void onSettingsChanged() { 71 | // Applying settings from toolbar menu, see BaseExampleActivity 72 | getSettingsController().apply(fullImage); 73 | // Resetting to initial image state 74 | fullImage.getController().resetState(); 75 | } 76 | 77 | private void openFullImage() { 78 | // Setting image drawable from 'from' view to 'to' to prevent flickering 79 | if (fullImage.getDrawable() == null) { 80 | fullImage.setImageDrawable(image.getDrawable()); 81 | } 82 | 83 | // Updating gesture image settings 84 | getSettingsController().apply(fullImage); 85 | // Resetting to initial image state 86 | fullImage.getController().resetState(); 87 | 88 | animator.enterSingle(true); 89 | GlideHelper.loadFull(fullImage, painting.imageId, painting.thumbId); 90 | } 91 | 92 | private void applyImageAnimationState(float position, boolean isLeaving) { 93 | fullBackground.setAlpha(position); 94 | fullBackground.setVisibility(position == 0f && isLeaving ? View.INVISIBLE : View.VISIBLE); 95 | fullImage.setVisibility(position == 0f && isLeaving ? View.INVISIBLE : View.VISIBLE); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/animations/RoundImageAnimationActivity.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.animations; 2 | 3 | import com.alexvasilkov.gestures.commons.circle.CircleGestureImageView; 4 | import com.alexvasilkov.gestures.commons.circle.CircleImageView; 5 | import com.alexvasilkov.gestures.sample.R; 6 | 7 | /** 8 | * Same as {@link ImageAnimationActivity} example but shows how to animate rounded image 9 | * using {@link CircleImageView} and {@link CircleGestureImageView}. 10 | */ 11 | public class RoundImageAnimationActivity extends ImageAnimationActivity { 12 | 13 | @Override 14 | protected void initContentView() { 15 | setContentView(R.layout.image_animation_round_screen); 16 | setTitle(R.string.example_image_animation_circular); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/animations/cross/CrossEvents.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.animations.cross; 2 | 3 | class CrossEvents { 4 | 5 | static final String SHOW_IMAGE = "show_image"; 6 | static final String POSITION_CHANGED = "position_changed"; 7 | 8 | private CrossEvents() {} 9 | 10 | } 11 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/animations/cross/ImageCrossAnimationActivity.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.animations.cross; 2 | 3 | import android.os.Bundle; 4 | import android.view.View; 5 | import android.widget.ImageView; 6 | 7 | import com.alexvasilkov.events.Events; 8 | import com.alexvasilkov.gestures.animation.ViewPosition; 9 | import com.alexvasilkov.gestures.sample.R; 10 | import com.alexvasilkov.gestures.sample.base.BaseActivity; 11 | import com.alexvasilkov.gestures.sample.ex.utils.GlideHelper; 12 | import com.alexvasilkov.gestures.sample.ex.utils.Painting; 13 | 14 | /** 15 | * This example demonstrates image animation that crosses activities bounds.
16 | * Cross-activities animation is pretty complicated, since we'll need to have a connection between 17 | * activities in order to properly coordinate image position changes and animation state.
18 | * In this example we will use {@link Events} library to set up such connection, but you can also 19 | * do it using e.g. LocalBroadcastManager or manually by setting and removing listeners. 20 | */ 21 | public class ImageCrossAnimationActivity extends BaseActivity { 22 | 23 | private static final int PAINTING_ID = 2; 24 | 25 | private ImageView image; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | super.onCreate(savedInstanceState); 30 | 31 | setContentView(R.layout.image_cross_animation_from_screen); 32 | setTitle(R.string.example_image_animation_cross); 33 | getSupportActionBarNotNull().setDisplayHomeAsUpEnabled(true); 34 | 35 | image = findViewById(R.id.single_image_from); 36 | 37 | // Loading image 38 | Painting painting = Painting.list(getResources())[PAINTING_ID]; 39 | GlideHelper.loadThumb(image, painting.thumbId); 40 | 41 | // Setting image click listener 42 | image.setOnClickListener(this::showFullImage); 43 | 44 | // Image position may change (e.g. when screen orientation is changed), so we should update 45 | // fullscreen image to ensure exit animation will return image into correct position. 46 | image.getViewTreeObserver().addOnGlobalLayoutListener(this::onLayoutChanges); 47 | } 48 | 49 | private void showFullImage(View image) { 50 | // Requesting opening image in a new activity with animation. 51 | // First of all we need to get current image position: 52 | ViewPosition position = ViewPosition.from(image); 53 | // Now pass this position to a new activity. New activity should start without any 54 | // animations and should have transparent window (set through activity theme). 55 | FullImageActivity.open(this, position, PAINTING_ID); 56 | } 57 | 58 | private void onLayoutChanges() { 59 | // Notifying fullscreen image activity about image position changes. 60 | ViewPosition position = ViewPosition.from(image); 61 | Events.create(CrossEvents.POSITION_CHANGED).param(position).post(); 62 | } 63 | 64 | @Events.Subscribe(CrossEvents.SHOW_IMAGE) 65 | private void showImage(boolean show) { 66 | // Fullscreen activity requested to show or hide original image 67 | image.setVisibility(show ? View.VISIBLE : View.INVISIBLE); 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/image/pager/ViewPagerActivity.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.image.pager; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.viewpager.widget.ViewPager; 6 | 7 | import com.alexvasilkov.gestures.commons.RecyclePagerAdapter; 8 | import com.alexvasilkov.gestures.sample.R; 9 | import com.alexvasilkov.gestures.sample.base.BaseSettingsActivity; 10 | import com.alexvasilkov.gestures.views.GestureImageView; 11 | 12 | import java.util.Objects; 13 | 14 | /** 15 | * This example demonstrates usage of {@link GestureImageView} within ViewPager.
16 | * Two things worth noting here: 17 | *

23 | */ 24 | public class ViewPagerActivity extends BaseSettingsActivity { 25 | 26 | private ViewPager viewPager; 27 | 28 | @Override 29 | protected void onCreate(Bundle savedInstanceState) { 30 | super.onCreate(savedInstanceState); 31 | 32 | setContentView(R.layout.image_pager_screen); 33 | setTitle(R.string.example_images_in_pager); 34 | setInfoText(R.string.info_image_pager); 35 | 36 | // Initializing ViewPager 37 | viewPager = findViewById(R.id.pager); 38 | viewPager.setAdapter(new ViewPagerAdapter(viewPager, getSettingsController())); 39 | viewPager.setPageMargin(getResources().getDimensionPixelSize(R.dimen.view_pager_margin)); 40 | } 41 | 42 | @Override 43 | protected void onSettingsChanged() { 44 | // Applying settings from toolbar menu, see BaseExampleActivity 45 | Objects.requireNonNull(viewPager.getAdapter()).notifyDataSetChanged(); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/image/pager/ViewPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.image.pager; 2 | 3 | import android.view.ViewGroup; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.viewpager.widget.ViewPager; 7 | 8 | import com.alexvasilkov.gestures.commons.RecyclePagerAdapter; 9 | import com.alexvasilkov.gestures.sample.base.settings.SettingsController; 10 | import com.alexvasilkov.gestures.sample.ex.utils.GlideHelper; 11 | import com.alexvasilkov.gestures.sample.ex.utils.Painting; 12 | import com.alexvasilkov.gestures.views.GestureImageView; 13 | 14 | class ViewPagerAdapter extends RecyclePagerAdapter { 15 | 16 | private final ViewPager viewPager; 17 | private final Painting[] paintings; 18 | private final SettingsController settingsController; 19 | 20 | ViewPagerAdapter(ViewPager pager, SettingsController listener) { 21 | this.viewPager = pager; 22 | this.paintings = Painting.list(pager.getResources()); 23 | this.settingsController = listener; 24 | } 25 | 26 | @Override 27 | public int getCount() { 28 | return paintings.length; 29 | } 30 | 31 | @Override 32 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup container) { 33 | final ViewHolder holder = new ViewHolder(container); 34 | 35 | // Applying custom settings 36 | holder.image.getController().getSettings() 37 | .setMaxZoom(6f) 38 | .setDoubleTapZoom(3f); 39 | 40 | // Enabling smooth scrolling when image panning turns into ViewPager scrolling. 41 | // Otherwise ViewPager scrolling will only be possible when image is in zoomed out state. 42 | holder.image.getController().enableScrollInViewPager(viewPager); 43 | 44 | return holder; 45 | } 46 | 47 | @Override 48 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 49 | // Applying settings from toolbar menu, see BaseExampleActivity 50 | settingsController.apply(holder.image); 51 | 52 | Painting painting = paintings[position]; 53 | GlideHelper.loadFull(holder.image, painting.imageId, painting.thumbId); 54 | } 55 | 56 | @Override 57 | public void onRecycleViewHolder(@NonNull ViewHolder holder) { 58 | GlideHelper.clear(holder.image); 59 | } 60 | 61 | static class ViewHolder extends RecyclePagerAdapter.ViewHolder { 62 | final GestureImageView image; 63 | 64 | ViewHolder(ViewGroup container) { 65 | super(new GestureImageView(container.getContext())); 66 | image = (GestureImageView) itemView; 67 | } 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/image/viewer/ImageViewerActivity.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.image.viewer; 2 | 3 | import android.os.Bundle; 4 | import android.widget.Toast; 5 | 6 | import com.alexvasilkov.gestures.sample.R; 7 | import com.alexvasilkov.gestures.sample.base.BaseSettingsActivity; 8 | import com.alexvasilkov.gestures.sample.ex.utils.GlideHelper; 9 | import com.alexvasilkov.gestures.sample.ex.utils.Painting; 10 | import com.alexvasilkov.gestures.views.GestureImageView; 11 | 12 | /** 13 | * This example demonstrates basic usage of {@link GestureImageView}. 14 | */ 15 | public class ImageViewerActivity extends BaseSettingsActivity { 16 | 17 | private static final int PAINTING_ID = 1; 18 | 19 | private GestureImageView imageViewer; 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | 25 | setContentView(R.layout.image_viewer_screen); 26 | setTitle(R.string.example_image_viewer); 27 | setInfoText(R.string.info_image_viewer); 28 | 29 | // Initializing image viewer 30 | imageViewer = findViewById(R.id.image_viewer); 31 | 32 | // Applying custom settings (note, that all settings can be also set in XML) 33 | imageViewer.getController().getSettings() 34 | .setMaxZoom(6f) 35 | .setDoubleTapZoom(3f); 36 | 37 | imageViewer.setOnClickListener(view -> showToast("Single click")); 38 | 39 | imageViewer.setOnLongClickListener(view -> { 40 | showToast("Long click"); 41 | return true; 42 | }); 43 | 44 | final Painting painting = Painting.list(getResources())[PAINTING_ID]; 45 | GlideHelper.loadFull(imageViewer, painting.imageId, painting.thumbId); 46 | } 47 | 48 | private void showToast(String text) { 49 | Toast.makeText(this, text, Toast.LENGTH_SHORT).show(); 50 | } 51 | 52 | @Override 53 | protected void onSettingsChanged() { 54 | // Applying settings from toolbar menu, see BaseExampleActivity 55 | getSettingsController().apply(imageViewer); 56 | 57 | // Resetting to initial image state 58 | imageViewer.getController().resetState(); 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/layout/pager/LayoutsInPagerActivity.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.layout.pager; 2 | 3 | import android.os.Bundle; 4 | 5 | import androidx.viewpager.widget.ViewPager; 6 | 7 | import com.alexvasilkov.gestures.commons.RecyclePagerAdapter; 8 | import com.alexvasilkov.gestures.sample.R; 9 | import com.alexvasilkov.gestures.sample.base.BaseSettingsActivity; 10 | import com.alexvasilkov.gestures.sample.ex.utils.Painting; 11 | import com.alexvasilkov.gestures.views.GestureFrameLayout; 12 | 13 | import java.util.Objects; 14 | 15 | /** 16 | * Simple example demonstrates usage of {@link GestureFrameLayout} within ViewPager.
17 | * Two things worth noting here: 18 | *
    19 | *
  1. For each GestureFrameLayout inside ViewPager we need to enable smooth scrolling by calling 20 | * {@code gestureLayout.getController().enableScrollInViewPager(viewPager)} 21 | *
  2. It is advised to use {@link RecyclePagerAdapter} as ViewPager adapter for better 22 | * performance. 23 | *
24 | */ 25 | public class LayoutsInPagerActivity extends BaseSettingsActivity { 26 | 27 | private ViewPager viewPager; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | 33 | setContentView(R.layout.layout_pager_screen); 34 | setTitle(R.string.example_layouts_in_pager); 35 | 36 | final Painting[] paintings = Painting.list(getResources()); 37 | 38 | viewPager = findViewById(R.id.frame_pager); 39 | viewPager.setAdapter( 40 | new LayoutsPagerAdapter(viewPager, paintings, getSettingsController())); 41 | viewPager.setPageMargin(getResources().getDimensionPixelSize(R.dimen.view_pager_margin)); 42 | } 43 | 44 | @Override 45 | protected void onSettingsChanged() { 46 | // Applying settings from toolbar menu, see BaseExampleActivity 47 | Objects.requireNonNull(viewPager.getAdapter()).notifyDataSetChanged(); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/layout/pager/LayoutsPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.layout.pager; 2 | 3 | import android.graphics.Typeface; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.ImageView; 7 | import android.widget.TextView; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.viewpager.widget.ViewPager; 11 | 12 | import com.alexvasilkov.android.commons.nav.Navigate; 13 | import com.alexvasilkov.android.commons.texts.SpannableBuilder; 14 | import com.alexvasilkov.android.commons.ui.Views; 15 | import com.alexvasilkov.gestures.commons.RecyclePagerAdapter; 16 | import com.alexvasilkov.gestures.sample.R; 17 | import com.alexvasilkov.gestures.sample.base.settings.SettingsController; 18 | import com.alexvasilkov.gestures.sample.ex.utils.GlideHelper; 19 | import com.alexvasilkov.gestures.sample.ex.utils.Painting; 20 | import com.alexvasilkov.gestures.views.GestureFrameLayout; 21 | 22 | class LayoutsPagerAdapter extends RecyclePagerAdapter { 23 | 24 | private final ViewPager viewPager; 25 | private final Painting[] paintings; 26 | private final SettingsController settingsController; 27 | 28 | LayoutsPagerAdapter(ViewPager pager, Painting[] paintings, SettingsController listener) { 29 | this.viewPager = pager; 30 | this.paintings = paintings; 31 | this.settingsController = listener; 32 | } 33 | 34 | @Override 35 | public int getCount() { 36 | return paintings.length; 37 | } 38 | 39 | @Override 40 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup container) { 41 | final ViewHolder holder = new ViewHolder(container); 42 | holder.layout.getController().enableScrollInViewPager(viewPager); 43 | holder.button.setOnClickListener(this::onButtonClick); 44 | return holder; 45 | } 46 | 47 | @Override 48 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 49 | settingsController.apply(holder.layout); 50 | 51 | Painting painting = paintings[position]; 52 | GlideHelper.loadFull(holder.image, painting.imageId, painting.thumbId); 53 | 54 | final CharSequence titleText = new SpannableBuilder(holder.itemView.getContext()) 55 | .createStyle().setFont(Typeface.DEFAULT_BOLD).apply() 56 | .append(paintings[position].author).append("\n") 57 | .clearStyle() 58 | .append(paintings[position].title) 59 | .build(); 60 | holder.title.setText(titleText); 61 | 62 | holder.button.setTag(paintings[position].link); 63 | } 64 | 65 | @Override 66 | public void onRecycleViewHolder(@NonNull ViewHolder holder) { 67 | // Resetting to initial image state 68 | holder.layout.getController().resetState(); 69 | GlideHelper.clear(holder.image); 70 | } 71 | 72 | private void onButtonClick(@NonNull View button) { 73 | final String url = (String) button.getTag(); 74 | Navigate.from(button.getContext()).external().browser().url(url).start(); 75 | } 76 | 77 | 78 | static class ViewHolder extends RecyclePagerAdapter.ViewHolder { 79 | final GestureFrameLayout layout; 80 | final ImageView image; 81 | final TextView title; 82 | final View button; 83 | 84 | ViewHolder(ViewGroup container) { 85 | super(Views.inflate(container, R.layout.layout_pager_item)); 86 | layout = itemView.findViewById(R.id.frame_item_layout); 87 | image = layout.findViewById(R.id.frame_item_image); 88 | title = layout.findViewById(R.id.frame_item_title); 89 | button = layout.findViewById(R.id.frame_item_button); 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/layout/viewer/LayoutViewerActivity.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.layout.viewer; 2 | 3 | import android.os.Bundle; 4 | import android.widget.ImageView; 5 | import android.widget.Toast; 6 | 7 | import com.alexvasilkov.gestures.sample.R; 8 | import com.alexvasilkov.gestures.sample.base.BaseSettingsActivity; 9 | import com.alexvasilkov.gestures.sample.ex.utils.GlideHelper; 10 | import com.alexvasilkov.gestures.sample.ex.utils.Painting; 11 | import com.alexvasilkov.gestures.views.GestureFrameLayout; 12 | 13 | /** 14 | * Simple example demonstrates usage of {@link GestureFrameLayout}. 15 | * Basically, all you need is to wrap your layout with {@link GestureFrameLayout} and apply 16 | * necessary settings. 17 | */ 18 | public class LayoutViewerActivity extends BaseSettingsActivity { 19 | 20 | private static final int PAINTING_ID = 0; 21 | 22 | private GestureFrameLayout layout; 23 | 24 | @Override 25 | protected void onCreate(Bundle savedInstanceState) { 26 | super.onCreate(savedInstanceState); 27 | 28 | setContentView(R.layout.layout_viewer_screen); 29 | setTitle(R.string.example_layout_viewer); 30 | 31 | layout = findViewById(R.id.frame_layout); 32 | 33 | // Initializing custom example settings 34 | setDefaultSettings(layout.getController().getSettings()); 35 | 36 | // Loading sample image 37 | final ImageView imageView = findViewById(R.id.frame_layout_image); 38 | final Painting painting = Painting.list(getResources())[PAINTING_ID]; 39 | GlideHelper.loadFull(imageView, painting.imageId, painting.thumbId); 40 | 41 | // Handling button click 42 | findViewById(R.id.frame_layout_button).setOnClickListener(view -> 43 | Toast.makeText(this, "Button clicked", Toast.LENGTH_SHORT).show()); 44 | } 45 | 46 | @Override 47 | protected void onSettingsChanged() { 48 | // Applying settings from toolbar menu, see BaseExampleActivity 49 | getSettingsController().apply(layout); 50 | 51 | // Resetting to initial state 52 | layout.getController().resetState(); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/other/markers/Marker.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.other.markers; 2 | 3 | import android.graphics.drawable.Drawable; 4 | import android.view.Gravity; 5 | 6 | @SuppressWarnings({ "WeakerAccess", "UnusedReturnValue", "SameParameterValue" }) // Public API 7 | public class Marker { 8 | 9 | private Drawable icon; 10 | private int gravity = Gravity.TOP | Gravity.LEFT; 11 | private int locationX = 0; 12 | private int locationY = 0; 13 | private int offsetX = 0; 14 | private int offsetY = 0; 15 | private float scale = 1f; 16 | private float rotation = 0f; 17 | private Mode mode = Mode.PIN; 18 | 19 | /** 20 | * @param icon Drawable to be shown 21 | */ 22 | public Marker setIcon(Drawable icon) { 23 | this.icon = icon; 24 | return this; 25 | } 26 | 27 | public Drawable getIcon() { 28 | return icon; 29 | } 30 | 31 | /** 32 | * @param gravity Gravity of the drawable. E.g. Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 33 | * means that drawable coordinates are applied to the center of the bottom edge of the 34 | * drawable. 35 | */ 36 | public Marker setGravity(int gravity) { 37 | this.gravity = gravity; 38 | return this; 39 | } 40 | 41 | public int getGravity() { 42 | return gravity; 43 | } 44 | 45 | /** 46 | * @param x X coordinate in pixels relative to original image. 47 | * @param y Y coordinate in pixels relative to original image. 48 | */ 49 | public Marker setLocation(int x, int y) { 50 | locationX = x; 51 | locationY = y; 52 | return this; 53 | } 54 | 55 | public int getLocationX() { 56 | return locationX; 57 | } 58 | 59 | public int getLocationY() { 60 | return locationY; 61 | } 62 | 63 | /** 64 | * @param x X offset in pixels relative to marker's icon. 65 | * @param y Y offset in pixels relative to marker's icon. 66 | */ 67 | public Marker setOffset(int x, int y) { 68 | offsetX = x; 69 | offsetY = y; 70 | return this; 71 | } 72 | 73 | public int getOffsetX() { 74 | return offsetX; 75 | } 76 | 77 | public int getOffsetY() { 78 | return offsetY; 79 | } 80 | 81 | /** 82 | * Sets default scale for marker's icon. 83 | */ 84 | public Marker setScale(float scale) { 85 | this.scale = scale; 86 | return this; 87 | } 88 | 89 | public float getScale() { 90 | return scale; 91 | } 92 | 93 | /** 94 | * Sets default rotation for marker's icon. 95 | */ 96 | public Marker setRotation(float rotation) { 97 | this.rotation = rotation; 98 | return this; 99 | } 100 | 101 | public float getRotation() { 102 | return rotation; 103 | } 104 | 105 | public Marker setMode(Mode mode) { 106 | this.mode = mode; 107 | return this; 108 | } 109 | 110 | public Mode getMode() { 111 | return mode; 112 | } 113 | 114 | 115 | public enum Mode { 116 | /** 117 | * Pin is attached to an image according to specified gravity 118 | * but does not follow zoom and rotation. 119 | */ 120 | PIN, 121 | /** 122 | * Pin is attached to an image according to specified gravity and follows zoom and rotation. 123 | */ 124 | STICK 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/other/scene/Item.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.other.scene; 2 | 3 | import com.alexvasilkov.gestures.State; 4 | 5 | class Item { 6 | 7 | private final int imageId; 8 | private final State state = new State(); 9 | 10 | Item(int imageId) { 11 | this.imageId = imageId; 12 | } 13 | 14 | int getImageId() { 15 | return imageId; 16 | } 17 | 18 | State getState() { 19 | return state; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/other/scene/SceneActivity.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.other.scene; 2 | 3 | import android.os.Bundle; 4 | import android.view.ViewTreeObserver.OnGlobalLayoutListener; 5 | 6 | import com.alexvasilkov.gestures.sample.R; 7 | import com.alexvasilkov.gestures.sample.base.BaseActivity; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * This example demonstrates advanced gesture controller usage. 14 | *

15 | * Single tap selects an item on the screen. Once selected an item can be moved, scaled and rotated 16 | * within the bounds of the screen. 17 | */ 18 | public class SceneActivity extends BaseActivity { 19 | 20 | private SceneView scene; 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | 26 | scene = new SceneView(this); 27 | setContentView(scene); 28 | setTitle(R.string.example_other_objects); 29 | setInfoText(R.string.info_objects_control); 30 | getSupportActionBarNotNull().setDisplayHomeAsUpEnabled(true); 31 | 32 | // Waiting for scene to be laid out before setting up the items 33 | scene.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() { 34 | @Override 35 | public void onGlobalLayout() { 36 | scene.getViewTreeObserver().removeOnGlobalLayoutListener(this); 37 | showItems(); 38 | } 39 | }); 40 | } 41 | 42 | private void showItems() { 43 | // Setting initial positions for bowling pins and ball 44 | List items = new ArrayList<>(11); 45 | 46 | final float sceneWidth = scene.getWidth(); 47 | final float sceneHeight = scene.getHeight(); 48 | final float zoom = 2f / getResources().getDisplayMetrics().density; 49 | 50 | final float pinWidth = 175 * zoom; 51 | final float pinHeight = 480 * zoom; 52 | final float ballSize = 480 * zoom; 53 | 54 | // Setting up pins in a pyramid 55 | final float[] pinsX = new float[] { -2f, -1f, 0f, 1f, -1.5f, -0.5f, 0.5f, -1f, 0f, -0.5f }; 56 | final float[] pinsY = new float[] { -3f, -3f, -3f, -3f, -2f, -2f, -2f, -1f, -1f, 0f }; 57 | 58 | for (int i = 0; i < 10; i++) { 59 | float posX = 0.5f * sceneWidth + pinsX[i] * pinWidth; 60 | float posY = 0.5f * sceneHeight - pinHeight + 0.2f * pinsY[i] * pinHeight; 61 | 62 | Item pin = new Item(R.drawable.item_pin); 63 | pin.getState().set(posX, posY, zoom, 0); 64 | items.add(pin); 65 | } 66 | 67 | // Adding a ball 68 | float posX = 0.5f * (sceneWidth - ballSize); 69 | float posY = 0.5f * (sceneHeight + ballSize); 70 | 71 | Item ball = new Item(R.drawable.item_ball); 72 | ball.getState().set(posX, posY, zoom, 0); 73 | items.add(ball); 74 | 75 | // Updating scene items, setting min zoom setting 76 | scene.setItems(items); 77 | scene.getController().getSettings().setMinZoom(zoom); 78 | } 79 | 80 | } 81 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/other/text/CustomViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.other.text; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.alexvasilkov.gestures.sample.R; 6 | import com.alexvasilkov.gestures.sample.base.BaseActivity; 7 | 8 | /** 9 | * This example demonstrates creation of custom text view with text size controlled by gestures. 10 | * See {@link GestureTextView} class. 11 | */ 12 | public class CustomViewActivity extends BaseActivity { 13 | 14 | @Override 15 | protected void onCreate(Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | 18 | setContentView(R.layout.custom_view_screen); 19 | setTitle(R.string.example_other_custom); 20 | getSupportActionBarNotNull().setDisplayHomeAsUpEnabled(true); 21 | 22 | final GestureTextView textView = findViewById(R.id.text_view); 23 | textView.getController().getSettings().setMaxZoom(1.5f); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/other/text/GestureTextView.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.other.text; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | import android.util.TypedValue; 7 | import android.view.MotionEvent; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.appcompat.widget.AppCompatTextView; 11 | 12 | import com.alexvasilkov.gestures.GestureController; 13 | import com.alexvasilkov.gestures.State; 14 | import com.alexvasilkov.gestures.views.interfaces.GestureView; 15 | 16 | /** 17 | * Sample of TextView with added gesture controls. 18 | */ 19 | public class GestureTextView extends AppCompatTextView implements GestureView { 20 | 21 | private final GestureController controller; 22 | 23 | private float origSize; 24 | private float size; 25 | 26 | public GestureTextView(Context context) { 27 | this(context, null, 0); 28 | } 29 | 30 | public GestureTextView(Context context, AttributeSet attrs) { 31 | this(context, attrs, 0); 32 | } 33 | 34 | public GestureTextView(Context context, AttributeSet attrs, int defStyle) { 35 | super(context, attrs, defStyle); 36 | 37 | controller = new GestureController(this); 38 | controller.getSettings().setOverzoomFactor(1f).setPanEnabled(false); 39 | controller.getSettings().initFromAttributes(context, attrs); 40 | controller.addOnStateChangeListener(new GestureController.OnStateChangeListener() { 41 | @Override 42 | public void onStateChanged(State state) { 43 | applyState(state); 44 | } 45 | 46 | @Override 47 | public void onStateReset(State oldState, State newState) { 48 | applyState(newState); 49 | } 50 | }); 51 | 52 | origSize = getTextSize(); 53 | } 54 | 55 | /** 56 | * {@inheritDoc} 57 | */ 58 | @NonNull 59 | @Override 60 | public GestureController getController() { 61 | return controller; 62 | } 63 | 64 | @SuppressLint("ClickableViewAccessibility") // performClick will be called by controller 65 | @Override 66 | public boolean onTouchEvent(@NonNull MotionEvent event) { 67 | return controller.onTouch(this, event); 68 | } 69 | 70 | @Override 71 | public void setTextSize(float size) { 72 | super.setTextSize(size); 73 | origSize = getTextSize(); 74 | applyState(controller.getState()); 75 | } 76 | 77 | @Override 78 | public void setTextSize(int unit, float size) { 79 | super.setTextSize(unit, size); 80 | origSize = getTextSize(); 81 | applyState(controller.getState()); 82 | } 83 | 84 | @Override 85 | protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { 86 | super.onSizeChanged(width, height, oldWidth, oldHeight); 87 | controller.getSettings().setViewport(width, height).setImage(width, height); 88 | controller.updateState(); 89 | } 90 | 91 | protected void applyState(State state) { 92 | float size = origSize * state.getZoom(); 93 | float maxSize = origSize * controller.getStateController().getMaxZoom(state); 94 | size = Math.max(origSize, Math.min(size, maxSize)); 95 | 96 | // Bigger text size steps for smoother scaling 97 | size = Math.round(size); 98 | 99 | if (!State.equals(this.size, size)) { 100 | this.size = size; 101 | super.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); 102 | } 103 | } 104 | 105 | } 106 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/transitions/complex/ListItem.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.transitions.complex; 2 | 3 | import android.content.Context; 4 | 5 | import com.alexvasilkov.gestures.sample.ex.utils.Painting; 6 | 7 | import java.util.ArrayList; 8 | import java.util.Arrays; 9 | import java.util.List; 10 | 11 | class ListItem { 12 | 13 | final String text; 14 | final List paintings; 15 | 16 | private ListItem(String text) { 17 | this.text = text; 18 | this.paintings = null; 19 | } 20 | 21 | private ListItem(Painting... paintings) { 22 | this.text = null; 23 | this.paintings = Arrays.asList(paintings); 24 | } 25 | 26 | 27 | /* ****************** 28 | * Mock data 29 | * ******************/ 30 | 31 | static List createItemsV1(Context context) { 32 | final List items = new ArrayList<>(); 33 | final Painting[] paintings = Painting.list(context.getResources()); 34 | 35 | items.add(new ListItem("Donec sem arcu, feugiat sit amet purus ut, cursus tristique" 36 | + " justo. Quisque eu ligula sed massa tristique elementum eu vel augue.")); 37 | items.add(new ListItem(paintings[0], paintings[1])); 38 | items.add(new ListItem(paintings[2])); 39 | items.add(new ListItem("Vivamus orci nulla, euismod ac purus id, porttitor vulputate" 40 | + " purus. Proin at aliquam justo. Integer eget eros vitae metus ornare lacinia eu" 41 | + " sit amet justo. Integer aliquam sit amet diam ac laoreet. Cras tincidunt dolor" 42 | + " ut nisl aliquet.")); 43 | items.add(new ListItem("Nulla sed eleifend quam")); 44 | items.add(new ListItem(paintings[3], paintings[4])); 45 | items.add(new ListItem("Suspendisse dignissim pretium nisi nec tincidunt. In ut" 46 | + " finibus arcu. Nunc pretium purus a eros convallis finibus. Vestibulum eleifend" 47 | + " efficitur arcu, ut fringilla lorem molestie nec.")); 48 | items.add(new ListItem(paintings[0])); 49 | items.add(new ListItem(paintings[1], paintings[2])); 50 | return items; 51 | } 52 | 53 | static List createItemsV2(Context context) { 54 | final List items = new ArrayList<>(); 55 | final Painting[] paintings = Painting.list(context.getResources()); 56 | 57 | items.add(new ListItem("Donec sem arcu, feugiat sit amet purus ut, cursus tristique" 58 | + " justo. Quisque eu ligula sed massa tristique elementum eu vel augue.")); 59 | items.add(new ListItem(paintings[0], paintings[1], paintings[2], paintings[3])); 60 | items.add(new ListItem("Vivamus orci nulla, euismod ac purus id, porttitor vulputate" 61 | + " purus. Proin at aliquam justo. Integer eget eros vitae metus ornare lacinia eu" 62 | + " sit amet justo. Integer aliquam sit amet diam ac laoreet. Cras tincidunt dolor" 63 | + " ut nisl aliquet.")); 64 | items.add(new ListItem("Nulla sed eleifend quam")); 65 | items.add(new ListItem(paintings[0], paintings[1], paintings[2])); 66 | items.add(new ListItem("Suspendisse dignissim pretium nisi nec tincidunt. In ut" 67 | + " finibus arcu. Nunc pretium purus a eros convallis finibus. Vestibulum eleifend" 68 | + " efficitur arcu, ut fringilla lorem molestie nec.")); 69 | items.add(new ListItem(paintings[0])); 70 | items.add(new ListItem(paintings[0], paintings[1])); 71 | return items; 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/transitions/complex/PagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.transitions.complex; 2 | 3 | import android.view.ViewGroup; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.viewpager.widget.ViewPager; 7 | 8 | import com.alexvasilkov.gestures.commons.RecyclePagerAdapter; 9 | import com.alexvasilkov.gestures.sample.base.settings.SettingsController; 10 | import com.alexvasilkov.gestures.sample.ex.utils.GlideHelper; 11 | import com.alexvasilkov.gestures.sample.ex.utils.Painting; 12 | import com.alexvasilkov.gestures.views.GestureImageView; 13 | 14 | import java.util.List; 15 | 16 | class PagerAdapter extends RecyclePagerAdapter { 17 | 18 | private final ViewPager viewPager; 19 | private final SettingsController settingsController; 20 | 21 | private List paintings; 22 | 23 | PagerAdapter(ViewPager pager, SettingsController listener) { 24 | this.viewPager = pager; 25 | this.settingsController = listener; 26 | } 27 | 28 | void setPaintings(List paintings) { 29 | this.paintings = paintings; 30 | notifyDataSetChanged(); 31 | } 32 | 33 | @Override 34 | public int getCount() { 35 | return paintings == null ? 0 : paintings.size(); 36 | } 37 | 38 | @Override 39 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup container) { 40 | ViewHolder holder = new ViewHolder(container); 41 | holder.image.getController().enableScrollInViewPager(viewPager); 42 | return holder; 43 | } 44 | 45 | @Override 46 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 47 | settingsController.apply(holder.image); 48 | Painting painting = paintings.get(position); 49 | GlideHelper.loadFull(holder.image, painting.imageId, painting.thumbId); 50 | } 51 | 52 | static GestureImageView getImageView(RecyclePagerAdapter.ViewHolder holder) { 53 | return ((ViewHolder) holder).image; 54 | } 55 | 56 | 57 | static class ViewHolder extends RecyclePagerAdapter.ViewHolder { 58 | final GestureImageView image; 59 | 60 | ViewHolder(ViewGroup container) { 61 | super(new GestureImageView(container.getContext())); 62 | image = (GestureImageView) itemView; 63 | } 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/transitions/recycler/PagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.transitions.recycler; 2 | 3 | import android.view.ViewGroup; 4 | 5 | import androidx.annotation.NonNull; 6 | import androidx.viewpager.widget.ViewPager; 7 | 8 | import com.alexvasilkov.gestures.commons.RecyclePagerAdapter; 9 | import com.alexvasilkov.gestures.sample.base.settings.SettingsController; 10 | import com.alexvasilkov.gestures.sample.ex.utils.GlideHelper; 11 | import com.alexvasilkov.gestures.sample.ex.utils.Painting; 12 | import com.alexvasilkov.gestures.views.GestureImageView; 13 | 14 | class PagerAdapter extends RecyclePagerAdapter { 15 | 16 | private final ViewPager viewPager; 17 | private final Painting[] paintings; 18 | private final SettingsController settingsController; 19 | 20 | PagerAdapter(ViewPager pager, Painting[] paintings, SettingsController listener) { 21 | this.viewPager = pager; 22 | this.paintings = paintings; 23 | this.settingsController = listener; 24 | } 25 | 26 | @Override 27 | public int getCount() { 28 | return paintings.length; 29 | } 30 | 31 | @Override 32 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup container) { 33 | ViewHolder holder = new ViewHolder(container); 34 | holder.image.getController().enableScrollInViewPager(viewPager); 35 | return holder; 36 | } 37 | 38 | @Override 39 | public void onBindViewHolder(@NonNull ViewHolder holder, int position) { 40 | settingsController.apply(holder.image); 41 | Painting painting = paintings[position]; 42 | GlideHelper.loadFull(holder.image, painting.imageId, painting.thumbId); 43 | } 44 | 45 | static GestureImageView getImageView(RecyclePagerAdapter.ViewHolder holder) { 46 | return ((ViewHolder) holder).image; 47 | } 48 | 49 | 50 | static class ViewHolder extends RecyclePagerAdapter.ViewHolder { 51 | final GestureImageView image; 52 | 53 | ViewHolder(ViewGroup container) { 54 | super(new GestureImageView(container.getContext())); 55 | image = (GestureImageView) itemView; 56 | } 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/transitions/recycler/RecyclerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.transitions.recycler; 2 | 3 | import android.graphics.Typeface; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.widget.ImageView; 7 | import android.widget.TextView; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.recyclerview.widget.RecyclerView; 11 | 12 | import com.alexvasilkov.android.commons.texts.SpannableBuilder; 13 | import com.alexvasilkov.android.commons.ui.Views; 14 | import com.alexvasilkov.gestures.sample.R; 15 | import com.alexvasilkov.gestures.sample.ex.utils.GlideHelper; 16 | import com.alexvasilkov.gestures.sample.ex.utils.Painting; 17 | 18 | class RecyclerAdapter extends RecyclerView.Adapter { 19 | 20 | private final Painting[] paintings; 21 | private final OnPaintingClickListener listener; 22 | 23 | RecyclerAdapter(Painting[] paintings, OnPaintingClickListener listener) { 24 | this.paintings = paintings; 25 | this.listener = listener; 26 | } 27 | 28 | @Override 29 | public int getItemCount() { 30 | return paintings.length; 31 | } 32 | 33 | @NonNull 34 | @Override 35 | public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 36 | ViewHolder holder = new ViewHolder(parent); 37 | holder.itemView.setOnClickListener(this::onItemClick); 38 | return holder; 39 | } 40 | 41 | @Override 42 | public void onBindViewHolder(ViewHolder holder, int position) { 43 | final Painting painting = paintings[position]; 44 | 45 | // Storing item position for click handler 46 | holder.itemView.setTag(R.id.tag_item, position); 47 | 48 | GlideHelper.loadThumb(holder.image, painting.thumbId); 49 | 50 | CharSequence text = new SpannableBuilder(holder.title.getContext()) 51 | .createStyle().setFont(Typeface.DEFAULT_BOLD).apply() 52 | .append(painting.author).append("\n") 53 | .clearStyle() 54 | .append(painting.title) 55 | .build(); 56 | holder.title.setText(text); 57 | } 58 | 59 | private void onItemClick(@NonNull View view) { 60 | int pos = (Integer) view.getTag(R.id.tag_item); 61 | listener.onPaintingClick(pos); 62 | } 63 | 64 | static ImageView getImageView(RecyclerView.ViewHolder holder) { 65 | return ((ViewHolder) holder).image; 66 | } 67 | 68 | 69 | static class ViewHolder extends RecyclerView.ViewHolder { 70 | final ImageView image; 71 | final TextView title; 72 | 73 | ViewHolder(ViewGroup parent) { 74 | super(Views.inflate(parent, R.layout.list_image_item)); 75 | image = itemView.findViewById(R.id.list_image); 76 | title = itemView.findViewById(R.id.list_image_title); 77 | } 78 | } 79 | 80 | interface OnPaintingClickListener { 81 | void onPaintingClick(int position); 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/utils/GlideHelper.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.utils; 2 | 3 | import android.graphics.drawable.Drawable; 4 | import android.widget.ImageView; 5 | 6 | import com.bumptech.glide.Glide; 7 | import com.bumptech.glide.RequestBuilder; 8 | import com.bumptech.glide.load.engine.DiskCacheStrategy; 9 | import com.bumptech.glide.request.RequestOptions; 10 | import com.bumptech.glide.request.target.Target; 11 | 12 | public class GlideHelper { 13 | 14 | private GlideHelper() {} 15 | 16 | /** 17 | * Loads thumbnail. 18 | */ 19 | public static void loadThumb(ImageView image, int thumbId) { 20 | // We don't want Glide to crop or resize our image 21 | final RequestOptions options = new RequestOptions() 22 | .diskCacheStrategy(DiskCacheStrategy.NONE) 23 | .override(Target.SIZE_ORIGINAL) 24 | .dontTransform(); 25 | 26 | Glide.with(image) 27 | .load(thumbId) 28 | .apply(options) 29 | .into(image); 30 | } 31 | 32 | /** 33 | * Loads thumbnail and then replaces it with full image. 34 | */ 35 | public static void loadFull(ImageView image, int imageId, int thumbId) { 36 | // We don't want Glide to crop or resize our image 37 | final RequestOptions options = new RequestOptions() 38 | .diskCacheStrategy(DiskCacheStrategy.NONE) 39 | .override(Target.SIZE_ORIGINAL) 40 | .dontTransform(); 41 | 42 | final RequestBuilder thumbRequest = Glide.with(image) 43 | .load(thumbId) 44 | .apply(options); 45 | 46 | Glide.with(image) 47 | .load(imageId) 48 | .apply(options) 49 | .thumbnail(thumbRequest) 50 | .into(image); 51 | } 52 | 53 | public static void clear(ImageView view) { 54 | // Clearing current Glide request (if any) 55 | Glide.with(view).clear(view); 56 | // Cleaning up resources 57 | view.setImageDrawable(null); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /sample/src/main/java/com/alexvasilkov/gestures/sample/ex/utils/Painting.java: -------------------------------------------------------------------------------- 1 | package com.alexvasilkov.gestures.sample.ex.utils; 2 | 3 | import android.content.res.Resources; 4 | import android.content.res.TypedArray; 5 | 6 | import com.alexvasilkov.gestures.sample.R; 7 | 8 | public class Painting { 9 | 10 | public final int imageId; 11 | public final int thumbId; 12 | public final String author; 13 | public final String title; 14 | public final String link; 15 | 16 | private Painting(int imageId, int thumbId, String author, String title, String link) { 17 | this.imageId = imageId; 18 | this.thumbId = thumbId; 19 | this.author = author; 20 | this.title = title; 21 | this.link = link; 22 | } 23 | 24 | 25 | public static Painting[] list(Resources res) { 26 | final String[] authors = res.getStringArray(R.array.paintings_authors); 27 | final String[] titles = res.getStringArray(R.array.paintings_titles); 28 | final String[] links = res.getStringArray(R.array.paintings_links); 29 | 30 | final TypedArray images = res.obtainTypedArray(R.array.paintings_images); 31 | final TypedArray thumbs = res.obtainTypedArray(R.array.paintings_thumbs); 32 | 33 | final int size = titles.length; 34 | final Painting[] paintings = new Painting[size]; 35 | 36 | for (int i = 0; i < size; i++) { 37 | final int imageId = images.getResourceId(i, -1); 38 | final int thumbId = thumbs.getResourceId(i, -1); 39 | paintings[i] = new Painting(imageId, thumbId, authors[i], titles[i], links[i]); 40 | } 41 | 42 | images.recycle(); 43 | thumbs.recycle(); 44 | 45 | return paintings; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_arrow_back_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_arrow_back_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_check_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_check_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_crop_16_9_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_crop_16_9_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_crop_circle_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_crop_circle_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_crop_original_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_crop_original_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_crop_square_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_crop_square_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_ex_complex_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_ex_complex_list.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_ex_crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_ex_crop.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_ex_custom_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_ex_custom_text.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_ex_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_ex_image.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_ex_image_animation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_ex_image_animation.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_ex_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_ex_layout.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_ex_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_ex_list.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_ex_markers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_ex_markers.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_ex_objects_control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_ex_objects_control.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_ex_pager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_ex_pager.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_ex_state_control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_ex_state_control.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_grid_on_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_grid_on_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_info_outline_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_info_outline_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_place_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_place_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_restore_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_restore_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_rotate_90_degrees_ccw_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_rotate_90_degrees_ccw_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_zoom_in_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_zoom_in_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_zoom_out_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-hdpi/ic_zoom_out_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-night-nodpi/start_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-night-nodpi/start_background.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/demo_top.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/demo_top.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/item_ball.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/item_ball.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/item_pin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/item_pin.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/painting_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/painting_01.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/painting_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/painting_02.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/painting_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/painting_03.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/painting_04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/painting_04.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/painting_05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/painting_05.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/painting_thumb_01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/painting_thumb_01.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/painting_thumb_02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/painting_thumb_02.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/painting_thumb_03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/painting_thumb_03.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/painting_thumb_04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/painting_thumb_04.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/painting_thumb_05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/painting_thumb_05.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/start_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/start_background.jpg -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/start_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/start_text.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-nodpi/world_map.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-nodpi/world_map.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_arrow_back_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_arrow_back_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_check_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_check_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_crop_16_9_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_crop_16_9_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_crop_circle_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_crop_circle_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_crop_original_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_crop_original_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_crop_square_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_crop_square_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_ex_complex_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_ex_complex_list.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_ex_crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_ex_crop.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_ex_custom_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_ex_custom_text.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_ex_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_ex_image.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_ex_image_animation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_ex_image_animation.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_ex_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_ex_layout.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_ex_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_ex_list.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_ex_markers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_ex_markers.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_ex_objects_control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_ex_objects_control.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_ex_pager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_ex_pager.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_ex_state_control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_ex_state_control.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_grid_on_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_grid_on_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_info_outline_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_info_outline_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_place_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_place_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_restore_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_restore_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_rotate_90_degrees_ccw_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_rotate_90_degrees_ccw_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_zoom_in_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_zoom_in_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_zoom_out_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xhdpi/ic_zoom_out_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_arrow_back_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_arrow_back_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_check_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_check_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_crop_16_9_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_crop_16_9_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_crop_circle_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_crop_circle_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_crop_original_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_crop_original_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_crop_square_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_crop_square_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_ex_complex_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_ex_complex_list.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_ex_crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_ex_crop.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_ex_custom_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_ex_custom_text.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_ex_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_ex_image.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_ex_image_animation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_ex_image_animation.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_ex_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_ex_layout.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_ex_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_ex_list.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_ex_markers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_ex_markers.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_ex_objects_control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_ex_objects_control.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_ex_pager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_ex_pager.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_ex_state_control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_ex_state_control.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_grid_on_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_grid_on_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_info_outline_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_info_outline_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_place_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_place_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_restore_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_restore_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_rotate_90_degrees_ccw_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_rotate_90_degrees_ccw_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_zoom_in_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_zoom_in_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_zoom_out_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxhdpi/ic_zoom_out_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_arrow_back_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_arrow_back_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_check_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_check_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_crop_16_9_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_crop_16_9_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_crop_circle_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_crop_circle_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_crop_original_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_crop_original_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_crop_square_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_crop_square_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_ex_complex_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_ex_complex_list.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_ex_crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_ex_crop.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_ex_custom_text.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_ex_custom_text.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_ex_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_ex_image.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_ex_image_animation.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_ex_image_animation.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_ex_layout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_ex_layout.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_ex_list.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_ex_list.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_ex_markers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_ex_markers.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_ex_objects_control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_ex_objects_control.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_ex_pager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_ex_pager.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_ex_state_control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_ex_state_control.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_grid_on_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_grid_on_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_info_outline_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_info_outline_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_place_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_place_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_restore_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_restore_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_rotate_90_degrees_ccw_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_rotate_90_degrees_ccw_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_zoom_in_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_zoom_in_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_zoom_out_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alexvasilkov/GestureViews/28d3a30f216a70881bca1136e71ccf276e24bb00/sample/src/main/res/drawable-xxxhdpi/ic_zoom_out_black_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable/demo_circle_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/demo_photo_border.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/item_left_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/item_right_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable/shadow_bottom.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/complex_list_item_images.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 20 | 21 | 27 | 28 | 35 | 36 | 46 | 47 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/complex_list_item_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/complex_list_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 17 | 18 | 24 | 25 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/custom_view_screen.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/demo_item_extra_error.xml: -------------------------------------------------------------------------------- 1 | 2 |