Refactor EasyRecyclerView
This commit is contained in:
1 parent
935c8e7d15
commit
9b3ffd1f2c
6 files changed
+147
-503
No files matched your search
@@ -1,470 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2015 Hippo Seven
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.hippo.ehviewer.ui.legacy
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcel
|
||||
import android.os.Parcelable
|
||||
import android.util.AttributeSet
|
||||
import android.util.SparseBooleanArray
|
||||
import android.view.View
|
||||
import android.widget.Checkable
|
||||
import androidx.collection.LongSparseArray
|
||||
import androidx.core.os.ParcelCompat.readParcelable
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
|
||||
class EasyRecyclerView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0,
|
||||
) : RecyclerView(context, attrs, defStyle) {
|
||||
// Fling friction
|
||||
private var pageScrollThreshold = 0f
|
||||
|
||||
/**
|
||||
* Controls if/how the user may choose/check items in the list
|
||||
*/
|
||||
private var mChoiceMode = CHOICE_MODE_NONE
|
||||
|
||||
/**
|
||||
* Listener for custom multiple choices
|
||||
*/
|
||||
private var mCustomChoiceListener: CustomChoiceListener? = null
|
||||
var isInCustomChoice = false
|
||||
private set
|
||||
|
||||
/**
|
||||
* A lock, avoid OutOfCustomChoiceMode when doing OutOfCustomChoiceMode
|
||||
*/
|
||||
private var mOutOfCustomChoiceModing = false
|
||||
private var mTempCheckStates: SparseBooleanArray? = null
|
||||
|
||||
/**
|
||||
* Running count of how many items are currently checked
|
||||
*/
|
||||
var checkedItemCount = 0
|
||||
private set
|
||||
|
||||
/**
|
||||
* Running state of which positions are currently checked
|
||||
*/
|
||||
private var mCheckStates: SparseBooleanArray? = null
|
||||
|
||||
/**
|
||||
* Running state of which IDs are currently checked.
|
||||
* If there is a value for a given key, the checked state for that ID is true
|
||||
* and the value holds the last known position in the adapter for that id.
|
||||
*/
|
||||
private var mCheckedIdStates: LongSparseArray<Int>? = null
|
||||
private var mAdapter: Adapter<*>? = null
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
override fun setAdapter(adapter: Adapter<*>?) {
|
||||
super.setAdapter(adapter)
|
||||
mAdapter = adapter
|
||||
if (adapter != null) {
|
||||
if (mChoiceMode != CHOICE_MODE_NONE && adapter.hasStableIds() && mCheckedIdStates == null) {
|
||||
mCheckedIdStates = LongSparseArray()
|
||||
}
|
||||
}
|
||||
if (mCheckStates != null) {
|
||||
mCheckStates!!.clear()
|
||||
}
|
||||
if (mCheckedIdStates != null) {
|
||||
mCheckedIdStates!!.clear()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onChildAttachedToWindow(child: View) {
|
||||
super.onChildAttachedToWindow(child)
|
||||
if (mCheckStates != null) {
|
||||
val position = getChildAdapterPosition(child)
|
||||
if (position >= 0) {
|
||||
setViewChecked(child, mCheckStates!![position])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the checked state of the specified position. The result is only
|
||||
* valid if the choice mode has been set to [.CHOICE_MODE_SINGLE]
|
||||
* or [.CHOICE_MODE_MULTIPLE].
|
||||
*
|
||||
* @param position The item whose checked state to return
|
||||
* @return The item's checked state or `false` if choice mode
|
||||
* is invalid
|
||||
* @see .setChoiceMode
|
||||
*/
|
||||
fun isItemChecked(position: Int): Boolean {
|
||||
return if (mChoiceMode != CHOICE_MODE_NONE && mCheckStates != null) {
|
||||
mCheckStates!![position]
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
val checkedItemPositions: SparseBooleanArray?
|
||||
/**
|
||||
* Returns the set of checked items in the list. The result is only valid if
|
||||
* the choice mode has not been set to [.CHOICE_MODE_NONE].
|
||||
*
|
||||
* @return A SparseBooleanArray which will return true for each call to
|
||||
* get(int position) where position is a checked position in the
|
||||
* list and false otherwise, or `null` if the choice
|
||||
* mode is set to [.CHOICE_MODE_NONE].
|
||||
*/
|
||||
get() = if (mChoiceMode != CHOICE_MODE_NONE) {
|
||||
mCheckStates
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
fun intoCustomChoiceMode() {
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM && !isInCustomChoice) {
|
||||
isInCustomChoice = true
|
||||
mCustomChoiceListener!!.onIntoCustomChoice(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun outOfCustomChoiceMode() {
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM && isInCustomChoice && !mOutOfCustomChoiceModing) {
|
||||
mOutOfCustomChoiceModing = true
|
||||
|
||||
// Copy mCheckStates
|
||||
mTempCheckStates!!.clear()
|
||||
run {
|
||||
for (i in 0 until mCheckStates!!.size()) {
|
||||
mTempCheckStates!!.put(mCheckStates!!.keyAt(i), mCheckStates!!.valueAt(i))
|
||||
}
|
||||
}
|
||||
// Uncheck remain checked items
|
||||
for (i in 0 until mTempCheckStates!!.size()) {
|
||||
if (mTempCheckStates!!.valueAt(i)) {
|
||||
setItemChecked(mTempCheckStates!!.keyAt(i), false)
|
||||
}
|
||||
}
|
||||
isInCustomChoice = false
|
||||
mCustomChoiceListener!!.onOutOfCustomChoice(this)
|
||||
mOutOfCustomChoiceModing = false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear any choices previously set
|
||||
*/
|
||||
private fun clearChoices() {
|
||||
if (mCheckStates != null) {
|
||||
mCheckStates!!.clear()
|
||||
}
|
||||
if (mCheckedIdStates != null) {
|
||||
mCheckedIdStates!!.clear()
|
||||
}
|
||||
checkedItemCount = 0
|
||||
updateOnScreenCheckedViews()
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets all items checked.
|
||||
*/
|
||||
fun checkAll() {
|
||||
if (mChoiceMode == CHOICE_MODE_NONE) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check is intoCheckMode
|
||||
check(!(mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM && !isInCustomChoice)) { "Call intoCheckMode first" }
|
||||
|
||||
for (i in 0 until mAdapter!!.itemCount) {
|
||||
val oldValue = mCheckStates!![i]
|
||||
mCheckStates!!.put(i, true)
|
||||
if (mCheckedIdStates != null && mAdapter!!.hasStableIds()) {
|
||||
mCheckedIdStates!!.put(mAdapter!!.getItemId(i), i)
|
||||
}
|
||||
if (!oldValue) {
|
||||
checkedItemCount++
|
||||
}
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM) {
|
||||
val id = mAdapter!!.getItemId(i)
|
||||
mCustomChoiceListener!!.onItemCheckedStateChanged(this, i, id, true)
|
||||
}
|
||||
}
|
||||
updateOnScreenCheckedViews()
|
||||
}
|
||||
|
||||
fun toggleItemChecked(position: Int) {
|
||||
if (mCheckStates != null) {
|
||||
setItemChecked(position, !mCheckStates!![position])
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the checked state of the specified position. The is only valid if
|
||||
* the choice mode has been set to [.CHOICE_MODE_SINGLE] or
|
||||
* [.CHOICE_MODE_MULTIPLE].
|
||||
*
|
||||
* @param position The item whose checked state is to be checked
|
||||
* @param value The new checked state for the item
|
||||
*/
|
||||
fun setItemChecked(position: Int, value: Boolean) {
|
||||
if (mChoiceMode == CHOICE_MODE_NONE) {
|
||||
return
|
||||
}
|
||||
|
||||
// Check is intoCheckMode
|
||||
check(!(mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM && !isInCustomChoice)) { "Call intoCheckMode first" }
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM) {
|
||||
val oldValue = mCheckStates!![position]
|
||||
mCheckStates!!.put(position, value)
|
||||
if (mCheckedIdStates != null && mAdapter!!.hasStableIds()) {
|
||||
if (value) {
|
||||
mCheckedIdStates!!.put(mAdapter!!.getItemId(position), position)
|
||||
} else {
|
||||
mCheckedIdStates!!.remove(mAdapter!!.getItemId(position))
|
||||
}
|
||||
}
|
||||
if (oldValue != value) {
|
||||
if (value) {
|
||||
checkedItemCount++
|
||||
} else {
|
||||
checkedItemCount--
|
||||
}
|
||||
}
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM) {
|
||||
val id = mAdapter!!.getItemId(position)
|
||||
mCustomChoiceListener!!.onItemCheckedStateChanged(this, position, id, value)
|
||||
}
|
||||
} else {
|
||||
val updateIds = mCheckedIdStates != null && mAdapter!!.hasStableIds()
|
||||
// Clear all values if we're checking something, or unchecking the currently
|
||||
// selected item
|
||||
if (value || isItemChecked(position)) {
|
||||
mCheckStates!!.clear()
|
||||
if (updateIds) {
|
||||
mCheckedIdStates!!.clear()
|
||||
}
|
||||
}
|
||||
// this may end up selecting the value we just cleared but this way
|
||||
// we ensure length of mCheckStates is 1, a fact getCheckedItemPosition relies on
|
||||
if (value) {
|
||||
mCheckStates!!.put(position, true)
|
||||
if (updateIds) {
|
||||
mCheckedIdStates!!.put(mAdapter!!.getItemId(position), position)
|
||||
}
|
||||
checkedItemCount = 1
|
||||
} else if (mCheckStates!!.size() == 0 || !mCheckStates!!.valueAt(0)) {
|
||||
checkedItemCount = 0
|
||||
}
|
||||
}
|
||||
updateOnScreenCheckedViews()
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines the choice behavior for the List. By default, Lists do not have any choice behavior
|
||||
* ([.CHOICE_MODE_NONE]). By setting the choiceMode to [.CHOICE_MODE_SINGLE], the
|
||||
* List allows up to one item to be in a chosen state. By setting the choiceMode to
|
||||
* [.CHOICE_MODE_MULTIPLE], the list allows any number of items to be chosen.
|
||||
*
|
||||
* @param choiceMode One of [.CHOICE_MODE_NONE], [.CHOICE_MODE_SINGLE], or
|
||||
* [.CHOICE_MODE_MULTIPLE]
|
||||
*/
|
||||
fun setChoiceMode(choiceMode: Int) {
|
||||
mChoiceMode = choiceMode
|
||||
if (mChoiceMode != CHOICE_MODE_NONE) {
|
||||
if (mCheckStates == null) {
|
||||
mCheckStates = SparseBooleanArray(0)
|
||||
}
|
||||
if (mCheckedIdStates == null && mAdapter != null && mAdapter!!.hasStableIds()) {
|
||||
mCheckedIdStates = LongSparseArray(0)
|
||||
}
|
||||
if (mChoiceMode == CHOICE_MODE_MULTIPLE_CUSTOM) {
|
||||
if (mTempCheckStates == null) {
|
||||
mTempCheckStates = SparseBooleanArray(0)
|
||||
}
|
||||
clearChoices()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setCustomCheckedListener(listener: CustomChoiceListener?) {
|
||||
mCustomChoiceListener = listener
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform a quick, in-place update of the checked or activated state
|
||||
* on all visible item views. This should only be called when a valid
|
||||
* choice mode is active.
|
||||
*/
|
||||
private fun updateOnScreenCheckedViews() {
|
||||
val count = childCount
|
||||
for (i in 0 until count) {
|
||||
val child = getChildAt(i)
|
||||
val position = getChildAdapterPosition(child)
|
||||
setViewChecked(child, mCheckStates!![position])
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
val scale = context.resources.displayMetrics.density
|
||||
// g (m/s^2)
|
||||
// inch/meter
|
||||
// A context-specific coefficient adjusted to physical values.
|
||||
// look and feel tuning
|
||||
pageScrollThreshold = scale * 80
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(): Parcelable {
|
||||
val ss = SavedState(super.onSaveInstanceState())
|
||||
ss.choiceMode = mChoiceMode
|
||||
ss.customChoice = isInCustomChoice
|
||||
ss.checkedItemCount = checkedItemCount
|
||||
ss.checkState = mCheckStates
|
||||
ss.checkIdState = mCheckedIdStates
|
||||
return ss
|
||||
}
|
||||
|
||||
override fun onRestoreInstanceState(state: Parcelable) {
|
||||
val ss = state as SavedState
|
||||
super.onRestoreInstanceState(ss.superState)
|
||||
setChoiceMode(ss.choiceMode)
|
||||
isInCustomChoice = ss.customChoice
|
||||
checkedItemCount = ss.checkedItemCount
|
||||
if (ss.checkState != null) {
|
||||
mCheckStates = ss.checkState
|
||||
}
|
||||
if (ss.checkIdState != null) {
|
||||
mCheckedIdStates = ss.checkIdState
|
||||
}
|
||||
updateOnScreenCheckedViews()
|
||||
}
|
||||
|
||||
/**
|
||||
* Custom checked
|
||||
*/
|
||||
interface CustomChoiceListener {
|
||||
fun onIntoCustomChoice(view: EasyRecyclerView)
|
||||
fun onOutOfCustomChoice(view: EasyRecyclerView)
|
||||
fun onItemCheckedStateChanged(
|
||||
view: EasyRecyclerView,
|
||||
position: Int,
|
||||
id: Long,
|
||||
checked: Boolean,
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* This saved state class is a Parcelable and should not extend
|
||||
* [android.view.View.BaseSavedState] nor [android.view.AbsSavedState]
|
||||
* because its super class AbsSavedState's constructor
|
||||
* currently passes null
|
||||
* as a class loader to read its superstate from Parcelable.
|
||||
* This causes [android.os.BadParcelableException] when restoring saved states.
|
||||
*
|
||||
*
|
||||
* The super class "RecyclerView" is a part of the support library,
|
||||
* and restoring its saved state requires the class loader that loaded the RecyclerView.
|
||||
* It seems that the class loader is not required when restoring from RecyclerView itself,
|
||||
* but it is required when restoring from RecyclerView's subclasses.
|
||||
*/
|
||||
internal open class SavedState : Parcelable {
|
||||
var choiceMode = 0
|
||||
var customChoice = false
|
||||
var checkedItemCount = 0
|
||||
var checkState: SparseBooleanArray? = null
|
||||
var checkIdState: LongSparseArray<Int>? = null
|
||||
|
||||
// This keeps the parent(RecyclerView)'s state
|
||||
var superState: Parcelable?
|
||||
|
||||
constructor() {
|
||||
superState = null
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor called from [.onSaveInstanceState]
|
||||
*/
|
||||
constructor(superState: Parcelable?) {
|
||||
this.superState = if (superState !== EMPTY_STATE) superState else null
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructor called from [.CREATOR]
|
||||
*/
|
||||
private constructor(`in`: Parcel) {
|
||||
// Parcel 'in' has its parent(RecyclerView)'s saved state.
|
||||
// To restore it, class loader that loaded RecyclerView is required.
|
||||
val superState =
|
||||
readParcelable(`in`, RecyclerView::class.java.classLoader, Parcelable::class.java)
|
||||
this.superState = superState ?: EMPTY_STATE
|
||||
choiceMode = `in`.readInt()
|
||||
customChoice = `in`.readInt() != 0
|
||||
checkedItemCount = `in`.readInt()
|
||||
checkState = `in`.readSparseBooleanArray()
|
||||
val n = `in`.readInt()
|
||||
if (n > 0) {
|
||||
checkIdState = LongSparseArray()
|
||||
for (i in 0 until n) {
|
||||
val key = `in`.readLong()
|
||||
val value = `in`.readInt()
|
||||
checkIdState!!.put(key, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun describeContents(): Int {
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun writeToParcel(out: Parcel, flags: Int) {
|
||||
out.writeParcelable(superState, flags)
|
||||
out.writeInt(choiceMode)
|
||||
out.writeInt(customChoice.compareTo(false))
|
||||
out.writeInt(checkedItemCount)
|
||||
out.writeSparseBooleanArray(checkState)
|
||||
val n = if (checkIdState != null) checkIdState!!.size() else 0
|
||||
out.writeInt(n)
|
||||
for (i in 0 until n) {
|
||||
out.writeLong(checkIdState!!.keyAt(i))
|
||||
out.writeInt(checkIdState!!.valueAt(i))
|
||||
}
|
||||
}
|
||||
|
||||
companion object CREATOR : Parcelable.Creator<SavedState> {
|
||||
private val EMPTY_STATE = SavedState()
|
||||
|
||||
override fun createFromParcel(parcel: Parcel): SavedState {
|
||||
return SavedState(parcel)
|
||||
}
|
||||
|
||||
override fun newArray(size: Int): Array<SavedState?> {
|
||||
return arrayOfNulls(size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val CHOICE_MODE_NONE = 0
|
||||
const val CHOICE_MODE_MULTIPLE_CUSTOM = 1
|
||||
fun setViewChecked(view: View, checked: Boolean) {
|
||||
if (view is Checkable) {
|
||||
(view as Checkable).isChecked = checked
|
||||
} else {
|
||||
view.isActivated = checked
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,125 @@
|
||||
package com.hippo.ehviewer.ui.legacy
|
||||
|
||||
import android.content.Context
|
||||
import android.os.Parcelable
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.widget.Checkable
|
||||
import androidx.core.view.children
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
class SelectableGalleryInfoRecyclerView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyle: Int = 0,
|
||||
) : RecyclerView(context, attrs, defStyle) {
|
||||
private var customChoiceListener: CustomChoiceListener? = null
|
||||
private var mOutOfCustomChoiceModing = false
|
||||
var isInCustomChoice = false
|
||||
private set
|
||||
val checkedItemCount
|
||||
get() = checkedGid.size
|
||||
lateinit var gidGetter: (Int) -> Long?
|
||||
private var checkedGid = mutableSetOf<Long>()
|
||||
|
||||
private fun checkChild(child: View) {
|
||||
val position = getChildAdapterPosition(child)
|
||||
val gid = gidGetter(position) ?: return
|
||||
setViewChecked(child, gid in checkedGid)
|
||||
}
|
||||
|
||||
override fun onChildAttachedToWindow(child: View) {
|
||||
super.onChildAttachedToWindow(child)
|
||||
checkChild(child)
|
||||
}
|
||||
|
||||
val checkedItemGid: Set<Long>
|
||||
get() = checkedGid
|
||||
|
||||
fun intoCustomChoiceMode() {
|
||||
if (!isInCustomChoice) {
|
||||
isInCustomChoice = true
|
||||
customChoiceListener?.onIntoCustomChoice(this)
|
||||
}
|
||||
}
|
||||
|
||||
fun outOfCustomChoiceMode() {
|
||||
if (isInCustomChoice && !mOutOfCustomChoiceModing) {
|
||||
mOutOfCustomChoiceModing = true
|
||||
clearChoices()
|
||||
isInCustomChoice = false
|
||||
customChoiceListener?.onOutOfCustomChoice(this)
|
||||
mOutOfCustomChoiceModing = false
|
||||
}
|
||||
}
|
||||
|
||||
private fun clearChoices() {
|
||||
checkedGid.clear()
|
||||
updateOnScreenCheckedViews()
|
||||
}
|
||||
|
||||
fun checkAll() {
|
||||
check(isInCustomChoice) { "Call intoCheckMode first" }
|
||||
// Foreach checked
|
||||
// Notify customChoiceListener!!.onItemCheckedStateChanged(this)
|
||||
updateOnScreenCheckedViews()
|
||||
}
|
||||
|
||||
fun toggleItemChecked(position: Int) {
|
||||
check(isInCustomChoice) { "Call intoCheckMode first" }
|
||||
val gid = gidGetter(position) ?: return
|
||||
if (gid in checkedGid) {
|
||||
checkedGid.remove(gid)
|
||||
} else {
|
||||
checkedGid.add(gid)
|
||||
}
|
||||
customChoiceListener?.onItemCheckedStateChanged(this)
|
||||
updateOnScreenCheckedViews()
|
||||
}
|
||||
|
||||
fun setCustomCheckedListener(listener: CustomChoiceListener?) {
|
||||
customChoiceListener = listener
|
||||
}
|
||||
|
||||
private fun updateOnScreenCheckedViews() {
|
||||
children.forEach {
|
||||
checkChild(it)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState() = SavedState(
|
||||
isInCustomChoice,
|
||||
checkedGid,
|
||||
super.onSaveInstanceState(),
|
||||
)
|
||||
|
||||
override fun onRestoreInstanceState(state: Parcelable) {
|
||||
val ss = state as SavedState
|
||||
super.onRestoreInstanceState(ss.superState)
|
||||
isInCustomChoice = ss.customChoice
|
||||
checkedGid = ss.checkedGid
|
||||
updateOnScreenCheckedViews()
|
||||
}
|
||||
|
||||
interface CustomChoiceListener {
|
||||
fun onIntoCustomChoice(view: SelectableGalleryInfoRecyclerView)
|
||||
fun onOutOfCustomChoice(view: SelectableGalleryInfoRecyclerView)
|
||||
fun onItemCheckedStateChanged(view: SelectableGalleryInfoRecyclerView)
|
||||
}
|
||||
|
||||
@Parcelize
|
||||
class SavedState(
|
||||
val customChoice: Boolean,
|
||||
val checkedGid: MutableSet<Long>,
|
||||
val superState: Parcelable?,
|
||||
) : Parcelable
|
||||
}
|
||||
|
||||
private fun setViewChecked(view: View, checked: Boolean) {
|
||||
if (view is Checkable) {
|
||||
(view as Checkable).isChecked = checked
|
||||
} else {
|
||||
view.isActivated = checked
|
||||
}
|
||||
}
|
||||
@@ -79,8 +79,6 @@ import com.hippo.ehviewer.spider.putToDownloadDir
|
||||
import com.hippo.ehviewer.ui.legacy.AutoStaggeredGridLayoutManager
|
||||
import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
|
||||
import com.hippo.ehviewer.ui.legacy.CheckBoxDialogBuilder
|
||||
import com.hippo.ehviewer.ui.legacy.EasyRecyclerView
|
||||
import com.hippo.ehviewer.ui.legacy.EasyRecyclerView.CustomChoiceListener
|
||||
import com.hippo.ehviewer.ui.legacy.EditTextDialogBuilder
|
||||
import com.hippo.ehviewer.ui.legacy.FabLayout
|
||||
import com.hippo.ehviewer.ui.legacy.FabLayout.OnClickFabListener
|
||||
@@ -88,6 +86,8 @@ import com.hippo.ehviewer.ui.legacy.FabLayout.OnExpandListener
|
||||
import com.hippo.ehviewer.ui.legacy.FastScroller.OnDragHandlerListener
|
||||
import com.hippo.ehviewer.ui.legacy.HandlerDrawable
|
||||
import com.hippo.ehviewer.ui.legacy.STRATEGY_MIN_SIZE
|
||||
import com.hippo.ehviewer.ui.legacy.SelectableGalleryInfoRecyclerView
|
||||
import com.hippo.ehviewer.ui.legacy.SelectableGalleryInfoRecyclerView.CustomChoiceListener
|
||||
import com.hippo.ehviewer.ui.legacy.ViewTransition
|
||||
import com.hippo.ehviewer.ui.main.requestOf
|
||||
import com.hippo.ehviewer.ui.navToReader
|
||||
@@ -277,7 +277,7 @@ class DownloadsScene :
|
||||
recyclerView.layoutManager = layoutManager
|
||||
recyclerView.clipToPadding = false
|
||||
recyclerView.clipChildren = false
|
||||
recyclerView.setChoiceMode(EasyRecyclerView.CHOICE_MODE_MULTIPLE_CUSTOM)
|
||||
recyclerView.gidGetter = { mList?.get(it)?.gid }
|
||||
recyclerView.setCustomCheckedListener(DownloadChoiceListener())
|
||||
// Cancel change animation
|
||||
val itemAnimator = recyclerView.itemAnimator
|
||||
@@ -566,16 +566,12 @@ class DownloadsScene :
|
||||
if (collectDownloadInfo) {
|
||||
downloadInfoList = LinkedList()
|
||||
}
|
||||
val stateArray = binding.recyclerView.checkedItemPositions!!
|
||||
for (i in 0 until stateArray.size()) {
|
||||
if (stateArray.valueAt(i)) {
|
||||
val info = list[stateArray.keyAt(i)]
|
||||
if (collectDownloadInfo) {
|
||||
downloadInfoList!!.add(info)
|
||||
}
|
||||
if (collectGid) {
|
||||
gidList!!.add(info.gid)
|
||||
}
|
||||
binding.recyclerView.checkedItemGid.mapNotNull { gid -> list.find { it.gid == gid } }.forEach {
|
||||
if (collectDownloadInfo) {
|
||||
downloadInfoList!!.add(it)
|
||||
}
|
||||
if (collectGid) {
|
||||
gidList!!.add(it.gid)
|
||||
}
|
||||
}
|
||||
when (position) {
|
||||
@@ -1050,26 +1046,21 @@ class DownloadsScene :
|
||||
}
|
||||
|
||||
private inner class DownloadChoiceListener : CustomChoiceListener {
|
||||
override fun onIntoCustomChoice(view: EasyRecyclerView) {
|
||||
override fun onIntoCustomChoice(view: SelectableGalleryInfoRecyclerView) {
|
||||
binding.fabLayout.isExpanded = true
|
||||
// Lock drawer
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START)
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END)
|
||||
}
|
||||
|
||||
override fun onOutOfCustomChoice(view: EasyRecyclerView) {
|
||||
override fun onOutOfCustomChoice(view: SelectableGalleryInfoRecyclerView) {
|
||||
binding.fabLayout.isExpanded = false
|
||||
// Unlock drawer
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START)
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END)
|
||||
}
|
||||
|
||||
override fun onItemCheckedStateChanged(
|
||||
view: EasyRecyclerView,
|
||||
position: Int,
|
||||
id: Long,
|
||||
checked: Boolean,
|
||||
) {
|
||||
override fun onItemCheckedStateChanged(view: SelectableGalleryInfoRecyclerView) {
|
||||
if (view.checkedItemCount == 0) {
|
||||
view.outOfCustomChoiceMode()
|
||||
}
|
||||
|
||||
@@ -37,7 +37,6 @@ import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.core.util.valueIterator
|
||||
import androidx.core.view.GravityCompat
|
||||
import androidx.core.view.ViewCompat
|
||||
import androidx.core.view.WindowInsetsAnimationCompat
|
||||
@@ -72,12 +71,12 @@ import com.hippo.ehviewer.databinding.SceneFavoritesBinding
|
||||
import com.hippo.ehviewer.ui.CommonOperations
|
||||
import com.hippo.ehviewer.ui.legacy.AddDeleteDrawable
|
||||
import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
|
||||
import com.hippo.ehviewer.ui.legacy.EasyRecyclerView
|
||||
import com.hippo.ehviewer.ui.legacy.EasyRecyclerView.CustomChoiceListener
|
||||
import com.hippo.ehviewer.ui.legacy.FabLayout
|
||||
import com.hippo.ehviewer.ui.legacy.FabLayout.OnClickFabListener
|
||||
import com.hippo.ehviewer.ui.legacy.FastScroller.OnDragHandlerListener
|
||||
import com.hippo.ehviewer.ui.legacy.HandlerDrawable
|
||||
import com.hippo.ehviewer.ui.legacy.SelectableGalleryInfoRecyclerView
|
||||
import com.hippo.ehviewer.ui.legacy.SelectableGalleryInfoRecyclerView.CustomChoiceListener
|
||||
import com.hippo.ehviewer.ui.legacy.ViewTransition
|
||||
import com.hippo.ehviewer.ui.legacy.WindowInsetsAnimationHelper
|
||||
import com.hippo.ehviewer.ui.setMD3Content
|
||||
@@ -354,11 +353,11 @@ class FavoritesScene : SearchBarScene() {
|
||||
}
|
||||
}
|
||||
}
|
||||
gidGetter = { adapter.peek(it)?.gid }
|
||||
}
|
||||
switchFav(Settings.recentFavCat)
|
||||
setChoiceMode(EasyRecyclerView.CHOICE_MODE_MULTIPLE_CUSTOM)
|
||||
setCustomCheckedListener(object : CustomChoiceListener {
|
||||
override fun onIntoCustomChoice(view: EasyRecyclerView) {
|
||||
override fun onIntoCustomChoice(view: SelectableGalleryInfoRecyclerView) {
|
||||
showSelectionFab()
|
||||
binding.fabLayout.setAutoCancel(false)
|
||||
// Delay expanding action to make layout work fine
|
||||
@@ -369,7 +368,7 @@ class FavoritesScene : SearchBarScene() {
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END)
|
||||
}
|
||||
|
||||
override fun onOutOfCustomChoice(view: EasyRecyclerView) {
|
||||
override fun onOutOfCustomChoice(view: SelectableGalleryInfoRecyclerView) {
|
||||
showNormalFab()
|
||||
binding.fabLayout.setAutoCancel(true)
|
||||
binding.fabLayout.isExpanded = false
|
||||
@@ -379,7 +378,7 @@ class FavoritesScene : SearchBarScene() {
|
||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END)
|
||||
}
|
||||
|
||||
override fun onItemCheckedStateChanged(view: EasyRecyclerView, position: Int, id: Long, checked: Boolean) {
|
||||
override fun onItemCheckedStateChanged(view: SelectableGalleryInfoRecyclerView) {
|
||||
if (view.checkedItemCount == 0) {
|
||||
view.outOfCustomChoiceMode()
|
||||
}
|
||||
@@ -470,9 +469,8 @@ class FavoritesScene : SearchBarScene() {
|
||||
}
|
||||
|
||||
private fun takeCheckedInfo() = run {
|
||||
val stateArray = binding.recyclerView.checkedItemPositions!!
|
||||
stateArray.valueIterator().asSequence().withIndex().filter { it.value }
|
||||
.mapNotNull { mAdapter?.peek(stateArray.keyAt(it.index)) }.toList()
|
||||
val snapshot = mAdapter?.snapshot()?.items
|
||||
binding.recyclerView.checkedItemGid.mapNotNull { gid -> snapshot?.find { it.gid == gid } }
|
||||
}.also { binding.recyclerView.outOfCustomChoiceMode() }
|
||||
|
||||
private fun checkedSize() = binding.recyclerView.checkedItemCount
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
||||
|
||||
<com.hippo.ehviewer.ui.legacy.EasyRecyclerView
|
||||
<com.hippo.ehviewer.ui.legacy.SelectableGalleryInfoRecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
@@ -42,7 +42,7 @@
|
||||
app:fitsSystemWindowsInsets="top"
|
||||
app:layout_behavior="@string/searchbar_scrolling_view_behavior">
|
||||
|
||||
<com.hippo.ehviewer.ui.legacy.EasyRecyclerView
|
||||
<com.hippo.ehviewer.ui.legacy.SelectableGalleryInfoRecyclerView
|
||||
android:id="@+id/recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
||||
Reference in new issue
Block a user