How to limit the height of Spinner drop down view in Android

Viewed 44296

Please suggest any approach which i use to create it .

Query : I am creating 2-Spinner view , where i have to add Country/Cities list , So like if i am selecting india then i am getting 50 items inside the drop down view , problem with this is that it is taking the whole page in height .

What i want : I want to create a drop down view , where user can see only 10 items in the drop down view , other items will be shown whenever user will scroll the drop down view .


My problem

10 Answers

As of year 2021 I would go with: Exposed Dropdown Menus and use inside AutoCompleteTextView the following:

android:dropDownHeight="300dp"

If you don't know what is this all about start exploring: Menu-Display & Menu-Documentation

I think the problem is Spinner is not scrolling . in my case : spinner is not working due to use of FLAG_LAYOUT_NO_LIMITS flag to my activity window . In order to restore original behavior I used the same solution

@Override public void getWindowVisibleDisplayFrame(Rect outRect) { WindowManager wm = (WindowManager) getContext.getSystemService(Context.WINDOW_SERVICE); Display d = wm.getDefaultDisplay(); d.getRectSize(outRect); outRect.set(outRect.left, <STATUS BAR HEIGHT>, outRect.right, outRect.bottom); }

If you still face a problem of not scrolling spinner items. use the material spinner which is best solution , you can customize the spinner height.

in your build.gradle file insert: implementation 'com.jaredrummler:material-spinner:1.3.1'

in your xml:

`       <com.jaredrummler.materialspinner.MaterialSpinner
        android:id="@+id/spinner"
        android:layout_width="145dp"
        android:background="@drawable/button_next"
        android:layout_height="wrap_content"
        android:layout_marginLeft="150dp"
        app:ms_text_color="@color/black"
        appms_padding_left="100dp"
        android:layout_marginTop="-40dp"
        app:ms_dropdown_max_height="300dp"
        tools:ignore="MissingConstraints" />`

` in java final MaterialSpinner spinner = (MaterialSpinner) findViewById(R.id.spinner);

I had the same problem that @shlee1's answer didn't work directly. By putting the reflection into a custom spinner worked for me:

public class MaxSpinner extends Spinner {

     private android.widget.ListPopupWindow popupWindow;

     public MaxSpinner(Context context) {
         super(context);
     }

     public MaxSpinner(Context context, int mode) {
         super(context, mode);
     }

     public MaxSpinner(Context context, AttributeSet attrs) {
         super(context, attrs);
     }

     public MaxSpinner(Context context, AttributeSet attrs, int defStyleAttr) {
         super(context, attrs, defStyleAttr);
     }

     public MaxSpinner(Context context, AttributeSet attrs, int defStyleAttr, int mode) {
         super(context, attrs, defStyleAttr, mode);
     }

     public MaxSpinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode) {
         super(context, attrs, defStyleAttr, defStyleRes, mode);
     }

     public MaxSpinner(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes, int mode, Resources.Theme popupTheme) {
         super(context, attrs, defStyleAttr, defStyleRes, mode, popupTheme);
     }

     @Override
     public void setAdapter(SpinnerAdapter adapter) {
         super.setAdapter(adapter);
         try {
             Field popup = Spinner.class.getDeclaredField("mPopup");
             popup.setAccessible(true);

             // Get private mPopup member variable and try cast to ListPopupWindow
             popupWindow = (android.widget.ListPopupWindow) popup.get(this);
             popupWindow.setHeight(500);
         } catch (Throwable e) {
             e.printStackTrace();
         }
     }

}

Now the custom spinner can simple be used in the xml:

<com.my.package.MaxSpinner
 android:layout_width="100dp"
 android:layout_height="40dp"
 android:spinnerMode="dropdown" />

If you looking for the simplest answer here it is. This solution works in 100%. I search whole stackoverflow and internet for solution, but unfortunately most of them dont work for me. In this solution you just put one object in spinner, this object have RecycleView layout and then you attach everything to recycleView as you attach to spinner

At the beginning you have to create layout with RecycleView

spinner_recycleview.xml

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintHeight_max="300dp" />

Then change your layout in custom adapter to new that you created before.

class NotificationSpinnerAdapter(
    notificationList: List<Notification>,
    var context: Context,
    var homeNotificationAdapterInterface: HomeNotificationAdapterInterface
) : BaseAdapter() {
    private val notificationList = notificationList

    override fun getCount(): Int {
        return 1
    }

    override fun getItem(p0: Int): Any {
        return 1
    }

    override fun getItemId(p0: Int): Long {
        return 1
    }

    @SuppressLint("ViewHolder")
    override fun getView(p0: Int, p1: View?, p2: ViewGroup?): View {
        val inflter = (LayoutInflater.from(context));
        val view = SpinnerRecycleviewBinding.inflate(inflter) // here I using buildFeatures -> dataBinding true in gradle

        if (notificationList.isEmpty()){
            val newList = notificationList.toMutableList()
            newList.add(0, Notification(0,"POW","","","","Brak powiadomień.","","","T"))
            view.recyclerView.adapter = NotificationRvAdapter(newList,context,homeNotificationAdapterInterface)
        }else
            view.recyclerView.adapter = NotificationRvAdapter(notificationList,context,homeNotificationAdapterInterface)

        view.recyclerView.layoutManager = LinearLayoutManager(context)
        return view.root
    }
}

And the last step is create adapter for recycleview and attach layout that you used before change in spinner.

lass NotificationRvAdapter(list: List<Notification>,private val context: Context,private val homeNotificationAdapterInterface: HomeNotificationAdapterInterface): RecyclerView.Adapter<NotificationRvAdapterViewHolder>(),innerNotificationInterface {
        private var notificationList = list
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationRvAdapterViewHolder =
        NotificationRvAdapterViewHolder(
            DataBindingUtil.inflate(
                LayoutInflater.from(parent.context), R.layout.card_spinner_notification, parent, false
            )
        )

    override fun onBindViewHolder(holder: NotificationRvAdapterViewHolder, position: Int) {
        val item = notificationList[position]
        holder.bind(item,context,this)
    }

    override fun getItemCount(): Int {
        return notificationList.size
    }


    @SuppressLint("NotifyDataSetChanged")
    override fun onUpdate(notification: Notification) {
        val newList = notificationList.toMutableList()
        val updateElement = notification
        var index = 0
        updateElement.isRead = "T"

        index = newList.indexOf(notification)
        newList.remove(notification)
        newList.add(index,updateElement)
        notificationList = newList
        notifyDataSetChanged()
        homeNotificationAdapterInterface.refreshList(newList)
    }
}

class NotificationRvAdapterViewHolder(binding: CardSpinnerNotificationBinding) : RecyclerView.ViewHolder(binding.root){
    private val title = binding.titleText
    private val body = binding.bodyNotification
    private val message = binding.messageText
    private val date = binding.dateText
    private val iconSeen = binding.imageSeen
    @SuppressLint("SetTextI18n", "ResourceAsColor")
    fun bind(elem: Notification, context: Context,innerNotificationInterface: innerNotificationInterface){
        title.text = elem.title
        message.text = elem.message
        date.text = elem.data


        if(elem.isRead.equals("T")){
            iconSeen.gone()
        }else{
            iconSeen.show()
        }

        body.setOnClickListener {
            innerNotificationInterface.onUpdate(elem)
        }
    }
}

interface innerNotificationInterface{
    fun onUpdate(notification: Notification)
}

interface HomeNotificationAdapterInterface{
    fun refreshList(newList: MutableList<Notification>)
}

You can use this awesome library MaterialSpinner that will do all the hard work for you.

Download: implementation 'com.jaredrummler:material-spinner:1.3.1'

fileName.xml :

<com.jaredrummler.materialspinner.MaterialSpinner
android:id="@+id/spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:ms_dropdown_max_height="400dp"/>

set height using app:ms_dropdown_max_height="400dp"

In my case, my spinner using default mode which is MODE_DIALOG, instead of MODE_DROPDOWN. Casting to ListPopupWindow only works for MODE_DROPDOWN. So, I create a spinner adapter:

    ArrayAdapter<?> adapter = new ArrayAdapter<Object>(this, android.R.layout.simple_spinner_item, list) {
        @Override
        public View getDropDownView(int position, View convertView, ViewGroup parent) {
            if (position == 0) {
                parent.setLayoutParams(new LinearLayout.LayoutParams(-1, 500));
            }
            View v = super.getDropDownView(position, convertView, parent);
            TextView textView = (TextView) v;
            textView.setTextSize(font.getSize() + 2);
            textView.setWidth(nativeSpinner.getWidth());
            textView.setGravity(alignment);
            return v;
        }
    };
    adapter.setDropDownViewResource(R.layout.simple_list_item_spinner);
    spinner.setAdapter(adapter);
Related