From c001b886f11f85fff7099d7860d6c48897afcd8a Mon Sep 17 00:00:00 2001 From: Robb Date: Sat, 4 Jul 2020 14:21:11 +0200 Subject: [PATCH 1/6] Working drawer swipe (WIP; still much cleanup to do) --- app/src/main/AndroidManifest.xml | 3 + .../fastadapter/app/SampleActivity.kt | 35 ++-- .../app/SwipeDrawerListActivity.kt | 175 +++++++++++++++++ .../app/items/SwipeableDrawerItem.kt | 175 +++++++++++++++++ .../main/res/layout/swipeable_choice_item.xml | 98 ++++++++++ app/src/main/res/values/ids.xml | 1 + app/src/main/res/values/strings.xml | 4 +- .../fastadapter/swipe/IDrawerSwipeable.kt | 11 ++ .../swipe/SimpleSwipeDrawerCallback.kt | 182 ++++++++++++++++++ .../SimpleSwipeDrawerDragCallback.kt | 76 ++++++++ 10 files changed, 743 insertions(+), 17 deletions(-) create mode 100644 app/src/main/java/com/mikepenz/fastadapter/app/SwipeDrawerListActivity.kt create mode 100644 app/src/main/java/com/mikepenz/fastadapter/app/items/SwipeableDrawerItem.kt create mode 100644 app/src/main/res/layout/swipeable_choice_item.xml create mode 100644 library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/IDrawerSwipeable.kt create mode 100644 library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt create mode 100644 library-extensions-utils/src/main/java/com/mikepenz/fastadapter/swipe_drag/SimpleSwipeDrawerDragCallback.kt diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 4bc6ebdca..8dbf42ddd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -34,6 +34,9 @@ + diff --git a/app/src/main/java/com/mikepenz/fastadapter/app/SampleActivity.kt b/app/src/main/java/com/mikepenz/fastadapter/app/SampleActivity.kt index b19f483f4..90c1dc285 100755 --- a/app/src/main/java/com/mikepenz/fastadapter/app/SampleActivity.kt +++ b/app/src/main/java/com/mikepenz/fastadapter/app/SampleActivity.kt @@ -48,6 +48,7 @@ class SampleActivity : AppCompatActivity() { //our `SelectExtension` private lateinit var selectExtension: SelectExtension + @Suppress("deprecation") override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -77,13 +78,14 @@ class SampleActivity : AppCompatActivity() { PrimaryDrawerItem().withName(R.string.sample_checkbox_item).withDescription(R.string.sample_checkbox_item_descr).withSelectable(false).withIdentifier(10).withIcon(CommunityMaterial.Icon.cmd_checkbox_marked), PrimaryDrawerItem().withName(R.string.sample_radiobutton_item).withDescription(R.string.sample_radiobutton_item_descr).withSelectable(false).withIdentifier(11).withIcon(CommunityMaterial.Icon2.cmd_radiobox_marked), PrimaryDrawerItem().withName(R.string.sample_swipe_list).withDescription(R.string.sample_swipe_list_descr).withSelectable(false).withIdentifier(12).withIcon(MaterialDesignIconic.Icon.gmi_format_align_left), - PrimaryDrawerItem().withName(R.string.sample_endless_scroll_list).withDescription(R.string.sample_endless_scroll_list_descr).withSelectable(false).withIdentifier(13).withIcon(MaterialDesignIconic.Icon.gmi_long_arrow_down), - PrimaryDrawerItem().withName(R.string.sample_sort).withDescription(R.string.sample_sort_descr).withSelectable(false).withIdentifier(14).withIcon(MaterialDesignIconic.Icon.gmi_sort_by_alpha), - PrimaryDrawerItem().withName(R.string.sample_mopub).withDescription(R.string.sample_mopub_descr).withSelectable(false).withIdentifier(15).withIcon(MaterialDesignIconic.Icon.gmi_accounts_list), - PrimaryDrawerItem().withName(R.string.sample_realm_list).withDescription(R.string.sample_realm_list_descr).withSelectable(false).withIdentifier(16).withIcon(MaterialDesignIconic.Icon.gmi_format_color_text), - PrimaryDrawerItem().withName(R.string.sample_collapsible_multi_select_delete).withDescription(R.string.sample_collapsible_multi_select_delete_descr).withSelectable(false).withIdentifier(17).withIcon(MaterialDesignIconic.Icon.gmi_check_all), - PrimaryDrawerItem().withName(R.string.sample_sticky_header_mopub).withDescription(R.string.sample_sticky_header_mopub_descr).withSelectable(false).withIdentifier(18).withIcon(MaterialDesignIconic.Icon.gmi_accounts_list), - PrimaryDrawerItem().withName(R.string.sample_diff_util).withDescription(R.string.sample_diff_util_descr).withSelectable(false).withIdentifier(19).withIcon(MaterialDesignIconic.Icon.gmi_refresh), + PrimaryDrawerItem().withName(R.string.sample_swipe_drawer_list).withDescription(R.string.sample_swipe_drawer_list_descr).withSelectable(false).withIdentifier(13).withIcon(MaterialDesignIconic.Icon.gmi_format_align_left), + PrimaryDrawerItem().withName(R.string.sample_endless_scroll_list).withDescription(R.string.sample_endless_scroll_list_descr).withSelectable(false).withIdentifier(14).withIcon(MaterialDesignIconic.Icon.gmi_long_arrow_down), + PrimaryDrawerItem().withName(R.string.sample_sort).withDescription(R.string.sample_sort_descr).withSelectable(false).withIdentifier(15).withIcon(MaterialDesignIconic.Icon.gmi_sort_by_alpha), + PrimaryDrawerItem().withName(R.string.sample_mopub).withDescription(R.string.sample_mopub_descr).withSelectable(false).withIdentifier(16).withIcon(MaterialDesignIconic.Icon.gmi_accounts_list), + PrimaryDrawerItem().withName(R.string.sample_realm_list).withDescription(R.string.sample_realm_list_descr).withSelectable(false).withIdentifier(17).withIcon(MaterialDesignIconic.Icon.gmi_format_color_text), + PrimaryDrawerItem().withName(R.string.sample_collapsible_multi_select_delete).withDescription(R.string.sample_collapsible_multi_select_delete_descr).withSelectable(false).withIdentifier(18).withIcon(MaterialDesignIconic.Icon.gmi_check_all), + PrimaryDrawerItem().withName(R.string.sample_sticky_header_mopub).withDescription(R.string.sample_sticky_header_mopub_descr).withSelectable(false).withIdentifier(19).withIcon(MaterialDesignIconic.Icon.gmi_accounts_list), + PrimaryDrawerItem().withName(R.string.sample_diff_util).withDescription(R.string.sample_diff_util_descr).withSelectable(false).withIdentifier(20).withIcon(MaterialDesignIconic.Icon.gmi_refresh), DividerDrawerItem(), PrimaryDrawerItem().withName(R.string.open_source).withSelectable(false).withIdentifier(100).withIcon(MaterialDesignIconic.Icon.gmi_github) ) @@ -101,14 +103,15 @@ class SampleActivity : AppCompatActivity() { 10L -> Intent(this@SampleActivity, CheckBoxSampleActivity::class.java) 11L -> Intent(this@SampleActivity, RadioButtonSampleActivity::class.java) 12L -> Intent(this@SampleActivity, SwipeListActivity::class.java) - 13L -> Intent(this@SampleActivity, EndlessScrollListActivity::class.java) - 14L -> Intent(this@SampleActivity, SortActivity::class.java) - 15L -> Intent(this@SampleActivity, MopubAdsActivity::class.java) - 16L -> Intent(this@SampleActivity, RealmActivity::class.java) - 17L -> Intent(this@SampleActivity, ExpandableMultiselectDeleteSampleActivity::class.java) - 18L -> Intent(this@SampleActivity, StickyHeaderMopubAdsActivity::class.java) - 19L -> Intent(this@SampleActivity, DiffUtilActivity::class.java) - 20L -> Intent(this@SampleActivity, PagedActivity::class.java) + 13L -> Intent(this@SampleActivity, SwipeDrawerListActivity::class.java) + 14L -> Intent(this@SampleActivity, EndlessScrollListActivity::class.java) + 15L -> Intent(this@SampleActivity, SortActivity::class.java) + 16L -> Intent(this@SampleActivity, MopubAdsActivity::class.java) + 17L -> Intent(this@SampleActivity, RealmActivity::class.java) + 18L -> Intent(this@SampleActivity, ExpandableMultiselectDeleteSampleActivity::class.java) + 19L -> Intent(this@SampleActivity, StickyHeaderMopubAdsActivity::class.java) + 20L -> Intent(this@SampleActivity, DiffUtilActivity::class.java) + 21L -> Intent(this@SampleActivity, PagedActivity::class.java) 100L -> LibsBuilder() .withFields(R.string::class.java.fields) .withActivityTitle(getString(R.string.open_source)) @@ -125,7 +128,7 @@ class SampleActivity : AppCompatActivity() { false } selectedItemPosition = RecyclerView.NO_POSITION - withSavedInstance(savedInstanceState) + setSavedInstance(savedInstanceState) } //create our ItemAdapter which will host our items diff --git a/app/src/main/java/com/mikepenz/fastadapter/app/SwipeDrawerListActivity.kt b/app/src/main/java/com/mikepenz/fastadapter/app/SwipeDrawerListActivity.kt new file mode 100644 index 000000000..d53178231 --- /dev/null +++ b/app/src/main/java/com/mikepenz/fastadapter/app/SwipeDrawerListActivity.kt @@ -0,0 +1,175 @@ +package com.mikepenz.fastadapter.app + +import android.graphics.Color +import android.os.Bundle +import android.text.TextUtils +import android.view.Menu +import android.view.MenuItem +import android.widget.Toast +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.widget.SearchView +import androidx.recyclerview.widget.DefaultItemAnimator +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.mikepenz.fastadapter.adapters.FastItemAdapter +import com.mikepenz.fastadapter.app.adapters.IDraggableViewHolder +import com.mikepenz.fastadapter.app.items.SwipeableDrawerItem +import com.mikepenz.fastadapter.drag.ItemTouchCallback +import com.mikepenz.fastadapter.drag.SimpleDragCallback +import com.mikepenz.fastadapter.swipe_drag.SimpleSwipeDrawerDragCallback +import com.mikepenz.fastadapter.utils.DragDropUtil +import com.mikepenz.iconics.IconicsDrawable +import com.mikepenz.iconics.typeface.library.materialdesigniconic.MaterialDesignIconic +import com.mikepenz.iconics.utils.actionBar +import com.mikepenz.iconics.utils.colorInt +import io.reactivex.functions.Consumer +import kotlinx.android.synthetic.main.activity_sample.* +import java.util.* + +class SwipeDrawerListActivity : AppCompatActivity(), ItemTouchCallback { + + //save our FastAdapter + private lateinit var fastItemDrawerAdapter: FastItemAdapter + + //drag & drop + private lateinit var touchCallback: SimpleDragCallback + private lateinit var touchHelper: ItemTouchHelper + + + private fun delete(item: SwipeableDrawerItem) { + item.deleteAction = null + val position12 = fastItemDrawerAdapter.getAdapterPosition(item) + if (position12 != RecyclerView.NO_POSITION) { + //this sample uses a filter. If a filter is used we should use the methods provided by the filter (to make sure filter and normal state is updated) + fastItemDrawerAdapter.itemFilter.remove(position12) + Toast.makeText(this, "Deleted", Toast.LENGTH_SHORT) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_sample) + + // Handle Toolbar + setSupportActionBar(toolbar) + + //create our FastAdapter which will manage everything + fastItemDrawerAdapter = FastItemAdapter() + //configure the itemAdapter + fastItemDrawerAdapter.itemFilter.filterPredicate = { item: SwipeableDrawerItem, constraint: CharSequence? -> + item.name?.textString.toString().contains(constraint.toString(), ignoreCase = true) + } + + //get our recyclerView and do basic setup + rv.layoutManager = LinearLayoutManager(this) + rv.itemAnimator = DefaultItemAnimator() + rv.adapter = fastItemDrawerAdapter + + //fill with some sample data + var x = 0 + val items = ArrayList() + for (s in ALPHABET) { + val count = Random().nextInt(20) + for (i in 1..count) { + val swipeableItem = SwipeableDrawerItem().withName("$s Test $x") + swipeableItem.identifier = (100 + x).toLong() + swipeableItem.withIsSwipeable(i % 5 != 0) + swipeableItem.withIsDraggable(i % 5 != 0) + swipeableItem.deleteAction = Consumer { item -> delete(item) } + items.add(swipeableItem) + x++ + } + } + fastItemDrawerAdapter.add(items) + + + //add drag and drop for item + //and add swipe as well + touchCallback = SimpleSwipeDrawerDragCallback( + this, + ItemTouchHelper.LEFT) + .withNotifyAllDrops(true) + .withSwipeRight() + .withSensitivity(10f) + + touchHelper = ItemTouchHelper(touchCallback) // Create ItemTouchHelper and pass with parameter the SimpleDragCallback + touchHelper.attachToRecyclerView(rv) // Attach ItemTouchHelper to RecyclerView + + //restore selections (this has to be done after the items were added) + fastItemDrawerAdapter.withSavedInstanceState(savedInstanceState) + + //set the back arrow in the toolbar + supportActionBar?.setDisplayHomeAsUpEnabled(true) + supportActionBar?.setHomeButtonEnabled(false) + } + + override fun onSaveInstanceState(_outState: Bundle) { + var outState = _outState + //add the values which need to be saved from the adapter to the bundle + outState = fastItemDrawerAdapter.saveInstanceState(outState) + super.onSaveInstanceState(outState) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + //handle the click on the back arrow click + return when (item.itemId) { + android.R.id.home -> { + onBackPressed() + true + } + else -> super.onOptionsItemSelected(item) + } + } + + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + // Inflate the menu items for use in the action bar + val inflater = menuInflater + inflater.inflate(R.menu.search, menu) + + //search icon + menu.findItem(R.id.search).icon = IconicsDrawable(this, MaterialDesignIconic.Icon.gmi_search).apply { colorInt = Color.BLACK; actionBar() } + + val searchView = menu.findItem(R.id.search).actionView as SearchView + searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { + override fun onQueryTextSubmit(s: String): Boolean { + touchCallback.setIsDragEnabled(false) + fastItemDrawerAdapter.filter(s) + return true + } + + + override fun onQueryTextChange(s: String): Boolean { + fastItemDrawerAdapter.filter(s) + touchCallback.setIsDragEnabled(TextUtils.isEmpty(s)) + return true + } + }) + + return super.onCreateOptionsMenu(menu) + } + + override fun itemTouchOnMove(oldPosition: Int, newPosition: Int): Boolean { + DragDropUtil.onMove(fastItemDrawerAdapter.itemAdapter, oldPosition, newPosition) // change position + return true + } + + override fun itemTouchDropped(oldPosition: Int, newPosition: Int) { + val vh: RecyclerView.ViewHolder? = rv.findViewHolderForAdapterPosition(newPosition) + if (vh is IDraggableViewHolder) { + (vh as IDraggableViewHolder).onDropped() + } + // save the new item order, i.e. in your database + } + + override fun itemTouchStartDrag(viewHolder: RecyclerView.ViewHolder) { + if (viewHolder is IDraggableViewHolder) { + (viewHolder as IDraggableViewHolder).onDragged() + } + } + + companion object { + private val ALPHABET = arrayOf("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") + } +} diff --git a/app/src/main/java/com/mikepenz/fastadapter/app/items/SwipeableDrawerItem.kt b/app/src/main/java/com/mikepenz/fastadapter/app/items/SwipeableDrawerItem.kt new file mode 100644 index 000000000..79cc411ac --- /dev/null +++ b/app/src/main/java/com/mikepenz/fastadapter/app/items/SwipeableDrawerItem.kt @@ -0,0 +1,175 @@ +package com.mikepenz.fastadapter.app.items + +import android.graphics.Color +import android.util.Log +import android.view.MotionEvent +import android.view.View +import android.widget.TextView +import androidx.annotation.StringRes +import androidx.recyclerview.widget.RecyclerView +import com.mikepenz.fastadapter.app.R +import com.mikepenz.fastadapter.app.adapters.IDraggableViewHolder +import com.mikepenz.fastadapter.drag.IDraggable +import com.mikepenz.fastadapter.items.AbstractItem +import com.mikepenz.fastadapter.swipe.IDrawerSwipeable +import com.mikepenz.fastadapter.swipe.ISwipeable +import com.mikepenz.materialdrawer.holder.StringHolder +import io.reactivex.functions.Consumer + + +/** + * Created by Robb on 2020-07-03 + */ +class SwipeableDrawerItem : AbstractItem(), ISwipeable, IDraggable { + + var name: StringHolder? = null + var description: StringHolder? = null + + var deleteAction: Consumer? = null + override var isSwipeable = true + override var isDraggable = true + + /** + * defines the type defining this item. must be unique. preferably an id + * + * @return the type + */ + override val type: Int + get() = R.id.fastadapter_swipable_choice_item_id + + /** + * defines the layout which will be used for this item in the list + * + * @return the layout for this item + */ + override val layoutRes: Int + get() = R.layout.swipeable_choice_item + + fun withName(Name: String): SwipeableDrawerItem { + this.name = StringHolder(Name) + return this + } + + fun withName(@StringRes NameRes: Int): SwipeableDrawerItem { + this.name = StringHolder(NameRes) + return this + } + + fun withDescription(description: String): SwipeableDrawerItem { + this.description = StringHolder(description) + return this + } + + fun withDescription(@StringRes descriptionRes: Int): SwipeableDrawerItem { + this.description = StringHolder(descriptionRes) + return this + } + + fun withIsSwipeable(swipeable: Boolean): SwipeableDrawerItem { + this.isSwipeable = swipeable + return this + } + + fun withIsDraggable(draggable: Boolean): SwipeableDrawerItem { + this.isDraggable = draggable + return this + } + + /** + * binds the data of this item onto the viewHolder + * + * @param holder the viewHolder of this item + */ + override fun bindView(holder: ViewHolder, payloads: List) { + super.bindView(holder, payloads) + + //set the text for the name + StringHolder.applyTo(name, holder.name) + //set the text for the description or hide + StringHolder.applyToOrHide(description, holder.description) + + holder.deleteActionRunnable = Runnable { delete() } + } + + private fun delete() { + deleteAction?.accept(this) + } + + override fun unbindView(holder: ViewHolder) { + super.unbindView(holder) + holder.name.text = null + holder.description.text = null + holder.deleteActionRunnable = null + holder.itemContent.translationX = 0f + } + + override fun getViewHolder(v: View): ViewHolder { + return ViewHolder(v) + } + + /** + * our ViewHolder + */ + class ViewHolder(view: View) : RecyclerView.ViewHolder(view), IDraggableViewHolder, IDrawerSwipeable { + var name: TextView = view.findViewById(R.id.material_drawer_name) + var description: TextView = view.findViewById(R.id.material_drawer_description) + var deleteBtn: View = view.findViewById(R.id.delete_btn) + var itemContent: View = view.findViewById(R.id.item_content) + var swipeResultContent: View = view.findViewById(R.id.swipe_result_content) + + var deleteActionRunnable: Runnable? = null + + init { + deleteBtn.setOnClickListener { + deleteActionRunnable?.run() + } + view.setOnTouchListener { v, event -> + when (event.action) { + MotionEvent.ACTION_DOWN -> { + + } + MotionEvent.ACTION_UP -> { + + } + } + Log.i("aa", ">> view click") + false + } + itemContent.setOnTouchListener { v, event -> + when (event.action) { + MotionEvent.ACTION_DOWN -> { + + } + MotionEvent.ACTION_UP -> { + + } + } + Log.i("aa", ">> itemContent click") + false + } + swipeResultContent.setOnTouchListener { v, event -> + when (event.action) { + MotionEvent.ACTION_DOWN -> { + + } + MotionEvent.ACTION_UP -> { + + } + } + Log.i("aa", ">> swipeResultContent click") + false + } + } + + override fun onDropped() { + itemContent.setBackgroundColor(Color.WHITE) + } + + override fun onDragged() { + itemContent.setBackgroundColor(Color.LTGRAY) + } + + override val swipeableView: View + get() = itemContent + } +} diff --git a/app/src/main/res/layout/swipeable_choice_item.xml b/app/src/main/res/layout/swipeable_choice_item.xml new file mode 100644 index 000000000..94a2ea639 --- /dev/null +++ b/app/src/main/res/layout/swipeable_choice_item.xml @@ -0,0 +1,98 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 0655e9f5e..3a663d69f 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -12,6 +12,7 @@ + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4dfd26c76..5603e4086 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,8 +5,10 @@ PageListAdapter SimpleItemList Sample Filter, Drag&Drop - SwipeList Sample + SwipeList Sample (delayed) Swipe, leave-behinds and actions + SwipeList Sample (drawer) + Swipe, buttons and actions IconGrid Sample Grid, Expandable ImageList Sample diff --git a/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/IDrawerSwipeable.kt b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/IDrawerSwipeable.kt new file mode 100644 index 000000000..bc427127f --- /dev/null +++ b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/IDrawerSwipeable.kt @@ -0,0 +1,11 @@ +package com.mikepenz.fastadapter.swipe + +import android.view.View + +/** + * Created by robb on 03.07.20. + */ +interface IDrawerSwipeable { + /** @return view to be swiped inside the ViewHolder */ + val swipeableView: View +} diff --git a/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt new file mode 100644 index 000000000..f89a235cd --- /dev/null +++ b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt @@ -0,0 +1,182 @@ +package com.mikepenz.fastadapter.swipe + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.util.Log +import android.view.GestureDetector +import android.view.GestureDetector.SimpleOnGestureListener +import android.view.MotionEvent +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener +import com.mikepenz.fastadapter.FastAdapter +import com.mikepenz.fastadapter.IItem + + +/** + * Created by Mattias on 2016-02-13. + */ +class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: Int = ItemTouchHelper.LEFT) : ItemTouchHelper.SimpleCallback(0, swipeDirs) { + // Swipe movement control + private var sensitivityFactor = 1f + + private var swipeWidthDp = 80 + + private lateinit var recyclerTouchListener: OnItemTouchListener + + private var recyclerViewListened = false + + + fun withSwipeLeft(): SimpleSwipeDrawerCallback { + setDefaultSwipeDirs(swipeDirs or ItemTouchHelper.LEFT) + return this + } + + fun withSwipeRight(): SimpleSwipeDrawerCallback { + setDefaultSwipeDirs(swipeDirs or ItemTouchHelper.RIGHT) + return this + } + + /** + * Control the sensitivity of the swipe gesture + * 0.5 : very sensitive + * 1 : Android default + * 10 : almost insensitive + */ + fun withSensitivity(f: Float): SimpleSwipeDrawerCallback { + sensitivityFactor = f + return this + } + + override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { + val item = FastAdapter.getHolderAdapterItem>(viewHolder) + return if (item is ISwipeable) { + if ((item as ISwipeable).isSwipeable) { + super.getSwipeDirs(recyclerView, viewHolder) + } else { + 0 + } + } else { + super.getSwipeDirs(recyclerView, viewHolder) + } + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + // Not enabled + } + + override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { + // Not enabled + return false + } + + + override fun getSwipeEscapeVelocity(defaultValue: Float): Float { + return defaultValue * sensitivityFactor + } + + override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) { + val itemView = viewHolder.itemView + + if (!recyclerViewListened) { + recyclerTouchListener = RecyclerItemClickListener(recyclerView.context, recyclerView, null) + recyclerView.setOnTouchListener { v, event -> + Log.i("aa", ">> recyclerView click " + event.x + " / " + event.y + " - " + event.actionMasked) + // Get the viewholder's root element + var childView = recyclerView.findChildViewUnder(event.x, event.y) + if (childView != null && childView is ViewGroup) { + val position = recyclerView.layoutManager?.getPosition(childView) + Log.i("aa", ">> recyclerView pos $position") + + // Get the ViewGroup that's been clicked on (upper or lower layer) + val childViewOffset = Rect() + childView.getHitRect(childViewOffset) + childView = childView.getInnermostViewByCoordinates(event.x - childViewOffset.left, event.y - childViewOffset.top) + if (childView != null) + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + childView.onTouchEvent(event) + } + MotionEvent.ACTION_UP -> { + childView.onTouchEvent(event) + } + } + } + false + } + recyclerViewListened = true + } + + if (viewHolder.adapterPosition == RecyclerView.NO_POSITION) { + return + } + + if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { + + // Click on the empty space that reveals the underlying buttons => do nothing; buttons should intercept the click +// if (isCurrentlyActive && abs(dX).toInt() == itemView.width) return + + val swipeWidthPc = (recyclerView.context.resources.displayMetrics.density * swipeWidthDp) / itemView.width + + var swipeableView = itemView + if (viewHolder is IDrawerSwipeable) swipeableView = viewHolder.swipeableView + + swipeableView.translationX = dX * swipeWidthPc + } else super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) + } + + fun ViewGroup.getInnermostViewByCoordinates(x: Float, y: Float): View? { + (childCount - 1 downTo 0) + .map { this.getChildAt(it) } + .forEach { + val bounds = Rect() + it.getHitRect(bounds) + if (bounds.contains(x.toInt(), y.toInt())) { + return if (it is ViewGroup) it.getInnermostViewByCoordinates(x - bounds.left, y - bounds.top) + else it + } + } + return null + } + + + class RecyclerItemClickListener(context: Context?, recyclerView: RecyclerView, private val mListener: OnItemClickListener?) : OnItemTouchListener { + + interface OnItemClickListener { + fun onItemClick(view: View?, position: Int) + fun onLongItemClick(view: View?, position: Int) + } + + var mGestureDetector: GestureDetector + override fun onInterceptTouchEvent(view: RecyclerView, e: MotionEvent): Boolean { + val childView = view.findChildViewUnder(e.x, e.y) + Log.i("aa", ">> RecyclerItemClickListener " + e.x + " / " + e.y + " - " + e.actionMasked) + if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) { + mListener.onItemClick(childView, view.getChildAdapterPosition(childView)) + return true + } + return false + } + + override fun onTouchEvent(view: RecyclerView, motionEvent: MotionEvent) {} + override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {} + + init { + mGestureDetector = GestureDetector(context, object : SimpleOnGestureListener() { + override fun onSingleTapUp(e: MotionEvent): Boolean { + return true + } + + override fun onLongPress(e: MotionEvent) { + val child = recyclerView.findChildViewUnder(e.x, e.y) + if (child != null && mListener != null) { + mListener.onLongItemClick(child, recyclerView.getChildAdapterPosition(child)) + } + } + }) + } + } +} diff --git a/library-extensions-utils/src/main/java/com/mikepenz/fastadapter/swipe_drag/SimpleSwipeDrawerDragCallback.kt b/library-extensions-utils/src/main/java/com/mikepenz/fastadapter/swipe_drag/SimpleSwipeDrawerDragCallback.kt new file mode 100644 index 000000000..b93f58874 --- /dev/null +++ b/library-extensions-utils/src/main/java/com/mikepenz/fastadapter/swipe_drag/SimpleSwipeDrawerDragCallback.kt @@ -0,0 +1,76 @@ +@file:Suppress("PackageName", "PackageNaming") + +package com.mikepenz.fastadapter.swipe_drag + +import android.graphics.Canvas +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.RecyclerView +import com.mikepenz.fastadapter.drag.ItemTouchCallback +import com.mikepenz.fastadapter.drag.SimpleDragCallback +import com.mikepenz.fastadapter.swipe.SimpleSwipeDrawerCallback + +/** + * Created by Mattias on 2016-02-13. + */ +class SimpleSwipeDrawerDragCallback @JvmOverloads constructor( + itemTouchCallback: ItemTouchCallback, + swipeDirs: Int = ItemTouchHelper.LEFT) : SimpleDragCallback(itemTouchCallback) { + + private val simpleSwipeCallback: SimpleSwipeDrawerCallback + private var defaultSwipeDirs: Int = 0 + + init { + setDefaultSwipeDirs(swipeDirs) + simpleSwipeCallback = SimpleSwipeDrawerCallback(swipeDirs) + } + + override fun setDefaultSwipeDirs(defaultSwipeDirs: Int) { + this.defaultSwipeDirs = defaultSwipeDirs + super.setDefaultSwipeDirs(defaultSwipeDirs) + } + + fun withNotifyAllDrops(notifyAllDrops: Boolean): SimpleSwipeDrawerDragCallback { + this.notifyAllDrops = notifyAllDrops + return this + } + + fun withSwipeRight(): SimpleSwipeDrawerDragCallback { + simpleSwipeCallback.withSwipeRight() + return this + } + + fun withSensitivity(f: Float): SimpleSwipeDrawerDragCallback { + simpleSwipeCallback.withSensitivity(f) + return this + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + simpleSwipeCallback.onSwiped(viewHolder, direction) + } + + override fun getSwipeDirs(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { + return simpleSwipeCallback.getSwipeDirs(recyclerView, viewHolder) + } + + override fun getSwipeEscapeVelocity(defaultValue: Float): Float { + return simpleSwipeCallback.getSwipeEscapeVelocity(defaultValue) + } + + override fun getSwipeThreshold(viewHolder: RecyclerView.ViewHolder): Float { + return simpleSwipeCallback.getSwipeThreshold(viewHolder) + } + + override fun onChildDraw( + c: Canvas, + recyclerView: RecyclerView, + viewHolder: RecyclerView.ViewHolder, + dX: Float, + dY: Float, + actionState: Int, + isCurrentlyActive: Boolean + ) { + simpleSwipeCallback.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) + //Happen to know that our direct parent class doesn't (currently) draw anything... + //super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + } +} From 69abdc60fce158427df34ab3cbec62a5c93cdd44 Mon Sep 17 00:00:00 2001 From: Robb Date: Sat, 4 Jul 2020 19:20:30 +0200 Subject: [PATCH 2/6] Cleaned up drawer swipe --- .../app/SwipeDrawerListActivity.kt | 25 ++- .../app/items/SwipeableDrawerItem.kt | 60 ++----- ...ice_item.xml => swipeable_drawer_item.xml} | 19 +++ app/src/main/res/values/ids.xml | 2 +- .../swipe/SimpleSwipeDrawerCallback.kt | 147 +++++++----------- .../SimpleSwipeDrawerDragCallback.kt | 9 +- 6 files changed, 126 insertions(+), 136 deletions(-) rename app/src/main/res/layout/{swipeable_choice_item.xml => swipeable_drawer_item.xml} (83%) diff --git a/app/src/main/java/com/mikepenz/fastadapter/app/SwipeDrawerListActivity.kt b/app/src/main/java/com/mikepenz/fastadapter/app/SwipeDrawerListActivity.kt index d53178231..4c3a7bd4d 100644 --- a/app/src/main/java/com/mikepenz/fastadapter/app/SwipeDrawerListActivity.kt +++ b/app/src/main/java/com/mikepenz/fastadapter/app/SwipeDrawerListActivity.kt @@ -43,7 +43,25 @@ class SwipeDrawerListActivity : AppCompatActivity(), ItemTouchCallback { if (position12 != RecyclerView.NO_POSITION) { //this sample uses a filter. If a filter is used we should use the methods provided by the filter (to make sure filter and normal state is updated) fastItemDrawerAdapter.itemFilter.remove(position12) - Toast.makeText(this, "Deleted", Toast.LENGTH_SHORT) + Toast.makeText(this, "Deleted", Toast.LENGTH_SHORT).show() + } + } + + private fun archive(item: SwipeableDrawerItem) { + item.archiveAction = null + val position12 = fastItemDrawerAdapter.getAdapterPosition(item) + if (position12 != RecyclerView.NO_POSITION) { + // Do something intelligent here + Toast.makeText(this, "Archived", Toast.LENGTH_SHORT).show() + } + } + + private fun share(item: SwipeableDrawerItem) { + item.shareAction = null + val position12 = fastItemDrawerAdapter.getAdapterPosition(item) + if (position12 != RecyclerView.NO_POSITION) { + // Do something intelligent here + Toast.makeText(this, "Shared", Toast.LENGTH_SHORT).show() } } @@ -77,6 +95,8 @@ class SwipeDrawerListActivity : AppCompatActivity(), ItemTouchCallback { swipeableItem.withIsSwipeable(i % 5 != 0) swipeableItem.withIsDraggable(i % 5 != 0) swipeableItem.deleteAction = Consumer { item -> delete(item) } + swipeableItem.archiveAction = Consumer { item -> archive(item) } + swipeableItem.shareAction = Consumer { item -> share(item) } items.add(swipeableItem) x++ } @@ -90,7 +110,8 @@ class SwipeDrawerListActivity : AppCompatActivity(), ItemTouchCallback { this, ItemTouchHelper.LEFT) .withNotifyAllDrops(true) - .withSwipeRight() + .withSwipeLeft(160) // Width of archive and share buttons + .withSwipeRight(80) // Width of delete button .withSensitivity(10f) touchHelper = ItemTouchHelper(touchCallback) // Create ItemTouchHelper and pass with parameter the SimpleDragCallback diff --git a/app/src/main/java/com/mikepenz/fastadapter/app/items/SwipeableDrawerItem.kt b/app/src/main/java/com/mikepenz/fastadapter/app/items/SwipeableDrawerItem.kt index 79cc411ac..79cb2ea4a 100644 --- a/app/src/main/java/com/mikepenz/fastadapter/app/items/SwipeableDrawerItem.kt +++ b/app/src/main/java/com/mikepenz/fastadapter/app/items/SwipeableDrawerItem.kt @@ -1,8 +1,6 @@ package com.mikepenz.fastadapter.app.items import android.graphics.Color -import android.util.Log -import android.view.MotionEvent import android.view.View import android.widget.TextView import androidx.annotation.StringRes @@ -26,6 +24,8 @@ class SwipeableDrawerItem : AbstractItem(), ISwi var description: StringHolder? = null var deleteAction: Consumer? = null + var archiveAction: Consumer? = null + var shareAction: Consumer? = null override var isSwipeable = true override var isDraggable = true @@ -35,7 +35,7 @@ class SwipeableDrawerItem : AbstractItem(), ISwi * @return the type */ override val type: Int - get() = R.id.fastadapter_swipable_choice_item_id + get() = R.id.fastadapter_swipable_drawer_item_id /** * defines the layout which will be used for this item in the list @@ -43,7 +43,7 @@ class SwipeableDrawerItem : AbstractItem(), ISwi * @return the layout for this item */ override val layoutRes: Int - get() = R.layout.swipeable_choice_item + get() = R.layout.swipeable_drawer_item fun withName(Name: String): SwipeableDrawerItem { this.name = StringHolder(Name) @@ -88,11 +88,9 @@ class SwipeableDrawerItem : AbstractItem(), ISwi //set the text for the description or hide StringHolder.applyToOrHide(description, holder.description) - holder.deleteActionRunnable = Runnable { delete() } - } - - private fun delete() { - deleteAction?.accept(this) + holder.deleteActionRunnable = Runnable { deleteAction?.accept(this) } + holder.archiveActionRunnable = Runnable { archiveAction?.accept(this) } + holder.shareActionRunnable = Runnable { shareAction?.accept(this) } } override fun unbindView(holder: ViewHolder) { @@ -100,6 +98,8 @@ class SwipeableDrawerItem : AbstractItem(), ISwi holder.name.text = null holder.description.text = null holder.deleteActionRunnable = null + holder.archiveActionRunnable = null + holder.shareActionRunnable = null holder.itemContent.translationX = 0f } @@ -113,51 +113,25 @@ class SwipeableDrawerItem : AbstractItem(), ISwi class ViewHolder(view: View) : RecyclerView.ViewHolder(view), IDraggableViewHolder, IDrawerSwipeable { var name: TextView = view.findViewById(R.id.material_drawer_name) var description: TextView = view.findViewById(R.id.material_drawer_description) + var archiveBtn: View = view.findViewById(R.id.archive_btn) var deleteBtn: View = view.findViewById(R.id.delete_btn) + var shareBtn: View = view.findViewById(R.id.share_btn) var itemContent: View = view.findViewById(R.id.item_content) var swipeResultContent: View = view.findViewById(R.id.swipe_result_content) var deleteActionRunnable: Runnable? = null + var archiveActionRunnable: Runnable? = null + var shareActionRunnable: Runnable? = null init { deleteBtn.setOnClickListener { deleteActionRunnable?.run() } - view.setOnTouchListener { v, event -> - when (event.action) { - MotionEvent.ACTION_DOWN -> { - - } - MotionEvent.ACTION_UP -> { - - } - } - Log.i("aa", ">> view click") - false + archiveBtn.setOnClickListener { + archiveActionRunnable?.run() } - itemContent.setOnTouchListener { v, event -> - when (event.action) { - MotionEvent.ACTION_DOWN -> { - - } - MotionEvent.ACTION_UP -> { - - } - } - Log.i("aa", ">> itemContent click") - false - } - swipeResultContent.setOnTouchListener { v, event -> - when (event.action) { - MotionEvent.ACTION_DOWN -> { - - } - MotionEvent.ACTION_UP -> { - - } - } - Log.i("aa", ">> swipeResultContent click") - false + shareBtn.setOnClickListener { + shareActionRunnable?.run() } } diff --git a/app/src/main/res/layout/swipeable_choice_item.xml b/app/src/main/res/layout/swipeable_drawer_item.xml similarity index 83% rename from app/src/main/res/layout/swipeable_choice_item.xml rename to app/src/main/res/layout/swipeable_drawer_item.xml index 94a2ea639..f08e5240a 100644 --- a/app/src/main/res/layout/swipeable_choice_item.xml +++ b/app/src/main/res/layout/swipeable_drawer_item.xml @@ -33,6 +33,25 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> + + - + \ No newline at end of file diff --git a/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt index f89a235cd..c13de9001 100644 --- a/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt +++ b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt @@ -1,41 +1,40 @@ package com.mikepenz.fastadapter.swipe -import android.content.Context import android.graphics.Canvas import android.graphics.Rect -import android.util.Log -import android.view.GestureDetector -import android.view.GestureDetector.SimpleOnGestureListener import android.view.MotionEvent import android.view.View +import android.view.View.VISIBLE import android.view.ViewGroup import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView -import androidx.recyclerview.widget.RecyclerView.OnItemTouchListener import com.mikepenz.fastadapter.FastAdapter import com.mikepenz.fastadapter.IItem /** - * Created by Mattias on 2016-02-13. + * Created by Robb on 2020-07-04. */ class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: Int = ItemTouchHelper.LEFT) : ItemTouchHelper.SimpleCallback(0, swipeDirs) { + // Swipe movement control private var sensitivityFactor = 1f + // "Drawer width" the swipe movement is allowed to reach before blocking + private var swipeWidthLeftDp = 80 + private var swipeWidthRightDp = 80 - private var swipeWidthDp = 80 - - private lateinit var recyclerTouchListener: OnItemTouchListener - - private var recyclerViewListened = false + // Indicates whether the touchTransmitter has been set on the RecyclerView + private var touchTransmitterSet = false - fun withSwipeLeft(): SimpleSwipeDrawerCallback { + fun withSwipeLeft(widthDp : Int): SimpleSwipeDrawerCallback { + swipeWidthLeftDp = widthDp setDefaultSwipeDirs(swipeDirs or ItemTouchHelper.LEFT) return this } - fun withSwipeRight(): SimpleSwipeDrawerCallback { + fun withSwipeRight(widthDp : Int): SimpleSwipeDrawerCallback { + swipeWidthRightDp = widthDp setDefaultSwipeDirs(swipeDirs or ItemTouchHelper.RIGHT) return this } @@ -81,33 +80,9 @@ class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) { val itemView = viewHolder.itemView - if (!recyclerViewListened) { - recyclerTouchListener = RecyclerItemClickListener(recyclerView.context, recyclerView, null) - recyclerView.setOnTouchListener { v, event -> - Log.i("aa", ">> recyclerView click " + event.x + " / " + event.y + " - " + event.actionMasked) - // Get the viewholder's root element - var childView = recyclerView.findChildViewUnder(event.x, event.y) - if (childView != null && childView is ViewGroup) { - val position = recyclerView.layoutManager?.getPosition(childView) - Log.i("aa", ">> recyclerView pos $position") - - // Get the ViewGroup that's been clicked on (upper or lower layer) - val childViewOffset = Rect() - childView.getHitRect(childViewOffset) - childView = childView.getInnermostViewByCoordinates(event.x - childViewOffset.left, event.y - childViewOffset.top) - if (childView != null) - when (event.actionMasked) { - MotionEvent.ACTION_DOWN -> { - childView.onTouchEvent(event) - } - MotionEvent.ACTION_UP -> { - childView.onTouchEvent(event) - } - } - } - false - } - recyclerViewListened = true + if (!touchTransmitterSet) { + recyclerView.setOnTouchListener(RecyclerTouchTransmitter(recyclerView)) + touchTransmitterSet = true } if (viewHolder.adapterPosition == RecyclerView.NO_POSITION) { @@ -115,11 +90,9 @@ class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: } if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { - - // Click on the empty space that reveals the underlying buttons => do nothing; buttons should intercept the click -// if (isCurrentlyActive && abs(dX).toInt() == itemView.width) return - - val swipeWidthPc = (recyclerView.context.resources.displayMetrics.density * swipeWidthDp) / itemView.width + val isLeft = dX > 0 + var swipeWidthPc = recyclerView.context.resources.displayMetrics.density / itemView.width + swipeWidthPc *= if (isLeft) swipeWidthLeftDp else swipeWidthRightDp var swipeableView = itemView if (viewHolder is IDrawerSwipeable) swipeableView = viewHolder.swipeableView @@ -128,55 +101,53 @@ class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: } else super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) } - fun ViewGroup.getInnermostViewByCoordinates(x: Float, y: Float): View? { - (childCount - 1 downTo 0) - .map { this.getChildAt(it) } - .forEach { - val bounds = Rect() - it.getHitRect(bounds) - if (bounds.contains(x.toInt(), y.toInt())) { - return if (it is ViewGroup) it.getInnermostViewByCoordinates(x - bounds.left, y - bounds.top) - else it + /** + * Hack to force-transmit click events to the first visible View at the clicked coordinates + * [< swiped area ] exposed sublayer ] + * Android default touch event mechanisms don't transmit these events to the sublayer : + * any click on the exposed surface just swipe the item back to where it came + */ + class RecyclerTouchTransmitter(rv: RecyclerView) : View.OnTouchListener { + + private val recyclerView = rv + + override fun onTouch(v: View?, event: MotionEvent): Boolean { + // Get the clicked viewholder's root element + var childView = recyclerView.findChildViewUnder(event.x, event.y) + if (childView != null && childView is ViewGroup) { + // Get the first visible View under the clicked coordinates + val childViewOffset = Rect() + childView.getHitRect(childViewOffset) + childView = childView.getFirstVisibleViewByCoordinates(event.x - childViewOffset.left, event.y - childViewOffset.top) + // Transmit the ACTION_DOWN and ACTION_UP events to this View + if (childView != null) + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + childView.onTouchEvent(event) + } + MotionEvent.ACTION_UP -> { + childView.onTouchEvent(event) + } } - } - return null - } - - - class RecyclerItemClickListener(context: Context?, recyclerView: RecyclerView, private val mListener: OnItemClickListener?) : OnItemTouchListener { - - interface OnItemClickListener { - fun onItemClick(view: View?, position: Int) - fun onLongItemClick(view: View?, position: Int) - } - - var mGestureDetector: GestureDetector - override fun onInterceptTouchEvent(view: RecyclerView, e: MotionEvent): Boolean { - val childView = view.findChildViewUnder(e.x, e.y) - Log.i("aa", ">> RecyclerItemClickListener " + e.x + " / " + e.y + " - " + e.actionMasked) - if (childView != null && mListener != null && mGestureDetector.onTouchEvent(e)) { - mListener.onItemClick(childView, view.getChildAdapterPosition(childView)) - return true } return false } - override fun onTouchEvent(view: RecyclerView, motionEvent: MotionEvent) {} - override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {} - - init { - mGestureDetector = GestureDetector(context, object : SimpleOnGestureListener() { - override fun onSingleTapUp(e: MotionEvent): Boolean { - return true - } - - override fun onLongPress(e: MotionEvent) { - val child = recyclerView.findChildViewUnder(e.x, e.y) - if (child != null && mListener != null) { - mListener.onLongItemClick(child, recyclerView.getChildAdapterPosition(child)) + /** + * Return the first visible non-ViewGroup View within the given ViewGroup, at the given coordinates + */ + private fun ViewGroup.getFirstVisibleViewByCoordinates(x: Float, y: Float): View? { + (childCount - 1 downTo 0) + .map { this.getChildAt(it) } + .forEach { + val bounds = Rect() + it.getHitRect(bounds) + if (bounds.contains(x.toInt(), y.toInt()) && VISIBLE == it.visibility) { + return if (it is ViewGroup) it.getFirstVisibleViewByCoordinates(x - bounds.left, y - bounds.top) + else it + } } - } - }) + return null } } } diff --git a/library-extensions-utils/src/main/java/com/mikepenz/fastadapter/swipe_drag/SimpleSwipeDrawerDragCallback.kt b/library-extensions-utils/src/main/java/com/mikepenz/fastadapter/swipe_drag/SimpleSwipeDrawerDragCallback.kt index b93f58874..8cb192d7f 100644 --- a/library-extensions-utils/src/main/java/com/mikepenz/fastadapter/swipe_drag/SimpleSwipeDrawerDragCallback.kt +++ b/library-extensions-utils/src/main/java/com/mikepenz/fastadapter/swipe_drag/SimpleSwipeDrawerDragCallback.kt @@ -34,8 +34,13 @@ class SimpleSwipeDrawerDragCallback @JvmOverloads constructor( return this } - fun withSwipeRight(): SimpleSwipeDrawerDragCallback { - simpleSwipeCallback.withSwipeRight() + fun withSwipeLeft(widthDp : Int): SimpleSwipeDrawerDragCallback { + simpleSwipeCallback.withSwipeLeft(widthDp) + return this + } + + fun withSwipeRight(widthDp : Int): SimpleSwipeDrawerDragCallback { + simpleSwipeCallback.withSwipeRight(widthDp) return this } From 3094afa12b31cea8c61458ac46d34a192a8103db Mon Sep 17 00:00:00 2001 From: Robb Date: Sat, 4 Jul 2020 19:27:25 +0200 Subject: [PATCH 3/6] Cleanup; fix left/right confusion --- .../app/SwipeDrawerListActivity.kt | 4 ++-- .../app/items/SwipeableDrawerItem.kt | 5 ++--- .../fastadapter/swipe/IDrawerSwipeable.kt | 11 ----------- .../swipe/IDrawerSwipeableViewHolder.kt | 11 +++++++++++ .../swipe/SimpleSwipeDrawerCallback.kt | 19 ++++++++++++------- 5 files changed, 27 insertions(+), 23 deletions(-) delete mode 100644 library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/IDrawerSwipeable.kt create mode 100644 library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/IDrawerSwipeableViewHolder.kt diff --git a/app/src/main/java/com/mikepenz/fastadapter/app/SwipeDrawerListActivity.kt b/app/src/main/java/com/mikepenz/fastadapter/app/SwipeDrawerListActivity.kt index 4c3a7bd4d..6d0b52546 100644 --- a/app/src/main/java/com/mikepenz/fastadapter/app/SwipeDrawerListActivity.kt +++ b/app/src/main/java/com/mikepenz/fastadapter/app/SwipeDrawerListActivity.kt @@ -110,8 +110,8 @@ class SwipeDrawerListActivity : AppCompatActivity(), ItemTouchCallback { this, ItemTouchHelper.LEFT) .withNotifyAllDrops(true) - .withSwipeLeft(160) // Width of archive and share buttons - .withSwipeRight(80) // Width of delete button + .withSwipeLeft(80) // Width of delete button + .withSwipeRight(160) // Width of archive and share buttons .withSensitivity(10f) touchHelper = ItemTouchHelper(touchCallback) // Create ItemTouchHelper and pass with parameter the SimpleDragCallback diff --git a/app/src/main/java/com/mikepenz/fastadapter/app/items/SwipeableDrawerItem.kt b/app/src/main/java/com/mikepenz/fastadapter/app/items/SwipeableDrawerItem.kt index 79cb2ea4a..36a072209 100644 --- a/app/src/main/java/com/mikepenz/fastadapter/app/items/SwipeableDrawerItem.kt +++ b/app/src/main/java/com/mikepenz/fastadapter/app/items/SwipeableDrawerItem.kt @@ -9,7 +9,7 @@ import com.mikepenz.fastadapter.app.R import com.mikepenz.fastadapter.app.adapters.IDraggableViewHolder import com.mikepenz.fastadapter.drag.IDraggable import com.mikepenz.fastadapter.items.AbstractItem -import com.mikepenz.fastadapter.swipe.IDrawerSwipeable +import com.mikepenz.fastadapter.swipe.IDrawerSwipeableViewHolder import com.mikepenz.fastadapter.swipe.ISwipeable import com.mikepenz.materialdrawer.holder.StringHolder import io.reactivex.functions.Consumer @@ -110,14 +110,13 @@ class SwipeableDrawerItem : AbstractItem(), ISwi /** * our ViewHolder */ - class ViewHolder(view: View) : RecyclerView.ViewHolder(view), IDraggableViewHolder, IDrawerSwipeable { + class ViewHolder(view: View) : RecyclerView.ViewHolder(view), IDraggableViewHolder, IDrawerSwipeableViewHolder { var name: TextView = view.findViewById(R.id.material_drawer_name) var description: TextView = view.findViewById(R.id.material_drawer_description) var archiveBtn: View = view.findViewById(R.id.archive_btn) var deleteBtn: View = view.findViewById(R.id.delete_btn) var shareBtn: View = view.findViewById(R.id.share_btn) var itemContent: View = view.findViewById(R.id.item_content) - var swipeResultContent: View = view.findViewById(R.id.swipe_result_content) var deleteActionRunnable: Runnable? = null var archiveActionRunnable: Runnable? = null diff --git a/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/IDrawerSwipeable.kt b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/IDrawerSwipeable.kt deleted file mode 100644 index bc427127f..000000000 --- a/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/IDrawerSwipeable.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.mikepenz.fastadapter.swipe - -import android.view.View - -/** - * Created by robb on 03.07.20. - */ -interface IDrawerSwipeable { - /** @return view to be swiped inside the ViewHolder */ - val swipeableView: View -} diff --git a/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/IDrawerSwipeableViewHolder.kt b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/IDrawerSwipeableViewHolder.kt new file mode 100644 index 000000000..f15ce1f64 --- /dev/null +++ b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/IDrawerSwipeableViewHolder.kt @@ -0,0 +1,11 @@ +package com.mikepenz.fastadapter.swipe + +import android.view.View + +/** + * Created by Robb on 03.07.20. + */ +interface IDrawerSwipeableViewHolder { + /** @return View that will move with the swipe gesture inside the ViewHolder */ + val swipeableView: View +} diff --git a/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt index c13de9001..5756552a3 100644 --- a/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt +++ b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt @@ -19,20 +19,26 @@ class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: // Swipe movement control private var sensitivityFactor = 1f - // "Drawer width" the swipe movement is allowed to reach before blocking - private var swipeWidthLeftDp = 80 - private var swipeWidthRightDp = 80 + // "Drawer width" swipe gesture is allowed to reach before blocking + private var swipeWidthLeftDp = 20 + private var swipeWidthRightDp = 20 // Indicates whether the touchTransmitter has been set on the RecyclerView private var touchTransmitterSet = false + /** + * Enable swipe to the left until the given width has been reached + */ fun withSwipeLeft(widthDp : Int): SimpleSwipeDrawerCallback { swipeWidthLeftDp = widthDp setDefaultSwipeDirs(swipeDirs or ItemTouchHelper.LEFT) return this } + /** + * Enable swipe to the right until the given width has been reached + */ fun withSwipeRight(widthDp : Int): SimpleSwipeDrawerCallback { swipeWidthRightDp = widthDp setDefaultSwipeDirs(swipeDirs or ItemTouchHelper.RIGHT) @@ -64,7 +70,7 @@ class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: } override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { - // Not enabled + // Not used } override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { @@ -72,7 +78,6 @@ class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: return false } - override fun getSwipeEscapeVelocity(defaultValue: Float): Float { return defaultValue * sensitivityFactor } @@ -90,12 +95,12 @@ class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: } if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) { - val isLeft = dX > 0 + val isLeft = dX < 0 var swipeWidthPc = recyclerView.context.resources.displayMetrics.density / itemView.width swipeWidthPc *= if (isLeft) swipeWidthLeftDp else swipeWidthRightDp var swipeableView = itemView - if (viewHolder is IDrawerSwipeable) swipeableView = viewHolder.swipeableView + if (viewHolder is IDrawerSwipeableViewHolder) swipeableView = viewHolder.swipeableView swipeableView.translationX = dX * swipeWidthPc } else super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive) From e17962fa33c4e1bf08789e98645da1f2f7497638 Mon Sep 17 00:00:00 2001 From: Robb Date: Sat, 4 Jul 2020 19:31:46 +0200 Subject: [PATCH 4/6] Simplify clicked child detection (no member RecyclerView) --- .../swipe/SimpleSwipeDrawerCallback.kt | 41 ++++++++----------- 1 file changed, 18 insertions(+), 23 deletions(-) diff --git a/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt index 5756552a3..8272ae3c9 100644 --- a/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt +++ b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt @@ -19,6 +19,7 @@ class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: // Swipe movement control private var sensitivityFactor = 1f + // "Drawer width" swipe gesture is allowed to reach before blocking private var swipeWidthLeftDp = 20 private var swipeWidthRightDp = 20 @@ -30,7 +31,7 @@ class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: /** * Enable swipe to the left until the given width has been reached */ - fun withSwipeLeft(widthDp : Int): SimpleSwipeDrawerCallback { + fun withSwipeLeft(widthDp: Int): SimpleSwipeDrawerCallback { swipeWidthLeftDp = widthDp setDefaultSwipeDirs(swipeDirs or ItemTouchHelper.LEFT) return this @@ -39,7 +40,7 @@ class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: /** * Enable swipe to the right until the given width has been reached */ - fun withSwipeRight(widthDp : Int): SimpleSwipeDrawerCallback { + fun withSwipeRight(widthDp: Int): SimpleSwipeDrawerCallback { swipeWidthRightDp = widthDp setDefaultSwipeDirs(swipeDirs or ItemTouchHelper.RIGHT) return this @@ -86,7 +87,7 @@ class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: val itemView = viewHolder.itemView if (!touchTransmitterSet) { - recyclerView.setOnTouchListener(RecyclerTouchTransmitter(recyclerView)) + recyclerView.setOnTouchListener(RecyclerTouchTransmitter()) touchTransmitterSet = true } @@ -112,29 +113,23 @@ class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: * Android default touch event mechanisms don't transmit these events to the sublayer : * any click on the exposed surface just swipe the item back to where it came */ - class RecyclerTouchTransmitter(rv: RecyclerView) : View.OnTouchListener { - - private val recyclerView = rv + class RecyclerTouchTransmitter() : View.OnTouchListener { override fun onTouch(v: View?, event: MotionEvent): Boolean { - // Get the clicked viewholder's root element - var childView = recyclerView.findChildViewUnder(event.x, event.y) - if (childView != null && childView is ViewGroup) { - // Get the first visible View under the clicked coordinates - val childViewOffset = Rect() - childView.getHitRect(childViewOffset) - childView = childView.getFirstVisibleViewByCoordinates(event.x - childViewOffset.left, event.y - childViewOffset.top) - // Transmit the ACTION_DOWN and ACTION_UP events to this View - if (childView != null) - when (event.actionMasked) { - MotionEvent.ACTION_DOWN -> { - childView.onTouchEvent(event) - } - MotionEvent.ACTION_UP -> { - childView.onTouchEvent(event) - } + if (null == v || v !is ViewGroup) return false + + // Get the first visible View under the clicked coordinates + val childView = v.getFirstVisibleViewByCoordinates(event.x, event.y) + // Transmit the ACTION_DOWN and ACTION_UP events to this View + if (childView != null) + when (event.actionMasked) { + MotionEvent.ACTION_DOWN -> { + childView.onTouchEvent(event) } - } + MotionEvent.ACTION_UP -> { + childView.onTouchEvent(event) + } + } return false } From db1b864009e2bf272c6e7037c73700e36f18f563 Mon Sep 17 00:00:00 2001 From: Robb Date: Sat, 4 Jul 2020 21:27:45 +0200 Subject: [PATCH 5/6] Remove empty constructor --- .../com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt index 8272ae3c9..499d25e55 100644 --- a/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt +++ b/library-extensions-swipe/src/main/java/com/mikepenz/fastadapter/swipe/SimpleSwipeDrawerCallback.kt @@ -113,7 +113,7 @@ class SimpleSwipeDrawerCallback @JvmOverloads constructor(private val swipeDirs: * Android default touch event mechanisms don't transmit these events to the sublayer : * any click on the exposed surface just swipe the item back to where it came */ - class RecyclerTouchTransmitter() : View.OnTouchListener { + class RecyclerTouchTransmitter : View.OnTouchListener { override fun onTouch(v: View?, event: MotionEvent): Boolean { if (null == v || v !is ViewGroup) return false From e42cae23663c3fd6101aa7a2b069f43fc03ed338 Mon Sep 17 00:00:00 2001 From: Robb Date: Sat, 4 Jul 2020 21:54:05 +0200 Subject: [PATCH 6/6] Comply with code beauty standards --- .../fastadapter/swipe_drag/SimpleSwipeDrawerDragCallback.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/library-extensions-utils/src/main/java/com/mikepenz/fastadapter/swipe_drag/SimpleSwipeDrawerDragCallback.kt b/library-extensions-utils/src/main/java/com/mikepenz/fastadapter/swipe_drag/SimpleSwipeDrawerDragCallback.kt index 8cb192d7f..9815a8d3c 100644 --- a/library-extensions-utils/src/main/java/com/mikepenz/fastadapter/swipe_drag/SimpleSwipeDrawerDragCallback.kt +++ b/library-extensions-utils/src/main/java/com/mikepenz/fastadapter/swipe_drag/SimpleSwipeDrawerDragCallback.kt @@ -34,12 +34,12 @@ class SimpleSwipeDrawerDragCallback @JvmOverloads constructor( return this } - fun withSwipeLeft(widthDp : Int): SimpleSwipeDrawerDragCallback { + fun withSwipeLeft(widthDp: Int): SimpleSwipeDrawerDragCallback { simpleSwipeCallback.withSwipeLeft(widthDp) return this } - fun withSwipeRight(widthDp : Int): SimpleSwipeDrawerDragCallback { + fun withSwipeRight(widthDp: Int): SimpleSwipeDrawerDragCallback { simpleSwipeCallback.withSwipeRight(widthDp) return this }