您的位置:首页 > 其它

安卓自定义搜索框(searchview)

2016-06-21 11:23 169 查看


分析

根据上面这张图,简单分析一下自定义搜索框的结构与功能,有 

1. 搜索界面大致由三部门组成,如图:输入框+(自动补全)提示框+结果列表。 

2. 提示框的数据与输入框输入的文本是实时联动的,而结果列表只有在每次进行搜索操作时才会更新数据 

3. 输入框的UI应是动态的,即UI随着输入的文本的改变而改变,如:在未输入文本时,清除按钮

应该是隐藏的;只有当框中有文本时才会显示。 

4. 软键盘也应该是动态的,如完成搜索时应自动隐藏。 

5. 选择提示框的选项会自动补全输入框,且自动进行搜索 

6. (external)有热门搜索推荐/记录搜索记录的功能——热门搜索推荐列表只在刚要进行搜索的时候弹出,即未输入文本时,可供用户选择。

根据上面的分析,我们认为一个搜索框应该包含输入框和提示框两个部分。搜索框可以设置一个回调监听接口,当需要进行搜索操作时,调用监听者的search()方法,从而实现具体的搜索操作以及结果列表的数据联动。


演示Demo



注意: 

1. 这里,博主图方便没有模拟太多数据,而且提示框和热搜列表也都只是使用String类型的数据,各位看官们可以根据自身需要去设置item_layout和相应的adapter。 

2. 由于个人习惯,博主在这个demo中使用了通用适配器,所以生成和设置adapter的代码比较简略,看官们可以根据传统的ViewHolder模式打造自己的adapter。或者学习一下通用适配器的打造。可以参考这里(鸿神博客Again)学习一下通用适配器的打造,在我的源码里面也有对应的源码。


实现

好了,说了那么多,开始来看代码吧

先看SearchView的布局文件 
search_layout.xml


[html]
view plain
copy

<?xml version="1.0" encoding="utf-8"?>  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
              android:background="#eee"  
              android:layout_width="match_parent"  
              android:layout_height="wrap_content"  
              android:orientation="vertical">  
  
    <LinearLayout  
        android:background="#eb4f38"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content"  
        android:orientation="horizontal">  
  
  
        <FrameLayout  
  
            android:layout_weight="1"  
            android:layout_width="0dp"  
            android:layout_height="wrap_content">  
  
            <EditText  
                android:id="@+id/search_et_input"  
                android:layout_gravity="center_vertical"  
                android:layout_margin="10dp"  
                android:drawableLeft="@drawa
4000
ble/search_icon"  
                android:drawablePadding="5dp"  
                android:layout_width="match_parent"  
                android:layout_height="wrap_content"  
                android:background="@drawable/search_edittext_shape"  
                android:textSize="16sp"  
                android:imeOptions="actionSearch"  
                android:inputType="text"  
                android:hint="请输入关键字"/>  
  
            <ImageView  
                android:visibility="gone"  
                android:layout_marginRight="20dp"  
                android:src="@drawable/iv_delete_bg"  
                android:id="@+id/search_iv_delete"  
                android:layout_gravity="right|center_vertical"  
                android:layout_width="wrap_content"  
                android:layout_height="wrap_content"/>  
        </FrameLayout>  
  
        <Button  
            android:id="@+id/search_btn_back"  
            android:layout_marginRight="10dp"  
            android:layout_marginTop="10dp"  
            android:layout_marginBottom="10dp"  
            android:layout_gravity="center_vertical"  
            android:background="@drawable/btn_search_bg"  
            android:layout_width="@dimen/btn_width"  
            android:layout_height="@dimen/btn_height"  
            android:text="返回"  
            android:textColor="@color/color_white"/>  
    </LinearLayout>  
  
    <ListView  
        android:visibility="gone"  
        android:id="@+id/search_lv_tips"  
        android:background="@drawable/lv_search_tips_bg"  
        android:layout_marginLeft="20dp"  
        android:layout_marginRight="20dp"  
        android:layout_marginBottom="10dp"  
        android:layout_width="match_parent"  
        android:layout_height="200dp">  
    </ListView>  
</LinearLayout>  

注意:demo中颜色什么的都直接用的rgb 值去设置,在实际开发时,需要把它们都统一管理到values目录下 。

比较简单,需要注意的是EditText的这个属性

android:imeOptions="actionSearch"
 

就是把Enter键设置为Search键,并把点击Enter键的动作设为actionSearch,这样既可在代码中监听何时按下search键

没什么说的,bg属性可以直接看看源码。接下来看模拟的bean类,这里直接就叫
Bean.Java


[java]
view plain
copy

public class Bean {  
  
    private int iconId;  
    private String title;  
    private String content;  
    private String comments;  
  
    public Bean(int iconId, String title, String content, String comments) {  
        this.iconId = iconId;  
        this.title = title;  
        this.content = content;  
        this.comments = comments;  
    }  
  
    public int getIconId() {  
        return iconId;  
    }  
  
    public void setIconId(int iconId) {  
        this.iconId = iconId;  
    }  
  
    public String getTitle() {  
        return title;  
    }  
  
    public void setTitle(String title) {  
        this.title = title;  
    }  
  
    public String getContent() {  
        return content;  
    }  
  
    public void setContent(String content) {  
        this.content = content;  
    }  
  
    public String getComments() {  
        return comments;  
    }  
  
    public void setComments(String comments) {  
        this.comments = comments;  
    }  
}  

接着看主角
SearchView.java


[java]
view plain
copy

public class SearchView extends LinearLayout implements View.OnClickListener {  
  
    /** 
     * 输入框 
     */  
    private EditText etInput;  
  
    /** 
     * 删除键 
     */  
    private ImageView ivDelete;  
  
    /** 
     * 返回按钮 
     */  
    private Button btnBack;  
  
    /** 
     * 上下文对象 
     */  
    private Context mContext;  
  
    /** 
    
14bb8
 * 弹出列表 
     */  
    private ListView lvTips;  
  
    /** 
     * 提示adapter (推荐adapter) 
     */  
    private ArrayAdapter<String> mHintAdapter;  
  
    /** 
     * 自动补全adapter 只显示名字 
     */  
    private ArrayAdapter<String> mAutoCompleteAdapter;  
  
    /** 
     * 搜索回调接口 
     */  
    private SearchViewListener mListener;  
  
    /** 
     * 设置搜索回调接口 
     * 
     * @param listener 监听者 
     */  
    public void setSearchViewListener(SearchViewListener listener) {  
        mListener = listener;  
    }  
  
    public SearchView(Context context, AttributeSet attrs) {  
        super(context, attrs);  
        mContext = context;  
        LayoutInflater.from(context).inflate(R.layout.search_layout, this);  
        initViews();  
    }  
  
    private void initViews() {  
        etInput = (EditText) findViewById(R.id.search_et_input);  
        ivDelete = (ImageView) findViewById(R.id.search_iv_delete);  
        btnBack = (Button) findViewById(R.id.search_btn_back);  
        lvTips = (ListView) findViewById(R.id.search_lv_tips);  
  
        lvTips.setOnItemClickListener(new AdapterView.OnItemClickListener() {  
            @Override  
            public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {  
                //set edit text  
                String text = lvTips.getAdapter().getItem(i).toString();  
                etInput.setText(text);  
                etInput.setSelection(text.length());  
                //hint list view gone and result list view show  
                lvTips.setVisibility(View.GONE);  
                notifyStartSearching(text);  
            }  
        });  
  
        ivDelete.setOnClickListener(this);  
        btnBack.setOnClickListener(this);  
  
        etInput.addTextChangedListener(new EditChangedListener());  
        etInput.setOnClickListener(this);  
        etInput.setOnEditorActionListener(new TextView.OnEditorActionListener() {  
            @Override  
            public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {  
                if (actionId == EditorInfo.IME_ACTION_SEARCH) {  
                    lvTips.setVisibility(GONE);  
                    notifyStartSearching(etInput.getText().toString());  
                }  
                return true;  
            }  
        });  
    }  
  
    /** 
     * 通知监听者 进行搜索操作 
     * @param text 
     */  
    private void notifyStartSearching(String text){  
        if (mListener != null) {  
            mListener.onSearch(etInput.getText().toString());  
        }  
        //隐藏软键盘  
        InputMethodManager imm = (InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE);  
        imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);  
    }  
  
    /** 
     * 设置热搜版提示 adapter 
     */  
    public void setTipsHintAdapter(ArrayAdapter<String> adapter) {  
        this.mHintAdapter = adapter;  
        if (lvTips.getAdapter() == null) {  
            lvTips.setAdapter(mHintAdapter);  
        }  
    }  
  
    /** 
     * 设置自动补全adapter 
     */  
    public void setAutoCompleteAdapter(ArrayAdapter<String> adapter) {  
        this.mAutoCompleteAdapter = adapter;  
    }  
  
    private class EditChangedListener implements TextWatcher {  
        @Override  
        public void beforeTextChanged(CharSequence charSequence, int i, int i2, int i3) {  
  
        }  
  
        @Override  
        public void onTextChanged(CharSequence charSequence, int i, int i2, int i3) {  
            if (!"".equals(charSequence.toString())) {  
                ivDelete.setVisibility(VISIBLE);  
                lvTips.setVisibility(VISIBLE);  
                if (mAutoCompleteAdapter != null && lvTips.getAdapter() != mAutoCompleteAdapter) {  
                    lvTips.setAdapter(mAutoCompleteAdapter);  
                }  
                //更新autoComplete数据  
                if (mListener != null) {  
                    mListener.onRefreshAutoComplete(charSequence + "");  
                }  
            } else {  
                ivDelete.setVisibility(GONE);  
                if (mHintAdapter != null) {  
                    lvTips.setAdapter(mHintAdapter);  
                }  
                lvTips.setVisibility(GONE);  
            }  
  
        }  
  
        @Override  
        public void afterTextChanged(Editable editable) {  
        }  
    }  
  
    @Override  
    public void onClick(View view) {  
        switch (view.getId()) {  
            case R.id.search_et_input:  
                lvTips.setVisibility(VISIBLE);  
                break;  
            case R.id.search_iv_delete:  
                etInput.setText("");  
                ivDelete.setVisibility(GONE);  
                break;  
            case R.id.search_btn_back:  
                ((Activity) mContext).finish();  
                break;  
        }  
    }  
  
    /** 
     * search view回调方法 
     */  
    public interface SearchViewListener {  
  
        /** 
         * 更新自动补全内容 
         * 
         * @param text 传入补全后的文本 
         */  
        void onRefreshAutoComplete(String text);  
  
        /** 
         * 开始搜索 
         * 
         * @param text 传入输入框的文本 
         */  
        void onSearch(String text);  
  
//        /**  
//         * 提示列表项点击时回调方法 (提示/自动补全)  
//         */  
//        void onTipsItemClick(String text);  
    }  
  
}  

搜索框主要包含两个结构:输入栏+弹出框(自动补全或热门搜素推荐)。

代码不多,实现很简单,主要是需要给EditText(输入框)设置点击监听和文本改变监听,有以下几点: 

1. 当输入框没有文本时,点击输入框,显示热门搜索列表框。 

2. 当输入框有文本时,点击输入框,应显示自动补全列表框。 

3. 当输入框的文本发生改变时,需要更新自动补全列表框的数据。由于这些数据应该是在外部(调用者)中获得的,所以可以通过接口回调的形式,当需要更新时,通知监听者更新数据。 

4. 当输入框的文本从空”“变换到非空时,即有字符时,界面应显示自动补全框,隐藏热门搜索框。 

5. 当输入框的文本从非空变为空时,系统应隐藏自动补全框和热门搜索框。 

6. 需要监听是否按下search键(enter),按下时通知监听者执行search操作

结合以上6点和在上文分析过的内容,就能很轻松地实现该view。

之后来看看搜索界面的布局文activity_main.xml


[html]
view plain
copy

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
              xmlns:tools="http://schemas.android.com/tools"  
              android:layout_width="match_parent"  
              android:layout_height="match_parent"  
              tools:context=".MainActivity"  
              android:orientation="vertical">  
  
    <com.yetwish.customsearchdemo.activity.widge.SearchView  
        android:id="@+id/main_search_layout"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content">  
    </com.yetwish.customsearchdemo.activity.widge.SearchView>  
  
    <ListView  
        android:visibility="gone"  
        android:id="@+id/main_lv_search_results"  
        android:layout_width="match_parent"  
        android:layout_height="wrap_content">  
  
    </ListView>  
</LinearLayout>  

就是一个SearchView加上一个结果列表,这些我们在上文都分析过了,所以也没什么好说的。布局可根据自身需求去自定义。

最后就是搜索界面调用该view  
MainActiviy.java


[java]
view plain
copy

public class MainActivity extends Activity implements SearchView.SearchViewListener {  
  
    /** 
     * 搜索结果列表view 
     */  
    private ListView lvResults;  
  
    /** 
     * 搜索view 
     */  
    private SearchView searchView;  
  
  
    /** 
     * 热搜框列表adapter 
     */  
    private ArrayAdapter<String> hintAdapter;  
  
    /** 
     * 自动补全列表adapter 
     */  
    private ArrayAdapter<String> autoCompleteAdapter;  
  
    /** 
     * 搜索结果列表adapter 
     */  
    private SearchAdapter resultAdapter;  
  
    /** 
     * 数据库数据,总数据 
     */  
    private List<Bean> dbData;  
  
    /** 
     * 热搜版数据 
     */  
    private List<String> hintData;  
  
    /** 
     * 搜索过程中自动补全数据 
     */  
    private List<String> autoCompleteData;  
  
    /** 
     * 搜索结果的数据 
     */  
    private List<Bean> resultData;  
  
    /** 
     * 默认提示框显示项的个数 
     */  
    private static int DEFAULT_HINT_SIZE = 4;  
  
    /** 
     * 提示框显示项的个数 
     */  
    private static int hintSize = DEFAULT_HINT_SIZE;  
  
    /** 
     * 设置提示框显示项的个数 
     * 
     * @param hintSize 提示框显示个数 
     */  
    public static void setHintSize(int hintSize) {  
        MainActivity.hintSize = hintSize;  
    }  
  
  
    @Override  
    protected void onCreate(Bundle savedInstanceState) {  
        super.onCreate(savedInstanceState);  
        requestWindowFeature(Window.FEATURE_NO_TITLE);  
        setContentView(R.layout.activity_main);  
        initData();  
        initViews();  
    }  
  
    /** 
     * 初始化视图 
     */  
    private void initViews() {  
        lvResults = (ListView) findViewById(R.id.main_lv_search_results);  
        searchView = (SearchView) findViewById(R.id.main_search_layout);  
        //设置监听  
        searchView.setSearchViewListener(this);  
        //设置adapter  
        searchView.setTipsHintAdapter(hintAdapter);  
        searchView.setAutoCompleteAdapter(autoCompleteAdapter);  
  
        lvResults.setOnItemClickListener(new AdapterView.OnItemClickListener() {  
            @Override  
            public void onItemClick(AdapterView<?> adapterView, View view, int position, long l) {  
                Toast.makeText(MainActivity.this, position + "", Toast.LENGTH_SHORT).show();  
            }  
        });  
    }  
  
    /** 
     * 初始化数据 
     */  
    private void initData() {  
        //从数据库获取数据  
        getDbData();  
        //初始化热搜版数据  
        getHintData();  
        //初始化自动补全数据  
        getAutoCompleteData(null);  
        //初始化搜索结果数据  
        getResultData(null);  
    }  
  
    /** 
     * 获取db 数据 
     */  
    private void getDbData() {  
        int size = 100;  
        dbData = new ArrayList<>(size);  
        for (int i = 0; i < size; i++) {  
            dbData.add(new Bean(R.drawable.icon, "android开发必备技能" + (i + 1), "Android自定义view——自定义搜索view", i * 20 + 2 + ""));  
        }  
    }  
  
    /** 
     * 获取热搜版data 和adapter 
     */  
    private void getHintData() {  
        hintData = new ArrayList<>(hintSize);  
        for (int i = 1; i <= hintSize; i++) {  
            hintData.add("热搜版" + i + ":Android自定义View");  
        }  
        hintAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, hintData);  
    }  
  
    /** 
     * 获取自动补全data 和adapter 
     */  
    private void getAutoCompleteData(String text) {  
        if (autoCompleteData == null) {  
            //初始化  
            autoCompleteData = new ArrayList<>(hintSize);  
        } else {  
            // 根据text 获取auto data  
            autoCompleteData.clear();  
            for (int i = 0, count = 0; i < dbData.size()  
                    && count < hintSize; i++) {  
                if (dbData.get(i).getTitle().contains(text.trim())) {  
                    autoCompleteData.add(dbData.get(i).getTitle());  
                    count++;  
                }  
            }  
        }  
        if (autoCompleteAdapter == null) {  
            autoCompleteAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, autoCompleteData);  
        } else {  
            autoCompleteAdapter.notifyDataSetChanged();  
        }  
    }  
  
    /** 
     * 获取搜索结果data和adapter 
     */  
    private void getResultData(String text) {  
        if (resultData == null) {  
            // 初始化  
            resultData = new ArrayList<>();  
        } else {  
            resultData.clear();  
            for (int i = 0; i < dbData.size(); i++) {  
                if (dbData.get(i).getTitle().contains(text.trim())) {  
                    resultData.add(dbData.get(i));  
                }  
            }  
        }  
        if (resultAdapter == null) {  
            resultAdapter = new SearchAdapter(this, resultData, R.layout.item_bean_list);  
        } else {  
            resultAdapter.notifyDataSetChanged();  
        }  
    }  
  
    /** 
     * 当搜索框 文本改变时 触发的回调 ,更新自动补全数据 
     * @param text 
     */  
    @Override  
    public void onRefreshAutoComplete(String text) {  
        //更新数据  
        getAutoCompleteData(text);  
    }  
  
    /** 
     * 点击搜索键时edit text触发的回调 
     * 
     * @param text 
     */  
    @Override  
    public void onSearch(String text) {  
        //更新result数据  
        getResultData(text);  
        lvResults.setVisibility(View.VISIBLE);  
        //第一次获取结果 还未配置适配器  
        if (lvResults.getAdapter() == null) {  
            //获取搜索数据 设置适配器  
            lvResults.setAdapter(resultAdapter);  
        } else {  
            //更新搜索数据  
            resultAdapter.notifyDataSetChanged();  
        }  
        Toast.makeText(this, "完成搜素", Toast.LENGTH_SHORT).show();  
    }  
  
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: