Skip to content

Commit

Permalink
Merge pull request #38 from avalanchas/prepared-shimmer
Browse files Browse the repository at this point in the history
Provide feature to have prepared shimmer layout
  • Loading branch information
skydoves authored Feb 2, 2024
2 parents a2d111f + c09e75f commit 3bd2f41
Show file tree
Hide file tree
Showing 22 changed files with 680 additions and 102 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,19 @@ veilRecyclerView.setLayoutManager(LinearLayoutManager(this)) // sets LayoutManag
veilRecyclerView.addVeiledItems(15) // add veiled 15 items
```

#### VeilRecyclerFrameView with a horizontal carousel

Automatically masking a horizontal layout is **not supported yet**. Horizontal (carousel) layouts **can** be used if you specify their shimmer layout yourself in advance (and tell the view to use this prepared layout by setting `isPrepared = true`). See `CarouselActivity` for an example
```kotlin
veilRecyclerView.setVeilLayout(
layout = R.layout.item_prepared_shimmer_carousel,
isPrepared = true
)
veilRecyclerView.setAdapter(adapter)
veilRecyclerView.setLayoutManager(LinearLayoutManager(this, RecyclerView.HORIZONTAL, false))
addVeiledItems(15)
```

#### Veil and UnVeil
We can implement veiled skeletons using below methods.

Expand Down
13 changes: 8 additions & 5 deletions androidveil/api/androidveil.api
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ public final class com/skydoves/androidveil/VeilLayout : android/widget/FrameLay
public final fun isVeiled ()Z
public final fun setDefaultChildVisible (Z)V
public final fun setDrawable (Landroid/graphics/drawable/Drawable;)V
public final fun setLayout (I)V
public final fun setLayout (IZ)V
public final fun setLayout (Landroid/view/View;)V
public static synthetic fun setLayout$default (Lcom/skydoves/androidveil/VeilLayout;IZILjava/lang/Object;)V
public final fun setRadius (F)V
public final fun setShimmer (Lcom/facebook/shimmer/Shimmer;)V
public final fun setShimmerEnable (Z)V
Expand All @@ -41,17 +42,19 @@ public final class com/skydoves/androidveil/VeilRecyclerFrameView : android/widg
public final fun getShimmer ()Lcom/facebook/shimmer/Shimmer;
public final fun getShimmerEnable ()Z
public final fun getVeiledRecyclerView ()Landroidx/recyclerview/widget/RecyclerView;
public final fun isPrepared ()Z
public final fun isVeiled ()Z
public final fun setAdapter (Landroidx/recyclerview/widget/RecyclerView$Adapter;)V
public final fun setAdapter (Landroidx/recyclerview/widget/RecyclerView$Adapter;Landroidx/recyclerview/widget/RecyclerView$LayoutManager;)V
public final fun setDefaultChildVisible (Z)V
public final fun setLayoutManager (Landroidx/recyclerview/widget/RecyclerView$LayoutManager;)V
public final fun setPrepared (Z)V
public final fun setShimmer (Lcom/facebook/shimmer/Shimmer;)V
public final fun setShimmerEnable (Z)V
public final fun setVeilLayout (I)V
public final fun setVeilLayout (II)V
public final fun setVeilLayout (ILcom/skydoves/androidveil/VeiledItemOnClickListener;)V
public final fun setVeilLayout (ILcom/skydoves/androidveil/VeiledItemOnClickListener;I)V
public final fun setVeilLayout (IIZLcom/skydoves/androidveil/VeiledItemOnClickListener;)V
public final fun setVeilLayout (IZLcom/skydoves/androidveil/VeiledItemOnClickListener;)V
public static synthetic fun setVeilLayout$default (Lcom/skydoves/androidveil/VeilRecyclerFrameView;IIZLcom/skydoves/androidveil/VeiledItemOnClickListener;ILjava/lang/Object;)V
public static synthetic fun setVeilLayout$default (Lcom/skydoves/androidveil/VeilRecyclerFrameView;IZLcom/skydoves/androidveil/VeiledItemOnClickListener;ILjava/lang/Object;)V
public final fun unVeil ()V
public final fun veil ()V
}
Expand Down
148 changes: 101 additions & 47 deletions androidveil/src/main/java/com/skydoves/androidveil/VeilLayout.kt
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,20 @@ public class VeilLayout : FrameLayout {
public var drawable: Drawable? = null

@LayoutRes
public var layout: Int = -1
set(value) {
field = value
invalidateLayout(value)
private var layout: Int = -1
private var isPrepared: Boolean = false

public fun setLayout(@LayoutRes layout: Int, isPrepared: Boolean = false) {
this.layout = layout
this.isPrepared = isPrepared
if (isPrepared) {
defaultChildVisible = true
}
invalidateLayout(layout)
}

@LayoutRes
public fun getLayout(): Int = layout

public var isVeiled: Boolean = false
private set
Expand All @@ -86,13 +95,21 @@ public class VeilLayout : FrameLayout {
set(value) {
field = value
shimmerContainer.setShimmer(value)
getPreparedView()?.setShimmer(value)
}
public var shimmerEnable: Boolean = true
set(value) {
field = value
when (value) {
true -> shimmerContainer.setShimmer(shimmer)
false -> shimmerContainer.setShimmer(nonShimmer)
true -> {
shimmerContainer.setShimmer(shimmer)
getPreparedView()?.setShimmer(shimmer)
}

false -> {
shimmerContainer.setShimmer(nonShimmer)
getPreparedView()?.setShimmer(nonShimmer)
}
}
}
public var defaultChildVisible: Boolean = false
Expand Down Expand Up @@ -170,6 +187,10 @@ public class VeilLayout : FrameLayout {
defaultChildVisible =
a.getBoolean(R.styleable.VeilLayout_veilLayout_defaultChildVisible, defaultChildVisible)
}
if (a.hasValue(R.styleable.VeilLayout_veilLayout_isPrepared)) {
isPrepared =
a.getBoolean(R.styleable.VeilLayout_veilLayout_isPrepared, isPrepared)
}
} finally {
a.recycle()
}
Expand All @@ -192,6 +213,9 @@ public class VeilLayout : FrameLayout {

/** Remove previous views and inflate a new layout using an inflated view. */
public fun setLayout(layout: View) {
require(!isPrepared || layout is ShimmerFrameLayout) {
"If you place a 'prepared' Layout, then it must be a ShimmerFrameLayout"
}
removeAllViews()
addView(layout)
shimmerContainer.removeAllViews()
Expand All @@ -202,8 +226,20 @@ public class VeilLayout : FrameLayout {
override fun onFinishInflate() {
super.onFinishInflate()
removeView(shimmerContainer)
addView(shimmerContainer)
addMaskElements(this)
if (!isPrepared) {
// The layout is not pre-shimmering, this VeilLayout will try to make a close representation
addView(shimmerContainer)
addMaskElements(this)
}
// Invalidate the whole masked view.
invalidate()

// Auto veiled
this.isVeiled = !this.isVeiled
when (this.isVeiled) {
true -> unVeil()
false -> veil()
}
}

/**
Expand All @@ -218,48 +254,50 @@ public class VeilLayout : FrameLayout {
if (child is ViewGroup) {
addMaskElements(child)
} else {
var marginX = 0f
var marginY = 0f
var grandParent = parent.parent
while (grandParent !is VeilLayout) {
if (grandParent is ViewGroup) {
val params = grandParent.layoutParams
if (params is MarginLayoutParams) {
marginX += grandParent.x
marginY += grandParent.y
}
grandParent = grandParent.parent
} else {
break
}
}

// create a masked view
View(context).apply {
layoutParams = LayoutParams(child.width, child.height)
x = marginX + parent.x + child.x
y = marginY + parent.y + child.y
setBackgroundColor(baseColor)

background = drawable ?: GradientDrawable().apply {
setColor(Color.DKGRAY)
cornerRadius = radius
}
shimmerContainer.addView(this)
}
val (marginX, marginY) = findMargins(parent)
val view = createMaskedView(child, marginX, parent, marginY)
shimmerContainer.addView(view)
}
}
}
}

// Invalidate the whole masked view.
invalidate()
private fun createMaskedView(
child: View,
marginX: Float,
parent: ViewGroup,
marginY: Float
): View {
return View(context).apply {
layoutParams = LayoutParams(child.width, child.height)
x = marginX + parent.x + child.x
y = marginY + parent.y + child.y
setBackgroundColor(baseColor)

background = drawable ?: GradientDrawable().apply {
setColor(Color.DKGRAY)
cornerRadius = radius
}
}
}

// Auto veiled
this.isVeiled = !this.isVeiled
when (this.isVeiled) {
true -> unVeil()
false -> veil()
private fun findMargins(parent: ViewGroup): Pair<Float, Float> {
var marginX = 0f
var marginY = 0f
var grandParent = parent.parent
while (grandParent !is VeilLayout) {
if (grandParent is ViewGroup) {
val params = grandParent.layoutParams
if (params is MarginLayoutParams) {
marginX += grandParent.x
marginY += grandParent.y
}
grandParent = grandParent.parent
} else {
break
}
}
return Pair(marginX, marginY)
}

/** Make appear the mask. */
Expand All @@ -282,9 +320,17 @@ public class VeilLayout : FrameLayout {

/** Starts the shimmer animation. */
public fun startShimmer() {
this.shimmerContainer.visible()
if (this.shimmerEnable) {
this.shimmerContainer.startShimmer()
if (shimmerEnable) {
if (isPrepared) {
getPreparedView()?.apply {
setShimmer(shimmer)
visible()
startShimmer()
}
} else {
shimmerContainer.visible()
shimmerContainer.startShimmer()
}
}
if (!this.defaultChildVisible) {
setChildVisibility(false)
Expand Down Expand Up @@ -313,4 +359,12 @@ public class VeilLayout : FrameLayout {
super.invalidate()
this.shimmerContainer.invalidate()
}

private fun getPreparedView(): ShimmerFrameLayout? {
if (childCount > 0) {
val view = getChildAt(0)
if (view is ShimmerFrameLayout) return view
}
return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import android.util.AttributeSet
import android.widget.RelativeLayout
import androidx.annotation.ColorInt
import androidx.annotation.FloatRange
import androidx.annotation.IntRange
import androidx.annotation.LayoutRes
import androidx.annotation.Px
import androidx.recyclerview.widget.GridLayoutManager
Expand Down Expand Up @@ -68,6 +69,7 @@ public class VeilRecyclerFrameView : RelativeLayout {
public var shimmer: Shimmer? = null
public var shimmerEnable: Boolean = true
public var defaultChildVisible: Boolean = false
public var isPrepared: Boolean = false
private var isItemWrapContentWidth = false
private var isItemWrapContentHeight = true
private val threshold = 10
Expand Down Expand Up @@ -145,6 +147,10 @@ public class VeilRecyclerFrameView : RelativeLayout {
defaultChildVisible
)
}
if (a.hasValue(R.styleable.VeilRecyclerFrameView_veilFrame_isPrepared)) {
isPrepared =
a.getBoolean(R.styleable.VeilRecyclerFrameView_veilFrame_isPrepared, isPrepared)
}
if (a.hasValue(R.styleable.VeilRecyclerFrameView_veilFrame_isItemWrapContentWidth)) {
isItemWrapContentWidth = a.getBoolean(
R.styleable.VeilRecyclerFrameView_veilFrame_isItemWrapContentWidth,
Expand Down Expand Up @@ -173,56 +179,46 @@ public class VeilRecyclerFrameView : RelativeLayout {
}

if (this.layout != -1) {
setVeilLayout(this.layout)
setVeilLayout(layout = this.layout, isPrepared = this.isPrepared)
}
}

/** Sets mask layout. */
public fun setVeilLayout(@LayoutRes layout: Int) {
this.veiledAdapter =
VeiledAdapter(
userLayout = layout,
isListItemWrapContentWidth = isItemWrapContentWidth,
isListItemWrapContentHeight = isItemWrapContentHeight
)
this.veiledRecyclerView.adapter = this.veiledAdapter
requestLayout()
}

/** Sets mask layout and VeiledItemOnClickListener. */
/** Sets mask layout */
public fun setVeilLayout(
@LayoutRes layout: Int,
onItemClickListener: VeiledItemOnClickListener
isPrepared: Boolean = false,
onItemClickListener: VeiledItemOnClickListener? = null
) {
this.veiledAdapter =
VeiledAdapter(
userLayout = layout,
isPrepared = isPrepared,
onItemClickListener = onItemClickListener,
isListItemWrapContentWidth = isItemWrapContentWidth,
isListItemWrapContentHeight = isItemWrapContentHeight
)
this.veiledRecyclerView.adapter = this.veiledAdapter
}

/** Sets mask layout and adds masked items. */
public fun setVeilLayout(@LayoutRes layout: Int, size: Int) {
this.setVeilLayout(layout)
this.addVeiledItems(size)
requestLayout()
}

/** Sets mask layout and VeiledItemOnClickListener and adds masked items. */
/** Sets mask layout and adds masked items. */
public fun setVeilLayout(
@LayoutRes layout: Int,
onItemClickListener: VeiledItemOnClickListener,
size: Int
@IntRange(from = 1) size: Int,
isPrepared: Boolean = false,
onItemClickListener: VeiledItemOnClickListener? = null
) {
this.setVeilLayout(layout, onItemClickListener)
this.setVeilLayout(
layout = layout,
isPrepared = isPrepared,
onItemClickListener = onItemClickListener
)
this.addVeiledItems(size)
requestLayout()
}

/** Adds masked items. */
public fun addVeiledItems(size: Int) {
public fun addVeiledItems(@IntRange(from = 1) size: Int) {
val paramList = ArrayList<VeilParams>()
for (i in 0 until size) {
paramList.add(
Expand Down Expand Up @@ -265,16 +261,19 @@ public class VeilRecyclerFrameView : RelativeLayout {
is GridLayoutManager ->
this.veiledRecyclerView.layoutManager =
GridLayoutManager(context, layoutManager.spanCount)

is StaggeredGridLayoutManager ->
this.veiledRecyclerView.layoutManager =
StaggeredGridLayoutManager(layoutManager.spanCount, layoutManager.orientation)

is LinearLayoutManager ->
this.veiledRecyclerView.layoutManager =
LinearLayoutManager(
context,
layoutManager.orientation,
layoutManager.reverseLayout
)

else -> this.veiledRecyclerView.layoutManager
}
}
Expand Down
Loading

0 comments on commit 3bd2f41

Please sign in to comment.