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