Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/stefan-niedermann/nextcloud-deck.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Niedermann <info@niedermann.it>2020-04-08 12:42:37 +0300
committerStefan Niedermann <info@niedermann.it>2020-04-08 12:42:37 +0300
commit35f20c292bb4f1f3c7b9aadc8427fd86358a64e9 (patch)
treea72f3cd5791fb144ab295149e8e12e7edf377618 /cross-tab-drag-and-drop
parentaae206befa986e11f29b61d8761b1cf7b6599fa3 (diff)
Move TabLayoutHelper to own library
Signed-off-by: Stefan Niedermann <info@niedermann.it>
Diffstat (limited to 'cross-tab-drag-and-drop')
-rw-r--r--cross-tab-drag-and-drop/.gitignore1
-rw-r--r--cross-tab-drag-and-drop/build.gradle40
-rw-r--r--cross-tab-drag-and-drop/consumer-rules.pro0
-rw-r--r--cross-tab-drag-and-drop/proguard-rules.pro21
-rw-r--r--cross-tab-drag-and-drop/src/androidTest/java/it/niedermann/android/crosstabdnd/ExampleInstrumentedTest.java27
-rw-r--r--cross-tab-drag-and-drop/src/main/AndroidManifest.xml1
-rw-r--r--cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/CrossTabDragAndDrop.java221
-rw-r--r--cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropAdapter.java14
-rw-r--r--cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropModel.java8
-rw-r--r--cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropTab.java12
-rw-r--r--cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropUtil.java18
-rw-r--r--cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DraggedItemLocalState.java95
-rw-r--r--cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/ItemMovedByDragListener.java5
-rw-r--r--cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/ScrollHelper.java46
-rw-r--r--cross-tab-drag-and-drop/src/main/res/values/integers.xml9
-rw-r--r--cross-tab-drag-and-drop/src/test/java/it/niedermann/android/crosstabdnd/ExampleUnitTest.java17
16 files changed, 535 insertions, 0 deletions
diff --git a/cross-tab-drag-and-drop/.gitignore b/cross-tab-drag-and-drop/.gitignore
new file mode 100644
index 000000000..796b96d1c
--- /dev/null
+++ b/cross-tab-drag-and-drop/.gitignore
@@ -0,0 +1 @@
+/build
diff --git a/cross-tab-drag-and-drop/build.gradle b/cross-tab-drag-and-drop/build.gradle
new file mode 100644
index 000000000..4b5fd3a59
--- /dev/null
+++ b/cross-tab-drag-and-drop/build.gradle
@@ -0,0 +1,40 @@
+apply plugin: 'com.android.library'
+
+android {
+ compileSdkVersion 29
+ buildToolsVersion "29.0.3"
+
+ defaultConfig {
+ minSdkVersion 14
+ targetSdkVersion 29
+ versionCode 1
+ versionName "1.0"
+
+ testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
+ consumerProguardFiles 'consumer-rules.pro'
+ }
+
+ buildTypes {
+ release {
+ minifyEnabled false
+ proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+}
+
+dependencies {
+ implementation fileTree(dir: 'libs', include: ['*.jar'])
+
+ implementation 'com.google.android.material:material:1.1.0'
+ implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation "androidx.viewpager2:viewpager2:1.0.0"
+
+ testImplementation 'junit:junit:4.13'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.1'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+}
diff --git a/cross-tab-drag-and-drop/consumer-rules.pro b/cross-tab-drag-and-drop/consumer-rules.pro
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/cross-tab-drag-and-drop/consumer-rules.pro
diff --git a/cross-tab-drag-and-drop/proguard-rules.pro b/cross-tab-drag-and-drop/proguard-rules.pro
new file mode 100644
index 000000000..f1b424510
--- /dev/null
+++ b/cross-tab-drag-and-drop/proguard-rules.pro
@@ -0,0 +1,21 @@
+# Add project specific ProGuard rules here.
+# You can control the set of applied configuration files using the
+# proguardFiles setting in build.gradle.
+#
+# For more details, see
+# http://developer.android.com/guide/developing/tools/proguard.html
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+# public *;
+#}
+
+# Uncomment this to preserve the line number information for
+# debugging stack traces.
+#-keepattributes SourceFile,LineNumberTable
+
+# If you keep the line number information, uncomment this to
+# hide the original source file name.
+#-renamesourcefileattribute SourceFile
diff --git a/cross-tab-drag-and-drop/src/androidTest/java/it/niedermann/android/crosstabdnd/ExampleInstrumentedTest.java b/cross-tab-drag-and-drop/src/androidTest/java/it/niedermann/android/crosstabdnd/ExampleInstrumentedTest.java
new file mode 100644
index 000000000..122d51143
--- /dev/null
+++ b/cross-tab-drag-and-drop/src/androidTest/java/it/niedermann/android/crosstabdnd/ExampleInstrumentedTest.java
@@ -0,0 +1,27 @@
+package it.niedermann.android.crosstabdnd;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Instrumented test, which will execute on an Android device.
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+@RunWith(AndroidJUnit4.class)
+public class ExampleInstrumentedTest {
+ @Test
+ public void useAppContext() {
+ // Context of the app under test.
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ assertEquals("it.niedermann.android.crosstabdnd.test", appContext.getPackageName());
+ }
+}
diff --git a/cross-tab-drag-and-drop/src/main/AndroidManifest.xml b/cross-tab-drag-and-drop/src/main/AndroidManifest.xml
new file mode 100644
index 000000000..627a1ec7c
--- /dev/null
+++ b/cross-tab-drag-and-drop/src/main/AndroidManifest.xml
@@ -0,0 +1 @@
+<manifest package="it.niedermann.android.crosstabdnd" />
diff --git a/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/CrossTabDragAndDrop.java b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/CrossTabDragAndDrop.java
new file mode 100644
index 000000000..4129bf9d8
--- /dev/null
+++ b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/CrossTabDragAndDrop.java
@@ -0,0 +1,221 @@
+package it.niedermann.android.crosstabdnd;
+
+import android.content.res.Resources;
+import android.util.Log;
+import android.view.DragEvent;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.google.android.material.tabs.TabLayout;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+public class CrossTabDragAndDrop<
+ TabFragment extends Fragment & DragAndDropTab<ItemAdapter>,
+ ItemAdapter extends RecyclerView.Adapter<?> & DragAndDropAdapter<ItemModel>,
+ ItemModel extends DragAndDropModel
+ > {
+
+ private static final String TAG = CrossTabDragAndDrop.class.getCanonicalName();
+ private static final ScrollHelper SCROLL_HELPER = new ScrollHelper();
+
+ private final float pxToReact;
+ private final float pxToReactTopBottom;
+ private final int dragAndDropMsToReact;
+ private final int dragAndDropMsToReactTopBottom;
+ private final int displayX;
+ private long lastSwap = 0;
+ private long lastMove = 0;
+
+ private final Set<ItemMovedByDragListener<ItemModel>> moveListenerList = new HashSet<>(1);
+
+ public CrossTabDragAndDrop(@NonNull Resources resources) {
+ this.displayX = resources.getDisplayMetrics().widthPixels;
+ final float density = resources.getDisplayMetrics().density;
+ this.pxToReact = resources.getInteger(R.integer.drag_n_drop_dp_to_react) * density;
+ this.dragAndDropMsToReactTopBottom = resources.getInteger(R.integer.drag_n_drop_dp_to_react_top_bottom);
+ this.pxToReactTopBottom = dragAndDropMsToReactTopBottom * density;
+ this.dragAndDropMsToReact = resources.getInteger(R.integer.drag_n_drop_ms_to_react);
+ }
+
+ public void register(final ViewPager2 viewPager, TabLayout stackLayout, FragmentManager fm) {
+ viewPager.setOnDragListener((View v, DragEvent dragEvent) -> {
+ DraggedItemLocalState<TabFragment, ItemAdapter, ItemModel> draggedItemLocalState = (DraggedItemLocalState<TabFragment, ItemAdapter, ItemModel>) dragEvent.getLocalState();
+ View draggedView = draggedItemLocalState.getDraggedView();
+ switch (dragEvent.getAction()) {
+ case DragEvent.ACTION_DRAG_STARTED: {
+ draggedView.setVisibility(View.INVISIBLE);
+ draggedItemLocalState.onDragStart(viewPager, fm);
+ break;
+ }
+ case DragEvent.ACTION_DRAG_LOCATION: {
+ RecyclerView currentRecyclerView = draggedItemLocalState.getRecyclerView();
+ ItemAdapter itemAdapter = draggedItemLocalState.getItemAdapter();
+
+ long now = System.currentTimeMillis();
+ if (lastSwap + dragAndDropMsToReact < now) { // don't change Tabs so fast!
+ int oldTabPosition = viewPager.getCurrentItem();
+
+ boolean shouldSwitchTab = true;
+
+ int newTabPosition = -1;
+ // change tab? if yes, which direction?
+ if (dragEvent.getX() <= pxToReact) {
+ newTabPosition = oldTabPosition - 1;
+ } else if (dragEvent.getX() >= displayX - pxToReact) {
+ newTabPosition = oldTabPosition + 1;
+ } else {
+ shouldSwitchTab = false;
+ }
+
+ if (shouldSwitchTab && isMovePossible(viewPager, newTabPosition)) {
+ removeItem(currentRecyclerView, draggedView, itemAdapter);
+ detectAndKillDuplicatesInNeighbourTab(viewPager, draggedItemLocalState.getDraggedItemModel(), fm, oldTabPosition, newTabPosition);
+ switchTab(dragEvent, viewPager, stackLayout, fm, draggedItemLocalState, now, newTabPosition);
+
+ return true;
+ }
+ }
+
+ //scroll if needed
+ if (dragEvent.getY() <= pxToReactTopBottom) {
+ SCROLL_HELPER.startScroll(currentRecyclerView, ScrollHelper.ScrollDirection.UP);
+ } else if (dragEvent.getY() >= currentRecyclerView.getBottom() - pxToReactTopBottom) {
+ SCROLL_HELPER.startScroll(currentRecyclerView, ScrollHelper.ScrollDirection.DOWN);
+ } else {
+ SCROLL_HELPER.stopScroll();
+ }
+
+ if (lastMove + dragAndDropMsToReactTopBottom < now) {
+ //push around the other items
+ pushAroundItems(draggedView, currentRecyclerView, dragEvent, itemAdapter, draggedItemLocalState, now);
+ }
+ break;
+ }
+ case DragEvent.ACTION_DRAG_ENDED:
+ case DragEvent.ACTION_DROP: {
+ draggedItemLocalState.getRecyclerView().removeOnChildAttachStateChangeListener(draggedItemLocalState.getInsertedListener());
+ SCROLL_HELPER.stopScroll();
+ draggedView.setVisibility(View.VISIBLE);
+ // Clean up the original dragged view, so the next onBindViewHolder() will not display the view at the position of the original dragged view as View.INVISIBLE
+ draggedItemLocalState.getOriginalDraggedView().setVisibility(View.VISIBLE);
+ notifyListeners(draggedItemLocalState);
+ break;
+ }
+ }
+ return true;
+ });
+ }
+
+ private void switchTab(DragEvent dragEvent, ViewPager2 viewPager, TabLayout stackLayout, FragmentManager fm, final DraggedItemLocalState<TabFragment, ItemAdapter, ItemModel> draggedItemLocalState, long now, int newPosition) {
+ viewPager.setCurrentItem(newPosition);
+ draggedItemLocalState.onTabChanged(viewPager, fm);
+ Objects.requireNonNull(stackLayout.getTabAt(newPosition)).select();
+
+ final RecyclerView recyclerView = draggedItemLocalState.getRecyclerView();
+ final ItemAdapter itemAdapter = draggedItemLocalState.getItemAdapter();
+
+ RecyclerView.OnChildAttachStateChangeListener onChildAttachStateChangeListener = new RecyclerView.OnChildAttachStateChangeListener() {
+ @Override
+ public void onChildViewAttachedToWindow(@NonNull View view) {
+ recyclerView.removeOnChildAttachStateChangeListener(this);
+ draggedItemLocalState.setInsertedListener(null);
+ view.setVisibility(View.INVISIBLE);
+ draggedItemLocalState.setDraggedView(view);
+ pushAroundItems(view, recyclerView, dragEvent, itemAdapter, (DraggedItemLocalState<TabFragment, ItemAdapter, ItemModel>) draggedItemLocalState, now);
+ }
+
+ @Override
+ public void onChildViewDetachedFromWindow(@NonNull View view) {/* do nothing */}
+ };
+ draggedItemLocalState.setInsertedListener(onChildAttachStateChangeListener);
+ recyclerView.addOnChildAttachStateChangeListener(onChildAttachStateChangeListener);
+
+ //insert item in new tab
+ View firstVisibleView = recyclerView.getChildAt(0);
+ int positionToInsert = firstVisibleView == null ? 0 : recyclerView.getChildAdapterPosition(firstVisibleView) + 1;
+ itemAdapter.insertItem(draggedItemLocalState.getDraggedItemModel(), positionToInsert);
+
+ lastSwap = now;
+ }
+
+ private void pushAroundItems(@NonNull View view, RecyclerView recyclerView, DragEvent dragEvent, ItemAdapter itemAdapter, DraggedItemLocalState<TabFragment, ItemAdapter, ItemModel> draggedItemLocalState, long now) {
+ View viewUnder = recyclerView.findChildViewUnder(dragEvent.getX(), dragEvent.getY());
+
+ if (viewUnder != null) {
+ int toPositon = recyclerView.getChildAdapterPosition(viewUnder);
+ if (toPositon != -1) {
+ int fromPosition = recyclerView.getChildAdapterPosition(view);
+ if (fromPosition != -1 && fromPosition != toPositon) {
+ recyclerView.post(() -> {
+ itemAdapter.moveItem(fromPosition, toPositon);
+ draggedItemLocalState.setPositionInItemAdapter(toPositon);
+ });
+ lastMove = now;
+ }
+ }
+ }
+ }
+
+ private static boolean isMovePossible(ViewPager2 viewPager, int newPosition) {
+ return newPosition < Objects.requireNonNull(viewPager.getAdapter()).getItemCount() && newPosition >= 0;
+ }
+
+ private void detectAndKillDuplicatesInNeighbourTab(ViewPager2 viewPager, ItemModel itemToFind, FragmentManager fm, int oldTabPosition, int newTabPosition) {
+ int tabPositionToCheck = newTabPosition > oldTabPosition ? newTabPosition + 1 : newTabPosition - 1;
+
+ if (isMovePossible(viewPager, tabPositionToCheck)) {
+ viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
+ @Override
+ public void onPageSelected(int position) {
+ super.onPageSelected(position);
+ viewPager.unregisterOnPageChangeCallback(this);
+ ItemAdapter itemAdapter = DragAndDropUtil.<TabFragment>getTabFragment(fm, Objects.requireNonNull(viewPager.getAdapter()).getItemId(tabPositionToCheck)).getAdapter();
+ itemAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
+ @Override
+ public void onChanged() {
+ super.onChanged();
+ itemAdapter.unregisterAdapterDataObserver(this);
+ List<ItemModel> itemList = itemAdapter.getItemList();
+ for (int i = 0; i < itemList.size(); i++) {
+ ItemModel c = itemList.get(i);
+ if (itemToFind.getComparableId().equals(c.getComparableId())) {
+ itemAdapter.removeItem(i);
+ itemAdapter.notifyItemRemoved(i);
+ Log.v(TAG, "DnD removed dupe at tab " + tabPositionToCheck + ": " + c.toString());
+ break;
+ }
+ }
+ }
+ });
+ }
+ });
+ }
+ }
+
+ private void removeItem(RecyclerView currentRecyclerView, View view, ItemAdapter itemAdapter) {
+ int oldItemPosition = currentRecyclerView.getChildAdapterPosition(view);
+
+ if (oldItemPosition != -1) {
+ itemAdapter.removeItem(oldItemPosition);
+ }
+ }
+
+ private void notifyListeners(DraggedItemLocalState draggedItemLocalState) {
+ for (ItemMovedByDragListener listener : moveListenerList) {
+ listener.onItemMoved(draggedItemLocalState.getDraggedItemModel(), draggedItemLocalState.getCurrentTabId(), draggedItemLocalState.getPositionInItemAdapter());
+ }
+ }
+
+ public void addItemMovedByDragListener(ItemMovedByDragListener<ItemModel> listener) {
+ moveListenerList.add(listener);
+ }
+}
diff --git a/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropAdapter.java b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropAdapter.java
new file mode 100644
index 000000000..4484d04f0
--- /dev/null
+++ b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropAdapter.java
@@ -0,0 +1,14 @@
+package it.niedermann.android.crosstabdnd;
+
+import java.util.List;
+
+public interface DragAndDropAdapter<Model> {
+
+ void removeItem(int position);
+
+ void moveItem(int fromPosition, int toPosition);
+
+ void insertItem(Model item, int position);
+
+ List<Model> getItemList();
+}
diff --git a/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropModel.java b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropModel.java
new file mode 100644
index 000000000..7d12c4275
--- /dev/null
+++ b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropModel.java
@@ -0,0 +1,8 @@
+package it.niedermann.android.crosstabdnd;
+
+import androidx.annotation.NonNull;
+
+public interface DragAndDropModel {
+ @NonNull
+ Long getComparableId();
+}
diff --git a/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropTab.java b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropTab.java
new file mode 100644
index 000000000..cabd3b0cf
--- /dev/null
+++ b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropTab.java
@@ -0,0 +1,12 @@
+package it.niedermann.android.crosstabdnd;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+public interface DragAndDropTab<ItemAdapter extends RecyclerView.Adapter<?> & DragAndDropAdapter<?>> {
+
+ ItemAdapter getAdapter();
+
+ RecyclerView getRecyclerView();
+}
+
+
diff --git a/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropUtil.java b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropUtil.java
new file mode 100644
index 000000000..677cb4f0c
--- /dev/null
+++ b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropUtil.java
@@ -0,0 +1,18 @@
+package it.niedermann.android.crosstabdnd;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentManager;
+
+// Public util
+@SuppressWarnings("WeakerAccess")
+public class DragAndDropUtil {
+
+ private DragAndDropUtil() {
+ // Util class
+ }
+
+ protected static <T> T getTabFragment(@NonNull FragmentManager fm, @Nullable Long currentStackId) throws IllegalArgumentException {
+ return (T) fm.findFragmentByTag("f" + currentStackId);
+ }
+}
diff --git a/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DraggedItemLocalState.java b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DraggedItemLocalState.java
new file mode 100644
index 000000000..b1f0ed535
--- /dev/null
+++ b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DraggedItemLocalState.java
@@ -0,0 +1,95 @@
+package it.niedermann.android.crosstabdnd;
+
+import android.view.View;
+
+import androidx.fragment.app.Fragment;
+import androidx.fragment.app.FragmentManager;
+import androidx.recyclerview.widget.RecyclerView;
+import androidx.viewpager2.widget.ViewPager2;
+
+import java.util.Objects;
+
+@SuppressWarnings("WeakerAccess")
+public class DraggedItemLocalState<
+ TabFragment extends Fragment & DragAndDropTab<ItemAdapter>,
+ ItemAdapter extends RecyclerView.Adapter<?> & DragAndDropAdapter<ItemModel>,
+ ItemModel> {
+ private ItemModel draggedCard;
+ /** The original dragged view */
+ private final View originalDraggedView;
+ /** The currently dragged view (can change when the tab changes */
+ private View draggedView;
+ private ItemAdapter itemAdapter;
+ private int positionInCardAdapter;
+ private RecyclerView.OnChildAttachStateChangeListener insertedListener = null;
+ private RecyclerView recyclerView = null;
+ private long currentTabId;
+
+ public DraggedItemLocalState(ItemModel draggedCard, View draggedView, ItemAdapter itemAdapter, int positionInCardAdapter) {
+ this.draggedCard = draggedCard;
+ this.draggedView = draggedView;
+ this.originalDraggedView = draggedView;
+ this.itemAdapter = itemAdapter;
+ this.positionInCardAdapter = positionInCardAdapter;
+ }
+
+ protected void onDragStart(ViewPager2 viewPager, FragmentManager fm) {
+ this.currentTabId = Objects.requireNonNull(viewPager.getAdapter()).getItemId(viewPager.getCurrentItem());
+ this.recyclerView = DragAndDropUtil.<TabFragment>getTabFragment(fm, currentTabId).getRecyclerView();
+ }
+
+ protected void onTabChanged(ViewPager2 viewPager, FragmentManager fm) {
+ if (insertedListener != null) {
+ this.recyclerView.removeOnChildAttachStateChangeListener(insertedListener);
+ this.insertedListener = null;
+ }
+ this.currentTabId = Objects.requireNonNull(viewPager.getAdapter()).getItemId(viewPager.getCurrentItem());
+ this.recyclerView = DragAndDropUtil.<TabFragment>getTabFragment(fm, currentTabId).getRecyclerView();
+ this.itemAdapter = (ItemAdapter) recyclerView.getAdapter();
+ }
+
+ protected long getCurrentTabId() {
+ return currentTabId;
+ }
+
+ protected ItemModel getDraggedItemModel() {
+ return draggedCard;
+ }
+
+ protected View getOriginalDraggedView() {
+ return originalDraggedView;
+ }
+
+ protected View getDraggedView() {
+ return draggedView;
+ }
+
+ protected void setDraggedView(View draggedView) {
+ this.draggedView = draggedView;
+ }
+
+ protected ItemAdapter getItemAdapter() {
+ return itemAdapter;
+ }
+
+ protected int getPositionInItemAdapter() {
+ return positionInCardAdapter;
+ }
+
+ protected void setPositionInItemAdapter(int positionInCardAdapter) {
+ this.positionInCardAdapter = positionInCardAdapter;
+ }
+
+ protected void setInsertedListener(RecyclerView.OnChildAttachStateChangeListener insertedListener) {
+ this.insertedListener = insertedListener;
+ }
+
+ public RecyclerView.OnChildAttachStateChangeListener getInsertedListener() {
+ return insertedListener;
+ }
+
+ protected RecyclerView getRecyclerView() {
+ return recyclerView;
+ }
+
+}
diff --git a/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/ItemMovedByDragListener.java b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/ItemMovedByDragListener.java
new file mode 100644
index 000000000..737bd2fb0
--- /dev/null
+++ b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/ItemMovedByDragListener.java
@@ -0,0 +1,5 @@
+package it.niedermann.android.crosstabdnd;
+
+public interface ItemMovedByDragListener<ItemModel> {
+ void onItemMoved(ItemModel movedItem, long tabId, int position);
+} \ No newline at end of file
diff --git a/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/ScrollHelper.java b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/ScrollHelper.java
new file mode 100644
index 000000000..47b1d9b17
--- /dev/null
+++ b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/ScrollHelper.java
@@ -0,0 +1,46 @@
+package it.niedermann.android.crosstabdnd;
+
+import android.os.Handler;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+@SuppressWarnings("WeakerAccess")
+public class ScrollHelper implements Runnable {
+
+ private static final int SROLL_SPEED = 200;
+
+ public enum ScrollDirection {
+ UP,
+ DOWN
+ }
+
+ private boolean shouldScroll = false;
+ private ScrollDirection scrollDirection;
+ private RecyclerView currentRecyclerView;
+ private Handler handler = new Handler();
+
+ public void startScroll(RecyclerView recyclerView, ScrollDirection scrollDirection) {
+ this.scrollDirection = scrollDirection;
+ this.currentRecyclerView = recyclerView;
+ if (!shouldScroll) {
+ this.shouldScroll = true;
+ handler.post(this);
+ }
+ }
+
+ public void stopScroll() {
+ this.shouldScroll = false;
+ }
+
+ @Override
+ public void run() {
+ if (scrollDirection == ScrollDirection.UP) {
+ currentRecyclerView.smoothScrollBy(0, SROLL_SPEED * -1);
+ } else {
+ currentRecyclerView.smoothScrollBy(0, SROLL_SPEED);
+ }
+ if (shouldScroll) {
+ handler.postDelayed(this, 100);
+ }
+ }
+} \ No newline at end of file
diff --git a/cross-tab-drag-and-drop/src/main/res/values/integers.xml b/cross-tab-drag-and-drop/src/main/res/values/integers.xml
new file mode 100644
index 000000000..d008bd0bf
--- /dev/null
+++ b/cross-tab-drag-and-drop/src/main/res/values/integers.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <!-- How many microseconds should be waited before switching to another tab when dragging cards -->
+ <integer name="drag_n_drop_ms_to_react">500</integer>
+ <!-- How many dp from border should one be away before switching to another tab when dragging cards -->
+ <integer name="drag_n_drop_dp_to_react">30</integer>
+ <!-- How many dp from top / bottom border should one be away before scrolling top / down when dragging cards -->
+ <integer name="drag_n_drop_dp_to_react_top_bottom">100</integer>
+</resources> \ No newline at end of file
diff --git a/cross-tab-drag-and-drop/src/test/java/it/niedermann/android/crosstabdnd/ExampleUnitTest.java b/cross-tab-drag-and-drop/src/test/java/it/niedermann/android/crosstabdnd/ExampleUnitTest.java
new file mode 100644
index 000000000..b07c4752a
--- /dev/null
+++ b/cross-tab-drag-and-drop/src/test/java/it/niedermann/android/crosstabdnd/ExampleUnitTest.java
@@ -0,0 +1,17 @@
+package it.niedermann.android.crosstabdnd;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Example local unit test, which will execute on the development machine (host).
+ *
+ * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
+ */
+public class ExampleUnitTest {
+ @Test
+ public void addition_isCorrect() {
+ assertEquals(4, 2 + 2);
+ }
+} \ No newline at end of file