├── settings.gradle
├── frescoimageviewer
├── src
│ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── com
│ │ │ └── stfalcon
│ │ │ └── frescoimageviewer
│ │ │ ├── OnDismissListener.java
│ │ │ ├── AnimationUtils.java
│ │ │ ├── adapter
│ │ │ ├── ViewHolder.java
│ │ │ └── RecyclingPagerAdapter.java
│ │ │ ├── drawee
│ │ │ ├── NonInterceptableAttacher.java
│ │ │ └── ZoomableDraweeView.java
│ │ │ ├── MultiTouchViewPager.java
│ │ │ ├── SwipeDirectionDetector.java
│ │ │ ├── SwipeToDismissListener.java
│ │ │ ├── ImageViewerAdapter.java
│ │ │ ├── ImageViewerView.java
│ │ │ └── ImageViewer.java
│ │ └── res
│ │ └── layout
│ │ └── image_viewer.xml
├── proguard-rules.pro
└── build.gradle
├── images
├── posters
│ ├── Max.jpg
│ ├── Daniel.jpg
│ ├── Driver.jpg
│ ├── Frank.jpg
│ ├── Jules.jpg
│ ├── Korben.jpg
│ ├── Marty.jpg
│ ├── Toretto.jpg
│ └── Vincent.jpg
├── fresco_image_viewer_demo.gif
└── fresco_image_viewer_customizing_demo.gif
├── sample
├── src
│ └── main
│ │ ├── res
│ │ ├── drawable
│ │ │ └── play_store_icon.png
│ │ ├── 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
│ │ ├── 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
│ │ ├── values
│ │ │ ├── dimens.xml
│ │ │ ├── colors.xml
│ │ │ ├── styles.xml
│ │ │ └── strings.xml
│ │ ├── menu
│ │ │ └── styling_options_menu.xml
│ │ └── layout
│ │ │ ├── activity_demo.xml
│ │ │ ├── view_image_overlay.xml
│ │ │ ├── fragment_demo_card.xml
│ │ │ ├── activity_main.xml
│ │ │ └── content_posters_grid.xml
│ │ ├── java
│ │ └── com
│ │ │ └── stfalcon
│ │ │ └── frescoimageviewersample
│ │ │ ├── common
│ │ │ ├── data
│ │ │ │ ├── models
│ │ │ │ │ └── CustomImage.java
│ │ │ │ └── Demo.java
│ │ │ └── views
│ │ │ │ └── ImageOverlayView.java
│ │ │ ├── features
│ │ │ ├── demo
│ │ │ │ ├── simple
│ │ │ │ │ └── SimpleUsageActivity.java
│ │ │ │ ├── DemoActivity.java
│ │ │ │ ├── formatter
│ │ │ │ │ └── CustomObjectsActivity.java
│ │ │ │ ├── rotation
│ │ │ │ │ └── DialogRotationExampleActivity.java
│ │ │ │ └── styled
│ │ │ │ │ └── StyledViewActivity.java
│ │ │ └── main
│ │ │ │ ├── MainActivityPagerAdapter.java
│ │ │ │ ├── MainActivity.java
│ │ │ │ └── DemoCardFragment.java
│ │ │ ├── App.java
│ │ │ └── utils
│ │ │ ├── StylingOptions.java
│ │ │ └── AppUtils.java
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle
├── gradle
└── wrapper
│ └── gradle-wrapper.properties
├── .gitignore
├── docs
└── CHANGELOG.md
├── gradle.properties
├── gradlew.bat
├── gradlew
└── README.md
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':sample', ':frescoimageviewer'
2 |
--------------------------------------------------------------------------------
/frescoimageviewer/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/images/posters/Max.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/images/posters/Max.jpg
--------------------------------------------------------------------------------
/images/posters/Daniel.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/images/posters/Daniel.jpg
--------------------------------------------------------------------------------
/images/posters/Driver.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/images/posters/Driver.jpg
--------------------------------------------------------------------------------
/images/posters/Frank.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/images/posters/Frank.jpg
--------------------------------------------------------------------------------
/images/posters/Jules.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/images/posters/Jules.jpg
--------------------------------------------------------------------------------
/images/posters/Korben.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/images/posters/Korben.jpg
--------------------------------------------------------------------------------
/images/posters/Marty.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/images/posters/Marty.jpg
--------------------------------------------------------------------------------
/images/posters/Toretto.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/images/posters/Toretto.jpg
--------------------------------------------------------------------------------
/images/posters/Vincent.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/images/posters/Vincent.jpg
--------------------------------------------------------------------------------
/images/fresco_image_viewer_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/images/fresco_image_viewer_demo.gif
--------------------------------------------------------------------------------
/images/fresco_image_viewer_customizing_demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/images/fresco_image_viewer_customizing_demo.gif
--------------------------------------------------------------------------------
/sample/src/main/res/drawable/play_store_icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/sample/src/main/res/drawable/play_store_icon.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/sample/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/sample/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/sample/src/main/res/drawable-hdpi/ic_settings_white_24dp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/appearance/FrescoImageViewer/master/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/appearance/FrescoImageViewer/master/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/appearance/FrescoImageViewer/master/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/appearance/FrescoImageViewer/master/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/appearance/FrescoImageViewer/master/sample/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png
--------------------------------------------------------------------------------
/sample/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 48dp
5 | 40dp
6 |
7 |
--------------------------------------------------------------------------------
/sample/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #727272
4 | #505050
5 | #FF5722
6 | #ffffff
7 |
8 |
9 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/sample/src/main/res/menu/styling_options_menu.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/activity_demo.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
11 |
12 |
--------------------------------------------------------------------------------
/.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
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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.
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/sample/src/main/res/layout/view_image_overlay.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
15 |
16 |
23 |
24 |
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/frescoimageviewer/src/main/res/layout/image_viewer.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
21 |
22 |
26 |
27 |
31 |
32 |
36 |
37 |
38 |
39 |
--------------------------------------------------------------------------------
/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/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
20 |
21 |
26 |
27 |
34 |
35 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/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/res/layout/fragment_demo_card.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
14 |
22 |
23 |
32 |
33 |
43 |
44 |
45 |
46 |
47 |
--------------------------------------------------------------------------------
/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/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Fresco Image Viewer
4 |
5 | Fresco Image Viewer
6 |
7 | Simple usage
8 | Styled view
9 | Rotation support
10 | Custom objects
11 |
12 | Traditional viewer without any customizing. The fastest way to implement.
13 | Customized viewer with overlay view, custom margins and padding.
14 | Example of how to handle activity rotation.
15 | Using formatter for custom objects - forget about converting.
16 | View
17 |
18 | Share
19 | Tap on any image to open viewer!
20 | Open options menu in toolbar to check available options to change!
21 | Dialog was successfully dismissed!
22 | Open the viewer and try to rotate device. Important! Don\'t forget to enable rotation settings :)
23 | This example uses custom image objects and shows images via custom formatter.
24 | Got it!
25 | Options
26 |
27 |
28 | - hide status bar
29 | - images margin
30 | - container padding
31 | - images rounding
32 | - swipe-to-dismiss
33 | - zooming
34 | - show overlay
35 | - random background
36 | - post-processing (via ImageRequestBuilder)
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 | }
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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/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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # FrescoImageViewer
2 |
3 | [](https://codebeat.co/projects/github-com-stfalcon-studio-frescoimageviewer-master) [  ](https://bintray.com/troy379/maven/FrescoImageViewer/_latestVersion)
4 |
5 | Simple customizable full screen image viewer for [Fresco library][frescoRepo] that includes "pinch to zoom" and "swipe to dismiss" gestures.
6 | Based on [PhotoDraweeView][photoDraweeViewRepo] by [ongakuer][coauthor].
7 |
8 | 
9 |
10 | ### Who we are
11 | 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/).
12 |
13 | ## Requirements
14 |
15 | * Fresco v.0.12.0 and higher
16 | * SDK 14 and and higher
17 |
18 | ## Demo Application
19 |
20 | [](https://play.google.com/store/apps/details?id=com.stfalcon.frescoimageviewersample)
21 |
22 | ## Usage
23 |
24 | #### Simple usage
25 | All you need to show a viewer is pass the context, start position and List or String[] into builder and call `show()`.
26 | ```java
27 | new ImageViewer.Builder(context, list)
28 | .setStartPosition(startPosition)
29 | .show();
30 | ```
31 |
32 | #### Custom objects
33 | 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?
34 | 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:
35 | ```java
36 | List images = getImages();
37 | new ImageViewer.Builder<>(this, images)
38 | .setFormatter(new ImageViewer.Formatter() {
39 | @Override
40 | public String format(CustomImage customImage) {
41 | return customImage.getUrl();
42 | }
43 | })
44 | .show();
45 | ```
46 | If formatter isn't passed, `Object.toString()` will be used for image formatting as default behavior.
47 |
48 | Piece of cake! :cake: :wink:
49 |
50 | ## Reminder
51 | Don't forget to initialize Fresco in your Application class:
52 | ```java
53 | Fresco.initialize(this);
54 | ```
55 |
56 | And if you expect to open really large images, use configuration below for better performance:
57 | ```java
58 | ImagePipelineConfig config = ImagePipelineConfig.newBuilder(this)
59 | .setProgressiveJpegConfig(new SimpleProgressiveJpegConfig())
60 | .setResizeAndRotateEnabledForNetwork(true)
61 | .setDownsampleEnabled(true)
62 | .build();
63 | Fresco.initialize(this, config);
64 | ```
65 |
66 | ## Customizing
67 |
68 | You can also customize a viewer to fit your needs.
69 |
70 | #### Background
71 | Use `setBackgroundColorRes(colorRes)` or `setBackgroundColor(colorInt)` to set color for fading background.
72 |
73 | #### Custom overlay view
74 | 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`.
75 |
76 | #### Custom drawee hierarchy
77 | Of course, according to Fresco flexibility, you can use your custom GenericDraweeHierarchy.
78 | To do this you simply need to create GenericDraweeHierarchy**Builder** and pass it into builder:
79 | ```java
80 | GenericDraweeHierarchyBuilder hierarchyBuilder = GenericDraweeHierarchyBuilder.newInstance(getResources())
81 | .setFailureImage(R.drawable.failureDrawable)
82 | .setProgressBarImage(R.drawable.progressBarDrawable)
83 | .setPlaceholderImage(R.drawable.placeholderDrawable);
84 |
85 | builder.setCustomDraweeHierarchyBuilder(hierarchyBuilder)
86 | ```
87 |
88 | :exclamation:**But there is a limitation**: default ScaleType in hierarchy is `ScaleType.FIT_CENTER`, so custom value will be ignored
89 |
90 | #### Custom image requests
91 | For rare cases like post-processing or bitmap resizing you need to use your custom ImageRequestBuilder.
92 | Create it with `ImageViewer.createImageRequestBuilder()` and after configuration pass it to viewer through `setCustomImageRequestBuilder(ImageRequestBuilder)`.
93 | ```java
94 | builder.setCustomImageRequestBuilder(
95 | ImageViewer.createImageRequestBuilder()
96 | .setPostprocessor(new GrayscalePostprocessor()));
97 | ```
98 |
99 | #### Image margin
100 | Simply add margins between images with dimens with setImageMargin(context, dimen) or in `px` using `setImageMarginPx(marginPx)`.
101 |
102 | #### Container padding
103 | 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.
104 | For setting padding in pixels, just use `setContainerPaddingPx(...)` method.
105 |
106 | #### Status bar visibility
107 | To show/hide status bar in view property you can set `hideStatusBar(boolean)` in builder. The default value is `true`.
108 |
109 | #### Gestures disabling
110 | If you need to disable some of gestures - do it using `allowSwipeToDismiss(boolean)` and `allowZooming(boolean)` accordingly.
111 |
112 | Here is an example that sets all the possible options:
113 |
114 | ```java
115 | new ImageViewer.Builder<>(this, images)
116 | .setStartPosition(startPosition)
117 | .hideStatusBar(false)
118 | .allowZooming(true)
119 | .allowSwipeToDismiss(true)
120 | .setBackgroundColorRes(colorRes)
121 | //.setBackgroundColor(color)
122 | .setImageMargin(margin)
123 | //.setImageMarginPx(marginPx)
124 | .setContainerPadding(this, dimen)
125 | //.setContainerPadding(this, dimenStart, dimenTop, dimenEnd, dimenBottom)
126 | //.setContainerPaddingPx(padding)
127 | //.setContainerPaddingPx(start, top, end, bottom)
128 | .setCustomImageRequestBuilder(imageRequestBuilder)
129 | .setCustomDraweeHierarchyBuilder(draweeHierarchyBuilder)
130 | .setImageChangeListener(imageChangeListener)
131 | .setOnDismissListener(onDismissListener)
132 | .setOverlayView(overlayView)
133 | .show();
134 |
135 | ```
136 |
137 | With this possibilities you can achieve something like this:
138 |
139 | 
140 |
141 | You can take a look at [sample project][sample] for more information.
142 |
143 | ## Install
144 |
145 | Download via **Gradle**:
146 | ```gradle
147 | compile 'com.github.stfalcon:frescoimageviewer:0.5.0'
148 | ```
149 |
150 | or **Maven**:
151 | ```xml
152 |
153 | com.github.stfalcon
154 | frescoimageviewer
155 | 0.5.0
156 | pom
157 |
158 | ```
159 |
160 | ## Changelog
161 | [See the changelog](docs/CHANGELOG.md) to be aware of latest improvements and fixes.
162 |
163 | ## License
164 |
165 | ```
166 | Copyright (C) 2017 stfalcon.com
167 |
168 | Licensed under the Apache License, Version 2.0 (the "License");
169 | you may not use this file except in compliance with the License.
170 | You may obtain a copy of the License at
171 |
172 | http://www.apache.org/licenses/LICENSE-2.0
173 |
174 | Unless required by applicable law or agreed to in writing, software
175 | distributed under the License is distributed on an "AS IS" BASIS,
176 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
177 | See the License for the specific language governing permissions and
178 | limitations under the License.
179 |
180 | ```
181 |
182 | [sample]:
183 | [frescoRepo]:
184 | [photoDraweeViewRepo]:
185 | [coauthor]:
186 |
187 |
188 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------