Merge pull request #6462 from thundernest/limit_swipe_distance
Swipe actions: Limit how far list items can be dragged
This commit is contained in:
commit
e5f5744186
10 changed files with 2712 additions and 21 deletions
|
@ -1,12 +1,12 @@
|
|||
package com.fsck.k9
|
||||
|
||||
enum class SwipeAction {
|
||||
None,
|
||||
ToggleSelection,
|
||||
ToggleRead,
|
||||
ToggleStar,
|
||||
Archive,
|
||||
Delete,
|
||||
Spam,
|
||||
Move
|
||||
enum class SwipeAction(val removesItem: Boolean) {
|
||||
None(removesItem = false),
|
||||
ToggleSelection(removesItem = false),
|
||||
ToggleRead(removesItem = false),
|
||||
ToggleStar(removesItem = false),
|
||||
Archive(removesItem = true),
|
||||
Delete(removesItem = true),
|
||||
Spam(removesItem = true),
|
||||
Move(removesItem = true)
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ dependencies {
|
|||
implementation "com.takisoft.preferencex:preferencex-colorpicker:${versions.preferencesFix}"
|
||||
implementation "androidx.recyclerview:recyclerview:${versions.androidxRecyclerView}"
|
||||
implementation project(':ui-utils:LinearLayoutManager')
|
||||
implementation project(':ui-utils:ItemTouchHelper')
|
||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:${versions.androidxLifecycle}"
|
||||
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:${versions.androidxLifecycle}"
|
||||
implementation "androidx.lifecycle:lifecycle-livedata-ktx:${versions.androidxLifecycle}"
|
||||
|
|
|
@ -15,9 +15,9 @@ import android.widget.Toast
|
|||
import androidx.appcompat.view.ActionMode
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import app.k9mail.ui.utils.itemtouchhelper.ItemTouchHelper
|
||||
import app.k9mail.ui.utils.linearlayoutmanager.LinearLayoutManager
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.Account.Expunge
|
||||
|
|
|
@ -10,9 +10,9 @@ import android.view.View.MeasureSpec
|
|||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.graphics.withTranslation
|
||||
import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import app.k9mail.ui.utils.itemtouchhelper.ItemTouchHelper
|
||||
import com.fsck.k9.SwipeAction
|
||||
import com.fsck.k9.ui.R
|
||||
import kotlin.math.abs
|
||||
|
@ -27,12 +27,16 @@ class MessageListSwipeCallback(
|
|||
private val adapter: MessageListAdapter,
|
||||
private val listener: MessageListSwipeListener
|
||||
) : ItemTouchHelper.Callback() {
|
||||
private val swipePadding = context.resources.getDimension(R.dimen.messageListSwipeIconPadding).toInt()
|
||||
private val swipeThreshold = context.resources.getDimension(R.dimen.messageListSwipeThreshold)
|
||||
private val backgroundColorPaint = Paint()
|
||||
|
||||
private val swipeRightLayout: View
|
||||
private val swipeLeftLayout: View
|
||||
|
||||
private var maxSwipeRightDistance: Int = -1
|
||||
private var maxSwipeLeftDistance: Int = -1
|
||||
|
||||
init {
|
||||
val layoutInflater = LayoutInflater.from(context)
|
||||
|
||||
|
@ -101,14 +105,16 @@ class MessageListSwipeCallback(
|
|||
val viewWidth = view.width
|
||||
val viewHeight = view.height
|
||||
|
||||
val isViewAnimatingBack = !isCurrentlyActive && abs(dX).toInt() >= viewWidth
|
||||
val isViewAnimatingBack = !isCurrentlyActive
|
||||
|
||||
canvas.withTranslation(x = view.left.toFloat(), y = view.top.toFloat()) {
|
||||
if (isViewAnimatingBack) {
|
||||
drawBackground(dX, viewWidth, viewHeight)
|
||||
} else {
|
||||
val holder = viewHolder as MessageViewHolder
|
||||
drawLayout(dX, viewWidth, viewHeight, holder)
|
||||
if (dX != 0F) {
|
||||
canvas.withTranslation(x = view.left.toFloat(), y = view.top.toFloat()) {
|
||||
if (isViewAnimatingBack) {
|
||||
drawBackground(dX, viewWidth, viewHeight)
|
||||
} else {
|
||||
val holder = viewHolder as MessageViewHolder
|
||||
drawLayout(dX, viewWidth, viewHeight, holder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,10 +172,42 @@ class MessageListSwipeCallback(
|
|||
val heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)
|
||||
swipeLayout.measure(widthMeasureSpec, heightMeasureSpec)
|
||||
swipeLayout.layout(0, 0, width, height)
|
||||
|
||||
if (swipeRight) {
|
||||
maxSwipeRightDistance = textView.right + swipePadding
|
||||
} else {
|
||||
maxSwipeLeftDistance = swipeLayout.width - textView.left + swipePadding
|
||||
}
|
||||
}
|
||||
|
||||
swipeLayout.draw(this)
|
||||
}
|
||||
|
||||
override fun getMaxSwipeDistance(recyclerView: RecyclerView, direction: Int): Int {
|
||||
return when (direction) {
|
||||
ItemTouchHelper.RIGHT -> if (maxSwipeRightDistance > 0) maxSwipeRightDistance else recyclerView.width
|
||||
ItemTouchHelper.LEFT -> if (maxSwipeLeftDistance > 0) maxSwipeLeftDistance else recyclerView.width
|
||||
else -> recyclerView.width
|
||||
}
|
||||
}
|
||||
|
||||
override fun shouldAnimateOut(direction: Int): Boolean {
|
||||
return when (direction) {
|
||||
ItemTouchHelper.RIGHT -> swipeRightAction.removesItem
|
||||
ItemTouchHelper.LEFT -> swipeLeftAction.removesItem
|
||||
else -> error("Unsupported direction")
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAnimationDuration(
|
||||
recyclerView: RecyclerView,
|
||||
animationType: Int,
|
||||
animateDx: Float,
|
||||
animateDy: Float
|
||||
): Long {
|
||||
val percentage = abs(animateDx) / recyclerView.width
|
||||
return (super.getAnimationDuration(recyclerView, animationType, animateDx, animateDy) * percentage).toLong()
|
||||
}
|
||||
}
|
||||
|
||||
fun interface SwipeActionSupportProvider {
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/swipe_action_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
|
@ -37,8 +37,9 @@
|
|||
android:maxLines="1"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
android:textColor="?attr/messageListSwipeIconTint"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toLeftOf="@+id/swipe_action_icon"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
<TextView
|
||||
android:id="@+id/swipe_action_text"
|
||||
android:layout_width="0dp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/messageListSwipeTextPadding"
|
||||
android:layout_marginTop="16dp"
|
||||
|
@ -37,6 +37,7 @@
|
|||
android:maxLines="1"
|
||||
android:textAppearance="@style/TextAppearance.AppCompat.Body1"
|
||||
android:textColor="?attr/messageListSwipeIconTint"
|
||||
app:layout_constrainedWidth="true"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintLeft_toRightOf="@+id/swipe_action_icon"
|
||||
|
|
|
@ -13,6 +13,7 @@ include ':app:autodiscovery:srvrecords'
|
|||
include ':app:autodiscovery:thunderbird'
|
||||
include ':app:html-cleaner'
|
||||
include ':ui-utils:LinearLayoutManager'
|
||||
include ':ui-utils:ItemTouchHelper'
|
||||
include ':mail:common'
|
||||
include ':mail:testing'
|
||||
include ':mail:protocols:imap'
|
||||
|
|
27
ui-utils/ItemTouchHelper/build.gradle
Normal file
27
ui-utils/ItemTouchHelper/build.gradle
Normal file
|
@ -0,0 +1,27 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
dependencies {
|
||||
api "androidx.recyclerview:recyclerview:${versions.androidxRecyclerView}"
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'app.k9mail.ui.utils.itemtouchhelper'
|
||||
|
||||
compileSdkVersion buildConfig.compileSdk
|
||||
buildToolsVersion buildConfig.buildTools
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion buildConfig.minSdk
|
||||
targetSdkVersion buildConfig.robolectricSdk
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
lintConfig file("$rootProject.projectDir/config/lint/lint.xml")
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility javaVersion
|
||||
targetCompatibility javaVersion
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,93 @@
|
|||
/*
|
||||
* Copyright 2018 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package app.k9mail.ui.utils.itemtouchhelper;
|
||||
|
||||
import android.graphics.Canvas;
|
||||
import android.os.Build;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.core.view.ViewCompat;
|
||||
import androidx.recyclerview.R;
|
||||
import androidx.recyclerview.widget.ItemTouchUIUtil;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
|
||||
/**
|
||||
* Package private class to keep implementations. Putting them inside ItemTouchUIUtil makes them
|
||||
* public API, which is not desired in this case.
|
||||
*/
|
||||
class ItemTouchUIUtilImpl implements ItemTouchUIUtil {
|
||||
static final ItemTouchUIUtil INSTANCE = new ItemTouchUIUtilImpl();
|
||||
|
||||
@Override
|
||||
public void onDraw(Canvas c, RecyclerView recyclerView, View view, float dX, float dY,
|
||||
int actionState, boolean isCurrentlyActive) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
if (isCurrentlyActive) {
|
||||
Object originalElevation = view.getTag(R.id.item_touch_helper_previous_elevation);
|
||||
if (originalElevation == null) {
|
||||
originalElevation = ViewCompat.getElevation(view);
|
||||
float newElevation = 1f + findMaxElevation(recyclerView, view);
|
||||
ViewCompat.setElevation(view, newElevation);
|
||||
view.setTag(R.id.item_touch_helper_previous_elevation, originalElevation);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
view.setTranslationX(dX);
|
||||
view.setTranslationY(dY);
|
||||
}
|
||||
|
||||
private static float findMaxElevation(RecyclerView recyclerView, View itemView) {
|
||||
final int childCount = recyclerView.getChildCount();
|
||||
float max = 0;
|
||||
for (int i = 0; i < childCount; i++) {
|
||||
final View child = recyclerView.getChildAt(i);
|
||||
if (child == itemView) {
|
||||
continue;
|
||||
}
|
||||
final float elevation = ViewCompat.getElevation(child);
|
||||
if (elevation > max) {
|
||||
max = elevation;
|
||||
}
|
||||
}
|
||||
return max;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDrawOver(Canvas c, RecyclerView recyclerView, View view, float dX, float dY,
|
||||
int actionState, boolean isCurrentlyActive) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearView(View view) {
|
||||
if (Build.VERSION.SDK_INT >= 21) {
|
||||
final Object tag = view.getTag(R.id.item_touch_helper_previous_elevation);
|
||||
if (tag instanceof Float) {
|
||||
ViewCompat.setElevation(view, (Float) tag);
|
||||
}
|
||||
view.setTag(R.id.item_touch_helper_previous_elevation, null);
|
||||
}
|
||||
|
||||
view.setTranslationX(0f);
|
||||
view.setTranslationY(0f);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSelected(View view) {
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue