From 6705989faa2beecd4b5a175d52de9fbeab4fa517 Mon Sep 17 00:00:00 2001 From: Grant Kamin Date: Wed, 26 Feb 2020 13:52:09 -0600 Subject: [PATCH 1/2] Switch to Dialog Fragment Switched the dialog to DialogFragment so when a configuration change (i.e. orientation) happens the dialog will still be there. Updated the listeners passed in to just call out to the hosting activity. --- .../imageviewer/StfalconImageViewer.java | 62 +++++------------ .../listeners/OnImageChangeListener.java | 2 + .../imageviewer/loader/OverlayLoader.java | 20 ++++++ .../imageviewer/viewer/builder/BuilderData.kt | 13 ++-- .../viewer/dialog/ImageViewerDialog.kt | 66 +++++++++++++------ .../viewer/view/ImageViewerView.kt | 9 ++- 6 files changed, 99 insertions(+), 73 deletions(-) create mode 100644 imageviewer/src/main/java/com/stfalcon/imageviewer/loader/OverlayLoader.java diff --git a/imageviewer/src/main/java/com/stfalcon/imageviewer/StfalconImageViewer.java b/imageviewer/src/main/java/com/stfalcon/imageviewer/StfalconImageViewer.java index 61d8179..22ea565 100644 --- a/imageviewer/src/main/java/com/stfalcon/imageviewer/StfalconImageViewer.java +++ b/imageviewer/src/main/java/com/stfalcon/imageviewer/StfalconImageViewer.java @@ -16,12 +16,15 @@ package com.stfalcon.imageviewer; +import android.app.Activity; import android.content.Context; import android.util.Log; import android.view.View; import android.widget.ImageView; import androidx.annotation.*; import androidx.core.content.ContextCompat; +import androidx.fragment.app.FragmentManager; + import com.stfalcon.imageviewer.listeners.OnDismissListener; import com.stfalcon.imageviewer.listeners.OnImageChangeListener; import com.stfalcon.imageviewer.loader.ImageLoader; @@ -43,14 +46,14 @@ public class StfalconImageViewer { protected StfalconImageViewer(@NonNull Context context, @NonNull BuilderData builderData) { this.context = context; this.builderData = builderData; - this.dialog = new ImageViewerDialog<>(context, builderData); + this.dialog = ImageViewerDialog.Companion.newInstance(builderData); } /** * Displays the built viewer if passed list of images is not empty */ - public void show() { - show(true); + public void show(FragmentManager fragmentManager) { + show(fragmentManager, true); } /** @@ -58,9 +61,9 @@ public void show() { * * @param animate whether the passed transition view should be animated on open. Useful for screen rotation handling. */ - public void show(boolean animate) { + public void show(FragmentManager fragmentManager, boolean animate) { if (!builderData.getImages().isEmpty()) { - dialog.show(animate); + dialog.show(fragmentManager, animate); } else { Log.w(context.getString(R.string.library_name), "Images list cannot be empty! Viewer ignored."); @@ -123,13 +126,13 @@ public static class Builder { private Context context; private BuilderData data; - public Builder(Context context, T[] images, ImageLoader imageLoader) { - this(context, new ArrayList<>(Arrays.asList(images)), imageLoader); + public Builder(Context context, T[] images) { + this(context, new ArrayList<>(Arrays.asList(images))); } - public Builder(Context context, List images, ImageLoader imageLoader) { + public Builder(Context context, List images) { this.context = context; - this.data = new BuilderData<>(images, imageLoader); + this.data = new BuilderData<>(images); } /** @@ -161,17 +164,6 @@ public Builder withBackgroundColorResource(@ColorRes int color) { return this.withBackgroundColor(ContextCompat.getColor(context, color)); } - /** - * Sets custom overlay view to be shown over the viewer. - * Commonly used for image description or counter displaying. - * - * @return This Builder object to allow calls chaining - */ - public Builder withOverlayView(View view) { - this.data.setOverlayView(view); - return this; - } - /** * Sets space between the images using dimension. * @@ -278,30 +270,10 @@ public Builder withTransitionFrom(ImageView imageView) { return this; } - /** - * Sets {@link OnImageChangeListener} for the viewer. - * - * @return This Builder object to allow calls chaining - */ - public Builder withImageChangeListener(OnImageChangeListener imageChangeListener) { - this.data.setImageChangeListener(imageChangeListener); - return this; - } - - /** - * Sets {@link OnDismissListener} for viewer. - * - * @return This Builder object to allow calls chaining - */ - public Builder withDismissListener(OnDismissListener onDismissListener) { - this.data.setOnDismissListener(onDismissListener); - return this; - } - /** * Creates a {@link StfalconImageViewer} with the arguments supplied to this builder. It does not * show the dialog. This allows the user to do any extra processing - * before displaying the dialog. Use {@link #show()} if you don't have any other processing + * before displaying the dialog. Use {@link #show(FragmentManager fragmentManager)} if you don't have any other processing * to do and want this to be created and displayed. */ public StfalconImageViewer build() { @@ -312,8 +284,8 @@ public StfalconImageViewer build() { * Creates the {@link StfalconImageViewer} with the arguments supplied to this builder and * shows the dialog. */ - public StfalconImageViewer show() { - return show(true); + public StfalconImageViewer show(FragmentManager fragmentManager) { + return show(fragmentManager, true); } /** @@ -322,9 +294,9 @@ public StfalconImageViewer show() { * * @param animate whether the passed transition view should be animated on open. Useful for screen rotation handling. */ - public StfalconImageViewer show(boolean animate) { + public StfalconImageViewer show(FragmentManager fragmentManager, boolean animate) { StfalconImageViewer viewer = build(); - viewer.show(animate); + viewer.show(fragmentManager, animate); return viewer; } } diff --git a/imageviewer/src/main/java/com/stfalcon/imageviewer/listeners/OnImageChangeListener.java b/imageviewer/src/main/java/com/stfalcon/imageviewer/listeners/OnImageChangeListener.java index eab4de4..20b6920 100644 --- a/imageviewer/src/main/java/com/stfalcon/imageviewer/listeners/OnImageChangeListener.java +++ b/imageviewer/src/main/java/com/stfalcon/imageviewer/listeners/OnImageChangeListener.java @@ -16,6 +16,8 @@ package com.stfalcon.imageviewer.listeners; +import com.stfalcon.imageviewer.viewer.dialog.ImageViewerDialog; + /** * Interface definition for a callback to be invoked when current image position was changed. */ diff --git a/imageviewer/src/main/java/com/stfalcon/imageviewer/loader/OverlayLoader.java b/imageviewer/src/main/java/com/stfalcon/imageviewer/loader/OverlayLoader.java new file mode 100644 index 0000000..3ff004d --- /dev/null +++ b/imageviewer/src/main/java/com/stfalcon/imageviewer/loader/OverlayLoader.java @@ -0,0 +1,20 @@ +package com.stfalcon.imageviewer.loader; + +import android.view.View; +import android.widget.ImageView; + +import com.stfalcon.imageviewer.StfalconImageViewer; +import com.stfalcon.imageviewer.viewer.dialog.ImageViewerDialog; + +/** + * Interface definition for a callback to be invoked when the overlay should be loaded + */ +//N.B.! This class is written in Java for convenient use of lambdas due to languages compatibility issues. +public interface OverlayLoader { + + /** + * Fires when the overlay needs to be loaded + * + */ + View loadOverlayFor(int position, ImageViewerDialog viewer); +} diff --git a/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/builder/BuilderData.kt b/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/builder/BuilderData.kt index 7153a95..cc178a6 100644 --- a/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/builder/BuilderData.kt +++ b/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/builder/BuilderData.kt @@ -17,21 +17,20 @@ package com.stfalcon.imageviewer.viewer.builder import android.graphics.Color +import android.os.Parcelable import android.view.View import android.widget.ImageView import com.stfalcon.imageviewer.listeners.OnDismissListener import com.stfalcon.imageviewer.listeners.OnImageChangeListener import com.stfalcon.imageviewer.loader.ImageLoader +import java.io.Serializable -internal class BuilderData( - val images: List, - val imageLoader: ImageLoader -) { + +class BuilderData( + val images: List +) : Serializable { var backgroundColor = Color.BLACK var startPosition: Int = 0 - var imageChangeListener: OnImageChangeListener? = null - var onDismissListener: OnDismissListener? = null - var overlayView: View? = null var imageMarginPixels: Int = 0 var containerPaddingPixels = IntArray(4) var shouldStatusBarHide = true diff --git a/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/dialog/ImageViewerDialog.kt b/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/dialog/ImageViewerDialog.kt index bfb14f5..8398b05 100644 --- a/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/dialog/ImageViewerDialog.kt +++ b/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/dialog/ImageViewerDialog.kt @@ -16,22 +16,29 @@ package com.stfalcon.imageviewer.viewer.dialog -import android.content.Context +import android.app.Dialog +import android.os.Bundle import android.view.KeyEvent import android.widget.ImageView import androidx.appcompat.app.AlertDialog +import androidx.fragment.app.DialogFragment +import androidx.fragment.app.FragmentManager import com.stfalcon.imageviewer.R +import com.stfalcon.imageviewer.StfalconImageViewer +import com.stfalcon.imageviewer.listeners.OnDismissListener +import com.stfalcon.imageviewer.listeners.OnImageChangeListener +import com.stfalcon.imageviewer.loader.ImageLoader +import com.stfalcon.imageviewer.loader.OverlayLoader import com.stfalcon.imageviewer.viewer.builder.BuilderData import com.stfalcon.imageviewer.viewer.view.ImageViewerView +import kotlin.math.max -internal class ImageViewerDialog( - context: Context, - private val builderData: BuilderData -) { +class ImageViewerDialog: DialogFragment() { - private val dialog: AlertDialog - private val viewerView: ImageViewerView = ImageViewerView(context) + private lateinit var dialog: AlertDialog + private lateinit var viewerView: ImageViewerView private var animateOpen = true + private lateinit var builderData: BuilderData private val dialogStyle: Int get() = if (builderData.shouldStatusBarHide) @@ -39,29 +46,42 @@ internal class ImageViewerDialog( else R.style.ImageViewerDialog_Default - init { + companion object { + private const val builderDataKey = "BuilderDataKey" + fun newInstance(builderData: BuilderData): ImageViewerDialog { + val args = Bundle() + args.putSerializable(builderDataKey, builderData) + val f = ImageViewerDialog() + f.arguments = args + return f + } + } + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + builderData = arguments?.getSerializable(builderDataKey) as BuilderData setupViewerView() dialog = AlertDialog - .Builder(context, dialogStyle) + .Builder(requireContext(), dialogStyle) .setView(viewerView) .setOnKeyListener { _, keyCode, event -> onDialogKeyEvent(keyCode, event) } .create() .apply { setOnShowListener { viewerView.open(builderData.transitionView, animateOpen) } - setOnDismissListener { builderData.onDismissListener?.onDismiss() } - } + setOnDismissListener { (activity as? OnDismissListener)?.onDismiss() } +} + return dialog } - fun show(animate: Boolean) { + fun show(fragmentManager: FragmentManager, animate: Boolean) { animateOpen = animate - dialog.show() + show(fragmentManager, "") } fun close() { viewerView.close() } - fun dismiss() { + override fun dismiss() { dialog.dismiss() } @@ -97,19 +117,27 @@ internal class ImageViewerDialog( } private fun setupViewerView() { + viewerView = ImageViewerView(requireContext()) viewerView.apply { isZoomingAllowed = builderData.isZoomingAllowed isSwipeToDismissAllowed = builderData.isSwipeToDismissAllowed containerPadding = builderData.containerPaddingPixels imagesMargin = builderData.imageMarginPixels - overlayView = builderData.overlayView + overlayView = (activity as? OverlayLoader)?.loadOverlayFor(max(viewerView.currentPosition, builderData.startPosition), + this@ImageViewerDialog) setBackgroundColor(builderData.backgroundColor) - setImages(builderData.images, builderData.startPosition, builderData.imageLoader) + val imageLoader = activity as? ImageLoader + if (imageLoader != null) { + setImages(builderData.images, builderData.startPosition, imageLoader) + } - onPageChange = { position -> builderData.imageChangeListener?.onImageChange(position) } - onDismiss = { dialog.dismiss() } + onPageChange = { position -> (activity as? OnImageChangeListener)?.onImageChange(position) } + onDismiss = { + dialog.dismiss() + (activity as? OnDismissListener)?.onDismiss() + } } } -} \ No newline at end of file +} diff --git a/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/view/ImageViewerView.kt b/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/view/ImageViewerView.kt index 7b7447e..bc26233 100644 --- a/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/view/ImageViewerView.kt +++ b/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/view/ImageViewerView.kt @@ -49,7 +49,7 @@ import com.stfalcon.imageviewer.common.pager.MultiTouchViewPager import com.stfalcon.imageviewer.loader.ImageLoader import com.stfalcon.imageviewer.viewer.adapter.ImagesPagerAdapter -internal class ImageViewerView @JvmOverloads constructor( +class ImageViewerView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 @@ -81,7 +81,12 @@ internal class ImageViewerView @JvmOverloads constructor( internal var overlayView: View? = null set(value) { field = value - value?.let { rootContainer.addView(it) } + value?.let { + if (it.parent != null) { + (it.parent as? ViewGroup)?.removeView(it) + } + rootContainer.addView(it) + } } private var rootContainer: ViewGroup From 84053166ef872043b7ad4bd6590a8b958ca23d9e Mon Sep 17 00:00:00 2001 From: Grant Kamin Date: Mon, 9 Mar 2020 13:42:39 -0500 Subject: [PATCH 2/2] Add Fragment Support Allow the dialog to be presented from a fragment and have the callbacks work. --- .../com/stfalcon/imageviewer/StfalconImageViewer.java | 8 ++++++++ .../imageviewer/viewer/dialog/ImageViewerDialog.kt | 11 ++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/imageviewer/src/main/java/com/stfalcon/imageviewer/StfalconImageViewer.java b/imageviewer/src/main/java/com/stfalcon/imageviewer/StfalconImageViewer.java index 22ea565..6d8db1c 100644 --- a/imageviewer/src/main/java/com/stfalcon/imageviewer/StfalconImageViewer.java +++ b/imageviewer/src/main/java/com/stfalcon/imageviewer/StfalconImageViewer.java @@ -23,6 +23,7 @@ import android.widget.ImageView; import androidx.annotation.*; import androidx.core.content.ContextCompat; +import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import com.stfalcon.imageviewer.listeners.OnDismissListener; @@ -102,6 +103,13 @@ public void updateImages(List images) { } } + /** + * Sets the target fragment of the dialog + */ + public void setTargetFragment(Fragment fragment, int requestCode) { + dialog.setTargetFragment(fragment, requestCode); + } + public int currentPosition() { return dialog.getCurrentPosition(); } diff --git a/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/dialog/ImageViewerDialog.kt b/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/dialog/ImageViewerDialog.kt index 8398b05..0ddef7f 100644 --- a/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/dialog/ImageViewerDialog.kt +++ b/imageviewer/src/main/java/com/stfalcon/imageviewer/viewer/dialog/ImageViewerDialog.kt @@ -22,6 +22,7 @@ import android.view.KeyEvent import android.widget.ImageView import androidx.appcompat.app.AlertDialog import androidx.fragment.app.DialogFragment +import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import com.stfalcon.imageviewer.R import com.stfalcon.imageviewer.StfalconImageViewer @@ -67,7 +68,7 @@ class ImageViewerDialog: DialogFragment() { .create() .apply { setOnShowListener { viewerView.open(builderData.transitionView, animateOpen) } - setOnDismissListener { (activity as? OnDismissListener)?.onDismiss() } + setOnDismissListener { ((targetFragment ?: activity) as? OnDismissListener)?.onDismiss() } } return dialog } @@ -124,19 +125,19 @@ class ImageViewerDialog: DialogFragment() { containerPadding = builderData.containerPaddingPixels imagesMargin = builderData.imageMarginPixels - overlayView = (activity as? OverlayLoader)?.loadOverlayFor(max(viewerView.currentPosition, builderData.startPosition), + overlayView = ((targetFragment ?: activity) as? OverlayLoader)?.loadOverlayFor(max(viewerView.currentPosition, builderData.startPosition), this@ImageViewerDialog) setBackgroundColor(builderData.backgroundColor) - val imageLoader = activity as? ImageLoader + val imageLoader = (targetFragment ?: activity) as? ImageLoader if (imageLoader != null) { setImages(builderData.images, builderData.startPosition, imageLoader) } - onPageChange = { position -> (activity as? OnImageChangeListener)?.onImageChange(position) } + onPageChange = { position -> ((targetFragment ?: activity) as? OnImageChangeListener)?.onImageChange(position) } onDismiss = { dialog.dismiss() - (activity as? OnDismissListener)?.onDismiss() + ((targetFragment ?: activity) as? OnDismissListener)?.onDismiss() } } }