web-dev-qa-db-ja.com

アクションバーに検索ボックスのようなGmailを作成するにはどうすればよいですか?

現在、SearchView内のActionBarcompatウィジェットを使用して、検索中にリストをフィルタリングしています。

ユーザーがテキストの入力を開始すると、メインレイアウトのListViewが結果をフィルターするアダプターで更新されます。これを行うには、OnQueryTextListenerを実装し、各キーストロークの結果をフィルター処理します。

代わりに、自動提案リストを生成し、基になるビューを変更しない、Gmailのような検索ボックスを作成します

enter image description here

SearchViewコンポーネントを使用する このチュートリアル を実行しましたが、searchableアクティビティが必要です。専用のアクティビティではなく、(Gmailアプリのように)リストビューがあるMainActivityの上にドロップダウンを配置します。
さらに、チュートリアルと同じ方法で実装することは、私が欲しいもの(単なるドロップダウン)に対してはやり過ぎのようです。

18
Michael

質問で説明されていることを行うコンポーネントだけが必要な場合は、これをお勧めします ライブラリ 。 out-of-the-bx searchable interface を実装することもできますが、UIの制限があることに注意してください。

Gmailアプリと同様のインターフェースを実装するには、次の概念を理解する必要があります。

  • コンテンツプロバイダー;
  • SQLiteでのデータの永続化
  • ListviewまたはRecyclerViewおよびそのアダプター。
  • アクティビティ間でのデータの受け渡し。

最終結果は次のようになります:

final result

同じ結果(またはそれ以上)を得るには(多くの)方法がたくさんあります。可能な方法の1つについて説明します。

パート01:レイアウト

新しいアクティビティでインターフェイス全体を管理することにしました。そのため、3つのXMLレイアウトを作成しました。

  • custom_searchable.xml :SearchActivityのコンテンツとして機能する1つのRelativeLayoutにすべてのUI要素をアセンブリします。

    <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 :ユーザーがクエリを入力する検索バーを保持します。また、mic、erase、および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();
    }    
    
  • google speech-to-text 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);
                    }
                }
            });
        }
    

コンテンツプロバイダー

検索インターフェースを構築する場合、開発者には通常2つのオプションがあります。

  1. 最近のクエリをユーザーに提案する:これは、ユーザーが検索を行うたびに、入力されたクエリがデータベースに保持され、後で検索するための提案として検索されることを意味します。
  2. ユーザーにカスタムオプションを提案する:開発者は、既に入力された文字を処理することにより、ユーザーが望むものを予測しようとします。

どちらの場合も、回答は、結果リストに表示されたときにコンテンツが表示されるCursorオブジェクトとして返されます。このプロセス全体は、コンテンツプロバイダーAPIを使用して実装できます。コンテンツプロバイダーの使用方法の詳細については、この link を参照してください。

開発者が1.で説明した動作を実装したい場合は、SearchRecentSuggestionsProviderクラスを拡張する戦略を使用すると便利です。それを行う方法の詳細は、この link で到達できます。

検索インターフェースの実装

このインターフェースは、次の動作を提供します。

  • ユーザーが文字を入力すると、取得したコンテンツプロバイダークラスのクエリメソッドを呼び出すと、塗りつぶされたカーソルが返され、リストに表示される候補が表示されます。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()メソッド内で、ResultListに表示される結果を含むリスト(doInBackground()メソッドからのリスト)を取得する必要があります(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;
            }
        }
    }
    
  • ユーザーが表示された提案の1つをクリックし、アイテム情報を含むインテントを送信するタイミングを特定します(このインテントは前のステップのインテントとは異なる必要があります)

    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();
        }
    }
    

記述されたステップは、あなた自身のインターフェースを実装するのに十分でなければなりません。すべてのコード例は here から取得されました。上記で説明したことのほとんどを行うライブラリを作成しました。まだ十分にテストされておらず、UI構成の一部はまだ使用できない場合があります。

この答えが困っている人の助けになることを願っています。

それを行うための小さなチュートリアルを設定しました

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

概要

提案どおり、SearchViewAutoCompleteTextViewに置き換える必要がありました。

まず、アダプターを作成します。私の場合、それはJSONObject ArrayAdapterでした。ドロップダウンに表示したいデータは、会場名と会場アドレスでした。アダプターは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>

次に、アクションバーに配置します

@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には影があるように見えます、私のものは平らです、線の区切り文字の色を変更します...

全体的に、これはsearchableアクティビティを作成するオーバーヘッドをすべて節約します。カスタマイズ方法等ご存知の方は追加してください。

5
Michael

ListPopupWindow を使用して、検索ビューウィジェットにアンカーする必要があります。

2
Evgeni Roitburg

ドロップダウン効果のみを実装したい場合は、 AutoCompleteTextView

そして、あなたは素敵なチュートリアルを見つけることができます ここ

次に、正確な設計を実装する場合は ActionBar を実装する必要があります。下位バージョンに実装する場合は、ActionBarの代わりに ActionBarCombat を実装する必要があります。

2
NARESH REDDY

私はMichaelsの返信( https://stackoverflow.com/a/18894726/24080 )をうまく使用しましたが、手動で操作するのが好きではありませんでした。ビューを膨らませてアクションバーに追加しました。状態の切り替えなど.

ビューを手動でactionbar/toolbarに追加する代わりに、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をフィルター処理したため、2つの検索ビューがあることに注意してください。

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

2
Ben987654

あなたが要求したものを正確に実装するこの例を参照してください: http://wptrafficanalyzer.in/blog/Android-searchview-widget-with-actionbarcompat-library/

1
Evgeni Roitburg