Contextual actions with list items are widely used with Android applications. It is rather convenient to select several elements or all elements of the list and apply some action to all selected elements at once. Delete, for example.
In Android applications, this can be used ActionMode , which allows you to display the available actions on the selected items on top of the Toolbar . You can also show the user how many items are currently selected or other useful information. This is convenient and looks good, but in some cases the information displayed on the Toolbar itself may be important and would not be desirable to hide it. For example, there may be the name and photo of the user, the list of messages with which is displayed in the list. When selecting some messages, it would be useful to see the name of the user to whom these messages are addressed.
In this case, you can display a context action panel with list items on top of the list itself, without blocking the Toolbar . I will discuss the creation of such a panel of contextual actions in this article.
Developed by CustomView - the contextual action panel I called FloatingActionMode or simply FAM .

FloatingActionMode while running (fixed below)
The comments indicated that it may not be very convenient for the user to drag the panel across the screen, so it can be fixed at the bottom of the screen, as shown in the screenshots and video above. (To do this, specify the attributes android:layout_gravity="bottom" and app:fam_can_drag="false" ).
At the same time, it is possible to allow the user to move the FAM around the screen, as shown in the following screens and videos.

FloatingActionMode while running
By default, the FAM does not have a background , so you can use whatever you need. Also, the android:translationZ="8dp" attribute can be used to create shadows on devices with API> = 21 android:translationZ="8dp"
To configure the FAM through a markup file, several special attributes are defined for it, which can also be changed programmatically:
fam_opened determines whether the FAM open upon creation. ( false by default)
fam_content_res is LayoutRes , which represents FAM content (several buttons, for example). View created from fam_content_res added to the FAM as a child View . The content can be changed programmatically while the application is running, therefore the FAM attribute can be specified android:animateLayoutChanges="true" for animated content changes. (no content by default)
fam_can_close determines whether the FAM have a button for closing. ( true by default)
fam_close_icon is a DrawableRes close button. (default is a cross)
fam_can_drag determines whether the FAM have a drag button. ( true by default)
fam_drag_icon is a fam_drag_icon DrawableRes button. (there is a default value)
fam_can_dismiss determines whether the FAM close if the user drags him far enough horizontally ( true by default)
fam_dismiss_threshold is the horizontal shift threshold starting at which the FAM will be closed when the user fam_drag_button . That is, if ( getTranslationX / getWidth )> dismissThreshold , then the FAM will be closed. ( 0.4f by default)
fam_minimize_direction determines the direction in which the FAM will move when collapsed. This attribute can have the following values ( nearest default):
top - the FAM will move to the upper bound of the parent (excluding indents) during folding.bottom - the FAM will move to the lower border of the parent (excluding indents) during folding.nearest - FAM will move to the nearest (upper or lower) border of the parent (excluding indents) while foldingfam_animation_duration determines the duration of the folding / fam_animation_duration animation. ( 400 ms by default)FAM also has OnCloseListener , which allows you to perform a specific action when the FAM closed by the user (deselect items from the list, for example).
The main actions with FAM are opening / closing and folding / unfolding. When opened, it appears and turns around, and when closed it collapses and disappears.
Expanding a FAM accompanied by an animation, during which it moves from the top or bottom edge of the parent ViewGroup (this edge is set by the fam_minimize_direction attribute) to its position specified by the markup file. Animation is set as follows:
animate() .scaleY(1f) .scaleX(1f) .translationY(calculateArrangeTranslationY()) .alpha(1f) When collapsing, the animation is performed "in the opposite direction":
animate() .scaleY(0.5f) .scaleX(0.5f) .translationY(calculateMinimizeTranslationY()) .alpha(0.5f) The calculateArrangeTranslationY() and calculateMinimizeTranslationY() methods allow you to calculate the translationY for the expanded and collapsed states, respectively, taking into account where the FAM user dragged the fam_minimize_direction attribute and the bottom and top indents, which will be discussed later.
For correct and beautiful work, FAM has buttons ( ImageView ) with the help of which the user can close the context actions mode or drag to another part of the screen vertically (if he is blocking the desired list item). The FAM can also be closed by dragging it horizontally (swipe to dismiss).
FAM is a LinearLayout , in which buttons are added during creation to close ( fam_drag_button ) and drag ( fam_close_button ). The ability to close / drag FAM can be turned on / off while the application is running, so the LinearLayout containing these buttons has the android:animateLayoutChanges="true" attribute android:animateLayoutChanges="true" .
<?xml version="1.0" encoding="utf-8"?> <merge xmlns:android="http://schemas.android.com/apk/res/android"> <LinearLayout android:layout_width="wrap_content" android:layout_height="?attr/actionBarSize" android:animateLayoutChanges="true" android:layout_gravity="center_vertical"> <ImageView android:id="@+id/fam_close_button" android:layout_width="?attr/actionBarSize" android:layout_height="?attr/actionBarSize" android:layout_gravity="center_vertical" android:background="@drawable/fam_image_button_background" android:scaleType="center" android:src="@drawable/fam_ic_close_white_24dp"/> <ImageView android:id="@+id/fam_drag_button" android:layout_width="?attr/actionBarSize" android:layout_height="?attr/actionBarSize" android:layout_gravity="center_vertical" android:background="@drawable/fam_image_button_background" android:scaleType="center" android:src="@drawable/fam_ic_drag_white_24dp"/> </LinearLayout> </merge> The drag and drop mechanism is implemented using OnTouchListener , which remembers the starting point of the touch and sets translationX and translationY to the touch when moving. When the user releases the drag button ( fam_drag_button ), the FAM returns to its original position horizontally, and if the user drags the FAM far enough horizontally, the method is called this@FloatingActionMode.close() .
fam_drag_button.setOnTouchListener(object : OnTouchListener { var prevTransitionY = 0f var startRawX = 0f var startRawY = 0f override fun onTouch(v: View, event: MotionEvent): Boolean { if (!this@FloatingActionMode.canDrag) { return false } val fractionX = Math.abs(event.rawX - startRawX) / this@FloatingActionMode.width when (event.actionMasked) { MotionEvent.ACTION_DOWN -> { this@FloatingActionMode.fam_drag_button.isPressed = true startRawX = event.rawX startRawY = event.rawY prevTransitionY = this@FloatingActionMode.translationY } MotionEvent.ACTION_MOVE -> { this@FloatingActionMode.maximizeTranslationY = prevTransitionY + event.rawY - startRawY translationX = event.rawX - startRawX if (canDismiss) { val alpha = if (fractionX < dismissThreshold) 1.0f else Math.pow(1.0 - (fractionX - dismissThreshold) / (1 - dismissThreshold), 4.0).toFloat() this@FloatingActionMode.alpha = alpha } } MotionEvent.ACTION_UP -> { fam_drag_button.isPressed = false this@FloatingActionMode.animate().translationX(0f) .duration = animationDuration if (canDismiss && fractionX > dismissThreshold) { this@FloatingActionMode.close() } } } return true } }) CoordinatorLayout UsageAs previously stated, the calculateArrangeTranslationY() and calculateMinimizeTranslationY() methods take into account the indents at the top and bottom to determine the correct position of the FAM . These indents are calculated using FloatingActionModeBehavior — the CoordinatorLayout.Behavior extensions, which AppBarLayout upper indent as the AppBarLayout height and the lower indent as the visible part of the Snackbar.SnackbarLayout .
Also, FloatingActionModeBehavior allows the FAM respond to scrolling, turning when scrolling down and turning around when scrolling up (quick return pattern).
open class FloatingActionModeBehavior @JvmOverloads constructor(context: Context? = null, attrs: AttributeSet? = null) : CoordinatorLayout.Behavior<FloatingActionMode>(context, attrs) { override fun layoutDependsOn(parent: CoordinatorLayout?, child: FloatingActionMode?, dependency: View?): Boolean { return dependency is AppBarLayout || dependency is Snackbar.SnackbarLayout } override fun onDependentViewChanged(parent: CoordinatorLayout, child: FloatingActionMode, dependency: View): Boolean { when (dependency) { is AppBarLayout -> child.topOffset = dependency.bottom is Snackbar.SnackbarLayout -> child.bottomOffset = dependency.height - dependency.translationY.toInt() } return false } override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout?, child: FloatingActionMode?, directTargetChild: View?, target: View?, nestedScrollAxes: Int): Boolean { return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL } override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, child: FloatingActionMode, target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int) { super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed) // FAM View. var parent = target.parent while (parent != coordinatorLayout) { if (parent == child) { return } parent = parent.parent } if (dyConsumed > 0) { child.minimize(true) } else if (dyConsumed < 0) { child.maximize(true) } } } This is how a FAM might look like in a markup file:
<android.support.design.widget.CoordinatorLayout> <android.support.design.widget.AppBarLayout> ... </android.support.design.widget.AppBarLayout> <android.support.v7.widget.RecyclerView app:layout_behavior="@string/appbar_scrolling_view_behavior"/> <com.qwert2603.floating_action_mode.FloatingActionMode android:id="@+id/floating_action_mode" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="@dimen/action_mode_margin" android:animateLayoutChanges="true" android:background="@drawable/action_mode_background" android:translationZ="8dp" app:fam_animation_duration="@integer/action_mode_animation_duration" app:fam_can_dismiss="true" app:fam_can_drag="true" app:fam_content_res="@layout/user_list_action_mode_2" app:fam_dismiss_threshold="0.35" app:fam_drag_icon="@drawable/ic_drag_white_24dp" app:fam_minimize_direction="nearest"/> </android.support.design.widget.CoordinatorLayout> The source code for FloatingActionMode is available on GitHub (the library directory). There is also a demo application using FAM ( app directory).
FloatingActionMode itself, as well as FloatingActionModeBehavior defined as open classes, so you can upgrade them as you need. Key FloatingActionMode methods are also defined as open .
Thanks for attention. Happy coding!
Source: https://habr.com/ru/post/319238/
All Articles