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

11

47

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

Revolutionary answered 15/12, 2013 at 17:37 Comment(4)
You should use custom PopupWindow.Concrescence
With the help of shlee1's answer I solved my problem. Don't know exactly why but this method won't work in my default spinners. I created a custom spinner and extended spinner class and used shlee1's answer with it. It worked for me.Gorden
@TusharPandey : Hi Tushar,I am facing the same issue but the answer accepted by you is not working on api level 5.0...do you have any solution for this?Chiarra
use my answer added in bottom.Revolutionary
P
69

You can use Reflection.

    Spinner spinner = (Spinner) findViewById(R.id.spinner);
    try {
        Field popup = Spinner.class.getDeclaredField("mPopup");
        popup.setAccessible(true);

        // Get private mPopup member variable and try cast to ListPopupWindow
        android.widget.ListPopupWindow popupWindow = (android.widget.ListPopupWindow) popup.get(spinner);

        // Set popupWindow height to 500px
        popupWindow.setHeight(500);
    }
    catch (NoClassDefFoundError | ClassCastException | NoSuchFieldException | IllegalAccessException e) {
        // silently fail...
    }
Psychometrics answered 30/4, 2015 at 5:32 Comment(13)
Please also explain the important parts of the source code to improve your answer and make it more useful.Baecher
It is throwing Exception java.lang.ClassCastException: android.widget.Spinner$DropdownPopup cannot be cast to android.support.v7.widget.ListPopupWindowAleece
Worked for me without any issues. Also, note that you can call the setHeight(int) method with MATCH_PARENT or WRAP_CONTENT (as stated in the ListPopupWindow.setHeight(int) method Javadoc).Topminnow
didn't work for me, although the code debugs and runs with no exceptions, Popup height doesn't changeCrystallize
Works like charm, Thanks a lot.Peregrinate
Good work. This code also work for the enabling the spinner scrolling feature.Epstein
@Psychometrics I used this code for spinner scrolling issue, it works for greater than 22 APIs. but it's not working in Android-Lollipop (API 21 andd 22)Epstein
Working fine, Thanks :)Vasomotor
This is not worked for me as it throws silent exception.... anybody else here please help me...Heaps
Y this only works in java project and not in the kotlin.I am stuck in it for 3 days can someone please give me a proper answerMerilynmeringue
@Merilynmeringue did you get any solutionHighbrow
Reflective access to mPopup, which is not part of the public SDK and therefore likely to change in future Android releasesTews
Use Spinner instead of AppCompatSpinnerKaryolymph
H
10

You can also affect drop down view location and size by subclassing Spinner and overriding its getWindowVisibleDisplayFrame(Rect outRect) which is used by android.widget.PopupWindow for calculations. Just set outRect to limit the area where drop down view can be displayed.

This approach is of course not suitable for all scenarios since sometimes you want to place drop down view so it doesn't obscure another view or by some other condition known only "outside the instance".

In my case, I needed to apply FLAG_LAYOUT_NO_LIMITS flag to my activity window which caused the outRect to be huge and therefore part of the drop down view got sometimes hidden behind navigation bar. In order to restore original behavior I used the following override:

@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);
}
Hagiology answered 13/1, 2016 at 10:15 Comment(4)
you solved my problem dude. i also have to make statusbar transparent completelyHoag
Works great on almost all devices, but I cannot get it to work on the Galaxy S3 4.4.4 Kitkat. Kitkat ignores the result of getWindowVisibleDisplayFrame completely. Also tried the reflection solution above, didn't work either. Anyone with an explanation/solution?Subreption
its go up, on clicking spinner in the nested scroll viewDardar
getWindowVisibleDisplayFrame method is not getting called.Northernmost
K
7

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

Kippie answered 21/5, 2020 at 19:41 Comment(5)
It says to limit the height of the spinner. This works well for AutoCompleteTextview but not for spinner.Highbrow
Thanks for this. I never knew about Exposed Dropdown MenusArena
this does nothing in spinnerTews
@Tews I have mentioned another workaround which is more practical, and follows the latest guidelines according to material design.Kippie
android:dropDownHeight - this option is not available for spinnerKishakishinev
J
6

Simple Solution:

Just set android:insetBottom="50dp" for popup background xml file in drawable:

<--spinner_popup_bg.xml-->

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android"
    android:exitFadeDuration="@android:integer/config_mediumAnimTime">
    <item android:state_pressed="true" android:drawable="@android:color/white" />
    <item android:state_selected="true" android:drawable="@android:color/white" />
    <item>
        <inset android:insetLeft="5dp" android:insetRight="5dp" android:insetBottom="50dp">
            <shape android:shape="rectangle">
                <stroke android:width="1dp"
                    android:color="#001726" />
                <solid android:color="#FFFF" />
            </shape>
        </inset>
    </item>
</selector>

and for spinner in activity set android:popupBackground="@drawable/spinner_popup_bg" i.e.

<Spinner
        android:id="@+id/myid"
...
        android:layout_width="300dp"
        android:layout_height="?attr/dropdownListPreferredItemHeight"
        android:popupBackground="@drawable/spinner_popup_bg"
...
        />

enter image description here

Joses answered 23/12, 2022 at 19:3 Comment(2)
Amazing in kotlin.. this was the only thing that worked!Telephoto
this isn't click through :( on the padding areaKanazawa
R
4

for that i have created my own , PopUpWindow as suggested by @theLittleNaruto , in the comment section .

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" 
    android:orientation="vertical" 
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content"> 

    <Button 
        android:layout_marginTop="80dp"
        android:id="@+id/btn"
        android:layout_width="wrap_content" 
        android:layout_height="wrap_content"
        android:text="Country"
        android:layout_gravity="center_vertical|center_horizontal"/>
</LinearLayout>

popup_example.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="10dip" >

    <ListView 
        android:id="@+id/lstview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

</LinearLayout>

showpopup_1.java

package com.example.spinnerworking;

import android.app.Activity;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.util.Log;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.PopupWindow;
import android.widget.TextView;
import android.widget.PopupWindow.OnDismissListener;
import android.widget.Toast;

public class showpopup_1 extends Activity {

    boolean click = true ;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // TODO Auto-generated method stub
        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
        final LayoutInflater inflater = (LayoutInflater) this
                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        final Button b = (Button) findViewById(R.id.btn);
        final View pview = inflater.inflate(R.layout.popup_example,
                (ViewGroup) findViewById(R.layout.main));
        final PopupWindow pw = new PopupWindow(pview);
        Log.i("hello", "hello") ;

        b.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                if (click) {
                    // if onclick written here, it gives null pointer exception.
                    // if onclick is written here it gives runtime exception.
                    pw.showAtLocation(v, Gravity.CENTER_HORIZONTAL, 0, 0);
                    pw.update(8, 0, 150, 200);
                    String[] array = new String[] { "tushar", "pandey",
                            "almora" };

                    ListView lst = (ListView) pview.findViewById(R.id.lstview);
                    adapterHello adapter = new adapterHello(showpopup_1.this);
                    lst.setAdapter(adapter);
                    lst.setOnItemClickListener(new OnItemClickListener() {
                        @Override
                        public void onItemClick(AdapterView<?> arg0, View arg1,
                                int arg2, long arg3) {
                            Toast.makeText(showpopup_1.this, "pandey",
                                    Toast.LENGTH_SHORT).show();
                        }

                    });
                    click = false ;
                }
                else
                {
                    pw.dismiss();
                    click = true;
                }

            }
        });
    }
}

class adapterHello extends BaseAdapter {
    String array[] = new String[] { "tushar", "pandey", "almora", "hello",
            "tushar", "pandey", "almora", "hello", "tushar", "pandey",
            "almora", "hello" };

    showpopup_1 context;

    public adapterHello(showpopup_1 context) {
        this.context = context;
    }

    public int getCount() {
        // TODO Auto-generated method stub
        return array.length;
    }

    @Override
    public Object getItem(int arg0) {
        // TODO Auto-generated method stub
        return arg0;
    }

    @Override
    public long getItemId(int position) {
        // TODO Auto-generated method stub
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        // TODO Auto-generated method stub
        TextView text = new TextView(context);
        text.setHeight(30);
        text.setPadding(10, 8, 0, 0);
        text.setTextColor(Color.BLACK);
        text.setText(array[position]);
        text.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                // TODO Auto-generated method stub
                Log.i("clicked", "tushar");
            }
        });
        return text;
    }

}
Revolutionary answered 16/12, 2013 at 10:17 Comment(0)
B
4

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" />
Braggadocio answered 24/5, 2022 at 15:27 Comment(0)
B
1

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);

Bonina answered 7/12, 2020 at 15:46 Comment(0)
I
1

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>)
}
Inquietude answered 2/9, 2022 at 9:54 Comment(0)
E
0
  1. add android:popupBackground="#00000000" to the Spinner
  2. in the Adapter

getDropDownView();
parentParams = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, (int) Utils.convertDpToPx(350));
parentParams.gravity = Gravity.BOTTOM;
parent.setLayoutParams(parentParams);
  1. you can move the popup by adding android:dropDownVerticalOffset="60dp"
Elver answered 27/1, 2015 at 9:49 Comment(2)
What is the "getDropDownView" and what is the "parent" ?Heigho
can any one explain above code with more detail...??Heaps
I
0

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"

Inweave answered 24/7, 2019 at 8:11 Comment(1)
It is not working for me either. Tested on Redmi device.Education
N
0

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);
Nautch answered 18/2, 2022 at 9:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.