HomeAndroidCreate a photograph grid with multiselect habits utilizing Jetpack Compose | by...

Create a photograph grid with multiselect habits utilizing Jetpack Compose | by Jolanda Verhoef | Android Builders | Jun, 2023


Polished UI experiences in Compose

Many apps present some form of multi-select habits, the place you possibly can typically drag to pick out an entire vary of parts. For instance, Google Pictures helps you to simply choose an entire vary of photographs to share, add to an album, or delete. On this weblog put up, we’ll implement related habits with this finish aim:

A screencast of an app with a vertical grid of 3 columns, each item displaying a random image. The user long-presses one image to select it, and continues to drag down and up to multiselect more images and scroll the grid while doing so.
A flowery picture grid with multi-select performance

The steps we’ll take to get to this finish end result:

  • Implement a fundamental grid
  • Add the choice state to the grid parts
  • Add gesture dealing with so we will choose / deselect parts with drag
  • Ending touches to make the weather appear like photographs

Simply wanna see the code? Right here’s the full snippet!

We implement this grid as a LazyVerticalGrid, in order that the app works effectively on all display screen sizes. Bigger screens will present extra columns, smaller screens will present much less columns.

We’re already referring to the weather as photographs, despite the fact that we’re simply exhibiting a easy coloured Floor at this cut-off date. With simply these couple of strains of code, we have already got a pleasant grid that we will scroll via:

A screencast of an app with a 3-column grid of pink boxes, where the user scrolls through this grid.
A really fundamental grid to get us began

Nonetheless, a easy grid doesn’t deliver us very far on our multi-select journey. We have to monitor the at the moment chosen gadgets, and whether or not we’re at the moment in choice mode, and make our parts replicate that state.

First, let’s extract our grid gadgets into their very own composable, that displays their choice state. This composable will:

  • Be empty if the consumer is not in choice mode
  • Present an empty radio button when the consumer is in choice mode and the aspect is not chosen
  • Present a checkmark when the consumer is in choice mode and the aspect is chosen
Renders of the three different states: No selection mode is an empty blue box, selection mode and deselected shows a blue box with empty circle in it, and selection mode and selected shows a blue box with a checkmark in it.
The assorted choice states of the merchandise

This composable is stateless, because it doesn’t maintain any of its personal state. It merely displays the state you cross into it.

To make the gadgets reply to their chosen states, the grid ought to hold monitor of those states. Additionally, the consumer ought to be capable to change the chosen worth by interacting with the gadgets within the grid. For now, we’ll merely toggle an merchandise’s chosen state when the consumer faucets it:

We monitor the chosen gadgets in a set. When the consumer clicks one of many ImageItem cases, the id of that merchandise is added or faraway from the set.

Whether or not we’re in choice mode is outlined by checking if there are any at the moment chosen parts. Each time the set of chosen ids modifications, this variable will routinely be recalculated.

With this addition, we will now add and take away parts from the choice by clicking them:

Screencast of same app, but user is clicking items to select them. Once at least one item is selected, all boxes show an empty box, and the selected ones show a checkmark.
This appears to be like like a elaborate sport of tic-tac-toe!

Now that we’re monitoring state, we will implement the right gestures that ought to add and take away parts from the choice. Our necessities are as follows:

  1. Enter choice mode by long-pressing a component
  2. Drag after long-press so as to add all or take away all parts between origin and goal aspect
  3. When in choice mode, add or take away parts by clicking them
  4. Lengthy-press on an already chosen aspect doesn’t do something

The second requirement is the trickiest. As we must adapt the set of chosen ids throughout drag, we have to add the gesture dealing with to the grid, not the weather themselves. We have to do our personal hit detection to determine which aspect within the grid the pointer is at the moment pointing at. That is doable with a mixture of LazyGridState and the drag change place.

To begin, let’s hoist the LazyGridState out of the lazy grid and cross it on in the direction of our customized gesture handler. This enables us to learn grid data and use it elsewhere. Extra particularly, we will use it to determine which merchandise within the grid the consumer is at the moment pointing at.

We are able to make the most of the pointerInput modifier and the detectDragGesturesAfterLongPress methodology to set-up our drag dealing with:

As you possibly can see on this code snippet, we’re monitoring the initialKey and the currentKey internally within the gesture handler. We’ll must set the preliminary key on drag begin, and replace the present key every time the consumer strikes to a unique aspect with their pointer.

Let’s first implement onDragStart:

Strolling via this step-by-step, this methodology:

  1. Finds the important thing of the merchandise beneath the pointer, if any. This represents the aspect that the consumer is long-pressing and can begin the drag gesture from.
  2. If it finds an merchandise (the consumer is pointing at a component within the grid), it checks if this merchandise remains to be unselected (thereby fulfilling requirement 4).
  3. Units each the preliminary and the present key to this key worth, and proactively provides it to the record of chosen parts.

We’ve to implement the helper methodology gridItemKeyAtPosition ourselves:

For every seen merchandise within the grid, this methodology checks if the hitPoint falls inside its bounds.

Now we solely must replace the onDrag lambda, that can be referred to as recurrently whereas the consumer strikes their pointer over the display screen:

A drag is simply dealt with when the preliminary key’s set. Primarily based on the preliminary key and the present key, this lambda will replace the set of chosen gadgets. It makes certain that every one parts between the preliminary key and the present key are chosen.

With this setup, we will now drag to pick out a number of parts:

App with the same checkmarks but now with the user’s finger dragging over the screen and thereby selecting whole groups of items at once.
Together with drag help for our checkmarks

Lastly, we have to exchange the clickable habits of the person parts, so we will add/take away them from the choice whereas we’re in choice mode. That is additionally the fitting time to begin enthusiastic about the accessibility of this gesture handler. The customized drag gesture we created with the pointerInput modifier doesn’t have accessibility help, so companies like Talkback won’t embody that long-press and drag habits. As an alternative, we will provide an various choice mechanism for customers of accessibility companies, letting them enter choice mode by long-pressing a component. We do that by setting the onLongClick semantic property.

The semantics modifier means that you can override or add properties and motion handlers utilized by accessibility companies to work together with the display screen with out counting on contact. More often than not, the Compose system handles this for you routinely, however on this case we have to explicitly add the long-press habits.

As well as, by utilizing the toggleable modifier for the merchandise (and solely including it when the consumer is in choice mode) we be certain Talkback can present data to the consumer concerning the present chosen state of the merchandise.

As you possibly can see within the display screen recording above, we at the moment can’t drag additional than the highest and backside edges of the display screen. This limits the performance of the choice mechanism. We’d just like the grid to scroll once we strategy the sides of the display screen with our pointer. Moreover, we should always scroll quicker the nearer we consumer strikes the pointer to the sting of the display screen.

The specified finish end result:

User dragging over the screen to multi-select, and when reaching the bottom of the screen, the grid scrolls down to allow for more selection.
So many checkmarks!

First, we’ll change our drag handler to have the ability to set the scroll velocity based mostly on the space from the highest or backside of the container:

As you possibly can see, we replace the scroll velocity based mostly on the edge and distance, and ensure to reset the scroll velocity when the drag ends or is canceled.

Now altering this scroll velocity worth from the gesture handler doesn’t do something but. We have to replace the PhotoGrid composable to begin scrolling the grid when the worth modifications:

Each time the worth of the scroll velocity variable modifications, the LaunchedEffect is retriggered and the scrolling will restart.

You would possibly marvel why we didn’t instantly change the scroll degree from throughout the onDrag handler. The reason being that the onDrag lambda is solely referred to as when the consumer really strikes the pointer! So if the consumer holds their finger very nonetheless on the display screen, the scrolling would cease. You may need seen this scrolling bug in apps earlier than, the place it is advisable “scrub” the underside of your display screen to let it scroll.

With this final addition, the habits of our grid is kind of strong. Nonetheless, it doesn’t look very similar to the instance we began the weblog put up with. Let’s ensure that the grid gadgets replicate precise photographs:

As you possibly can see, we expanded the record of photographs to have a URL along with the id. Utilizing that URL, we will load a picture within the grid merchandise. When switching between choice modes, the padding and nook form of that picture modifications, and we use an animation to make that change seem easily.

A screencast of an app with a vertical grid of 3 columns, each item displaying a random image. The user long-presses one image to select it, and continues to drag down and up to multiselect more images and scroll the grid while doing so.
The top end result. Isn’t it stunning?

Examine the complete code on this GitHub snippet. With lower than 200 strains of code, we created a strong UI that features wealthy interactions.

Made your individual cool interplay utilizing Compose? Let me know on https://androiddev.social/@lojanda

Because of Rebecca Franks, Florina Muntenescu, and Levi Albuquerque for reviewing this put up.

Code snippets license:

Copyright 2023 Google LLC.
SPDX-License-Identifier: Apache-2.0
RELATED ARTICLES

LEAVE A REPLY

Please enter your comment!
Please enter your name here

Most Popular

Recent Comments