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)! - - - Get it on Google Play - +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 @@ -