diff --git a/README.md b/README.md index 9b48d60..9672fbf 100644 --- a/README.md +++ b/README.md @@ -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. 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 } diff --git a/androidveil/src/main/java/com/skydoves/androidveil/VeilLayout.kt b/androidveil/src/main/java/com/skydoves/androidveil/VeilLayout.kt index 1b89a58..a54630d 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 @@ -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 @@ -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() } @@ -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() @@ -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() + } } /** @@ -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 { + 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. */ @@ -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) @@ -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 + } } diff --git a/androidveil/src/main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt b/androidveil/src/main/java/com/skydoves/androidveil/VeilRecyclerFrameView.kt index 9927cb3..edcc213 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 @@ -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 @@ -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, @@ -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() for (i in 0 until size) { paramList.add( @@ -265,9 +261,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 +273,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..eae7b18 100644 --- a/androidveil/src/main/java/com/skydoves/androidveil/VeiledAdapter.kt +++ b/androidveil/src/main/java/com/skydoves/androidveil/VeiledAdapter.kt @@ -24,6 +24,7 @@ import com.skydoves.androidveil.databinding.VeilItemLayoutBinding internal class VeiledAdapter( @LayoutRes private val userLayout: Int, + private val isPrepared: Boolean = false, private val onItemClickListener: VeiledItemOnClickListener? = null, private val isListItemWrapContentWidth: Boolean = false, private val isListItemWrapContentHeight: Boolean = true @@ -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,8 @@ internal class VeiledAdapter( radius = veilParams.radius drawable = veilParams.drawable shimmerEnable = veilParams.shimmerEnable - defaultChildVisible = veilParams.defaultChildVisible + // 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..8322ec2 100644 --- a/androidveil/src/main/res/values/attrs_veil.xml +++ b/androidveil/src/main/res/values/attrs_veil.xml @@ -27,6 +27,7 @@ + @@ -42,5 +43,6 @@ + \ No newline at end of file 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..44a51ac --- /dev/null +++ b/app/src/main/java/com/skydoves/androidveildemo/CarouselActivity.kt @@ -0,0 +1,103 @@ +/* + * 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.ProfileCarouselAdapter + +class CarouselActivity : + AppCompatActivity(), + VeiledItemOnClickListener, + ProfileCarouselAdapter.ProfileViewHolder.Delegate { + + override fun onSupportNavigateUp(): Boolean { + onBackPressed() + return true + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val binding = ActivityCarouselBinding.inflate(layoutInflater) + setContentView(binding.root) + setSupportActionBar(binding.toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setDisplayShowHomeEnabled(true) + + val adapter = ProfileCarouselAdapter(this) + // sets VeilRecyclerView's properties + binding.veilRecyclerView.run { + setVeilLayout( + layout = R.layout.item_prepared_shimmer_carousel, + isPrepared = true, + onItemClickListener = this@CarouselActivity + ) + setAdapter(adapter) + setLayoutManager(LinearLayoutManager(this@CarouselActivity, RecyclerView.HORIZONTAL, false)) + addVeiledItems(10) + } + + binding.veilRecyclerView2.run { + setVeilLayout( + layout = R.layout.item_prepared_shimmer_carousel, + isPrepared = true, + onItemClickListener = this@CarouselActivity + ) + setAdapter(adapter) + setLayoutManager(LinearLayoutManager(this@CarouselActivity, RecyclerView.HORIZONTAL, false)) + addVeiledItems(10) + } + + // add profile times to adapter + adapter.addProfiles(ListItemUtils.getProfiles(this)) + + // delay-auto-unveil + Handler(Looper.getMainLooper()).postDelayed( + { + binding.veilRecyclerView.unVeil() + }, + 3000 + ) + Handler(Looper.getMainLooper()).postDelayed( + { + binding.veilRecyclerView2.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 76% rename from app/src/main/java/com/skydoves/androidveildemo/SecondActivity.kt rename to app/src/main/java/com/skydoves/androidveildemo/GridActivity.kt index 8f0dcc7..fb07637 100644 --- a/app/src/main/java/com/skydoves/androidveildemo/SecondActivity.kt +++ b/app/src/main/java/com/skydoves/androidveildemo/GridActivity.kt @@ -22,29 +22,41 @@ 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 { private val adapter = ProfileAdapter(this) + override fun onSupportNavigateUp(): Boolean { + onBackPressed() + return true + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val binding = ActivitySecondBinding.inflate(layoutInflater) + val binding = ActivityGridBinding.inflate(layoutInflater) setContentView(binding.root) + setSupportActionBar(binding.toolbar) + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setDisplayShowHomeEnabled(true) // 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..8990f59 100644 --- a/app/src/main/java/com/skydoves/androidveildemo/MainActivity.kt +++ b/app/src/main/java/com/skydoves/androidveildemo/MainActivity.kt @@ -20,6 +20,7 @@ import android.content.Intent import android.os.Bundle import android.os.Handler import android.os.Looper +import android.view.View import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.recyclerview.widget.LinearLayoutManager @@ -33,23 +34,27 @@ 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,50 @@ class MainActivity : ) } + private fun setupFloatingActionButtons() { + binding.fab.setOnClickListener { + if (!isFloatingMenuOpen) { + showFloatingMenu() + } else { + closeFloatingMenu() + } + } + binding.fabGrid.setOnClickListener { + startActivity(Intent(this, GridActivity::class.java)) + closeFloatingMenu() + } + binding.fabCarousel.setOnClickListener { + startActivity(Intent(this, CarouselActivity::class.java)) + closeFloatingMenu() + } + } + + override fun onBackPressed() { + if (isFloatingMenuOpen) { + closeFloatingMenu() + } else { + super.onBackPressed() + } + } + + private fun showFloatingMenu() { + isFloatingMenuOpen = true + binding.containerFabCarousel.animate() + .translationY(-resources.getDimension(R.dimen.distance_fab_first)) + binding.containerFabGrid.animate() + .translationY(-resources.getDimension(R.dimen.distance_fab_second)) + binding.fabGridText.visibility = View.VISIBLE + binding.fabCarouselText.visibility = View.VISIBLE + } + + private fun closeFloatingMenu() { + isFloatingMenuOpen = false + binding.containerFabCarousel.animate().translationY(0f) + binding.containerFabGrid.animate().translationY(0f) + binding.fabGridText.visibility = View.GONE + binding.fabCarouselText.visibility = View.GONE + } + /** 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..7e3422a --- /dev/null +++ b/app/src/main/res/layout/activity_carousel.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + \ 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 94% rename from app/src/main/res/layout/activity_second.xml rename to app/src/main/res/layout/activity_grid.xml index 15f9a54..a8c19ac 100644 --- a/app/src/main/res/layout/activity_second.xml +++ b/app/src/main/res/layout/activity_grid.xml @@ -23,7 +23,7 @@ android:orientation="vertical"> diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 0616c60..d335d8c 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. --> - - \ No newline at end of file + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_prepared_shimmer_carousel.xml b/app/src/main/res/layout/item_prepared_shimmer_carousel.xml new file mode 100644 index 0000000..7e2e750 --- /dev/null +++ b/app/src/main/res/layout/item_prepared_shimmer_carousel.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + 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..42689a5 --- /dev/null +++ b/app/src/main/res/layout/item_profile_carousel.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + 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/colors.xml b/app/src/main/res/values/colors.xml index 2e07984..d22af1c 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -26,4 +26,4 @@ #c2c2c2 #ffffff #DFDEDE - \ No newline at end of file + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..142aad8 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,24 @@ + + + + + 72dp + 55dp + 105dp + 12dp + +