From 0c38a0226a13c731fe7288bd5473cb905738f202 Mon Sep 17 00:00:00 2001 From: Mala Ruparel Date: Wed, 8 May 2024 16:54:46 +0530 Subject: [PATCH] #Multi Selection List View --- .../java/com/ss/smartfilter/MainActivity.kt | 4 +- app/src/main/res/layout/activity_main.xml | 2 +- .../com/ss/smartfilterlib/BaseSelection.kt | 57 ++++++ .../smartfilterlib/MultiSelectionListView.kt | 161 +++++++---------- .../smartfilterlib/SingleSelectionListView.kt | 171 +++++++++--------- .../radiogroup/callback/RadioGroupCallback.kt | 1 + 6 files changed, 218 insertions(+), 178 deletions(-) create mode 100644 ss-smart-filter/src/main/java/com/ss/smartfilterlib/BaseSelection.kt diff --git a/app/src/main/java/com/ss/smartfilter/MainActivity.kt b/app/src/main/java/com/ss/smartfilter/MainActivity.kt index 704908a..e9cb7a0 100644 --- a/app/src/main/java/com/ss/smartfilter/MainActivity.kt +++ b/app/src/main/java/com/ss/smartfilter/MainActivity.kt @@ -14,8 +14,8 @@ class MainActivity : ComponentActivity() { binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) - binding.multiSelectionListView.setOnMultiSelectionClicked() { - showToast("MultiSelection", this) + binding.multiSelectionListView.setOnMultiSelection { + showToast( it.joinToString { it.name } , this) } } diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 65f1c58..d35d101 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -13,7 +13,7 @@ android:layout_height="match_parent" android:background="@color/white" app:ss_Listitem="@array/ss_array" - app:ss_Orientation="0" + /> diff --git a/ss-smart-filter/src/main/java/com/ss/smartfilterlib/BaseSelection.kt b/ss-smart-filter/src/main/java/com/ss/smartfilterlib/BaseSelection.kt new file mode 100644 index 0000000..30f10bf --- /dev/null +++ b/ss-smart-filter/src/main/java/com/ss/smartfilterlib/BaseSelection.kt @@ -0,0 +1,57 @@ +package com.ss.smartfilterlib + +import RadioGroupCallback +import android.content.Context +import android.graphics.Typeface +import android.util.AttributeSet +import android.widget.CheckedTextView +import androidx.recyclerview.widget.RecyclerView +import com.ss.smartfilterlib.singlechoice.radiogroup.data.RadioGroupData +import com.ss.smartfilterlib.singlechoice.util.Constant +import com.ss.smartfilterlib.singlechoice.util.PaddingAttributes +import com.ss.smartfilterlib.singlechoice.util.TextAttributes + +/** + * created by Mala Ruparel ON 08/05/24 + */ +abstract class BaseClass : RecyclerView { + + + protected var primaryTextColor: Int = Constant.PRIMARY_TEXT_COLOR + protected var orientation: Int = SmartOrientation.VERTICAL + protected var checkSelector: Int = 0 + protected var paddingAttributes: PaddingAttributes = PaddingAttributes() + protected var textAttributes: TextAttributes = TextAttributes() + protected var onMultiSelectionClicked: ((List) -> Unit)? = null + protected var onSingleSelectionClicked: RadioGroupCallback? = null + protected var dataFromXml: Int = 0 + protected var data: List = emptyList() + protected val selectedItemsPositions = mutableListOf() + constructor(context: Context) : super(context) + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) + + // Common methods + protected abstract fun initAttributes(attrs: AttributeSet?) + protected abstract fun initializeView() + + protected fun applyTextAttributes(textView: CheckedTextView) { + textAttributes.let { attributes -> + textView.apply { + textSize = attributes.textSize + typeface = Typeface.create(Typeface.DEFAULT, attributes.fontFamily) + } + } + } + protected fun applyPaddingAttributes(textView: CheckedTextView) { + paddingAttributes.let { attributes -> + textView.setPadding( + attributes.paddingStart, + attributes.paddingTop, + attributes.paddingEnd, + attributes.paddingBottom + ) + } + } + protected fun setData(data: RadioGroupData) = data.name +} \ No newline at end of file diff --git a/ss-smart-filter/src/main/java/com/ss/smartfilterlib/MultiSelectionListView.kt b/ss-smart-filter/src/main/java/com/ss/smartfilterlib/MultiSelectionListView.kt index eadb70d..7f0b4e3 100644 --- a/ss-smart-filter/src/main/java/com/ss/smartfilterlib/MultiSelectionListView.kt +++ b/ss-smart-filter/src/main/java/com/ss/smartfilterlib/MultiSelectionListView.kt @@ -1,70 +1,56 @@ package com.ss.smartfilterlib import android.content.Context -import android.content.res.ColorStateList -import android.graphics.drawable.Drawable import android.util.AttributeSet +import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.CheckedTextView -import androidx.core.content.ContextCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.card.MaterialCardView import com.ss.smartfilterlib.singlechoice.radiogroup.data.RadioGroupData -import com.ss.smartfilterlib.singlechoice.util.PaddingAttributes /** * created by Mala Ruparel ON 02/05/24 */ -class MultiSelectionListView @JvmOverloads constructor( context: Context,attrs: AttributeSet? = null,defStyle: Int = 0) : RecyclerView(context, attrs, defStyle) { +class MultiSelectionListView @JvmOverloads constructor( context: Context,attrs: AttributeSet? = null,defStyle: Int = 0) : BaseClass(context, attrs, defStyle){ - private var textSelectorColor: ColorStateList? = null - private var bgSelector: Drawable? = null - private var orientation: Int = com.ss.smartfilterlib.singlechoice.util.Orientation.VERTICAL - private var checkDrawableSelector: Int = 0 - private var paddingAttributes: PaddingAttributes = PaddingAttributes() - private var onMultiSelectionClicked: ((List) -> Unit)? = null - - private var dataFromXml: Int = 0 - private val selectedItemsPositions = mutableListOf() init { - initAttrs(attrs) - setupView() - setDataFromAttrs() + initAttributes(attrs=attrs) + initializeView() + populateDataFromAttributes() } - private fun setDataFromAttrs() { - if (dataFromXml != 0) { - val mData = resources.getStringArray(dataFromXml); - val data = ArrayList() - for (i in mData.indices) { - data.add(RadioGroupData(name = mData[i])) - } - configureView(data,orientation,checkDrawableSelector,textSelectorColor,onMultiSelectionClicked) - + private fun populateDataFromAttributes() { + val mData = resources.getStringArray(dataFromXml); + val data = mData.map { RadioGroupData(name = it) } as ArrayList + configureView( + data = data, + orientation = orientation, + checkSelector = checkSelector, + primaryTextColor = primaryTextColor, + onCheckedChangeListener = onMultiSelectionClicked + ) - } } - private fun setupView() { + override fun initializeView() { layoutManager = when (orientation) { - com.ss.smartfilterlib.singlechoice.util.Orientation.VERTICAL -> LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) - else -> LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) + SmartOrientation.VERTICAL -> LinearLayoutManager(context, VERTICAL, false) + else -> LinearLayoutManager(context, HORIZONTAL, false) } - } - private fun initAttrs(attrs: AttributeSet?) { + override fun initAttributes(attrs: AttributeSet?) { val typedArray = context.obtainStyledAttributes(attrs, R.styleable.SingleSelectionView) try { - bgSelector = typedArray.getDrawable(R.styleable.SingleSelectionView_ss_Background) - textSelectorColor = typedArray.getColorStateList(R.styleable.SingleSelectionView_ss_TextSelector) + primaryTextColor = typedArray.getColor(R.styleable.SingleSelectionView_ss_TextSelector, primaryTextColor) orientation = typedArray.getInt(R.styleable.SingleSelectionView_ss_Orientation,RecyclerView.VERTICAL) - checkDrawableSelector = typedArray.getResourceId(R.styleable.SingleSelectionView_ss_checkDrawableSelector,R.drawable.ic_check_selector) + checkSelector = typedArray.getResourceId(R.styleable.SingleSelectionView_ss_checkDrawableSelector,R.drawable.ic_check_selector) dataFromXml = typedArray.getResourceId(R.styleable.SingleSelectionView_ss_Listitem, 0) } finally { typedArray.recycle() @@ -72,27 +58,32 @@ class MultiSelectionListView @JvmOverloads constructor( context: Context,attrs: } fun configureView( - mData: ArrayList, + data: ArrayList, orientation: Int, - checkSelector: Int, textSelector: ColorStateList?, onCheckedChangeListener: ((List) -> Unit)? + checkSelector: Int, primaryTextColor: Int, onCheckedChangeListener: ((List) -> Unit)? ) { - this.orientation = orientation - this.onMultiSelectionClicked = onCheckedChangeListener - setupView() - adapter = CustomAdapter(checkSelector, textSelector) - setItems(mData) + updateValue(orientation, checkSelector, primaryTextColor, onCheckedChangeListener) + initializeView() + setItems(data) } + private fun updateValue( + orientation: Int, + checkSelector: Int, + primaryTextColor: Int, + onCheckedChangeListener: ((List) -> Unit)? + ) { + this.onMultiSelectionClicked = onCheckedChangeListener + this.orientation = orientation + this.checkSelector = checkSelector + this.primaryTextColor = primaryTextColor + } private fun setItems(items: List) { - (adapter as? CustomAdapter)?.data = items + adapter = MultiSelectionListAdapter().apply { data = items } } - private inner class CustomAdapter( - var checkSelector: Int, - var textSelector: ColorStateList?, - - ) : RecyclerView.Adapter() { + private inner class MultiSelectionListAdapter() : RecyclerView.Adapter() { private var selectedItemPosition: Int = RecyclerView.NO_POSITION @@ -102,27 +93,25 @@ class MultiSelectionListView @JvmOverloads constructor( context: Context,attrs: notifyDataSetChanged() } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder { - val view = LayoutInflater.from(parent.context) - .inflate(R.layout.item_simple_list_checkable, parent, false) - return CustomViewHolder(view) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MultiselectionViewHolder { + val view = LayoutInflater.from(parent.context) .inflate(R.layout.item_simple_list_checkable, parent, false) + return MultiselectionViewHolder(view) } - override fun onBindViewHolder(holder: CustomViewHolder, position: Int) { - holder.bind(data[position], - position == selectedItemPosition - ) + override fun onBindViewHolder(holder: MultiselectionViewHolder, position: Int) { + holder.bind(data[position],position == selectedItemPosition) } override fun getItemCount(): Int = data.size - inner class CustomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + inner class MultiselectionViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + val cardView: MaterialCardView by lazy { MaterialCardView(itemView.context) } + val textView: CheckedTextView by lazy { CheckedTextView(itemView.context) } - private val cardView = MaterialCardView(itemView.context) - private val textView = CheckedTextView(itemView.context) init { - cardView.apply { + with(cardView) { layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT @@ -130,7 +119,6 @@ class MultiSelectionListView @JvmOverloads constructor( context: Context,attrs: addView(textView) } - (itemView as ViewGroup).addView(cardView) itemView.setOnClickListener { @@ -144,29 +132,25 @@ class MultiSelectionListView @JvmOverloads constructor( context: Context,attrs: fun bind(data: RadioGroupData, isSelected: Boolean) { textView.apply { - - setPadding( - paddingAttributes.paddingStart, - paddingAttributes.paddingTop, - paddingAttributes.paddingEnd, - paddingAttributes.paddingBottom - ) // Set - text = data.name - applySelector(this) - // setTextColor(textSelector) - isChecked = adapterPosition == selectedItemPosition - checkDrawableSelector = checkSelector - setCheckMarkDrawable(checkDrawableSelector) - checkMarkDrawable?.setBounds( - 40, - 400, - checkMarkDrawable.intrinsicWidth, - checkMarkDrawable.intrinsicHeight - ) isChecked = selectedItemsPositions.contains(adapterPosition) + applyTextAttributes(this) + applyPaddingAttributes(this) + text = setData(data) + applySelector(this) + extracted() } } + private fun CheckedTextView.extracted() { + checkMarkDrawable?.setBounds( + 0, + 0, + checkMarkDrawable.intrinsicWidth, + checkMarkDrawable.intrinsicHeight + ) + gravity = Gravity.CENTER_VERTICAL + setPadding(10, 10, 10, 10) + } } private fun toggleItemSelection(position: Int) { @@ -181,20 +165,13 @@ class MultiSelectionListView @JvmOverloads constructor( context: Context,attrs: } } private fun applySelector(textView: CheckedTextView) { - with(textView) { - setTextColor(textSelectorColor ?: setDefaultTextColor()) - // radioButton.buttonDrawable = checkDrawableSelector?.constantState?.newDrawable()?.mutate() - } - } - private fun setDefaultTextColor(): ColorStateList? { - return ContextCompat.getColorStateList(context, R.color.black)?.let { - it - } ?: run { - null - } + + textView.setTextColor(primaryTextColor) + textView.setCheckMarkDrawable(checkSelector) + } - fun setOnMultiSelectionClicked(callback: (List) -> Unit) { + fun setOnMultiSelection(callback: (List) -> Unit) { onMultiSelectionClicked = callback } } diff --git a/ss-smart-filter/src/main/java/com/ss/smartfilterlib/SingleSelectionListView.kt b/ss-smart-filter/src/main/java/com/ss/smartfilterlib/SingleSelectionListView.kt index 220b17e..7a5e003 100644 --- a/ss-smart-filter/src/main/java/com/ss/smartfilterlib/SingleSelectionListView.kt +++ b/ss-smart-filter/src/main/java/com/ss/smartfilterlib/SingleSelectionListView.kt @@ -2,91 +2,84 @@ package com.ss.smartfilterlib import RadioGroupCallback import android.content.Context -import android.content.res.ColorStateList -import android.graphics.drawable.Drawable +import android.graphics.Typeface import android.util.AttributeSet +import android.view.Gravity import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.CheckedTextView -import androidx.core.content.ContextCompat import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.card.MaterialCardView import com.ss.smartfilterlib.singlechoice.radiogroup.data.RadioGroupData -import com.ss.smartfilterlib.singlechoice.util.PaddingAttributes - +typealias SmartOrientation = com.ss.smartfilterlib.singlechoice.util.Orientation /** * created by Mala Ruparel ON 02/05/24 */ -class SingleSelectionView @JvmOverloads constructor( context: Context,attrs: AttributeSet? = null,defStyle: Int = 0) : RecyclerView(context, attrs, defStyle) { +class SingleSelectionView @JvmOverloads constructor( context: Context,attrs: AttributeSet? = null,defStyle: Int = 0) : BaseClass(context, attrs, defStyle){ - private var primaryTextColor: ColorStateList? = null - private var checkSelector: Drawable? = null - private var orientation: Int = com.ss.smartfilterlib.singlechoice.util.Orientation.VERTICAL - private var checkDrawableSelector: Int = 0 - private var paddingAttributes: PaddingAttributes = PaddingAttributes() - private var onCheckedChangeListener: RadioGroupCallback? = null - private var dataFromXml: Int = 0 init { - initAttrs(attrs) - setupView() - setDataFromAttrs() + initAttributes(attrs=attrs) + initializeView() + populateDataFromAttributes() } - private fun setDataFromAttrs() { + private fun populateDataFromAttributes() { val mData = resources.getStringArray(dataFromXml); - val data = ArrayList() - for (i in mData.indices) { - data.add(RadioGroupData(name = mData[i])) - } - - + val data = mData.map { RadioGroupData(name = it) } as ArrayList + configureView( + data = data, + orientation = orientation, + checkSelector = checkSelector, + primaryTextColor = primaryTextColor, + onCheckedChangeListener = onSingleSelectionClicked + ) } - private fun setupView() { - layoutManager = - if (orientation == com.ss.smartfilterlib.singlechoice.util.Orientation.VERTICAL) { - LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) - } else { - LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false) - } - + override fun initializeView() { + layoutManager = when (orientation) { + SmartOrientation.VERTICAL -> LinearLayoutManager(context, VERTICAL, false) + else -> LinearLayoutManager(context, HORIZONTAL, false) + } } - private fun initAttrs(attrs: AttributeSet?) { + override fun initAttributes(attrs: AttributeSet?) { val typedArray = context.obtainStyledAttributes(attrs, R.styleable.SingleSelectionView) try { - checkSelector = typedArray.getDrawable(R.styleable.SingleSelectionView_ss_Background) - primaryTextColor = typedArray.getColorStateList(R.styleable.SingleSelectionView_ss_TextSelector) - + primaryTextColor = typedArray.getColor(R.styleable.SingleSelectionView_ss_TextSelector, primaryTextColor) orientation = typedArray.getInt(R.styleable.SingleSelectionView_ss_Orientation,RecyclerView.VERTICAL) - checkDrawableSelector = typedArray.getResourceId(R.styleable.SingleSelectionView_ss_checkDrawableSelector,R.drawable.ic_check_selector) - dataFromXml = typedArray.getResourceId(R.styleable.SingleLineRadioGroup_rg_sl_Listitem, 0) + checkSelector = typedArray.getResourceId(R.styleable.SingleSelectionView_ss_checkDrawableSelector,R.drawable.ic_check_selector) + dataFromXml = typedArray.getResourceId(R.styleable.SingleSelectionView_ss_Listitem, 0) } finally { typedArray.recycle() } } - fun configureView(mData: ArrayList, orientation: Int, checkSelector: Int, textSelector: Int, callbacks: RadioGroupCallback) { - this.onCheckedChangeListener = callbacks + fun configureView(data: ArrayList, orientation: Int, checkSelector: Int, primaryTextColor: Int, onCheckedChangeListener: RadioGroupCallback?){ + updateValue(orientation, checkSelector, primaryTextColor, onCheckedChangeListener) + initializeView() + setItems(data) + } + private fun updateValue( + orientation: Int, + checkSelector: Int, + primaryTextColor: Int, + onCheckedChangeListener: RadioGroupCallback? + ) { + this.onSingleSelectionClicked = onCheckedChangeListener this.orientation = orientation - setupView() - adapter = CustomAdapter(checkSelector, textSelector,callbacks) - setItems(mData) + this.checkSelector = checkSelector + this.primaryTextColor = primaryTextColor } private fun setItems(items: List) { - (adapter as? CustomAdapter)?.data = items + adapter = SingleSelectionListAdapter().apply { data = items } } - private inner class CustomAdapter( - var checkSelector: Int, - var textSelector: Int, - var callbacks: RadioGroupCallback - ) : RecyclerView.Adapter() { + private inner class SingleSelectionListAdapter() : RecyclerView.Adapter() { private var selectedItemPosition: Int = RecyclerView.NO_POSITION @@ -96,39 +89,37 @@ class SingleSelectionView @JvmOverloads constructor( context: Context,attrs: Att notifyDataSetChanged() } - override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder { - val view = LayoutInflater.from(parent.context) - .inflate(R.layout.item_simple_list_checkable, parent, false) - return CustomViewHolder(view) + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SingleSelectionListViewHolder { + val view = LayoutInflater.from(parent.context).inflate(R.layout.item_simple_list_checkable, parent, false) + return SingleSelectionListViewHolder(view) } - override fun onBindViewHolder(holder: CustomViewHolder, position: Int) { - holder.bind(data[position], - position == selectedItemPosition - ) + override fun onBindViewHolder(holder: SingleSelectionListViewHolder, position: Int) { + holder.bind(data[position],position == selectedItemPosition) } override fun getItemCount(): Int = data.size - inner class CustomViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + inner class SingleSelectionListViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { - private val cardView = MaterialCardView(itemView.context) - private val textView = CheckedTextView(itemView.context) + val cardView: MaterialCardView by lazy { MaterialCardView(itemView.context) } + val textView: CheckedTextView by lazy { CheckedTextView(itemView.context) } init { - val cardLayoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.WRAP_CONTENT - ) - cardView.layoutParams = cardLayoutParams - cardView.addView(textView) + with(cardView) { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.WRAP_CONTENT + ) + addView(textView) + } (itemView as ViewGroup).addView(cardView) itemView.setOnClickListener { val position = adapterPosition if (position != RecyclerView.NO_POSITION) { - selectItem(position) + toggleItemSelection(position) } } } @@ -136,38 +127,52 @@ class SingleSelectionView @JvmOverloads constructor( context: Context,attrs: Att fun bind(data: RadioGroupData, isSelected: Boolean) { textView.apply { - - setPadding( - paddingAttributes.paddingStart, - paddingAttributes.paddingTop, - paddingAttributes.paddingEnd, - paddingAttributes.paddingBottom - ) // Set - text = data.name - setTextColor(textSelector.let { ContextCompat.getColorStateList(context, it) }) isChecked = adapterPosition == selectedItemPosition - checkDrawableSelector = checkSelector - setCheckMarkDrawable(checkDrawableSelector) + applyTextAttributes(this) + applyPaddingAttributes(this) + text = setData(data) + applySelector(this) + compoundDrawablePadding =10 + extracted() + + } + + } + + + private fun CheckedTextView.extracted() { checkMarkDrawable?.setBounds( - 40, - 400, + 0, + 0, checkMarkDrawable.intrinsicWidth, checkMarkDrawable.intrinsicHeight ) - - } + gravity = Gravity.CENTER_VERTICAL + setPadding(10, 10, 10, 10) } } - private fun selectItem(position: Int) { + private fun toggleItemSelection(position: Int) { val previousSelectedItemPosition = selectedItemPosition selectedItemPosition = position notifyItemChanged(previousSelectedItemPosition) notifyItemChanged(selectedItemPosition) - callbacks.onSingleSelection(data[position]) + onSingleSelectionClicked?.onSingleSelection(data[position]) } + + } + private fun applySelector(textView: CheckedTextView) { + + textView.setTextColor(primaryTextColor) + textView.setCheckMarkDrawable(checkSelector) + } + + + fun setOnSingleSelection(callback: RadioGroupCallback) { + onSingleSelectionClicked = callback + } } diff --git a/ss-smart-filter/src/main/java/com/ss/smartfilterlib/singlechoice/radiogroup/callback/RadioGroupCallback.kt b/ss-smart-filter/src/main/java/com/ss/smartfilterlib/singlechoice/radiogroup/callback/RadioGroupCallback.kt index 1adf03d..45bb345 100644 --- a/ss-smart-filter/src/main/java/com/ss/smartfilterlib/singlechoice/radiogroup/callback/RadioGroupCallback.kt +++ b/ss-smart-filter/src/main/java/com/ss/smartfilterlib/singlechoice/radiogroup/callback/RadioGroupCallback.kt @@ -13,3 +13,4 @@ interface MultiChoiceCallback : (List) -> Unit { } +