android 手动实现可输入下拉框Spinner控件(工具类) - Go语言中文社区

android 手动实现可输入下拉框Spinner控件(工具类)


一、前言

一丝感想

一直想着多写几篇博客,一个为了提升自己,也是给需要的人提供帮助。可惜,项目太近,琐事太多,实在闲不下来,唉。不管怎样,还是想抽点时间出来完成这篇博客!

进入正题

android 是自带有下拉框spinner控件的,但是android原生的Spinner控件是不支持用户输入的(据我所知),仅仅支持在数据列表确定的情况下进行选择。所以要实现一个手动输入的下拉框,我们需要自己手动实现。
在这里要感谢该博客给的灵感:
https://blog.csdn.net/u013700040/article/details/52914070
大致思想是这样:通过一个EditText和一个ImageView的组合来实现控件,然后采用popupWindow进行界面弹出。
PopupWindow不了解的可以先去了解一下,这里不做深究,PopupWindow和AlertDialog相似,都是android用于与用户交互的对话框界面。

二、可输入Spinner实现

1、实现SpinnerPopupWindow类

  • 作用:该类用于加载Spinner控件的下拉框界面,当点击自定义的Spinner时,会像android原生的Spinner那样弹出下拉供选择项。
  • 代码:
/*
 * @ Description:
 * @ Time: 2019/5/18 18:40:24
 * @ Author: lgy
 */
public class SpinnerPopupWindow<T> extends PopupWindow {
    private List<T> datas;//listview数据
    private NormalAdapter adapter;
    private LayoutInflater inflater;
    private ListView mListView;
    private Context mContext; // 上下文参参数

    public SpinnerPopupWindow(Context context, List<T> datas, AdapterView.OnItemClickListener clickListener){
        super(context);
        mContext=context;
        inflater = LayoutInflater.from(context);
        this.datas = datas;
        init(clickListener);
    }

    private void init(AdapterView.OnItemClickListener clickListener){
        View view = inflater.inflate(R.layout.spinner_window_layout, null);
        setContentView(view);
        setWidth(LinearLayout.LayoutParams.WRAP_CONTENT);
        setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
        setFocusable(true);
        ColorDrawable dw = new ColorDrawable(0x00);
        setBackgroundDrawable(dw);
        mListView = (ListView) view.findViewById(R.id.popup_listview);
        mListView.setAdapter(adapter = new NormalAdapter(mContext,datas,R.layout.spinner_popup_item));
        mListView.setOnItemClickListener(clickListener);
    }
}

  • 说明:代码比较简单,不做详细说明。将SpinnerPopupWindow定义为模板类,是为了方便扩展。在定义该类时传入一个类型即可。在构造函数中,传入一个 AdapterView.OnItemClickListener,该类型是适配器,也就是ListView的子项点击消息响应,以回调的形式传入。
  • 界面:写完后,我们要手写一个界面来供该popupWindow加载(相当于Activity加载xml界面一样),命名为:spinner_window_layout,放在layout文件夹下。该界面代码应该只包含一个listView。代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <ListView
        android:id="@+id/popup_listview"
        android:scrollbars="none"
        android:background="@drawable/round_edge_radius_tv_bl"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

  • 资源背景:好吧,差点忘记了给出round_edge_radius_tv_bl资源文件了,这个资源文件是设置ListView的背景,可以自己设计,以下给出代码:
    文件命名:round_edge_radius_tv_bl.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <solid android:color="#ffffff" /><!-- 背景填充颜色 -->
    <stroke
        android:width="1dp"
        android:color="#30000000" /><!-- 描边,边框宽度、颜色 -->
    <corners android:radius="5dp" /><!-- 边角圆弧的半径 -->
</shape>

  • 适配器:既然定义了ListView,那么我们要定义一个适配器给该列表提供数据源。文件命名为NormalAdapter,代码如下:
/*
 * @ Description:
 * @ Time: 2019/5/21 17:00:22
 * @ Author: lgy
 */
public class NormalAdapter<T> extends BaseAdapter {
    protected Context context;
    private  List<T> datas;
    protected LayoutInflater inflater;
    protected int layoutId;

    public NormalAdapter(Context context, List<T> datas, int layoutId) {
        this.context = context;
        this.datas = datas;
        inflater = LayoutInflater.from(context);//关联布局
        this.layoutId = layoutId;
    }

    @Override
    public int getCount() {
        return datas.size();
    }

    @Override
    public Object getItem(int position) {
        return datas.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
      ViewHolder viewHolder;
        if(convertView == null){
            viewHolder = new ViewHolder();
            convertView = inflater.inflate(R.layout.spinner_popup_item,null);
            viewHolder.textTv = (TextView) convertView.findViewById(R.id.tv_item);
            convertView.setTag(viewHolder);
        }else{
            viewHolder = (ViewHolder) convertView.getTag();
        }
        viewHolder.textTv.setText(getItem(position).toString());
        return convertView;
    }

    private class ViewHolder{
        private TextView textTv;
    }

}

到这里,PopupWindow的实现大致就结束了。popupWindow主要是定义了一个弹出界面,也就是一个ListView。

2、定义一个Spinner工具类

  • 作用:在定义完PopupWindow之后,我们需要定义一个Spinner(下拉框),通过点击Spinner上的某个控件来实现PopupWindow的弹出。 前面已经说过,Spinner使用一个EditText和ImageView的组合来实现。那么他们的作用是什么呢?想你应该能猜到。
    EditText:用来显示用户的选择数据和支持用户输入
    ImageView:接收用户点击消息(点击弹出PopupWindow)
    ,当然你也可以使用Button,TextView,背景设为图片就行等。
    类命名为:CommonSpinner,定义成模板类,便于扩展
  • 实现类代码:
/*
 * @ Description:通用下拉框工具类
 * @ Time: 2019/5/19 13:36:43
 * @ Author: lgy
 */
public class CommonSpinner<T> extends RelativeLayout {
    private Context context;//上下文
    private List<T> datas;//下拉菜单数据集
    private View Spinner;
    private SpinnerPopupWindow<T> popupWindow;
    private RelativeLayout rl_text;
    private EditText et_text;
    private ImageView iv_text;

    private String showTitle;
    private String showText;

    //接口,消息响应
    private Spinner_ClickListener spinner_ClickListener;
    //用于显示popupWindow
    private View.OnClickListener clickListener= new OnClickListener() {
        @Override
        public void onClick(View v) {
            switch (v.getId()) {
                case R.id.common_iv:
                    popupWindow.setWidth(rl_text.getWidth());
                    popupWindow.showAsDropDown(rl_text);
     //               setTextImage(R.mipmap.arrow_down_bl);
                    break;
            }
        }
    };
    //用于popupWindow中ListView的子项点击事件
    private AdapterView.OnItemClickListener itemClickListener=new AdapterView.OnItemClickListener() {
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            popupWindow.dismiss();
            et_text.setText(datas.get(position).toString());
            showText = datas.get(position).toString();
            //这个接口是用来在其他界面做点击操作的时候负责调用的
            if(spinner_ClickListener != null){
                spinner_ClickListener.ClickListener(datas.get(position).toString());
            }
        }
    };

    public CommonSpinner(Context context, String showTitle, List<T> datas) {
        super(context);
        this.context=context;
        this.showTitle = showTitle;
        this.datas=datas;
        init();
    }
    //设置接口
    public void setSpinner_ClickListener(Spinner_ClickListener spinner_ClickListener){
        this.spinner_ClickListener = spinner_ClickListener;
    }
    private void init() {
        LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        Spinner = inflater.inflate(R.layout.common_spinner_layout, this);
        rl_text=(RelativeLayout)Spinner.findViewById(R.id.common_rl);
        et_text = (EditText) Spinner.findViewById(R.id.common_et);
        iv_text=(ImageView) Spinner.findViewById(R.id.common_iv);
        et_text.setText(showTitle);
        //设置TextView点击事件
        iv_text.setOnClickListener(clickListener);
        //初始化popupWindow
        popupWindow = new SpinnerPopupWindow<>(getContext(), datas, itemClickListener);
        popupWindow.setOnDismissListener(dismissListener);
    }
    /**
     * 监听popupwindow取消
     */
    private PopupWindow.OnDismissListener dismissListener = new PopupWindow.OnDismissListener() {
        @Override
        public void onDismiss() {
        //    setTextImage(R.mipmap.spinner_up);
        }
    };
    /**
     * 给TextView右边设置图片
     * @param resId
     */
    private void setTextImage(int resId) {
        Drawable drawable = getResources().getDrawable(resId);
        drawable.setBounds(0, 0, drawable.getMinimumWidth(), drawable.getMinimumHeight());// 必须设置图片大小,否则不显示
        et_text.setCompoundDrawables(null, null, drawable, null);
    }
    
    //用于外部获取TextView中的值
    public String getText(){
        return et_text.getText().toString().trim();
    }
    /**
     * @Description: 在外部获取Spinner界面上的EditText
     * @Author:lgy
     * @Date:2019/5/20 15:22
     */
    public EditText getEditView(){
        return et_text;
    }
}

说明:定义一个Spinner工具类,继承自RelativeLayout,加载一个布局xml文件即可,文件命名为:common_spinner_layout(仅包含一个EditText和ImageView)。在下拉框工具类中,构造函数传入一个
①View.OnClickListener:控件的点击响应,用在ImageView上弹出PopupWindow。
②AdapterView.OnItemClickListener:ListView的子项点击响应函数,当点击ListView上的子项时,将数据显示到EditText上。

请重点考虑一下这行代码:
Spinner = inflater.inflate(R.layout.common_spinner_layout, this);

这句话的作用是将定义的界面布局加载到定义了工具类(RelativeLayout)上。

  • 界面代码:文件命名为common_spinner_layout
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/common_rl"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/round_edge_radius_tv_bl">
    <EditText
        android:id="@+id/common_et"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_margin="2dp"
        android:layout_centerVertical="true"
        android:paddingLeft="@dimen/dp_2"
        android:background="@color/purewhite"
        android:text="下拉框"
        android:textSize="12sp"/>
        <ImageView
            android:id="@+id/common_iv"
            android:layout_width="30dp"
            android:layout_height="30dp"
            android:layout_alignParentRight="true"
            android:layout_centerVertical="true"
            android:paddingTop="@dimen/dp_10"
            android:paddingBottom="@dimen/dp_10"
            android:paddingLeft="@dimen/dp_10"
            android:paddingRight="@dimen/dp_5"
            android:src="@mipmap/arrow_down_bl"/>
</RelativeLayout>

界面只包含一个EditText和右边的一个ImageView,ImageView加载图片为下箭头,代表下拉框标志。
图片资源:arrow_down_bl(可以自己去Iconfont上找矢量图,然后引用即可)
在这里插入图片描述
到这里,Spinner工具类也封装完成,那么下面该到Activity里面来引用这个工具类了。

3、引用写好的CommonSpinner类:

在activity里定义一个布局,这里我采用的是LinearLayout线性布局,代码如下:
这里只定义一个线性布局,通过这个布局来加载我们写好的工具类CommonSpinner。

    <LinearLayout
                    android:id="@+id/geology_age"
                    android:layout_width="0dp"
                    android:layout_height="30dp"
                    android:layout_weight="1"
                    android:layout_marginLeft="@dimen/dp_5"
                    android:layout_marginRight="@dimen/dp_20"
                    android:layout_gravity="center_vertical"
                    android:gravity="center"
                    android:textSize="12sp"
                    android:background="@drawable/round_edge_radius_tv_bl"
                    android:orientation="horizontal">
                </LinearLayout>

活动代码:

        List<String> data_list;
        String[] strings=getResources().getStringArray(R.array.geology_factor);
        data_list= Arrays.asList(strings);
        spinner_factor=new CommonSpinner<>(GeotechnicalDescription.this,"请选择",data_list);
        spinner_factor.setSpinner_ClickListener(new Spinner_ClickListener() {
            @Override
            public void ClickListener(String data) {
                if(data.equals(getResources().getString(R.string.DIY))){
                    spinner_factor.getEditView().setText("");
                    EditText et=spinner_factor.getEditView();
                    et.setFocusable(true);
                    et.setFocusableInTouchMode(true);
                    InputMethodManager inputManager =(InputMethodManager)et.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                    if(inputManager!=null) {
                        et.requestFocus();
                        inputManager.showSoftInput(et, 0);
                    }
                }
            }
        });

下拉框中的数据是已经在string.xml文件中定义好的:如

 <string name="DIY">"自定义"</string>
    <string-array name="geology_factor">
        <item>al</item>
        <item>al+pl</item>
        <item>h</item>
        <item>自定义</item>
    </string-array>

这里根据自己的需要定义即可。

到这里整个下拉框工具类的封装就完成了,一个很简单的demo。相信android初学者都能看懂,毕竟笔者也才是android入门级的。

4、效果图

差点忘了添加效果图:
1

在这里插入图片描述

三、总结

好吧,到这里这篇博客的内容大致就写完了。我一直觉得写博客的目的一个是为了分享知识,给需要的人提供帮助,但是最主要的应该还是自己对知识的巩固吧。网上这方面的知识太多,因为笔者也是参照了先者的成果,然后根据自己的需求进行改造。希望对看到这篇博客的你能提供你所需要的帮助,那也达到了我写博客的目的。如果有什么错误或者纰漏,或者不懂的地方,欢迎留言交流。
一起学习,一起成长。

---------------------------------------------------------------------分割线------------------------------------------------------------------------------------------忘记说了,在CommonSpinner类中用到了一个接口 【 private Spinner_ClickListener spinner_ClickListener;】,这个接口用来将用户在下拉框中选中的数据显示在输入框中。
新定义一个接口

public interface Spinner_ClickListener {
    public void ClickListener(String data);

其中ClickListener为接口函数,负责将下拉框的数据显示在输入框中。
然后在CommonSpinner类中引入该接口即可。

版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/leegyoungfree/article/details/90417299
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

猜你喜欢