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.AutoStaggeredGridLayoutManager
|
||||||
import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
|
import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
|
||||||
import com.hippo.ehviewer.ui.legacy.CheckBoxDialogBuilder
|
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.EditTextDialogBuilder
|
||||||
import com.hippo.ehviewer.ui.legacy.FabLayout
|
import com.hippo.ehviewer.ui.legacy.FabLayout
|
||||||
import com.hippo.ehviewer.ui.legacy.FabLayout.OnClickFabListener
|
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.FastScroller.OnDragHandlerListener
|
||||||
import com.hippo.ehviewer.ui.legacy.HandlerDrawable
|
import com.hippo.ehviewer.ui.legacy.HandlerDrawable
|
||||||
import com.hippo.ehviewer.ui.legacy.STRATEGY_MIN_SIZE
|
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.legacy.ViewTransition
|
||||||
import com.hippo.ehviewer.ui.main.requestOf
|
import com.hippo.ehviewer.ui.main.requestOf
|
||||||
import com.hippo.ehviewer.ui.navToReader
|
import com.hippo.ehviewer.ui.navToReader
|
||||||
@@ -277,7 +277,7 @@ class DownloadsScene :
|
|||||||
recyclerView.layoutManager = layoutManager
|
recyclerView.layoutManager = layoutManager
|
||||||
recyclerView.clipToPadding = false
|
recyclerView.clipToPadding = false
|
||||||
recyclerView.clipChildren = false
|
recyclerView.clipChildren = false
|
||||||
recyclerView.setChoiceMode(EasyRecyclerView.CHOICE_MODE_MULTIPLE_CUSTOM)
|
recyclerView.gidGetter = { mList?.get(it)?.gid }
|
||||||
recyclerView.setCustomCheckedListener(DownloadChoiceListener())
|
recyclerView.setCustomCheckedListener(DownloadChoiceListener())
|
||||||
// Cancel change animation
|
// Cancel change animation
|
||||||
val itemAnimator = recyclerView.itemAnimator
|
val itemAnimator = recyclerView.itemAnimator
|
||||||
@@ -566,16 +566,12 @@ class DownloadsScene :
|
|||||||
if (collectDownloadInfo) {
|
if (collectDownloadInfo) {
|
||||||
downloadInfoList = LinkedList()
|
downloadInfoList = LinkedList()
|
||||||
}
|
}
|
||||||
val stateArray = binding.recyclerView.checkedItemPositions!!
|
binding.recyclerView.checkedItemGid.mapNotNull { gid -> list.find { it.gid == gid } }.forEach {
|
||||||
for (i in 0 until stateArray.size()) {
|
if (collectDownloadInfo) {
|
||||||
if (stateArray.valueAt(i)) {
|
downloadInfoList!!.add(it)
|
||||||
val info = list[stateArray.keyAt(i)]
|
}
|
||||||
if (collectDownloadInfo) {
|
if (collectGid) {
|
||||||
downloadInfoList!!.add(info)
|
gidList!!.add(it.gid)
|
||||||
}
|
|
||||||
if (collectGid) {
|
|
||||||
gidList!!.add(info.gid)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
when (position) {
|
when (position) {
|
||||||
@@ -1050,26 +1046,21 @@ class DownloadsScene :
|
|||||||
}
|
}
|
||||||
|
|
||||||
private inner class DownloadChoiceListener : CustomChoiceListener {
|
private inner class DownloadChoiceListener : CustomChoiceListener {
|
||||||
override fun onIntoCustomChoice(view: EasyRecyclerView) {
|
override fun onIntoCustomChoice(view: SelectableGalleryInfoRecyclerView) {
|
||||||
binding.fabLayout.isExpanded = true
|
binding.fabLayout.isExpanded = true
|
||||||
// Lock drawer
|
// Lock drawer
|
||||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START)
|
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START)
|
||||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END)
|
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOutOfCustomChoice(view: EasyRecyclerView) {
|
override fun onOutOfCustomChoice(view: SelectableGalleryInfoRecyclerView) {
|
||||||
binding.fabLayout.isExpanded = false
|
binding.fabLayout.isExpanded = false
|
||||||
// Unlock drawer
|
// Unlock drawer
|
||||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START)
|
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.START)
|
||||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END)
|
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onItemCheckedStateChanged(
|
override fun onItemCheckedStateChanged(view: SelectableGalleryInfoRecyclerView) {
|
||||||
view: EasyRecyclerView,
|
|
||||||
position: Int,
|
|
||||||
id: Long,
|
|
||||||
checked: Boolean,
|
|
||||||
) {
|
|
||||||
if (view.checkedItemCount == 0) {
|
if (view.checkedItemCount == 0) {
|
||||||
view.outOfCustomChoiceMode()
|
view.outOfCustomChoiceMode()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,7 +37,6 @@ import androidx.compose.ui.platform.ComposeView
|
|||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.core.os.bundleOf
|
import androidx.core.os.bundleOf
|
||||||
import androidx.core.util.valueIterator
|
|
||||||
import androidx.core.view.GravityCompat
|
import androidx.core.view.GravityCompat
|
||||||
import androidx.core.view.ViewCompat
|
import androidx.core.view.ViewCompat
|
||||||
import androidx.core.view.WindowInsetsAnimationCompat
|
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.CommonOperations
|
||||||
import com.hippo.ehviewer.ui.legacy.AddDeleteDrawable
|
import com.hippo.ehviewer.ui.legacy.AddDeleteDrawable
|
||||||
import com.hippo.ehviewer.ui.legacy.BaseDialogBuilder
|
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
|
||||||
import com.hippo.ehviewer.ui.legacy.FabLayout.OnClickFabListener
|
import com.hippo.ehviewer.ui.legacy.FabLayout.OnClickFabListener
|
||||||
import com.hippo.ehviewer.ui.legacy.FastScroller.OnDragHandlerListener
|
import com.hippo.ehviewer.ui.legacy.FastScroller.OnDragHandlerListener
|
||||||
import com.hippo.ehviewer.ui.legacy.HandlerDrawable
|
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.ViewTransition
|
||||||
import com.hippo.ehviewer.ui.legacy.WindowInsetsAnimationHelper
|
import com.hippo.ehviewer.ui.legacy.WindowInsetsAnimationHelper
|
||||||
import com.hippo.ehviewer.ui.setMD3Content
|
import com.hippo.ehviewer.ui.setMD3Content
|
||||||
@@ -354,11 +353,11 @@ class FavoritesScene : SearchBarScene() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
gidGetter = { adapter.peek(it)?.gid }
|
||||||
}
|
}
|
||||||
switchFav(Settings.recentFavCat)
|
switchFav(Settings.recentFavCat)
|
||||||
setChoiceMode(EasyRecyclerView.CHOICE_MODE_MULTIPLE_CUSTOM)
|
|
||||||
setCustomCheckedListener(object : CustomChoiceListener {
|
setCustomCheckedListener(object : CustomChoiceListener {
|
||||||
override fun onIntoCustomChoice(view: EasyRecyclerView) {
|
override fun onIntoCustomChoice(view: SelectableGalleryInfoRecyclerView) {
|
||||||
showSelectionFab()
|
showSelectionFab()
|
||||||
binding.fabLayout.setAutoCancel(false)
|
binding.fabLayout.setAutoCancel(false)
|
||||||
// Delay expanding action to make layout work fine
|
// Delay expanding action to make layout work fine
|
||||||
@@ -369,7 +368,7 @@ class FavoritesScene : SearchBarScene() {
|
|||||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END)
|
setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOutOfCustomChoice(view: EasyRecyclerView) {
|
override fun onOutOfCustomChoice(view: SelectableGalleryInfoRecyclerView) {
|
||||||
showNormalFab()
|
showNormalFab()
|
||||||
binding.fabLayout.setAutoCancel(true)
|
binding.fabLayout.setAutoCancel(true)
|
||||||
binding.fabLayout.isExpanded = false
|
binding.fabLayout.isExpanded = false
|
||||||
@@ -379,7 +378,7 @@ class FavoritesScene : SearchBarScene() {
|
|||||||
setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END)
|
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) {
|
if (view.checkedItemCount == 0) {
|
||||||
view.outOfCustomChoiceMode()
|
view.outOfCustomChoiceMode()
|
||||||
}
|
}
|
||||||
@@ -470,9 +469,8 @@ class FavoritesScene : SearchBarScene() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun takeCheckedInfo() = run {
|
private fun takeCheckedInfo() = run {
|
||||||
val stateArray = binding.recyclerView.checkedItemPositions!!
|
val snapshot = mAdapter?.snapshot()?.items
|
||||||
stateArray.valueIterator().asSequence().withIndex().filter { it.value }
|
binding.recyclerView.checkedItemGid.mapNotNull { gid -> snapshot?.find { it.gid == gid } }
|
||||||
.mapNotNull { mAdapter?.peek(stateArray.keyAt(it.index)) }.toList()
|
|
||||||
}.also { binding.recyclerView.outOfCustomChoiceMode() }
|
}.also { binding.recyclerView.outOfCustomChoiceMode() }
|
||||||
|
|
||||||
private fun checkedSize() = binding.recyclerView.checkedItemCount
|
private fun checkedSize() = binding.recyclerView.checkedItemCount
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:layout_behavior="@string/appbar_scrolling_view_behavior">
|
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:id="@+id/recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
app:fitsSystemWindowsInsets="top"
|
app:fitsSystemWindowsInsets="top"
|
||||||
app:layout_behavior="@string/searchbar_scrolling_view_behavior">
|
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:id="@+id/recycler_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
|||||||
Reference in new issue
Block a user