diff --git a/app/src/main/java/com/nikhilpanju/fabfilter/main/MainActivity.kt b/app/src/main/java/com/nikhilpanju/fabfilter/main/MainActivity.kt index d84c5bb..ddd03f1 100644 --- a/app/src/main/java/com/nikhilpanju/fabfilter/main/MainActivity.kt +++ b/app/src/main/java/com/nikhilpanju/fabfilter/main/MainActivity.kt @@ -8,7 +8,6 @@ import android.os.Bundle import android.view.View import android.widget.TextView import androidx.appcompat.app.AppCompatActivity -import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.view.GravityCompat import androidx.drawerlayout.widget.DrawerLayout import androidx.recyclerview.widget.LinearLayoutManager @@ -29,17 +28,20 @@ class MainActivity : AppCompatActivity() { private val recyclerView: RecyclerView by bindView(R.id.recycler_view) private val appbar: AppBarLayout by bindView(R.id.appbar) + private val toolbar: View by bindView(R.id.appbar_container) + private val toolbarTitle: View by bindView(R.id.toolbar_title) + private val placeHolderToolbar: View by bindView(R.id.place_holder_toolbar) private val drawerIcon: View by bindView(R.id.drawer_icon) - // layout/nav_drawer views private val drawerLayout: DrawerLayout by bindView(R.id.drawer_layout) private val animationSpeedSeekbar: CrystalSeekbar by bindView(R.id.animation_speed_seekbar) + private val animationSpeedText: TextView by bindView(R.id.animation_speed_text) private val githubCodeLink: TextView by bindView(R.id.github_code_link) private val githubMeLink: TextView by bindView(R.id.github_me_link) - private lateinit var mainListAdapter: MainListAdapter + private lateinit var onToolbarOffsetChangedListener: OnToolbarOffsetChangedListener /** * Used by FiltersLayout since we don't want to expose mainListAdapter (why?) @@ -56,8 +58,13 @@ class MainActivity : AppCompatActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) - // Appbar behavior init - (appbar.layoutParams as CoordinatorLayout.LayoutParams).behavior = ToolbarBehavior() + onToolbarOffsetChangedListener = OnToolbarOffsetChangedListener( + toolbar, + toolbarTitle, + drawerIcon, + placeHolderToolbar + ) + appbar.addOnOffsetChangedListener(onToolbarOffsetChangedListener) // RecyclerView Init mainListAdapter = MainListAdapter(this) @@ -86,6 +93,13 @@ class MainActivity : AppCompatActivity() { startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(getString(resId)))) } + override fun onDestroy() { + if (::onToolbarOffsetChangedListener.isInitialized) { + appbar.removeOnOffsetChangedListener(onToolbarOffsetChangedListener) + } + super.onDestroy() + } + /** * Called from FiltersLayout to get adapter scale down animator */ diff --git a/app/src/main/java/com/nikhilpanju/fabfilter/main/OnToolbarOffsetChangedListener.kt b/app/src/main/java/com/nikhilpanju/fabfilter/main/OnToolbarOffsetChangedListener.kt new file mode 100644 index 0000000..e59d3d0 --- /dev/null +++ b/app/src/main/java/com/nikhilpanju/fabfilter/main/OnToolbarOffsetChangedListener.kt @@ -0,0 +1,94 @@ +package com.nikhilpanju.fabfilter.main + +import android.view.View +import com.google.android.material.appbar.AppBarLayout +import kotlin.math.roundToInt + +class OnToolbarOffsetChangedListener( + private val toolbar: View, + private val toolbarTitle: View, + private val drawerIcon: View, + /** + * [placeHolderToolbar] is a dummy invisible view to make sure that whole toolbar will not + * be hidden in scroll up + */ + private val placeHolderToolbar: View +) : AppBarLayout.OnOffsetChangedListener { + companion object { + private const val SCALE = 0.6F + } + + private var toolbarCollapsedHeight: Int = -1 + private var toolbarOriginalHeight: Int = -1 + + private var initialized: Boolean = false + + init { + /** + * Post a function that set calculated collapsed size on [placeHolderToolbar] + */ + placeHolderToolbar.post { + initToolbarSize() + + placeHolderToolbar.layoutParams.height = toolbarCollapsedHeight + placeHolderToolbar.requestLayout() + } + + } + + override fun onOffsetChanged(appBarLayout: AppBarLayout?, verticalOffset: Int) { + if (appBarLayout == null || appBarLayout.totalScrollRange <= 0) { + // views are not initialized yet + return + } + + /** + * Calculate Toolbar collapse offset from 1 (Expanded) to 0 (fully collapsed) + */ + updateToolbarView( + (appBarLayout.totalScrollRange + verticalOffset).toFloat() + / appBarLayout.totalScrollRange + ) + } + + /** + * Tales [offset] and calculates related toolbar height and drawer icon translation + * and toolbarTitle scale + * + * @param [offset] Toolbar collapse offset from 1 (Expanded) to 0 (fully collapsed) + */ + private fun updateToolbarView( + offset: Float + ) { + initToolbarSize() + val newHeight = toolbarCollapsedHeight + (offset * (toolbarOriginalHeight - toolbarCollapsedHeight)).roundToInt() + if (newHeight != toolbar.layoutParams.height) { + toolbar.layoutParams.height = newHeight + toolbar.requestLayout() + } + + // Hamburger menu transition + drawerIcon.translationY = -((1 - offset) * toolbarOriginalHeight) + + /** + * It will map [0, 1] range to [0.6, 1] range + * Title scale should not be less than 0.6 + */ + val ratio = SCALE + (offset * (1 - SCALE)) + + // Title scale + toolbarTitle.scaleX = ratio + toolbarTitle.scaleY = toolbarTitle.scaleX + } + + /** + * Set the required view variables. Only will be accessed once because of the [initialized] variable. + */ + private fun initToolbarSize() { + if (initialized) return + initialized = true + + toolbarOriginalHeight = toolbar.layoutParams.height + toolbarCollapsedHeight = (toolbarOriginalHeight * SCALE).roundToInt() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/nikhilpanju/fabfilter/main/ToolbarBehavior.kt b/app/src/main/java/com/nikhilpanju/fabfilter/main/ToolbarBehavior.kt deleted file mode 100644 index ea3839c..0000000 --- a/app/src/main/java/com/nikhilpanju/fabfilter/main/ToolbarBehavior.kt +++ /dev/null @@ -1,101 +0,0 @@ -package com.nikhilpanju.fabfilter.main - -import android.view.View -import androidx.coordinatorlayout.widget.CoordinatorLayout -import androidx.core.view.ViewCompat -import com.google.android.material.appbar.AppBarLayout -import com.nikhilpanju.fabfilter.R - -/** - * This behavior animates the toolbar elements (toolbarTitle and drawerIcon) as - * the recyclerView in MainActivity scrolls - */ -class ToolbarBehavior : CoordinatorLayout.Behavior() { - private lateinit var toolbar: View - private lateinit var toolbarTitle: View - private lateinit var drawerIcon: View - - private var toolbarOriginalHeight: Float = -1f - private var toolbarCollapsedHeight: Float = -1f - private var viewsSet = false - private var minScale = 0.6f - - /** - * Set the required view variables. Only accessed once because of the viewsSet variable. - */ - private fun getViews(child: AppBarLayout) { - if (viewsSet) return - viewsSet = true - - toolbar = child.findViewById(R.id.appbar_container) - toolbarTitle = toolbar.findViewById(R.id.toolbar_title) - drawerIcon = toolbar.findViewById(R.id.drawer_icon) - - toolbarOriginalHeight = toolbar.layoutParams.height.toFloat() - toolbarCollapsedHeight = toolbarOriginalHeight * minScale - } - - /** - * Consume if vertical scroll because we don't care about other scrolls - */ - override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, child: AppBarLayout, directTargetChild: View, - target: View, axes: Int, type: Int): Boolean { - getViews(child) - return axes == ViewCompat.SCROLL_AXIS_VERTICAL || - super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, axes, type) - } - - - /** - * Perform actual animation by determining the dY amount - */ - override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: AppBarLayout, target: View, - dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, - type: Int, consumed: IntArray) { - super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, type, consumed) - getViews(child) - - if (dyConsumed > 0) { - - // scroll up: - if (toolbar.layoutParams.height > toolbarCollapsedHeight) { - - //--- shrink toolbar - val height = toolbar.layoutParams.height - dyConsumed - toolbar.layoutParams.height = if (height < toolbarCollapsedHeight) toolbarCollapsedHeight.toInt() else height - toolbar.requestLayout() - - //--- translate up drawer icon - var translate: Float = (toolbarOriginalHeight - toolbar.layoutParams.height) / (toolbarOriginalHeight - toolbarCollapsedHeight) - translate *= toolbarOriginalHeight - drawerIcon.translationY = -translate - - //--- title - val scale = toolbar.layoutParams.height / toolbarOriginalHeight - toolbarTitle.scaleX = if (scale < minScale) minScale else scale - toolbarTitle.scaleY = toolbarTitle.scaleX - } - } else if (dyUnconsumed < 0) { - - // scroll down - if (toolbar.layoutParams.height < toolbarOriginalHeight) { - - //--- expand toolbar - // subtract because dyUnconsumed is < 0 - val height = toolbar.layoutParams.height - dyUnconsumed - toolbar.layoutParams.height = if (height > toolbarOriginalHeight) toolbarOriginalHeight.toInt() else height - toolbar.requestLayout() - - //--- translate down drawer icon - var translate: Float = (toolbarOriginalHeight - toolbar.layoutParams.height) / (toolbarOriginalHeight - toolbarCollapsedHeight) - translate *= toolbarOriginalHeight - drawerIcon.translationY = -translate - - //--- title - val scale = toolbar.layoutParams.height / toolbarOriginalHeight - toolbarTitle.scaleX = if (scale < minScale) minScale else scale - toolbarTitle.scaleY = toolbarTitle.scaleX - } - } - } -} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 3bcfc69..a35009f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,6 +1,5 @@ - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file