Skip to content

Releases

2.2.0

Choose a tag to compare

@github-actions github-actions released this 17 Sep 13:35
ca55984

Added

  • smoothScrollToPosition(int), on all three views, animates the cell holding
    the position to parchment_snapPosition the way a fling gets there, where
    setSelection jumps. It runs at RecyclerView's programmatic speed, 25 ms
    per inch, four times a snap's: a cell already laid out is landed on with one
    decelerating scroll of exactly its snap distance; a cell that is not is
    sought at a constant speed by the distance extrapolated from the edge cell's
    size plus a viewport of runway, handed to the landing in the frame it is
    laid out, and sought again from wherever it is whenever less than a viewport
    of runway is left, so the motion stays even when the cells between were
    larger than the estimate assumed. The landing is not capped at half a
    second, so a long scroll decelerates from the seek's speed rather than
    snapping the rest of the way. It obeys every bound a fling obeys:
    android:padding*, parchment_scrollWithinContent, under which a cell the
    bound holds short of its snap point decelerates onto the bound and rests
    there, and parchment_isCircularScroll, under which it takes the shorter
    way round. A parchment_isViewPager view is crossed page by page rather
    than held to one, which needed the page clamp to learn that an animation is
    not a gesture. It reports settling then idle to the scroll listener, and
    under parchment_selectOnSnap selects the cell at the snap position when it
    comes to rest, once. The position is clamped to the adapter; before the first
    layout, on an empty adapter, or when the cell is already at its snap point
    nothing happens; it is ignored while a finger is down, and a touch cancels it
    like a fling. Under onScreen it scrolls the minimum that shows the cell
    whole. A position asked for before the first layout is not remembered; a
    pending target is a follow-up (#54, asked for in #19).

Changed

  • A jump ends a running animation. setSelection and a data set change now
    stop whatever was moving the content, a fling included, in the layout pass
    the jump asks for, snapping whatever residual the jump left, so the view
    rests with the incoming cell at its snap point and reports one idle; the
    fling used to carry on from the jumped offset. At rest nothing changes: no
    frame runs and nothing is reported.
  • Breaking: LayoutManager gains an abstract getCellBreadth, a cell's
    extent across the scroll axis, and measureBreadth, which
    AbstractAdapterView.onMeasure asks for the view's size across it;
    ScrollDirectionManager gains the cross-axis spec, padding and width/height
    mappings that serve them. All are implementation details the views build and
    call for themselves, so nothing outside the library should be subclassing or
    calling them.

Fixed

  • parchment_selectOnSnap reported the selection a stop made only on the next
    layout pass, so a fling that ended exactly on a snap point, with no residual
    snap to run a further frame, selected the cell but did not tell the
    OnItemSelectedListener until something else laid the view out. A frame
    that ends at rest now reports the selection its stop made, which is what
    every smoothScrollToPosition landing needs
    (aFlingThatEndsOnASnapPoint_withSelectOnSnap_reportsTheSelectionWithoutALayoutPass).
    Two consequences to know about: under parchment_selectOnSnap a data set
    change at rest that keeps or grows the count now reports its unchanged
    selection one layout pass earlier, the same cell in the same place, once;
    and a jump that lands mid-animation reports its selection where the cell is
    at the jump, after which the residual snap moves it the few pixels to its
    snap point, as it already did on main with the fling still running. Under
    parchment_isCircularScroll with parchment_selectOnSnap, a tap on a cell
    reports that cell and then the cell nearest the start edge, because the
    stop's snap selects the nearest cell to the onScreen position circular
    scrolling forces; main did the same silently, and this is now reported
    rather than fixed.
  • A fling, snap or smooth scroll that ran to rest within its first frame, as a
    short snap does and as any animation does when the main thread stalls for a
    frame, reported its displacement but no scroll state: the dispatcher reports
    a change at the end of a frame and the frame ended where it began, at
    idle. It now reports the state the frame ran in before the state it ended
    in, when the frame moved the content, so such a frame reports settling
    then idle after its onScrolled and a frame that moved nothing still
    reports nothing
    (aSmoothScrollThatEndsInItsFirstFrame_stillReportsSettlingThenIdle,
    aFlingThatEndsInItsFirstFrame_stillReportsSettlingThenIdle).
  • android:layout_height="wrap_content" on a horizontal view and
    android:layout_width="wrap_content" on a vertical one used to take every
    pixel the parent had left, pushing the next sibling of a LinearLayout off
    the screen: onMeasure set the measured size to the spec size whatever the
    spec's mode, so AT_MOST was treated as EXACTLY. Across the scroll axis a
    ListView now measures to its largest cell and a GridView to its largest
    row or column, the spacing between the views of the group included, plus
    android:padding*, capped at the size offered under AT_MOST and uncapped
    under UNSPECIFIED. EXACTLY is untouched, so match_parent and a fixed
    size lay out exactly as before. Along the scroll axis wrap_content still
    fills the parent, since wrapping there would mean measuring every item in the
    adapter, and GridPatternView, whose cells are sized from the view, still
    fills it on both axes. The size comes from the cells present at measure time:
    the cells laid out, or before the first layout the cells that fill the
    viewport from the start position, obtained through the recycler and handed
    back to it the way the platform ListView measures under AT_MOST. That
    first estimate cannot see a cell the layout back-fills before the start
    position, one the over-scroll correction pulls in, or one a scroll reveals
    later, so a layout that draws a cell larger than the size it was measured to
    asks the view for one more layout, and the next measure, which reads the
    drawn cells, grows the view to it. Cells of varying size across the scroll
    axis are therefore better served by a fixed size, because the view resizes
    as they come into view (#59, the remaining half of #10).
  • A horizontal ListView gave its children the mode of its own width spec as
    their height mode, and a GridView did the same for the views it obtains for
    a jump. They now take the mode of the height spec. Under wrap_content that
    is what lets a match_parent child measure to its content rather than to
    the whole height the parent offered; under a bounded width and an exact
    height, a horizontal list inside a HorizontalScrollView or a
    wrap_content-wide parent, a match_parent child is now given the exact
    height instead of a bound it measured itself to zero against. Under EXACTLY
    on both axes nothing changes.

2.1.0

Choose a tag to compare

@github-actions github-actions released this 16 Sep 19:12
4aee25a

Added

  • parchment_scrollWithinContent, on all three views, keeps the content inside
    the view: the first cell's start never moves inside the view's start edge and
    the last cell's end never moves inside its end edge, the bound that onScreen
    has always applied. With start the last cell now stops with its end at the
    view's end instead of being dragged to the start edge with empty space after
    it, end is the mirror, and center holds both ends; content shorter than
    the view does not scroll and sits at the snap position, at the start, at the
    end or centred. The content rests either on a cell at the snap position or at
    one of the two ends, whichever is nearer, so a drag, fling, page, tap or
    setSelection that runs into an end stops there and asks for nothing further,
    whether or not the cells divide the view evenly; a cell larger than the view is the exception,
    and rests at its snap point as it does under onScreen. At an end,
    parchment_selectOnSnap selects the cell that would have snapped there: the
    nearest of the cells the bound holds short of their snap point. A page is
    measured from where the anchor cell would snap without the bound, so paging
    back from an end returns to the page the gesture came from. Content that fits
    the view is put in place by the same correction on the first layout, so with
    end or center that first layout already selects, as onScreen has always
    done for short content. android:padding* bounds the content the way it
    bounds a snap. The attribute is off by default and is opt-in because the old
    behaviour is deliberate: letting every cell reach the snap point, the last and
    first included, is what lets parchment_selectOnSnap select every cell, and a
    layout that relies on that must not change under it. It does nothing under
    onScreen, which already keeps the content inside the view, or under
    parchment_isCircularScroll, which has no ends (#21).

Changed

  • Breaking: the LayoutManagerAttributes, GridLayoutManagerAttributes and
    GridPatternLayoutManagerAttributes constructors take the new flag, after the
    snap position; SnapPositionInterface.getSnapToPixelDistance takes the drawn
    cells and the interface gains getCellSettleDistance and
    getUnboundedSnapToPixelDistance. All are implementation details the views
    build and call for themselves, so nothing outside the library should be
    constructing or implementing them.

Fixed

  • A drag that pushes the content past its end used to hold the last cell at the
    snap position and then move the content one cell back under the finger,
    reporting a settling state and firing a second selection for
    parchment_selectOnSnap. The clamp stopped the animation from inside the
    frame, before the correction had been laid out, so the snap that a stop hands
    off to measured the cells where they had been rather than where they were. The
    stop and the selection now run once the frame is laid out: the content stays
    held on the last cell, reports no settling state, and selects it once. The
    cell selected at an end is the cell nearest the snap position among those laid
    out, which without parchment_scrollWithinContent is the held cell as before,
    and under onScreen, whose every visible cell is at its snap position, is
    still the held cell.

2.0.0

Choose a tag to compare

@github-actions github-actions released this 16 Sep 14:41
77e0901

The first work on Parchment since 2014. Apart from the new cell divider,
behaviour of the views is unchanged; everything around them is new.
Published to Maven Central as mobi.parchment:parchment:2.0.0, the same
coordinates as 1.x.

Upgrading from 1.6.x

  • Depend on mobi.parchment:parchment:2.0.0; the artifact is an AAR now,
    not an apklib. Minimum SDK is 21.
  • Every Parchment XML attribute gained a parchment_ prefix:
    orientation is parchment_orientation, cellSpacing is
    parchment_cellSpacing, and so on for all of them. Declare the namespace
    as xmlns:parchment="http://schemas.android.com/apk/res-auto". The
    old names are not kept as aliases, so a layout that still uses one fails
    to build rather than silently ignoring it. The README lists every
    attribute.
  • Class names, packages and the public Java API are unchanged. Only two
    protected hooks changed shape, createAdapterViewInitializer and the
    constructors of AdapterAnimator and ChildTouchGestureListener; a
    subclass that overrides or calls them has to pass the new parameters on.

Highlights

  • setOnScrollListener on every view, shaped like
    RecyclerView.OnScrollListener, with the state and the displacement.
  • parchment_divider and parchment_dividerSize draw a divider between
    cells, in every view.
  • parchment_viewPagerInterval pages a fixed number of cells instead of a
    viewport.
  • A snapping view comes to rest after a gesture instead of animating
    forever, and animation frames no longer re-lay-out the whole ancestor tree.
  • The sample app is a playground that reaches every attribute and prints
    the XML that reproduces what is on screen.

Everything below is the full record.

Changed

  • Breaking: AbstractAdapterView.createAdapterViewInitializer takes two
    more parameters, the divider drawable and its size. It is protected and
    an implementation detail, but a subclass that overrides it — the pattern
    the in-tree test views use to reach the gesture listener — has to take the
    two new parameters and pass them on.

  • The GridPatternView sample screen and the wrapping-height GridView
    sample screen draw a divider, so the feature is visible in the app. A
    colour has no intrinsic size, so both declare an explicit
    parchment_dividerSize.

  • The sample's four fixed screens are replaced by a playground that reaches
    every attribute and every combination of them. The first page lists the
    high-level items: the three views, and the behaviours that cut across them
    (paging, snapping, circular scrolling, dividers, selection, the scroll
    listener). Each opens the playground pre-filled with a preset that shows
    that item off — the four old screens are four of the presets — where every
    parchment_* attribute is a control, plus the GridPatternView pattern.
    Every control carries a hint saying what its attribute does, and one whose
    attribute would have no effect under the other settings disables itself
    and says what it needs; the printed XML leaves such an attribute out too.
    Show inflates the configured view with a status line of what
    setOnScrollListener, OnItemSelectedListener and OnItemClickListener
    report, and an info button shows the layout that reproduces it, which Copy
    puts on the clipboard. Parchment reads its attributes only when a view is
    inflated and has no setters for them, so the playground layouts reference
    ?attr/playground_* theme attributes and PlaygroundTheme applies one
    overlay style per chosen option to the theme it inflates under. The README
    had the GridPatternItemDefinition parameters as
    (left, top, width, height); the constructor takes
    (top, left, height, width), and its example, which tiles either way, is
    unchanged.

  • Breaking: the AdapterAnimator and ChildTouchGestureListener
    constructors take a ScrollListenerDispatcher. Both classes are
    implementation details that the views build for themselves — the in-tree
    test views reach the gesture listener by overriding
    createAdapterViewInitializer, not by constructing one — so nothing outside
    the library should be calling these constructors. The animator needs the
    dispatcher because it owns the state machine: a state change that reaches no
    frame, as when a drag is released exactly on the snap position and no
    animation follows, would otherwise never be reported, and asking the
    dispatcher there is what keeps a view with no listener from scheduling a
    frame it does not need.

  • GridView rows report their bounds without allocating. Group.getTop,
    getBottom, getLeft and getRight walked their views with an iterator
    and a boxed Integer accumulator; they now index the list and accumulate
    an int, because the divider pass reads a cell's bounds on every frame
    while the view moves. An empty group reported those bounds by throwing a
    NullPointerException and now reports 0, matching getMeasuredWidth and
    getMeasuredHeight, which already did.

  • Breaking: every custom XML attribute now carries a parchment_
    prefix: orientation is parchment_orientation, cellSpacing is
    parchment_cellSpacing, and so on for all eleven. Custom attribute names
    share one flat namespace across everything the resource merger sees, so
    Parchment's unqualified names collided with any other library declaring
    the same name and the consumer could not build at all; aapt2 stops the
    merge with duplicate value for resource 'attr/orientation' with config ''. Two declarations survive that merge only when they match exactly, so
    cellSpacing as a dimension here and an integer there was already fatal,
    and an attribute carrying enum or flag children — orientation,
    snapPosition, gravity — collided even when both declarations were
    byte-for-byte identical. Format, meaning, and the enum and flag values
    are unchanged, and the old names are not kept as deprecated aliases:
    2.0 is the breaking window and reading both would double the parsing
    code. The README tables list them all. Substituting your own
    res-auto prefix for parchment below — never android, whose own
    orientation and gravity are a different namespace and must not be
    touched — this is the whole migration:

    sed -i -E 's/\bparchment:(orientation|cellSpacing|isCircularScroll|snapToPosition|selectOnSnap|isViewPager|snapPosition|selectWhileScrolling|numberOfViewsPerCell|gravity|ratio)=/parchment:parchment_\1=/g' $(git ls-files '*.xml')
    

    The declare-styleable names (ListView, GridView, GridPatternView)
    are deliberately unchanged: the resource merger unions same-named
    styleables instead of failing them, and each app relinks the library's R
    indices, so a consumer declaring its own ListView styleable still
    builds.

  • Sample: each photo is requested from the Unsplash resizer at the size it
    will be shown at, instead of a fixed 800px wide image for every cell. The
    size is a pair of dimension resources per sample screen, so the existing
    -port and sw###dp qualifiers pick it the same way they already pick the
    cell sizes themselves; the three that a layout dimension already states
    alias it rather than repeating the number. Requests are in device pixels,
    so density is applied once by getDimensionPixelSize.

  • Sample: Picasso 2.8 (was 2.71828). Same okhttp, but it depends on
    androidx.exifinterface instead of com.android.support:exifinterface and
    support-annotations, which were the last com.android.support artifacts
    in the build.

  • Sample: Picasso is built with a memory cache of a third of the heap instead
    of the seventh it sizes itself at. A photo requested at the size of the view
    is several times larger than the fixed 800px one it replaces, and on a
    256MB-heap device only three GridPatternView cells fit in the default cache,
    so scrolling back evicted almost everything: 4 hits against 104 misses over
    a pass through the pattern view and back. A third of the heap, with the
    pattern view's request trimmed, makes that 24 hits against 72.

  • With parchment_snapToPosition on, a fling ends on the nearest snap position
    instead of decelerating to a stop and then starting a separate snap. The
    layout manager computes the adjustment when the fling starts, extrapolating
    with the edge cell's size when the end lies beyond the visible cells, and
    the fling's end point is moved there while its physics stay the same.

  • Snap, page, tap-to-snap, and programmatic scrolls use a decelerate curve
    with a duration proportional to the distance (100 ms per inch, stretched
    for the deceleration, capped at 500 ms), the same numbers RecyclerView's
    snap helper uses. The old fixed 500 ms viscous-fluid curve covered 97% of
    the distance in its first half and crawled the last few pixels one at a
    time over the second half, which read as stutter at the end of a scroll.
    Fling physics are unchanged.

  • Animation frames (fling, snap, page, drag) run the layout step directly
    from a postOnAnimation callback and invalidate, instead of posting a
    requestLayout() that re-measured and re-laid out the whole ancestor
    tree every frame. A full layout pass still happens whenever the framework
    asks for one, and a frame that fires while one is pending yields to it.

  • Breaking: minimum SDK is 21 (was 8) and the library is compiled with
    Java 17 against SDK 37.

  • Breaking: the library is an AAR built by Gradle. The Maven apklib
    packaging, the pom.xml files, and the Eclipse project files are gone.

  • Custom attributes are declared through the res-auto namespace
    (xmlns:parchment="http://schemas.android.com/apk/res-auto").

  • Build: Gradle 9.7.1 with a checksummed wrapper, AGP 9.4.0, a version
    catalog, configuration cache, and a JDK 21 daemon toolchain provisioned
    by Gradle.

  • Tests: Robolectric 4.16.1, AssertJ 3.27, androidx.test 1.7; the old
    integration Maven module lives in library/src/test now.
    ...

Read more