From 499ab689ff5fbb0b016fc272799e839537dbfa83 Mon Sep 17 00:00:00 2001
From: radiopatrick
Date: Wed, 24 Jan 2024 13:46:01 +0100
Subject: [PATCH 1/8] Provide feature to have prepared shimmer layout
add separate activities in demo app, each demonstrates one type of recyclerview
---
.../com/skydoves/androidveil/VeilLayout.kt | 110 +++++++++++-------
.../androidveil/VeilRecyclerFrameView.kt | 49 ++++----
.../com/skydoves/androidveil/VeiledAdapter.kt | 9 +-
app/src/main/AndroidManifest.xml | 3 +-
.../androidveildemo/CarouselActivity.kt | 80 +++++++++++++
.../androidveildemo/DetailActivity.kt | 1 -
.../{SecondActivity.kt => GridActivity.kt} | 14 ++-
.../skydoves/androidveildemo/MainActivity.kt | 49 +++++++-
.../androidveildemo/profile/ProfileAdapter.kt | 6 +-
.../profile/ProfileCarouselAdapter.kt | 64 ++++++++++
app/src/main/res/layout/activity_carousel.xml | 45 +++++++
...{activity_second.xml => activity_grid.xml} | 2 +-
app/src/main/res/layout/activity_main.xml | 45 ++++++-
.../main/res/layout/item_preview_carousel.xml | 45 +++++++
...item_preview.xml => item_preview_grid.xml} | 0
.../main/res/layout/item_profile_carousel.xml | 51 ++++++++
...item_profile.xml => item_profile_list.xml} | 0
app/src/main/res/values/dimens.xml | 8 ++
18 files changed, 485 insertions(+), 96 deletions(-)
create mode 100644 app/src/main/java/com/skydoves/androidveildemo/CarouselActivity.kt
rename app/src/main/java/com/skydoves/androidveildemo/{SecondActivity.kt => GridActivity.kt} (83%)
create mode 100644 app/src/main/java/com/skydoves/androidveildemo/profile/ProfileCarouselAdapter.kt
create mode 100644 app/src/main/res/layout/activity_carousel.xml
rename app/src/main/res/layout/{activity_second.xml => activity_grid.xml} (96%)
create mode 100644 app/src/main/res/layout/item_preview_carousel.xml
rename app/src/main/res/layout/{item_preview.xml => item_preview_grid.xml} (100%)
create mode 100644 app/src/main/res/layout/item_profile_carousel.xml
rename app/src/main/res/layout/{item_profile.xml => item_profile_list.xml} (100%)
create mode 100644 app/src/main/res/values/dimens.xml
diff --git a/androidveil/src/main/java/com/skydoves/androidveil/VeilLayout.kt b/androidveil/src/main/java/com/skydoves/androidveil/VeilLayout.kt
index 1b89a58..45d13fe 100644
--- a/androidveil/src/main/java/com/skydoves/androidveil/VeilLayout.kt
+++ b/androidveil/src/main/java/com/skydoves/androidveil/VeilLayout.kt
@@ -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
@@ -120,7 +129,7 @@ public class VeilLayout : FrameLayout {
context: Context,
attrs: AttributeSet?,
defStyleAttr: Int,
- defStyleRes: Int
+ defStyleRes: Int,
) : super(
context,
attrs,
@@ -201,9 +210,20 @@ public class VeilLayout : FrameLayout {
/** Invokes addMaskElements method after inflating. */
override fun onFinishInflate() {
super.onFinishInflate()
- removeView(shimmerContainer)
- addView(shimmerContainer)
- addMaskElements(this)
+ if (!isPrepared) {
+ removeView(shimmerContainer)
+ addView(shimmerContainer)
+ addMaskElements(this)
+ }
+ // Invalidate the whole masked view.
+ invalidate()
+
+ // Auto veiled
+ this.isVeiled = !this.isVeiled
+ when (this.isVeiled) {
+ true -> unVeil()
+ false -> veil()
+ }
}
/**
@@ -218,48 +238,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 {
+ 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. */
diff --git a/androidveil/src/main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt b/androidveil/src/main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt
index 9927cb3..6c4b9c9 100644
--- a/androidveil/src/main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt
+++ b/androidveil/src/main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt
@@ -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
@@ -173,56 +174,47 @@ public class VeilRecyclerFrameView : RelativeLayout {
}
if (this.layout != -1) {
- setVeilLayout(this.layout)
+ // TODO get isPrepared from attrs as well
+ setVeilLayout(layout = this.layout, isPrepared = false)
}
}
- /** 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,
+ 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()
for (i in 0 until size) {
paramList.add(
@@ -252,7 +244,7 @@ public class VeilRecyclerFrameView : RelativeLayout {
/** Sets userRecyclerView's adapter and RecyclerViews LayoutManager. */
public fun setAdapter(
adapter: RecyclerView.Adapter<*>?,
- layoutManager: RecyclerView.LayoutManager
+ layoutManager: RecyclerView.LayoutManager,
) {
this.setAdapter(adapter)
this.setLayoutManager(layoutManager)
@@ -265,9 +257,11 @@ 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(
@@ -275,6 +269,7 @@ public class VeilRecyclerFrameView : RelativeLayout {
layoutManager.orientation,
layoutManager.reverseLayout
)
+
else -> this.veiledRecyclerView.layoutManager
}
}
diff --git a/androidveil/src/main/java/com/skydoves/androidveil/VeiledAdapter.kt b/androidveil/src/main/java/com/skydoves/androidveil/VeiledAdapter.kt
index 4b91d23..3019402 100644
--- a/androidveil/src/main/java/com/skydoves/androidveil/VeiledAdapter.kt
+++ b/androidveil/src/main/java/com/skydoves/androidveil/VeiledAdapter.kt
@@ -24,9 +24,10 @@ import com.skydoves.androidveil.databinding.VeilItemLayoutBinding
internal class VeiledAdapter(
@LayoutRes private val userLayout: Int,
+ private val isPrepared: Boolean,
private val onItemClickListener: VeiledItemOnClickListener? = null,
private val isListItemWrapContentWidth: Boolean = false,
- private val isListItemWrapContentHeight: Boolean = true
+ private val isListItemWrapContentHeight: Boolean = true,
) : RecyclerView.Adapter() {
private val veilParamList: MutableList = mutableListOf()
@@ -53,8 +54,8 @@ internal class VeiledAdapter(
getLayoutParams(isListItemWrapContentWidth),
getLayoutParams(isListItemWrapContentHeight)
)
- if (layout == -1) {
- layout = userLayout
+ if (getLayout() == -1) {
+ setLayout(userLayout, isPrepared)
veilParams.shimmer?.let {
shimmer = it
} ?: let {
@@ -69,7 +70,7 @@ internal class VeiledAdapter(
radius = veilParams.radius
drawable = veilParams.drawable
shimmerEnable = veilParams.shimmerEnable
- defaultChildVisible = veilParams.defaultChildVisible
+ defaultChildVisible = veilParams.defaultChildVisible || isPrepared
} else {
startShimmer()
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 0f71e99..60f1afc 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -29,7 +29,8 @@
-
+
+
diff --git a/app/src/main/java/com/skydoves/androidveildemo/CarouselActivity.kt b/app/src/main/java/com/skydoves/androidveildemo/CarouselActivity.kt
new file mode 100644
index 0000000..206f05c
--- /dev/null
+++ b/app/src/main/java/com/skydoves/androidveildemo/CarouselActivity.kt
@@ -0,0 +1,80 @@
+/*
+ * Designed and developed by 2018 skydoves (Jaewoong Eum)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.skydoves.androidveildemo
+
+import android.content.Intent
+import android.os.Bundle
+import android.os.Handler
+import android.os.Looper
+import android.widget.Toast
+import androidx.appcompat.app.AppCompatActivity
+import androidx.recyclerview.widget.LinearLayoutManager
+import androidx.recyclerview.widget.RecyclerView
+import com.skydoves.androidveil.VeiledItemOnClickListener
+import com.skydoves.androidveildemo.databinding.ActivityCarouselBinding
+import com.skydoves.androidveildemo.profile.ListItemUtils
+import com.skydoves.androidveildemo.profile.Profile
+import com.skydoves.androidveildemo.profile.ProfileAdapter
+import com.skydoves.androidveildemo.profile.ProfileCarouselAdapter
+
+class CarouselActivity :
+ AppCompatActivity(),
+ VeiledItemOnClickListener,
+ ProfileCarouselAdapter.ProfileViewHolder.Delegate {
+
+ private val adapter = ProfileCarouselAdapter(this)
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ val binding = ActivityCarouselBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ // sets VeilRecyclerView's properties
+ binding.veilRecyclerView.run {
+ setVeilLayout(
+ layout = R.layout.item_preview_carousel,
+ isPrepared = true,
+ onItemClickListener = this@CarouselActivity
+ )
+ setAdapter(adapter)
+ setLayoutManager(LinearLayoutManager(this@CarouselActivity, RecyclerView.HORIZONTAL, false))
+ addVeiledItems(6)
+ }
+
+ // add profile times to adapter
+ adapter.addProfiles(ListItemUtils.getProfiles(this))
+
+ // delay-auto-unveil
+ Handler(Looper.getMainLooper()).postDelayed(
+ {
+ binding.veilRecyclerView.unVeil()
+ },
+ 5000
+ )
+ }
+
+ /** OnItemClickListener by Veiled Item */
+ override fun onItemClicked(pos: Int) {
+ Toast.makeText(this, getString(R.string.msg_loading), Toast.LENGTH_SHORT).show()
+ }
+
+ /** OnItemClickListener by User Item */
+ override fun onItemClickListener(profile: Profile) {
+ startActivity(Intent(this, DetailActivity::class.java))
+ }
+}
diff --git a/app/src/main/java/com/skydoves/androidveildemo/DetailActivity.kt b/app/src/main/java/com/skydoves/androidveildemo/DetailActivity.kt
index fbd0c26..89d31ed 100644
--- a/app/src/main/java/com/skydoves/androidveildemo/DetailActivity.kt
+++ b/app/src/main/java/com/skydoves/androidveildemo/DetailActivity.kt
@@ -26,7 +26,6 @@ import com.skydoves.androidveildemo.databinding.ActivityDetailBinding
* Developed by skydoves on 2018-10-30.
* Copyright (c) 2018 skydoves rights reserved.
*/
-
class DetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
diff --git a/app/src/main/java/com/skydoves/androidveildemo/SecondActivity.kt b/app/src/main/java/com/skydoves/androidveildemo/GridActivity.kt
similarity index 83%
rename from app/src/main/java/com/skydoves/androidveildemo/SecondActivity.kt
rename to app/src/main/java/com/skydoves/androidveildemo/GridActivity.kt
index 8f0dcc7..ec08b4b 100644
--- a/app/src/main/java/com/skydoves/androidveildemo/SecondActivity.kt
+++ b/app/src/main/java/com/skydoves/androidveildemo/GridActivity.kt
@@ -22,12 +22,12 @@ import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.GridLayoutManager
import com.skydoves.androidveil.VeiledItemOnClickListener
-import com.skydoves.androidveildemo.databinding.ActivitySecondBinding
+import com.skydoves.androidveildemo.databinding.ActivityGridBinding
import com.skydoves.androidveildemo.profile.ListItemUtils
import com.skydoves.androidveildemo.profile.Profile
import com.skydoves.androidveildemo.profile.ProfileAdapter
-class SecondActivity :
+class GridActivity :
AppCompatActivity(),
VeiledItemOnClickListener,
ProfileAdapter.ProfileViewHolder.Delegate {
@@ -37,14 +37,18 @@ class SecondActivity :
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- val binding = ActivitySecondBinding.inflate(layoutInflater)
+ val binding = ActivityGridBinding.inflate(layoutInflater)
setContentView(binding.root)
// sets VeilRecyclerView's properties
binding.veilFrameView.run {
- setVeilLayout(R.layout.item_preview, this@SecondActivity)
+ setVeilLayout(
+ layout = R.layout.item_preview_grid,
+ isPrepared = false,
+ onItemClickListener = this@GridActivity
+ )
setAdapter(adapter)
- setLayoutManager(GridLayoutManager(this@SecondActivity, 2))
+ setLayoutManager(GridLayoutManager(this@GridActivity, 2))
addVeiledItems(12)
}
diff --git a/app/src/main/java/com/skydoves/androidveildemo/MainActivity.kt b/app/src/main/java/com/skydoves/androidveildemo/MainActivity.kt
index b282ea9..2055b53 100644
--- a/app/src/main/java/com/skydoves/androidveildemo/MainActivity.kt
+++ b/app/src/main/java/com/skydoves/androidveildemo/MainActivity.kt
@@ -29,27 +29,32 @@ import com.skydoves.androidveildemo.profile.ListItemUtils
import com.skydoves.androidveildemo.profile.Profile
import com.skydoves.androidveildemo.profile.ProfileAdapter
+
/**
* Developed by skydoves on 2018-10-30.
* Copyright (c) 2018 skydoves rights reserved.
*/
-
class MainActivity :
AppCompatActivity(),
VeiledItemOnClickListener,
ProfileAdapter.ProfileViewHolder.Delegate {
private val adapter = ProfileAdapter(this)
+ private var isFloatingMenuOpen = false
+ private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
-
- val binding = ActivityMainBinding.inflate(layoutInflater)
+ binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
+ setupFloatingActionButtons()
// sets VeilRecyclerView's properties
binding.veilRecyclerView.run {
- setVeilLayout(R.layout.item_profile, this@MainActivity)
+ setVeilLayout(
+ layout = R.layout.item_profile_list,
+ onItemClickListener = this@MainActivity
+ )
setAdapter(adapter)
setLayoutManager(LinearLayoutManager(this@MainActivity))
addVeiledItems(15)
@@ -67,6 +72,42 @@ class MainActivity :
)
}
+ private fun setupFloatingActionButtons() {
+ binding.fab.setOnClickListener {
+ if (!isFloatingMenuOpen) {
+ showFloatingMenu()
+ } else {
+ closeFloatingMenu()
+ }
+ }
+ binding.fabGrid.setOnClickListener {
+ startActivity(Intent(this, GridActivity::class.java))
+ }
+ binding.fabCarousel.setOnClickListener {
+ startActivity(Intent(this, CarouselActivity::class.java))
+ }
+ }
+
+ override fun onBackPressed() {
+ if (isFloatingMenuOpen) {
+ closeFloatingMenu()
+ } else {
+ super.onBackPressed()
+ }
+ }
+
+ private fun showFloatingMenu() {
+ isFloatingMenuOpen = true
+ binding.fabCarousel.animate().translationY(-resources.getDimension(R.dimen.distance_fab_first))
+ binding.fabGrid.animate().translationY(-resources.getDimension(R.dimen.distance_fab_second))
+ }
+
+ private fun closeFloatingMenu() {
+ isFloatingMenuOpen = false;
+ binding.fabCarousel.animate().translationY(0f);
+ binding.fabGrid.animate().translationY(0f);
+ }
+
/** OnItemClickListener by Veiled Item */
override fun onItemClicked(pos: Int) {
Toast.makeText(this, getString(R.string.msg_loading), Toast.LENGTH_SHORT).show()
diff --git a/app/src/main/java/com/skydoves/androidveildemo/profile/ProfileAdapter.kt b/app/src/main/java/com/skydoves/androidveildemo/profile/ProfileAdapter.kt
index b1c113a..32097ae 100644
--- a/app/src/main/java/com/skydoves/androidveildemo/profile/ProfileAdapter.kt
+++ b/app/src/main/java/com/skydoves/androidveildemo/profile/ProfileAdapter.kt
@@ -19,7 +19,7 @@ package com.skydoves.androidveildemo.profile
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
-import com.skydoves.androidveildemo.databinding.ItemProfileBinding
+import com.skydoves.androidveildemo.databinding.ItemProfileListBinding
/**
* Developed by skydoves on 2018-10-30.
@@ -33,7 +33,7 @@ class ProfileAdapter(
private val profileList: MutableList = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder {
- val binding = ItemProfileBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ val binding = ItemProfileListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return ProfileViewHolder(binding)
}
@@ -54,7 +54,7 @@ class ProfileAdapter(
override fun getItemCount() = this.profileList.size
- class ProfileViewHolder(val binding: ItemProfileBinding) :
+ class ProfileViewHolder(val binding: ItemProfileListBinding) :
RecyclerView.ViewHolder(binding.root) {
fun interface Delegate {
diff --git a/app/src/main/java/com/skydoves/androidveildemo/profile/ProfileCarouselAdapter.kt b/app/src/main/java/com/skydoves/androidveildemo/profile/ProfileCarouselAdapter.kt
new file mode 100644
index 0000000..4be0bdf
--- /dev/null
+++ b/app/src/main/java/com/skydoves/androidveildemo/profile/ProfileCarouselAdapter.kt
@@ -0,0 +1,64 @@
+/*
+ * Designed and developed by 2018 skydoves (Jaewoong Eum)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.skydoves.androidveildemo.profile
+
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.skydoves.androidveildemo.databinding.ItemProfileCarouselBinding
+
+/**
+ * Developed by skydoves on 2018-10-30.
+ * Copyright (c) 2018 skydoves rights reserved.
+ */
+
+class ProfileCarouselAdapter(
+ private val delegate: ProfileViewHolder.Delegate
+) : RecyclerView.Adapter() {
+
+ private val profileList: MutableList = mutableListOf()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ProfileViewHolder {
+ val binding = ItemProfileCarouselBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return ProfileViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: ProfileViewHolder, position: Int) {
+ val profileItem = profileList[position]
+ holder.binding.apply {
+ profileItem.image?.let { profile.setImageDrawable(it) }
+ name.text = profileItem.name
+ content.text = profileItem.content
+ root.setOnClickListener { delegate.onItemClickListener(profileItem) }
+ }
+ }
+
+ fun addProfiles(profiles: List) {
+ this.profileList.addAll(profiles)
+ notifyDataSetChanged()
+ }
+
+ override fun getItemCount() = this.profileList.size
+
+ class ProfileViewHolder(val binding: ItemProfileCarouselBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ fun interface Delegate {
+ fun onItemClickListener(profile: Profile)
+ }
+ }
+}
diff --git a/app/src/main/res/layout/activity_carousel.xml b/app/src/main/res/layout/activity_carousel.xml
new file mode 100644
index 0000000..fb660af
--- /dev/null
+++ b/app/src/main/res/layout/activity_carousel.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_second.xml b/app/src/main/res/layout/activity_grid.xml
similarity index 96%
rename from app/src/main/res/layout/activity_second.xml
rename to app/src/main/res/layout/activity_grid.xml
index 15f9a54..a07c525 100644
--- a/app/src/main/res/layout/activity_second.xml
+++ b/app/src/main/res/layout/activity_grid.xml
@@ -35,7 +35,7 @@
android:layout_height="match_parent"
app:veilFrame_baseColor="@color/shimmerBase0"
app:veilFrame_highlightColor="@color/shimmerHighlight1"
- app:veilFrame_layout="@layout/item_profile"
+ app:veilFrame_layout="@layout/item_profile_list"
app:veilFrame_radius="4dp"
app:veilFrame_shimmerEnable="true"
app:veilFrame_veiled="true" />
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index 0616c60..85cd503 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -1,5 +1,4 @@
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_preview_carousel.xml b/app/src/main/res/layout/item_preview_carousel.xml
new file mode 100644
index 0000000..f2c1559
--- /dev/null
+++ b/app/src/main/res/layout/item_preview_carousel.xml
@@ -0,0 +1,45 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_preview.xml b/app/src/main/res/layout/item_preview_grid.xml
similarity index 100%
rename from app/src/main/res/layout/item_preview.xml
rename to app/src/main/res/layout/item_preview_grid.xml
diff --git a/app/src/main/res/layout/item_profile_carousel.xml b/app/src/main/res/layout/item_profile_carousel.xml
new file mode 100644
index 0000000..82ba2b5
--- /dev/null
+++ b/app/src/main/res/layout/item_profile_carousel.xml
@@ -0,0 +1,51 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/item_profile.xml b/app/src/main/res/layout/item_profile_list.xml
similarity index 100%
rename from app/src/main/res/layout/item_profile.xml
rename to app/src/main/res/layout/item_profile_list.xml
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..8e79ec2
--- /dev/null
+++ b/app/src/main/res/values/dimens.xml
@@ -0,0 +1,8 @@
+
+
+
+ 65dp
+ 55dp
+ 105dp
+ 12dp
+
From 11dfa1862ca524cc729e4899c76ef9ebfeba5f89 Mon Sep 17 00:00:00 2001
From: radiopatrick
Date: Thu, 25 Jan 2024 16:23:56 +0100
Subject: [PATCH 2/8] finalize loading items for carousel
add toolbar navigation and better FAB UI
add "isPrepared" to attrs
---
.../com/skydoves/androidveil/VeilLayout.kt | 44 +++++++++++--
.../androidveil/VeilRecyclerFrameView.kt | 8 ++-
.../com/skydoves/androidveil/VeiledAdapter.kt | 1 +
.../src/main/res/values/attrs_veil.xml | 3 +
.../androidveildemo/CarouselActivity.kt | 31 +++++++--
.../skydoves/androidveildemo/GridActivity.kt | 8 +++
.../skydoves/androidveildemo/MainActivity.kt | 23 +++++--
app/src/main/res/layout/activity_carousel.xml | 35 ++++++++--
app/src/main/res/layout/activity_grid.xml | 5 +-
app/src/main/res/layout/activity_main.xml | 66 +++++++++++++++----
.../layout/item_prepared_shimmer_carousel.xml | 36 ++++++++++
.../main/res/layout/item_preview_carousel.xml | 45 -------------
.../main/res/layout/item_profile_carousel.xml | 3 +-
app/src/main/res/values/colors.xml | 2 +-
app/src/main/res/values/dimens.xml | 3 +-
15 files changed, 227 insertions(+), 86 deletions(-)
create mode 100644 app/src/main/res/layout/item_prepared_shimmer_carousel.xml
delete mode 100644 app/src/main/res/layout/item_preview_carousel.xml
diff --git a/androidveil/src/main/java/com/skydoves/androidveil/VeilLayout.kt b/androidveil/src/main/java/com/skydoves/androidveil/VeilLayout.kt
index 45d13fe..437df14 100644
--- a/androidveil/src/main/java/com/skydoves/androidveil/VeilLayout.kt
+++ b/androidveil/src/main/java/com/skydoves/androidveil/VeilLayout.kt
@@ -95,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
@@ -179,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()
}
@@ -201,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()
@@ -210,8 +225,9 @@ public class VeilLayout : FrameLayout {
/** Invokes addMaskElements method after inflating. */
override fun onFinishInflate() {
super.onFinishInflate()
+ removeView(shimmerContainer)
if (!isPrepared) {
- removeView(shimmerContainer)
+ // The layout is not pre-shimmering, this VeilLayout will try to make a close representation
addView(shimmerContainer)
addMaskElements(this)
}
@@ -304,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)
@@ -335,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
+ }
}
diff --git a/androidveil/src/main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt b/androidveil/src/main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt
index 6c4b9c9..bdae475 100644
--- a/androidveil/src/main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt
+++ b/androidveil/src/main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt
@@ -69,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
@@ -146,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,
@@ -174,8 +179,7 @@ public class VeilRecyclerFrameView : RelativeLayout {
}
if (this.layout != -1) {
- // TODO get isPrepared from attrs as well
- setVeilLayout(layout = this.layout, isPrepared = false)
+ setVeilLayout(layout = this.layout, isPrepared = this.isPrepared)
}
}
diff --git a/androidveil/src/main/java/com/skydoves/androidveil/VeiledAdapter.kt b/androidveil/src/main/java/com/skydoves/androidveil/VeiledAdapter.kt
index 3019402..4323cdd 100644
--- a/androidveil/src/main/java/com/skydoves/androidveil/VeiledAdapter.kt
+++ b/androidveil/src/main/java/com/skydoves/androidveil/VeiledAdapter.kt
@@ -70,6 +70,7 @@ internal class VeiledAdapter(
radius = veilParams.radius
drawable = veilParams.drawable
shimmerEnable = veilParams.shimmerEnable
+ // Make sure prepared layout (which is the first child view) is always visible
defaultChildVisible = veilParams.defaultChildVisible || isPrepared
} else {
startShimmer()
diff --git a/androidveil/src/main/res/values/attrs_veil.xml b/androidveil/src/main/res/values/attrs_veil.xml
index 255ae6e..276fe69 100644
--- a/androidveil/src/main/res/values/attrs_veil.xml
+++ b/androidveil/src/main/res/values/attrs_veil.xml
@@ -1,4 +1,5 @@
+
72dp
From 8afab08ee85f73a8691750be498496bb4b54a210 Mon Sep 17 00:00:00 2001
From: radiopatrick
Date: Thu, 25 Jan 2024 16:38:02 +0100
Subject: [PATCH 4/8] add default on recycler and adapter as well
---
.../main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt | 2 +-
.../src/main/java/com/skydoves/androidveil/VeiledAdapter.kt | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/androidveil/src/main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt b/androidveil/src/main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt
index d6040f3..edcc213 100644
--- a/androidveil/src/main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt
+++ b/androidveil/src/main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt
@@ -205,7 +205,7 @@ public class VeilRecyclerFrameView : RelativeLayout {
public fun setVeilLayout(
@LayoutRes layout: Int,
@IntRange(from = 1) size: Int,
- isPrepared: Boolean,
+ isPrepared: Boolean = false,
onItemClickListener: VeiledItemOnClickListener? = null
) {
this.setVeilLayout(
diff --git a/androidveil/src/main/java/com/skydoves/androidveil/VeiledAdapter.kt b/androidveil/src/main/java/com/skydoves/androidveil/VeiledAdapter.kt
index 6c642b2..eae7b18 100644
--- a/androidveil/src/main/java/com/skydoves/androidveil/VeiledAdapter.kt
+++ b/androidveil/src/main/java/com/skydoves/androidveil/VeiledAdapter.kt
@@ -24,7 +24,7 @@ import com.skydoves.androidveil.databinding.VeilItemLayoutBinding
internal class VeiledAdapter(
@LayoutRes private val userLayout: Int,
- private val isPrepared: Boolean,
+ private val isPrepared: Boolean = false,
private val onItemClickListener: VeiledItemOnClickListener? = null,
private val isListItemWrapContentWidth: Boolean = false,
private val isListItemWrapContentHeight: Boolean = true
From 60eaacf5944e53150274488eb30bf9e828dfa2b2 Mon Sep 17 00:00:00 2001
From: radiopatrick
Date: Mon, 29 Jan 2024 09:50:12 +0100
Subject: [PATCH 5/8] update api doc
---
androidveil/api/androidveil.api | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/androidveil/api/androidveil.api b/androidveil/api/androidveil.api
index 8690a89..aa19181 100644
--- a/androidveil/api/androidveil.api
+++ b/androidveil/api/androidveil.api
@@ -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
@@ -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
}
From be874289d801ed3ac29b5bd02756c582ba3123ee Mon Sep 17 00:00:00 2001
From: RadioPatrick
Date: Tue, 30 Jan 2024 07:37:19 +0100
Subject: [PATCH 6/8] update readme
---
README.md | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/README.md b/README.md
index 9b48d60..d225644 100644
--- a/README.md
+++ b/README.md
@@ -120,6 +120,17 @@ veilRecyclerView.setAdapter(adapter) // sets your own adapter
veilRecyclerView.setLayoutManager(LinearLayoutManager(this)) // sets LayoutManager
veilRecyclerView.addVeiledItems(15) // add veiled 15 items
```
+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.
From c38bc3dc0bf22cac07044716453e100d859aaacc Mon Sep 17 00:00:00 2001
From: RadioPatrick
Date: Tue, 30 Jan 2024 07:40:19 +0100
Subject: [PATCH 7/8] beautify readme
---
README.md | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index d225644..870a338 100644
--- a/README.md
+++ b/README.md
@@ -120,8 +120,10 @@ veilRecyclerView.setAdapter(adapter) // sets your own adapter
veilRecyclerView.setLayoutManager(LinearLayoutManager(this)) // sets LayoutManager
veilRecyclerView.addVeiledItems(15) // add veiled 15 items
```
-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
+
+#### Carousel View
+
+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,
From c09e75f1813e6dae97f3a9625506771a16c298d8 Mon Sep 17 00:00:00 2001
From: RadioPatrick
Date: Tue, 30 Jan 2024 07:41:02 +0100
Subject: [PATCH 8/8] beautify readme
---
README.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 870a338..9672fbf 100644
--- a/README.md
+++ b/README.md
@@ -121,7 +121,7 @@ veilRecyclerView.setLayoutManager(LinearLayoutManager(this)) // sets LayoutManag
veilRecyclerView.addVeiledItems(15) // add veiled 15 items
```
-#### Carousel View
+#### 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