如何在操作栏中构建类似搜索框的Gmail?

Mic*_*ael 17 android autocomplete android-actionbar searchview

我目前正在使用SearchViewwidget ActionBarcompat来搜索时过滤列表.

当用户开始输入文本时,主布局中的ListView会使用适配器进行更新以过滤结果.我这样做是通过实现OnQueryTextListener并过滤每个键击的结果.

相反,我想创建一个类似Gmail的搜索框,其中包含生成的自动建议列表,并且不会更改基础视图

在此输入图像描述

我已经阅读了使用该SearchView组件的本教程,但它需要一个可搜索的活动.我希望下拉列表位于MainActivity上,我有ListView(就像在Gmail应用程序中一样),而不是专用的Activity.
此外,以与教程相同的方式实现它似乎对我想要的东西(只是一个下拉列表)有点过分

Edg*_*des 26

如果你只是想要一个能够解决问题所描述内容的组件,我建议使用这个.您还可以实现out-of-the-bx 可搜索界面,但请注意,它确实存在UI限制:

要实现类似于Gmail App的界面,您必须了解以下内容:

  • 内容提供商;
  • 在SQLite中保留数据
  • Listview或RecyclerView及其适配器;
  • 在活动之间传递数据;

最终结果应该类似于:

最后结果

有很多(很多)方法可以得到相同的结果(或更好),我会描述一种可能的方式.

第01部分:布局

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

  • custom_searchable.xml:汇集一个RelativeLayout中的所有UI元素,它们将作为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"/>
    
    Run Code Online (Sandbox Code Playgroud)

  • 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>
    
    Run Code Online (Sandbox Code Playgroud)

  • 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"/>
    
    Run Code Online (Sandbox Code Playgroud)

第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
       }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 为返回按钮添加侦听器(依次调用finish()并返回调用者活动):

    this.dismissDialog.setOnClickListener(new View.OnClickListener() {
        public void onClick(View v) {
        finish();
    }    
    
    Run Code Online (Sandbox Code Playgroud)
  • 调用谷歌语音到文本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);
                    }
                }
            });
        }
    
    Run Code Online (Sandbox Code Playgroud)

内容提供商

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

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

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

在开发人员想要实现1.中描述的行为的情况下,使用exventing 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();
            }
        }
    
    Run Code Online (Sandbox Code Playgroud)
  • 在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;
    
    Run Code Online (Sandbox Code Playgroud)
  • 确定用户何时从软键盘触摸搜索按钮.当他这样做时,向可搜索的活动(负责处理搜索结果的活动)发送意图并将该查询添加为意图中的额外信息

    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;
            }
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  • 确定用户何时单击显示的建议之一以及发送和包含项目信息的意图(此意图应与上一步骤不同)

    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();
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)

所描述的步骤应足以实现您自己的界面.所有代码示例均来自此处.我已经使这个库完成了上面描述的内容.它尚未经过良好测试,部分UI配置可能尚未推出.

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

  • 真棒的答案!你应该得到更多的赞成! (4认同)

Mic*_*ael 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;
}
};
Run Code Online (Sandbox Code Playgroud)

这是自定义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);
            }
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

现在设置搜索框(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>
Run Code Online (Sandbox Code Playgroud)

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

<?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>
Run Code Online (Sandbox Code Playgroud)

接下来我们想把它放在动作栏中

@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);
}
Run Code Online (Sandbox Code Playgroud)

就是这样,我还有一些东西需要弄清楚:

  • 这会在操作栏中显示"始终存在"搜索,我希望它像SearchView小工具一样 - 放大镜玻璃,当您单击它时会打开一个搜索框(并且有一个小X按钮可以将其关闭并恢复正常)
  • 还没弄清楚如何自定义下拉框,例如,Gmail似乎有阴影,我的只是扁平,改变行分隔符的颜色等...

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


Evg*_*urg 2

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