Search Function with Custom Search Suggestions

In this tutorial I will show you how you can create a dedicated search view with custom search suggestions („autocomplete“) in combination with a list of recent search queries. The data source for the autocomplete comes from a REST call that is executed every time the user types in a letter in the search field.

SearchView with custom search suggestions (autocomplete) loaded from ArrayList.

Search Functionality in Action

Have a look at this video to see it in action:

Prerequisites / General Info

In this project I am using the ButterKnife library for the binding of the layout and views (i.e. @BindView(R.id.relative_layout)). If you don’t use ButterKnife you have to alter the parts of the code snippets accordingly.

app // build.gradle

minSdkVersion 21
targetSdkVersion 26

implementation "com.jakewharton:butterknife:8.8.1"
implementation "com.zsoltsafrany:needle:1.0.0"

AndroidManifest.xml

Add this part to your AndroidManifest.xml file. Replace AppTheme.RedToolbar with whatever style you have for your toolbar in your project.

<activity
android:name=".SearchActivity"
android:launchMode="singleTop"
android:theme="@style/AppTheme.RedToolbar">
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>

<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".MainActivity" />
<meta-data
android:name="android.app.searchable"
android:resource="@xml/searchable" />
<meta-data
android:name="android.app.default_searchable"
android:value=".SearchActivity" />

</activity>

res/xml/searchable.xml

Create a searchable.xml file and place it in the /res/xml subfolder in your project.

<?xml version="1.0" encoding="utf-8"?>
<searchable xmlns:android="http://schemas.android.com/apk/res/android"
android:hint="TYPE TO SEARCH"
android:label="LABEL"
android:searchSuggestSelection=" ?"
android:voiceSearchMode="showVoiceSearchButton|launchRecognizer" />

SearchActivity

SearchActivity.java is an activity with a toolbar with an active search input field that is initially empty until search results will populate the RecyclerList. While the user is typing search suggestions will be displayed in a menu below the search field. It also saves the last 5 submitted search queries in the SharedPreferences.

The class looks like this:

SearchActivity.java

public class SearchActivity extends AppCompatActivity implements SearchView.OnQueryTextListener, SearchView.OnSuggestionListener, OnSearchResultListItemSelectedListener, OnSearchCompletedListener {
private static String TAG = SearchActivity.class.getSimpleName();

static final int GRID_COLUMN_COUNT = 2;
private static final String COLUMN_ID = "_id";
private static final String COLUMN_SUGGESTION = "suggestion";
private static final String COLUMN_CONTEXT = "context";
private static final String COLUMN_SCORE = "score";
private static final String DEFAULT = "default";

private SearchResultListItemRecyclerViewAdapter searchResultListItemRecyclerViewAdapter;
private MovieSearchSuggestionAdapter movieSearchSuggestionAdapter;
private String queryString;

private SearchManager searchManager;
private SearchView searchView;

private SharedPreferences sharedPreferences;
private SharedPreferences.Editor editor;

public static String[] columns = new String[]{COLUMN_ID, COLUMN_SUGGESTION, COLUMN_CONTEXT, COLUMN_SCORE};

@BindView(R.id.relative_layout)
RelativeLayout relativeLayout;
@BindView(R.id.searchresult_list)
RecyclerView recyclerView;
@BindView(R.id.empty)
View emptyView;
@BindView(R.id.empty_image_view)
ImageView emptyImageView;
@BindView(R.id.empty_label)
TextView emptyLabel;


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_search);
ButterKnife.bind(this);

getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setHomeButtonEnabled(true);

searchResultListItemRecyclerViewAdapter = new SearchResultListItemRecyclerViewAdapter(new ArrayList<>(), this, this);

GridLayoutManager gridLayoutManager = new GridLayoutManager(this, GRID_COLUMN_COUNT);
recyclerView.setLayoutManager(gridLayoutManager);
gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
switch (searchResultListItemRecyclerViewAdapter.getItemViewType(position)) {
case ListItem.TYPE_HEADER:
// Header uses 2 columns in 2 column grid
return GRID_COLUMN_COUNT;
default:
// Movie uses 1 column in 2 column grid
return 1;
}
}
});

recyclerView.setAdapter(searchResultListItemRecyclerViewAdapter);
recyclerView.addItemDecoration(new MovieGridListItemSpacingDecoration(GRID_COLUMN_COUNT, getResources().getDimensionPixelSize(R.dimen.grid_list_item_spacing), true));

movieSearchSuggestionAdapter = new MovieSearchSuggestionAdapter(getApplicationContext(), null, null, searchView);

sharedPreferences = getSharedPreferences(Preferences.CINEMAN, Context.MODE_PRIVATE);
editor = sharedPreferences.edit();
}

@Override
protected void onNewIntent(Intent intent) {
handleIntent(intent);
}

@Override
public void onStart() {
super.onStart();
if (searchResultListItemRecyclerViewAdapter.getItemCount() == 0) {
emptyView.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
emptyLabel.setText("Universelle Filmsuche");
} else {
emptyView.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.search_view_options_menu, menu);

searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
searchView = (SearchView) menu.findItem(R.id.search).getActionView();
searchView.setMaxWidth(Integer.MAX_VALUE);
searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
searchView.setOnQueryTextListener(this);
searchView.setIconifiedByDefault(true);
searchView.setQueryHint(getString(R.string.movies_search));
searchView.setElevation(10);
searchView.onActionViewExpanded(); // Activate on launch
searchView.setSuggestionsAdapter(movieSearchSuggestionAdapter);

return true;
}

@OnClick(R.id.empty)
void clickEmptyView() {
searchView.onActionViewExpanded();
}

@Override
public boolean onQueryTextSubmit(String query) {
queryString = query.trim();
if (queryString.length() > 2) {
handleAndUpdateSearchHistoryList(queryString);
searchResultListItemRecyclerViewAdapter.globalMovieSearch(query);
} else {
Handler handler = new Handler();
handler.postDelayed(() -> Snackbar.make(relativeLayout, "Bitte gebe mindestens drei Zeichen für die Suche ein.", Snackbar.LENGTH_LONG).show(), 400);
}

ViewUtils.hideKeyboard(this, searchView);
return true;
}

@Override
public boolean onQueryTextChange(String newText) {
queryString = newText.trim();
autocomplete(queryString);
return true;
}

private void autocomplete(final String query) {
final MatrixCursor matrixCursor = new MatrixCursor(columns);
final Object[] object = new Object[]{0, DEFAULT, DEFAULT, DEFAULT};

// Get the Search History Items
String searchHistoryCommaSeparated = sharedPreferences.getString(Preferences.PREFS_SEARCH_HISTORY_ITEMS, "");
List<String> searchHistoryItemList = new ArrayList<>();
String[] searchHistoryArray = searchHistoryCommaSeparated.split(",");
if (searchHistoryArray.length > 1) {
Collections.addAll(searchHistoryItemList, searchHistoryArray);
}

// Delete duplicate search history items
HashSet<String> hashSet = new HashSet<>();
hashSet.addAll(searchHistoryItemList);
searchHistoryItemList.clear();
searchHistoryItemList.addAll(hashSet);

if ((query != null) && (query.trim().length() > 2)) {
CinemanAPI.movie().autocomplete(query.trim(), new RepositoryEntityListCallback<Autocomplete>() {

@Override
public void onSuccess(List<Autocomplete> tList, boolean isCachedResponse) {

int i = 0;
while (i < tList.size()) {
object[0] = i;
object[1] = tList.get(i).getSuggestion();
object[2] = tList.get(i).getContext();
object[3] = tList.get(i).getScore();

matrixCursor.addRow(object);
i++;
}

for (String historyQuery : searchHistoryItemList) {
Autocomplete ac = new Autocomplete();
ac.setContext("history");
ac.setScore(1000);
ac.setSuggestion(historyQuery);
tList.add(ac);

object[0] = i;
object[1] = ac.getSuggestion();
object[2] = ac.getContext();
object[3] = ac.getScore();

matrixCursor.addRow(object);
i++;
}

updateAdapter(matrixCursor, tList);
}

@Override
public void onFailure(Exception e) {
Log.w(TAG, e.getLocalizedMessage());
}
});
} else if ((query != null) && (query.trim().length() > 0)) {
List<Autocomplete> tList = new ArrayList<>();

int i = 0;
for (String historyQuery : searchHistoryItemList) {
if (historyQuery.length() > 0) {
Autocomplete ac = new Autocomplete();
ac.setContext("history");
ac.setScore(1000);
ac.setSuggestion(historyQuery);
tList.add(ac);

object[0] = i;
object[1] = ac.getSuggestion();
object[2] = ac.getContext();
object[3] = ac.getScore();

matrixCursor.addRow(object);
i++;
}
}

updateAdapter(matrixCursor, tList);
} else {
updateAdapter(matrixCursor, new ArrayList<>());
}
}

private void updateAdapter(MatrixCursor matrixCursor, List<Autocomplete> tList) {
movieSearchSuggestionAdapter = new MovieSearchSuggestionAdapter(getApplicationContext(), matrixCursor, tList, searchView);
searchView.setSuggestionsAdapter(movieSearchSuggestionAdapter);
movieSearchSuggestionAdapter.swapCursor(matrixCursor);
movieSearchSuggestionAdapter.notifyDataSetChanged();
}

private void updateUI() {
if (searchResultListItemRecyclerViewAdapter.getItemCount() == 0) {
emptyView.setVisibility(View.VISIBLE);
recyclerView.setVisibility(View.GONE);
emptyImageView.setImageResource(R.drawable.shrug);
emptyLabel.setText("NOTHING FOUND");
} else {
emptyView.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
}
}

@Override
public void onTheatreListItemSelected(Theatre theatre) {
// TODO kommt später mal...
}

@Override
public void onMovieGridListItemSelected(Movie movie) {
if (movie != null) {
// Intent intent = new Intent(this, MovieDetailActivity.class);
// intent.putExtra(movie);
// startActivity(intent);
}
}

private void handleIntent(Intent intent) {
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
queryString = intent.getStringExtra(SearchManager.QUERY);
if (queryString.length() > 1) {
searchResultListItemRecyclerViewAdapter.globalMovieSearch(queryString);
searchView.clearFocus();
}
}
}

private void handleAndUpdateSearchHistoryList(String nextSearchQueryAddition) {
String searchHistoryCommaSeparated = sharedPreferences.getString(Preferences.PREFS_SEARCH_HISTORY_ITEMS, "");
List<String> searchHistoryItemList = new ArrayList<>();

String[] searchHistoryArray = searchHistoryCommaSeparated.split(",");
if (searchHistoryArray.length > 0) {
Collections.addAll(searchHistoryItemList, searchHistoryArray);
}

searchHistoryItemList.add(nextSearchQueryAddition);
if (searchHistoryItemList.size() > 5) {
searchHistoryItemList = searchHistoryItemList.subList(1, searchHistoryItemList.size()); // Limit to 5 items
}

StringBuilder csvBuilder = new StringBuilder();
for (String query : searchHistoryItemList) {
csvBuilder.append(query);
csvBuilder.append(",");
}

String historyQueriesString = csvBuilder.toString();
String historyQueriesCommaSeparated = historyQueriesString.substring(0, historyQueriesString.length() - ",".length());

editor.putString(Preferences.PREFS_SEARCH_HISTORY_ITEMS, historyQueriesCommaSeparated);
editor.apply();
}

@Override
public void onOnSearchCompleted() {
updateUI();

// Hide the autocomplete dropdown list
onQueryTextChange("");
}

@Override
public boolean onSuggestionSelect(int position) {
searchView.clearFocus();
return false;
}

@Override
public boolean onSuggestionClick(int position) {
searchView.clearFocus();
return false;
}
}

OnSearchResultListItemSelectedListener.java

interface OnSearchResultListItemSelectedListener {
void onMovieGridListItemSelected(Movie movie);
}

RepositoryEntityListCallback.java

public abstract class RepositoryEntityListCallback<T> {
public abstract void onSuccess(List<T> entityList, boolean isCachedResponse);

public abstract void onFailure(Exception e);
}

Autocomplete.java

public class Autocomplete {
private int score;
private String suggestion;
private String context;

public int getScore() {
return score;
}

public void setScore(int score) {
this.score = score;
}

public String getSuggestion() {
return suggestion;
}

public void setSuggestion(String suggestion) {
this.suggestion = suggestion;
}

public String getContext() {
return context;
}

public void setContext(String context) {
this.context = context;
}

}

activity_search.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/relative_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorBackground"
tools:context=".SearchActivity">

<android.support.v7.widget.RecyclerView xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/searchresult_list"
android:name=".SearchActivity"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="GridLayoutManager"
tools:context=".SearchActivity"
tools:listitem="@layout/movie_grid_list_item" />

<RelativeLayout
android:id="@+id/empty"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/colorBackground"
android:clickable="true"
android:visibility="gone">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:orientation="vertical"
android:paddingBottom="33dp">

<ImageView
android:id="@+id/empty_image_view"
app:srcCompat="@drawable/ic_onboarding_map"
android:layout_width="match_parent"
android:layout_height="67dp"
android:scaleType="fitCenter" />

<TextView
android:id="@+id/empty_label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingLeft="48dp"
android:paddingTop="16dp"
android:paddingRight="48dp"
android:text="@string/settings_regions_empty_alert"
android:textAppearance="@style/TextAppearance.primaryText" />

</LinearLayout>

</RelativeLayout>
</RelativeLayout>

search_view_options_menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">

<item
android:id="@+id/search"
android:icon="@drawable/ic_search"
android:showAsAction="always"
android:title="@string/movies_search"
app:actionViewClass="android.support.v7.widget.SearchView"
app:showAsAction="always"
tools:ignore="AppCompatResource" />

</menu>

movie_grid_list_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<ImageView
android:id="@+id/moviePoster"
android:layout_width="match_parent"
android:layout_height="@dimen/grid_list_item_poster_height"
android:scaleType="centerCrop" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/colorGridItemBackground"
android:orientation="horizontal">

<TextView
android:id="@+id/movieNameLabel"
android:gravity="center_vertical|left"
android:layout_width="0dp"
android:layout_height="44dp"
android:layout_marginTop="6dp"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="2"
android:paddingLeft="@dimen/grid_list_item_label_padding_left_right"
android:textAppearance="@style/TextAppearance.posterText" />

<LinearLayout
android:id="@+id/watchlistButton"
android:layout_width="48dp"
android:layout_height="56dp"
android:clickable="true">

<ImageView
android:id="@+id/watchlistButtonImageView"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginLeft="16dp"
android:layout_marginRight="8dp"
android:layout_marginTop="14dp"
android:scaleType="fitXY"
app:srcCompat="@drawable/ic_add_circle_outline_black" />

</LinearLayout>

</LinearLayout>

</LinearLayout>

MovieSearchSuggestionAdapter.java

public class MovieSearchSuggestionAdapter extends CursorAdapter implements View.OnClickListener {
private final static String TAG = MovieSearchSuggestionAdapter.class.getSimpleName();

private Context context;
private final LayoutInflater mInflater;
private ImageView imageView;
private TextView suggestionTextView;
private SearchView searchView;

private final List<Autocomplete> autocompleteList;

public MovieSearchSuggestionAdapter(final Context context, final Cursor cursor, final List<Autocomplete> autocompleteList, SearchView searchView) {
super(context, cursor, 0);

this.context = context;
this.searchView = searchView;
this.autocompleteList = autocompleteList;
this.mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}

@Override
public View newView(Context context, Cursor cursor, ViewGroup parent) {
final View view = mInflater.inflate(R.layout.search_result_item_layout, parent, false);

imageView = view.findViewById(R.id.icon);
suggestionTextView = view.findViewById(R.id.suggestion);

return view;
}

@Override
public void bindView(View view, Context context, Cursor cursor) {
final int position = cursor.getPosition();
if (cursorInBounds(position)) {

final Autocomplete auto = autocompleteList.get(position);
suggestionTextView.setText(auto.getSuggestion());

if (auto.getContext().equalsIgnoreCase("movie")) {
imageView.setImageResource(R.drawable.ic_search);
} else if (auto.getContext().equalsIgnoreCase("history")) {
imageView.setImageResource(R.drawable.ic_time);
}

view.setTag(position);
view.setOnClickListener(this);
} else {
// Something went wrong
}
}

private boolean cursorInBounds(final int position) {
return position < autocompleteList.size();
}

@Override
public void onClick(final View view) {
final int position = (Integer) view.getTag();

if (cursorInBounds(position)) {
final Autocomplete selected = autocompleteList.get(position);

ViewUtils.hideKeyboard(context, searchView);
searchView.setQuery(selected.getSuggestion(), true);
} else {
// Something went wrong
}
}


}

sarch_result.item.xml

<?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="wrap_content"
android:background="@color/colorBackground">

<ImageView
android:id="@+id/icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:layout_margin="10dp"
android:padding="5dp"
android:src="@drawable/ic_search" />

<TextView
android:id="@+id/suggestion"
android:layout_width="wrap_content"
android:layout_height="23dp"
android:layout_centerVertical="true"
android:layout_toRightOf="@id/icon" />

</RelativeLayout>

SearchResultListItemRecyclerViewAdapter.java

class SearchResultListItemRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private final static String TAG = SearchResultListItemRecyclerViewAdapter.class.getSimpleName();

private List<Movie> movieList;
private List<ListItem> listItems;
private final OnSearchCompletedListener onSearchCompletedListener;
private final OnSearchResultListItemSelectedListener onSearchResultListItemSelectedListener;

SearchResultListItemRecyclerViewAdapter(List<Movie> items, OnSearchResultListItemSelectedListener onSearchResultListItemSelectedListener, OnSearchCompletedListener onSearchCompletedListener) {
listItems = new ArrayList<>();
this.onSearchResultListItemSelectedListener = onSearchResultListItemSelectedListener;
this.onSearchCompletedListener = onSearchCompletedListener;
setMovieList(items);

globalMovieSearch(null);
}

@Override
public int getItemViewType(int position) {
return listItems.get(position).getType();
}

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType == ListItem.TYPE_HEADER) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.generic_list_header_item, parent, false);
return new GenericListHeaderViewHolder(itemView);
}

View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.movie_search_result_grid_list_item, parent, false);
return new MovieSearchResultGridListItemViewHolder(itemView);
}

@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int type = getItemViewType(position);

if (type == ListItem.TYPE_HEADER) {
GenericListHeaderItem genericListHeaderItem = (GenericListHeaderItem) listItems.get(position);
GenericListHeaderViewHolder genericListHeaderItemViewHolder = (GenericListHeaderViewHolder) holder;
genericListHeaderItemViewHolder.setLabelText(genericListHeaderItem.getLabel());
return;
} else {
final MovieGridListItem movieGridListItem = (MovieGridListItem) listItems.get(position);
MovieSearchResultGridListItemViewHolder movieSearchResultGridListItemViewHolder = (MovieSearchResultGridListItemViewHolder) holder;
movieSearchResultGridListItemViewHolder.setMoviePosition(movieGridListItem.getMoviePosition());
movieSearchResultGridListItemViewHolder.setMovie(movieGridListItem.getMovie(), CinemanApplication.getCurrentAppInstance().getApplicationContext());
movieSearchResultGridListItemViewHolder.view.setOnClickListener(v -> onSearchResultListItemSelectedListener.onMovieGridListItemSelected(movieGridListItem.getMovie()));
}
}


@Override
public int getItemCount() {
return listItems.size();
}

void setMovieList(List<Movie> movieList) {
this.movieList = movieList;
}

/**
* Replace "
CinemanAPI.movie().searchMovies()" with your own REST call / data access with the search query from the parameter for the search. The return type in this example is an ArrayList with Movie objects which gets forwarded to the formatMovieList() method where we format the list to head- and body-type of recycler list (or grid for that matter) items. This way we could group the search results in various sections i.e. movies, theatres etc.
*
*
@param query
*/
public void globalMovieSearch(final String query) {
if ((query != null) && (query.trim().length() > 2)) {
CinemanAPI.movie().searchMovies(query.trim(), new RepositoryEntityListCallback<Movie>() {

@Override
public void onSuccess(List<Movie> mList, boolean isCachedResponse) {
movieList.clear();
movieList.addAll(mList);

formatMovieList(mList);
}

@Override
public void onFailure(Exception e) {
Log.e(TAG, e.getLocalizedMessage());
formatMovieList(new ArrayList<>());
}
});
} else {
movieList.clear();
}
}

/**
* Loop through the movie list and create head- and movie items
*
*
@param mList
*/
private void formatMovieList(List<Movie> mList) {
final List<ListItem> newListItems = new ArrayList<>();

Needle.onBackgroundThread().withTaskType("globalMovieSearch").serially().execute(() -> {
GenericListHeaderItem genericListHeaderItem = new GenericListHeaderItem();
genericListHeaderItem.setLabel(CinemanApplication.getCurrentAppInstance().getString(R.string.movies_title));
newListItems.add(genericListHeaderItem);

int i = 2;
for (Movie movie : mList) {
MovieGridListItem movieGridListItem = new MovieGridListItem();
movieGridListItem.setMoviePosition(++i);
movieGridListItem.setMovie(movie);
newListItems.add(movieGridListItem);
}

if (mList.size() == 0) {
newListItems.clear();
}

Needle.onMainThread().execute(() -> {
listItems.clear();
listItems.addAll(newListItems);
notifyDataSetChanged();
onSearchCompletedListener.onOnSearchCompleted();
});
});
}

}

generic_list_header_item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

<TextView
android:id="@+id/label"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginTop="@dimen/activity_vertical_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_marginBottom="@dimen/activity_vertical_margin"
android:ellipsize="end"
android:maxLines="2"
android:textAppearance="@style/TextAppearance.subHeader" />
</LinearLayout>

movie_search_result_grid_list_item.xml

<?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="wrap_content"
android:background="@color/ticketingHighlightColor">

<ImageView
android:id="@+id/movie_poster"
android:layout_width="match_parent"
android:layout_height="@dimen/grid_list_item_poster_height"
android:scaleType="centerCrop" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@+id/movie_poster"
android:layout_alignParentBottom="true"
android:background="@color/colorGridItemBackground"
android:orientation="horizontal">

<TextView
android:id="@+id/movie_title_label"
android:layout_width="wrap_content"
android:layout_height="44dp"
android:layout_marginLeft="@dimen/grid_list_item_label_padding_left_right"
android:layout_marginTop="6dp"
android:layout_weight="1"
android:ellipsize="end"
android:gravity="center_vertical|left"
android:maxLines="2"
android:text="Movie Title"
android:textAppearance="@style/TextAppearance.posterText" />

</LinearLayout>

</RelativeLayout>

GenericListHeaderViewHolder.java

class GenericListHeaderViewHolder extends RecyclerView.ViewHolder {
final View view;
private String labelText;

@BindView(R.id.label)
TextView labelView;

String getLabelText() {
return labelText;
}

void setLabelText(String labelText) {
this.labelText = labelText;
labelView.setText(labelText);
}

GenericListHeaderViewHolder(View view) {
super(view);
this.view = view;
ButterKnife.bind(this, view);
}

@Override
public String toString() {
return super.toString() + " '" + labelView.getText() + "'";
}
}

MovieSearchResultGridListItemViewHolder.java

class MovieSearchResultGridListItemViewHolder extends RecyclerView.ViewHolder {
private final static double posterRatio = 1.3825;
final View view;
private Movie movie;
private int moviePosition = -1;
private ColorDrawable placeHolderColorDrawable;

@BindView(R.id.movie_poster)
ImageView moviePoster;
@BindView(R.id.movie_title_label)
TextView movieNameLabel;


Movie getMovie() {
return movie;
}

void setMovie(Movie movie, Context context) {
this.movie = movie;
movieNameLabel.setText(movie.getTitle());
if (movie.getColorMeta() != null && movie.getColorMeta().getPosterBest() != null && movie.getColorMeta().getPosterBest().size() > 0) {
String hexColor = String.format("#%06X", (0xFFFFFF & movie.getColorMeta().getPosterBest().get(0)));
placeHolderColorDrawable.setColor(Color.parseColor(hexColor));
Picasso.with(context).load(movie.getPoster().getUrl2x()).placeholder(placeHolderColorDrawable).into(moviePoster);
} else {
Picasso.with(context).load(movie.getPoster().getUrl2x()).into(moviePoster);
}

public int getMoviePosition() {
return moviePosition;
}

public void setMoviePosition(int moviePosition) {
this.moviePosition = moviePosition;
}

MovieSearchResultGridListItemViewHolder(View view) {
super(view);
this.view = view;
ButterKnife.bind(this, view);

placeHolderColorDrawable = new ColorDrawable();
int imageWidth = (CinemanApplication.getCurrentAppInstance().getScreenWidth() - ((int) view.getResources().getDimension(R.dimen.grid_list_item_spacing) * (MovieGridListFragment.GRID_COLUMN_COUNT + 1))) / MovieGridListFragment.GRID_COLUMN_COUNT;
int desiredHeight = (int) (imageWidth * posterRatio);
if (moviePoster.getLayoutParams().height != desiredHeight) {
moviePoster.getLayoutParams().height = desiredHeight;
moviePoster.requestLayout();
}
}

@Override
public String toString() {
return super.toString() + " '" + movieNameLabel.getText() + "'";
}
}

Launching the Search

To start the search add this code block to any other activity or button click event.

Intent intent = new Intent(getActivity(), SearchActivity.class);
startActivity(intent);

Got improvements or questions? Or is anything missing? Let me know in the comments section below.

About Dominik
Currently working as an Android/iOS developer for Cineman, Switzerland. Owner of http://www.ukon.ch and https://play.google.com/store/apps/details?id=com.cheatdatabase

Speak Your Mind

Tell us what you're thinking...
and oh, if you want a pic to show with your comment, go get a gravatar!

You must be logged in to post a comment.