diff options
author | Stefan Niedermann <info@niedermann.it> | 2020-04-08 12:42:37 +0300 |
---|---|---|
committer | Stefan Niedermann <info@niedermann.it> | 2020-04-08 12:42:37 +0300 |
commit | 35f20c292bb4f1f3c7b9aadc8427fd86358a64e9 (patch) | |
tree | a72f3cd5791fb144ab295149e8e12e7edf377618 /cross-tab-drag-and-drop | |
parent | aae206befa986e11f29b61d8761b1cf7b6599fa3 (diff) |
Move TabLayoutHelper to own library
Signed-off-by: Stefan Niedermann <info@niedermann.it>
Diffstat (limited to 'cross-tab-drag-and-drop')
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 |