Ver Fonte

1、本地闹钟
2、logo设置优化
3、币值单位优化

ccc há 18 horas atrás
pai
commit
41140f569c
32 ficheiros alterados com 1384 adições e 33 exclusões
  1. 1 0
      lib_base/src/main/res/values/strings.xml
  2. 13 0
      lib_common/src/main/java/com/quyunshuo/sbm10/common/constant/MMKVName.kt
  3. 2 0
      module_backstage/build.gradle
  4. 5 1
      module_backstage/src/main/java/com/module/backstage/adapter/PriceAdapter.kt
  5. 25 23
      module_backstage/src/main/java/com/module/backstage/adapter/TestAdapter.kt
  6. 3 1
      module_backstage/src/main/java/com/module/backstage/repo/SystemSettingsFragmentRepo.kt
  7. 1 1
      module_backstage/src/main/res/layout/backstage_fragment_other.xml
  8. 5 3
      module_backstage/src/main/res/layout/backstage_item_spinner.xml
  9. 4 1
      module_home/src/main/java/com/quyunshuo/module/home/adapter/HomeProductAdapter.kt
  10. 11 3
      module_pay/src/main/java/com/module/pay/common/OtherEnum.kt
  11. 1 0
      settings.gradle.kts
  12. 1 0
      spinnerlib/.gitignore
  13. 21 0
      spinnerlib/build.gradle
  14. 21 0
      spinnerlib/gradle.properties
  15. 17 0
      spinnerlib/proguard-rules.pro
  16. 6 0
      spinnerlib/src/main/AndroidManifest.xml
  17. 639 0
      spinnerlib/src/main/java/com/jaredrummler/materialspinner/MaterialSpinner.java
  18. 55 0
      spinnerlib/src/main/java/com/jaredrummler/materialspinner/MaterialSpinnerAdapter.java
  19. 61 0
      spinnerlib/src/main/java/com/jaredrummler/materialspinner/MaterialSpinnerAdapterWrapper.java
  20. 170 0
      spinnerlib/src/main/java/com/jaredrummler/materialspinner/MaterialSpinnerBaseAdapter.java
  21. 83 0
      spinnerlib/src/main/java/com/jaredrummler/materialspinner/Utils.java
  22. BIN
      spinnerlib/src/main/res/drawable-hdpi/ms__drop_down_shadow.9.png
  23. 30 0
      spinnerlib/src/main/res/drawable-v21/ms__selector.xml
  24. BIN
      spinnerlib/src/main/res/drawable-xhdpi/ms__shadow_background.9.png
  25. BIN
      spinnerlib/src/main/res/drawable-xxhdpi/ms__shadow_background.9.png
  26. 25 0
      spinnerlib/src/main/res/drawable/ms__arrow.xml
  27. 30 0
      spinnerlib/src/main/res/drawable/ms__drawable.xml
  28. 28 0
      spinnerlib/src/main/res/drawable/ms__menu_down.xml
  29. 22 0
      spinnerlib/src/main/res/drawable/ms__selector.xml
  30. 36 0
      spinnerlib/src/main/res/layout/ms__list_item.xml
  31. 43 0
      spinnerlib/src/main/res/values/attrs.xml
  32. 25 0
      spinnerlib/src/main/res/values/dimens.xml

+ 1 - 0
lib_base/src/main/res/values/strings.xml

@@ -314,6 +314,7 @@
     <string name="cannot_exceed_size">当前可添加数量不能超过</string>
     <string name="value_too_large_small">修改失败,请在当前区间内进行调整</string>
     <string name="set_fail">修改失败</string>
+    <string name="currency_unit">货币单位</string>
 
 </resources>
 <!--中文-->

+ 13 - 0
lib_common/src/main/java/com/quyunshuo/sbm10/common/constant/MMKVName.kt

@@ -257,6 +257,19 @@ abstract class MMKVName {
         val WAY: String
             //上传 联系方式
             get() = "way"
+        val CURRENCY_UNIT: String
+            //货币单位
+            get() = "CURRENCY_UNIT"
+
+        val UNIT_DATA: List<String>
+            //货币单位
+            get() =
+                listOf("¥", "$","€","руб","៛","₪","₴","zł",
+                    "Ұ","NT","₩","₮","〒","S/.","R$","৲৳","Rp",
+                    "Sk","R","Q","P","₱","₦","Lm","₤","£",
+                    "kr","₭","₲","Rs.","Ft","ƒ","₫","₡","₵",
+                    "Br","Bs","¤",
+                    "﷼")
 
     }
     // 这里可以继续添加其他常量

+ 2 - 0
module_backstage/build.gradle

@@ -1,3 +1,4 @@
+import com.quyunshuo.sbm10.buildsrc.DependencyConfig
 import com.quyunshuo.sbm10.buildsrc.ProjectBuildConfig
 
 //****************************************
@@ -29,5 +30,6 @@ dependencies{
     implementation project(path: ':lib_base')
     implementation project(':zloadingview')
     implementation project(path: ':barchartlib')
+    implementation project(path: ':spinnerlib')
 
 }

+ 5 - 1
module_backstage/src/main/java/com/module/backstage/adapter/PriceAdapter.kt

@@ -8,8 +8,10 @@ import androidx.recyclerview.widget.RecyclerView
 import com.bumptech.glide.Glide
 import com.module.backstage.R
 import com.module.backstage.databinding.BackstageItemPriceBinding
+import com.quyunshuo.sbm10.base.utils.SpUtils
 import com.quyunshuo.sbm10.common.listener.AdapterClickListener
 import com.quyunshuo.sbm10.common.bean.ProductDataBean
+import com.quyunshuo.sbm10.common.constant.MMKVName
 import com.quyunshuo.sbm10.common.util.UiUtil
 
 class PriceAdapter(var productList:ArrayList<ProductDataBean>): RecyclerView.Adapter<PriceAdapter.MyViewHolder>() {
@@ -17,6 +19,8 @@ class PriceAdapter(var productList:ArrayList<ProductDataBean>): RecyclerView.Ada
     private val _position = MutableLiveData<Int>()
     val clickPosition:LiveData<Int> =  _position
     private var itemListener: AdapterClickListener? = null
+    private val unit= SpUtils.getInt(MMKVName.CURRENCY_UNIT,0)
+
     fun setItemListener(itemListener: AdapterClickListener?) {
         this.itemListener = itemListener
     }
@@ -37,7 +41,7 @@ class PriceAdapter(var productList:ArrayList<ProductDataBean>): RecyclerView.Ada
                 }
                 tvName.setText(UiUtil.getResId(productDataBean.nameId, R.string::class.java))
                 etSugarprice.setText(""+productDataBean.price)
-
+                backstageTextview7.setText(MMKVName.UNIT_DATA[unit!!])
             }
         }
     }

+ 25 - 23
module_backstage/src/main/java/com/module/backstage/adapter/TestAdapter.kt

@@ -2,22 +2,21 @@ package com.module.backstage.adapter
 
 import ZtlApi.ZtlManager
 import android.annotation.SuppressLint
-import android.app.XzjhSystemManager
 import android.content.Context
 import android.content.Intent
 import android.media.AudioManager
-import android.os.Build
 import android.os.Handler
 import android.provider.Settings
-import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
-import android.view.View.OnLongClickListener
 import android.view.ViewGroup
 import android.widget.AdapterView
 import android.widget.AdapterView.OnItemSelectedListener
 import android.widget.ArrayAdapter
+import android.widget.ListPopupWindow
 import android.widget.SeekBar
+import android.widget.Spinner
+import androidx.appcompat.widget.AppCompatSpinner
 import androidx.core.content.ContextCompat.startActivity
 import androidx.recyclerview.widget.RecyclerView
 import com.chad.library.adapter4.BaseMultiItemAdapter
@@ -34,13 +33,13 @@ import com.module.pay.common.OtherEnum
 import com.quyunshuo.sbm10.base.utils.SpUtils
 import com.quyunshuo.sbm10.base.utils.XLogUtil
 import com.quyunshuo.sbm10.common.constant.MMKVName
-import com.quyunshuo.sbm10.common.constant.MqName
 import com.quyunshuo.sbm10.common.constant.event.ApiMessageEvent
 import com.quyunshuo.sbm10.common.listener.AdapterClickListener
 import com.quyunshuo.sbm10.common.util.LongClickUtils
 import com.quyunshuo.sbm10.common.util.ToastUtil
 import com.quyunshuo.sbm10.common.util.UiUtil
 import org.greenrobot.eventbus.EventBus
+import java.lang.reflect.Field
 
 
 class TestAdapter(var productList: MutableList<OtherEnum>) :
@@ -241,25 +240,28 @@ class TestAdapter(var productList: MutableList<OtherEnum>) :
 
                 private fun initSpinner(mBinding: BackstageItemSpinnerBinding, item: OtherEnum) {
                     //需要进行比较特殊的处理。比如语言的不同。
-                    var language2ArrayAdapter =
-                        ArrayAdapter<String>(this@TestAdapter.context, R.layout.spinner_text)
-                    language2ArrayAdapter.addAll(item.default as ArrayList<String>)
-                    mBinding.spMultiple.adapter = language2ArrayAdapter
-                    mBinding.spMultiple.setOnItemSelectedListener(object : OnItemSelectedListener {
-                        override fun onItemSelected(
-                            parent: AdapterView<*>?,
-                            view: View?,
-                            position: Int,
-                            id: Long,
-                        ) {
+//                    var language2ArrayAdapter =
+//                        ArrayAdapter<String>(this@TestAdapter.context, R.layout.spinner_text)
+//                    language2ArrayAdapter.addAll(item.default as ArrayList<String>)
+//                    mBinding.spMultiple.adapter = language2ArrayAdapter
+                    mBinding.spMultiple.setItems(item.default as ArrayList<String>)
+                    mBinding.spMultiple.setOnItemSelectedListener { view, position, id, item ->
                             this@TestAdapter.type3Position = position
-
-                        }
-
-                        override fun onNothingSelected(parent: AdapterView<*>?) {
-                        }
-                    })
-                    mBinding.spMultiple.setSelection(SpUtils.getInt(item.mmkvName, 0)!!)
+                    }
+//                    object : OnItemSelectedListener {
+//                        override fun onItemSelected(
+//                            parent: AdapterView<*>?,
+//                            view: View?,
+//                            position: Int,
+//                            id: Long,
+//                        ) {
+//                            this@TestAdapter.type3Position = position
+//                        }
+//                        override fun onNothingSelected(parent: AdapterView<*>?) {
+//                        }
+//                    })
+                    mBinding.spMultiple.setSelectedIndex(SpUtils.getInt(item.mmkvName, 0)!!)
+//                    mBinding.spMultiple.setSelectedIndex(SpUtils.getInt(item.mmkvName, 0)!!)
                 }
 
                 override fun onCreate(

+ 3 - 1
module_backstage/src/main/java/com/module/backstage/repo/SystemSettingsFragmentRepo.kt

@@ -77,6 +77,7 @@ class SystemSettingsFragmentRepo @Inject constructor() : BaseRepository() {
         OtherEnum.AD_RULE,
         OtherEnum.AUTO_RETURN_HOME,
         OtherEnum.SLEEP_TEXT,
+        OtherEnum.CURRENCY_UNIT
     )
     //其他
     var otherSetListLogo: MutableList<OtherEnum> = Arrays.asList(
@@ -85,7 +86,8 @@ class SystemSettingsFragmentRepo @Inject constructor() : BaseRepository() {
         OtherEnum.AD_RULE,
         OtherEnum.AUTO_RETURN_HOME,
         OtherEnum.SLEEP_TEXT,
-        OtherEnum.LOGO_TEXT
+        OtherEnum.LOGO_TEXT,
+        OtherEnum.CURRENCY_UNIT
     )
 
 }

+ 1 - 1
module_backstage/src/main/res/layout/backstage_fragment_other.xml

@@ -2,7 +2,7 @@
 <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:layout_width="match_parent"
-    android:layout_height="match_parent">
+    android:layout_height="2080dp">
 
     <net.lucode.hackware.magicindicator.MagicIndicator
         android:id="@+id/magic_other"

+ 5 - 3
module_backstage/src/main/res/layout/backstage_item_spinner.xml

@@ -18,13 +18,15 @@
         app:layout_constraintStart_toStartOf="parent"
         app:layout_constraintTop_toTopOf="@+id/sp_multiple" />
 
-    <androidx.appcompat.widget.AppCompatSpinner
+    <com.jaredrummler.materialspinner.MaterialSpinner
         android:id="@+id/sp_multiple"
-        android:layout_width="wrap_content"
+        android:layout_width="200dp"
         android:minWidth="200dp"
-        android:layout_height="100dp"
+        android:layout_height="wrap_content"
         android:layout_marginStart="20dp"
         android:layout_marginEnd="8dp"
+        app:ms_background_color="#faf8dd"
+        app:ms_dropdown_max_height="800dp"
         android:textSize="52sp"
         app:layout_constraintBottom_toBottomOf="parent"
         app:layout_constraintStart_toEndOf="@+id/tv_name"

+ 4 - 1
module_home/src/main/java/com/quyunshuo/module/home/adapter/HomeProductAdapter.kt

@@ -1,10 +1,12 @@
 package com.quyunshuo.module.home.adapter
 
+import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import androidx.recyclerview.widget.RecyclerView
 import com.bumptech.glide.Glide
+import com.module.pay.common.OtherEnum
 import com.quyunshuo.sbm10.common.bean.ProductDataBean
 import com.quyunshuo.sbm10.common.constant.MMKVName
 import com.quyunshuo.sbm10.common.listener.AdapterClickListener
@@ -17,7 +19,7 @@ class HomeProductAdapter(var productList:ArrayList<ProductDataBean>,val material
     private val TAG = "HomeProductAdapter"
     var listener:AdapterClickListener? = null
     private var material = SpUtils.getBoolean(MMKVName.MATERIAL,false)
-
+    private val unit= SpUtils.getInt(MMKVName.CURRENCY_UNIT,0)
     inner class MyViewHolder(binding: HomeItemHomeProductBinding): RecyclerView.ViewHolder(binding.root){
         private val mBinding = binding
         fun bind(productDataBean: ProductDataBean) {
@@ -40,6 +42,7 @@ class HomeProductAdapter(var productList:ArrayList<ProductDataBean>,val material
                 }
                 tvName.typeface = MMKVName.typeface1
 
+                tvUnit.setText (MMKVName.UNIT_DATA[unit!!])
                 //价格
                 tvPrice.setText(""+productDataBean.price)
 

+ 11 - 3
module_pay/src/main/java/com/module/pay/common/OtherEnum.kt

@@ -53,7 +53,7 @@ enum class OtherEnum(var nameId:Int,var nameS:String,var category:String,var def
     AUTO_RETURN_HOME(R.string.auto_return_home,"自动返回首页(分钟)","其他","10", 2,MMKVName.AUTO_RETURN_HOME),
     SLEEP_TEXT(R.string.sleep_text,"休眠中文本","其他","", 7,MMKVName.SLEEP_TEXT),
     LOGO_TEXT(R.string.logo_text,"图标变更","其他","7777", 2,MMKVName.LOGO_TEXT),
-
+    CURRENCY_UNIT(R.string.currency_unit,"货币单位","其他",ArrayList<String>(MMKVName.UNIT_DATA), 3,MMKVName.CURRENCY_UNIT),
 
     SHOPPING_TROLLEY(R.string.shopping_trolley,"购物车功能","购物车设置",true, 1,MMKVName.SHOPPING_TROLLEY),
     SHOPPING_CART_SIZE(R.string.shopping_cart_size,"购物车可添加数量","购物车设置","3", 2,MMKVName.SHOPPING_CART_SIZE),
@@ -71,5 +71,13 @@ enum class OtherEnum(var nameId:Int,var nameS:String,var category:String,var def
     CHANGE_ALLOW_NUMBER(R.string.change_allow_number,"允许一次找零个数","找零设置","7", 2,MMKVName.CHANGE_ALLOW_NUMBER),
     CHANGE_WARNING_NUMBER(R.string.change_warning_number,"找零预警库存","找零设置","100", 2,MMKVName.CHANGE_WARNING_NUMBER),
     COIN_MULTIPLY(R.string.coin_multiply,"硬币脉冲相乘","找零设置","1", 2,MMKVName.COIN_PULSE_MULTIPLY),
-    COIN_DIVIDE(R.string.coin_divide,"硬币脉冲相除","找零设置","1", 2,MMKVName.COIN_PULSE_DIVIDE),
-}
+    COIN_DIVIDE(R.string.coin_divide,"硬币脉冲相除","找零设置","1", 2,MMKVName.COIN_PULSE_DIVIDE),;
+}
+
+fun unitList() =
+    listOf("¥", "$","€","руб","៛","₪","₴","zł",
+        "Ұ","NT","₩","₮","〒","S/.","R$","৲৳","Rp",
+        "Sk","R","Q","P","₱","₦","Lm","₤","£",
+        "kr","₭","₲","Rs.","Ft","ƒ","₫","₡","₵",
+        "Br","Bs","¤",
+        "﷼")

+ 1 - 0
settings.gradle.kts

@@ -40,3 +40,4 @@ include(":serialportlib")
 include(":zloadingview")
 
 include(":barchartlib")
+include(":spinnerlib")

+ 1 - 0
spinnerlib/.gitignore

@@ -0,0 +1 @@
+/build

+ 21 - 0
spinnerlib/build.gradle

@@ -0,0 +1,21 @@
+apply plugin: 'com.android.library'
+
+android {
+  namespace 'com.github.jaredrummler'  // <-- 在这里添加 namespace
+
+  compileSdkVersion 28
+  buildToolsVersion '28.0.3'
+  resourcePrefix 'ms__'
+
+  defaultConfig {
+    minSdkVersion 14
+    targetSdkVersion 28
+  }
+}
+
+dependencies {
+  implementation 'androidx.annotation:annotation:1.0.0'
+  testImplementation 'junit:junit:4.12'
+}
+
+//apply from: rootProject.file('gradle/maven-push.gradle')

+ 21 - 0
spinnerlib/gradle.properties

@@ -0,0 +1,21 @@
+VERSION_NAME=1.3.1
+VERSION_CODE=131
+GROUP=com.jaredrummler
+
+POM_NAME=Material Spinner
+POM_ARTIFACT_ID=material-spinner
+POM_PACKAGING=aar
+
+POM_DESCRIPTION=A spinner view for Android
+POM_URL=https://github.com/jaredrummler/Material-Spinner
+POM_SCM_URL=https://github.com/jaredrummler/MaterialSpinner
+POM_SCM_CONNECTION=scm:git@github.com:jaredrummler/MaterialSpinner.git
+POM_SCM_DEV_CONNECTION=scm:git@github.com:jaredrummler/MaterialSpinner.git
+POM_LICENCE_NAME=The Apache Software License, Version 2.0
+POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt
+POM_LICENCE_DIST=repo
+POM_DEVELOPER_ID=jaredrummler
+POM_DEVELOPER_NAME=Jared Rummler
+
+SNAPSHOT_REPOSITORY_URL=https://oss.sonatype.org/content/repositories/snapshots
+RELEASE_REPOSITORY_URL=https://oss.sonatype.org/service/local/staging/deploy/maven2

+ 17 - 0
spinnerlib/proguard-rules.pro

@@ -0,0 +1,17 @@
+# Add project specific ProGuard rules here.
+# By default, the flags in this file are appended to flags specified
+# in C:\android-sdk/tools/proguard/proguard-android.txt
+# You can edit the include path and order by changing the proguardFiles
+# directive in build.gradle.
+#
+# For more details, see
+#   http://developer.android.com/guide/developing/tools/proguard.html
+
+# Add any project specific keep options here:
+
+# If your project uses WebView with JS, uncomment the following
+# and specify the fully qualified class name to the JavaScript interface
+# class:
+#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
+#   public *;
+#}

+ 6 - 0
spinnerlib/src/main/AndroidManifest.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <application>
+    </application>
+
+</manifest>

+ 639 - 0
spinnerlib/src/main/java/com/jaredrummler/materialspinner/MaterialSpinner.java

@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2016 Jared Rummler
+ *
+ * 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.jaredrummler.materialspinner;
+
+import android.animation.ObjectAnimator;
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.StateListDrawable;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.AdapterView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.github.jaredrummler.R;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A spinner that shows a {@link PopupWindow} under the view when clicked.
+ */
+public class MaterialSpinner extends TextView {
+
+  private OnNothingSelectedListener onNothingSelectedListener;
+  private OnItemSelectedListener onItemSelectedListener;
+  private MaterialSpinnerBaseAdapter adapter;
+  private PopupWindow popupWindow;
+  private ListView listView;
+  private Drawable arrowDrawable;
+  private boolean hideArrow;
+  private boolean nothingSelected;
+  private int popupWindowMaxHeight;
+  private int popupWindowHeight;
+  private int selectedIndex;
+  private int backgroundColor;
+  private int backgroundSelector;
+  private int arrowColor;
+  private int arrowColorDisabled;
+  private int textColor;
+  private int hintColor;
+  private int popupPaddingTop;
+  private int popupPaddingLeft;
+  private int popupPaddingBottom;
+  private int popupPaddingRight;
+  private String hintText;
+
+  public MaterialSpinner(Context context) {
+    super(context);
+    init(context, null);
+  }
+
+  public MaterialSpinner(Context context, AttributeSet attrs) {
+    super(context, attrs);
+    init(context, attrs);
+  }
+
+  public MaterialSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
+    super(context, attrs, defStyleAttr);
+    init(context, attrs);
+  }
+
+  private void init(Context context, AttributeSet attrs) {
+    TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.MaterialSpinner);
+    int defaultColor = getTextColors().getDefaultColor();
+    boolean rtl = Utils.isRtl(context);
+
+    int paddingLeft, paddingTop, paddingRight, paddingBottom;
+    int defaultPaddingLeft, defaultPaddingTop, defaultPaddingRight, defaultPaddingBottom;
+    int defaultPopupPaddingLeft, defaultPopupPaddingTop, defaultPopupPaddingRight, defaultPopupPaddingBottom;
+
+    Resources resources = getResources();
+    defaultPaddingLeft = defaultPaddingRight =
+        defaultPaddingBottom = defaultPaddingTop = resources.getDimensionPixelSize(R.dimen.ms__padding_top);
+    if (rtl) {
+      defaultPaddingRight = resources.getDimensionPixelSize(R.dimen.ms__padding_left);
+    } else {
+      defaultPaddingLeft = resources.getDimensionPixelSize(R.dimen.ms__padding_left);
+    }
+    defaultPopupPaddingLeft =
+        defaultPopupPaddingRight = resources.getDimensionPixelSize(R.dimen.ms__popup_padding_left);
+    defaultPopupPaddingTop = defaultPopupPaddingBottom = resources.getDimensionPixelSize(R.dimen.ms__popup_padding_top);
+
+    try {
+      backgroundColor = ta.getColor(R.styleable.MaterialSpinner_ms_background_color, Color.WHITE);
+      backgroundSelector = ta.getResourceId(R.styleable.MaterialSpinner_ms_background_selector, 0);
+      textColor = ta.getColor(R.styleable.MaterialSpinner_ms_text_color, defaultColor);
+      hintColor = ta.getColor(R.styleable.MaterialSpinner_ms_hint_color, defaultColor);
+      arrowColor = ta.getColor(R.styleable.MaterialSpinner_ms_arrow_tint, textColor);
+      hideArrow = ta.getBoolean(R.styleable.MaterialSpinner_ms_hide_arrow, false);
+      hintText = ta.getString(R.styleable.MaterialSpinner_ms_hint) == null ? ""
+          : ta.getString(R.styleable.MaterialSpinner_ms_hint);
+      popupWindowMaxHeight = ta.getDimensionPixelSize(R.styleable.MaterialSpinner_ms_dropdown_max_height, 0);
+      popupWindowHeight = ta.getLayoutDimension(R.styleable.MaterialSpinner_ms_dropdown_height,
+          WindowManager.LayoutParams.WRAP_CONTENT);
+      paddingTop = ta.getDimensionPixelSize(R.styleable.MaterialSpinner_ms_padding_top, defaultPaddingTop);
+      paddingLeft = ta.getDimensionPixelSize(R.styleable.MaterialSpinner_ms_padding_left, defaultPaddingLeft);
+      paddingBottom = ta.getDimensionPixelSize(R.styleable.MaterialSpinner_ms_padding_bottom, defaultPaddingBottom);
+      paddingRight = ta.getDimensionPixelSize(R.styleable.MaterialSpinner_ms_padding_right, defaultPaddingRight);
+      popupPaddingTop =
+          ta.getDimensionPixelSize(R.styleable.MaterialSpinner_ms_popup_padding_top, defaultPopupPaddingTop);
+      popupPaddingLeft =
+          ta.getDimensionPixelSize(R.styleable.MaterialSpinner_ms_popup_padding_left, defaultPopupPaddingLeft);
+      popupPaddingBottom =
+          ta.getDimensionPixelSize(R.styleable.MaterialSpinner_ms_popup_padding_bottom, defaultPopupPaddingBottom);
+      popupPaddingRight =
+          ta.getDimensionPixelSize(R.styleable.MaterialSpinner_ms_popup_padding_right, defaultPopupPaddingRight);
+      arrowColorDisabled = Utils.lighter(arrowColor, 0.8f);
+    } finally {
+      ta.recycle();
+    }
+
+    nothingSelected = true;
+
+    setGravity(Gravity.CENTER_VERTICAL | Gravity.START);
+    setClickable(true);
+    setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom);
+
+    setBackgroundResource(R.drawable.ms__selector);
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && rtl) {
+      setLayoutDirection(View.LAYOUT_DIRECTION_RTL);
+      setTextDirection(View.TEXT_DIRECTION_RTL);
+    }
+
+    if (!hideArrow) {
+      arrowDrawable = Utils.getDrawable(context, R.drawable.ms__arrow).mutate();
+      arrowDrawable.setColorFilter(arrowColor, PorterDuff.Mode.SRC_IN);
+      Drawable[] drawables = getCompoundDrawables();
+      if (rtl) {
+        drawables[0] = arrowDrawable;
+      } else {
+        drawables[2] = arrowDrawable;
+      }
+      setCompoundDrawablesWithIntrinsicBounds(drawables[0], drawables[1], drawables[2], drawables[3]);
+    }
+
+    listView = new ListView(context);
+    listView.setId(getId());
+    listView.setDivider(null);
+    listView.setItemsCanFocus(true);
+    listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
+
+      @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+        if (position >= selectedIndex
+            && position < adapter.getCount()
+            && adapter.getItems().size() != 1
+            && TextUtils.isEmpty(hintText)) {
+          position++;
+        }
+        selectedIndex = position;
+        nothingSelected = false;
+        Object item = adapter.get(position);
+        adapter.notifyItemSelected(position);
+        setTextColor(textColor);
+        setText(item.toString());
+        collapse();
+        if (onItemSelectedListener != null) {
+          //noinspection unchecked
+          onItemSelectedListener.onItemSelected(MaterialSpinner.this, position, id, item);
+        }
+      }
+    });
+
+    popupWindow = new PopupWindow(context);
+    popupWindow.setContentView(listView);
+    popupWindow.setOutsideTouchable(true);
+    popupWindow.setFocusable(true);
+
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+      popupWindow.setElevation(16);
+      popupWindow.setBackgroundDrawable(Utils.getDrawable(context, R.drawable.ms__drawable));
+    } else {
+      popupWindow.setBackgroundDrawable(Utils.getDrawable(context, R.drawable.ms__drop_down_shadow));
+    }
+
+    if (backgroundColor != Color.WHITE) { // default color is white
+      setBackgroundColor(backgroundColor);
+    } else if (backgroundSelector != 0) {
+      setBackgroundResource(backgroundSelector);
+    }
+    if (textColor != defaultColor) {
+      setTextColor(textColor);
+    }
+
+    popupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
+
+      @Override public void onDismiss() {
+        if (nothingSelected && onNothingSelectedListener != null) {
+          onNothingSelectedListener.onNothingSelected(MaterialSpinner.this);
+        }
+        if (!hideArrow) {
+          animateArrow(false);
+        }
+      }
+    });
+  }
+
+  @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+    popupWindow.setWidth(MeasureSpec.getSize(widthMeasureSpec));
+    popupWindow.setHeight(calculatePopupWindowHeight());
+    if (adapter != null) {
+      CharSequence currentText = getText();
+      String longestItem = currentText.toString();
+      for (int i = 0; i < adapter.getCount(); i++) {
+        String itemText = adapter.getItemText(i);
+        if (itemText.length() > longestItem.length()) {
+          longestItem = itemText;
+        }
+      }
+      setText(longestItem);
+      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+      setText(currentText);
+    } else {
+      super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+    }
+  }
+
+  @Override public boolean onTouchEvent(@NonNull MotionEvent event) {
+    if (event.getAction() == MotionEvent.ACTION_UP) {
+      if (isEnabled() && isClickable()) {
+        if (!popupWindow.isShowing()) {
+          expand();
+        } else {
+          collapse();
+        }
+      }
+    }
+    return super.onTouchEvent(event);
+  }
+
+  @Override public void setBackgroundColor(int color) {
+    backgroundColor = color;
+    Drawable background = getBackground();
+    if (background instanceof StateListDrawable) { // pre-L
+      try {
+        Method getStateDrawable = StateListDrawable.class.getDeclaredMethod("getStateDrawable", int.class);
+        if (!getStateDrawable.isAccessible()) getStateDrawable.setAccessible(true);
+        int[] colors = { Utils.darker(color, 0.85f), color };
+        for (int i = 0; i < colors.length; i++) {
+          ColorDrawable drawable = (ColorDrawable) getStateDrawable.invoke(background, i);
+          drawable.setColor(colors[i]);
+        }
+      } catch (Exception e) {
+        Log.e("MaterialSpinner", "Error setting background color", e);
+      }
+    } else if (background != null) { // 21+ (RippleDrawable)
+      background.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+    }
+    popupWindow.getBackground().setColorFilter(color, PorterDuff.Mode.SRC_IN);
+  }
+
+  @Override public void setTextColor(int color) {
+    textColor = color;
+    if (adapter != null) {
+      adapter.setTextColor(textColor);
+      adapter.notifyDataSetChanged();
+    }
+    super.setTextColor(color);
+  }
+
+  public void setHintColor(int color) {
+    hintColor = color;
+    super.setTextColor(color);
+  }
+
+  @Override public Parcelable onSaveInstanceState() {
+    Bundle bundle = new Bundle();
+    bundle.putParcelable("state", super.onSaveInstanceState());
+    bundle.putInt("selected_index", selectedIndex);
+    bundle.putBoolean("nothing_selected", nothingSelected);
+    if (popupWindow != null) {
+      bundle.putBoolean("is_popup_showing", popupWindow.isShowing());
+      collapse();
+    } else {
+      bundle.putBoolean("is_popup_showing", false);
+    }
+    return bundle;
+  }
+
+  @Override public void onRestoreInstanceState(Parcelable savedState) {
+    if (savedState instanceof Bundle) {
+      Bundle bundle = (Bundle) savedState;
+      selectedIndex = bundle.getInt("selected_index");
+      nothingSelected = bundle.getBoolean("nothing_selected");
+      if (adapter != null) {
+        if (nothingSelected && !TextUtils.isEmpty(hintText)) {
+          setHintColor(hintColor);
+          setText(hintText);
+        } else {
+          setTextColor(textColor);
+          setText(adapter.get(selectedIndex).toString());
+        }
+        adapter.notifyItemSelected(selectedIndex);
+      }
+      if (bundle.getBoolean("is_popup_showing")) {
+        if (popupWindow != null) {
+          // Post the show request into the looper to avoid bad token exception
+          post(new Runnable() {
+
+            @Override public void run() {
+              expand();
+            }
+          });
+        }
+      }
+      savedState = bundle.getParcelable("state");
+    }
+    super.onRestoreInstanceState(savedState);
+  }
+
+  @Override public void setEnabled(boolean enabled) {
+    super.setEnabled(enabled);
+    if (arrowDrawable != null) {
+      arrowDrawable.setColorFilter(enabled ? arrowColor : arrowColorDisabled, PorterDuff.Mode.SRC_IN);
+    }
+  }
+
+  /**
+   * @return the selected item position
+   */
+  public int getSelectedIndex() {
+    return selectedIndex;
+  }
+
+  /**
+   * Set the default spinner item using its index
+   *
+   * @param position the item's position
+   */
+  public void setSelectedIndex(int position) {
+    if (adapter != null) {
+      if (position >= 0 && position <= adapter.getCount()) {
+        adapter.notifyItemSelected(position);
+        selectedIndex = position;
+        setText(adapter.get(position).toString());
+      } else {
+        throw new IllegalArgumentException("Position must be lower than adapter count!");
+      }
+    }
+  }
+
+  /**
+   * Register a callback to be invoked when an item in the dropdown is selected.
+   *
+   * @param onItemSelectedListener The callback that will run
+   */
+  public void setOnItemSelectedListener(@Nullable OnItemSelectedListener onItemSelectedListener) {
+    this.onItemSelectedListener = onItemSelectedListener;
+  }
+
+  /**
+   * Register a callback to be invoked when the {@link PopupWindow} is shown but the user didn't select an item.
+   *
+   * @param onNothingSelectedListener the callback that will run
+   */
+  public void setOnNothingSelectedListener(@Nullable OnNothingSelectedListener onNothingSelectedListener) {
+    this.onNothingSelectedListener = onNothingSelectedListener;
+  }
+
+  /**
+   * Set the dropdown items
+   *
+   * @param items A list of items
+   * @param <T> The item type
+   */
+  public <T> void setItems(@NonNull T... items) {
+    setItems(Arrays.asList(items));
+  }
+
+  /**
+   * Set the dropdown items
+   *
+   * @param items A list of items
+   * @param <T> The item type
+   */
+  public <T> void setItems(@NonNull List<T> items) {
+    adapter = new MaterialSpinnerAdapter<>(getContext(), items)
+        .setPopupPadding(popupPaddingLeft, popupPaddingTop, popupPaddingRight, popupPaddingBottom)
+        .setBackgroundSelector(backgroundSelector)
+        .setTextColor(textColor);
+    setAdapterInternal(adapter);
+  }
+
+  /**
+   * Set a custom adapter for the dropdown items
+   *
+   * @param adapter The list adapter
+   */
+  public void setAdapter(@NonNull ListAdapter adapter) {
+    this.adapter = new MaterialSpinnerAdapterWrapper(getContext(), adapter)
+        .setPopupPadding(popupPaddingLeft, popupPaddingTop, popupPaddingRight, popupPaddingBottom)
+        .setBackgroundSelector(backgroundSelector)
+        .setTextColor(textColor);
+    setAdapterInternal(this.adapter);
+  }
+
+  /**
+   * Set the custom adapter for the dropdown items
+   *
+   * @param adapter The adapter
+   * @param <T> The type
+   */
+  public <T> void setAdapter(MaterialSpinnerAdapter<T> adapter) {
+    this.adapter = adapter;
+    this.adapter.setTextColor(textColor);
+    this.adapter.setBackgroundSelector(backgroundSelector);
+    this.adapter.setPopupPadding(popupPaddingLeft, popupPaddingTop, popupPaddingRight, popupPaddingBottom);
+    setAdapterInternal(adapter);
+  }
+
+  private void setAdapterInternal(@NonNull MaterialSpinnerBaseAdapter adapter) {
+    boolean shouldResetPopupHeight = listView.getAdapter() != null;
+    adapter.setHintEnabled(!TextUtils.isEmpty(hintText));
+    listView.setAdapter(adapter);
+    if (selectedIndex >= adapter.getCount()) {
+      selectedIndex = 0;
+    }
+    if (adapter.getItems().size() > 0) {
+      if (nothingSelected && !TextUtils.isEmpty(hintText)) {
+        setText(hintText);
+        setHintColor(hintColor);
+      } else {
+        setTextColor(textColor);
+        setText(adapter.get(selectedIndex).toString());
+      }
+    } else {
+      setText("");
+    }
+    if (shouldResetPopupHeight) {
+      popupWindow.setHeight(calculatePopupWindowHeight());
+    }
+  }
+
+  /**
+   * Get the list of items in the adapter
+   *
+   * @param <T> The item type
+   * @return A list of items or {@code null} if no items are set.
+   */
+  public <T> List<T> getItems() {
+    if (adapter == null) {
+      return null;
+    }
+    //noinspection unchecked
+    return adapter.getItems();
+  }
+
+  /**
+   * Show the dropdown menu
+   */
+  public void expand() {
+    if (canShowPopup()) {
+      if (!hideArrow) {
+        animateArrow(true);
+      }
+      nothingSelected = true;
+      popupWindow.showAsDropDown(this);
+    }
+  }
+
+  /**
+   * Closes the dropdown menu
+   */
+  public void collapse() {
+    if (!hideArrow) {
+      animateArrow(false);
+    }
+    popupWindow.dismiss();
+  }
+
+  /**
+   * Set the tint color for the dropdown arrow
+   *
+   * @param color the color value
+   */
+  public void setArrowColor(@ColorInt int color) {
+    arrowColor = color;
+    arrowColorDisabled = Utils.lighter(arrowColor, 0.8f);
+    if (arrowDrawable != null) {
+      arrowDrawable.setColorFilter(arrowColor, PorterDuff.Mode.SRC_IN);
+    }
+  }
+
+  private boolean canShowPopup() {
+    Activity activity = getActivity();
+    if (activity == null || activity.isFinishing()) {
+      return false;
+    }
+    boolean isLaidOut;
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
+      isLaidOut = isLaidOut();
+    } else {
+      isLaidOut = getWidth() > 0 && getHeight() > 0;
+    }
+    return isLaidOut;
+  }
+
+  private Activity getActivity() {
+    Context context = getContext();
+    while (context instanceof ContextWrapper) {
+      if (context instanceof Activity) {
+        return (Activity) context;
+      }
+      context = ((ContextWrapper) context).getBaseContext();
+    }
+    return null;
+  }
+
+  private void animateArrow(boolean shouldRotateUp) {
+    int start = shouldRotateUp ? 0 : 10000;
+    int end = shouldRotateUp ? 10000 : 0;
+    ObjectAnimator animator = ObjectAnimator.ofInt(arrowDrawable, "level", start, end);
+    animator.start();
+  }
+
+  /**
+   * Set the maximum height of the dropdown menu.
+   *
+   * @param height the height in pixels
+   */
+  public void setDropdownMaxHeight(int height) {
+    popupWindowMaxHeight = height;
+    popupWindow.setHeight(calculatePopupWindowHeight());
+  }
+
+  /**
+   * Set the height of the dropdown menu
+   *
+   * @param height the height in pixels
+   */
+  public void setDropdownHeight(int height) {
+    popupWindowHeight = height;
+    popupWindow.setHeight(calculatePopupWindowHeight());
+  }
+
+  private int calculatePopupWindowHeight() {
+    if (adapter == null) {
+      return WindowManager.LayoutParams.WRAP_CONTENT;
+    }
+    float itemHeight = getResources().getDimension(R.dimen.ms__item_height);
+    float listViewHeight = adapter.getCount() * itemHeight;
+    if (popupWindowMaxHeight > 0 && listViewHeight > popupWindowMaxHeight) {
+      return popupWindowMaxHeight;
+    } else if (popupWindowHeight != WindowManager.LayoutParams.MATCH_PARENT
+        && popupWindowHeight != WindowManager.LayoutParams.WRAP_CONTENT
+        && popupWindowHeight <= listViewHeight) {
+      return popupWindowHeight;
+    } else if (listViewHeight == 0 && adapter.getItems().size() == 1) {
+      return (int) itemHeight;
+    }
+    return WindowManager.LayoutParams.WRAP_CONTENT;
+  }
+
+  /**
+   * Get the {@link PopupWindow}.
+   *
+   * @return The {@link PopupWindow} that is displayed when the view has been clicked.
+   */
+  public PopupWindow getPopupWindow() {
+    return popupWindow;
+  }
+
+  /**
+   * Get the {@link ListView} that is used in the dropdown menu
+   *
+   * @return the ListView shown in the PopupWindow.
+   */
+  public ListView getListView() {
+    return listView;
+  }
+
+  /**
+   * Interface definition for a callback to be invoked when an item in this view has been selected.
+   *
+   * @param <T> Adapter item type
+   */
+  public interface OnItemSelectedListener<T> {
+
+    /**
+     * <p>Callback method to be invoked when an item in this view has been selected. This callback is invoked only when
+     * the newly selected position is different from the previously selected position or if there was no selected
+     * item.</p>
+     *
+     * @param view The {@link MaterialSpinner} view
+     * @param position The position of the view in the adapter
+     * @param id The row id of the item that is selected
+     * @param item The selected item
+     */
+    void onItemSelected(MaterialSpinner view, int position, long id, T item);
+  }
+
+  /**
+   * Interface definition for a callback to be invoked when the dropdown is dismissed and no item was selected.
+   */
+  public interface OnNothingSelectedListener {
+
+    /**
+     * Callback method to be invoked when the {@link PopupWindow} is dismissed and no item was selected.
+     *
+     * @param spinner the {@link MaterialSpinner}
+     */
+    void onNothingSelected(MaterialSpinner spinner);
+  }
+}

+ 55 - 0
spinnerlib/src/main/java/com/jaredrummler/materialspinner/MaterialSpinnerAdapter.java

@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 Jared Rummler
+ *
+ * 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.jaredrummler.materialspinner;
+
+import android.content.Context;
+import java.util.List;
+
+public class MaterialSpinnerAdapter<T> extends MaterialSpinnerBaseAdapter {
+
+  private final List<T> items;
+
+  public MaterialSpinnerAdapter(Context context, List<T> items) {
+    super(context);
+    this.items = items;
+  }
+
+  @Override public int getCount() {
+    int size = items.size();
+    if (size == 1 || isHintEnabled()) return size;
+    return size - 1;
+  }
+
+  @Override public T getItem(int position) {
+    if (isHintEnabled()) {
+      return items.get(position);
+    } else if (position >= getSelectedIndex() && items.size() != 1) {
+      return items.get(position + 1);
+    } else {
+      return items.get(position);
+    }
+  }
+
+  @Override public T get(int position) {
+    return items.get(position);
+  }
+
+  @Override public List<T> getItems() {
+    return items;
+  }
+}

+ 61 - 0
spinnerlib/src/main/java/com/jaredrummler/materialspinner/MaterialSpinnerAdapterWrapper.java

@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2016 Jared Rummler
+ *
+ * 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.jaredrummler.materialspinner;
+
+import android.content.Context;
+import android.widget.ListAdapter;
+import java.util.ArrayList;
+import java.util.List;
+
+final class MaterialSpinnerAdapterWrapper extends MaterialSpinnerBaseAdapter {
+
+  private final ListAdapter listAdapter;
+
+  public MaterialSpinnerAdapterWrapper(Context context, ListAdapter toWrap) {
+    super(context);
+    listAdapter = toWrap;
+  }
+
+  @Override public int getCount() {
+    int size = listAdapter.getCount();
+    if (size == 1 || isHintEnabled()) return size;
+    return size - 1;
+  }
+
+  @Override public Object getItem(int position) {
+    if (isHintEnabled()) {
+      return listAdapter.getItem(position);
+    } else if (position >= getSelectedIndex() && listAdapter.getCount() != 1) {
+      return listAdapter.getItem(position + 1);
+    } else {
+      return listAdapter.getItem(position);
+    }
+  }
+
+  @Override public Object get(int position) {
+    return listAdapter.getItem(position);
+  }
+
+  @Override public List<Object> getItems() {
+    List<Object> items = new ArrayList<>();
+    for (int i = 0; i < listAdapter.getCount(); i++) {
+      items.add(listAdapter.getItem(i));
+    }
+    return items;
+  }
+}

+ 170 - 0
spinnerlib/src/main/java/com/jaredrummler/materialspinner/MaterialSpinnerBaseAdapter.java

@@ -0,0 +1,170 @@
+/*
+ * Copyright (C) 2016 Jared Rummler
+ *
+ * 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.jaredrummler.materialspinner;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.os.Build;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+import android.widget.TextView;
+import androidx.annotation.ColorInt;
+import androidx.annotation.DrawableRes;
+
+
+import com.github.jaredrummler.R;
+
+import java.util.List;
+
+public abstract class MaterialSpinnerBaseAdapter<T> extends BaseAdapter {
+
+  private final Context context;
+  private int selectedIndex;
+  private int textColor;
+  private int backgroundSelector;
+  private int popupPaddingTop;
+  private int popupPaddingLeft;
+  private int popupPaddingBottom;
+  private int popupPaddingRight;
+  private boolean isHintEnabled;
+
+  public MaterialSpinnerBaseAdapter(Context context) {
+    this.context = context;
+  }
+
+  @Override public View getView(int position, View convertView, ViewGroup parent) {
+    final TextView textView;
+    if (convertView == null) {
+      LayoutInflater inflater = LayoutInflater.from(context);
+      convertView = inflater.inflate(R.layout.ms__list_item, parent, false);
+      textView = (TextView) convertView.findViewById(R.id.tv_tinted_spinner);
+      textView.setTextColor(textColor);
+
+      textView.setPadding(popupPaddingLeft, popupPaddingTop, popupPaddingRight, popupPaddingBottom);
+//
+//      int left, right, bottom, top;
+//      if (popupPaddingTop != -1) {
+//        left = textView.getPaddingLeft();
+//        right = textView.getPaddingRight();
+//        bottom = textView.getPaddingBottom();
+//
+//        textView.setPadding(left, popupPaddingTop, right, bottom);
+//      }
+//
+//      if (popupPaddingLeft != -1) {
+//        top = textView.getPaddingTop();
+//        right = textView.getPaddingRight();
+//        bottom = textView.getPaddingBottom();
+//
+//        textView.setPadding(popupPaddingLeft, top, right, bottom);
+//      }
+//
+//      if (popupPaddingBottom != -1) {
+//        left = textView.getPaddingLeft();
+//        top = textView.getPaddingTop();
+//        right = textView.getPaddingRight();
+//
+//        textView.setPadding(left, top, right, popupPaddingBottom);
+//      }
+//
+//      if (popupPaddingRight != -1) {
+//        left = textView.getPaddingLeft();
+//        top = textView.getPaddingTop();
+//        bottom = textView.getPaddingBottom();
+//
+//        textView.setPadding(left, top, popupPaddingRight, bottom);
+//      }
+
+      if (backgroundSelector != 0) {
+        textView.setBackgroundResource(backgroundSelector);
+      }
+      if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+        Configuration config = context.getResources().getConfiguration();
+        if (config.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL) {
+          textView.setTextDirection(View.TEXT_DIRECTION_RTL);
+        }
+      }
+      convertView.setTag(new ViewHolder(textView));
+    } else {
+      textView = ((ViewHolder) convertView.getTag()).textView;
+    }
+    textView.setText(getItemText(position));
+    return convertView;
+  }
+
+  public String getItemText(int position) {
+    return getItem(position).toString();
+  }
+
+  public int getSelectedIndex() {
+    return selectedIndex;
+  }
+
+  public void notifyItemSelected(int index) {
+    selectedIndex = index;
+  }
+
+  @Override public long getItemId(int position) {
+    return position;
+  }
+
+  @Override public abstract T getItem(int position);
+
+  @Override public abstract int getCount();
+
+  public abstract T get(int position);
+
+  public abstract List<T> getItems();
+
+  public void setHintEnabled(boolean isHintEnabled) {
+    this.isHintEnabled = isHintEnabled;
+  }
+
+  public boolean isHintEnabled() {
+    return this.isHintEnabled;
+  }
+
+  public MaterialSpinnerBaseAdapter<T> setTextColor(@ColorInt int textColor) {
+    this.textColor = textColor;
+    return this;
+  }
+
+  public MaterialSpinnerBaseAdapter<T> setBackgroundSelector(@DrawableRes int backgroundSelector) {
+    this.backgroundSelector = backgroundSelector;
+    return this;
+  }
+
+  public MaterialSpinnerBaseAdapter<T> setPopupPadding(int left, int top, int right, int bottom) {
+    this.popupPaddingLeft = left;
+    this.popupPaddingTop = top;
+    this.popupPaddingRight = right;
+    this.popupPaddingBottom = bottom;
+    return this;
+  }
+
+  private static class ViewHolder {
+
+    private TextView textView;
+
+    private ViewHolder(TextView textView) {
+      this.textView = textView;
+    }
+  }
+}

+ 83 - 0
spinnerlib/src/main/java/com/jaredrummler/materialspinner/Utils.java

@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 Jared Rummler
+ *
+ * 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.jaredrummler.materialspinner;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.view.View;
+
+final class Utils {
+
+  /**
+   * Darkens a color by a given factor.
+   *
+   * @param color the color to darken
+   * @param factor The factor to darken the color.
+   * @return darker version of specified color.
+   */
+  static int darker(int color, float factor) {
+    return Color.argb(Color.alpha(color), Math.max((int) (Color.red(color) * factor), 0),
+        Math.max((int) (Color.green(color) * factor), 0), Math.max((int) (Color.blue(color) * factor), 0));
+  }
+
+  /**
+   * Lightens a color by a given factor.
+   *
+   * @param color The color to lighten
+   * @param factor The factor to lighten the color. 0 will make the color unchanged. 1 will make the
+   * color white.
+   * @return lighter version of the specified color.
+   */
+  static int lighter(int color, float factor) {
+    int red = (int) ((Color.red(color) * (1 - factor) / 255 + factor) * 255);
+    int green = (int) ((Color.green(color) * (1 - factor) / 255 + factor) * 255);
+    int blue = (int) ((Color.blue(color) * (1 - factor) / 255 + factor) * 255);
+    return Color.argb(Color.alpha(color), red, green, blue);
+  }
+
+  /**
+   * Check if layout direction is RTL
+   *
+   * @param context the current context
+   * @return {@code true} if the layout direction is right-to-left
+   */
+  static boolean isRtl(Context context) {
+    return Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1
+        && context.getResources().getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
+  }
+
+  /**
+   * Return a drawable object associated with a particular resource ID.
+   *
+   * <p>Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the returned drawable will be styled for the
+   * specified Context's theme.</p>
+   *
+   * @param id The desired resource identifier, as generated by the aapt tool.
+   * This integer encodes the package, type, and resource entry.
+   * The value 0 is an invalid identifier.
+   * @return Drawable An object that can be used to draw this resource.
+   */
+  static Drawable getDrawable(Context context, int id) {
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+      return context.getDrawable(id);
+    }
+    return context.getResources().getDrawable(id);
+  }
+}

BIN
spinnerlib/src/main/res/drawable-hdpi/ms__drop_down_shadow.9.png


+ 30 - 0
spinnerlib/src/main/res/drawable-v21/ms__selector.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 Jared Rummler
+  ~
+  ~ 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.
+  ~
+  -->
+
+<ripple
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:color="?android:attr/colorControlHighlight">
+  <item
+      android:id="@android:id/mask"
+      android:drawable="@android:color/white"/>
+  <item android:bottom="1dp">
+    <shape>
+      <solid android:color="@android:color/white"/>
+    </shape>
+  </item>
+</ripple>

BIN
spinnerlib/src/main/res/drawable-xhdpi/ms__shadow_background.9.png


BIN
spinnerlib/src/main/res/drawable-xxhdpi/ms__shadow_background.9.png


+ 25 - 0
spinnerlib/src/main/res/drawable/ms__arrow.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 Jared Rummler
+  ~
+  ~ 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.
+  ~
+  -->
+
+<rotate
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:drawable="@drawable/ms__menu_down"
+    android:fromDegrees="0"
+    android:pivotX="50%"
+    android:pivotY="50%"
+    android:toDegrees="180"/>

+ 30 - 0
spinnerlib/src/main/res/drawable/ms__drawable.xml

@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  ~ Copyright (C) 2016 Jared Rummler
+  ~
+  ~ 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.
+  ~
+  -->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:top="3dp">
+    <shape android:shape="rectangle">
+      <solid android:color="@android:color/white"/>
+      <corners
+          android:bottomLeftRadius="1dp"
+          android:bottomRightRadius="1dp"
+          android:topLeftRadius="1dp"
+          android:topRightRadius="1dp"/>
+    </shape>
+  </item>
+</layer-list>

+ 28 - 0
spinnerlib/src/main/res/drawable/ms__menu_down.xml

@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 Jared Rummler
+  ~
+  ~ 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.
+  ~
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:height="24dp"
+    android:viewportHeight="24"
+    android:viewportWidth="24"
+    android:width="24dp">
+  <path
+      android:fillColor="#FFFFFFFF"
+      android:pathData="M7,10L12,15L17,10H7Z"/>
+</vector>

+ 22 - 0
spinnerlib/src/main/res/drawable/ms__selector.xml

@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 Jared Rummler
+  ~
+  ~ 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.
+  ~
+  -->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+  <item android:drawable="@android:color/darker_gray" android:state_pressed="true"/>
+  <item android:drawable="@android:color/white" android:state_pressed="false"/>
+</selector>

+ 36 - 0
spinnerlib/src/main/res/layout/ms__list_item.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 Jared Rummler
+  ~
+  ~ 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.
+  ~
+  -->
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/tv_tinted_spinner"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="start|center_vertical"
+    android:ellipsize="marquee"
+    android:gravity="center_vertical"
+    android:textSize="52sp"
+    android:minHeight="@dimen/ms__item_height"
+    android:paddingBottom="@dimen/ms__popup_padding_top"
+    android:paddingLeft="@dimen/ms__popup_padding_left"
+    android:paddingRight="@dimen/ms__popup_padding_left"
+    android:paddingStart="@dimen/ms__popup_padding_left"
+    android:paddingTop="@dimen/ms__popup_padding_top"
+    android:singleLine="true"
+    />
+

+ 43 - 0
spinnerlib/src/main/res/values/attrs.xml

@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 Jared Rummler
+  ~
+  ~ 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.
+  ~
+  -->
+
+<resources xmlns:tools="http://schemas.android.com/tools">
+  <declare-styleable name="MaterialSpinner" tools:ignore="ResourceName">
+    <attr format="color" name="ms_arrow_tint"/>
+    <attr format="boolean" name="ms_hide_arrow"/>
+    <attr format="color" name="ms_background_color"/>
+    <attr format="color" name="ms_text_color"/>
+    <attr format="dimension" name="ms_dropdown_max_height"/>
+    <attr format="dimension" name="ms_dropdown_height">
+      <enum name="fill_parent" value="-1"/>
+      <enum name="match_parent" value="-1"/>
+      <enum name="wrap_content" value="-2"/>
+    </attr>
+    <attr format="integer" name="ms_background_selector"/>
+    <attr format="dimension" name="ms_padding_top"/>
+    <attr format="dimension" name="ms_padding_left"/>
+    <attr format="dimension" name="ms_padding_bottom"/>
+    <attr format="dimension" name="ms_padding_right"/>
+    <attr format="dimension" name="ms_popup_padding_top"/>
+    <attr format="dimension" name="ms_popup_padding_left"/>
+    <attr format="dimension" name="ms_popup_padding_bottom"/>
+    <attr format="dimension" name="ms_popup_padding_right"/>
+    <attr format="string" name="ms_hint"/>
+    <attr format="color" name="ms_hint_color"/>
+  </declare-styleable>
+</resources>

+ 25 - 0
spinnerlib/src/main/res/values/dimens.xml

@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2016 Jared Rummler
+  ~
+  ~ 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.
+  ~
+  -->
+
+<resources>
+  <dimen name="ms__padding_left">24dp</dimen>
+  <dimen name="ms__padding_top">12dp</dimen>
+  <dimen name="ms__item_height">60dp</dimen>
+  <dimen name="ms__popup_padding_left">24dp</dimen>
+  <dimen name="ms__popup_padding_top">12dp</dimen>
+</resources>