18

我目前正在使用SearchView内部小部件在ActionBarcompat搜索时过滤列表。

当用户开始输入文本时,主布局中的 ListView 会使用 Adapter 更新以过滤结果。我通过实现 aOnQueryTextListener并过滤每个击键的结果来做到这一点。

相反,我想创建一个类似 Gmail 的搜索框,生成自动建议列表,并且不更改底层视图

在此处输入图像描述

我已经完成了使用该组件的本教程SearchView,但它需要一个可搜索的活动。我希望下拉菜单位于我拥有 ListView 的 MainActivity(如在 Gmail 应用程序中)而不是专用 Activity 的上方。
此外,以与教程中相同的方式实现它似乎对我想要的东西来说有点过头了(只是一个下拉菜单)

4

7 回答 7

28

如果您只想要一个执行问题中描述的组件,我建议您使用这个。您还可以实现 out-of-the-bx searchable interface,但是请注意它确实有 UI 限制:

要实现类似于 Gmail 应用程序的界面,您必须了解以下概念:

  • 内容提供者;
  • 在 SQLite 中持久化数据
  • Listview 或 RecyclerView 及其适配器;
  • 在活动之间传递数据;

最终结果应类似于:

最后结果

有很多(很多)方法可以获得相同的结果(或更好),我将描述一种可能的方法。

第 1 部分:布局

我决定在一个新的 Activity 中管理整个界面,为此我创建了三个 XML 布局:

  • custom_searchable.xml:将所有 UI 元素组装到一个 RelativeLayout 中,作为 SearchActivity 的内容;

    <include
        android:id="@+id/cs_header"
        layout="@layout/custom_searchable_header_layout" />
    
    <android.support.v7.widget.RecyclerView
        android:id="@+id/cs_result_list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:stackFromBottom="true"
        android:transcriptMode="normal"/>
    

  • custom_searchable_header_layout.xml:保存用户将在其中输入查询的搜索栏。它还将包含麦克风、擦除和返回 btn;

    <RelativeLayout
        android:id="@+id/custombar_return_wrapper"
        android:layout_width="55dp"
        android:layout_height="fill_parent"
        android:gravity="center_vertical"
        android:background="@drawable/right_oval_ripple"
        android:focusable="true"
        android:clickable="true" >
    
        <ImageView
            android:id="@+id/custombar_return"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_centerHorizontal="true"
            android:background="#00000000"
            android:src="@drawable/arrow_left_icon"/>
    </RelativeLayout>
    
    <android.support.design.widget.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_toRightOf="@+id/custombar_return_wrapper"
        android:layout_marginRight="60dp"
        android:layout_marginLeft="10dp"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp">
    
        <EditText
            android:id="@+id/custombar_text"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:hint="Search..."
            android:textColor="@color/textPrimaryColor"
            android:singleLine="true"
            android:imeOptions="actionSearch"
            android:background="#00000000">
            <requestFocus/>
        </EditText>
    
    </android.support.design.widget.TextInputLayout>
    
    <RelativeLayout
        android:id="@+id/custombar_mic_wrapper"
        android:layout_width="55dp"
        android:layout_height="fill_parent"
        android:layout_alignParentRight="true"
        android:gravity="center_vertical"
        android:background="@drawable/left_oval_ripple"
        android:focusable="true"
        android:clickable="true" >
    
        <ImageView
            android:id="@+id/custombar_mic"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_centerHorizontal="true"
            android:background="#00000000"
            android:src="@drawable/mic_icon"/>
    </RelativeLayout>
    

  • custom_searchable_row_details.xml:保存要在结果列表中显示的 UI 元素,以响应用户查询;

    <ImageView
        android:id="@+id/rd_left_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="3dp"
        android:layout_centerVertical="true"
        android:layout_marginLeft="5dp"
        android:src="@drawable/clock_icon" />
    
    <LinearLayout
        android:id="@+id/rd_wrapper"
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:gravity="center_vertical"
        android:layout_toRightOf="@+id/rd_left_icon"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="50dp">
    
        <TextView
            android:id="@+id/rd_header_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/textPrimaryColor"
            android:text="Header"
            android:textSize="16dp"
            android:textStyle="bold"
            android:maxLines="1"/>
    
        <TextView
            android:id="@+id/rd_sub_header_text"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:textColor="@color/textPrimaryColor"
            android:text="Sub Header"
            android:textSize="14dp"
            android:maxLines="1" />
    </LinearLayout>
    
    <ImageView
        android:id="@+id/rd_right_icon"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="3dp"
        android:layout_alignParentRight="true"
        android:layout_centerVertical="true"
        android:src="@drawable/arrow_left_up_icon"/>
    

第 02 部分:实现 SearchActivity

这个想法是,当用户键入搜索按钮(您可以将其放在任何您想要的地方)时,这个SearchActivity将被调用。它有一些主要职责:

  • 绑定到custom_searchable_header_layout.xml中的 UI 元素:通过这样做,可以:

  • 为 EditText 提供侦听器(用户将在其中键入他的查询):

    TextView.OnEditorActionListener searchListener = new TextView.OnEditorActionListener() {
    public boolean onEditorAction(TextView exampleView, int actionId, KeyEvent event) {
        // do processing
       }
    }
    
    searchInput.setOnEditorActionListener(searchListener);
    
    searchInput.addTextChangedListener(new TextWatcher() {        
    public void onTextChanged(final CharSequence s, int start, int before, int count) {
         // Do processing
       }
    }
    
  • 为返回按钮添加监听器(它会调用 finish() 并返回到调用者活动):

    this.dismissDialog.setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
        finish();
    }    
    
  • 调用谷歌语音到文本 API 的意图:

        private void implementVoiceInputListener () {
            this.voiceInput.setOnClickListener(new View.OnClickListener() {
    
                public void onClick(View v) {
                    if (micIcon.isSelected()) {
                        searchInput.setText("");
                        query = "";
                        micIcon.setSelected(Boolean.FALSE);
                        micIcon.setImageResource(R.drawable.mic_icon);
                    } else {
                        Intent intent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
    
                        intent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, RecognizerIntent.LANGUAGE_MODEL_FREE_FORM);
                        intent.putExtra(RecognizerIntent.EXTRA_PROMPT, "Speak now");
    
                        SearchActivity.this.startActivityForResult(intent, VOICE_RECOGNITION_CODE);
                    }
                }
            });
        }
    

内容提供商

在构建搜索界面时,开发人员通常有两种选择:

  1. 向用户建议最近的查询:这意味着每次用户进行搜索时,键入的查询将被保存在数据库中,以便以后检索作为未来搜索的建议;
  2. 向用户建议自定义选项:开发者会尝试通过处理已经输入的字母来预测用户想要什么;

在这两种情况下,答案都应作为 Cursor 对象返回,该对象的内容将在结果列表中显示为 itens。这整个过程可以使用 Content Provider API 来实现。有关如何使用内容提供者的详细信息,请访问此链接

在开发人员想要实现 1. 中描述的行为的情况下,使用扩展 SearchRecentSuggestionsProvider 类的策略会很有用。可以在此链接中获得有关如何执行此操作的详细信息。

实现搜索界面

该接口应提供以下行为:

  • 当用户键入一个字母时,对检索到的内容提供程序类的查询方法的调用应该返回一个填充的光标,其中包含要显示在列表中的建议 - 您应该注意不要冻结 UI 线程,所以我建议执行此操作在 AsyncTask 中搜索:

        public void onTextChanged(final CharSequence s, int start, int before, int count) {
            if (!"".equals(searchInput.getText().toString())) {
                query = searchInput.getText().toString();
    
                setClearTextIcon();
    
                if (isRecentSuggestionsProvider) {
                    // Provider is descendant of SearchRecentSuggestionsProvider
                    mapResultsFromRecentProviderToList(); // query is performed in this method
                } else {
                    // Provider is custom and shall follow the contract
                    mapResultsFromCustomProviderToList(); // query is performed in this method
                }
            } else {
                setMicIcon();
            }
        }
    
  • 在 AsyncTask 的 onPostExecute() 方法中,您应该检索一个列表(应该来自 doInBackground() 方法),其中包含要在 ResultList 中显示的结果(您可以将其映射到 POJO 类中并将其传递给您的自定义适配器,或者您可以使用 CursorAdapter 这将是此任务的最佳实践):

    protected void onPostExecute(List resultList) {
         SearchAdapter adapter = new SearchAdapter(resultList);
         searchResultList.setAdapter(adapter);
    }
    
    protected List doInBackground(Void[] params) {
        Cursor results = results = queryCustomSuggestionProvider();
        List<ResultItem> resultList = new ArrayList<>();
    
        Integer headerIdx = results.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
        Integer subHeaderIdx = results.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
        Integer leftIconIdx = results.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
        Integer rightIconIdx = results.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
    
        while (results.moveToNext()) {
            String header = results.getString(headerIdx);
            String subHeader = (subHeaderIdx == -1) ? null : results.getString(subHeaderIdx);
            Integer leftIcon = (leftIconIdx == -1) ? 0 : results.getInt(leftIconIdx);
            Integer rightIcon = (rightIconIdx == -1) ? 0 : results.getInt(rightIconIdx);
    
            ResultItem aux = new ResultItem(header, subHeader, leftIcon, rightIcon);
            resultList.add(aux);
        }
    
        results.close();
        return resultList;
    
  • 识别用户何时从软键盘触摸搜索按钮。当他这样做时,向可搜索的活动(负责处理搜索结果的活动)发送一个意图,并将查询作为额外信息添加到意图中

    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case VOICE_RECOGNITION_CODE: {
                if (resultCode == RESULT_OK && null != data) {
                    ArrayList<String> text = data.getStringArrayListExtra(RecognizerIntent.EXTRA_RESULTS);
                    searchInput.setText(text.get(0));
                }
                break;
            }
        }
    }
    
  • 识别用户何时单击显示的建议之一并发送包含项目信息的意图(此意图应该与上一步的意图不同)

    private void sendSuggestionIntent(ResultItem item) {
        try {
            Intent sendIntent = new Intent(this, Class.forName(searchableActivity));
            sendIntent.setAction(Intent.ACTION_VIEW);
            sendIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);
    
            Bundle b = new Bundle();
            b.putParcelable(CustomSearchableConstants.CLICKED_RESULT_ITEM, item);
    
            sendIntent.putExtras(b);
            startActivity(sendIntent);
            finish();
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
    

所描述的步骤应该足以实现您自己的接口。所有代码示例均取自此处。我已经制作了这个库,它可以完成上述大部分内容。它尚未经过良好测试,并且某些 UI 配置可能尚不可用。

我希望这个答案可以帮助有需要的人。

于 2015-07-03T04:34:52.603 回答
5

我已经设置了一个小教程来做到这一点

http://drzon.net/how-to-create-a-clearable-autocomplete-dropdown-with-autocompletetextview/

概述

我不得不按照建议替换SearchViewAutoCompleteTextView

首先,创建一个适配器。就我而言,它是一个JSONObjectArrayAdapter。我想在下拉列表中显示的数据是场地名称和场地地址。请注意,适配器必须是Filtarable并且覆盖getFilter()

// adapter for the search dropdown auto suggest
ArrayAdapter<JSONObject> searchAdapter = new ArrayAdapter<JSONObject>(this, android.R.id.text1) {
private Filter filter;

public View getView(final int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        convertView = this.getLayoutInflater().inflate(R.layout.search_item, parent, false);
    }

    TextView venueName = (TextView) convertView.findViewById(R.id.search_item_venue_name);
    TextView venueAddress = (TextView) convertView.findViewById(R.id.search_item_venue_address);

    final JSONObject venue = this.getItem(position);
    convertView.setTag(venue);
    try {

        CharSequence name = highlightText(venue.getString("name"));
        CharSequence address = highlightText(venue.getString("address"));

        venueName.setText(name);
        venueAddress.setText(address);
    }
    catch (JSONException e) {
        Log.i(Consts.TAG, e.getMessage());
    }

    return convertView;

}

@Override
public Filter getFilter() {
    if (filter == null) {
        filter = new VenueFilter();
    }
    return filter;
}
};

这是习俗VenueFilter

private class VenueFilter extends Filter {

    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
        List<JSONObject> list = new ArrayList<JSONObject>(venues);
        FilterResults result = new FilterResults();
        String substr = constraint.toString().toLowerCase();

        if (substr == null || substr.length() == 0) {
            result.values = list;
            result.count = list.size();
        } else {

            final ArrayList<JSONObject> retList = new ArrayList<JSONObject>();
            for (JSONObject venue : list) {
                try {
                    if (venue.getString("name").toLowerCase().contains(constraint) ||  venue.getString("address").toLowerCase().contains(constraint) || 
                         {
                        retList.add(venue);
                    }
                } catch (JSONException e) {
                    Log.i(Consts.TAG, e.getMessage());
                }
            }
            result.values = retList;
            result.count = retList.size();
        }
        return result;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
        searchAdapter.clear();
        if (results.count > 0) {
            for (JSONObject o : (ArrayList<JSONObject>) results.values) {
                searchAdapter.add(o);
            }
        }
    }

}

现在设置搜索框 ( actionbar_search.xml) 的布局:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="match_parent"
    android:layout_gravity="fill_horizontal"
    android:focusable="true" >

    <AutoCompleteTextView
        android:id="@+id/search_box"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:dropDownVerticalOffset="5dp"
        android:dropDownWidth="wrap_content"
        android:inputType="textAutoComplete|textAutoCorrect"
        android:popupBackground="@color/white"
        android:textColor="#FFFFFF" >
    </AutoCompleteTextView>

</RelativeLayout>

以及单个下拉项目的布局(场地名称和场地地址)。这个看起来很糟糕,你必须自定义它:

<?xml version="1.0" encoding="UTF-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:textAlignment="gravity" >

    <TextView
        android:id="@+id/search_item_venue_name"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textColor="@color/cyan"
        android:layout_gravity="right" />

    <TextView
        android:id="@+id/search_item_venue_address"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_toStartOf="@+id/search_item_venue_name"
        android:gravity="right"
        android:textColor="@color/white" />


</RelativeLayout>

接下来我们要把它放到action bar里面

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    ActionBar actionBar = getSupportActionBar();
    actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM | ActionBar.DISPLAY_USE_LOGO | ActionBar.DISPLAY_SHOW_HOME
            | ActionBar.DISPLAY_HOME_AS_UP);
    LayoutInflater inflater = (LayoutInflater)this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View v = inflater.inflate(R.layout.actionbar_search, null);
    AutoCompleteTextView textView =  (AutoCompleteTextView) v.findViewById(R.id.search_box);

    textView.setAdapter(searchAdapter);

    textView.setOnClickListener(new View.OnClickListener() {

        @Override
        public void onClick(View v) {
            // do something when the user clicks
        }
    });
    actionBar.setCustomView(v);
}

就是这样,我还有一些事情要弄清楚:

  • 这会在操作栏中放置一个“始终存在”搜索,我希望它像SearchView小部件 - 一个放大镜,当您单击它时会打开一个搜索框(并且有一个小X按钮可以关闭它并恢复正常)
  • 还没想好怎么自定义下拉框,比如Gmail好像有阴影,我的是平的,换行分隔符的颜色等等……

总的来说,这节省了创建可搜索活动的所有开销。如果您知道如何自定义它等,请添加。

于 2013-09-19T12:32:51.587 回答
2

如果您只想实现下拉效果,请选择AutoCompleteTextView

你可以在这里找到一个很好的教程

然后,如果你想实现精确的设计,你必须实现ActionBar ,如果你想实现更低的版本,你需要实现ActionBarCombat 而不是 ActionBar

于 2013-09-18T09:33:45.030 回答
2

您应该使用ListPopupWindow并将其锚定到搜索视图小部件。

于 2013-09-18T09:18:12.913 回答
2

我已经成功地使用了 Michaels 的回复(https://stackoverflow.com/a/18894726/2408033),但我不喜欢它的手动方式、膨胀视图并将其添加到操作栏、切换其状态等。

我将其修改为使用 ActionBar ActionView 而不是手动将视图添加到操作栏/工具栏。

我发现这工作得更好,因为我不需要像他在添加链接中的 toggleSearch 方法的示例中那样管理视图的打开/关闭状态和隐藏。它还可以与后退按钮完美配合。

在我的 menu.xml

 <item
        android:id="@+id/global_search"
        android:icon="@android:drawable/ic_menu_search"
        android:title="Search"
        app:actionLayout="@layout/actionbar_search"
        app:showAsAction="ifRoom|collapseActionView" />

在我的 onCreateOptionsMenu

 View actionView = menu.findItem(R.id.global_search).getActionView();
 searchTextView = (ClearableAutoCompleteTextView) actionView.findViewById(R.id.search_box);
 searchTextView.setAdapter(searchAdapter);

您可以在我的项目中找到该实现的完整工作版本。请注意,当我使用实际的 SearchView 过滤 listView 时,有两个搜索视图。

https://github.com/babramovitch/ShambaTimes/blob/master/app/src/main/java/com/shambatimes/schedule/MainActivity.java

于 2015-08-23T17:53:16.280 回答
1

请参阅此示例,它完全实现了您的要求: http ://wptrafficanalyzer.in/blog/android-searchview-widget-with-actionbarcompat-library/

于 2013-09-18T09:49:40.100 回答
-1

您需要使用collapseActionView属性。

collapseActionView 属性允许您的 SearchView 展开以占据整个操作栏,并在不使用时折叠回正常的操作栏项目。

例如:

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
    <item android:id="@+id/search"
          android:title="@string/search_title"
          android:icon="@drawable/ic_search"
          android:showAsAction="collapseActionView|ifRoom"
          android:actionViewClass="android.widget.SearchView" />
</menu>
于 2013-09-18T09:19:14.323 回答