If you just want a component that does what is discribed in the question, I suggest this library. You can also implement the out-of-the-bx searchable interface, however, be aware that it does have UI limitations:
To implement an interface similar to Gmail App, you will have to understand conceps of:
- Content Providers;
- Persisting data in SQLite
- Listview or RecyclerView and its adapters;
- Passing data between activities;
Final result should look something like:
There are many (many) ways to get to the same result (or better), I'll discribed one possible way.
Part 01: Layout
I decided to manage the entire interface in a new Activity, for that I've created three XML layouts:
custom_searchable.xml: assemblys all UI elements in one RelativeLayout that will serve as content for the 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: holds the search bar where the user will type his query. It will also contain the mic, erase and return 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: holds the UI elements to be displayed in the result list to be displayed in response to the user query;
<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"/>
Part 02: Implementing the SearchActivity
The idea is that, when the user types the search button (which you can place any where you want), this SearchActivity will be called. It has some main responsabilities:
Bind to the UI elements in the custom_searchable_header_layout.xml: by doing that, it is possible:
to provide listeners for the EditText (where the user will type his query):
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
}
}
add listener for the return button (which by its turn will just call finish() and return to the caller activity):
this.dismissDialog.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
finish();
}
calls the intent for 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);
}
}
});
}
Content Provider
When building the a search interface the developer has tipically two options:
- Suggest recent queries to the user: this implies that everytime the user makes a search, the typed query will be persisted in a database to be retrieved latter on as a suggestion for future searches;
- Suggest custom options to the user: the developer will try to predict what the user wants by processing the already typed letters;
In both cases the answers shall be delivered back as a Cursor object that will have its content displayed as itens in the result list. This whole process can be implement using the Content Provider API. Details about how to use Content Providers can be reached in this link.
In the case where the developer wants to implement the behavior described in 1., it can be usefull to use the strategy of exteding the SearchRecentSuggestionsProvider class. Details about how to do it can be reached in this link.
Implementing the search interface
This interface shall provide the following behavior:
When the user types a letter a call to the query method of the retrieved content provider class should return a filled cursor with the suggestion to be displayed in the list - you should take to not freeze the UI thread, so it I recommend to perform this search in an 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();
}
}
Inside the onPostExecute() method of your AsyncTask, you should retrieve a list (that should come from the doInBackground() method) containing the results to be displayed in the ResultList (you can map it in a POJO class and pass it to your custom adapter or you can use a CursorAdapter which would be the best practive for this task):
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(SearchMana
与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…