diff --git a/README.md b/README.md index d539fa2..65aa43b 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,116 @@ -# SpinnerTools -Advanced spinner views for Android including bottomsheets & searching +# SpinnerTools [![](https://jitpack.io/v/MattJAshworth/SpinnerTools.svg)](https://jitpack.io/#MattJAshworth/SpinnerTools) +Spinner View for Android with bottomsheet and searching built in + +# Screenshots +Spinner Tools Demo + +# Download +## build.gradle (Groovy) +Add to your project level `build.gradle` +```Java +allprojects { + repositories { + ... + maven { url "https://jitpack.io" } + } +} +``` +Add a dependency to your module `build.gradle`: +```Java +dependencies { + implementation 'com.github.MattJAshworth:SpinnerTools:1.2' +} +``` + +## build.gradle.kts (Kotlin DSL) + +Add to `settings.gradle.kts` +```Kotlin +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + google() + mavenCentral() + maven { url = uri("https://www.jitpack.io" ) } + + } +} +``` + +Add a dependency to your `build.gradle.kts`: +```Kotlin +dependencies { + implementation("com.github.MattJAshworth:SpinnerTools:1.2") +} +``` +# Implementation +Add the `xyz.mattjashworth.spinnertools.sheet.Spinner` to your layout XML file. + +Below are all the YesNoButton's xml attributes. You cannot currently set these programmatically. +```XML + +``` + +When binding to the view element specify the type. For example `ExampleObject` +```Kotlin +data class ExampleObject( + val name: String, + val age: String +) +``` +```Kotlin +val searchSpinner = findViewById>(R.id.app_spinner) +``` + +Set the spinner items by calling `setItems()` +```Kotlin +searchSpinner.setItems(data) +``` + +Attach an item selected listener to return the item selected by the user. The listener returns the type specified. In this case `ExampleObject`. +```Kotlin +searchSpinner.setOnItemSelectedListener(object : Spinner.OnItemSelectedListener { + override fun onItemSelected(model: ExampleObject) { + Snackbar.make(rootView, model.name, Snackbar.LENGTH_LONG).show() + } + +}) +``` + +# License +``` +MIT License + +Copyright (c) 2024 Matt J Ashworth + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +``` \ No newline at end of file diff --git a/SpinnerTools/src/main/java/xyz/mattjashworth/spinnertools/sheet/Spinner.kt b/SpinnerTools/src/main/java/xyz/mattjashworth/spinnertools/sheet/Spinner.kt index 9879e5f..80ebc7b 100644 --- a/SpinnerTools/src/main/java/xyz/mattjashworth/spinnertools/sheet/Spinner.kt +++ b/SpinnerTools/src/main/java/xyz/mattjashworth/spinnertools/sheet/Spinner.kt @@ -2,40 +2,46 @@ package xyz.mattjashworth.spinnertools.sheet import android.content.Context import android.content.res.TypedArray -import android.graphics.Color import android.os.Build import android.util.AttributeSet +import android.view.LayoutInflater import android.view.View import android.view.View.OnClickListener import android.view.ViewGroup +import android.view.inputmethod.InputMethodManager import android.widget.EditText import android.widget.LinearLayout -import androidx.annotation.AttrRes +import android.widget.PopupWindow import androidx.annotation.RequiresApi import androidx.cardview.widget.CardView +import androidx.core.content.ContextCompat +import androidx.core.content.ContextCompat.getSystemService +import androidx.core.view.marginLeft +import androidx.core.view.marginRight +import androidx.recyclerview.widget.DividerItemDecoration +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView import com.google.android.material.textfield.TextInputLayout import com.google.gson.Gson import com.google.gson.JsonParser import xyz.mattjashworth.spinnertools.R +import xyz.mattjashworth.spinnertools.sheet.adapters.SearchSpinnerAdapter - - -@RequiresApi(Build.VERSION_CODES.N_MR1) class Spinner(context: Context, attributeSet: AttributeSet) : LinearLayout(context, attributeSet) { private lateinit var selectedItem: EditText private var card: CardView private var items = ArrayList() - private var SelectedObject: T? = null + private var selectedObject: T? = null - private var DisplayMember: String? = null - private var Title = "Select Item" - private var SelectedMode: Mode = Mode.Sheet - private var Searchable = false - private var DismissWhenSelected = false + private var displayMember: String? = null + private var title = "Select Item" + private var searchable = false + private var dismissWhenSelected = false + + private lateinit var textInputLayout: TextInputLayout - private enum class Mode {Sheet, Dropdown} private var type: Any? = null private var onItemSelectedListener: OnItemSelectedListener? = null @@ -45,25 +51,27 @@ class Spinner(context: Context, attributeSet: AttributeSet) : LinearLayout(co val root = inflate(context, R.layout.spinner, this) val ta: TypedArray = getContext().obtainStyledAttributes(attributeSet, R.styleable.Spinner) - DisplayMember = ta.getString(R.styleable.Spinner_DisplayMember)!! - SelectedMode = Mode.values()[ta.getInt(R.styleable.Spinner_Mode, 0)] - Title = ta.getString(R.styleable.Spinner_Title)!! - Searchable = ta.getBoolean(R.styleable.Spinner_Searchable, false) - DismissWhenSelected = ta.getBoolean(R.styleable.Spinner_DismissWhenSelected, false) + displayMember = ta.getString(R.styleable.Spinner_DisplayMember) ?: "" + title = ta.getString(R.styleable.Spinner_Title) ?: "" + searchable = ta.getBoolean(R.styleable.Spinner_Searchable, false) + dismissWhenSelected = ta.getBoolean(R.styleable.Spinner_DismissWhenSelected, false) + ta.recycle() + + selectedItem = findViewById(R.id.tv_spinner_selected) - val layout = findViewById(R.id.tIL) + textInputLayout = findViewById(R.id.tIL) card = findViewById(R.id.card_spinner) - layout.hint = Title + textInputLayout.hint = title setChildListener(rootView, OnClickListener { - val s = SpinnerSheet(context, items, Title, DisplayMember) - if (SelectedObject != null) - s.setSelectedObject(SelectedObject!!) + val s = SpinnerSheet(context, items, title, displayMember, searchable) + if (selectedObject != null) + s.setSelectedObject(selectedObject!!) s.setOnItemClickListener(object : SpinnerSheet.OnSearchSpinnerClickListener { override fun onClick(position: Int, model: T) { - if (DismissWhenSelected) + if (dismissWhenSelected) s.dismiss() val gson = Gson() @@ -72,6 +80,7 @@ class Spinner(context: Context, attributeSet: AttributeSet) : LinearLayout(co if (model is String) { selectedItem.setText(model) + } else { val obj = JsonParser.parseString(jsonStr).asJsonObject @@ -81,14 +90,16 @@ class Spinner(context: Context, attributeSet: AttributeSet) : LinearLayout(co var res = "" - if (!DisplayMember.isNullOrEmpty()) res = obj.get(DisplayMember).asString + if (!displayMember.isNullOrEmpty()) res = + obj.get(displayMember).asString else res = obj.get(keys.max()).asString - SelectedObject = model + selectedObject = model selectedItem.setText(res) - onItemSelectedListener?.onItemSelected(model) } + + onItemSelectedListener?.onItemSelected(model) } }) @@ -122,11 +133,11 @@ class Spinner(context: Context, attributeSet: AttributeSet) : LinearLayout(co } fun setDisplayMember(id: String) { - DisplayMember = id + displayMember = id } fun setTitle(title: String) { - Title = title + this.title = title } } \ No newline at end of file diff --git a/SpinnerTools/src/main/java/xyz/mattjashworth/spinnertools/sheet/SpinnerSheet.kt b/SpinnerTools/src/main/java/xyz/mattjashworth/spinnertools/sheet/SpinnerSheet.kt index 8e3b1be..9045207 100644 --- a/SpinnerTools/src/main/java/xyz/mattjashworth/spinnertools/sheet/SpinnerSheet.kt +++ b/SpinnerTools/src/main/java/xyz/mattjashworth/spinnertools/sheet/SpinnerSheet.kt @@ -2,6 +2,7 @@ package xyz.mattjashworth.spinnertools.sheet import android.content.Context import android.view.LayoutInflater +import android.view.View import android.widget.EditText import android.widget.TextView import androidx.core.widget.addTextChangedListener @@ -16,7 +17,7 @@ import xyz.mattjashworth.spinnertools.R import xyz.mattjashworth.spinnertools.sheet.adapters.SearchSpinnerAdapter import java.lang.reflect.Modifier -internal class SpinnerSheet(context: Context, items: ArrayList, title: String, displayMember: String?) { +internal class SpinnerSheet(context: Context, items: ArrayList, title: String, displayMember: String?, searchable: Boolean) { private var onSearchSpinnerClickListener: OnSearchSpinnerClickListener? = null @@ -35,6 +36,7 @@ internal class SpinnerSheet(context: Context, items: ArrayList, title: Str val spinnerSheetView = layoutInflater.inflate(R.layout.bottomsheet_spinner, null) val search = spinnerSheetView.findViewById(R.id.et_bottomsheet_search) + if (!searchable) search.visibility = View.GONE val sheetTitle = spinnerSheetView.findViewById(R.id.tv_sheet_title) sheetTitle.text = title diff --git a/SpinnerTools/src/main/res/layout/dropdown_spinner.xml b/SpinnerTools/src/main/res/layout/dropdown_spinner.xml deleted file mode 100644 index 945e480..0000000 --- a/SpinnerTools/src/main/res/layout/dropdown_spinner.xml +++ /dev/null @@ -1,27 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/SpinnerTools/src/main/res/layout/spinner.xml b/SpinnerTools/src/main/res/layout/spinner.xml index 9024168..c394006 100644 --- a/SpinnerTools/src/main/res/layout/spinner.xml +++ b/SpinnerTools/src/main/res/layout/spinner.xml @@ -12,7 +12,6 @@ android:id="@+id/card_spinner" android:layout_width="match_parent" android:layout_height="wrap_content" - app:cardElevation="10dp" android:background="@android:color/transparent" app:contentPadding="10dp" app:cardUseCompatPadding="true" diff --git a/SpinnerTools/src/main/res/values/attrs.xml b/SpinnerTools/src/main/res/values/attrs.xml index 48ff5b0..46963f1 100644 --- a/SpinnerTools/src/main/res/values/attrs.xml +++ b/SpinnerTools/src/main/res/values/attrs.xml @@ -3,10 +3,6 @@ - - - - diff --git a/sample/src/main/java/xyz/mattjashworth/sample/MainActivity.kt b/sample/src/main/java/xyz/mattjashworth/sample/MainActivity.kt index e2829a9..903a372 100644 --- a/sample/src/main/java/xyz/mattjashworth/sample/MainActivity.kt +++ b/sample/src/main/java/xyz/mattjashworth/sample/MainActivity.kt @@ -60,9 +60,9 @@ class MainActivity : AppCompatActivity() { ) - val s = findViewById>(R.id.app_spinner) - s.setItems(data) - s.setOnItemSelectedListener(object : Spinner.OnItemSelectedListener { + val searchSpinner = findViewById>(R.id.app_spinner) + searchSpinner.setItems(data) + searchSpinner.setOnItemSelectedListener(object : Spinner.OnItemSelectedListener { override fun onItemSelected(model: ExampleObject) { Snackbar.make(rootView, model.name, Snackbar.LENGTH_LONG).show() } diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml index c9d18f6..359aa4a 100644 --- a/sample/src/main/res/layout/activity_main.xml +++ b/sample/src/main/res/layout/activity_main.xml @@ -11,10 +11,13 @@ android:id="@+id/app_spinner" android:layout_width="0dp" android:layout_height="wrap_content" - app:DisplayMember="ID" - app:Mode="Sheet" + android:layout_marginStart="10dp" + android:layout_marginTop="10dp" + android:layout_marginEnd="10dp" app:DismissWhenSelected="true" - app:Title="Select Remedy Code" + app:Title="Select Person" + app:Searchable="true" + app:DisplayMember="name" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> diff --git a/screenshots/spinnertools.gif b/screenshots/spinnertools.gif new file mode 100644 index 0000000..65e46d4 Binary files /dev/null and b/screenshots/spinnertools.gif differ