Android: Providing recent search suggestions without searchable activity?

懵懂的女人 提交于 2019-11-27 17:37:58

Ok, I spent my time for this. I make my own simple suggestion implementation from SQLiteDatabase.

We will create 3 classes like the following

  1. MainActivity - for test of SearchView suggestion from database
  2. SuggestionDatabase - this will store your recent search keyword.
  3. SuggestionSimpleCursorAdapter - this is a subclass of SimpleCursorAdapter. I'll explain why I make this class instead of using SimpleCursorAdapter.

The codes

// MainActivity.java

public class MainActivity 
    extends Activity
    implements SearchView.OnQueryTextListener,
                SearchView.OnSuggestionListener
{

    private SuggestionsDatabase database;
    private SearchView searchView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);


        database = new SuggestionsDatabase(this);
        searchView = (SearchView) findViewById(R.id.searchView1);
        searchView.setOnQueryTextListener(this); 
        searchView.setOnSuggestionListener(this);
    }

    @Override
    public boolean onSuggestionSelect(int position) {

        return false;
    }

    @Override
    public boolean onSuggestionClick(int position) {

        SQLiteCursor cursor = (SQLiteCursor) searchView.getSuggestionsAdapter().getItem(position);
        int indexColumnSuggestion = cursor.getColumnIndex( SuggestionsDatabase.FIELD_SUGGESTION);

        searchView.setQuery(cursor.getString(indexColumnSuggestion), false);

        return true;
    }

    @Override
    public boolean onQueryTextSubmit(String query) {
        long result = database.insertSuggestion(query);
        return result != -1;
    }

    @Override
    public boolean onQueryTextChange(String newText) {

        Cursor cursor = database.getSuggestions(newText);
        if(cursor.getCount() != 0)
        {
            String[] columns = new String[] {SuggestionsDatabase.FIELD_SUGGESTION };
            int[] columnTextId = new int[] { android.R.id.text1};

            SuggestionSimpleCursorAdapter simple = new SuggestionSimpleCursorAdapter(getBaseContext(),
                    android.R.layout.simple_list_item_1, cursor,
                    columns , columnTextId
                    , 0);

            searchView.setSuggestionsAdapter(simple);
            return true;
        }
        else
        {
            return false;
        }
    }

}

How it works

  1. When user taps the search button, the onQueryTextSubmit() will be triggered and then the search keyword will be saved in our database. Let's say we submit a keyword "Hello"
  2. If the user writes a string for example "Hel" or "H" in SearchView the onQueryTextChange() will be called and then we search this keyword in SQLiteDatabase (SuggestionDatabase). If "Hel" or "H" matches "Hello" , display the results of query by setting the returned Cursor in SuggestionSimpleCursorAdapter and then set this adapter in SearchView. Here's the picture.


3. Ofcourse we will tap the suggestion which is "Hello", onSuggestionClick(int position) will be called for that. We get the SQLiteCursor object from the SearchView's adapter (SuggestionSimpleCursorAdapter) and get the Suggestion text from it, set the suggestion text in SearchView object
SQLiteCursor cursor = (SQLiteCursor) searchView.getSuggestionsAdapter().getItem(position);
int indexColumnSuggestion = cursor.getColumnIndex( SuggestionsDatabase.FIELD_SUGGESTION);
searchView.setQuery(cursor.getString(indexColumnSuggestion), false);

If we use SimpleCursorAdapter it also works properly but let's say we have this scenario

  1. If We run this program in smartphone and type the keyword "Hel", the suggestion will appear properly.

  1. What if We rotate the screen in landscape? It will switch in Full Screen mode and you can still type the keyword.

What will happen in suggestion? Let's take a look.

See the weird suggestion? How we solve that? By overriding the convertToString(Cursor cursor) which returns a CharSequence

 // SuggestionSimpleCursorAdapter.java
public class SuggestionSimpleCursorAdapter
    extends SimpleCursorAdapter
{

    public SuggestionSimpleCursorAdapter(Context context, int layout, Cursor c,
            String[] from, int[] to) {
        super(context, layout, c, from, to);
    }

    public SuggestionSimpleCursorAdapter(Context context, int layout, Cursor c,
            String[] from, int[] to, int flags) {
        super(context, layout, c, from, to, flags);
    }

    @Override
    public CharSequence convertToString(Cursor cursor) {

        int indexColumnSuggestion = cursor.getColumnIndex(SuggestionsDatabase.FIELD_SUGGESTION);

        return cursor.getString(indexColumnSuggestion);
    }


}

By overriding convertToString(Cursor cursor) , here's the result

And here's the database

// SuggestionDatabase.java
public class SuggestionsDatabase {

  public static final String DB_SUGGESTION = "SUGGESTION_DB";
  public final static String TABLE_SUGGESTION = "SUGGESTION_TB";
  public final static String FIELD_ID = "_id";
  public final static String FIELD_SUGGESTION = "suggestion";

  private SQLiteDatabase db;
  private Helper helper;

  public SuggestionsDatabase(Context context) {

    helper = new Helper(context, DB_SUGGESTION, null, 1);
    db = helper.getWritableDatabase();
  }

  public long insertSuggestion(String text)
  {
    ContentValues values = new ContentValues();
    values.put(FIELD_SUGGESTION, text);
    return db.insert(TABLE_SUGGESTION, null, values);
  }

  public Cursor getSuggestions(String text)
  {
    return db.query(TABLE_SUGGESTION, new String[] {FIELD_ID, FIELD_SUGGESTION}, 
            FIELD_SUGGESTION+" LIKE '"+ text +"%'", null, null, null, null);
  }


    private class Helper extends SQLiteOpenHelper
    {

    public Helper(Context context, String name, CursorFactory factory,
            int version) {
        super(context, name, factory, version);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL("CREATE TABLE "+TABLE_SUGGESTION+" ("+
                    FIELD_ID+" integer primary key autoincrement, "+FIELD_SUGGESTION+" text);");
        Log.d("SUGGESTION", "DB CREATED");
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

  }

}

I hope this answer is useful to other programmers alot. :)

My need was even more simplistic - I didn't require a database, as I had the suggestions I many want to show contained in an ArrayList.

Here's an example implementation:

import java.util.ArrayList;

import android.app.Activity;
import android.app.SearchManager;
import android.content.Context;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.CursorAdapter;
import android.widget.SearchView;
import android.widget.SearchView.OnQueryTextListener;
import android.widget.TextView;
import android.widget.Toast;

public class ActivityTest extends Activity implements OnQueryTextListener {

    private static final String COLUMN_ID = "_id";
    private static final String COLUMN_TERM = "term";
    private static final String DEFAULT = "default";

    private SearchManager searchManager;
    private SearchView searchView;
    private MenuItem searchMenuItem;
    private SuggestAdapter suggestionsAdapter;
    private final ArrayList<String> suggestionsArray = new ArrayList<String>();
    private final ArrayList<String> dummyArray = new ArrayList<String>();

    @Override
    protected void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Create some dummy entries
        dummyArray.add("apples");
        dummyArray.add("oranges");
        dummyArray.add("bananas");
        dummyArray.add("pears");
        dummyArray.add("plums");

    }

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

        searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
        searchMenuItem = menu.findItem(R.id.action_search);

        searchView = (SearchView) searchMenuItem.getActionView();
        searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName()));
        searchView.setOnQueryTextListener(this);

        final MatrixCursor matrixCursor = getCursor(suggestionsArray);
        suggestionsAdapter = new SuggestAdapter(this, matrixCursor, suggestionsArray);
        searchView.setSuggestionsAdapter(suggestionsAdapter);
        suggestionsAdapter.notifyDataSetChanged();

        return true;
    }

    @Override
    public boolean onQueryTextChange(final String newText) {

        suggestionsArray.clear();

        for (int i = 0; i < dummyArray.size(); i++) {

            if (dummyArray.get(i).contains(newText)) {
                suggestionsArray.add(dummyArray.get(i));
            }
        }

        final MatrixCursor matrixCursor = getCursor(suggestionsArray);
        suggestionsAdapter = new SuggestAdapter(this, matrixCursor, suggestionsArray);
        searchView.setSuggestionsAdapter(suggestionsAdapter);
        suggestionsAdapter.notifyDataSetChanged();

        return true;
    }

    @Override
    public boolean onQueryTextSubmit(final String query) {
        // TODO Auto-generated method stub
        return false;
    }

    private class SuggestAdapter extends CursorAdapter implements OnClickListener {

        private final ArrayList<String> mObjects;
        private final LayoutInflater mInflater;
        private TextView tvSearchTerm;

        public SuggestAdapter(final Context ctx, final Cursor cursor, final ArrayList<String> mObjects) {
            super(ctx, cursor, 0);

            this.mObjects = mObjects;
            this.mInflater = (LayoutInflater) ctx.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        }

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

            tvSearchTerm = (TextView) view.findViewById(R.id.tvSearchTerm);

            return view;
        }

        @Override
        public void bindView(final View view, final Context ctx, final Cursor cursor) {

            tvSearchTerm = (TextView) view.findViewById(R.id.tvSearchTerm);

            final int position = cursor.getPosition();

            if (cursorInBounds(position)) {

                final String term = mObjects.get(position);
                tvSearchTerm.setText(term);

                view.setTag(position);
                view.setOnClickListener(this);

            } else {
                // Something went wrong
            }
        }

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

        @Override
        public void onClick(final View view) {

            final int position = (Integer) view.getTag();

            if (cursorInBounds(position)) {

                final String selected = mObjects.get(position);

                Toast.makeText(getApplicationContext(), selected, Toast.LENGTH_SHORT).show();

                // Do something

            } else {
                // Something went wrong
            }
        }
    }

    private MatrixCursor getCursor(final ArrayList<String> suggestions) {

        final String[] columns = new String[] { COLUMN_ID, COLUMN_TERM };
        final Object[] object = new Object[] { 0, DEFAULT };

        final MatrixCursor matrixCursor = new MatrixCursor(columns);

        for (int i = 0; i < suggestions.size(); i++) {

            object[0] = i;
            object[1] = suggestions.get(i);

            matrixCursor.addRow(object);
        }

        return matrixCursor;
    }
}

In my actual code, I have a custom Interface which populates the ArrayList with dynamic terms fetched from a Server. You would update the dataset in this way:

@Override
public void onDataReceived(final ArrayList<String> results) {

    suggestionsArray.clear();
    suggestionsArray.addAll(results);

    final MatrixCursor matrixCursor = getCursor(suggestionsArray);
    suggestionsAdapter = new SuggestAdapter(this, matrixCursor, suggestionsArray);
    searchView.setSuggestionsAdapter(suggestionsAdapter);
    suggestionsAdapter.notifyDataSetChanged();
} 

I found that not initialising an empty cursor or re-creating it every time caused issues.

Hope it helps.

user2795263

In the MainActivity class from above in onCreate method use this code first

AutoCompleteTextView search_text = (AutoCompleteTextView) searchView.findViewById(searchView.getContext().getResources().getIdentifier("android:id/search_src_text", null, null));
search_text.setThreshold(1);

This setThreshold(1) means it can search the text from one character as well now.

Devguy

There is one issue i notice in the above approach.

When the user enters just one character (eg "H"), after getting the entries from the DB and setting the adapter to the searchView via searchView.setSuggestionsAdapter(<adapter>), the drop down list is not shown.

Only after entering a second character (eg " ", "a"), the suggestion list gets displayed. Is anybody else observing this behavior ?

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!