带有筛选功能的Spinner

这篇博客介绍了如何在Android开发中为Spinner添加筛选功能。作者通过分析Spinner的源码,发现无法直接扩展Spinner来实现需求。因此,他选择创建一个新的组件FuzzyMatchSpinner,继承自FrameLayout,并在点击时显示带筛选输入框的对话框。通过监听EditText的输入变化,动态匹配数据并更新ListView。此外,他还实现了选中项置顶和设置适配器的方法,以提高用户体验。

Spinner下拉列表我们经常用到,一般我们在xml文件定义好,然后在代码中设置一下adapter即可。

<FrameLayout
            android:layout_width="0dp"
            android:layout_height="32dp"
            android:layout_weight="1.0"
            android:paddingRight="8dp" >

            <Spinner
                android:id="@+id/sp_mode_ddjdevwrite"
                android:layout_width="fill_parent"
                android:layout_height="30dp"
                android:layout_gravity="center_vertical"
                android:layout_marginLeft="8dp"
                android:animationCache="true"
                android:background="#FFFFFF"
                android:drawSelectorOnTop="true"
                android:prompt="@string/mode"
                android:spinnerMode="dialog" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="right|center_vertical"
                android:src="@drawable/list" />
        </FrameLayout>

spMode = (Spinner)findViewById(R.id.sp_mode_ddjdevwrite);
		
String[] modeArray = new String[]{"YR710-4", "YR800-4", "YR710-6/1180", "YR800-6/1180", 
				"YR900-6/1180", "YR1000-6/1180", "YR1120-6/1180", "YR1250-6/1430", "YR1400-6/1430", 
				"YR1600-6/1430", "YR1800-6/1430", "YR2000-6/1430", "YR2240-6/1730",
				"YR2500-6/1730", "YR630-8/1180", "YR710-8/1180", "YR800-8/1180", "YR900-8/1430",
				"YR1000-8/1430", "YR1120-8/1430", "YR1250-8/1430", "YR1400-8/1430", 
				"YR1600-8/1730", "YR1800-8/1730", "YR2000-8/1730", "YR2240-8/2150", 
				"YR2500-8/2150", "YR500-10/1180", "YR560-10/1180", "YR630-10/1180", 
				"YR710-10/1180", "YR800-10/1180", "YR900-10/1430", "YR1000-10/1430", 
				"YR1120-10/1430", "YR1250-10/1430", "YR1400-10/1430", "YR1600-10/1730", 
				"YR1800-10/1730", "YR2000-10/1730", "YR2240-10/2150", "YR2500-10/2150", 
				"YR450-12/1430", "YR500-12/1430", "YR560-12/1430", "YR630-12/1430", 
				"YR710-12/1430", "YR800-12/1430", "YR900-12/1730", "YR1000-12/1730",
				"YR1120-12/1730", "YR1250-12/1730", "YR1400-12/1730", "YR1600-12/1730", 
				"YR1800-12/1730", "YR2000-12/2150", "YR2240-12/2150", "YR2500-12/2150", 
				"YR450-16/1430", "YR500-16/1430", "YR560-16/1430", "YR630-16/1430", "YR710-16/1430", 
				"YR800-16/1430", "YR900-16/1730", "YR1000-16/1730", "YR1120-16/1730", "YR1250-16/1730", 
				"YR1400-16/2150"};
		
modelAdapter = new ArrayAdapter<String>(this,android.R.layout.simple_spinner_item, modeArray);
modelAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		
//为Spinner设置适配器
spMode.setAdapter(modelAdapter);


如果下拉的数据比较多,想要找到我们想要的数据就很不方便,如果有个筛选输入框(在输入的时候自动去匹配)就好,

 

有这样的需求那么接下来就实现这样的需求:

开始我是在想Spinner要是有个方法能设置它的布局就好,但是Spinner并没有提供这样的方法,于是就从Spinner的源码分析,发现没那么简单

点击Spinner时内部调用的是Spinner内部类DialogPopup的show()方法,而show()方法创建的是有一个AlertDialog。此时想扩展继承Spinner覆盖它的内部类DialogPopup同时覆盖它的show方法 那更是有难度(从JAVA的角度,java根本就没有提供内部类“覆盖”这么一种机制, 参考 http://blog.csdn.net/dksokil/article/details/4546189)

至此 想通过继承Spinner的方式来扩展是不行的啦,看来只能模仿了Spinner的内部实现。


实现思路:

(1) 创建FuzzyMatchSpinner 继承FrameLayout

(2) 重写perfomClick()方法 

      判断dialog 是否为空,为空则创建Dialog, 不为空则显示

(3) 提供setAdapter方法 

      setAdapter(SpinnerAdapter adapter,List<String> data)

      setAdapter(SpinnerAdapter adapter,String[] data)

(4) 提供setOnItemSelectedListener方法

(5) 创建自定义Dialog (使用自定义layout) 该layout包含了EditText, ListView 

      对EditText进行输入变化的监听,变化后的内容去匹配data,匹配后的结果封装到adapter中,设置ListView的adapter

(6) 每次显示下列列表内容的时候 将上次选中的项选中并置顶

(7) 选中某项后将FuzzyMatchSpinner 的显示内容设置成该项内容


具体实现源码如下:

attrs.xml 增加自定义属性

<declare-styleable name="fuzzymatchspinner">
        <attr name="prompt" format="string"></attr>
    </declare-styleable>


FuzzyMatchSpinner.java

package com.example.demotest.widget;

import java.util.ArrayList;
import java.util.List;

import android.app.Dialog;
import android.content.Context;
import android.content.res.TypedArray;
import android.database.DataSetObserver;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.FrameLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.Spinner;
import android.widget.SpinnerAdapter;
import android.widget.TextView;

import com.example.demotest.R;

/**
 * 带有筛选功能的Spinner
 * @author Lue
 * @since 2015-05-06
 */
public class FuzzyMatchSpinner extends FrameLayout
{
	private String TAG = "FuzzyMatchSpinner";

	private TextView tvCheckValue;

	private Dialog mPopup;
	
	private int checkPosition;
	
	private String mPrompt;
	
	private SpinnerAdapter listAdapter;
	
	private String[] data;
	
	/**
	 * 显示结果
	 */
	private ListView lvResult;
	
	/**
	 * 筛选输入框
	 */
	private EditText etKeyWords;
	
	private Spinner.OnItemSelectedListener onItemSelectedListener;

	public FuzzyMatchSpinner(Context context, AttributeSet attrs, int defStyle)
	{
		super(context, attrs, defStyle);
	}

	public FuzzyMatchSpinner(Context context, AttributeSet attrs)
	{
		super(context, attrs);

		LayoutInflater.from(context).inflate(R.layout.widget_fuzzymatchspinner,
				this);

		tvCheckValue = (TextView) findViewById(R.id.tvcheckvalue_fuzzymatchspinner);
		
		TypedArray a = context.obtainStyledAttributes(attrs,R.styleable.fuzzymatchspinner);
		
		mPrompt = a.getString(R.styleable.fuzzymatchspinner_prompt);
		
		a.recycle();
	}

	public FuzzyMatchSpinner(Context context)
	{
		super(context);
		// TODO Auto-generated constructor stub
	}

	@Override
	public boolean performClick()
	{
		boolean handled = super.performClick();
		
		if(!handled)
		{
			handled = true;
			
			if(mPopup == null)
			{
				createAlertDialog();
			}
			else if(!mPopup.isShowing())
			{
				mPopup.show();
			}
		}
		
		return handled;
	}
	
	public void setAdapter(SpinnerAdapter adapter,List<String> data) 
    {
		if(data == null || data.size() == 0)
		{
			setAdapter(adapter,new String[0]);
		}
		else
		{
			String[] tmpArray = new String[data.size()];
			data.toArray(tmpArray);
			setAdapter(adapter,tmpArray);
		}
    }
	
    public void setAdapter(SpinnerAdapter adapter,String[] data) 
    {
    	listAdapter = adapter;
    	this.data = data;
    	
    	checkPosition = 0;
    	
    	if(data != null && data.length > 0)
		{
    		tvCheckValue.setText(data[checkPosition]);
    		
    		if(onItemSelectedListener != null)
    		{
    			onItemSelectedListener.onItemSelected(lvResult, null, checkPosition, 0);	
    		}
		}
    }
    
    public void setOnItemSelectedListener(Spinner.OnItemSelectedListener onItemSelectedListener)
    {
    	this.onItemSelectedListener = onItemSelectedListener;
    }
	
    private void createAlertDialog()
    {
    	mPopup = new Dialog(getContext());
    	mPopup.show();
    	if(mPrompt != null)
    	{
    		mPopup.setTitle(mPrompt);	
    	}
    	
    	mPopup.setContentView(R.layout.widget_fuzzyspiner_dialog);
    	
    	etKeyWords = (EditText)mPopup.findViewById(R.id.etkeywords_spinnerdialog);
    	lvResult = (ListView)mPopup.findViewById(R.id.lv_results_spinnerdialog);
    	    	
    	//增加变化监听
    	etKeyWords.addTextChangedListener(new TextWatcher(){
    		@Override
    		public void afterTextChanged(Editable s)
    		{
    			if(s == null || s.toString() == null || "".equals(s.toString().trim()))
    			{
    				if(listAdapter != null)
        	    	{
        	    		lvResult.setAdapter(new DropDownAdapter(listAdapter));	
        	    	}
    			}
    			else
    			{
    				String[] result = assembleMatchItems(s.toString());
    				
    				ArrayAdapter tempAdapter = new ArrayAdapter<String>(getContext(),android.R.layout.simple_spinner_item, result);

					tempAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
					
					lvResult.setAdapter(new DropDownAdapter(tempAdapter));
    			}
    		}
    		
    		@Override
    		public void beforeTextChanged(CharSequence s, int start, int count,
    				int after)
    		{
    			// TODO Auto-generated method stub
    			
    		}
    		@Override
    		public void onTextChanged(CharSequence s, int start, int before,
    				int count)
    		{
    			// TODO Auto-generated method stub
    			
    		}
    	});
    	
    	if(data == null || data.length == 0 || data.length < 5)
    	{
    		etKeyWords.setVisibility(View.GONE);
    	}
    	else
    	{
    		etKeyWords.setVisibility(View.VISIBLE);
    	}
    	
    	if(listAdapter != null)
    	{
    		lvResult.setAdapter(new DropDownAdapter(listAdapter));
    		
    		lvResult.setSelection(checkPosition);
        	//lvResult.setSelectionFromTop(checkPosition, 0);
    		
    		lvResult.setItemChecked(checkPosition, true);
    	}
    	
    	lvResult.setOnItemClickListener(new AdapterView.OnItemClickListener()
		{
    		@Override
    		public void onItemClick(AdapterView<?> parent, View view,
    				int position, long id)
    		{
    			DropDownAdapter adapter = (DropDownAdapter)parent.getAdapter();
    			if(adapter != null)
    			{
    				String checkValue = (String)adapter.getItem(position);
    				
    				if(checkPosition != position && onItemSelectedListener != null)
    				{
    					onItemSelectedListener.onItemSelected(parent, view, position, id);
    				}
    				
    				setSelection(findCheckPosition(checkValue));
    				
    				tvCheckValue.setText(checkValue);
    			}
    			
    			mPopup.dismiss();
    			mPopup = null;
    		}
		});
    }
    
    /**
     * 筛选匹配
     * @param keyWords 关键字
     * @return
     */
    private String[] assembleMatchItems(String keyWords)
    {
    	String[] matchResult = new String[0];
    	
    	if(data != null)
    	{
    		List<String> rt = new ArrayList<String>();
    		
    		int len = data.length;
    		for(int i=0; i<len; i++)
    		{
    			if(data[i].toLowerCase().contains(keyWords.toLowerCase()))
    			{
    				rt.add(data[i]);
    			}
    		}
    		
    		if(rt.size() > 0)
    		{
    			matchResult = new String[rt.size()];
    			rt.toArray(matchResult);
    		}
    	}
    	
    	return matchResult;
    }
    
    private int findCheckPosition(String value)
    {
    	int checkPosition = 0;
    	
    	if(data != null)
    	{
    		int len = data.length;
    		for(int i=0; i<len; i++)
    		{
    			if(data[i].equals(value))
    			{
    				checkPosition = i;
    				break;
    			}
    		}
    	}
    	    	
    	return checkPosition;
    }
    
	public void setSelection(int which)
	{
		this.checkPosition = which;
	}
	
	/**
     * <p>Wrapper class for an Adapter. Transforms the embedded Adapter instance
     * into a ListAdapter.</p>
     */
    private static class DropDownAdapter implements ListAdapter, SpinnerAdapter {
        private SpinnerAdapter mAdapter;
        private ListAdapter mListAdapter;

        /**
         * <p>Creates a new ListAdapter wrapper for the specified adapter.</p>
         *
         * @param adapter the Adapter to transform into a ListAdapter
         */
        public DropDownAdapter(SpinnerAdapter adapter) {
            this.mAdapter = adapter;
            if (adapter instanceof ListAdapter) {
                this.mListAdapter = (ListAdapter) adapter;
               
            }
        }
        

        public int getCount() {
            return mAdapter == null ? 0 : mAdapter.getCount();
        }

        public Object getItem(int position) {
            return mAdapter == null ? null : mAdapter.getItem(position);
        }

        public long getItemId(int position) {
            return mAdapter == null ? -1 : mAdapter.getItemId(position);
        }

        public View getView(int position, View convertView, ViewGroup parent) {
            return getDropDownView(position, convertView, parent);
        }

        public View getDropDownView(int position, View convertView, ViewGroup parent) {
            return mAdapter == null ? null :
                    mAdapter.getDropDownView(position, convertView, parent);
        }

        public boolean hasStableIds() {
            return mAdapter != null && mAdapter.hasStableIds();
        }

        public void registerDataSetObserver(DataSetObserver observer) {
            if (mAdapter != null) {
                mAdapter.registerDataSetObserver(observer);
            }
        }

        public void unregisterDataSetObserver(DataSetObserver observer) {
            if (mAdapter != null) {
                mAdapter.unregisterDataSetObserver(observer);
            }
        }

        /**
         * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
         * Otherwise, return true. 
         */
        public boolean areAllItemsEnabled() {
            final ListAdapter adapter = mListAdapter;
            if (adapter != null) {
                return adapter.areAllItemsEnabled();
            } else {
                return true;
            }
        }

        /**
         * If the wrapped SpinnerAdapter is also a ListAdapter, delegate this call.
         * Otherwise, return true.
         */
        public boolean isEnabled(int position) {
            final ListAdapter adapter = mListAdapter;
            if (adapter != null) {
                return adapter.isEnabled(position);
            } else {
                return true;
            }
        }

        public int getItemViewType(int position) {
            return 0;
        }

        public int getViewTypeCount() {
            return 1;
        }
        
        public boolean isEmpty() {
            return getCount() == 0;
        }
    }
	
	@Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        
        if (mPopup != null && mPopup.isShowing()) {
            mPopup.dismiss();
        }
    }
}

      使用FuzzyMatchSpinner

(1) 在xml 声明

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    <span style="color:#ff0000;">xmlns:myViewXmlns="http://schemas.android.com/apk/res-auto/com.example.demotest"</span>
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF"
    android:orientation="vertical" >

    <!-- 型号 -->
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="48dp"
        android:layout_marginLeft="36dp"
        android:layout_marginTop="16dp"
        android:layout_marginRight="36dp"
        android:gravity="center_vertical"
        android:orientation="horizontal" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#000000"
            android:text="型号:" />

        <FrameLayout
            android:layout_width="0dp"
            android:layout_height="32dp"
            android:layout_weight="1.0"
            android:paddingRight="8dp" >

            <Spinner
                android:id="@+id/sp_mode_ddjdevwrite"
                android:layout_width="fill_parent"
                android:layout_height="30dp"
                android:layout_gravity="center_vertical"
                android:layout_marginLeft="8dp"
                android:animationCache="true"
                android:background="#FFFFFF"
                android:drawSelectorOnTop="true"
                android:prompt="@string/mode"
                android:spinnerMode="dialog" />

            <ImageView
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_gravity="right|center_vertical"
                android:src="@drawable/list" />
        </FrameLayout>
    </LinearLayout>

    <!-- 型号 -->
    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="48dp"
        android:layout_marginLeft="36dp"
        android:layout_marginTop="16dp"
        android:layout_marginRight="36dp"
        android:gravity="center_vertical"
        android:orientation="horizontal" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#000000"
            android:text="型号:" />

        <com.example.demotest.widget.FuzzyMatchSpinner
            android:id="@+id/fuzzysp_mode_ddjdevwrite"
            android:layout_width="0dp"
            android:layout_height="32dp"
            android:layout_weight="1.0"
            android:clickable="true"
            <span style="color:#ff0000;">myViewXmlns:prompt="型号"</span>
            />
    </LinearLayout>
    
</LinearLayout>


注意以上 <com.example.demotest.widget.FuzzyMatchSpinner> 需要增加 android:clickable="true" 属性的,否点击的时候 performClick()方法是不会被执行到的,也就不会弹出下拉列表的,Spinner的perfomClick()方法之所以能被执行也是因为Spinner的属性文件也有android:clickable="true" 属性设置的。(这个具体原因就请看View的事件分发机制了)

(2) 代码初始化

Spinner  spMode = (Spinner)findViewById(R.id.sp_mode_ddjdevwrite);
FuzzyMatchSpinner spMode2 = (FuzzyMatchSpinner)findViewById(R.id.fuzzysp_mode_ddjdevwrite);
		
		String[] modeArray = new String[]{"YR710-4", "YR800-4", "YR710-6/1180", "YR800-6/1180", 
				"YR900-6/1180", "YR1000-6/1180", "YR1120-6/1180", "YR1250-6/1430", "YR1400-6/1430", 
				"YR1600-6/1430", "YR1800-6/1430", "YR2000-6/1430", "YR2240-6/1730",
				"YR2500-6/1730", "YR630-8/1180", "YR710-8/1180", "YR800-8/1180", "YR900-8/1430",
				"YR1000-8/1430", "YR1120-8/1430", "YR1250-8/1430", "YR1400-8/1430", 
				"YR1600-8/1730", "YR1800-8/1730", "YR2000-8/1730", "YR2240-8/2150", 
				"YR2500-8/2150", "YR500-10/1180", "YR560-10/1180", "YR630-10/1180", 
				"YR710-10/1180", "YR800-10/1180", "YR900-10/1430", "YR1000-10/1430", 
				"YR1120-10/1430", "YR1250-10/1430", "YR1400-10/1430", "YR1600-10/1730", 
				"YR1800-10/1730", "YR2000-10/1730", "YR2240-10/2150", "YR2500-10/2150", 
				"YR450-12/1430", "YR500-12/1430", "YR560-12/1430", "YR630-12/1430", 
				"YR710-12/1430", "YR800-12/1430", "YR900-12/1730", "YR1000-12/1730",
				"YR1120-12/1730", "YR1250-12/1730", "YR1400-12/1730", "YR1600-12/1730", 
				"YR1800-12/1730", "YR2000-12/2150", "YR2240-12/2150", "YR2500-12/2150", 
				"YR450-16/1430", "YR500-16/1430", "YR560-16/1430", "YR630-16/1430", "YR710-16/1430", 
				"YR800-16/1430", "YR900-16/1730", "YR1000-16/1730", "YR1120-16/1730", "YR1250-16/1730", 
				"YR1400-16/2150"};
		
		modelAdapter = new ArrayAdapter<String>(this,
				android.R.layout.simple_spinner_item, modeArray);

		modelAdapter
				.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		
		//为Spinner设置适配器
		spMode.setAdapter(modelAdapter);
		
		//String[] modeArray2 = new String[]{"YR710-4", "YR800-4", "YR710-6/1180", "YR800-6/1180"};
		
		ArrayAdapter modelAdapter2 = new ArrayAdapter<String>(this,
				android.R.layout.simple_spinner_item, modeArray);

		modelAdapter2
				.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
		
		spMode2.setAdapter(modelAdapter2,modeArray);

运行效果图如下:

   --> 

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值