add Drag selection to directories
This commit is contained in:
parent
44a71c032b
commit
c174b7d395
4 changed files with 163 additions and 8 deletions
|
@ -124,6 +124,10 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
|
||||||
override fun onStop() {
|
override fun onStop() {
|
||||||
super.onStop()
|
super.onStop()
|
||||||
config.temporarilyShowHidden = false
|
config.temporarilyShowHidden = false
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
super.onDestroy()
|
||||||
DirectoryAdapter.cleanup()
|
DirectoryAdapter.cleanup()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -211,6 +215,10 @@ class MainActivity : SimpleActivity(), DirectoryAdapter.DirOperationsListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun itemLongClicked(position: Int) {
|
||||||
|
directories_grid.setDragSelectActive(position)
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleZooming() {
|
private fun handleZooming() {
|
||||||
val layoutManager = directories_grid.layoutManager as GridLayoutManager
|
val layoutManager = directories_grid.layoutManager as GridLayoutManager
|
||||||
layoutManager.spanCount = config.dirColumnCnt
|
layoutManager.spanCount = config.dirColumnCnt
|
||||||
|
|
|
@ -321,6 +321,10 @@ class DirectoryAdapter(val activity: SimpleActivity, val dirs: MutableList<Direc
|
||||||
|
|
||||||
override fun getItemCount() = dirs.size
|
override fun getItemCount() = dirs.size
|
||||||
|
|
||||||
|
fun selectItem(pos: Int) {
|
||||||
|
toggleItemSelection(true, pos)
|
||||||
|
}
|
||||||
|
|
||||||
fun selectRange(from: Int, to: Int, min: Int, max: Int) {
|
fun selectRange(from: Int, to: Int, min: Int, max: Int) {
|
||||||
if (from == to) {
|
if (from == to) {
|
||||||
(min..max).filter { it != from }
|
(min..max).filter { it != from }
|
||||||
|
|
|
@ -1,13 +1,38 @@
|
||||||
package com.simplemobiletools.gallery.views
|
package com.simplemobiletools.gallery.views
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.os.Handler
|
||||||
import android.support.v7.widget.RecyclerView
|
import android.support.v7.widget.RecyclerView
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.ScaleGestureDetector
|
import android.view.ScaleGestureDetector
|
||||||
|
import com.simplemobiletools.gallery.R
|
||||||
|
import com.simplemobiletools.gallery.adapters.DirectoryAdapter
|
||||||
|
|
||||||
|
// drag selection is based on https://github.com/afollestad/drag-select-recyclerview
|
||||||
class MyScalableRecyclerView : RecyclerView {
|
class MyScalableRecyclerView : RecyclerView {
|
||||||
var mScaleDetector: ScaleGestureDetector
|
private val AUTO_SCROLL_DELAY = 25L
|
||||||
|
|
||||||
|
private var mScaleDetector: ScaleGestureDetector
|
||||||
|
|
||||||
|
private var dragSelectActive = false
|
||||||
|
private var lastDraggedIndex = -1
|
||||||
|
private var minReached = 0
|
||||||
|
private var maxReached = 0
|
||||||
|
private var initialSelection = 0
|
||||||
|
|
||||||
|
private var hotspotHeight = 0
|
||||||
|
private var hotspotOffsetTop = 0
|
||||||
|
private var hotspotOffsetBottom = 0
|
||||||
|
|
||||||
|
private var hotspotTopBoundStart = 0
|
||||||
|
private var hotspotTopBoundEnd = 0
|
||||||
|
private var hotspotBottomBoundStart = 0
|
||||||
|
private var hotspotBottomBoundEnd = 0
|
||||||
|
private var autoScrollVelocity = 0
|
||||||
|
|
||||||
|
private var inTopHotspot = false
|
||||||
|
private var inBottomHotspot = false
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
var mListener: ZoomListener? = null
|
var mListener: ZoomListener? = null
|
||||||
|
@ -20,22 +45,138 @@ class MyScalableRecyclerView : RecyclerView {
|
||||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
hotspotHeight = context.resources.getDimensionPixelSize(R.dimen.dragselect_hotspot_height)
|
||||||
mScaleDetector = ScaleGestureDetector(context, GestureListener())
|
mScaleDetector = ScaleGestureDetector(context, GestureListener())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
override fun onMeasure(widthSpec: Int, heightSpec: Int) {
|
||||||
super.dispatchTouchEvent(ev)
|
super.onMeasure(widthSpec, heightSpec)
|
||||||
if (ev.action == MotionEvent.ACTION_UP) {
|
if (hotspotHeight > -1) {
|
||||||
mCurrScaleFactor = 1.0f
|
hotspotTopBoundStart = hotspotOffsetTop
|
||||||
mLastUp = System.currentTimeMillis()
|
hotspotTopBoundEnd = hotspotOffsetTop + hotspotHeight
|
||||||
|
hotspotBottomBoundStart = measuredHeight - hotspotHeight - hotspotOffsetBottom
|
||||||
|
hotspotBottomBoundEnd = measuredHeight - hotspotOffsetBottom
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var autoScrollHandler = Handler()
|
||||||
|
private val autoScrollRunnable = object : Runnable {
|
||||||
|
override fun run() {
|
||||||
|
if (inTopHotspot) {
|
||||||
|
scrollBy(0, -autoScrollVelocity)
|
||||||
|
autoScrollHandler.postDelayed(this, AUTO_SCROLL_DELAY)
|
||||||
|
} else if (inBottomHotspot) {
|
||||||
|
scrollBy(0, autoScrollVelocity)
|
||||||
|
autoScrollHandler.postDelayed(this, AUTO_SCROLL_DELAY)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
|
||||||
|
if (!dragSelectActive)
|
||||||
|
super.dispatchTouchEvent(ev)
|
||||||
|
|
||||||
|
when (ev.action) {
|
||||||
|
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
|
||||||
|
dragSelectActive = false
|
||||||
|
mCurrScaleFactor = 1.0f
|
||||||
|
mLastUp = System.currentTimeMillis()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
MotionEvent.ACTION_MOVE -> {
|
||||||
|
if (dragSelectActive) {
|
||||||
|
val itemPosition = getItemPosition(ev)
|
||||||
|
if (hotspotHeight > -1) {
|
||||||
|
if (ev.y in hotspotTopBoundStart..hotspotTopBoundEnd) {
|
||||||
|
inBottomHotspot = false
|
||||||
|
if (!inTopHotspot) {
|
||||||
|
inTopHotspot = true
|
||||||
|
autoScrollHandler.removeCallbacks(autoScrollRunnable)
|
||||||
|
autoScrollHandler.postDelayed(autoScrollRunnable, AUTO_SCROLL_DELAY.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
val simulatedFactor = (hotspotTopBoundEnd - hotspotTopBoundStart).toFloat()
|
||||||
|
val simulatedY = ev.y - hotspotTopBoundStart
|
||||||
|
autoScrollVelocity = (simulatedFactor - simulatedY).toInt() / 2
|
||||||
|
} else if (ev.y in hotspotBottomBoundStart..hotspotBottomBoundEnd) {
|
||||||
|
inTopHotspot = false
|
||||||
|
if (!inBottomHotspot) {
|
||||||
|
inBottomHotspot = true
|
||||||
|
autoScrollHandler.removeCallbacks(autoScrollRunnable)
|
||||||
|
autoScrollHandler.postDelayed(autoScrollRunnable, AUTO_SCROLL_DELAY.toLong())
|
||||||
|
}
|
||||||
|
|
||||||
|
val simulatedY = ev.y + hotspotBottomBoundEnd
|
||||||
|
val simulatedFactor = (hotspotBottomBoundStart + hotspotBottomBoundEnd).toFloat()
|
||||||
|
autoScrollVelocity = (simulatedY - simulatedFactor).toInt() / 2
|
||||||
|
} else if (inTopHotspot || inBottomHotspot) {
|
||||||
|
autoScrollHandler.removeCallbacks(autoScrollRunnable)
|
||||||
|
inTopHotspot = false
|
||||||
|
inBottomHotspot = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (itemPosition != RecyclerView.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
|
||||||
|
}
|
||||||
|
|
||||||
|
(adapter as DirectoryAdapter).selectRange(initialSelection, lastDraggedIndex, minReached, maxReached)
|
||||||
|
|
||||||
|
if (initialSelection == lastDraggedIndex) {
|
||||||
|
minReached = lastDraggedIndex
|
||||||
|
maxReached = lastDraggedIndex
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return mScaleDetector.onTouchEvent(ev)
|
return mScaleDetector.onTouchEvent(ev)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun setDragSelectActive(initialSelection: Int) {
|
||||||
|
if (dragSelectActive)
|
||||||
|
return
|
||||||
|
|
||||||
|
lastDraggedIndex = -1
|
||||||
|
minReached = -1
|
||||||
|
maxReached = -1
|
||||||
|
this.initialSelection = initialSelection
|
||||||
|
dragSelectActive = true
|
||||||
|
(adapter as DirectoryAdapter).selectItem(initialSelection)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getItemPosition(e: MotionEvent): Int {
|
||||||
|
val v = findChildViewUnder(e.x, e.y) ?: return RecyclerView.NO_POSITION
|
||||||
|
|
||||||
|
if (v.tag == null || v.tag !is RecyclerView.ViewHolder) {
|
||||||
|
throw IllegalStateException("Make sure your adapter makes a call to super.onBindViewHolder(), and doesn't override itemView tags.")
|
||||||
|
}
|
||||||
|
|
||||||
|
val holder = v.tag as RecyclerView.ViewHolder
|
||||||
|
return holder.adapterPosition
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class GestureListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
class GestureListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {
|
||||||
val ZOOM_IN_THRESHOLD = -0.6f
|
private val ZOOM_IN_THRESHOLD = -0.4f
|
||||||
val ZOOM_OUT_THRESHOLD = 0.25f
|
private val ZOOM_OUT_THRESHOLD = 0.15f
|
||||||
|
|
||||||
override fun onScale(detector: ScaleGestureDetector): Boolean {
|
override fun onScale(detector: ScaleGestureDetector): Boolean {
|
||||||
if (System.currentTimeMillis() - mLastUp < 1000)
|
if (System.currentTimeMillis() - mLastUp < 1000)
|
||||||
|
|
|
@ -7,4 +7,6 @@
|
||||||
<dimen name="play_outline_size_big">150dp</dimen>
|
<dimen name="play_outline_size_big">150dp</dimen>
|
||||||
<dimen name="timer_padding">24dp</dimen>
|
<dimen name="timer_padding">24dp</dimen>
|
||||||
<dimen name="tmb_shadow_height">50dp</dimen>
|
<dimen name="tmb_shadow_height">50dp</dimen>
|
||||||
|
|
||||||
|
<dimen name="dragselect_hotspot_height">56dp</dimen>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue