chrisbanes / insetter

Insetter is a library to help apps handle WindowInsets more easily
https://chrisbanes.github.io/insetter
Apache License 2.0
1.13k stars 42 forks source link

Request: Remove insets #120

Closed Christophe668 closed 1 day ago

Christophe668 commented 3 years ago

What: Ability to remove insets on views.

Why With some complex views sometimes we have to play with insets in order to match a specific design. With Insetter it makes it super easy and clean to add insets, but we don't have the opportunity yet to remove an inset that was previously applied.

Would it make sense to have a feature like this?

Thanks a lot in advance!

chrisbanes commented 2 years ago

Hey, I'm not sure what you mean. Are you asking for a way to reset everything back to the original state? You could then change the insetter and request an inset pass.

patrick-elmquist commented 2 years ago

Also having this issue in a use case where we have a fragment that should sometimes draw under the status bar and sometimes not.

So I'm currently using this code for handling the status bar, but the issue I'm having is that I can't seem to shake the padding/margin once it's applied, i.e the None case doesn't seem to help.

override fun applyStatusBarOffsetToView(view: View, spacing: Spacing) {
    view.applyInsetter {
        type(statusBars = true) {
            when (spacing) {
                is Padding -> padding(top = true)
                is Margin -> margin(top = true)
                is None -> {
                    // Thought this might work but seems to have no effect
                    // when trying to remove the insets again.
                    margin(vertical = false)
                    padding(vertical = false)
                }
            }
        }
    }
}

// used like
applyStatusBarOffsetToView(binding.container, Margin) // works
// and at a later point...
applyStatusBarOffsetToView(binding.container, None) // does nothing

And calling applyInsetter should trigger a new inset request right? So not sure how to re-trigger it in this case?

ninovanhooff commented 2 years ago

I'm interested in this as well. Usage scenario is Dynamic screens, where that content may have a toolbar depending on server data.

Can you give an example of an inset pass @chrisbanes ?

Based on @patrick-elmquist 's expierence, applyInsetter { // no-op } probably wouldn't work

patrick-elmquist commented 2 years ago

Did this as a workaround, tho this was months ago and the code was never finalized or merged so not sure if it still works

/**
  * Resets the margin and/or padding to its original state
  */
fun View.resetInsets(margin: Boolean = false, padding: Boolean = false) {
    (getTag(R.id.insetter_initial_state) as? ViewState)?.let { state ->
        if (padding) {
            updatePadding(
                left = state.paddings.left,
                top = state.paddings.top,
                right = state.paddings.right,
                bottom = state.paddings.bottom
            )
        }
        if (margin) {
            updateMargin(
                left = state.margins.left,
                top = state.margins.top,
                right = state.margins.right,
                bottom = state.margins.bottom
            )
        }
    }
}
patrick-elmquist commented 2 years ago

Having now (one parental leave later) finished the PR where this was needed it seems that the workaround actually works as intended. I was also looking into why it's needed and it would seem that pushing Side.NONE through the system will intentionally keep the current offset instead of resetting it back to its initial state. Here's the code used for padding

private fun View.applyPadding(
    insets: WindowInsetsCompat,
    typesToApply: SideApply,
    initialPaddings: ViewDimensions,
    ignoreVisibility: Boolean,
) {
    // If there's no types to apply, nothing to do...
    if (typesToApply.isEmpty) return

    val paddingLeft = when (typesToApply.left) {
        Side.NONE -> paddingLeft
        else -> initialPaddings.left + insets.getInsets(typesToApply.left, ignoreVisibility).left
    }
    val paddingTop = when (typesToApply.top) {
        Side.NONE -> paddingTop
        else -> initialPaddings.top + insets.getInsets(typesToApply.top, ignoreVisibility).top
    }
    val paddingRight = when (typesToApply.right) {
        Side.NONE -> paddingRight
        else -> initialPaddings.right + insets.getInsets(typesToApply.right, ignoreVisibility).right
    }
    val paddingBottom = when (typesToApply.bottom) {
        Side.NONE -> paddingBottom
        else -> initialPaddings.bottom + insets.getInsets(typesToApply.bottom, ignoreVisibility).bottom
    }

    // setPadding() does it's own value change check, so no need to do our own to avoid layout
    setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)
}

So in my sample, setting vertical = false make all sides go in the Side.NONE blocks and hence keep their old values. Don't have the full picture on how everything is connected but is there a reason why you wouldn't reset to the initial values? I guess that it could make for some weird behavior if the padding is altered outside of Insetter but that should also be the case when an inset actually change and the padding is set to initial + inset.

PhilGlass commented 1 year ago

Don't have the full picture on how everything is connected but is there a reason why you wouldn't reset to the initial values?

I'm wondering the same thing - resetting to initial values rather than keeping current values seems like the behaviour I'd expect in this case, but I might be missing something.

You can hack around this by passing a non-zero but invalid type bit field (currently only 9 bits are used by WindowInsetsCompat.Type):

applyInsetter {
    type(if (applyInsets) WindowInsetsCompat.Type.whatever() else (1 shl 31)) {
        padding(bottom = true)
    }
}