前段时间有个项目用到了下拉列表,有一项需求是限制列表选项显示的数目,刚开始使用的是系统自带的spinner可是查了半天才发现无法设置它的显示项数甚至无法指定列表的高度。由于时间比较紧迫就在网上找了个自定义的spinner再根据需求改一改,现在项目完成后也比较闲了就随手写篇简单的博文吧,代码比较简单适合新手学习,在这里先贴上原文地址:
Android开发自定义下拉框下拉列表_android 自定义下拉列表_我叫王童心的博客-CSDN博客
代码在原文的基础上优化修改了一下,由于我的个人习惯所以变量命名和备注都比较啰嗦,有需求的话就稍微给点耐心吧。废话不多说,直接上代码,复制就能直接使用
效果:
java代码:
package com.example.SecondProject.View;import android.content.Context;import android.graphics.Color;import android.graphics.drawable.ColorDrawable;import android.util.AttributeSet;import android.util.Log;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.view.ViewTreeObserver;import android.widget.AdapterView;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.LinearLayout;import android.widget.ListAdapter;import android.widget.ListView;import android.widget.PopupWindow;import android.widget.TextView;import androidx.annotation.NonNull;import androidx.annotation.Nullable;import androidx.recyclerview.widget.LinearLayoutManager;import androidx.recyclerview.widget.RecyclerView;import com.example.SecondProject.R;import java.util.ArrayList;import java.util.List;// 自定义 Spinner LinearLayoutpublic class CustomSpinnerLinearLayout extends LinearLayout {private String TAG = "CustomSpinner";private View spinner_header; // spinner 头部private TextView spinner_text; // spinner 头部的文本private ImageView spinner_arrow; // spinner 头部的收缩/展开箭头private PopupWindow spinner_body; // spinner 身体(显示的选项列表弹窗视图)private View spinner_body_view; // 填充身体用的 viewprivate TextView spinner_title; // spinner 的标题(自己决定要或者不要)private LayoutInflater layoutInflater;private CustomSpinnerListAdapter my_adapter; // 选项列表 adapterprivate List<String> list_items = new ArrayList<>(); // 选项列表数据private boolean isListShow = false; // 列表的显示状态标识private int spinner_title_height = 0; // 标题的高度private int spinner_body_height = 0; // 展示的列表高度private int spinner_item_count = 3; // 展示的子项数目,默认3条(高度/数目二选一)private int selected_index = 0; // 当前选中项的索引public CustomSpinnerLinearLayout(Context context) {super(context);initView(context);}public CustomSpinnerLinearLayout(Context context, @Nullable AttributeSet attrs) {super(context, attrs);initView(context);}public CustomSpinnerLinearLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {super(context, attrs, defStyleAttr);initView(context);}public CustomSpinnerLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {super(context, attrs, defStyleAttr, defStyleRes);initView(context);}private void initView(final Context context) {layoutInflater = LayoutInflater.from(context);spinner_header = layoutInflater.inflate(R.layout.custom_spinner_header, null); // 初始化spinner头部spinner_header.setLayoutParams(new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));spinner_body_view = layoutInflater.inflate(R.layout.custom_spinner_body, null); // 初始化spinner身体spinner_title = spinner_body_view.findViewById(R.id.title);// 测量列表标题的高度spinner_title.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));spinner_title_height = spinner_title.getMeasuredHeight();spinner_text = spinner_header.findViewById(R.id.text);spinner_arrow = spinner_header.findViewById(R.id.arrow);// 头部点击事件:收缩/展开 选项列表spinner_header.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View view) {Log.e(TAG, "当前列表展开状态: " + isListShow );if (list_items != null){// 首次点击初始化if(spinner_body == null){// 初始化列表 --------------------------------------RecyclerView spinner_list = spinner_body_view.findViewById(R.id.spinner_list);my_adapter = new CustomSpinnerListAdapter(context,list_items);// 选项列表点击事件my_adapter.setOnListItemClickListener(new onListItemClickListener() {@Overridepublic void onItemClick(Object position) {int index = (int) position;Log.e(TAG, "列表点击项数: " + index );selected_index = index;my_adapter.notifyDataSetChanged(); // 更新列表状态(改变选中项背景)spinner_text.setText(list_items.get(index)); // 修改头部文本spinner_arrow.setImageResource(R.drawable.chibi_maruko); // 修改头部箭头为展开(下),这里放入自己的资源文件spinner_body.dismiss(); // 隐藏列表isListShow = false; // 修改标识if(onSpinnerItemClickListener!=null){onSpinnerItemClickListener.onSpinnerItemClick(position);} // 传到外部}});spinner_list.setAdapter(my_adapter);spinner_list.setLayoutManager(new LinearLayoutManager(context));// 初始化列表高度 --------------------------------------// 如果设置了展示数目就显示数目,否则显示高度int body_height = spinner_item_count != 0 ? spinner_title_height + (my_adapter.getItemHeight()*spinner_item_count) : spinner_body_height;spinner_body = new PopupWindow(spinner_body_view, spinner_header.getWidth(),body_height , true);spinner_body.setFocusable(false); // 设置不可取消spinner_body.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); // 设置不遮挡软键盘spinner_body.setSoftInputMode(PopupWindow.INPUT_METHOD_FROM_FOCUSABLE); // 设置不遮挡软键盘spinner_body.showAsDropDown(view,0, 0); // 显示列表// 初始化小箭头 --------------------------------------spinner_arrow.setImageResource(R.mipmap.meatball); // 收缩(上),这里放入自己的资源文件// 初始化列表显示状态 --------------------------------------isListShow = true;}else{// 后续点击收缩/展开 列表if(!isListShow){spinner_arrow.setImageResource(R.mipmap.meatball);spinner_body.showAsDropDown(view, 0, 0);isListShow = true;}else{spinner_arrow.setImageResource(R.drawable.chibi_maruko);spinner_body.dismiss();isListShow = false;}}}}});// 初始化头部文本if (list_items == null || list_items.size() == 0){spinner_text.setText("");}else{spinner_text.setText(list_items.get(0));}addView(spinner_header);}public void setData(List<String> list){this.list_items = list;spinner_text.setText(list.get(0));}// 设置列表弹窗总高度public void setBodyHeiht(int height){if(height<=0){return;}this.spinner_body_height = height;this.spinner_item_count = 0;}// 设置列表项显示数目public void setItemCount(int count){if(count<1){return;}this.spinner_item_count = count;this.spinner_body_height = 0;}// 设置选中项public void setSelectedItem(int position) {if (position >= 0 && position < list_items.size()) {selected_index = position;spinner_text.setText(list_items.get(selected_index));if (my_adapter != null) {my_adapter.notifyDataSetChanged();}}}// 列表适配器private class CustomSpinnerListAdapter extends RecyclerView.Adapter<CustomSpinnerListAdapter.MyViewHolder> {Context my_context;List<String> items = new ArrayList<>(); // 用到的操作名称public CustomSpinnerListAdapter(Context context, List<String> items){my_context = context;this.items = items;}@NonNull@Overridepublic MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {View view = LayoutInflater.from(my_context).inflate(R.layout.custom_spinner_item, parent, false);MyViewHolder holder = new MyViewHolder(view);return holder;}@Overridepublic void onBindViewHolder(@NonNull MyViewHolder holder, int position) {String item_str = items.get(position);// 如果项数是选中项则修改背景颜色if (position == selected_index){holder.itemView.setBackgroundColor(Color.rgb(237,237,237)); // 自行修改}else {holder.itemView.setBackgroundColor(Color.WHITE);}holder.item.setText(item_str);holder.itemView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View view) {if(onListItemClickListener!=null){onListItemClickListener.onItemClick(position);}}});}@Overridepublic int getItemCount() {return items.size();}// 获取列表子项的高度,当spinner设置为显示项数时需要用到public int getItemHeight() {// 获取子视图View itemView = layoutInflater.inflate(R.layout.custom_spinner_item, null);MyViewHolder holder = new MyViewHolder(itemView);onBindViewHolder(holder, 0);// 测量高度itemView.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));return itemView.getMeasuredHeight();}public class MyViewHolder extends RecyclerView.ViewHolder{private TextView item;public MyViewHolder(View itemView) {super(itemView);item = itemView.findViewById(R.id.text);}}// 接口 ------------------------------------------public onListItemClickListener onListItemClickListener;public void setOnListItemClickListener(onListItemClickListener onListItemClickListener){this.onListItemClickListener = onListItemClickListener;}}// 接口 ----------------------------------------------------------// 规范起见,这里使用两层接口,一层是recyclerview的,一层是外部自定义view的,但是这里为了方便展示就把recyclerview的adapter类直接作为内部类定义,因此无法在内部类里面定义recyclerview的接口就把他写在外面// recyclerview 接口 -----------private interface onListItemClickListener<T>{void onItemClick(T position);}// CustomSpinnerLinearLayout 接口 -----------public interface onSpinnerItemClickListener<T>{void onSpinnerItemClick(T position);}public onSpinnerItemClickListener onSpinnerItemClickListener;public void setOnSpinnerItemClickListener(onSpinnerItemClickListener onSpinnerItemClickListener){this.onSpinnerItemClickListener = onSpinnerItemClickListener;}@Overridepublic void destroyDrawingCache() {if (spinner_body != null && spinner_body.isShowing()){spinner_body.dismiss();}super.destroyDrawingCache();}}
用到的几个layout文件:R.layout.custom_spinner_header
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/spinner"android:background="@color/white"android:paddingLeft="5dp"android:paddingRight="5dp"><TextViewandroid:id="@+id/text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textColor="@color/black"android:gravity="center"android:text="选项名称"android:layout_centerInParent="true"/><!--收缩/展开箭头,默认放展开(下)箭头--><ImageViewandroid:id="@+id/arrow"android:layout_width="30dp"android:layout_height="30dp"android:src="@drawable/chibi_maruko"android:layout_toRightOf="@+id/text"android:layout_marginLeft="5dp"/></RelativeLayout>
R.layout.custom_spinner_body
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:background="@color/white"><!-- 自定义--><TextViewandroid:id="@+id/title"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:text="我是列表视图"/><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/spinner_list"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>
R.layout.custom_spinner_item
<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:orientation="horizontal"android:id="@+id/list_item"android:paddingTop="3dp"android:paddingBottom="3dp"android:paddingLeft="5dp"android:paddingRight="5dp"><TextViewandroid:id="@+id/text"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textColor="@color/black"android:gravity="center"android:text="选项名称1"/><!--其他自定义内容,根据需求增加--><ImageViewandroid:id="@+id/icon"android:layout_width="30dp"android:layout_height="30dp"android:layout_marginLeft="2dp"android:visibility="gone"/></LinearLayout>
基本用法:
小结:
总体思路很简单,创建一个LinearLayout并在初始化时添加一个头布局(文本加箭头)作为列表结果项,添加一个身体布局(PopupWindow)来作为选项列表,为这个结果项添加点击事件来显示选项列表.... 好了,我要下班了,代码里的备注已经很详细了其余功能需求自行增加/修改