diff --git a/README.md b/README.md
index ef88416..5770b89 100644
--- a/README.md
+++ b/README.md
@@ -3,17 +3,11 @@
This library allows you to implement Google Photos style multi-selection in your apps! You start
by long pressing an item in your list, then you drag your finger without letting go to select more.
-![Art](https://github.com/afollestad/drag-select-recyclerview/raw/master/art/showcase2.gif)
+![Art](https://github.com/afollestad/drag-select-recyclerview/raw/master/art/showcase3.gif)
# Sample
-You can [download a sample APK](https://github.com/afollestad/drag-select-recyclerview/raw/master/sample/sample.apk) or
-[view it on Google Play](https://play.google.com/store/apps/details?id=com.afollestad.dragselectrecyclerviewsample)!
-
-
-
-
+You can [download a sample APK](https://github.com/afollestad/drag-select-recyclerview/raw/master/sample/sample.apk).
---
@@ -33,161 +27,90 @@ Add the following to your module's `build.gradle` file:
```Gradle
dependencies {
// ... other dependencies
- implementation 'com.afollestad:drag-select-recyclerview:1.0.0'
+ implementation 'com.afollestad:drag-select-recyclerview:2.0.0'
}
```
---
-# Tutorial
-
-1. [Introduction](https://github.com/afollestad/drag-select-recyclerview#introduction)
-2. [DragSelectRecyclerView](https://github.com/afollestad/drag-select-recyclerview#dragselectrecyclerview)
- 1. How to create a DragSelectRecyclerView in your layout. How to set it up from code.
-3. [Adapter Implementation](https://github.com/afollestad/drag-select-recyclerview#adapter-implementation)
- 1. An example of how adapter's should be setup.
-4. [User Activation, Initializing Drag Selection](https://github.com/afollestad/drag-select-recyclerview#user-activation-initializing-drag-selection)
- 1. How drag selection mode is activated by a long press. How to maintain selected items through configuration changes, etc.
-6. [Auto Scroll](https://github.com/afollestad/drag-select-recyclerview#auto-scroll)
- 1. By default, this library will auto-scroll up or down if the user holds their finger at the top or bottom of the list during selection mode.
-
----
-
# Introduction
-`DragSelectRecyclerView` is the main class of this library.
+`DragSelectTouchListener` is the main class of this library.
This library will handle drag interception and auto scroll logic - if you drag to the top of the RecyclerView,
the list will scroll up, and vice versa.
---
-# DragSelectRecyclerView
+# DragSelectTouchListener
-`DragSelectRecyclerView` replaces the regular `RecyclerView` in your layouts. It intercepts touch events
-when you tell if selection mode is active, and automatically reports to your adapter.
+### Basics
-```xml
-
-```
+`DragSelectTouchListener` attaches to your RecyclerViews. It intercepts touch events
+when it's active, and reports to a receiver which handles updating UI
-Setup is basically the same as it would be for a regular `RecyclerView`. You just set a `LayoutManager`
-and `RecyclerView.Adapter` to it:
-
-```Java
-DragSelectRecyclerView list = (DragSelectRecyclerView) findViewById(R.id.list);
-list.setLayoutManager(new GridLayoutManager(this, 3));
-list.setAdapter(adapter);
+```kotlin
+val receiver: DragSelectReceiver = // ...
+val touchListener = DragSelectTouchListener.create(context, receiver)
```
----
-
-# Adapter Implementation
+### Configuration
-You use regular `RecyclerView.Adapter`'s with the `DragSelectRecyclerView`. However, it **has to
-implement the `IDragSelectAdapter` interface**:
+There are a few things that you can configure, mainly around auto scroll.
-```java
-public class MainAdapter extends RecyclerView.Adapter
- implements IDragSelectAdapter {
+```kotlin
+DragSelectTouchListener.create(context, adapter) {
+ // Configure the auto-scroll hotspot
+ hotspotHeight = resources.getDimensionPixelSize(R.dimen.default_56dp)
+ hotspotOffsetTop = resources.getDimensionPixelSize(R.dimen.default_zero)
+ hotspotOffsetBottom = resources.getDimensionPixelSize(R.dimen.default_zero)
- @Override
- public void setSelected(int index, boolean selected) {
- // 1. Make this index as selected in your implementation.
- // 2. Tell the RecyclerView.Adapter to render this item's changes.
- notifyItemChanged(index);
- }
-
- @Override
- public boolean isIndexSelectable(int index) {
- // Return false if you don't want this position to be selectable.
- // Useful for items like section headers.
- return true;
- }
-
- // The rest of your regular adapter overrides
+ // Or instead of the above...
+ disableAutoScroll()
}
```
-**Checkout the sample project for an in-depth example.**
+The auto-scroll hotspot is a invisible section at the top and bottom of your
+RecyclerView, when your finger is in one of those sections, auto scroll is
+triggered and the list will move up or down until you lift your finger.
---
-# User Activation, Initializing Drag Selection
-
-The library won't start selection mode unless you tell it to. You want the user to be able to active it.
-The click listener implementation setup in the adapter above will help with this.
-
-```java
-public class MainActivity extends AppCompatActivity implements
- MainAdapter.ClickListener, DragSelectRecyclerViewAdapter.SelectionListener {
+# Interaction
- private DragSelectRecyclerView listView;
- private MainAdapter adapter;
+A receiver looks like this:
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
+```kotlin
+class MyReceiver : DragSelectReceiver {
- // Setup adapter and callbacks
- adapter = new MainAdapter(this);
-
- // Setup the recycler view
- listView = (DragSelectRecyclerView) findViewById(R.id.list);
- listView.setLayoutManager(
- new GridLayoutManager(this,
- getResources().getInteger(R.integer.grid_width)));
- listView.setAdapter(adapter);
+ override fun setSelected(index: Int, selected: Boolean) {
+ // do something to mark this index as selected/unselected
}
-
- /**
- * See the adapter in the sample project for a click listener implementation. Click listeners
- * aren't provided by this library.
- */
- @Override
- public void onClick(int index) {
- // Single click will select or deselect an item
- adapter.toggleSelected(index);
+
+ override fun isIndexSelectable(index: Int): Boolean {
+ // if you return false, this index can't be used with setIsActive()
+ return true
}
- /**
- * See the adapter in the sample project for a click listener implementation. Click listeners
- * aren't provided by this library.
- */
- @Override
- public void onLongClick(int index) {
- // Initialize drag selection -- also selects the initial item
- listView.setDragSelectActive(true, index);
+ override fun getItemCount(): Int {
+ // return size of your data set
+ return 0
}
}
```
----
-
-# Auto Scroll
+In the sample project, our adapter is also our receiver.
-By default, this library will auto scroll. During drag selection, moving your finger to the top
-of the list will scroll up. Moving your finger to the bottom of the list will scroll down.
+To start drag selection, you use `setIsActive`, which should be triggered
+from user input such as a long press on a list item.
-At the start of the activation point at the top or bottom, the list will scroll slowly. The further
-you move into the activation area, the faster it will scroll.
+```kotlin
+val recyclerView: RecyclerView = // ...
+val receiver: DragSelectReceiver = // ...
-You can disable auto scroll, or change the activation hotspot from your layout XML:
-
-```xml
-
-```
+val touchListener = DragSelectTouchListener.create(context, receiver)
+recyclerView.addOnItemTouchListener(touchListener) // important!!
-56dp is the default hotspot height, you can raise or lower it if necessary. Smaller hotspots will
-scroll quickly since there's not much room for velocity change.
+// true for active = true, 0 is the initial selected index
+touchListener.setIsActive(true, 0)
+````
diff --git a/art/showcase2.gif b/art/showcase2.gif
deleted file mode 100644
index c1db702..0000000
Binary files a/art/showcase2.gif and /dev/null differ
diff --git a/art/showcase3.gif b/art/showcase3.gif
new file mode 100644
index 0000000..a2a328e
Binary files /dev/null and b/art/showcase3.gif differ
diff --git a/dependencies.gradle b/dependencies.gradle
index 8192bd0..eac059b 100644
--- a/dependencies.gradle
+++ b/dependencies.gradle
@@ -2,8 +2,8 @@ ext.versions = [
minSdk : 14,
compileSdk : 28,
buildTools : '28.0.1',
- publishVersion : '1.0.0',
- publishVersionCode: 21,
+ publishVersion : '2.0.0',
+ publishVersionCode: 22,
gradlePlugin : '3.1.3',
kotlin : '1.2.51',
@@ -12,5 +12,5 @@ ext.versions = [
bintrayRelease : '0.8.1',
supportLib : '28.0.+',
- materialCab : '0.1.12'
+ materialCab : '1.0.0'
]
\ No newline at end of file
diff --git a/library/build.gradle b/library/build.gradle
index 5d8b6a8..5735382 100644
--- a/library/build.gradle
+++ b/library/build.gradle
@@ -33,7 +33,8 @@ dependencies {
google()
}
- implementation 'com.android.support:recyclerview-v7:' + versions.supportLib
+ api 'com.android.support:recyclerview-v7:' + versions.supportLib
+ implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + versions.kotlin
}
publish {
diff --git a/library/src/main/java/com/afollestad/dragselectrecyclerview/DragSelectReceiver.kt b/library/src/main/java/com/afollestad/dragselectrecyclerview/DragSelectReceiver.kt
new file mode 100644
index 0000000..aa3d9d9
--- /dev/null
+++ b/library/src/main/java/com/afollestad/dragselectrecyclerview/DragSelectReceiver.kt
@@ -0,0 +1,14 @@
+package com.afollestad.dragselectrecyclerview
+
+/** @author Aidan Follestad (afollestad) */
+interface DragSelectReceiver {
+
+ fun getItemCount(): Int
+
+ fun setSelected(
+ index: Int,
+ selected: Boolean
+ )
+
+ fun isIndexSelectable(index: Int): Boolean
+}
diff --git a/library/src/main/java/com/afollestad/dragselectrecyclerview/DragSelectRecyclerView.java b/library/src/main/java/com/afollestad/dragselectrecyclerview/DragSelectRecyclerView.java
deleted file mode 100644
index ab7a35c..0000000
--- a/library/src/main/java/com/afollestad/dragselectrecyclerview/DragSelectRecyclerView.java
+++ /dev/null
@@ -1,372 +0,0 @@
-package com.afollestad.dragselectrecyclerview;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.os.Handler;
-import android.support.annotation.Nullable;
-import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.MotionEvent;
-import android.view.View;
-
-/** @author Aidan Follestad (afollestad) */
-@SuppressWarnings("unused")
-public class DragSelectRecyclerView extends RecyclerView {
-
- @SuppressWarnings("WeakerAccess")
- public interface FingerListener {
- void onDragSelectFingerAction(boolean fingerDown);
- }
-
- private static final int AUTO_SCROLL_DELAY = 25;
-
- public DragSelectRecyclerView(Context context) {
- super(context);
- init(context, null);
- }
-
- public DragSelectRecyclerView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(context, attrs);
- }
-
- public DragSelectRecyclerView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(context, attrs);
- }
-
- private void LOG(String message, Object... args) {
- if (!debugEnabled) {
- return;
- }
- if (args != null) {
- message = String.format(message, args);
- }
- if (message.equals(lastDebugMsg)) {
- return;
- }
- lastDebugMsg = message;
- Log.d("DragSelectRecyclerView", message);
- }
-
- private int lastDraggedIndex = -1;
- private IDragSelectAdapter adapter;
- private int initialSelection;
- private boolean dragSelectActive;
- private int minReached;
- private int maxReached;
-
- private int hotspotHeight;
- private int hotspotOffsetTop;
- private int hotspotOffsetBottom;
-
- private int hotspotTopBoundStart;
- private int hotspotTopBoundEnd;
- private int hotspotBottomBoundStart;
- private int hotspotBottomBoundEnd;
- private int autoScrollVelocity;
-
- private FingerListener fingerListener;
-
- private void init(Context context, AttributeSet attrs) {
- autoScrollHandler = new Handler();
- final int defaultHotspotHeight =
- context.getResources().getDimensionPixelSize(R.dimen.dsrv_defaultHotspotHeight);
-
- if (attrs != null) {
- TypedArray a =
- context
- .getTheme()
- .obtainStyledAttributes(attrs, R.styleable.DragSelectRecyclerView, 0, 0);
- try {
- boolean autoScrollEnabled =
- a.getBoolean(R.styleable.DragSelectRecyclerView_dsrv_autoScrollEnabled, true);
- if (!autoScrollEnabled) {
- hotspotHeight = -1;
- hotspotOffsetTop = -1;
- hotspotOffsetBottom = -1;
- LOG("Auto-scroll disabled");
- } else {
- hotspotHeight =
- a.getDimensionPixelSize(
- R.styleable.DragSelectRecyclerView_dsrv_autoScrollHotspotHeight,
- defaultHotspotHeight);
- hotspotOffsetTop =
- a.getDimensionPixelSize(
- R.styleable.DragSelectRecyclerView_dsrv_autoScrollHotspot_offsetTop, 0);
- hotspotOffsetBottom =
- a.getDimensionPixelSize(
- R.styleable.DragSelectRecyclerView_dsrv_autoScrollHotspot_offsetBottom, 0);
- LOG("Hotspot height = %d", hotspotHeight);
- }
- } finally {
- a.recycle();
- }
- } else {
- hotspotHeight = defaultHotspotHeight;
- LOG("Hotspot height = %d", hotspotHeight);
- }
- }
-
- public void setFingerListener(@Nullable FingerListener listener) {
- this.fingerListener = listener;
- }
-
- @Override
- protected void onMeasure(int widthSpec, int heightSpec) {
- super.onMeasure(widthSpec, heightSpec);
- if (hotspotHeight > -1) {
- hotspotTopBoundStart = hotspotOffsetTop;
- hotspotTopBoundEnd = hotspotOffsetTop + hotspotHeight;
- hotspotBottomBoundStart = (getMeasuredHeight() - hotspotHeight) - hotspotOffsetBottom;
- hotspotBottomBoundEnd = getMeasuredHeight() - hotspotOffsetBottom;
- LOG("RecyclerView height = %d", getMeasuredHeight());
- LOG("Hotspot top bound = %d to %d", hotspotTopBoundStart, hotspotTopBoundStart);
- LOG("Hotspot bottom bound = %d to %d", hotspotBottomBoundStart, hotspotBottomBoundEnd);
- }
- }
-
- public boolean setDragSelectActive(boolean active, int initialSelection) {
- if (active && dragSelectActive) {
- LOG("Drag selection is already active.");
- return false;
- }
- lastDraggedIndex = -1;
- minReached = -1;
- maxReached = -1;
- if (!adapter.isIndexSelectable(initialSelection)) {
- dragSelectActive = false;
- this.initialSelection = -1;
- lastDraggedIndex = -1;
- LOG("Index %d is not selectable.", initialSelection);
- return false;
- }
- adapter.setSelected(initialSelection, true);
- dragSelectActive = active;
- this.initialSelection = initialSelection;
- lastDraggedIndex = initialSelection;
- if (fingerListener != null) {
- fingerListener.onDragSelectFingerAction(true);
- }
- LOG("Drag selection initialized, starting at index %d.", initialSelection);
- return true;
- }
-
- @Override
- public void setAdapter(Adapter adapter) {
- if (!(adapter instanceof IDragSelectAdapter)) {
- throw new IllegalArgumentException("Adapter must be implement IDragSelectAdapter.");
- }
- this.adapter = (IDragSelectAdapter) adapter;
- super.setAdapter(adapter);
- }
-
- private boolean inTopHotspot;
- private boolean inBottomHotspot;
-
- private Handler autoScrollHandler;
- private Runnable autoScrollRunnable =
- new Runnable() {
- @Override
- public void run() {
- if (autoScrollHandler == null) {
- return;
- }
- if (inTopHotspot) {
- scrollBy(0, -autoScrollVelocity);
- autoScrollHandler.postDelayed(this, AUTO_SCROLL_DELAY);
- } else if (inBottomHotspot) {
- scrollBy(0, autoScrollVelocity);
- autoScrollHandler.postDelayed(this, AUTO_SCROLL_DELAY);
- }
- }
- };
-
- private int getItemPosition(MotionEvent e) {
- final View v = findChildViewUnder(e.getX(), e.getY());
- if (v == null) {
- return NO_POSITION;
- }
- return getChildAdapterPosition(v);
- }
-
- private RectF topBoundRect;
- private RectF bottomBoundRect;
- private Paint debugPaint;
- private boolean debugEnabled = false;
- private String lastDebugMsg;
-
- public final void enableDebug() {
- debugEnabled = true;
- invalidate();
- }
-
- @Override
- public void onDraw(Canvas c) {
- super.onDraw(c);
-
- if (debugEnabled) {
- if (debugPaint == null) {
- debugPaint = new Paint();
- debugPaint.setColor(Color.BLACK);
- debugPaint.setAntiAlias(true);
- debugPaint.setStyle(Paint.Style.FILL);
- topBoundRect = new RectF(0, hotspotTopBoundStart, getMeasuredWidth(), hotspotTopBoundEnd);
- bottomBoundRect =
- new RectF(0, hotspotBottomBoundStart, getMeasuredWidth(), hotspotBottomBoundEnd);
- }
- c.drawRect(topBoundRect, debugPaint);
- c.drawRect(bottomBoundRect, debugPaint);
- }
- }
-
- private void selectRange(int from, int to, int min, int max) {
- if (from == to) {
- // Finger is back on the initial item, unselect everything else
- for (int i = min; i <= max; i++) {
- if (i == from) {
- continue;
- }
- adapter.setSelected(i, false);
- }
- return;
- }
-
- if (to < from) {
- // When selecting from one to previous items
- for (int i = to; i <= from; i++) {
- adapter.setSelected(i, true);
- }
- if (min > -1 && min < to) {
- // Unselect items that were selected during this drag but no longer are
- for (int i = min; i < to; i++) {
- if (i == from) {
- continue;
- }
- adapter.setSelected(i, false);
- }
- }
- if (max > -1) {
- for (int i = from + 1; i <= max; i++) {
- adapter.setSelected(i, false);
- }
- }
- } else {
- // When selecting from one to next items
- for (int i = from; i <= to; i++) {
- adapter.setSelected(i, true);
- }
- if (max > -1 && max > to) {
- // Unselect items that were selected during this drag but no longer are
- for (int i = to + 1; i <= max; i++) {
- if (i == from) {
- continue;
- }
- adapter.setSelected(i, false);
- }
- }
- if (min > -1) {
- for (int i = min; i < from; i++) {
- adapter.setSelected(i, false);
- }
- }
- }
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent e) {
- if (adapter == null) {
- LOG("No IDragSelectAdapter has been set.");
- return super.dispatchTouchEvent(e);
- }
- if (adapter.getItemCount() == 0) {
- LOG("Adapter reported 0 item count.");
- return super.dispatchTouchEvent(e);
- }
- if (dragSelectActive) {
- LOG("Drag selection is active");
- final int itemPosition = getItemPosition(e);
- if (e.getAction() == MotionEvent.ACTION_UP) {
- dragSelectActive = false;
- inTopHotspot = false;
- inBottomHotspot = false;
- autoScrollHandler.removeCallbacks(autoScrollRunnable);
- if (fingerListener != null) {
- fingerListener.onDragSelectFingerAction(false);
- }
- return true;
- } else if (e.getAction() == MotionEvent.ACTION_MOVE) {
- // Check for auto-scroll hotspot
- if (hotspotHeight > -1) {
- if (e.getY() >= hotspotTopBoundStart && e.getY() <= hotspotTopBoundEnd) {
- inBottomHotspot = false;
- if (!inTopHotspot) {
- inTopHotspot = true;
- LOG("Now in TOP hotspot");
- autoScrollHandler.removeCallbacks(autoScrollRunnable);
- autoScrollHandler.postDelayed(autoScrollRunnable, AUTO_SCROLL_DELAY);
- }
-
- final float simulatedFactor = hotspotTopBoundEnd - hotspotTopBoundStart;
- final float simulatedY = e.getY() - hotspotTopBoundStart;
- autoScrollVelocity = (int) (simulatedFactor - simulatedY) / 2;
-
- LOG("Auto scroll velocity = %d", autoScrollVelocity);
- } else if (e.getY() >= hotspotBottomBoundStart && e.getY() <= hotspotBottomBoundEnd) {
- inTopHotspot = false;
- if (!inBottomHotspot) {
- inBottomHotspot = true;
- LOG("Now in BOTTOM hotspot");
- autoScrollHandler.removeCallbacks(autoScrollRunnable);
- autoScrollHandler.postDelayed(autoScrollRunnable, AUTO_SCROLL_DELAY);
- }
-
- final float simulatedY = e.getY() + hotspotBottomBoundEnd;
- final float simulatedFactor = hotspotBottomBoundStart + hotspotBottomBoundEnd;
- autoScrollVelocity = (int) (simulatedY - simulatedFactor) / 2;
-
- LOG("Auto scroll velocity = %d", autoScrollVelocity);
- } else if (inTopHotspot || inBottomHotspot) {
- LOG("Left the hotspot");
- autoScrollHandler.removeCallbacks(autoScrollRunnable);
- inTopHotspot = false;
- inBottomHotspot = false;
- }
- }
-
- // Drag selection logic
- if (itemPosition != NO_POSITION && lastDraggedIndex != itemPosition) {
- lastDraggedIndex = itemPosition;
- if (minReached == -1) {
- minReached = lastDraggedIndex;
- }
- if (maxReached == -1) {
- maxReached = lastDraggedIndex;
- }
- if (lastDraggedIndex > maxReached) {
- maxReached = lastDraggedIndex;
- }
- if (lastDraggedIndex < minReached) {
- minReached = lastDraggedIndex;
- }
- if (adapter != null) {
- selectRange(initialSelection, lastDraggedIndex, minReached, maxReached);
- }
- if (initialSelection == lastDraggedIndex) {
- minReached = lastDraggedIndex;
- maxReached = lastDraggedIndex;
- }
- }
- return true;
- }
- } else {
- LOG("Drag selection is not active.");
- }
- return super.dispatchTouchEvent(e);
- }
-}
diff --git a/library/src/main/java/com/afollestad/dragselectrecyclerview/DragSelectTouchListener.kt b/library/src/main/java/com/afollestad/dragselectrecyclerview/DragSelectTouchListener.kt
new file mode 100644
index 0000000..ed7e7cd
--- /dev/null
+++ b/library/src/main/java/com/afollestad/dragselectrecyclerview/DragSelectTouchListener.kt
@@ -0,0 +1,272 @@
+@file:Suppress("MemberVisibilityCanBePrivate")
+
+package com.afollestad.dragselectrecyclerview
+
+import android.content.Context
+import android.os.Handler
+import android.support.annotation.RestrictTo
+import android.support.annotation.RestrictTo.Scope
+import android.support.v7.widget.RecyclerView
+import android.support.v7.widget.RecyclerView.NO_POSITION
+import android.util.Log
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
+
+/** @author Aidan Follestad (afollestad) */
+class DragSelectTouchListener private constructor(
+ context: Context,
+ private val receiver: DragSelectReceiver
+) : RecyclerView.OnItemTouchListener {
+
+ private val autoScrollHandler = Handler()
+ private val autoScrollRunnable = object : Runnable {
+ override fun run() {
+ if (inTopHotspot) {
+ recyclerView?.scrollBy(0, -autoScrollVelocity)
+ autoScrollHandler.postDelayed(this, AUTO_SCROLL_DELAY.toLong())
+ } else if (inBottomHotspot) {
+ recyclerView?.scrollBy(0, autoScrollVelocity)
+ autoScrollHandler.postDelayed(this, AUTO_SCROLL_DELAY.toLong())
+ }
+ }
+ }
+
+ var hotspotHeight: Int = context.dimen(R.dimen.dsrv_defaultHotspotHeight)
+ var hotspotOffsetTop: Int = 0
+ var hotspotOffsetBottom: Int = 0
+
+ fun disableAutoScroll() {
+ hotspotHeight = -1
+ hotspotOffsetTop = -1
+ hotspotOffsetBottom = -1
+ }
+
+ private var recyclerView: RecyclerView? = null
+
+ private var lastDraggedIndex = -1
+ private var initialSelection: Int = 0
+ private var dragSelectActive: Boolean = false
+ private var minReached: Int = 0
+ private var maxReached: Int = 0
+
+ private var hotspotTopBoundStart: Int = 0
+ private var hotspotTopBoundEnd: Int = 0
+ private var hotspotBottomBoundStart: Int = 0
+ private var hotspotBottomBoundEnd: Int = 0
+ private var inTopHotspot: Boolean = false
+ private var inBottomHotspot: Boolean = false
+
+ private var autoScrollVelocity: Int = 0
+
+ companion object {
+
+ private const val AUTO_SCROLL_DELAY = 25
+ private const val DEBUG_MODE = false
+
+ @Suppress("ConstantConditionIf")
+ private fun log(msg: String) {
+ if (!DEBUG_MODE) return
+ Log.d("DragSelectTL", msg)
+ }
+
+ fun create(
+ context: Context,
+ receiver: DragSelectReceiver,
+ config: (DragSelectTouchListener.() -> Unit)? = null
+ ): DragSelectTouchListener {
+ val listener = DragSelectTouchListener(context, receiver)
+ if (config != null) {
+ listener.config()
+ }
+ return listener
+ }
+ }
+
+ fun setIsActive(
+ active: Boolean,
+ initialSelection: Int
+ ): Boolean {
+ if (active && dragSelectActive) {
+ log("Drag selection is already active.")
+ return false
+ }
+ lastDraggedIndex = -1
+ minReached = -1
+ maxReached = -1
+ if (!receiver.isIndexSelectable(initialSelection)) {
+ dragSelectActive = false
+ this.initialSelection = -1
+ lastDraggedIndex = -1
+ log("Index $initialSelection is not selectable.")
+ return false
+ }
+ receiver.setSelected(initialSelection, true)
+ dragSelectActive = active
+ this.initialSelection = initialSelection
+ lastDraggedIndex = initialSelection
+ log("Drag selection initialized, starting at index $initialSelection.")
+ return true
+ }
+
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ override fun onInterceptTouchEvent(
+ view: RecyclerView,
+ event: MotionEvent
+ ): Boolean {
+ val adapterIsEmpty = view.adapter?.isEmpty() ?: true
+ val result = dragSelectActive && !adapterIsEmpty
+
+ if (result) {
+ recyclerView = view
+ log("RecyclerView height = ${view.measuredHeight}")
+
+ if (hotspotHeight > -1) {
+ hotspotTopBoundStart = hotspotOffsetTop
+ hotspotTopBoundEnd = hotspotOffsetTop + hotspotHeight
+ hotspotBottomBoundStart = view.measuredHeight - hotspotHeight - hotspotOffsetBottom
+ hotspotBottomBoundEnd = view.measuredHeight - hotspotOffsetBottom
+ log("Hotspot top bound = $hotspotTopBoundStart to $hotspotTopBoundEnd")
+ log("Hotspot bottom bound = $hotspotBottomBoundStart to $hotspotBottomBoundEnd")
+ }
+ }
+
+ return result
+ }
+
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ override fun onTouchEvent(
+ view: RecyclerView,
+ event: MotionEvent
+ ) {
+ val action = event.action
+ val itemPosition = view.getItemPosition(event)
+ val y = event.y
+
+ when (action) {
+ ACTION_UP -> {
+ dragSelectActive = false
+ inTopHotspot = false
+ inBottomHotspot = false
+ autoScrollHandler.removeCallbacks(autoScrollRunnable)
+ return
+ }
+ ACTION_MOVE -> {
+ if (hotspotHeight > -1) {
+ // Check for auto-scroll hotspot
+ if (y >= hotspotTopBoundStart && y <= hotspotTopBoundEnd) {
+ inBottomHotspot = false
+ if (!inTopHotspot) {
+ inTopHotspot = true
+ log("Now in TOP hotspot")
+ autoScrollHandler.removeCallbacks(autoScrollRunnable)
+ autoScrollHandler.postDelayed(autoScrollRunnable, AUTO_SCROLL_DELAY.toLong())
+ }
+ val simulatedFactor = (hotspotTopBoundEnd - hotspotTopBoundStart).toFloat()
+ val simulatedY = y - hotspotTopBoundStart
+ autoScrollVelocity = (simulatedFactor - simulatedY).toInt() / 2
+ log("Auto scroll velocity = $autoScrollVelocity")
+ } else if (y >= hotspotBottomBoundStart && y <= hotspotBottomBoundEnd) {
+ inTopHotspot = false
+ if (!inBottomHotspot) {
+ inBottomHotspot = true
+ log("Now in BOTTOM hotspot")
+ autoScrollHandler.removeCallbacks(autoScrollRunnable)
+ autoScrollHandler.postDelayed(autoScrollRunnable, AUTO_SCROLL_DELAY.toLong())
+ }
+ val simulatedY = y + hotspotBottomBoundEnd
+ val simulatedFactor = (hotspotBottomBoundStart + hotspotBottomBoundEnd).toFloat()
+ autoScrollVelocity = (simulatedY - simulatedFactor).toInt() / 2
+ log("Auto scroll velocity = $autoScrollVelocity")
+ } else if (inTopHotspot || inBottomHotspot) {
+ log("Left the hotspot")
+ autoScrollHandler.removeCallbacks(autoScrollRunnable)
+ inTopHotspot = false
+ inBottomHotspot = false
+ }
+ }
+
+ // Drag selection logic
+ if (itemPosition != NO_POSITION && lastDraggedIndex != itemPosition) {
+ lastDraggedIndex = itemPosition
+ if (minReached == -1) minReached = lastDraggedIndex
+ if (maxReached == -1) maxReached = lastDraggedIndex
+ if (lastDraggedIndex > maxReached) maxReached = lastDraggedIndex
+ if (lastDraggedIndex < minReached) minReached = lastDraggedIndex
+ selectRange(initialSelection, lastDraggedIndex, minReached, maxReached)
+ if (initialSelection == lastDraggedIndex) {
+ minReached = lastDraggedIndex
+ maxReached = lastDraggedIndex
+ }
+ }
+
+ return
+ }
+ }
+ }
+
+ @RestrictTo(Scope.LIBRARY_GROUP)
+ override fun onRequestDisallowInterceptTouchEvent(disallow: Boolean) {
+ // no-op
+ }
+
+ private fun selectRange(
+ from: Int,
+ to: Int,
+ min: Int,
+ max: Int
+ ) {
+ with(receiver) {
+ if (from == to) {
+ // Finger is back on the initial item, unselect everything else
+ for (i in min..max) {
+ if (i == from) {
+ continue
+ }
+ setSelected(i, false)
+ }
+ return
+ }
+
+ if (to < from) {
+ // When selecting from one to previous items
+ for (i in to..from) {
+ setSelected(i, true)
+ }
+ if (min > -1 && min < to) {
+ // Unselect items that were selected during this drag but no longer are
+ for (i in min until to) {
+ if (i == from) {
+ continue
+ }
+ setSelected(i, false)
+ }
+ }
+ if (max > -1) {
+ for (i in from + 1..max) {
+ setSelected(i, false)
+ }
+ }
+ } else {
+ // When selecting from one to next items
+ for (i in from..to) {
+ setSelected(i, true)
+ }
+ if (max > -1 && max > to) {
+ // Unselect items that were selected during this drag but no longer are
+ for (i in to + 1..max) {
+ if (i == from) {
+ continue
+ }
+ setSelected(i, false)
+ }
+ }
+ if (min > -1) {
+ for (i in min until from) {
+ setSelected(i, false)
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/library/src/main/java/com/afollestad/dragselectrecyclerview/Extensions.kt b/library/src/main/java/com/afollestad/dragselectrecyclerview/Extensions.kt
new file mode 100644
index 0000000..6d2ddd9
--- /dev/null
+++ b/library/src/main/java/com/afollestad/dragselectrecyclerview/Extensions.kt
@@ -0,0 +1,22 @@
+package com.afollestad.dragselectrecyclerview
+
+import android.content.Context
+import android.support.annotation.DimenRes
+import android.support.annotation.Px
+import android.support.v7.widget.RecyclerView
+import android.view.MotionEvent
+
+@Px internal fun Context.dimen(@DimenRes res: Int): Int {
+ return resources.getDimensionPixelSize(res)
+}
+
+typealias RecyclerViewAdapter = RecyclerView.Adapter
+
+internal fun RecyclerViewAdapter<*>.isEmpty(): Boolean {
+ return itemCount == 0
+}
+
+internal fun RecyclerView.getItemPosition(e: MotionEvent): Int {
+ val v = findChildViewUnder(e.x, e.y) ?: return RecyclerView.NO_POSITION
+ return getChildAdapterPosition(v)
+}
\ No newline at end of file
diff --git a/library/src/main/java/com/afollestad/dragselectrecyclerview/IDragSelectAdapter.java b/library/src/main/java/com/afollestad/dragselectrecyclerview/IDragSelectAdapter.java
deleted file mode 100644
index 07598b4..0000000
--- a/library/src/main/java/com/afollestad/dragselectrecyclerview/IDragSelectAdapter.java
+++ /dev/null
@@ -1,12 +0,0 @@
-package com.afollestad.dragselectrecyclerview;
-
-/** @author Aidan Follestad (afollestad) */
-@SuppressWarnings("WeakerAccess")
-public interface IDragSelectAdapter {
-
- void setSelected(int index, boolean selected);
-
- boolean isIndexSelectable(int index);
-
- int getItemCount();
-}
diff --git a/library/src/main/res/values/attrs.xml b/library/src/main/res/values/attrs.xml
deleted file mode 100644
index aa8f3f4..0000000
--- a/library/src/main/res/values/attrs.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/sample/build.gradle b/sample/build.gradle
index 9b9b134..15c72ef 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -38,6 +38,7 @@ dependencies {
implementation 'com.android.support:appcompat-v7:' + versions.supportLib
implementation 'com.afollestad:material-cab:' + versions.materialCab
+ implementation 'org.jetbrains.kotlin:kotlin-stdlib-jdk7:' + versions.kotlin
}
repositories {
diff --git a/sample/sample.apk b/sample/sample.apk
index d48c990..1c0e6f6 100644
Binary files a/sample/sample.apk and b/sample/sample.apk differ
diff --git a/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/Extensions.kt b/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/Extensions.kt
new file mode 100644
index 0000000..6ea433d
--- /dev/null
+++ b/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/Extensions.kt
@@ -0,0 +1,32 @@
+package com.afollestad.dragselectrecyclerviewsample
+
+import android.app.Activity
+import android.content.Context
+import android.os.Build
+import android.support.annotation.ColorInt
+import android.support.annotation.ColorRes
+import android.support.annotation.DimenRes
+import android.support.annotation.IntegerRes
+import android.support.annotation.Px
+import android.support.v4.content.ContextCompat
+import android.view.View
+
+@Px internal fun Context.dimen(@DimenRes res: Int): Int {
+ return resources.getDimensionPixelSize(res)
+}
+
+@ColorInt internal fun Context.color(@ColorRes res: Int): Int {
+ return ContextCompat.getColor(this, res)
+}
+
+internal fun Context.integer(@IntegerRes res: Int): Int {
+ return resources.getInteger(res)
+}
+
+internal fun Activity.setLightNavBarCompat() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ var flags = window.decorView.systemUiVisibility
+ flags = flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR
+ window.decorView.systemUiVisibility = flags
+ }
+}
\ No newline at end of file
diff --git a/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/MainActivity.java b/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/MainActivity.java
deleted file mode 100644
index 3a5964b..0000000
--- a/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/MainActivity.java
+++ /dev/null
@@ -1,129 +0,0 @@
-package com.afollestad.dragselectrecyclerviewsample;
-
-import android.annotation.SuppressLint;
-import android.graphics.Color;
-import android.os.Build;
-import android.os.Bundle;
-import android.support.v7.app.AppCompatActivity;
-import android.support.v7.widget.GridLayoutManager;
-import android.support.v7.widget.Toolbar;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.View;
-import android.widget.Toast;
-import com.afollestad.dragselectrecyclerview.DragSelectRecyclerView;
-import com.afollestad.materialcab.MaterialCab;
-
-/** @author Aidan Follestad (afollestad) */
-public class MainActivity extends AppCompatActivity
- implements MainAdapter.Listener, MaterialCab.Callback {
-
- private DragSelectRecyclerView listView;
- private MainAdapter adapter;
- private MaterialCab cab;
-
- @SuppressLint("InlinedApi")
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- setSupportActionBar((Toolbar) findViewById(R.id.main_toolbar));
-
- // Setup adapter and callbacks
- adapter = new MainAdapter(this);
-
- // Setup the RecyclerView
- listView = (DragSelectRecyclerView) findViewById(R.id.list);
- listView.setLayoutManager(
- new GridLayoutManager(this, getResources().getInteger(R.integer.grid_width)));
- listView.setAdapter(adapter);
-
- cab = MaterialCab.restoreState(savedInstanceState, this, this);
-
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
- int flags = getWindow().getDecorView().getSystemUiVisibility();
- flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
- getWindow().getDecorView().setSystemUiVisibility(flags);
- }
- }
-
- @Override
- protected void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- if (cab != null) {
- cab.saveState(outState);
- }
- }
-
- @Override
- public void onClick(int index) {
- adapter.toggleSelected(index);
- }
-
- @Override
- public void onLongClick(int index) {
- listView.setDragSelectActive(true, index);
- }
-
- @Override
- public void onSelectionChanged(int count) {
- if (count > 0) {
- if (cab == null) {
- cab =
- new MaterialCab(this, R.id.cab_stub)
- .setMenu(R.menu.cab)
- .setCloseDrawableRes(R.drawable.ic_close)
- .start(this);
- cab.getToolbar().setTitleTextColor(Color.BLACK);
- }
- cab.setTitleRes(R.string.cab_title_x, count);
- } else if (cab != null && cab.isActive()) {
- cab.reset().finish();
- cab = null;
- }
- }
-
- // Material CAB Callbacks
-
- @Override
- public boolean onCabCreated(MaterialCab cab, Menu menu) {
- return true;
- }
-
- @SuppressLint("DefaultLocale")
- @Override
- public boolean onCabItemClicked(MenuItem item) {
- if (item.getItemId() == R.id.done) {
- StringBuilder sb = new StringBuilder();
- int traverse = 0;
- for (Integer index : adapter.getSelectedIndices()) {
- if (traverse > 0) sb.append(", ");
- sb.append(adapter.getItem(index));
- traverse++;
- }
- Toast.makeText(
- this,
- String.format(
- "Selected letters (%d): %s", adapter.getSelectedIndices().size(), sb.toString()),
- Toast.LENGTH_LONG)
- .show();
- adapter.clearSelected();
- }
- return true;
- }
-
- @Override
- public void onBackPressed() {
- if (!adapter.getSelectedIndices().isEmpty()) {
- adapter.clearSelected();
- } else {
- super.onBackPressed();
- }
- }
-
- @Override
- public boolean onCabFinished(MaterialCab cab) {
- adapter.clearSelected();
- return true;
- }
-}
diff --git a/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/MainActivity.kt b/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/MainActivity.kt
new file mode 100644
index 0000000..0ece808
--- /dev/null
+++ b/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/MainActivity.kt
@@ -0,0 +1,93 @@
+package com.afollestad.dragselectrecyclerviewsample
+
+import android.annotation.SuppressLint
+import android.graphics.Color
+import android.os.Bundle
+import android.support.v7.app.AppCompatActivity
+import android.support.v7.widget.GridLayoutManager
+import android.widget.Toast
+import com.afollestad.dragselectrecyclerview.DragSelectTouchListener
+import com.afollestad.materialcab.MaterialCab
+import kotlinx.android.synthetic.main.activity_main.list
+
+/** @author Aidan Follestad (afollestad) */
+class MainActivity : AppCompatActivity(), MainAdapter.Listener {
+
+ private lateinit var adapter: MainAdapter
+ private lateinit var touchListener: DragSelectTouchListener
+
+ @SuppressLint("InlinedApi")
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_main)
+ setSupportActionBar(findViewById(R.id.main_toolbar))
+
+ adapter = MainAdapter(this)
+ touchListener = DragSelectTouchListener.create(this, adapter)
+
+ // Setup the RecyclerView
+ list.layoutManager = GridLayoutManager(this, integer(R.integer.grid_width))
+ list.adapter = adapter
+ list.addOnItemTouchListener(touchListener)
+
+ MaterialCab.tryRestore(this, savedInstanceState)
+ setLightNavBarCompat()
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ super.onSaveInstanceState(outState)
+ MaterialCab.saveState(outState)
+ }
+
+ override fun onClick(index: Int) {
+ adapter.toggleSelected(index)
+ }
+
+ override fun onLongClick(index: Int) {
+ touchListener.setIsActive(true, index)
+ }
+
+ override fun onSelectionChanged(count: Int) {
+ if (count > 0) {
+ MaterialCab.attach(this, R.id.cab_stub) {
+ menuRes = R.menu.cab
+ closeDrawableRes = R.drawable.ic_close
+ titleColor = Color.BLACK
+ title = getString(R.string.cab_title_x, count)
+
+ onSelection {
+ if (it.itemId == R.id.done) {
+ val sb = StringBuilder()
+ for ((traverse, index) in adapter.selectedIndices.withIndex()) {
+ if (traverse > 0) sb.append(", ")
+ sb.append(adapter[index])
+ }
+ Toast.makeText(
+ this@MainActivity,
+ "Selected letters (${adapter.selectedIndices.size}): $sb",
+ Toast.LENGTH_LONG
+ )
+ .show()
+ adapter.clearSelected()
+ true
+ } else {
+ false
+ }
+ }
+
+ onDestroy {
+ adapter.clearSelected()
+ true
+ }
+ }
+ } else {
+ MaterialCab.destroy()
+ }
+ }
+
+ override fun onBackPressed() {
+ if (!MaterialCab.destroy()) {
+ super.onBackPressed()
+ }
+ }
+}
diff --git a/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/MainAdapter.java b/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/MainAdapter.java
deleted file mode 100644
index d917bc0..0000000
--- a/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/MainAdapter.java
+++ /dev/null
@@ -1,186 +0,0 @@
-package com.afollestad.dragselectrecyclerviewsample;
-
-import android.content.Context;
-import android.graphics.Color;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.support.v4.content.ContextCompat;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.FrameLayout;
-import android.widget.TextView;
-import com.afollestad.dragselectrecyclerview.IDragSelectAdapter;
-import java.util.ArrayList;
-import java.util.List;
-
-/** @author Aidan Follestad (afollestad) */
-class MainAdapter extends RecyclerView.Adapter
- implements IDragSelectAdapter {
-
- private final List selectedIndices;
-
- private static final String[] ALPHABET =
- "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z".split(" ");
- private static final int[] COLORS =
- new int[] {
- Color.parseColor("#F44336"),
- Color.parseColor("#E91E63"),
- Color.parseColor("#9C27B0"),
- Color.parseColor("#673AB7"),
- Color.parseColor("#3F51B5"),
- Color.parseColor("#2196F3"),
- Color.parseColor("#03A9F4"),
- Color.parseColor("#00BCD4"),
- Color.parseColor("#009688"),
- Color.parseColor("#4CAF50"),
- Color.parseColor("#8BC34A"),
- Color.parseColor("#CDDC39"),
- Color.parseColor("#FFEB3B"),
- Color.parseColor("#FFC107"),
- Color.parseColor("#FF9800"),
- Color.parseColor("#FF5722"),
- Color.parseColor("#795548"),
- Color.parseColor("#9E9E9E"),
- Color.parseColor("#607D8B"),
- Color.parseColor("#F44336"),
- Color.parseColor("#E91E63"),
- Color.parseColor("#9C27B0"),
- Color.parseColor("#673AB7"),
- Color.parseColor("#3F51B5"),
- Color.parseColor("#2196F3"),
- Color.parseColor("#03A9F4")
- };
-
- interface Listener {
- void onClick(int index);
-
- void onLongClick(int index);
-
- void onSelectionChanged(int count);
- }
-
- private final Listener callback;
-
- MainAdapter(Listener callback) {
- super();
- this.selectedIndices = new ArrayList<>(16);
- this.callback = callback;
- }
-
- String getItem(int index) {
- return ALPHABET[index];
- }
-
- List getSelectedIndices() {
- return selectedIndices;
- }
-
- void toggleSelected(int index) {
- if (selectedIndices.contains(index)) {
- selectedIndices.remove((Integer) index);
- } else {
- selectedIndices.add(index);
- }
- notifyItemChanged(index);
- if (callback != null) {
- callback.onSelectionChanged(selectedIndices.size());
- }
- }
-
- void clearSelected() {
- if (selectedIndices.isEmpty()) {
- return;
- }
- selectedIndices.clear();
- notifyDataSetChanged();
- if (callback != null) {
- callback.onSelectionChanged(0);
- }
- }
-
- @Override
- public MainViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- View v =
- LayoutInflater.from(parent.getContext()).inflate(R.layout.griditem_main, parent, false);
- return new MainViewHolder(v, callback);
- }
-
- @Override
- public void onBindViewHolder(MainViewHolder holder, int position) {
- holder.label.setText(getItem(position));
-
- final Drawable d;
- final Context c = holder.itemView.getContext();
-
- if (selectedIndices.contains(position)) {
- d = new ColorDrawable(ContextCompat.getColor(c, R.color.grid_foreground_selected));
- holder.label.setTextColor(ContextCompat.getColor(c, R.color.grid_label_text_selected));
- } else {
- d = null;
- holder.label.setTextColor(ContextCompat.getColor(c, R.color.grid_label_text_normal));
- }
-
- //noinspection RedundantCast
- ((FrameLayout) holder.colorSquare).setForeground(d);
- holder.colorSquare.setBackgroundColor(COLORS[position]);
- }
-
- @Override
- public void setSelected(int index, boolean selected) {
- Log.d("MainAdapter", "setSelected(" + index + ", " + selected + ")");
- if (!selected) {
- selectedIndices.remove((Integer) index);
- } else if (!selectedIndices.contains(index)) {
- selectedIndices.add(index);
- }
- notifyItemChanged(index);
- if (callback != null) {
- callback.onSelectionChanged(selectedIndices.size());
- }
- }
-
- @Override
- public boolean isIndexSelectable(int index) {
- return true;
- }
-
- @Override
- public int getItemCount() {
- return ALPHABET.length;
- }
-
- static class MainViewHolder extends RecyclerView.ViewHolder
- implements View.OnClickListener, View.OnLongClickListener {
-
- private final TextView label;
- final RectangleView colorSquare;
- private final Listener callback;
-
- MainViewHolder(View itemView, Listener callback) {
- super(itemView);
- this.callback = callback;
- this.label = itemView.findViewById(R.id.label);
- this.colorSquare = itemView.findViewById(R.id.colorSquare);
- this.itemView.setOnClickListener(this);
- this.itemView.setOnLongClickListener(this);
- }
-
- @Override
- public void onClick(View v) {
- if (callback != null) {
- callback.onClick(getAdapterPosition());
- }
- }
-
- @Override
- public boolean onLongClick(View v) {
- if (callback != null) {
- callback.onLongClick(getAdapterPosition());
- }
- return true;
- }
- }
-}
diff --git a/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/MainAdapter.kt b/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/MainAdapter.kt
new file mode 100644
index 0000000..250e7af
--- /dev/null
+++ b/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/MainAdapter.kt
@@ -0,0 +1,147 @@
+package com.afollestad.dragselectrecyclerviewsample
+
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.graphics.drawable.Drawable
+import android.support.v7.widget.RecyclerView
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.FrameLayout
+import com.afollestad.dragselectrecyclerview.DragSelectReceiver
+import kotlinx.android.synthetic.main.griditem_main.view.colorSquare
+import kotlinx.android.synthetic.main.griditem_main.view.label
+
+/** @author Aidan Follestad (afollestad) */
+class MainAdapter(private val callback: Listener?) :
+ RecyclerView.Adapter(),
+ DragSelectReceiver {
+
+ val selectedIndices: MutableList = mutableListOf()
+
+ interface Listener {
+ fun onClick(index: Int)
+
+ fun onLongClick(index: Int)
+
+ fun onSelectionChanged(count: Int)
+ }
+
+ operator fun get(index: Int): String {
+ return ALPHABET[index]
+ }
+
+ fun toggleSelected(index: Int) {
+ if (selectedIndices.contains(index)) {
+ selectedIndices.remove(index)
+ } else {
+ selectedIndices.add(index)
+ }
+ notifyItemChanged(index)
+ callback?.onSelectionChanged(selectedIndices.size)
+ }
+
+ fun clearSelected() {
+ if (selectedIndices.isEmpty()) {
+ return
+ }
+ selectedIndices.clear()
+ notifyDataSetChanged()
+ callback?.onSelectionChanged(0)
+ }
+
+ override fun onCreateViewHolder(
+ parent: ViewGroup,
+ viewType: Int
+ ): MainViewHolder {
+ val v = LayoutInflater.from(parent.context)
+ .inflate(R.layout.griditem_main, parent, false)
+ return MainViewHolder(v, callback)
+ }
+
+ override fun onBindViewHolder(
+ holder: MainViewHolder,
+ position: Int
+ ) {
+ holder.itemView.label.text = this[position]
+
+ val d: Drawable?
+ val c = holder.itemView.context
+
+ if (selectedIndices.contains(position)) {
+ d = ColorDrawable(c.color(R.color.grid_foreground_selected))
+ holder.itemView.label.setTextColor(
+ c.color(R.color.grid_label_text_selected)
+ )
+ } else {
+ d = null
+ holder.itemView.label.setTextColor(c.color(R.color.grid_label_text_normal))
+ }
+
+
+ (holder.itemView.colorSquare as FrameLayout).foreground = d
+ holder.itemView.colorSquare.setBackgroundColor(COLORS[position])
+ }
+
+ override fun setSelected(
+ index: Int,
+ selected: Boolean
+ ) {
+ Log.d("MainAdapter", "setSelected($index, $selected)")
+ if (!selected) {
+ selectedIndices.remove(index)
+ } else if (!selectedIndices.contains(index)) {
+ selectedIndices.add(index)
+ }
+ notifyItemChanged(index)
+ callback?.onSelectionChanged(selectedIndices.size)
+ }
+
+ override fun isIndexSelectable(index: Int): Boolean {
+ return true
+ }
+
+ override fun getItemCount(): Int {
+ return ALPHABET.size
+ }
+
+ class MainViewHolder(
+ itemView: View,
+ private val callback: Listener?
+ ) : RecyclerView.ViewHolder(itemView), View.OnClickListener, View.OnLongClickListener {
+
+ init {
+ this.itemView.setOnClickListener(this)
+ this.itemView.setOnLongClickListener(this)
+ }
+
+ override fun onClick(v: View) {
+ callback?.onClick(adapterPosition)
+ }
+
+ override fun onLongClick(v: View): Boolean {
+ callback?.onLongClick(adapterPosition)
+ return true
+ }
+ }
+
+ companion object {
+
+ private val ALPHABET =
+ "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z".split(" ")
+ .dropLastWhile { it.isEmpty() }
+ .toTypedArray()
+ private val COLORS = intArrayOf(
+ Color.parseColor("#F44336"), Color.parseColor("#E91E63"), Color.parseColor("#9C27B0"),
+ Color.parseColor("#673AB7"), Color.parseColor("#3F51B5"), Color.parseColor("#2196F3"),
+ Color.parseColor("#03A9F4"), Color.parseColor("#00BCD4"), Color.parseColor("#009688"),
+ Color.parseColor("#4CAF50"), Color.parseColor("#8BC34A"), Color.parseColor("#CDDC39"),
+ Color.parseColor("#FFEB3B"), Color.parseColor("#FFC107"), Color.parseColor("#FF9800"),
+ Color.parseColor("#FF5722"), Color.parseColor("#795548"), Color.parseColor("#9E9E9E"),
+ Color.parseColor("#607D8B"), Color.parseColor("#F44336"), Color.parseColor("#E91E63"),
+ Color.parseColor("#9C27B0"), Color.parseColor("#673AB7"), Color.parseColor("#3F51B5"),
+ Color.parseColor("#2196F3"), Color.parseColor("#03A9F4")
+ )
+ }
+}
diff --git a/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/RectangleView.java b/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/RectangleView.java
deleted file mode 100644
index 8876174..0000000
--- a/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/RectangleView.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package com.afollestad.dragselectrecyclerviewsample;
-
-import android.content.Context;
-import android.util.AttributeSet;
-import android.widget.FrameLayout;
-
-/** @author Aidan Follestad (afollestad) */
-public class RectangleView extends FrameLayout {
-
- public RectangleView(Context context) {
- super(context);
- }
-
- public RectangleView(Context context, AttributeSet attrs) {
- super(context, attrs);
- }
-
- public RectangleView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(context, attrs, defStyleAttr);
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- setMeasuredDimension(getMeasuredWidth(), (int) (getMeasuredWidth() * 1.4f));
- }
-}
diff --git a/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/RectangleView.kt b/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/RectangleView.kt
new file mode 100644
index 0000000..857ab31
--- /dev/null
+++ b/sample/src/main/java/com/afollestad/dragselectrecyclerviewsample/RectangleView.kt
@@ -0,0 +1,20 @@
+package com.afollestad.dragselectrecyclerviewsample
+
+import android.content.Context
+import android.util.AttributeSet
+import android.widget.FrameLayout
+
+/** @author Aidan Follestad (afollestad) */
+class RectangleView(
+ context: Context?,
+ attrs: AttributeSet?
+) : FrameLayout(context, attrs) {
+
+ override fun onMeasure(
+ widthMeasureSpec: Int,
+ heightMeasureSpec: Int
+ ) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec)
+ setMeasuredDimension(measuredWidth, (measuredWidth * 1.4f).toInt())
+ }
+}
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
index ac1aee5..55ca6e5 100644
--- a/sample/src/main/res/layout/activity_main.xml
+++ b/sample/src/main/res/layout/activity_main.xml
@@ -32,7 +32,7 @@
-