├── .gitignore ├── README.md ├── build.gradle ├── docs └── CHANGELOG.md ├── frescoimageviewer ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── stfalcon │ │ └── frescoimageviewer │ │ ├── AnimationUtils.java │ │ ├── ImageViewer.java │ │ ├── ImageViewerAdapter.java │ │ ├── ImageViewerView.java │ │ ├── MultiTouchViewPager.java │ │ ├── OnDismissListener.java │ │ ├── SwipeDirectionDetector.java │ │ ├── SwipeToDismissListener.java │ │ ├── adapter │ │ ├── RecyclingPagerAdapter.java │ │ └── ViewHolder.java │ │ └── drawee │ │ ├── NonInterceptableAttacher.java │ │ └── ZoomableDraweeView.java │ └── res │ └── layout │ └── image_viewer.xml ├── gradle.properties ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── fresco_image_viewer_customizing_demo.gif ├── fresco_image_viewer_demo.gif └── posters │ ├── Daniel.jpg │ ├── Driver.jpg │ ├── Frank.jpg │ ├── Jules.jpg │ ├── Korben.jpg │ ├── Marty.jpg │ ├── Max.jpg │ ├── Toretto.jpg │ └── Vincent.jpg ├── sample ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── stfalcon │ │ └── frescoimageviewersample │ │ ├── App.java │ │ ├── common │ │ ├── data │ │ │ ├── Demo.java │ │ │ └── models │ │ │ │ └── CustomImage.java │ │ └── views │ │ │ └── ImageOverlayView.java │ │ ├── features │ │ ├── demo │ │ │ ├── DemoActivity.java │ │ │ ├── formatter │ │ │ │ └── CustomObjectsActivity.java │ │ │ ├── rotation │ │ │ │ └── DialogRotationExampleActivity.java │ │ │ ├── simple │ │ │ │ └── SimpleUsageActivity.java │ │ │ └── styled │ │ │ │ └── StyledViewActivity.java │ │ └── main │ │ │ ├── DemoCardFragment.java │ │ │ ├── MainActivity.java │ │ │ └── MainActivityPagerAdapter.java │ │ └── utils │ │ ├── AppUtils.java │ │ └── StylingOptions.java │ └── res │ ├── drawable-hdpi │ └── ic_settings_white_24dp.png │ ├── drawable-mdpi │ └── ic_settings_white_24dp.png │ ├── drawable-xhdpi │ └── ic_settings_white_24dp.png │ ├── drawable-xxhdpi │ └── ic_settings_white_24dp.png │ ├── drawable-xxxhdpi │ └── ic_settings_white_24dp.png │ ├── drawable │ └── play_store_icon.png │ ├── layout │ ├── activity_demo.xml │ ├── activity_main.xml │ ├── content_posters_grid.xml │ ├── fragment_demo_card.xml │ └── view_image_overlay.xml │ ├── menu │ └── styling_options_menu.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ └── ic_launcher.png │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | *.jar 5 | 6 | # files for the dex VM 7 | *.dex 8 | 9 | # Java class files 10 | *.class 11 | 12 | # generated files 13 | android/bin/ 14 | android/gen/ 15 | 16 | # Local configuration file (sdk path, etc) 17 | local.properties 18 | 19 | # Eclipse project files 20 | .classpath 21 | .project 22 | .properties 23 | .settings/ 24 | libprojects/ 25 | 26 | # Proguard folder generated by Eclipse 27 | android/proguard/ 28 | 29 | # Intellij project files 30 | *.iml 31 | *.ipr 32 | *.iws 33 | .idea/ 34 | build/ 35 | .gradle -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### This project is no longer supported. If you're able to switch from Fresco to any other library that works with the Android's ImageView, please migrate to the [StfalconImageViewer](https://github.com/stfalcon-studio/StfalconImageViewer) which is a better version of the FrescoImageViewer. Otherwise, you can still use the latest version of this library. Anyway, PRs are welcome! 2 | 3 | # FrescoImageViewer 4 | 5 | [![codebeat badge](https://codebeat.co/badges/60dbbc38-c85e-4680-b25b-6b04d859e904)](https://codebeat.co/projects/github-com-stfalcon-studio-frescoimageviewer-master) [ ![Download](https://api.bintray.com/packages/troy379/maven/FrescoImageViewer/images/download.svg) ](https://bintray.com/troy379/maven/FrescoImageViewer/_latestVersion) 6 | 7 | Simple customizable full screen image viewer for [Fresco library][frescoRepo] that includes "pinch to zoom" and "swipe to dismiss" gestures. 8 | Based on [PhotoDraweeView][photoDraweeViewRepo] by [ongakuer][coauthor]. 9 | 10 | ![alt tag](images/fresco_image_viewer_demo.gif) 11 | 12 | ### Who we are 13 | Need iOS and Android apps, MVP development or prototyping? Contact us via info@stfalcon.com. We develop software since 2009, and we're known experts in this field. Check out our [portfolio](https://stfalcon.com/en/portfolio) and see more libraries from [stfalcon-studio](https://stfalcon-studio.github.io/). 14 | 15 | ## Requirements 16 | 17 | * Fresco v.0.12.0 and higher 18 | * SDK 14 and and higher 19 | 20 | ## Demo Application 21 | 22 | [![Get it on Google Play](https://play.google.com/intl/en_us/badges/images/badge_new.png)](https://play.google.com/store/apps/details?id=com.stfalcon.frescoimageviewersample) 23 | 24 | ## Usage 25 | 26 | #### Simple usage 27 | All you need to show a viewer is pass the context, start position and List or String[] into builder and call `show()`. 28 | ```java 29 | new ImageViewer.Builder(context, list) 30 | .setStartPosition(startPosition) 31 | .show(); 32 | ``` 33 | 34 | #### Custom objects 35 | But what if in your application images are represented not only with urls? For example, you have object with url and description? You'll have to convert it to list of Strings and only then pass it to viewer, right? 36 | No, it's unnecessary! With `ImageViewer.Formatter` you can pass list of your custom images to viewer and simply write a rule for url extracting: 37 | ```java 38 | List images = getImages(); 39 | new ImageViewer.Builder<>(this, images) 40 | .setFormatter(new ImageViewer.Formatter() { 41 | @Override 42 | public String format(CustomImage customImage) { 43 | return customImage.getUrl(); 44 | } 45 | }) 46 | .show(); 47 | ``` 48 | If formatter isn't passed, `Object.toString()` will be used for image formatting as default behavior. 49 | 50 | Piece of cake! :cake: :wink: 51 | 52 | ## Reminder 53 | Don't forget to initialize Fresco in your Application class: 54 | ```java 55 | Fresco.initialize(this); 56 | ``` 57 | 58 | And if you expect to open really large images, use configuration below for better performance: 59 | ```java 60 | ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this) 61 | .setProgressiveJpegConfig(new SimpleProgressiveJpegConfig()) 62 | .setResizeAndRotateEnabledForNetwork(true) 63 | .setDownsampleEnabled(true) 64 | .build(); 65 | Fresco.initialize(this, config); 66 | ``` 67 | 68 | ## Customizing 69 | 70 | You can also customize a viewer to fit your needs. 71 | 72 | #### Background 73 | Use `setBackgroundColorRes(colorRes)` or `setBackgroundColor(colorInt)` to set color for fading background. 74 | 75 | #### Custom overlay view 76 | If you need some content over the image (e.g. sharing or download button, description, numeration etc.) you can set your custom view using `setOverlayView(customView)` and bind it with viewer through `ImageViewer.OnImageChangeListener`. 77 | 78 | #### Custom drawee hierarchy 79 | Of course, according to Fresco flexibility, you can use your custom GenericDraweeHierarchy. 80 | To do this you simply need to create GenericDraweeHierarchy**Builder** and pass it into builder: 81 | ```java 82 | GenericDraweeHierarchyBuilder hierarchyBuilder = GenericDraweeHierarchyBuilder.newInstance(getResources()) 83 | .setFailureImage(R.drawable.failureDrawable) 84 | .setProgressBarImage(R.drawable.progressBarDrawable) 85 | .setPlaceholderImage(R.drawable.placeholderDrawable); 86 | 87 | builder.setCustomDraweeHierarchyBuilder(hierarchyBuilder) 88 | ``` 89 | 90 | :exclamation:**But there is a limitation**: default ScaleType in hierarchy is `ScaleType.FIT_CENTER`, so custom value will be ignored 91 | 92 | #### Custom image requests 93 | For rare cases like post-processing or bitmap resizing you need to use your custom ImageRequestBuilder. 94 | Create it with `ImageViewer.createImageRequestBuilder()` and after configuration pass it to viewer through `setCustomImageRequestBuilder(ImageRequestBuilder)`. 95 | ```java 96 | builder.setCustomImageRequestBuilder( 97 | ImageViewer.createImageRequestBuilder() 98 | .setPostprocessor(new GrayscalePostprocessor())); 99 | ``` 100 | 101 | #### Image margin 102 | Simply add margins between images with dimens with setImageMargin(context, dimen) or in `px` using `setImageMarginPx(marginPx)`. 103 | 104 | #### Container padding 105 | Overlay image hides part of image? Set container padding with dimens using `setContainerPadding(context, start, top, end, bottom)` or `setContainerPadding(context, dimean)` for all sides at once. 106 | For setting padding in pixels, just use `setContainerPaddingPx(...)` method. 107 | 108 | #### Status bar visibility 109 | To show/hide status bar in view property you can set `hideStatusBar(boolean)` in builder. The default value is `true`. 110 | 111 | #### Gestures disabling 112 | If you need to disable some of gestures - do it using `allowSwipeToDismiss(boolean)` and `allowZooming(boolean)` accordingly. 113 | 114 | Here is an example that sets all the possible options: 115 | 116 | ```java 117 | new ImageViewer.Builder<>(this, images) 118 | .setStartPosition(startPosition) 119 | .hideStatusBar(false) 120 | .allowZooming(true) 121 | .allowSwipeToDismiss(true) 122 | .setBackgroundColorRes(colorRes) 123 | //.setBackgroundColor(color) 124 | .setImageMargin(margin) 125 | //.setImageMarginPx(marginPx) 126 | .setContainerPadding(this, dimen) 127 | //.setContainerPadding(this, dimenStart, dimenTop, dimenEnd, dimenBottom) 128 | //.setContainerPaddingPx(padding) 129 | //.setContainerPaddingPx(start, top, end, bottom) 130 | .setCustomImageRequestBuilder(imageRequestBuilder) 131 | .setCustomDraweeHierarchyBuilder(draweeHierarchyBuilder) 132 | .setImageChangeListener(imageChangeListener) 133 | .setOnDismissListener(onDismissListener) 134 | .setOverlayView(overlayView) 135 | .show(); 136 | 137 | ``` 138 | 139 | With this possibilities you can achieve something like this: 140 | 141 | ![alt tag](images/fresco_image_viewer_customizing_demo.gif) 142 | 143 | You can take a look at [sample project][sample] for more information. 144 | 145 | ## Install 146 | 147 | Download via **Gradle**: 148 | ```gradle 149 | compile 'com.github.stfalcon:frescoimageviewer:0.5.0' 150 | ``` 151 | 152 | or **Maven**: 153 | ```xml 154 | 155 | com.github.stfalcon 156 | frescoimageviewer 157 | 0.5.0 158 | pom 159 | 160 | ``` 161 | 162 | ## Changelog 163 | [See the changelog](docs/CHANGELOG.md) to be aware of latest improvements and fixes. 164 | 165 | ## License 166 | 167 | ``` 168 | Copyright (C) 2017 stfalcon.com 169 | 170 | Licensed under the Apache License, Version 2.0 (the "License"); 171 | you may not use this file except in compliance with the License. 172 | You may obtain a copy of the License at 173 | 174 | http://www.apache.org/licenses/LICENSE-2.0 175 | 176 | Unless required by applicable law or agreed to in writing, software 177 | distributed under the License is distributed on an "AS IS" BASIS, 178 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 179 | See the License for the specific language governing permissions and 180 | limitations under the License. 181 | 182 | ``` 183 | 184 | [sample]: 185 | [frescoRepo]: 186 | [photoDraweeViewRepo]: 187 | [coauthor]: 188 | 189 | 190 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.1' 9 | classpath 'com.novoda:bintray-release:0.3.4' 10 | 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | jcenter() 19 | } 20 | } 21 | 22 | task clean(type: Delete) { 23 | delete rootProject.buildDir 24 | } 25 | -------------------------------------------------------------------------------- /docs/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## Version 0.5.0 (2017/03/14) 2 | 3 | * formatter for custom objects collections; 4 | * container padding; 5 | * custom image requests; 6 | * gestures enabling/disabling; 7 | * fixed dismiss listener on back button press; 8 | 9 | ## Version 0.4.1 (2016/12/27) 10 | 11 | * fixed memory leak for large amount of images; 12 | * fixed scale processing logic. 13 | 14 | ## Version 0.3.1 (2016/11/01) 15 | 16 | * hide status bar option; 17 | * dismiss listener. 18 | 19 | ## Version 0.2.0 (2016/09/14) 20 | 21 | * custom hierarchy support, images margin; 22 | * custom overlay view; 23 | * image change listener. 24 | 25 | ## Version 0.1.0 (2016/08/29) 26 | 27 | * Release. -------------------------------------------------------------------------------- /frescoimageviewer/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'com.novoda.bintray-release' 3 | 4 | android { 5 | compileSdkVersion 25 6 | buildToolsVersion "25.0.2" 7 | 8 | defaultConfig { 9 | minSdkVersion 14 10 | targetSdkVersion 25 11 | versionCode 1 12 | versionName "0.5.0" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | android { 21 | lintOptions { 22 | abortOnError false 23 | } 24 | } 25 | 26 | } 27 | 28 | publish { 29 | groupId = 'com.github.stfalcon' 30 | artifactId = 'frescoimageviewer' 31 | publishVersion = '0.5.0' 32 | desc = 'Customizable full screen image viewer based on Fresco library, that includes "pinch to zoom" and "swipe to dismiss" gestures' 33 | licences = ['Apache-2.0'] 34 | uploadName = 'FrescoImageViewer' 35 | website = 'https://github.com/stfalcon-studio/FrescoImageViewer.git' 36 | } 37 | 38 | dependencies { 39 | compile fileTree(dir: 'libs', include: ['*.jar']) 40 | compile 'com.android.support:appcompat-v7:25.1.1' 41 | compile 'me.relex:photodraweeview:1.1.0' 42 | provided 'com.facebook.fresco:fresco:0.12.0' 43 | } 44 | -------------------------------------------------------------------------------- /frescoimageviewer/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/troy379/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /frescoimageviewer/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frescoimageviewer/src/main/java/com/stfalcon/frescoimageviewer/AnimationUtils.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewer; 2 | 3 | import android.animation.Animator; 4 | import android.animation.AnimatorListenerAdapter; 5 | import android.animation.ObjectAnimator; 6 | import android.view.View; 7 | import android.view.ViewConfiguration; 8 | 9 | /* 10 | * Created by troy379 on 14.09.16. 11 | */ 12 | final class AnimationUtils { 13 | private AnimationUtils() { 14 | throw new AssertionError(); 15 | } 16 | 17 | static void animateVisibility(final View view) { 18 | final boolean isVisible = view.getVisibility() == View.VISIBLE; 19 | float from = isVisible ? 1.0f : 0.0f, 20 | to = isVisible ? 0.0f : 1.0f; 21 | 22 | ObjectAnimator animation = ObjectAnimator.ofFloat(view, "alpha", from, to); 23 | animation.setDuration(ViewConfiguration.getDoubleTapTimeout()); 24 | 25 | if (isVisible) { 26 | animation.addListener(new AnimatorListenerAdapter() { 27 | @Override 28 | public void onAnimationEnd(Animator animation) { 29 | view.setVisibility(View.GONE); 30 | } 31 | }); 32 | } else view.setVisibility(View.VISIBLE); 33 | 34 | animation.start(); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /frescoimageviewer/src/main/java/com/stfalcon/frescoimageviewer/ImageViewer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 stfalcon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.stfalcon.frescoimageviewer; 18 | 19 | import android.content.Context; 20 | import android.content.DialogInterface; 21 | import android.graphics.Color; 22 | import android.net.Uri; 23 | import android.support.annotation.ColorInt; 24 | import android.support.annotation.ColorRes; 25 | import android.support.annotation.DimenRes; 26 | import android.support.annotation.StyleRes; 27 | import android.support.v4.view.ViewPager; 28 | import android.support.v7.app.AlertDialog; 29 | import android.util.Log; 30 | import android.view.KeyEvent; 31 | import android.view.View; 32 | 33 | import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; 34 | import com.facebook.imagepipeline.request.ImageRequestBuilder; 35 | 36 | import java.util.ArrayList; 37 | import java.util.Arrays; 38 | import java.util.List; 39 | 40 | /* 41 | * Created by Alexander Krol (troy379) on 29.08.16. 42 | */ 43 | public class ImageViewer implements OnDismissListener, DialogInterface.OnKeyListener { 44 | 45 | private static final String TAG = ImageViewer.class.getSimpleName(); 46 | 47 | private Builder builder; 48 | private AlertDialog dialog; 49 | private ImageViewerView viewer; 50 | 51 | protected ImageViewer(Builder builder) { 52 | this.builder = builder; 53 | createDialog(); 54 | } 55 | 56 | /** 57 | * Displays the built viewer if passed images list isn't empty 58 | */ 59 | public void show() { 60 | if (!builder.dataSet.data.isEmpty()) { 61 | dialog.show(); 62 | } else { 63 | Log.w(TAG, "Images list cannot be empty! Viewer ignored."); 64 | } 65 | } 66 | 67 | public String getUrl() { 68 | return viewer.getUrl(); 69 | } 70 | 71 | private void createDialog() { 72 | viewer = new ImageViewerView(builder.context); 73 | viewer.setCustomImageRequestBuilder(builder.customImageRequestBuilder); 74 | viewer.setCustomDraweeHierarchyBuilder(builder.customHierarchyBuilder); 75 | viewer.allowZooming(builder.isZoomingAllowed); 76 | viewer.allowSwipeToDismiss(builder.isSwipeToDismissAllowed); 77 | viewer.setOnDismissListener(this); 78 | viewer.setBackgroundColor(builder.backgroundColor); 79 | viewer.setOverlayView(builder.overlayView); 80 | viewer.setImageMargin(builder.imageMarginPixels); 81 | viewer.setContainerPadding(builder.containerPaddingPixels); 82 | viewer.setUrls(builder.dataSet, builder.startPosition); 83 | viewer.setPageChangeListener(new ViewPager.SimpleOnPageChangeListener() { 84 | @Override 85 | public void onPageSelected(int position) { 86 | if (builder.imageChangeListener != null) { 87 | builder.imageChangeListener.onImageChange(position); 88 | } 89 | } 90 | }); 91 | 92 | dialog = new AlertDialog.Builder(builder.context, getDialogStyle()) 93 | .setView(viewer) 94 | .setOnKeyListener(this) 95 | .create(); 96 | dialog.setOnDismissListener(new DialogInterface.OnDismissListener() { 97 | @Override 98 | public void onDismiss(DialogInterface dialogInterface) { 99 | if (builder.onDismissListener != null) { 100 | builder.onDismissListener.onDismiss(); 101 | } 102 | } 103 | }); 104 | } 105 | 106 | /** 107 | * Fires when swipe to dismiss was initiated 108 | */ 109 | @Override 110 | public void onDismiss() { 111 | dialog.dismiss(); 112 | } 113 | 114 | /** 115 | * Resets image on {@literal KeyEvent.KEYCODE_BACK} to normal scale if needed, otherwise - hide the viewer. 116 | */ 117 | @Override 118 | public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) { 119 | if (keyCode == KeyEvent.KEYCODE_BACK && 120 | event.getAction() == KeyEvent.ACTION_UP && 121 | !event.isCanceled()) { 122 | if (viewer.isScaled()) { 123 | viewer.resetScale(); 124 | } else { 125 | dialog.cancel(); 126 | } 127 | } 128 | return true; 129 | } 130 | 131 | /** 132 | * Creates new {@code ImageRequestBuilder}. 133 | */ 134 | public static ImageRequestBuilder createImageRequestBuilder() { 135 | return ImageRequestBuilder.newBuilderWithSource(Uri.parse("")); 136 | } 137 | 138 | /** 139 | * Interface definition for a callback to be invoked when image was changed 140 | */ 141 | public interface OnImageChangeListener { 142 | void onImageChange(int position); 143 | } 144 | 145 | /** 146 | * Interface definition for a callback to be invoked when viewer was dismissed 147 | */ 148 | public interface OnDismissListener { 149 | void onDismiss(); 150 | } 151 | 152 | private @StyleRes int getDialogStyle() { 153 | return builder.shouldStatusBarHide 154 | ? android.R.style.Theme_Translucent_NoTitleBar_Fullscreen 155 | : android.R.style.Theme_Translucent_NoTitleBar; 156 | } 157 | 158 | /** 159 | * Interface used to format custom objects into an image url. 160 | */ 161 | public interface Formatter { 162 | 163 | /** 164 | * Formats an image url representation of the object. 165 | * 166 | * @param t The object that needs to be formatted into url. 167 | * @return An url of image. 168 | */ 169 | String format(T t); 170 | } 171 | 172 | static class DataSet { 173 | 174 | private List data; 175 | private Formatter formatter; 176 | 177 | DataSet(List data) { 178 | this.data = data; 179 | } 180 | 181 | String format(int position) { 182 | return format(data.get(position)); 183 | } 184 | 185 | String format(T t) { 186 | if (formatter == null) return t.toString(); 187 | else return formatter.format(t); 188 | } 189 | 190 | public List getData() { 191 | return data; 192 | } 193 | } 194 | 195 | /** 196 | * Builder class for {@link ImageViewer} 197 | */ 198 | public static class Builder { 199 | 200 | private Context context; 201 | private DataSet dataSet; 202 | private @ColorInt int backgroundColor = Color.BLACK; 203 | private int startPosition; 204 | private OnImageChangeListener imageChangeListener; 205 | private OnDismissListener onDismissListener; 206 | private View overlayView; 207 | private int imageMarginPixels; 208 | private int[] containerPaddingPixels = new int[4]; 209 | private ImageRequestBuilder customImageRequestBuilder; 210 | private GenericDraweeHierarchyBuilder customHierarchyBuilder; 211 | private boolean shouldStatusBarHide = true; 212 | private boolean isZoomingAllowed = true; 213 | private boolean isSwipeToDismissAllowed = true; 214 | 215 | /** 216 | * Constructor using a context and images urls array for this builder and the {@link ImageViewer} it creates. 217 | */ 218 | public Builder(Context context, T[] images) { 219 | this(context, new ArrayList<>(Arrays.asList(images))); 220 | } 221 | 222 | /** 223 | * Constructor using a context and images urls list for this builder and the {@link ImageViewer} it creates. 224 | */ 225 | public Builder(Context context, List images) { 226 | this.context = context; 227 | this.dataSet = new DataSet<>(images); 228 | } 229 | 230 | /** 231 | * If you use an non-string collection, you can use custom {@link Formatter} to represent it as url. 232 | */ 233 | public Builder setFormatter(Formatter formatter) { 234 | this.dataSet.formatter = formatter; 235 | return this; 236 | } 237 | 238 | /** 239 | * Set background color resource for viewer 240 | * 241 | * @return This Builder object to allow for chaining of calls to set methods 242 | */ 243 | @SuppressWarnings("deprecation") 244 | public Builder setBackgroundColorRes(@ColorRes int color) { 245 | return this.setBackgroundColor(context.getResources().getColor(color)); 246 | } 247 | 248 | /** 249 | * Set background color int for viewer 250 | * 251 | * @return This Builder object to allow for chaining of calls to set methods 252 | */ 253 | public Builder setBackgroundColor(@ColorInt int color) { 254 | this.backgroundColor = color; 255 | return this; 256 | } 257 | 258 | /** 259 | * Set background color int for viewer 260 | * 261 | * @return This Builder object to allow for chaining of calls to set methods 262 | */ 263 | public Builder setStartPosition(int position) { 264 | this.startPosition = position; 265 | return this; 266 | } 267 | 268 | /** 269 | * Set {@link ImageViewer.OnImageChangeListener} for viewer. 270 | * 271 | * @return This Builder object to allow for chaining of calls to set methods 272 | */ 273 | public Builder setImageChangeListener(OnImageChangeListener imageChangeListener) { 274 | this.imageChangeListener = imageChangeListener; 275 | return this; 276 | } 277 | 278 | /** 279 | * Set overlay view 280 | * 281 | * @return This Builder object to allow for chaining of calls to set methods 282 | */ 283 | public Builder setOverlayView(View view) { 284 | this.overlayView = view; 285 | return this; 286 | } 287 | 288 | /** 289 | * Set space between the images in px. 290 | * 291 | * @return This Builder object to allow for chaining of calls to set methods 292 | */ 293 | public Builder setImageMarginPx(int marginPixels) { 294 | this.imageMarginPixels = marginPixels; 295 | return this; 296 | } 297 | 298 | /** 299 | * Set space between the images using dimension. 300 | * 301 | * @return This Builder object to allow for chaining of calls to set methods 302 | */ 303 | public Builder setImageMargin(Context context, @DimenRes int dimen) { 304 | this.imageMarginPixels = Math.round(context.getResources().getDimension(dimen)); 305 | return this; 306 | } 307 | 308 | /** 309 | * Set {@code start}, {@code top}, {@code end} and {@code bottom} padding for zooming and scrolling area in px. 310 | * 311 | * @return This Builder object to allow for chaining of calls to set methods 312 | */ 313 | public Builder setContainerPaddingPx(int start, int top, int end, int bottom) { 314 | this.containerPaddingPixels = new int[]{start, top, end, bottom}; 315 | return this; 316 | } 317 | 318 | /** 319 | * Set {@code start}, {@code top}, {@code end} and {@code bottom} padding for zooming and scrolling area using dimension. 320 | * 321 | * @return This Builder object to allow for chaining of calls to set methods 322 | */ 323 | public Builder setContainerPadding(Context context, 324 | @DimenRes int start, @DimenRes int top, 325 | @DimenRes int end, @DimenRes int bottom) { 326 | setContainerPaddingPx( 327 | Math.round(context.getResources().getDimension(start)), 328 | Math.round(context.getResources().getDimension(top)), 329 | Math.round(context.getResources().getDimension(end)), 330 | Math.round(context.getResources().getDimension(bottom)) 331 | ); 332 | return this; 333 | } 334 | 335 | /** 336 | * Set common padding for zooming and scrolling area in px. 337 | * 338 | * @return This Builder object to allow for chaining of calls to set methods 339 | */ 340 | public Builder setContainerPaddingPx(int padding) { 341 | this.containerPaddingPixels = new int[]{padding, padding, padding, padding}; 342 | return this; 343 | } 344 | 345 | /** 346 | * Set common padding for zooming and scrolling area using dimension. 347 | * 348 | * @return This Builder object to allow for chaining of calls to set methods 349 | */ 350 | public Builder setContainerPadding(Context context, @DimenRes int padding) { 351 | int paddingPx = Math.round(context.getResources().getDimension(padding)); 352 | setContainerPaddingPx(paddingPx, paddingPx, paddingPx, paddingPx); 353 | return this; 354 | } 355 | 356 | /** 357 | * Set status bar visibility. By default is true. 358 | * 359 | * @return This Builder object to allow for chaining of calls to set methods 360 | */ 361 | public Builder hideStatusBar(boolean shouldHide) { 362 | this.shouldStatusBarHide = shouldHide; 363 | return this; 364 | } 365 | 366 | /** 367 | * Allow or disallow zooming. By default is true. 368 | * 369 | * @return This Builder object to allow for chaining of calls to set methods 370 | */ 371 | public Builder allowZooming(boolean value) { 372 | this.isZoomingAllowed = value; 373 | return this; 374 | } 375 | 376 | /** 377 | * Allow or disallow swipe to dismiss gesture. By default is true. 378 | * 379 | * @return This Builder object to allow for chaining of calls to set methods 380 | */ 381 | public Builder allowSwipeToDismiss(boolean value) { 382 | this.isSwipeToDismissAllowed = value; 383 | return this; 384 | } 385 | 386 | /** 387 | * Set {@link ImageViewer.OnDismissListener} for viewer. 388 | * 389 | * @return This Builder object to allow for chaining of calls to set methods 390 | */ 391 | public Builder setOnDismissListener(OnDismissListener onDismissListener) { 392 | this.onDismissListener = onDismissListener; 393 | return this; 394 | } 395 | 396 | /** 397 | * Set @{@code ImageRequestBuilder} for drawees. Use it for post-processing, custom resize options etc. 398 | * Use {@link ImageViewer#createImageRequestBuilder()} to create its new instance. 399 | * 400 | * @return This Builder object to allow for chaining of calls to set methods 401 | */ 402 | public Builder setCustomImageRequestBuilder(ImageRequestBuilder customImageRequestBuilder) { 403 | this.customImageRequestBuilder = customImageRequestBuilder; 404 | return this; 405 | } 406 | 407 | /** 408 | * Set {@link GenericDraweeHierarchyBuilder} for drawees inside viewer. 409 | * Use it for drawee customizing (e.g. failure image, placeholder, progressbar etc.) 410 | * N.B.! Due to zoom logic there is limitation of scale type which always equals FIT_CENTER. Other values will be ignored 411 | * 412 | * @return This Builder object to allow for chaining of calls to set methods 413 | */ 414 | public Builder setCustomDraweeHierarchyBuilder(GenericDraweeHierarchyBuilder customHierarchyBuilder) { 415 | this.customHierarchyBuilder = customHierarchyBuilder; 416 | return this; 417 | } 418 | 419 | /** 420 | * Creates a {@link ImageViewer} with the arguments supplied to this builder. It does not 421 | * {@link ImageViewer#show()} the dialog. This allows the user to do any extra processing 422 | * before displaying the dialog. Use {@link #show()} if you don't have any other processing 423 | * to do and want this to be created and displayed. 424 | */ 425 | public ImageViewer build() { 426 | return new ImageViewer(this); 427 | } 428 | 429 | /** 430 | * Creates a {@link ImageViewer} with the arguments supplied to this builder and 431 | * {@link ImageViewer#show()}'s the dialog. 432 | */ 433 | public ImageViewer show() { 434 | ImageViewer dialog = build(); 435 | dialog.show(); 436 | return dialog; 437 | } 438 | } 439 | } 440 | -------------------------------------------------------------------------------- /frescoimageviewer/src/main/java/com/stfalcon/frescoimageviewer/ImageViewerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewer; 2 | 3 | import android.content.Context; 4 | import android.graphics.drawable.Animatable; 5 | import android.net.Uri; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.facebook.drawee.backends.pipeline.Fresco; 10 | import com.facebook.drawee.backends.pipeline.PipelineDraweeControllerBuilder; 11 | import com.facebook.drawee.controller.BaseControllerListener; 12 | import com.facebook.drawee.drawable.ScalingUtils; 13 | import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; 14 | import com.facebook.imagepipeline.image.ImageInfo; 15 | import com.facebook.imagepipeline.request.ImageRequestBuilder; 16 | import com.stfalcon.frescoimageviewer.adapter.RecyclingPagerAdapter; 17 | import com.stfalcon.frescoimageviewer.adapter.ViewHolder; 18 | import com.stfalcon.frescoimageviewer.drawee.ZoomableDraweeView; 19 | 20 | import java.util.HashSet; 21 | 22 | import me.relex.photodraweeview.OnScaleChangeListener; 23 | 24 | /* 25 | * Created by troy379 on 07.12.16. 26 | */ 27 | class ImageViewerAdapter 28 | extends RecyclingPagerAdapter { 29 | 30 | private Context context; 31 | private ImageViewer.DataSet dataSet; 32 | private HashSet holders; 33 | private ImageRequestBuilder imageRequestBuilder; 34 | private GenericDraweeHierarchyBuilder hierarchyBuilder; 35 | private boolean isZoomingAllowed; 36 | 37 | ImageViewerAdapter(Context context, ImageViewer.DataSet dataSet, 38 | ImageRequestBuilder imageRequestBuilder, 39 | GenericDraweeHierarchyBuilder hierarchyBuilder, 40 | boolean isZoomingAllowed) { 41 | this.context = context; 42 | this.dataSet = dataSet; 43 | this.holders = new HashSet<>(); 44 | this.imageRequestBuilder = imageRequestBuilder; 45 | this.hierarchyBuilder = hierarchyBuilder; 46 | this.isZoomingAllowed = isZoomingAllowed; 47 | } 48 | 49 | @Override 50 | public ImageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 51 | ZoomableDraweeView drawee = new ZoomableDraweeView(context); 52 | drawee.setEnabled(isZoomingAllowed); 53 | 54 | ImageViewHolder holder = new ImageViewHolder(drawee); 55 | holders.add(holder); 56 | 57 | return holder; 58 | } 59 | 60 | @Override 61 | public void onBindViewHolder(ImageViewHolder holder, int position) { 62 | holder.bind(position); 63 | } 64 | 65 | @Override 66 | public int getItemCount() { 67 | return dataSet.getData().size(); 68 | } 69 | 70 | 71 | boolean isScaled(int index) { 72 | for (ImageViewHolder holder : holders) { 73 | if (holder.position == index) { 74 | return holder.isScaled; 75 | } 76 | } 77 | return false; 78 | } 79 | 80 | void resetScale(int index) { 81 | for (ImageViewHolder holder : holders) { 82 | if (holder.position == index) { 83 | holder.resetScale(); 84 | break; 85 | } 86 | } 87 | } 88 | 89 | String getUrl(int index) { 90 | return dataSet.format(index); 91 | } 92 | 93 | private BaseControllerListener 94 | getDraweeControllerListener(final ZoomableDraweeView drawee) { 95 | return new BaseControllerListener() { 96 | @Override 97 | public void onFinalImageSet(String id, ImageInfo imageInfo, Animatable animatable) { 98 | super.onFinalImageSet(id, imageInfo, animatable); 99 | if (imageInfo == null) { 100 | return; 101 | } 102 | drawee.update(imageInfo.getWidth(), imageInfo.getHeight()); 103 | } 104 | }; 105 | } 106 | 107 | class ImageViewHolder extends ViewHolder implements OnScaleChangeListener { 108 | 109 | private int position = -1; 110 | private ZoomableDraweeView drawee; 111 | private boolean isScaled; 112 | 113 | ImageViewHolder(View itemView) { 114 | super(itemView); 115 | drawee = (ZoomableDraweeView) itemView; 116 | } 117 | 118 | void bind(int position) { 119 | this.position = position; 120 | 121 | tryToSetHierarchy(); 122 | setController(dataSet.format(position)); 123 | 124 | drawee.setOnScaleChangeListener(this); 125 | } 126 | 127 | @Override 128 | public void onScaleChange(float scaleFactor, float focusX, float focusY) { 129 | isScaled = drawee.getScale() > 1.0f; 130 | } 131 | 132 | void resetScale() { 133 | drawee.setScale(1.0f, true); 134 | } 135 | 136 | private void tryToSetHierarchy() { 137 | if (hierarchyBuilder != null) { 138 | hierarchyBuilder.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER); 139 | drawee.setHierarchy(hierarchyBuilder.build()); 140 | } 141 | } 142 | 143 | private void setController(String url) { 144 | PipelineDraweeControllerBuilder controllerBuilder = Fresco.newDraweeControllerBuilder(); 145 | controllerBuilder.setUri(url); 146 | controllerBuilder.setOldController(drawee.getController()); 147 | controllerBuilder.setControllerListener(getDraweeControllerListener(drawee)); 148 | if (imageRequestBuilder != null) { 149 | imageRequestBuilder.setSource(Uri.parse(url)); 150 | controllerBuilder.setImageRequest(imageRequestBuilder.build()); 151 | } 152 | drawee.setController(controllerBuilder.build()); 153 | } 154 | 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /frescoimageviewer/src/main/java/com/stfalcon/frescoimageviewer/ImageViewerView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 stfalcon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.stfalcon.frescoimageviewer; 18 | 19 | import android.content.Context; 20 | import android.support.v4.view.GestureDetectorCompat; 21 | import android.support.v4.view.ViewPager; 22 | import android.util.AttributeSet; 23 | import android.view.GestureDetector; 24 | import android.view.MotionEvent; 25 | import android.view.ScaleGestureDetector; 26 | import android.view.View; 27 | import android.view.ViewGroup; 28 | import android.widget.RelativeLayout; 29 | 30 | import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; 31 | import com.facebook.imagepipeline.request.ImageRequestBuilder; 32 | 33 | /* 34 | * Created by Alexander Krol (troy379) on 29.08.16. 35 | */ 36 | class ImageViewerView extends RelativeLayout 37 | implements OnDismissListener, SwipeToDismissListener.OnViewMoveListener { 38 | 39 | private View backgroundView; 40 | private MultiTouchViewPager pager; 41 | private ImageViewerAdapter adapter; 42 | private SwipeDirectionDetector directionDetector; 43 | private ScaleGestureDetector scaleDetector; 44 | private ViewPager.OnPageChangeListener pageChangeListener; 45 | private GestureDetectorCompat gestureDetector; 46 | 47 | private ViewGroup dismissContainer; 48 | private SwipeToDismissListener swipeDismissListener; 49 | private View overlayView; 50 | 51 | private SwipeDirectionDetector.Direction direction; 52 | 53 | private ImageRequestBuilder customImageRequestBuilder; 54 | private GenericDraweeHierarchyBuilder customDraweeHierarchyBuilder; 55 | 56 | private boolean wasScaled; 57 | private OnDismissListener onDismissListener; 58 | private boolean isOverlayWasClicked; 59 | 60 | private boolean isZoomingAllowed = true; 61 | private boolean isSwipeToDismissAllowed = true; 62 | 63 | public ImageViewerView(Context context) { 64 | super(context); 65 | init(); 66 | } 67 | 68 | public ImageViewerView(Context context, AttributeSet attrs) { 69 | super(context, attrs); 70 | init(); 71 | } 72 | 73 | public ImageViewerView(Context context, AttributeSet attrs, int defStyleAttr) { 74 | super(context, attrs, defStyleAttr); 75 | init(); 76 | } 77 | 78 | public void setUrls(ImageViewer.DataSet dataSet, int startPosition) { 79 | adapter = new ImageViewerAdapter( 80 | getContext(), dataSet, customImageRequestBuilder, customDraweeHierarchyBuilder, isZoomingAllowed); 81 | pager.setAdapter(adapter); 82 | setStartPosition(startPosition); 83 | } 84 | 85 | public void setCustomImageRequestBuilder(ImageRequestBuilder customImageRequestBuilder) { 86 | this.customImageRequestBuilder = customImageRequestBuilder; 87 | } 88 | 89 | public void setCustomDraweeHierarchyBuilder(GenericDraweeHierarchyBuilder customDraweeHierarchyBuilder) { 90 | this.customDraweeHierarchyBuilder = customDraweeHierarchyBuilder; 91 | } 92 | 93 | @Override 94 | public void setBackgroundColor(int color) { 95 | findViewById(R.id.backgroundView) 96 | .setBackgroundColor(color); 97 | } 98 | 99 | public void setOverlayView(View view) { 100 | this.overlayView = view; 101 | if (overlayView != null) { 102 | dismissContainer.addView(view); 103 | } 104 | } 105 | 106 | public void allowZooming(boolean allowZooming) { 107 | this.isZoomingAllowed = allowZooming; 108 | } 109 | 110 | public void allowSwipeToDismiss(boolean allowSwipeToDismiss) { 111 | this.isSwipeToDismissAllowed = allowSwipeToDismiss; 112 | } 113 | 114 | public void setImageMargin(int marginPixels) { 115 | pager.setPageMargin(marginPixels); 116 | } 117 | 118 | public void setContainerPadding(int[] paddingPixels) { 119 | pager.setPadding( 120 | paddingPixels[0], 121 | paddingPixels[1], 122 | paddingPixels[2], 123 | paddingPixels[3]); 124 | } 125 | 126 | private void init() { 127 | inflate(getContext(), R.layout.image_viewer, this); 128 | 129 | backgroundView = findViewById(R.id.backgroundView); 130 | pager = (MultiTouchViewPager) findViewById(R.id.pager); 131 | 132 | dismissContainer = (ViewGroup) findViewById(R.id.container); 133 | swipeDismissListener = new SwipeToDismissListener(findViewById(R.id.dismissView), this, this); 134 | dismissContainer.setOnTouchListener(swipeDismissListener); 135 | 136 | directionDetector = new SwipeDirectionDetector(getContext()) { 137 | @Override 138 | public void onDirectionDetected(Direction direction) { 139 | ImageViewerView.this.direction = direction; 140 | } 141 | }; 142 | 143 | scaleDetector = new ScaleGestureDetector(getContext(), 144 | new ScaleGestureDetector.SimpleOnScaleGestureListener()); 145 | 146 | gestureDetector = new GestureDetectorCompat(getContext(), new GestureDetector.SimpleOnGestureListener() { 147 | @Override 148 | public boolean onSingleTapConfirmed(MotionEvent e) { 149 | if (pager.isScrolled()) { 150 | onClick(e, isOverlayWasClicked); 151 | } 152 | return false; 153 | } 154 | }); 155 | } 156 | 157 | @Override 158 | public boolean dispatchTouchEvent(MotionEvent event) { 159 | onUpDownEvent(event); 160 | 161 | if (direction == null) { 162 | if (scaleDetector.isInProgress() || event.getPointerCount() > 1) { 163 | wasScaled = true; 164 | return pager.dispatchTouchEvent(event); 165 | } 166 | } 167 | 168 | if (!adapter.isScaled(pager.getCurrentItem())) { 169 | directionDetector.onTouchEvent(event); 170 | if (direction != null) { 171 | switch (direction) { 172 | case UP: 173 | case DOWN: 174 | if (isSwipeToDismissAllowed && !wasScaled && pager.isScrolled()) { 175 | return swipeDismissListener.onTouch(dismissContainer, event); 176 | } else break; 177 | case LEFT: 178 | case RIGHT: 179 | return pager.dispatchTouchEvent(event); 180 | } 181 | } 182 | return true; 183 | } 184 | return super.dispatchTouchEvent(event); 185 | } 186 | 187 | @Override 188 | public void onDismiss() { 189 | if (onDismissListener != null) { 190 | onDismissListener.onDismiss(); 191 | } 192 | } 193 | 194 | @Override 195 | public void onViewMove(float translationY, int translationLimit) { 196 | float alpha = 1.0f - (1.0f / translationLimit / 4) * Math.abs(translationY); 197 | backgroundView.setAlpha(alpha); 198 | if (overlayView != null) overlayView.setAlpha(alpha); 199 | } 200 | 201 | public void setOnDismissListener(OnDismissListener onDismissListener) { 202 | this.onDismissListener = onDismissListener; 203 | } 204 | 205 | public void resetScale() { 206 | adapter.resetScale(pager.getCurrentItem()); 207 | } 208 | 209 | public boolean isScaled() { 210 | return adapter.isScaled(pager.getCurrentItem()); 211 | } 212 | 213 | public String getUrl() { 214 | return adapter.getUrl(pager.getCurrentItem()); 215 | } 216 | 217 | public void setPageChangeListener(ViewPager.OnPageChangeListener pageChangeListener) { 218 | pager.removeOnPageChangeListener(this.pageChangeListener); 219 | this.pageChangeListener = pageChangeListener; 220 | pager.addOnPageChangeListener(pageChangeListener); 221 | pageChangeListener.onPageSelected(pager.getCurrentItem()); 222 | } 223 | 224 | private void setStartPosition(int position) { 225 | pager.setCurrentItem(position); 226 | } 227 | 228 | private void onUpDownEvent(MotionEvent event) { 229 | if (event.getAction() == MotionEvent.ACTION_UP) { 230 | onActionUp(event); 231 | } 232 | 233 | if (event.getAction() == MotionEvent.ACTION_DOWN) { 234 | onActionDown(event); 235 | } 236 | 237 | scaleDetector.onTouchEvent(event); 238 | gestureDetector.onTouchEvent(event); 239 | } 240 | 241 | private void onActionDown(MotionEvent event) { 242 | direction = null; 243 | wasScaled = false; 244 | pager.dispatchTouchEvent(event); 245 | swipeDismissListener.onTouch(dismissContainer, event); 246 | isOverlayWasClicked = dispatchOverlayTouch(event); 247 | } 248 | 249 | private void onActionUp(MotionEvent event) { 250 | swipeDismissListener.onTouch(dismissContainer, event); 251 | pager.dispatchTouchEvent(event); 252 | isOverlayWasClicked = dispatchOverlayTouch(event); 253 | } 254 | 255 | private void onClick(MotionEvent event, boolean isOverlayWasClicked) { 256 | if (overlayView != null && !isOverlayWasClicked) { 257 | AnimationUtils.animateVisibility(overlayView); 258 | super.dispatchTouchEvent(event); 259 | } 260 | } 261 | 262 | private boolean dispatchOverlayTouch(MotionEvent event) { 263 | return overlayView != null 264 | && overlayView.getVisibility() == VISIBLE 265 | && overlayView.dispatchTouchEvent(event); 266 | } 267 | 268 | } 269 | -------------------------------------------------------------------------------- /frescoimageviewer/src/main/java/com/stfalcon/frescoimageviewer/MultiTouchViewPager.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 stfalcon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.stfalcon.frescoimageviewer; 18 | 19 | import android.content.Context; 20 | import android.support.annotation.NonNull; 21 | import android.support.v4.view.ViewPager; 22 | import android.util.AttributeSet; 23 | import android.view.MotionEvent; 24 | 25 | /* 26 | * Created by Alexander Krol (troy379) on 29.08.16. 27 | */ 28 | class MultiTouchViewPager extends ViewPager { 29 | 30 | private boolean isDisallowIntercept, isScrolled = true; 31 | 32 | public MultiTouchViewPager(Context context) { 33 | super(context); 34 | setScrollStateListener(); 35 | } 36 | 37 | public MultiTouchViewPager(Context context, AttributeSet attrs) { 38 | super(context, attrs); 39 | setScrollStateListener(); 40 | } 41 | 42 | @Override 43 | public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { 44 | isDisallowIntercept = disallowIntercept; 45 | super.requestDisallowInterceptTouchEvent(disallowIntercept); 46 | } 47 | 48 | @Override 49 | public boolean dispatchTouchEvent(@NonNull MotionEvent ev) { 50 | if (ev.getPointerCount() > 1 && isDisallowIntercept) { 51 | requestDisallowInterceptTouchEvent(false); 52 | boolean handled = super.dispatchTouchEvent(ev); 53 | requestDisallowInterceptTouchEvent(true); 54 | return handled; 55 | } else { 56 | return super.dispatchTouchEvent(ev); 57 | } 58 | } 59 | 60 | @Override 61 | public boolean onInterceptTouchEvent(MotionEvent ev) { 62 | if (ev.getPointerCount() > 1) { 63 | return false; 64 | } else { 65 | try { 66 | return super.onInterceptTouchEvent(ev); 67 | } catch (IllegalArgumentException ex) { 68 | return false; 69 | } 70 | } 71 | } 72 | 73 | @Override 74 | public boolean onTouchEvent(MotionEvent ev) { 75 | try { 76 | return super.onTouchEvent(ev); 77 | } catch (IllegalArgumentException ex) { 78 | return false; 79 | } 80 | } 81 | 82 | public boolean isScrolled() { 83 | return isScrolled; 84 | } 85 | 86 | private void setScrollStateListener() { 87 | addOnPageChangeListener(new SimpleOnPageChangeListener() { 88 | @Override 89 | public void onPageScrollStateChanged(int state) { 90 | isScrolled = state == SCROLL_STATE_IDLE; 91 | } 92 | }); 93 | } 94 | } -------------------------------------------------------------------------------- /frescoimageviewer/src/main/java/com/stfalcon/frescoimageviewer/OnDismissListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 stfalcon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.stfalcon.frescoimageviewer; 18 | 19 | /* 20 | * Created by Alexander Krol (troy379) on 29.08.16. 21 | */ 22 | interface OnDismissListener { 23 | 24 | void onDismiss(); 25 | } 26 | -------------------------------------------------------------------------------- /frescoimageviewer/src/main/java/com/stfalcon/frescoimageviewer/SwipeDirectionDetector.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 stfalcon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.stfalcon.frescoimageviewer; 18 | 19 | import android.content.Context; 20 | import android.view.MotionEvent; 21 | import android.view.ViewConfiguration; 22 | 23 | /* 24 | * Created by Alexander Krol (troy379) on 29.08.16. 25 | */ 26 | abstract class SwipeDirectionDetector { 27 | 28 | public abstract void onDirectionDetected(Direction direction); 29 | 30 | private int touchSlop; 31 | private float startX, startY; 32 | private boolean isDetected; 33 | 34 | public SwipeDirectionDetector(Context context) { 35 | this.touchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 36 | } 37 | 38 | public boolean onTouchEvent(MotionEvent event) { 39 | switch (event.getAction()) { 40 | case MotionEvent.ACTION_DOWN: 41 | startX = event.getX(); 42 | startY = event.getY(); 43 | break; 44 | case MotionEvent.ACTION_CANCEL: 45 | case MotionEvent.ACTION_UP: 46 | if (!isDetected) { 47 | onDirectionDetected(Direction.NOT_DETECTED); 48 | } 49 | startX = startY = 0.0f; 50 | isDetected = false; 51 | break; 52 | case MotionEvent.ACTION_MOVE: 53 | if (!isDetected && getDistance(event) > touchSlop) { 54 | isDetected = true; 55 | float x = event.getX(); 56 | float y = event.getY(); 57 | 58 | Direction direction = getDirection(startX, startY, x, y); 59 | onDirectionDetected(direction); 60 | } 61 | break; 62 | } 63 | return false; 64 | } 65 | 66 | /** 67 | * Given two points in the plane p1=(x1, x2) and p2=(y1, y1), this method 68 | * returns the direction that an arrow pointing from p1 to p2 would have. 69 | * 70 | * @param x1 the x position of the first point 71 | * @param y1 the y position of the first point 72 | * @param x2 the x position of the second point 73 | * @param y2 the y position of the second point 74 | * @return the direction 75 | */ 76 | public Direction getDirection(float x1, float y1, float x2, float y2) { 77 | double angle = getAngle(x1, y1, x2, y2); 78 | return Direction.get(angle); 79 | } 80 | 81 | /** 82 | * Finds the angle between two points in the plane (x1,y1) and (x2, y2) 83 | * The angle is measured with 0/360 being the X-axis to the right, angles 84 | * increase counter clockwise. 85 | * 86 | * @param x1 the x position of the first point 87 | * @param y1 the y position of the first point 88 | * @param x2 the x position of the second point 89 | * @param y2 the y position of the second point 90 | * @return the angle between two points 91 | */ 92 | public double getAngle(float x1, float y1, float x2, float y2) { 93 | double rad = Math.atan2(y1 - y2, x2 - x1) + Math.PI; 94 | return (rad * 180 / Math.PI + 180) % 360; 95 | } 96 | 97 | private float getDistance(MotionEvent ev) { 98 | float distanceSum = 0; 99 | 100 | float dx = (ev.getX(0) - startX); 101 | float dy = (ev.getY(0) - startY); 102 | distanceSum += Math.sqrt(dx * dx + dy * dy); 103 | 104 | return distanceSum; 105 | } 106 | 107 | public enum Direction { 108 | NOT_DETECTED, 109 | UP, 110 | DOWN, 111 | LEFT, 112 | RIGHT; 113 | 114 | public static Direction get(double angle) { 115 | if (inRange(angle, 45, 135)) { 116 | return Direction.UP; 117 | } else if (inRange(angle, 0, 45) || inRange(angle, 315, 360)) { 118 | return Direction.RIGHT; 119 | } else if (inRange(angle, 225, 315)) { 120 | return Direction.DOWN; 121 | } else { 122 | return Direction.LEFT; 123 | } 124 | } 125 | 126 | private static boolean inRange(double angle, float init, float end) { 127 | return (angle >= init) && (angle < end); 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /frescoimageviewer/src/main/java/com/stfalcon/frescoimageviewer/SwipeToDismissListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 stfalcon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.stfalcon.frescoimageviewer; 18 | 19 | import android.animation.Animator; 20 | import android.animation.AnimatorListenerAdapter; 21 | import android.animation.ObjectAnimator; 22 | import android.animation.ValueAnimator; 23 | import android.graphics.Rect; 24 | import android.view.MotionEvent; 25 | import android.view.View; 26 | import android.view.animation.AccelerateInterpolator; 27 | 28 | /* 29 | * Created by Alexander Krol (troy379) on 29.08.16. 30 | */ 31 | class SwipeToDismissListener implements View.OnTouchListener { 32 | 33 | private static final String PROPERTY_TRANSLATION_X = "translationY"; 34 | 35 | private final View swipeView; 36 | private int translationLimit; 37 | private OnDismissListener dismissListener; 38 | private OnViewMoveListener moveListener; 39 | 40 | public SwipeToDismissListener(View swipeView, OnDismissListener dismissListener, 41 | OnViewMoveListener moveListener) { 42 | this.swipeView = swipeView; 43 | this.dismissListener = dismissListener; 44 | this.moveListener = moveListener; 45 | } 46 | 47 | private boolean tracking = false; 48 | private float startY; 49 | 50 | @Override 51 | public boolean onTouch(View v, MotionEvent event) { 52 | translationLimit = v.getHeight() / 4; 53 | switch (event.getAction()) { 54 | case MotionEvent.ACTION_DOWN: 55 | Rect hitRect = new Rect(); 56 | swipeView.getHitRect(hitRect); 57 | if (hitRect.contains((int) event.getX(), (int) event.getY())) { 58 | tracking = true; 59 | } 60 | startY = event.getY(); 61 | return true; 62 | case MotionEvent.ACTION_UP: 63 | case MotionEvent.ACTION_CANCEL: 64 | if (tracking) { 65 | tracking = false; 66 | animateSwipeView(v.getHeight()); 67 | } 68 | return true; 69 | case MotionEvent.ACTION_MOVE: 70 | if (tracking) { 71 | float translationY = event.getY() - startY; 72 | swipeView.setTranslationY(translationY); 73 | callMoveListener(translationY, translationLimit); 74 | } 75 | return true; 76 | } 77 | return false; 78 | } 79 | 80 | private void animateSwipeView(int parentHeight) { 81 | float currentPosition = swipeView.getTranslationY(); 82 | float animateTo = 0.0f; 83 | 84 | if (currentPosition < -translationLimit) { 85 | animateTo = -parentHeight; 86 | } else if (currentPosition > translationLimit) { 87 | animateTo = parentHeight; 88 | } 89 | 90 | final boolean isDismissed = animateTo != 0.0f; 91 | ObjectAnimator animator = ObjectAnimator.ofFloat( 92 | swipeView, PROPERTY_TRANSLATION_X, currentPosition, animateTo); 93 | 94 | animator.setDuration(200); 95 | animator.setInterpolator(new AccelerateInterpolator()); 96 | animator.addListener( 97 | new AnimatorListenerAdapter() { 98 | @Override 99 | public void onAnimationEnd(Animator animation) { 100 | super.onAnimationEnd(animation); 101 | if (isDismissed) callDismissListener(); 102 | } 103 | }); 104 | animator.addUpdateListener( 105 | new ValueAnimator.AnimatorUpdateListener() { 106 | @Override 107 | public void onAnimationUpdate(ValueAnimator animation) { 108 | callMoveListener((float) animation.getAnimatedValue(), translationLimit); 109 | } 110 | }); 111 | animator.start(); 112 | } 113 | 114 | private void callDismissListener() { 115 | if (dismissListener != null) { 116 | dismissListener.onDismiss(); 117 | } 118 | } 119 | 120 | private void callMoveListener(float translationY, int translationLimit) { 121 | if (moveListener != null) { 122 | moveListener.onViewMove(translationY, translationLimit); 123 | } 124 | } 125 | 126 | interface OnViewMoveListener { 127 | void onViewMove(float translationY, int translationLimit); 128 | } 129 | } -------------------------------------------------------------------------------- /frescoimageviewer/src/main/java/com/stfalcon/frescoimageviewer/adapter/RecyclingPagerAdapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 "Henry Tao " 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.stfalcon.frescoimageviewer.adapter; 18 | 19 | import android.os.Bundle; 20 | import android.os.Parcelable; 21 | import android.support.v4.view.PagerAdapter; 22 | import android.util.SparseArray; 23 | import android.view.View; 24 | import android.view.ViewGroup; 25 | 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | /* 30 | * Created by henrytao on 11/13/15. 31 | */ 32 | public abstract class RecyclingPagerAdapter extends PagerAdapter { 33 | 34 | public abstract int getItemCount(); 35 | 36 | public abstract void onBindViewHolder(VH holder, int position); 37 | 38 | public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); 39 | 40 | private static final String STATE = RecyclingPagerAdapter.class.getSimpleName(); 41 | 42 | private static final String TAG = RecyclingPagerAdapter.class.getSimpleName(); 43 | 44 | public static boolean DEBUG = false; 45 | 46 | private SparseArray mRecycleTypeCaches = new SparseArray<>(); 47 | 48 | private SparseArray mSavedStates = new SparseArray<>(); 49 | 50 | public RecyclingPagerAdapter() { 51 | } 52 | 53 | @Override 54 | public void destroyItem(ViewGroup parent, int position, Object object) { 55 | if (object instanceof ViewHolder) { 56 | ((ViewHolder) object).detach(parent); 57 | } 58 | } 59 | 60 | @Override 61 | public int getCount() { 62 | return getItemCount(); 63 | } 64 | 65 | @Override 66 | public int getItemPosition(Object object) { 67 | return POSITION_NONE; 68 | } 69 | 70 | @SuppressWarnings("unchecked") 71 | @Override 72 | public Object instantiateItem(ViewGroup parent, int position) { 73 | int viewType = getItemViewType(position); 74 | if (mRecycleTypeCaches.get(viewType) == null) { 75 | mRecycleTypeCaches.put(viewType, new RecycleCache(this)); 76 | } 77 | ViewHolder viewHolder = mRecycleTypeCaches.get(viewType).getFreeViewHolder(parent, viewType); 78 | viewHolder.attach(parent, position); 79 | onBindViewHolder((VH) viewHolder, position); 80 | viewHolder.onRestoreInstanceState(mSavedStates.get(getItemId(position))); 81 | return viewHolder; 82 | } 83 | 84 | @Override 85 | public boolean isViewFromObject(View view, Object object) { 86 | return object instanceof ViewHolder && ((ViewHolder) object).itemView == view; 87 | } 88 | 89 | @Override 90 | public void notifyDataSetChanged() { 91 | super.notifyDataSetChanged(); 92 | for (ViewHolder viewHolder : getAttachedViewHolders()) { 93 | onNotifyItemChanged(viewHolder); 94 | } 95 | } 96 | 97 | @Override 98 | public void restoreState(Parcelable state, ClassLoader loader) { 99 | if (state instanceof Bundle) { 100 | Bundle bundle = (Bundle) state; 101 | bundle.setClassLoader(loader); 102 | SparseArray ss = bundle.containsKey(STATE) ? bundle.getSparseParcelableArray(STATE) : null; 103 | mSavedStates = ss != null ? ss : new SparseArray(); 104 | } 105 | super.restoreState(state, loader); 106 | } 107 | 108 | @Override 109 | public Parcelable saveState() { 110 | Bundle bundle = new Bundle(); 111 | for (ViewHolder viewHolder : getAttachedViewHolders()) { 112 | mSavedStates.put(getItemId(viewHolder.mPosition), viewHolder.onSaveInstanceState()); 113 | } 114 | bundle.putSparseParcelableArray(STATE, mSavedStates); 115 | return bundle; 116 | } 117 | 118 | public int getItemId(int position) { 119 | return position; 120 | } 121 | 122 | public int getItemViewType(int position) { 123 | return 0; 124 | } 125 | 126 | protected void onNotifyItemChanged(ViewHolder viewHolder) { 127 | } 128 | 129 | private List getAttachedViewHolders() { 130 | List attachedViewHolders = new ArrayList<>(); 131 | int n = mRecycleTypeCaches.size(); 132 | for (int i = 0; i < n; i++) { 133 | for (ViewHolder viewHolder : mRecycleTypeCaches.get(mRecycleTypeCaches.keyAt(i)).mCaches) { 134 | if (viewHolder.mIsAttached) { 135 | attachedViewHolders.add(viewHolder); 136 | } 137 | } 138 | } 139 | return attachedViewHolders; 140 | } 141 | 142 | private static class RecycleCache { 143 | 144 | private final RecyclingPagerAdapter mAdapter; 145 | 146 | private final List mCaches; 147 | 148 | RecycleCache(RecyclingPagerAdapter adapter) { 149 | mAdapter = adapter; 150 | mCaches = new ArrayList<>(); 151 | } 152 | 153 | ViewHolder getFreeViewHolder(ViewGroup parent, int viewType) { 154 | int i = 0; 155 | ViewHolder viewHolder; 156 | for (int n = mCaches.size(); i < n; i++) { 157 | viewHolder = mCaches.get(i); 158 | if (!viewHolder.mIsAttached) { 159 | return viewHolder; 160 | } 161 | } 162 | viewHolder = mAdapter.onCreateViewHolder(parent, viewType); 163 | mCaches.add(viewHolder); 164 | return viewHolder; 165 | } 166 | } 167 | } -------------------------------------------------------------------------------- /frescoimageviewer/src/main/java/com/stfalcon/frescoimageviewer/adapter/ViewHolder.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 "Henry Tao " 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.stfalcon.frescoimageviewer.adapter; 18 | 19 | import android.os.Bundle; 20 | import android.os.Parcelable; 21 | import android.util.SparseArray; 22 | import android.view.View; 23 | import android.view.ViewGroup; 24 | 25 | /* 26 | * Created by henrytao on 11/13/15. 27 | */ 28 | public abstract class ViewHolder { 29 | 30 | private static final String STATE = ViewHolder.class.getSimpleName(); 31 | 32 | public final View itemView; 33 | 34 | boolean mIsAttached; 35 | 36 | int mPosition; 37 | 38 | public ViewHolder(View itemView) { 39 | if (itemView == null) { 40 | throw new IllegalArgumentException("itemView should not be null"); 41 | } 42 | this.itemView = itemView; 43 | } 44 | 45 | void attach(ViewGroup parent, int position) { 46 | mIsAttached = true; 47 | mPosition = position; 48 | parent.addView(itemView); 49 | } 50 | 51 | void detach(ViewGroup parent) { 52 | parent.removeView(itemView); 53 | mIsAttached = false; 54 | } 55 | 56 | void onRestoreInstanceState(Parcelable state) { 57 | if (state instanceof Bundle) { 58 | Bundle bundle = (Bundle) state; 59 | SparseArray ss = bundle.containsKey(STATE) ? bundle.getSparseParcelableArray(STATE) : null; 60 | if (ss != null) { 61 | itemView.restoreHierarchyState(ss); 62 | } 63 | } 64 | } 65 | 66 | Parcelable onSaveInstanceState() { 67 | SparseArray state = new SparseArray<>(); 68 | itemView.saveHierarchyState(state); 69 | Bundle bundle = new Bundle(); 70 | bundle.putSparseParcelableArray(STATE, state); 71 | return bundle; 72 | } 73 | } -------------------------------------------------------------------------------- /frescoimageviewer/src/main/java/com/stfalcon/frescoimageviewer/drawee/NonInterceptableAttacher.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 stfalcon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.stfalcon.frescoimageviewer.drawee; 18 | 19 | import android.view.ViewParent; 20 | 21 | import com.facebook.drawee.generic.GenericDraweeHierarchy; 22 | import com.facebook.drawee.view.DraweeView; 23 | 24 | import me.relex.photodraweeview.Attacher; 25 | import me.relex.photodraweeview.OnScaleChangeListener; 26 | 27 | /* 28 | * Created by Alexander Krol (troy379) on 29.08.16. 29 | */ 30 | class NonInterceptableAttacher extends Attacher { 31 | 32 | private OnScaleChangeListener scaleChangeListener; 33 | 34 | public NonInterceptableAttacher(DraweeView draweeView) { 35 | super(draweeView); 36 | } 37 | 38 | @Override 39 | public void onDrag(float dx, float dy) { 40 | DraweeView draweeView = getDraweeView(); 41 | if (draweeView != null) { 42 | getDrawMatrix().postTranslate(dx, dy); 43 | checkMatrixAndInvalidate(); 44 | 45 | ViewParent parent = draweeView.getParent(); 46 | if (parent == null) { 47 | return; 48 | } 49 | 50 | if (getScale() == 1.0f) { 51 | parent.requestDisallowInterceptTouchEvent(false); 52 | } else { 53 | parent.requestDisallowInterceptTouchEvent(true); 54 | } 55 | } 56 | } 57 | 58 | @Override 59 | public void onDetachedFromWindow() { 60 | super.onDetachedFromWindow(); 61 | } 62 | 63 | @Override 64 | public void setOnScaleChangeListener(OnScaleChangeListener listener) { 65 | this.scaleChangeListener = listener; 66 | } 67 | 68 | @Override 69 | public void onScale(float scaleFactor, float focusX, float focusY) { 70 | super.onScale(scaleFactor, focusX, focusY); 71 | if (scaleChangeListener != null) { 72 | scaleChangeListener.onScaleChange(scaleFactor, focusX, focusY); 73 | } 74 | } 75 | } -------------------------------------------------------------------------------- /frescoimageviewer/src/main/java/com/stfalcon/frescoimageviewer/drawee/ZoomableDraweeView.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 stfalcon.com 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.stfalcon.frescoimageviewer.drawee; 18 | 19 | import android.content.Context; 20 | import android.graphics.Canvas; 21 | import android.support.annotation.NonNull; 22 | import android.util.AttributeSet; 23 | import android.view.GestureDetector; 24 | import android.view.MotionEvent; 25 | 26 | import com.facebook.drawee.generic.GenericDraweeHierarchy; 27 | import com.facebook.drawee.view.SimpleDraweeView; 28 | 29 | import me.relex.photodraweeview.IAttacher; 30 | import me.relex.photodraweeview.OnPhotoTapListener; 31 | import me.relex.photodraweeview.OnScaleChangeListener; 32 | import me.relex.photodraweeview.OnViewTapListener; 33 | 34 | /* 35 | * Created by Alexander Krol (troy379) on 29.08.16. 36 | */ 37 | public class ZoomableDraweeView extends SimpleDraweeView implements IAttacher { 38 | 39 | private NonInterceptableAttacher attacher; 40 | 41 | public ZoomableDraweeView(Context context, GenericDraweeHierarchy hierarchy) { 42 | super(context, hierarchy); 43 | init(); 44 | } 45 | 46 | public ZoomableDraweeView(Context context) { 47 | super(context); 48 | init(); 49 | } 50 | 51 | public ZoomableDraweeView(Context context, AttributeSet attrs) { 52 | super(context, attrs); 53 | init(); 54 | } 55 | 56 | public ZoomableDraweeView(Context context, AttributeSet attrs, int defStyle) { 57 | super(context, attrs, defStyle); 58 | init(); 59 | } 60 | 61 | protected void init() { 62 | if (attacher == null || attacher.getDraweeView() == null) { 63 | attacher = new NonInterceptableAttacher(this); 64 | } 65 | } 66 | 67 | @Override 68 | public boolean onTouchEvent(MotionEvent event) { 69 | 70 | return super.onTouchEvent(event); 71 | } 72 | 73 | @Override 74 | protected void onDraw(@NonNull Canvas canvas) { 75 | int saveCount = canvas.save(); 76 | canvas.concat(attacher.getDrawMatrix()); 77 | super.onDraw(canvas); 78 | canvas.restoreToCount(saveCount); 79 | } 80 | 81 | @Override 82 | protected void onAttachedToWindow() { 83 | init(); 84 | super.onAttachedToWindow(); 85 | } 86 | 87 | @Override 88 | protected void onDetachedFromWindow() { 89 | attacher.onDetachedFromWindow(); 90 | super.onDetachedFromWindow(); 91 | } 92 | 93 | @Override 94 | public float getMinimumScale() { 95 | return attacher.getMinimumScale(); 96 | } 97 | 98 | @Override 99 | public float getMediumScale() { 100 | return attacher.getMediumScale(); 101 | } 102 | 103 | @Override 104 | public float getMaximumScale() { 105 | return attacher.getMaximumScale(); 106 | } 107 | 108 | @Override 109 | public void setMinimumScale(float minimumScale) { 110 | attacher.setMinimumScale(minimumScale); 111 | } 112 | 113 | @Override 114 | public void setMediumScale(float mediumScale) { 115 | attacher.setMediumScale(mediumScale); 116 | } 117 | 118 | @Override 119 | public void setMaximumScale(float maximumScale) { 120 | attacher.setMaximumScale(maximumScale); 121 | } 122 | 123 | @Override 124 | public float getScale() { 125 | return attacher.getScale(); 126 | } 127 | 128 | @Override 129 | public void setScale(float scale) { 130 | attacher.setScale(scale); 131 | } 132 | 133 | @Override 134 | public void setScale(float scale, boolean animate) { 135 | setScale(scale, getRight() / 2, getBottom() / 2, animate); 136 | } 137 | 138 | @Override 139 | public void setScale(float scale, float focalX, float focalY, boolean animate) { 140 | attacher.setScale(scale, focalX, focalY, animate); 141 | } 142 | 143 | @Override 144 | public void setZoomTransitionDuration(long duration) { 145 | attacher.setZoomTransitionDuration(duration); 146 | } 147 | 148 | @Override 149 | public void setAllowParentInterceptOnEdge(boolean allow) { 150 | attacher.setAllowParentInterceptOnEdge(allow); 151 | } 152 | 153 | @Override 154 | public void setOnDoubleTapListener(GestureDetector.OnDoubleTapListener listener) { 155 | attacher.setOnDoubleTapListener(listener); 156 | } 157 | 158 | @Override 159 | public void setOnScaleChangeListener(OnScaleChangeListener listener) { 160 | attacher.setOnScaleChangeListener(listener); 161 | } 162 | 163 | @Override 164 | public void setOnLongClickListener(OnLongClickListener listener) { 165 | attacher.setOnLongClickListener(listener); 166 | } 167 | 168 | @Override 169 | public void setOnPhotoTapListener(OnPhotoTapListener listener) { 170 | attacher.setOnPhotoTapListener(listener); 171 | } 172 | 173 | @Override 174 | public void setOnViewTapListener(OnViewTapListener listener) { 175 | attacher.setOnViewTapListener(listener); 176 | } 177 | 178 | @Override 179 | public OnPhotoTapListener getOnPhotoTapListener() { 180 | return attacher.getOnPhotoTapListener(); 181 | } 182 | 183 | @Override 184 | public OnViewTapListener getOnViewTapListener() { 185 | return attacher.getOnViewTapListener(); 186 | } 187 | 188 | @Override 189 | public void update(int imageInfoWidth, int imageInfoHeight) { 190 | attacher.update(imageInfoWidth, imageInfoHeight); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /frescoimageviewer/src/main/res/layout/image_viewer.xml: -------------------------------------------------------------------------------- 1 | 16 | 17 | 21 | 22 | 26 | 27 | 31 | 32 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Tue Apr 11 09:58:14 EEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 158 | function splitJvmOpts() { 159 | JVM_OPTS=("$@") 160 | } 161 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 162 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 163 | 164 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 165 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | 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= 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 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /images/fresco_image_viewer_customizing_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/images/fresco_image_viewer_customizing_demo.gif -------------------------------------------------------------------------------- /images/fresco_image_viewer_demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/images/fresco_image_viewer_demo.gif -------------------------------------------------------------------------------- /images/posters/Daniel.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/images/posters/Daniel.jpg -------------------------------------------------------------------------------- /images/posters/Driver.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/images/posters/Driver.jpg -------------------------------------------------------------------------------- /images/posters/Frank.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/images/posters/Frank.jpg -------------------------------------------------------------------------------- /images/posters/Jules.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/images/posters/Jules.jpg -------------------------------------------------------------------------------- /images/posters/Korben.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/images/posters/Korben.jpg -------------------------------------------------------------------------------- /images/posters/Marty.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/images/posters/Marty.jpg -------------------------------------------------------------------------------- /images/posters/Max.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/images/posters/Max.jpg -------------------------------------------------------------------------------- /images/posters/Toretto.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/images/posters/Toretto.jpg -------------------------------------------------------------------------------- /images/posters/Vincent.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/images/posters/Vincent.jpg -------------------------------------------------------------------------------- /sample/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | 7 | defaultConfig { 8 | applicationId "com.stfalcon.frescoimageviewersample" 9 | minSdkVersion 15 10 | targetSdkVersion 25 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | renderscriptTargetApi 25 15 | renderscriptSupportModeEnabled true 16 | } 17 | buildTypes { 18 | release { 19 | minifyEnabled false 20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 21 | } 22 | } 23 | android { 24 | lintOptions { 25 | abortOnError false 26 | } 27 | } 28 | } 29 | 30 | ext { 31 | supportVersion = '25.1.1' 32 | frescoVersion = '1.2.0' 33 | } 34 | 35 | dependencies { 36 | compile project(':frescoimageviewer') 37 | compile "com.android.support:design:$supportVersion" 38 | compile "com.android.support:appcompat-v7:$supportVersion" 39 | compile "com.android.support:support-v4:$supportVersion" 40 | compile "com.android.support:cardview-v7:$supportVersion" 41 | 42 | compile "com.facebook.fresco:fresco:$frescoVersion" 43 | 44 | compile 'com.github.mmin18:realtimeblurview:1.1.0' 45 | compile 'me.relex:circleindicator:1.2.2@aar' 46 | 47 | compile 'jp.wasabeef:fresco-processors:2.0.0' 48 | compile 'jp.co.cyberagent.android.gpuimage:gpuimage-library:1.3.0' 49 | } 50 | -------------------------------------------------------------------------------- /sample/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /Users/troy379/Library/Android/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /sample/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 26 | 30 | 33 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /sample/src/main/java/com/stfalcon/frescoimageviewersample/App.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewersample; 2 | 3 | import android.app.Application; 4 | 5 | import com.facebook.drawee.backends.pipeline.Fresco; 6 | 7 | /* 8 | * Created by Alexander Krol (troy379) on 29.08.16. 9 | */ 10 | public class App extends Application { 11 | 12 | @Override 13 | public void onCreate() { 14 | super.onCreate(); 15 | 16 | /** 17 | * IMPORTANT! Enable the configuration below, if you expect to open really large images. 18 | * Also you can add the {@code android:largeHeap="true"} to Manifest file to avoid an OOM error.*/ 19 | // ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this) 20 | // .setProgressiveJpegConfig(new SimpleProgressiveJpegConfig()) 21 | // .setResizeAndRotateEnabledForNetwork(true) 22 | // .setDownsampleEnabled(true) 23 | // .build(); 24 | // Fresco.initialize(this, config); 25 | 26 | Fresco.initialize(this); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /sample/src/main/java/com/stfalcon/frescoimageviewersample/common/data/Demo.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewersample.common.data; 2 | 3 | import com.stfalcon.frescoimageviewersample.common.data.models.CustomImage; 4 | 5 | import java.util.ArrayList; 6 | import java.util.List; 7 | 8 | /* 9 | * Created by troy379 on 14.09.16. 10 | */ 11 | public final class Demo { 12 | private Demo() { 13 | throw new AssertionError(); 14 | } 15 | 16 | private static final String POSTERS_PATH = "https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/v.0.5.0/images/posters"; 17 | 18 | public static String[] getPosters() { 19 | return new String[]{ 20 | POSTERS_PATH + "/Vincent.jpg", 21 | POSTERS_PATH + "/Jules.jpg", 22 | POSTERS_PATH + "/Korben.jpg", 23 | POSTERS_PATH + "/Toretto.jpg", 24 | POSTERS_PATH + "/Marty.jpg", 25 | POSTERS_PATH + "/Driver.jpg", 26 | POSTERS_PATH + "/Frank.jpg", 27 | POSTERS_PATH + "/Max.jpg", 28 | POSTERS_PATH + "/Daniel.jpg" 29 | }; 30 | } 31 | 32 | public static String[] getDescriptions() { 33 | return new String[]{ 34 | "Vincent Vega is a hitman and associate of Marsellus Wallace. He had a brother named Vic Vega who was shot and killed by an undercover cop while on a job. He worked in Amsterdam for over three years and recently returned to Los Angeles, where he has been partnered with Jules Winnfield.", 35 | "Jules Winnfield - initially he is a Hitman working alongside Vincent Vega but after revelation, or as he refers to it \"a moment of clarity\" he decides to leave to \"Walk the Earth.\" During the film he is stated to be from Inglewood, California", 36 | "Korben Dallas. A post-America taxi driver in New York City with a grand military background simply lives his life day to day, that is, before he meets Leeloo. Leeloo captures his heart soon after crashing into his taxi cab one day after escaping from a government-run laboratory. Korben soon finds himself running from the authorities in order to protect Leeloo, as well as becoming the center of a desperate ploy to save the world from an unknown evil.", 37 | "Dominic \"Dom\" Toretto is the brother of Mia Toretto, uncle to Jack and husband to Letty Ortiz. The protagonist in The Fast and the Furious franchise, Dominic is an elite street racer and auto mechanic.", 38 | "Martin Seamus \"Marty\" McFly Sr. - he is the world's second time traveler, the first to travel backwards in time and the first human to travel though time. He was also a high school student at Hill Valley High School in 1985. He is best friends with Dr. Emmett Brown, who unveiled his first working invention to him.", 39 | "The Driver - real name unknown - is a quiet man who has made a career out of stealing fast cars and using them as getaway vehicles in big-time robberies all over Los Angeles. Hot on the Driver's trail is the Detective (Bruce Dern), a conceited (and similarly nameless) cop who refers to the Driver as \"Cowboy\".", 40 | "Frank Martin (Transporter) - he initially serves as a reluctant hero. He is portrayed as a former Special Forces operative who was a team leader of a search and destroy unit. His military background includes operations \"in and out of\" Lebanon, Syria and Sudan. He retires from this after becoming fatigued and disenchanted with his superior officers.", 41 | "Maximillian \"Max\" Rockatansky started his apocalyptic adventure as a Main Force Patrol officer who fought for peace on the decaying roads of Australian civilization. Max served as the last line of defense against the reckless marauders terrorizing the roadways, driving a V8 Interceptor.", 42 | "Daniel Morales - the fastest delivery man for the local pizza parlor Pizza Joe in Marseille, France. On the last day of work, he sets a new speed record, then leaves the job to pursue a new career as a taxi driver with the blessings of his boss and co-workers. Daniel's vehicle is a white 1997 Peugeot 406..." 43 | }; 44 | } 45 | 46 | public static List getCustomImages() { 47 | String[] posters = getPosters(); 48 | String[] descriptions = getDescriptions(); 49 | List images = new ArrayList<>(); 50 | 51 | for (int i = 0; i < posters.length; i++) { 52 | images.add(new CustomImage(posters[i], descriptions[i])); 53 | } 54 | 55 | return images; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /sample/src/main/java/com/stfalcon/frescoimageviewersample/common/data/models/CustomImage.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewersample.common.data.models; 2 | 3 | /* 4 | * Created by troy379 on 10.03.17. 5 | */ 6 | public class CustomImage { 7 | 8 | private String url; 9 | private String description; 10 | 11 | public CustomImage(String url, String description) { 12 | this.url = url; 13 | this.description = description; 14 | } 15 | 16 | public String getUrl() { 17 | return url; 18 | } 19 | 20 | public String getDescription() { 21 | return description; 22 | } 23 | } -------------------------------------------------------------------------------- /sample/src/main/java/com/stfalcon/frescoimageviewersample/common/views/ImageOverlayView.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewersample.common.views; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.widget.RelativeLayout; 8 | import android.widget.TextView; 9 | 10 | import com.stfalcon.frescoimageviewersample.R; 11 | 12 | /* 13 | * Created by Alexander Krol (troy379) on 29.08.16. 14 | */ 15 | public class ImageOverlayView extends RelativeLayout { 16 | 17 | private TextView tvDescription; 18 | 19 | private String sharingText; 20 | 21 | public ImageOverlayView(Context context) { 22 | super(context); 23 | init(); 24 | } 25 | 26 | public ImageOverlayView(Context context, AttributeSet attrs) { 27 | super(context, attrs); 28 | init(); 29 | } 30 | 31 | public ImageOverlayView(Context context, AttributeSet attrs, int defStyleAttr) { 32 | super(context, attrs, defStyleAttr); 33 | init(); 34 | } 35 | 36 | public void setDescription(String description) { 37 | tvDescription.setText(description); 38 | } 39 | 40 | public void setShareText(String text) { 41 | this.sharingText = text; 42 | } 43 | 44 | private void sendShareIntent() { 45 | Intent sendIntent = new Intent(); 46 | sendIntent.setAction(Intent.ACTION_SEND); 47 | sendIntent.putExtra(Intent.EXTRA_TEXT, sharingText); 48 | sendIntent.setType("text/plain"); 49 | getContext().startActivity(sendIntent); 50 | } 51 | 52 | private void init() { 53 | View view = inflate(getContext(), R.layout.view_image_overlay, this); 54 | tvDescription = (TextView) view.findViewById(R.id.tvDescription); 55 | view.findViewById(R.id.btnShare).setOnClickListener(new OnClickListener() { 56 | @Override 57 | public void onClick(View v) { 58 | sendShareIntent(); 59 | } 60 | }); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /sample/src/main/java/com/stfalcon/frescoimageviewersample/features/demo/DemoActivity.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewersample.features.demo; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.view.View; 6 | 7 | import com.facebook.drawee.view.SimpleDraweeView; 8 | import com.stfalcon.frescoimageviewersample.R; 9 | import com.stfalcon.frescoimageviewersample.common.data.Demo; 10 | 11 | /* 12 | * Created by troy379 on 06.03.17. 13 | */ 14 | public abstract class DemoActivity extends AppCompatActivity { 15 | 16 | private static final int[] ids = new int[]{ 17 | R.id.firstImage, R.id.secondImage, 18 | R.id.thirdImage, R.id.fourthImage, 19 | R.id.fifthImage, R.id.sixthImage, 20 | R.id.seventhImage, R.id.eighthImage, 21 | R.id.ninethImage 22 | }; 23 | 24 | protected String[] posters, descriptions; 25 | 26 | @Override 27 | protected void onCreate(Bundle savedInstanceState) { 28 | super.onCreate(savedInstanceState); 29 | setContentView(R.layout.activity_demo); 30 | 31 | init(); 32 | } 33 | 34 | protected void showPicker(int startPosition) { 35 | 36 | } 37 | 38 | protected void init() { 39 | posters = Demo.getPosters(); 40 | descriptions = Demo.getDescriptions(); 41 | 42 | for (int i = 0; i < ids.length; i++) { 43 | SimpleDraweeView drawee = (SimpleDraweeView) findViewById(ids[i]); 44 | initDrawee(drawee, i); 45 | } 46 | } 47 | 48 | private void initDrawee(SimpleDraweeView drawee, final int startPosition) { 49 | drawee.setOnClickListener(new View.OnClickListener() { 50 | @Override 51 | public void onClick(View v) { 52 | showPicker(startPosition); 53 | } 54 | }); 55 | drawee.setImageURI(posters[startPosition]); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /sample/src/main/java/com/stfalcon/frescoimageviewersample/features/demo/formatter/CustomObjectsActivity.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewersample.features.demo.formatter; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.stfalcon.frescoimageviewer.ImageViewer; 6 | import com.stfalcon.frescoimageviewersample.R; 7 | import com.stfalcon.frescoimageviewersample.common.data.Demo; 8 | import com.stfalcon.frescoimageviewersample.common.data.models.CustomImage; 9 | import com.stfalcon.frescoimageviewersample.common.views.ImageOverlayView; 10 | import com.stfalcon.frescoimageviewersample.features.demo.DemoActivity; 11 | import com.stfalcon.frescoimageviewersample.utils.AppUtils; 12 | 13 | import java.util.List; 14 | 15 | /* 16 | * Created by troy379 on 06.03.17. 17 | */ 18 | public class CustomObjectsActivity extends DemoActivity { 19 | 20 | private List images; 21 | private ImageOverlayView overlayView; 22 | 23 | @Override 24 | protected void onCreate(Bundle savedInstanceState) { 25 | super.onCreate(savedInstanceState); 26 | 27 | images = Demo.getCustomImages(); 28 | AppUtils.showGotItSnackbar(findViewById(R.id.coordinator), R.string.custom_objects_hint); 29 | } 30 | 31 | @Override 32 | protected void showPicker(int startPosition) { 33 | overlayView = new ImageOverlayView(this); 34 | new ImageViewer.Builder<>(this, images) 35 | .setFormatter(getCustomFormatter()) 36 | .setImageChangeListener(getImageChangeListener()) 37 | .setOverlayView(overlayView) 38 | .show(); 39 | } 40 | 41 | private ImageViewer.Formatter getCustomFormatter() { 42 | return new ImageViewer.Formatter() { 43 | @Override 44 | public String format(CustomImage customImage) { 45 | return customImage.getUrl(); 46 | } 47 | }; 48 | } 49 | 50 | private ImageViewer.OnImageChangeListener getImageChangeListener() { 51 | return new ImageViewer.OnImageChangeListener() { 52 | @Override 53 | public void onImageChange(int position) { 54 | CustomImage image = images.get(position); 55 | overlayView.setShareText(image.getUrl()); 56 | overlayView.setDescription(image.getDescription()); 57 | } 58 | }; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /sample/src/main/java/com/stfalcon/frescoimageviewersample/features/demo/rotation/DialogRotationExampleActivity.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewersample.features.demo.rotation; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.stfalcon.frescoimageviewer.ImageViewer; 6 | import com.stfalcon.frescoimageviewersample.R; 7 | import com.stfalcon.frescoimageviewersample.common.data.Demo; 8 | import com.stfalcon.frescoimageviewersample.features.demo.DemoActivity; 9 | import com.stfalcon.frescoimageviewersample.utils.AppUtils; 10 | 11 | public class DialogRotationExampleActivity extends DemoActivity { 12 | 13 | private static final String KEY_IS_DIALOG_SHOWN = "IS_DIALOG_SHOWN"; 14 | private static final String KEY_CURRENT_POSITION = "CURRENT_POSITION"; 15 | 16 | private boolean isDialogShown; 17 | private int currentPosition; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | 23 | AppUtils.showInfoSnackbar(findViewById(R.id.coordinator), 24 | R.string.message_rotate, true); 25 | } 26 | 27 | @Override 28 | protected void onRestoreInstanceState(Bundle savedInstanceState) { 29 | super.onRestoreInstanceState(savedInstanceState); 30 | if (savedInstanceState != null) { 31 | isDialogShown = savedInstanceState.getBoolean(KEY_IS_DIALOG_SHOWN); 32 | currentPosition = savedInstanceState.getInt(KEY_CURRENT_POSITION); 33 | } 34 | 35 | if (isDialogShown) { 36 | showPicker(currentPosition); 37 | } 38 | } 39 | 40 | @Override 41 | protected void onSaveInstanceState(Bundle outState) { 42 | outState.putBoolean(KEY_IS_DIALOG_SHOWN, isDialogShown); 43 | outState.putInt(KEY_CURRENT_POSITION, currentPosition); 44 | super.onSaveInstanceState(outState); 45 | } 46 | 47 | @Override 48 | protected void showPicker(int startPosition) { 49 | isDialogShown = true; 50 | currentPosition = startPosition; 51 | 52 | new ImageViewer.Builder<>(this, Demo.getPosters()) 53 | .setStartPosition(startPosition) 54 | .setImageChangeListener(getImageChangeListener()) 55 | .setOnDismissListener(getDismissListener()) 56 | .show(); 57 | } 58 | 59 | private ImageViewer.OnImageChangeListener getImageChangeListener() { 60 | return new ImageViewer.OnImageChangeListener() { 61 | @Override 62 | public void onImageChange(int position) { 63 | currentPosition = position; 64 | } 65 | }; 66 | } 67 | 68 | private ImageViewer.OnDismissListener getDismissListener() { 69 | return new ImageViewer.OnDismissListener() { 70 | @Override 71 | public void onDismiss() { 72 | isDialogShown = false; 73 | } 74 | }; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /sample/src/main/java/com/stfalcon/frescoimageviewersample/features/demo/simple/SimpleUsageActivity.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewersample.features.demo.simple; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.stfalcon.frescoimageviewer.ImageViewer; 6 | import com.stfalcon.frescoimageviewersample.R; 7 | import com.stfalcon.frescoimageviewersample.features.demo.DemoActivity; 8 | import com.stfalcon.frescoimageviewersample.utils.AppUtils; 9 | 10 | public class SimpleUsageActivity extends DemoActivity { 11 | 12 | @Override 13 | protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | 16 | AppUtils.showInfoSnackbar(findViewById(R.id.coordinator), 17 | R.string.message_open_viewer, true); 18 | } 19 | 20 | @Override 21 | protected void showPicker(int startPosition) { 22 | new ImageViewer.Builder<>(this, posters) 23 | .setStartPosition(startPosition) 24 | .show(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /sample/src/main/java/com/stfalcon/frescoimageviewersample/features/demo/styled/StyledViewActivity.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewersample.features.demo.styled; 2 | 3 | import android.graphics.Color; 4 | import android.os.Bundle; 5 | import android.view.Menu; 6 | import android.view.MenuItem; 7 | 8 | import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; 9 | import com.facebook.drawee.generic.RoundingParams; 10 | import com.stfalcon.frescoimageviewer.ImageViewer; 11 | import com.stfalcon.frescoimageviewersample.R; 12 | import com.stfalcon.frescoimageviewersample.common.views.ImageOverlayView; 13 | import com.stfalcon.frescoimageviewersample.features.demo.DemoActivity; 14 | import com.stfalcon.frescoimageviewersample.utils.AppUtils; 15 | import com.stfalcon.frescoimageviewersample.utils.StylingOptions; 16 | 17 | import java.util.Random; 18 | 19 | import jp.wasabeef.fresco.processors.GrayscalePostprocessor; 20 | 21 | /* 22 | * Created by troy379 on 06.03.17. 23 | */ 24 | public class StyledViewActivity extends DemoActivity { 25 | 26 | private ImageOverlayView overlayView; 27 | private StylingOptions options; 28 | 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | super.onCreate(savedInstanceState); 32 | options = new StylingOptions(); 33 | 34 | AppUtils.showGotItSnackbar(findViewById(R.id.coordinator), 35 | R.string.message_styling); 36 | } 37 | 38 | @Override 39 | public boolean onCreateOptionsMenu(Menu menu) { 40 | getMenuInflater().inflate(R.menu.styling_options_menu, menu); 41 | return super.onCreateOptionsMenu(menu); 42 | } 43 | 44 | @Override 45 | public boolean onOptionsItemSelected(MenuItem item) { 46 | options.showDialog(this); 47 | return super.onOptionsItemSelected(item); 48 | } 49 | 50 | @Override 51 | protected void showPicker(int startPosition) { 52 | ImageViewer.Builder builder = new ImageViewer.Builder<>(this, posters) 53 | .setStartPosition(startPosition) 54 | .setOnDismissListener(getDismissListener()); 55 | 56 | builder.hideStatusBar(options.get(StylingOptions.Property.HIDE_STATUS_BAR)); 57 | 58 | if (options.get(StylingOptions.Property.IMAGE_MARGIN)) { 59 | builder.setImageMargin(this, R.dimen.image_margin); 60 | } 61 | 62 | if (options.get(StylingOptions.Property.CONTAINER_PADDING)) { 63 | builder.setContainerPadding(this, R.dimen.image_margin); 64 | } 65 | 66 | if (options.get(StylingOptions.Property.IMAGES_ROUNDING)) { 67 | builder.setCustomDraweeHierarchyBuilder(getRoundedHierarchyBuilder()); 68 | } 69 | 70 | builder.allowSwipeToDismiss(options.get(StylingOptions.Property.SWIPE_TO_DISMISS)); 71 | 72 | builder.allowZooming(options.get(StylingOptions.Property.ZOOMING)); 73 | 74 | if (options.get(StylingOptions.Property.SHOW_OVERLAY)) { 75 | overlayView = new ImageOverlayView(this); 76 | builder.setOverlayView(overlayView); 77 | builder.setImageChangeListener(getImageChangeListener()); 78 | } 79 | 80 | if (options.get(StylingOptions.Property.RANDOM_BACKGROUND)) { 81 | builder.setBackgroundColor(getRandomColor()); 82 | } 83 | 84 | if (options.get(StylingOptions.Property.POST_PROCESSING)) { 85 | builder.setCustomImageRequestBuilder( 86 | ImageViewer.createImageRequestBuilder() 87 | .setPostprocessor(new GrayscalePostprocessor())); 88 | } 89 | 90 | builder.show(); 91 | } 92 | 93 | private ImageViewer.OnImageChangeListener getImageChangeListener() { 94 | return new ImageViewer.OnImageChangeListener() { 95 | @Override 96 | public void onImageChange(int position) { 97 | String url = posters[position]; 98 | overlayView.setShareText(url); 99 | overlayView.setDescription(descriptions[position]); 100 | } 101 | }; 102 | } 103 | 104 | private ImageViewer.OnDismissListener getDismissListener() { 105 | return new ImageViewer.OnDismissListener() { 106 | @Override 107 | public void onDismiss() { 108 | AppUtils.showInfoSnackbar(findViewById(R.id.coordinator), 109 | R.string.message_on_dismiss, false); 110 | } 111 | }; 112 | } 113 | 114 | private GenericDraweeHierarchyBuilder getRoundedHierarchyBuilder() { 115 | RoundingParams roundingParams = new RoundingParams(); 116 | roundingParams.setRoundAsCircle(true); 117 | 118 | return GenericDraweeHierarchyBuilder.newInstance(getResources()) 119 | .setRoundingParams(roundingParams); 120 | } 121 | 122 | private int getRandomColor() { 123 | Random random = new Random(); 124 | return Color.argb(255, random.nextInt(156), random.nextInt(156), random.nextInt(156)); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /sample/src/main/java/com/stfalcon/frescoimageviewersample/features/main/DemoCardFragment.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewersample.features.main; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.v4.app.Fragment; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.Button; 10 | import android.widget.TextView; 11 | 12 | import com.stfalcon.frescoimageviewersample.R; 13 | 14 | public class DemoCardFragment extends Fragment 15 | implements View.OnClickListener { 16 | 17 | private static final String ARG_ID = "id"; 18 | private static final String ARG_TITLE = "title"; 19 | private static final String ARG_DESCRIPTION = "description"; 20 | 21 | private int id; 22 | private String title, description; 23 | private OnActionListener actionListener; 24 | 25 | public DemoCardFragment() { 26 | 27 | } 28 | 29 | public static DemoCardFragment newInstance(int id, String title, String description) { 30 | DemoCardFragment fragment = new DemoCardFragment(); 31 | Bundle args = new Bundle(); 32 | args.putInt(ARG_ID, id); 33 | args.putString(ARG_TITLE, title); 34 | args.putString(ARG_DESCRIPTION, description); 35 | fragment.setArguments(args); 36 | return fragment; 37 | } 38 | 39 | @Override 40 | public void onCreate(Bundle savedInstanceState) { 41 | super.onCreate(savedInstanceState); 42 | if (getArguments() != null) { 43 | this.id = getArguments().getInt(ARG_ID); 44 | this.title = getArguments().getString(ARG_TITLE); 45 | this.description = getArguments().getString(ARG_DESCRIPTION); 46 | } 47 | } 48 | 49 | @Override 50 | public View onCreateView(LayoutInflater inflater, ViewGroup container, 51 | Bundle savedInstanceState) { 52 | View v = inflater.inflate(R.layout.fragment_demo_card, container, false); 53 | 54 | TextView tvTitle = (TextView) v.findViewById(R.id.tvTitle); 55 | TextView tvDescription = (TextView) v.findViewById(R.id.tvDescription); 56 | Button button = (Button) v.findViewById(R.id.button); 57 | 58 | tvTitle.setText(title); 59 | tvDescription.setText(description); 60 | button.setOnClickListener(this); 61 | 62 | return v; 63 | } 64 | 65 | @Override 66 | public void onClick(View view) { 67 | switch (view.getId()) { 68 | case R.id.button: 69 | onAction(); 70 | break; 71 | } 72 | } 73 | 74 | public void onAction() { 75 | if (actionListener != null) { 76 | actionListener.onAction(this.id); 77 | } 78 | } 79 | 80 | @Override 81 | public void onAttach(Context context) { 82 | super.onAttach(context); 83 | if (context instanceof OnActionListener) { 84 | actionListener = (OnActionListener) context; 85 | } else { 86 | throw new RuntimeException(context.toString() 87 | + " must implement OnActionListener"); 88 | } 89 | } 90 | 91 | @Override 92 | public void onDetach() { 93 | super.onDetach(); 94 | actionListener = null; 95 | } 96 | 97 | public interface OnActionListener { 98 | void onAction(int id); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /sample/src/main/java/com/stfalcon/frescoimageviewersample/features/main/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewersample.features.main; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.support.v4.view.ViewPager; 6 | 7 | import com.stfalcon.frescoimageviewersample.R; 8 | import com.stfalcon.frescoimageviewersample.features.demo.DemoActivity; 9 | import com.stfalcon.frescoimageviewersample.features.demo.formatter.CustomObjectsActivity; 10 | import com.stfalcon.frescoimageviewersample.features.demo.rotation.DialogRotationExampleActivity; 11 | import com.stfalcon.frescoimageviewersample.features.demo.simple.SimpleUsageActivity; 12 | import com.stfalcon.frescoimageviewersample.features.demo.styled.StyledViewActivity; 13 | 14 | import me.relex.circleindicator.CircleIndicator; 15 | 16 | /* 17 | * Created by Alexander Krol (troy379) on 29.08.16. 18 | */ 19 | public class MainActivity extends DemoActivity 20 | implements DemoCardFragment.OnActionListener { 21 | 22 | @Override 23 | protected void onCreate(Bundle savedInstanceState) { 24 | super.onCreate(savedInstanceState); 25 | setContentView(R.layout.activity_main); 26 | init(); 27 | 28 | ViewPager pager = (ViewPager) findViewById(R.id.pager); 29 | pager.setAdapter(new MainActivityPagerAdapter(this, getSupportFragmentManager())); 30 | pager.setPageMargin((int) getResources().getDimension(R.dimen.card_padding) / 4); 31 | pager.setOffscreenPageLimit(3); 32 | 33 | CircleIndicator indicator = (CircleIndicator) findViewById(R.id.indicator); 34 | indicator.setViewPager(pager); 35 | } 36 | 37 | @Override 38 | public void onAction(int id) { 39 | Class activity = null; 40 | switch (id) { 41 | case MainActivityPagerAdapter.ID_SIMPLE_USAGE: 42 | activity = SimpleUsageActivity.class; 43 | break; 44 | case MainActivityPagerAdapter.ID_STYLED_VIEW: 45 | activity = StyledViewActivity.class; 46 | break; 47 | case MainActivityPagerAdapter.ID_ROTATION_SUPPORT: 48 | activity = DialogRotationExampleActivity.class; 49 | break; 50 | case MainActivityPagerAdapter.ID_CUSTOM_OBJECTS: 51 | activity = CustomObjectsActivity.class; 52 | break; 53 | } 54 | startActivity(new Intent(this, activity)); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sample/src/main/java/com/stfalcon/frescoimageviewersample/features/main/MainActivityPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewersample.features.main; 2 | 3 | import android.content.Context; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v4.app.FragmentManager; 6 | import android.support.v4.app.FragmentStatePagerAdapter; 7 | 8 | import com.stfalcon.frescoimageviewersample.R; 9 | 10 | /* 11 | * Created by troy379 on 06.03.17. 12 | */ 13 | class MainActivityPagerAdapter extends FragmentStatePagerAdapter { 14 | 15 | static final int ID_SIMPLE_USAGE = 0; 16 | static final int ID_STYLED_VIEW = 1; 17 | static final int ID_ROTATION_SUPPORT = 2; 18 | static final int ID_CUSTOM_OBJECTS = 3; 19 | 20 | private Context context; 21 | 22 | MainActivityPagerAdapter(Context context, FragmentManager fm) { 23 | super(fm); 24 | this.context = context; 25 | } 26 | 27 | @Override 28 | public Fragment getItem(int position) { 29 | String title = null; 30 | String description = null; 31 | switch (position) { 32 | case MainActivityPagerAdapter.ID_SIMPLE_USAGE: 33 | title = context.getString(R.string.action_simple_usage); 34 | description = context.getString(R.string.action_description_simple_usage); 35 | break; 36 | case MainActivityPagerAdapter.ID_STYLED_VIEW: 37 | title = context.getString(R.string.action_styled_view); 38 | description = context.getString(R.string.action_description_styled_view); 39 | break; 40 | case MainActivityPagerAdapter.ID_ROTATION_SUPPORT: 41 | title = context.getString(R.string.action_rotation_support); 42 | description = context.getString(R.string.action_description_rotation_support); 43 | break; 44 | case MainActivityPagerAdapter.ID_CUSTOM_OBJECTS: 45 | title = context.getString(R.string.action_custom_objects); 46 | description = context.getString(R.string.action_description_custom_objects); 47 | break; 48 | } 49 | return DemoCardFragment.newInstance(position, title, description); 50 | } 51 | 52 | @Override 53 | public int getCount() { 54 | return 4; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /sample/src/main/java/com/stfalcon/frescoimageviewersample/utils/AppUtils.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewersample.utils; 2 | 3 | import android.content.Context; 4 | import android.os.Handler; 5 | import android.support.annotation.StringRes; 6 | import android.support.design.widget.BaseTransientBottomBar; 7 | import android.support.design.widget.Snackbar; 8 | import android.view.View; 9 | import android.widget.TextView; 10 | import android.widget.Toast; 11 | 12 | import com.stfalcon.frescoimageviewersample.R; 13 | 14 | /* 15 | * Created by troy379 on 27.12.16. 16 | */ 17 | public final class AppUtils { 18 | private AppUtils() { 19 | throw new AssertionError(); 20 | } 21 | 22 | public static void showToast(Context context, @StringRes int text, boolean isLong) { 23 | showToast(context, context.getString(text), isLong); 24 | } 25 | 26 | public static void showToast(Context context, String text, boolean isLong) { 27 | Toast.makeText(context, text, isLong ? Toast.LENGTH_LONG : Toast.LENGTH_SHORT).show(); 28 | } 29 | 30 | public static void showInfoSnackbar(final View view, final @StringRes int text, final boolean isLong) { 31 | new Handler().postDelayed(new Runnable() { 32 | @Override 33 | public void run() { 34 | multilineSnackbar( 35 | Snackbar.make( 36 | view, text, 37 | isLong ? BaseTransientBottomBar.LENGTH_LONG : BaseTransientBottomBar.LENGTH_SHORT) 38 | ).show(); 39 | } 40 | }, 100); 41 | } 42 | 43 | public static void showGotItSnackbar(final View view, final @StringRes int text) { 44 | new Handler().postDelayed(new Runnable() { 45 | @Override 46 | public void run() { 47 | multilineSnackbar( 48 | Snackbar.make( 49 | view, text, BaseTransientBottomBar.LENGTH_INDEFINITE) 50 | .setAction(R.string.got_it, new View.OnClickListener() { 51 | @Override 52 | public void onClick(View view) { 53 | 54 | } 55 | }) 56 | ).show(); 57 | } 58 | }, 200); 59 | } 60 | 61 | private static Snackbar multilineSnackbar(Snackbar snackbar) { 62 | TextView textView = (TextView) snackbar.getView().findViewById(android.support.design.R.id.snackbar_text); 63 | textView.setMaxLines(5); 64 | return snackbar; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /sample/src/main/java/com/stfalcon/frescoimageviewersample/utils/StylingOptions.java: -------------------------------------------------------------------------------- 1 | package com.stfalcon.frescoimageviewersample.utils; 2 | 3 | import android.content.Context; 4 | import android.content.DialogInterface; 5 | import android.support.v7.app.AlertDialog; 6 | 7 | import com.stfalcon.frescoimageviewersample.R; 8 | 9 | import java.util.Collection; 10 | import java.util.Map; 11 | import java.util.TreeMap; 12 | 13 | /* 14 | * Created by troy379 on 10.03.17. 15 | */ 16 | public class StylingOptions { 17 | 18 | private Map options = new TreeMap() {{ 19 | put(Property.HIDE_STATUS_BAR, true); 20 | put(Property.IMAGE_MARGIN, true); 21 | put(Property.CONTAINER_PADDING, false); 22 | put(Property.IMAGES_ROUNDING, false); 23 | put(Property.SWIPE_TO_DISMISS, true); 24 | put(Property.ZOOMING, true); 25 | put(Property.SHOW_OVERLAY, true); 26 | put(Property.RANDOM_BACKGROUND, false); 27 | put(Property.POST_PROCESSING, false); 28 | }}; 29 | 30 | public boolean get(Property property) { 31 | return options.get(property); 32 | } 33 | 34 | public void showDialog(Context context) { 35 | AlertDialog dialog = new AlertDialog.Builder(context) 36 | .setMultiChoiceItems( 37 | context.getResources().getStringArray(R.array.options), 38 | toPrimitiveBooleanArray(options.values()), 39 | new DialogInterface.OnMultiChoiceClickListener() { 40 | @Override 41 | public void onClick(DialogInterface dialog, int indexSelected, boolean isChecked) { 42 | options.put(Property.values()[indexSelected], isChecked); 43 | } 44 | }).create(); 45 | dialog.show(); 46 | } 47 | 48 | public enum Property { 49 | HIDE_STATUS_BAR, 50 | IMAGE_MARGIN, 51 | CONTAINER_PADDING, 52 | IMAGES_ROUNDING, 53 | SWIPE_TO_DISMISS, 54 | ZOOMING, 55 | SHOW_OVERLAY, 56 | RANDOM_BACKGROUND, 57 | POST_PROCESSING 58 | } 59 | 60 | private static boolean[] toPrimitiveBooleanArray(Collection collection) { 61 | boolean[] array = new boolean[collection.size()]; 62 | int i = 0; 63 | for (Boolean value : collection) { 64 | array[i] = value; 65 | i++; 66 | } 67 | return array; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /sample/src/main/res/drawable-hdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/sample/src/main/res/drawable-hdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-mdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/sample/src/main/res/drawable-mdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/sample/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/sample/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/sample/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /sample/src/main/res/drawable/play_store_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stfalcon-studio/FrescoImageViewer/d5de181a25054c6e458f16d05ddf33816dc5f4c7/sample/src/main/res/drawable/play_store_icon.png -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_demo.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | 15 | 16 | 20 | 21 | 28 | 29 | 34 | 35 | 44 | 45 | 49 | 50 | 59 | 60 | 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/content_posters_grid.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 19 | 20 | 25 | 26 | 31 | 32 | 33 | 34 | 39 | 40 | 45 | 46 | 51 | 52 | 57 | 58 | 59 | 60 | 65 | 66 | 71 | 72 | 77 | 78 | 83 | 84 | 85 | 86 | -------------------------------------------------------------------------------- /sample/src/main/res/layout/fragment_demo_card.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | 14 | 22 | 23 | 32 | 33 |