Coder Social home page Coder Social logo

contour's People

Contributors

aerb avatar aleckazakova avatar carranca avatar dimsuz avatar dongheeleeme avatar egorand avatar jakewharton avatar jamolkhon avatar jingibus avatar jrodbx avatar kunlin666 avatar louiscad avatar msfjarvis avatar oldergod avatar orgmir avatar saket avatar shenghaiyang avatar vitusortner avatar zacsweers avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

contour's Issues

Allow opting out of padding-conscious layout behaviour

In #28 we introduced automatically taking view padding into account when laying out subviews.

For existing code, this might require some migration work, and we don't want adoption of the latest and greatest release of Contour to be hindered by this.

Therefore, let's add a new field to ContourLayout that goes like this:

@Deprecated("Migrate view to properly take padding into account or explicitly null it out")
val respectPadding: Boolean = true

When this field is set to false, the view's padding fields should not be taken into account anywhere in the Contour stack.

Skip layout of children with visibility set to GONE

When a child View's visibility is set to GONE, traditional layouts skip laying the child and measure it as 0 width & height. One can argue that this is kind of an implicit behavior, but given that other layouts have always carried this behaved, should we consider matching this expected behavior?

How to update an existing view?

It could be answered by #31
But let me single out this question here:
Consider:

private val name: TextView =
    TextView(context).apply {
      text = "Ben Sisko"
      setTextColor(White)
      setTextSize(TypedValue.COMPLEX_UNIT_DIP, 18f)
      applyLayout(
        x = leftTo { parent.left() + 15.dip },
        y = topTo { parent.top() + 15.dip }
      )
    }

How to update that name TextView to "John Doe"?

Question: How to understand ContourLayout.invoke()?

In SampleActivity, there's views[i]() which calls invoke().
Using breakpoints, it seems invoke() reinstantiate everything in SampleView1 and SampleView2
Is that correct?
Or is invoke() kinda redraw the view with a new state or sth like that?

Couldn't find invoke() function defined inside ContourLayout that's why I couldn't help myself.
Are there use cases for parameters to be passed to the ContourLayout.invoke() function?

Lint error when adding a view manually to a contour layout.

This would otherwise crash when casting the LayoutSpec

  private inline fun View.spec(): LayoutSpec {
    if (parent !== this@ContourLayout) {
      throw IllegalArgumentException("Referencing view outside of ViewGroup.")
    }
    return layoutParams as LayoutSpec
  }

Text is cut during transition

After running the Sample app without any modifications, and clicking the first card; I've noticed that the text is cut by some margin (I think) during the transition.

See the following video: https://imgur.com/amsY2I8

The avatar is acting normally. It is "cut" by the card only at the very bottom.

I'm not sure what's causing this bug as I'm not very familiar with Contour yet, but I believe it could be because of the centerY() part, on this line:

else -> avatar.centerY() - bio.preferredHeight() / 2

Usage

Hi, may you update readme about setup contour dependency from scratch please

How to handle multiple gone views?

I have a header and chip group that can be visible, or gone. What is the best way to handle this and not have views overlap? It seems that if the view that visible relies on the view that gone it doesn't always evaluate correct.

Here is the sample in the init of the conditional fields. if for example there is no demographic then i hide the text and chips. This seems to work in some scenarios. One that does does not work is if contentType, format, theme are gone. Then the genre text overlaps in the demographic chip.

        demographicText.layoutBy(
            x = leftTo { parent.left() },
            y = topTo { descriptionExpanded.bottom() }
        )
        demographicChips.layoutBy(
            x = matchParentX(),
            y = topTo { demographicText.bottom() - 8.ydip }
        )

        contentTypeText.layoutBy(
            x = leftTo { parent.left() },
            y = topTo { demographicChips.bottom() }
        )
        contentTypeChips.layoutBy(
            x = matchParentX(),
            y = topTo { contentTypeText.bottom() - 8.ydip }
        )
        formatText.layoutBy(
            x = leftTo { parent.left() },
            y = topTo { contentTypeChips.bottom() }
        )
        formatChips.layoutBy(
            x = matchParentX(),
            y = topTo { formatText.bottom() - 8.ydip }
        )
        themeText.layoutBy(
            x = leftTo { parent.left() },
            y = topTo { formatChips.bottom() }
        )
        themeChips.layoutBy(
            x = matchParentX(),
            y = topTo { themeText.bottom() - 8.ydip }
        )
        genreText.layoutBy(
            x = leftTo { parent.left() },
            y = topTo { themeChips.bottom() }
        )
        genreChips.layoutBy(
            x = matchParentX(),
            y = topTo { genreText.bottom() - 8.ydip }
        )
        altTitlesText.layoutBy(
            x = leftTo { parent.left() },
            y = topTo { genreChips.bottom() }
        )
        altTitles.layoutBy(
            x = matchParentX(),
            y = topTo { altTitlesText.bottom() - 12.ydip }
        )

Recipes section in README

We've discussed creating a Recipes section in the README for some patterns of use of Contour APIs that aren't necessarily the most straightforward.

One such example is, in the words of @aerb ,

if you find yourself wanting to use measuredWidth / measuredHeight there is an extension you can use called preferredWidth / preferredHeight on Contour which will trigger a measure() without requiring a full layout. That should help you avoid scenarios where you are asking for measuredWidth before measure has occurred

Allow overriding height iff it's greater than the parent height

Scenario:
ContourLayout inside of a ScrollView.

Wanted API (name TBD):

init {
  contourMaxHeightOf { lastView.bottom() }
}

Behavior:
Sets height to max(availableSpace, lambda)

I tried this using contourHeightOf and couldn't get it to work:

contourHeightOf { maxOf((parent as ViewGroup).height.toYInt(), lastView.bottom()) }

Does contour have a performance benchmark

I want to know the performance compare between contour vs constraintLayout. I have seen in some places that if a simple layout uses constraintLayout, it will not be faster than using frameLayout or linearLayout, so contour?

How to make the sub view use native wrap_content?

The layout api must specify the solver for the x and y axes. If I want the x axis to have similar behaviors like wrap_content, what should I do?

I only saw matchParentX/Y, why not provide wrapContentX/Y?

In native, if you don't specify width or height, LayoutParams.WRAP_CONTENT will be used by default. Can contour do the same thing?

Allow automatic respecting of padding in layouts

The problem

When laying out a view, the parent ContourLayout's padding values are not respected. Aligning a subview to parent.top() will place the view at the top of the parent, even if the parent has a non-zero top padding. Similarly, constraining a view to parent.width() or parent.height() will constrain the view to the parent's total width and height, ignoring the padding.

Open questions

  1. Should respecting the padding be an overridable behaviour? (I'm not sure)
  2. Should respecting the padding be the default behaviour? (I think so)

Is 0.1.8 version released for public use?

CHANGELOG.md Suggests that 0.1.8 version was released on 2020-06-11.
But I could not find it in maven.
Also README.md points to 0.1.7 version. And that makes me wonder 0.1.8 version is released for public use?

Allow aligning of a view to a group of views

This is something accomplished using Barrier in ConstraintLayout, but I think we can make it more straightforward.

Given views A, B, C:

Scenarios

  1. Aligning a view A to at any given time be to the left of both B and C
  2. Aligning a view A to at any given time be to the right of both B and C
  3. Aligning a view A to at any given time be horizontally centered to the middle point between B and C
  4. Aligning a view A to at any given time be above both B and C
  5. Aligning a view A to at any given time be below both B and C
  6. Aligning a view A to at any given time be vertically centered to the middle point between B and C

When the layout of B or C is updated, the positioning of A must be updated.

Remove `onInitializeLayout()`

Discussion in #11 has revealed that it's not really a good idea to keep it around. I'm deprecating it as part of #75 but it should be removed altogether before we go public with 1.0.0

Contour Debug Mode

The idea here would be to print to logcat whenever the ContourLayout or one of its direct subviews has its position or size recalculated, i.e. whenever one of the lambdas defined in the context of that layout gets run.

It would be useful for debugging view layouts (especially for more complex layouts or layouts whose size changes frequently.

Int.xdip/Int.ydip & Float.xdip/Float.ydip cause crash on Kotlin 1.4.30

This can be easily reproduced by changing the dependency on the sample app from the module to the binary:
implementation project(':contour') -> implementation 'app.cash.contour:contour:1.0.0'

Here's the stack trace:

java.lang.NoSuchMethodError: No virtual method getYdip-dBGyhoQ(I)I in class 
Lcom/squareup/contour/sample/SampleView; or its super classes (declaration of 
'com.squareup.contour.sample.SampleView' appears in /data/app/~~0Ba-
93BI78uARYazH4agJA==/com.squareup.contour.sample-VKrVvhwusycb2ElCbF70RQ==/base.apk!classes2.dex)
        at com.squareup.contour.sample.SampleView$3.invoke(SampleView.kt:56)

Int.dip doesn't seem to be an issue.

At least on my project, reverting to 1.4.21 fixes the issue immediately.

Add an example of compareable solvers in a README?

I've only now watched Adam's presentation about Contour and while I was using it for some time now, this slide blew my mind

I've been occasionally struggling with the fact that sometimes I want one solver and sometimes (depending on view config) another. As a result I had to invent some clever tricks, while I didn't know about this feature.

Can it be somehow made more obvious? Perhaps a mention in README or in some other documentation source?

Best practices for layout preview?

Given that XML isn't involved, the layout preview in studio doesn't apply here - what's the recommended way of previewing contour layouts (if any) besides building/deploying?
Just curious if the cash app team has the same problem, has found another solution, or if its not really an issue re-deploying after making changes.

Thanks!

Support foreground on API below 23

Since ContourLayout extends ViewGroup, it doesn't support foreground on API below 23, a common workaround is to wrap the layout in a FrameLayout, but this seems redundant

Add support for more than 2 arguments to minOf/maxOf (both for XInt/YInt, XAxisSolver/YAxisSolver variants)

It is not the first time I have this usecase, where I want more than two arguments for minOf.

minOf currently supports XInt/YInt and XAxisSolver/YAxisSolver (but only 2 args for both kinds)...

I have just ran into another case with the latter. The layout is like this:

TITLE
PRIMARY_ACTION (optional, maybe GONE)
SECONDARY_ACTION (optional, maybe GONE)
---screen-bottom---

I want to do something like this:

titleView.layoutBy(
  x = matchParentX(),
  y = minOf(
     bottomTo { primaryAction.top() - 16.ydip },
     bottomTo { secondaryAction.top() - 16.ydip },
     bottomTo { parent.bottom() - 16.ydip },
  )
)

but minOf has only 2 args... Similar situation is for xint/yint, I often find myself wishing for more args.

Is this possible or maybe I also miss some easy workaround to achive what I want? I know I can put if (someView.isVisible) rule1 else rule2 in my lambdas, but that looks not very elegant and a bit messy...

P.S. Can't help but think that if we'd be in the functional programming land, XInt/Yint, and axis solvers could be an instance of Monoid + Ord and all the stdlib functions (including min) would just work on them. But oh well :)

Readme Example Seems Wrong

In the code, the example shows the name being constrained to the top:

y = topTo { parent.top() + 15.dip }

With the avatar being constrained below it:

y = topTo { name.bottom() }

But the gif shows it constrained to the bottom with the avatar above it.

Understanding Visibility Changes, Layout, and maxOf/minOf

Overview:

I'm trying to achieve an effect similar to #116, but with the maxOf function. Unfortunately for me, I'm a little bit confused by Contour's behavior when I change the visibility of one of the optional views.

Steps:

We can easily reproduce the behavior that's confusing me using the sample app.

  • Add private val card3 = ExpandableBioCard1(context) to SampleView:
  • Add this to SampleView's init block
card3.layoutBy(
 x = matchParentX(marginLeft = 24.dip, marginRight = 24.dip),
 y = maxOf(topTo { card2.bottom() + 24.ydip }, topTo { card1.bottom() + 24.ydip })
)
  • Add isVisible = false to ExpandableBioCard2's init block (in my real app this can happen in the render method, but it has the same effect here)
  • Notice that there is additional space in between card3 and card1

(Possibly Misguided) Expectations:

I expected card2's visibility change to GONE to result in card2.bottom() evaluating to 0, causing maxOf to pick the second argument.
I expected this for two reasons:

  • The docs for View.GONE state that the "view is invisible, and it doesn't take any space for layout purposes"
  • In a LinearLayout that contains two views, if you set the visibility of the first view to GONE the second view automatically takes its place

Of course, this is not what's happening. maxOf is always picking topTo { card2.bottom() + 24.ydip } because while card2.height()=0, card2.bottom() is still > card1.bottom().

Current (Possibly Permanent) Workaround:

I can get the desired behavior by doing this:

card3.layoutBy(
  x = matchParentX(marginLeft = 24.dip, marginRight = 24.dip),
  y = topTo {
    when {
       card2.isVisible -> card2.bottom() + 24.ydip
       else -> card1.bottom() + 24.ydip
    }
  }
)

Questions:

  • Is this working as intended? Why does card2.bottom() not evaluate to 0?
  • Is checking the visibility in topTo the recommended approach for achieving this effect?

Thank you all for this amazing library! I won't ever write another layout in XML if I can help it πŸ˜„ .

Problem with kotlin 1.4.30

java.lang.NoSuchMethodError: No virtual method getYdip-dBGyhoQ(I)I in class

Tried with contour 1.1.0-SNAPSHOT as well, same error.

When I downgrade kotlin to 1.4.20 everything works.

I have a header and chip group that can be visible, or gone. What is the best way to handle this and not have views overlap? It seems that if the view that visible relies on the view that gone it doesn't always evaluate correct.

I have a header and chip group that can be visible, or gone. What is the best way to handle this and not have views overlap? It seems that if the view that visible relies on the view that gone it doesn't always evaluate correct.

Here is the sample in the init of the conditional fields. if for example there is no demographic then i hide the text and chips. This seems to work in some scenarios. One that does does not work is if contentType, format, theme are gone. Then the genre text overlaps in the demographic chip.

        demographicText.layoutBy(
            x = leftTo { parent.left() },
            y = topTo { descriptionExpanded.bottom() }
        )
        demographicChips.layoutBy(
            x = matchParentX(),
            y = topTo { demographicText.bottom() - 8.ydip }
        )

        contentTypeText.layoutBy(
            x = leftTo { parent.left() },
            y = topTo { demographicChips.bottom() }
        )
        contentTypeChips.layoutBy(
            x = matchParentX(),
            y = topTo { contentTypeText.bottom() - 8.ydip }
        )
        formatText.layoutBy(
            x = leftTo { parent.left() },
            y = topTo { contentTypeChips.bottom() }
        )
        formatChips.layoutBy(
            x = matchParentX(),
            y = topTo { formatText.bottom() - 8.ydip }
        )
        themeText.layoutBy(
            x = leftTo { parent.left() },
            y = topTo { formatChips.bottom() }
        )
        themeChips.layoutBy(
            x = matchParentX(),
            y = topTo { themeText.bottom() - 8.ydip }
        )
        genreText.layoutBy(
            x = leftTo { parent.left() },
            y = topTo { themeChips.bottom() }
        )
        genreChips.layoutBy(
            x = matchParentX(),
            y = topTo { genreText.bottom() - 8.ydip }
        )
        altTitlesText.layoutBy(
            x = leftTo { parent.left() },
            y = topTo { genreChips.bottom() }
        )
        altTitles.layoutBy(
            x = matchParentX(),
            y = topTo { altTitlesText.bottom() - 12.ydip }
        )

Originally posted by @CarlosEsco in #125

Nested ContourLayout causing StackOverflow

The following code causes a stack overflow during layout:

    class ParentView(context: Context): ContourLayout(context) {
        init {
            setBackgroundColor(Color.RED)
        }

        val childView = ChildView(context).apply {
            applyLayout(
                x = leftTo { parent.left() }.widthOf { 100.xdip },
                y = topTo { parent.top() }.heightOf { 100.ydip }
            )
        }
    }

    class ChildView(context: Context) : ContourLayout(context) {
        init {
            setBackgroundColor(Color.BLUE)
        }
    }

However, moving ChildView's applyLayout into it's onInitializeLayout solves the issue:

    class ParentView(context: Context): ContourLayout(context) {
        init {
            setBackgroundColor(Color.RED)
        }

        val childView = ChildView(context).also { addView(it) }
    }

    class ChildView(context: Context) : ContourLayout(context) {
        init {
            setBackgroundColor(Color.BLUE)
        }

        override fun onInitializeLayout() {
            super.onInitializeLayout()
            applyLayout(
                x = leftTo { parent.left() }.widthOf { 100.xdip },
                y = topTo { parent.top() }.heightOf { 100.ydip }
            )
        }
    }

I don't know if this is a bug or not, but my hunch is it's the intended behavior (if not, I'd be happy to look into it).

I realize nesting layouts is discouraged, but there are going to be situations where it is useful and people are going to do it anyway.

The pattern Contour uses for configuring child views (MyView(context).apply { applyLayout() }) is great, and it seems clunky to have to violate it iff the child happens to be another ContourLayout (I can imagine during refactoring a View that didn't start as Contour might become one later).

Could we make it so layout constraints declared at construction time get deferred until onInitializeLayout? Either by having applyLayout behave differently for ContourLayouts or with an onInitializeLayout DSL:

    class ParentView(context: Context): ContourLayout(context) {
        init {
            setBackgroundColor(Color.RED)
        }

        val childView = ChildView(context).apply {
            onInitializeLayout {
                applyLayout(
                    x = leftTo { parent.left() }.widthOf { 100.xdip },
                    y = topTo { parent.top() }.heightOf { 100.ydip }
                )
            }
        }
    }

    class ChildView(context: Context) : ContourLayout(context) {
        init {
            setBackgroundColor(Color.BLUE)
        }
    }

Anchor contents to bottom

I'm using ContourLayout to build a view that will be placed inside a ScrollView (with fillViewport="true"). When scrolling is not required, I'd like for the contents to be anchored to the bottom of the ScrollView.

Here is how I'm going about it (for simplicity, let's just say there are 2 children views):

class MyContourView(
    context: Context,
    private val view1: View,
    private val view2: View,
    private val interItemMarginPx: Int,
    private val lrMarginPx: Int
) : ContourLayout(context) {
    init {
        contourHeightOf { availableHeight ->
            maxOf(availableHeight, view2.bottom() + interItemMarginPx
        }
    }

    override fun onInitializeLayout() {
        view2.applyLayout(
            leftTo { parent.left() + lrMarginPx }.rightTo { parent.right() - lrMarginPx },
            bottomTo { parent.bottom() - interItemMarginPx }
        )

        view1.applyLayout(
            leftTo { parent.left() + lrMarginPx }.rightTo { parent.right() - lrMarginPx },
            bottomTo { view2.top() - interItemMarginPx }
        )
    }
}

But I get a CircularReferenceDetected error (at the line where I call view2.bottom() in contourHeightOf {}). I can see why that's the case: view2 references parent.bottom() and the parent references view2.bottom() in determining its height.

Everything works if I remove the contourHeightOf {} block -- in which case it's always going to be availableHeight. But this breaks the scrolling if the height of the contents is greater than availableHeight.

How do I go about this use case?

Default layout width should be consistent

I needed a textView and a button, one below the other, both right aligned in the parent (while still respecting the left constraint of the parent's edge).

For the textView, I did the following and it worked:

textView.applyLayout(
    rightTo { parent.right() - 32.dip }.leftTo { parent.left() + 32.dip },
    // some YAxisSolver
)

The textView was wrap_content and right aligned, and if the text grew to multiple lines, the left constraint was still respected. However, doing the same for the button resulted in an always-match_parent button, even if the contents inside were much narrower. To achieve what I wanted, I had to use SizeMode.AtMost like so:

button.applyLayout(
    rightTo { parent.right() - 32.dip }.leftTo(SizeMode.AtMost) { parent.left() + 32.dip },
    topTo { textView.bottom() + 16.dip }
)

Why is this the case? Shouldn't the default width strategy be the same for all view types?

java.lang.NoSuchMethodError: No virtual method bottom-h0YXg9w(Landroid/view/View;)I in class

kotlin 1.4.0

works fine on 1.3.72

stack trace:

java.lang.NoSuchMethodError: No virtual method bottom-h0YXg9w(Landroid/view/View;)I in class Lcom/priceline/android/contour/CollectionsView; or its super classes (declaration of 'com.priceline.android.contour.CollectionsView' appears in /data/app/com.priceline.android.contour-jK1itqBV6DI3fBgme768dQ==/base.apk!classes2.dex)
at com.priceline.android.contour.CollectionsView$4.invoke(CollectionsView.kt:16)
at com.priceline.android.contour.CollectionsView$4.invoke(CollectionsView.kt:7)
at com.squareup.contour.utils.XYIntUtilsKt$unwrapYIntToYIntLambda$1.invoke(XYIntUtils.kt:60)
at com.squareup.contour.utils.XYIntUtilsKt$unwrapYIntToYIntLambda$1.invoke(Unknown Source:6)
at com.squareup.contour.constraints.SizeConfig.resolve(SizeConfig.kt:27)
at com.squareup.contour.ContourLayout.onMeasure(ContourLayout.kt:194)
at android.view.View.measure(View.java:25086)
at androidx.constraintlayout.widget.ConstraintLayout$Measurer.measure(ConstraintLayout.java:763)
at androidx.constraintlayout.solver.widgets.analyzer.BasicMeasure.measure(BasicMeasure.java:426)
at androidx.constraintlayout.solver.widgets.analyzer.BasicMeasure.measureChildren(BasicMeasure.java:105)
at androidx.constraintlayout.solver.widgets.analyzer.BasicMeasure.solverMeasure(BasicMeasure.java:247)
at androidx.constraintlayout.solver.widgets.ConstraintWidgetContainer.measure(ConstraintWidgetContainer.java:117)
at androidx.constraintlayout.widget.ConstraintLayout.resolveSystem(ConstraintLayout.java:1532)
at androidx.constraintlayout.widget.ConstraintLayout.onMeasure(ConstraintLayout.java:1607)
at android.view.View.measure(View.java:25086)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6872)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at androidx.appcompat.widget.ContentFrameLayout.onMeasure(ContentFrameLayout.java:146)
at android.view.View.measure(View.java:25086)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6872)
at androidx.appcompat.widget.ActionBarOverlayLayout.onMeasure(ActionBarOverlayLayout.java:490)
at android.view.View.measure(View.java:25086)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6872)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at android.view.View.measure(View.java:25086)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6872)
at android.widget.LinearLayout.measureChildBeforeLayout(LinearLayout.java:1552)
at android.widget.LinearLayout.measureVertical(LinearLayout.java:842)
at android.widget.LinearLayout.onMeasure(LinearLayout.java:721)
at android.view.View.measure(View.java:25086)
at android.view.ViewGroup.measureChildWithMargins(ViewGroup.java:6872)
at android.widget.FrameLayout.onMeasure(FrameLayout.java:194)
at com.android.internal.policy.DecorView.onMeasure(DecorView.java:742)
at android.view.View.measure(View.java:25086)
at android.view.ViewRootImpl.performMeasure(ViewRootImpl.java:3083)
at android.view.ViewRootImpl.measureHierarchy(ViewRootImpl.java:1857)
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2146)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1745)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7768)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:967)
at android.view.Choreographer.doCallbacks(Choreographer.java:791)
at android.view.Choreographer.doFrame(Choreographer.java:726)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)

View state saving inside ContourLayout

We recently started using contour and are really liking the no-XML approach. However, I'm a little confused as to the best way of saving state.

Say I'm using ContourLayout for my fragment like so:

class MyFragmentView(context: Context) : ContourLayout(context) {
    val editText1
    val editText2
    val editText3

    override fun onInitializeLayout() {
        // applyLayout calls
    }
}

and then in my Fragment,

class MyFragment : Fragment() {
    private lateinit var contourView: MyFragmentView

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? = MyFragmentView(requireContext()).apply {
        contourView = this
    }

    // other fragment functions
}

Now if the fragment view hierarchy is re-created (say via the Developer Options setting "Don't keep activities"), naturally, user input in the editTexts is not restored.

To get around this, we can do:

class MyFragment : Fragment() {
    // other fragment functions

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putString("input1", contourView.editText1.getText())
        outState.putString("input2", contourView.editText2.getText())
        outState.putString("input3", contourView.editText3.getText())
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        savedInstanceState?.let { 
            contourView.editText1.setText(it.getString("input1"))
            contourView.editText2.setText(it.getString("input1"))
            contourView.editText3.setText(it.getString("input1"))
        }
    }
}

But I have a gut feeling this may not be the best way. Is there some way view state restoration can happen internally inside ContourLayout given the way we're using it in MyFragment above?

ContourLayout and existing Layouts

Is it possibile to add a ContourLayout to an existing layout? When i try to add it to a FrameLayout i obtain ths exception:

java.lang.ClassCastException: com.squareup.contour.ContourLayout$LayoutSpec cannot be cast to android.view.ViewGroup$MarginLayoutParams

Right to left layouts support

Hi folks! πŸ™‚

I just wanted to ask if it's in the plans to add support for right to left layouts in the library. I guess that library users are mostly interested in supporting LTR locales, but I was wondering what are your thoughts/plans on this.

wrap_content

Hi! Using 2 nested contour layout, is it possible to have something like wrap_content?

Usage in lists

Some of the points that are great with contour which are stated in the readme are:

  • ViewHolders aren’t needed

  • Add & remove components based on app state

  • Lazy loading elements of the UI.

These points together sound like this layout could replace recycler views.

In the application I'm currently working on about 95% of the screens are scrollable lists, so basically every screen is made of a single RecyclerView.

How is this library designed to be used? Would you use one contourlayout inside a each ViewHolder?

Allow constraints to use the other axis

It would be pretty cool to use information about the other Axis to build constraints.
E.g. Imagine building a square like:

    private val square = View(context).applyLayout(
        x = leftTo { parent.left() }.rightTo { parent.right() },
        y = topTo { parent.bottom() }.heightOf { width().toY() }
    )
 

This would currently fail, because of "circular reference".

Is Contour how Cash app is built?

Just wanted to understand if that's the case. And whether you are planning to improve and promote this library more for Android UI development.

Crash XYIntUtils kotlin 1.4.30

.FooterView$4.invoke(FooterView.kt:47)
        at ....FooterView$4.invoke(FooterView.kt:13)
        at com.squareup.contour.utils.XYIntUtilsKt$unwrapYIntLambda$1.invoke(XYIntUtils.kt:51)
        at com.squareup.contour.utils.XYIntUtilsKt$unwrapYIntLambda$1.invoke(Unknown Source:2)
        at com.squareup.contour.constraints.Constraint.resolve(Constraint.kt:48)
        at com.squareup.contour.solvers.SimpleAxisSolver.measureSpec(SimpleAxisSolver.kt:173)
        at com.squareup.contour.ContourLayout$LayoutSpec.measureSelf$contour_release(ContourLayout.kt:758)
        at com.squareup.contour.solvers.SimpleAxisSolver.max(SimpleAxisSolver.kt:110)
        at com.squareup.contour.ContourLayout$LayoutSpec.bottom-h0YXg9w$contour_release(ContourLayout.kt:729)
        at com.squareup.contour.ContourLayout.bottom-h0YXg9w(ContourLayout.kt:469)
        at ....FooterView$6.invoke(FooterView.kt:52)
        at ....FooterView$6.invoke(FooterView.kt:13)
        at com.squareup.contour.utils.XYIntUtilsKt$unwrapYIntLambda$1.invoke(XYIntUtils.kt:51)
        at com.squareup.contour.utils.XYIntUtilsKt$unwrapYIntLambda$1.invoke(Unknown Source:2)
        at com.squareup.contour.constraints.Constraint.resolve(Constraint.kt:48)
        at com.squareup.contour.solvers.SimpleAxisSolver.resolveAxis(SimpleAxisSolver.kt:131)
        at com.squareup.contour.solvers.SimpleAxisSolver.max(SimpleAxisSolver.kt:111)
        at com.squareup.contour.ContourLayout$LayoutSpec.bottom-h0YXg9w$contour_release(ContourLayout.kt:729)
        at com.squareup.contour.ContourLayout.bottom-h0YXg9w(ContourLayout.kt:469)
        at ....FooterView$11.invoke(FooterView.kt:62)
        at ....FooterView$11.invoke(FooterView.kt:13)
        at com.squareup.contour.utils.XYIntUtilsKt$unwrapYIntLambda$1.invoke(XYIntUtils.kt:51)
        at com.squareup.contour.utils.XYIntUtilsKt$unwrapYIntLambda$1.invoke(Unknown Source:2)
        at com.squareup.contour.constraints.Constraint.resolve(Constraint.kt:48)
        at com.squareup.contour.solvers.SimpleAxisSolver.resolveAxis(SimpleAxisSolver.kt:131)
        at com.squareup.contour.solvers.SimpleAxisSolver.max(SimpleAxisSolver.kt:111)
        at com.squareup.contour.ContourLayout$LayoutSpec.bottom-h0YXg9w$contour_release(ContourLayout.kt:729)
        at com.squareup.contour.ContourLayout.bottom-h0YXg9w(ContourLayout.kt:469)
        at ....FooterView$16.invoke(FooterView.kt:75)
        at ....FooterView$16.invoke(FooterView.kt:13)
        at com.squareup.contour.utils.XYIntUtilsKt$unwrapYIntToYIntLambda$1.invoke(XYIntUtils.kt:61)
        at com.squareup.contour.utils.XYIntUtilsKt$unwrapYIntToYIntLambda$1.invoke(Unknown Source:6)
        at com.squareup.contour.constraints.SizeConfig.resolve(SizeConfig.kt:29)
        at com.squareup.contour.ContourLayout.onMeasure(ContourLayout.kt:213)
        at android.view.View.measure(View.java:25466)

Easier MATCH_PARENT AxisSolver

Is there an easier way to achieve a MATCH_PARENT parameter for widths and heights?

So far I've done it this way:

applyLayout(
    x = leftTo { parent.left() }
        .rightTo { parent.right() },
        y = topTo { parent.top() }
    )

But it is not straightforward and it looks like a workaround.

Add compareTo operator for XInt/YInt

There are certain scenarios (like the one described in #59) in which it is handy to do something like this:

if (view1.bottom() < view2.bottom()) view1.bottom() else otherView.top()

Currently XInt/YInt lack operator fun compareTo which prevents the above from compiling.
The workaround could be to use minOf(view1.bottom() - view2.bottom(), 0.ydip) == 0.ydip but that's a bit ugly.

Can those be added (I can forge a PR) or do you have something specific in mind why this is not a good idea?

Don't suppress warnings for experimental Kotlin features

Since Contour is using experimental features (inline classes), it should be either propagating the warnings through annotations or Gradle flags (see Experimental API markers), or introducing its own experimental annotation (see an example of this in KotlinPoet). An argument in favor of a library-specific annotation is that developers might be fine with using experimental features in their own code, but reluctant to use 3rd party libraries that use them, hence the library should require explicit opt-in.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.