48

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

4
  • 3
    You should use custom PopupWindow. Commented Dec 15, 2013 at 17:41
  • 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. Commented Jan 31, 2017 at 11:02
  • @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? Commented Mar 24, 2017 at 7:09
  • use my answer added in bottom. Commented Mar 27, 2017 at 5:08

12 Answers 12

70

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...
    }
Sign up to request clarification or add additional context in comments.

11 Comments

Please also explain the important parts of the source code to improve your answer and make it more useful.
It is throwing Exception java.lang.ClassCastException: android.widget.Spinner$DropdownPopup cannot be cast to android.support.v7.widget.ListPopupWindow
didn't work for me, although the code debugs and runs with no exceptions, Popup height doesn't change
This is not worked for me as it throws silent exception.... anybody else here please help me...
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 answer
|
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);
}

4 Comments

you solved my problem dude. i also have to make statusbar transparent completely
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?
its go up, on clicking spinner in the nested scroll view
getWindowVisibleDisplayFrame method is not getting called.
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

5 Comments

It says to limit the height of the spinner. This works well for AutoCompleteTextview but not for spinner.
Thanks for this. I never knew about Exposed Dropdown Menus
this does nothing in spinner
@Darksymphony I have mentioned another workaround which is more practical, and follows the latest guidelines according to material design.
android:dropDownHeight - this option is not available for spinner
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

2 Comments

Amazing in kotlin.. this was the only thing that worked!
this isn't click through :( on the padding area
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;
    }

}

Comments

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" />

Comments

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

Comments

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

Comments

1

As @Megaetron pointed out in his answer, even though reflexion seems to be the way, @shlee1's answer doesn't work right away and does nothing, even if no exception is thrown.

But instead of defining a custom Spinner, using an AppCompatSpinner when setting the height fixed it for me. So I ended up setting the dropdown height like so:

val spinner = findViewById<Spinner>(R.id.spinner)
try {
    // Here use AppCompatSpinner instead of Spinner
    val popup = AppCompatSpinner::class.java.getDeclaredField("mPopup") 
    popup.isAccessible = true

    val popupWindow = popup.get(spinner) as ListPopupWindow

    // Set popupWindow height to 500px
    popupWindow.height = 500
} catch (e: Exception) {
    e.printStackTrace()
}

Now, while this effectively sets the dropdown height to 500px, OP wanted to display only n items, which we are not currently doing by setting a raw height value.

One way to display exactly n items is to measure one item's height, and set the dropdown height so that it equals n * one_item_height.

To do that I created an extension method to calculate the height of one dropdown item. The measuredHeight value doesn't take paddings and margins into account, so those must be added to the measured height:

fun Spinner.measureDropdownItemHeight(
    layoutInflater: LayoutInflater, 
    @LayoutRes layoutRes: Int
): Int {
    val itemView = layoutInflater.inflate(layoutRes, this, false)

    itemView.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)

    val itemHeight = itemView.measuredHeight
        .plus(paddingTop).plus(paddingBottom)
        .plus(marginTop).plus(marginBottom)

    return itemHeight
}

With all this, you can now set your dropdown height to the value you juste measured with this extension method:

fun Spinner.setMaxVisibleItems(
    layoutInflater: LayoutInflater, 
    @LayoutRes layoutRes: Int, 
    itemCount: Int
) {
    val itemHeight = measureDropdownItemHeight(layoutInflater, layoutRes)
    val maxHeight = itemHeight * itemCount

    try {
        val popup = AppCompatSpinner::class.java.getDeclaredField("mPopup")
        popup.isAccessible = true

        val popupWindow = popup.get(this) as ListPopupWindow
        popupWindow.height = maxHeight
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

Which you then call like:

val spinner = findViewById<Spinner>(R.id.spinner)
val itemLayout = android.R.layout.simple_spinner_dropdown_item // Or any custom layout
val maxVisibleItemCount = 5 // Replace to display your desired number of items
spinner.setMaxVisibleItems(layoutInflater, itemLayout, maxVisibleItemCount)

Note that if you call setMaxVisibleItems with the actual item count being larger than your provided maxVisibleItemCount, your dropdown will have some blank space at the bottom. So it's better to check that it's not the case without calling it:

if (actualItemCount > maxVisibleItemCount)
    spinner.setMaxVisibleItems(layoutInflater, itemLayout, maxVisibleItemCount)

Comments

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"

2 Comments

What is the "getDropDownView" and what is the "parent" ?
can any one explain above code with more detail...??
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"

1 Comment

It is not working for me either. Tested on Redmi device.
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);

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.