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

Tushar Pandey
  • 4,557
  • 4
  • 33
  • 50
  • 3
    You should use custom PopupWindow. – TheLittleNaruto Dec 15 '13 at 17:41
  • @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? – Nibha Jain Mar 24 '17 at 07:09
  • use my answer added in bottom. – Tushar Pandey Mar 27 '17 at 05:08
  • 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. – Ankita Jan 31 '17 at 11:02

11 Answers11

68

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...
    }
shlee1
  • 720
  • 6
  • 5
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);
}
Almighty
  • 835
  • 8
  • 16
  • you solved my problem dude. i also have to make statusbar transparent completely – Thecarisma Dec 26 '17 at 09:13
  • 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? – j3App Mar 11 '18 at 15:04
  • its go up, on clicking spinner in the nested scroll view – Basi Mar 03 '20 at 11:53
  • 1
    `getWindowVisibleDisplayFrame` method is not getting called. – Starwave Aug 03 '21 at 10:12
9

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

F.Mysir
  • 2,838
  • 28
  • 39
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;
    }

}
Tushar Pandey
  • 4,557
  • 4
  • 33
  • 50
4

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

C.F.G
  • 817
  • 8
  • 16
3

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" />
Megaetron
  • 1,154
  • 2
  • 15
  • 29
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);

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>)
}
Daniel
  • 53
  • 7
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"
yennsarah
  • 5,467
  • 2
  • 27
  • 48
Shimi Uhlman
  • 149
  • 1
  • 6
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"

Mahmoud Ayman
  • 1,157
  • 1
  • 17
  • 27
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);
Harun
  • 667
  • 7
  • 13