Skip to content

Commit

Permalink
semplified the view and sample, added README
Browse files Browse the repository at this point in the history
  • Loading branch information
luca committed Dec 16, 2019
1 parent cec598c commit 11ecdb5
Show file tree
Hide file tree
Showing 8 changed files with 188 additions and 128 deletions.
54 changes: 54 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
LargeImageView
=====

LargeImageView is a custom view for Android, written in Kotlin, helping you manage a [SubsamplingImageView](https://github.com/davemorrissey/subsampling-scale-image-view).

With LIV is easy to show a thumbnail while loading the source image in the background,
show an error view in case something goes wrong.

LIV makes it easy to use the subsampling, zooming and gestures capabilities of SSIV in combination with a transition or shared-element-transition,
allowing you to animate the thumbnail and delay the loading of source image until the animation is complete.


### Usage

To manage the beahviour of LIV you must provide an ImageLoader, like so:

```
liv_basic.setImageLoader(object : ImageLoader {
override fun getThumbnailView(context: Context): View {
...
}
override fun loadThumbnail(view: View, url: String) {
...
}
override fun getErrorView(context: Context): View {
...
}
override fun preloadSource(url: String, callback: ImageReadyCallback) {
...
callback.onImageReady(file)
...
callback.onImageErrored()
}
})
```

after doing that, start the process with:

```
liv.startLoading()
```

It's that simple!

### License
This project is licensed under the MIT License
2 changes: 2 additions & 0 deletions lib/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_ver"
implementation 'androidx.core:core-ktx:1.1.0'

implementation 'androidx.annotation:annotation:1.1.0'

api "com.github.KirkBushman:subsampling-scale-image-view:$ssiv_ver"
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ interface ImageLoader {
/**
* Load the image to the View provided, the method is up to the user
*/
fun loadThumbnail(view: View, url: String)
fun loadThumbnail(view: View)

/**
* Receive from the user the view that binds to errorView
Expand All @@ -25,5 +25,5 @@ interface ImageLoader {
* the method used is up to the user, use the callback to report if the image is available
* or the fetching went wrong.
*/
fun preloadSource(url: String, callback: ImageReadyCallback)
fun preloadSource(callback: ImageReadyCallback)
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface ImageReadyCallback {
/**
* The large image preloaded is ready, as a java File Object
*/
fun onImageReady(file: File, forceImageShow: Boolean = false)
fun onImageReady(file: File)

/**
* The process did return an error.
Expand Down
146 changes: 82 additions & 64 deletions lib/src/main/java/com/kirkbushman/largeimageview/LargeImageView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import android.util.AttributeSet
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.annotation.IntDef
import androidx.annotation.UiThread
import com.davemorrissey.labs.subscaleview.ImageSource
import com.davemorrissey.labs.subscaleview.SubsamplingScaleImageView
Expand All @@ -14,8 +15,17 @@ import java.io.File
@Suppress("unused", "MemberVisibilityCanBePrivate")
class LargeImageView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr) {

private var thumbnailUrl: String? = null
private var sourceUrl: String? = null
companion object {

const val SHOWING_NOTHING = 0
const val SHOWING_THUMBNAIL = 1
const val SHOWING_ERROR = 2
const val SHOWING_SOURCE = 3

@IntDef(SHOWING_NOTHING, SHOWING_THUMBNAIL, SHOWING_ERROR, SHOWING_SOURCE)
@Retention(AnnotationRetention.SOURCE)
annotation class State
}

private var thumbnailView: View? = null
private var errorView: View? = null
Expand All @@ -24,7 +34,10 @@ class LargeImageView @JvmOverloads constructor(context: Context, attrs: Attribut
private var loader: ImageLoader? = null
private var viewsShownListener: OnViewsShownListener? = null

private var showImageWhenAvailable = true
@State
private var state: Int = SHOWING_NOTHING

private var showImageWhenAvailable: Boolean

init {

Expand All @@ -39,10 +52,23 @@ class LargeImageView @JvmOverloads constructor(context: Context, attrs: Attribut
this.loader = loader
}

fun getThumbnailView(): View? {
return thumbnailView
}

fun getErrorView(): View? {
return errorView
}

fun getSsiv(): SubsamplingScaleImageView? {
return sourceView
}

@State
fun getState(): Int {
return state
}

fun getShowImageWhenAvailable(): Boolean {
return showImageWhenAvailable
}
Expand All @@ -55,13 +81,6 @@ class LargeImageView @JvmOverloads constructor(context: Context, attrs: Attribut
this.viewsShownListener = viewsShownListener
}

fun setImage(thumbnailUrl: String?, sourceUrl: String?) {
this.thumbnailUrl = thumbnailUrl
this.sourceUrl = sourceUrl

startLoading()
}

fun triggerShowImage() {

if (showImageWhenAvailable) {
Expand All @@ -71,32 +90,32 @@ class LargeImageView @JvmOverloads constructor(context: Context, attrs: Attribut
showImage()
}

private fun startLoading() {
fun startLoading(showThumbnail: Boolean = true, showSource: Boolean = true) {

clearViews()

loader?.let { loader ->

// show the thumbnail in the meantime,
// while the large image is loading in the background.
thumbnailUrl?.let { thumbUrl ->
if (showThumbnail) {

thumbnailView = loader.getThumbnailView(context)
thumbnailView?.let { thumbView ->

loader.loadThumbnail(thumbView, thumbUrl)
loader.loadThumbnail(thumbView)

showThumbnail()
}
}

// start loading in the background the real image.
sourceUrl?.let { url ->
if (showSource) {

sourceView = SubsamplingScaleImageView(context)
sourceView?.let { view ->

loader.preloadSource(url, object : ImageReadyCallback {
loader.preloadSource(object : ImageReadyCallback {

// if the image is ready set it in the SSIV
//
Expand All @@ -105,11 +124,11 @@ class LargeImageView @JvmOverloads constructor(context: Context, attrs: Attribut
//
// otherwise wait for triggerShowImage() to get called.
//
override fun onImageReady(file: File, forceImageShow: Boolean) {
override fun onImageReady(file: File) {

view.setImage(ImageSource.uri(Uri.fromFile(file)))

if (showImageWhenAvailable || forceImageShow) {
if (showImageWhenAvailable) {
showImage()
}
}
Expand All @@ -129,10 +148,13 @@ class LargeImageView @JvmOverloads constructor(context: Context, attrs: Attribut
}

fun clearViews() {

if (childCount > 0) {
removeAllViews()
}

state = SHOWING_NOTHING

thumbnailView = null
errorView = null
sourceView = null
Expand All @@ -141,81 +163,77 @@ class LargeImageView @JvmOverloads constructor(context: Context, attrs: Attribut
@UiThread
private fun showThumbnail() {

if (thumbnailView != null) {
if (findViewById<View>(thumbnailView!!.id) == null) {
if (state == SHOWING_THUMBNAIL) {
return
}

addView(
thumbnailView,
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
thumbnailView?.let {

viewsShownListener?.onThumbnailViewShown(thumbnailView!!)
}
addChildIfNotPresent(it)
enableChildView(it)

if (thumbnailView?.visibility != View.VISIBLE) {
thumbnailView?.visibility = View.VISIBLE
state = SHOWING_THUMBNAIL

viewsShownListener?.onThumbnailViewShown(it)
}
}

@UiThread
private fun showErrorView() {

if (errorView != null) {
if (findViewById<View>(errorView!!.id) == null) {

addView(
errorView,
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}

viewsShownListener?.onErrorViewShown(errorView!!)
if (state == SHOWING_ERROR) {
return
}

if (errorView?.visibility != View.VISIBLE) {
errorView?.visibility = View.VISIBLE
}
errorView?.let {

if (childCount > 0) {
addChildIfNotPresent(it)
enableChildView(it)

postDelayed({
state = SHOWING_ERROR

sourceView?.visibility = View.GONE
thumbnailView?.visibility = View.GONE
}, 1000)
viewsShownListener?.onErrorViewShown(it)
}
}

@UiThread
private fun showImage() {

if (sourceView != null) {
if (findViewById<View>(sourceView!!.id) == null) {
if (state == SHOWING_SOURCE) {
return
}

addView(
sourceView,
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
sourceView?.let {

viewsShownListener?.onImageViewShown(sourceView!!)
addChildIfNotPresent(it)
enableChildView(it)

state = SHOWING_SOURCE

viewsShownListener?.onImageViewShown(it)
}
}

private fun addChildIfNotPresent(view: View) {

if (findViewById<View>(view.id) == null) {

if (sourceView?.visibility != View.VISIBLE) {
sourceView?.visibility = View.VISIBLE
addView(
view,
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
}
}

if (childCount > 0) {
private fun enableChildView(view: View) {

postDelayed({
if (view.visibility != View.VISIBLE) {
view.visibility = View.VISIBLE
}

errorView?.visibility = View.GONE
thumbnailView?.visibility = View.GONE
}, 1000)
if (childCount > 0) {
view.bringToFront()
}
}
}
Loading

0 comments on commit 11ecdb5

Please sign in to comment.