【问题标题】:Android: Providing recent search suggestions without searchable activity?Android:在没有可搜索活动的情况下提供最近的搜索建议?
【发布时间】:2012-11-23 21:10:54
【问题描述】:

我有一个 ActionBar SearchView,我可以成功地使用它进行搜索。 android 文档没有解释如何实现搜索建议。我不想有可搜索的活动。

这是我的搜索代码:

public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_add_song, menu);
        final SearchView searchView = (SearchView) menu.findItem(R.id.song_search).getActionView();
        searchView.setFocusable(true);
        searchView.setIconified(false);
        final AddSongActivity activity = this;
        searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
            @Override
            public boolean onQueryTextChange(String newText) {
                // Do nothing
                return true;
            }

            @Override
            public boolean onQueryTextSubmit(String query) {
                // Clear SearchView
                searchView.clearFocus();
                // Begin Spotify Search
                TextView notice = (TextView)findViewById(R.id.search_notice);
                URL url;
                try {
                    url = new URL("http://ws.spotify.com/search/1/track.json?q=" + URLEncoder.encode(query,"UTF-8"));
                } catch (MalformedURLException e) {
                    notice.setText("Malformed Search");
                    notice.setHeight(noticeHeight);
                    return true;
                } catch (UnsupportedEncodingException e) {
                    notice.setText("Unsupported Encoding. Maybe a problem with your device.");
                    notice.setHeight(noticeHeight);
                    return true;
                }
                new SearchDownload(url, activity).execute();
                notice.setText("Loading Tracks");
                notice.setHeight(noticeHeight);
                Log.i("infodb","" + noticeHeight);
                return true;
            }
        });

这适用于搜索,但我不知道要实现最近的搜索查询建议。我该怎么做?

谢谢。

【问题讨论】:

  • 不确定你是否在谷歌搜索中错过了这个,但搜索文档非常好:developer.android.com/guide/topics/search/…
  • 如果我错了,请原谅我。但是你不是每次进入 onQueryTextChange 时都会泄漏一个游标吗?

标签: java android searchview


【解决方案1】:

我的需求更加简单 - 我不需要数据库,因为我有很多我想在 ArrayList 中显示的建议。

这是一个示例实现:

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

在我的实际代码中,我有一个自定义接口,它使用从服务器获取的动态术语填充 ArrayList。您可以通过这种方式更新数据集:

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

我发现不初始化空游标或每次都重新创建它会导致问题。

希望对你有帮助。

【讨论】:

  • 抱歉,@brandall 只是想知道 R.layout.list_item_search 的 XML 是什么样的?
  • omg 在找到您的代码之前,我一直在寻找 DAYS 的有效解决方案。它就像一个魅力!非常感谢。
【解决方案2】:

onCreate method 上面的MainActivity 类中,首先使用此代码

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

setThreshold(1) 表示它现在也可以从一个字符开始搜索文本。

【讨论】:

    【解决方案3】:

    好的,我花了我的时间。我从SQLiteDatabase 提出自己的简单建议实施。

    我们将创建 3 个类,如下所示

    1. MainActivity - 用于测试来自数据库的 SearchView 建议
    2. SuggestionDatabase - 这将存储您最近的搜索关键字。
    3. SuggestionSimpleCursorAdapter - 这是SimpleCursorAdapter 的子类。我会解释为什么我做这个类而不是使用SimpleCursorAdapter

    代码

    // 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;
            }
        }
    
    }
    

    它是如何工作的

    1. 当用户点击搜索按钮时,onQueryTextSubmit() 将被触发,然后搜索关键字将保存在我们的数据库中。假设我们提交了一个关键字“Hello”
    2. 如果用户在SearchView 中写入例如“Hel”或“H”的字符串,则将调用onQueryTextChange(),然后我们在SQLiteDatabase (SuggestionDatabase) 中搜索此关键字。如果 "Hel" 或 "H" 与 "Hello" 匹配,则通过将返回的 Cursor 设置为 SuggestionSimpleCursorAdapter 来显示查询结果,然后将此适配器设置为 SearchView。这是图片。


    3. 当然我们会点击“你好”的建议,onSuggestionClick(int position) 会被调用。我们从SearchView的适配器(SuggestionSimpleCursorAdapter)中获取SQLiteCursor对象并从中获取建议文本,将建议文本设置在SearchView对象中

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

    如果我们使用SimpleCursorAdapter,它也可以正常工作,但假设我们有这种情况

    1. 如果我们在智能手机上运行此程序并输入关键字“Hel”,建议将正确显示。

    1. 如果我们横向旋转屏幕会怎样?它将切换到全屏模式,您仍然可以输入关键字。

    建议会发生什么?一起来看看吧。

    看到奇怪的建议了吗?我们如何解决这个问题?通过覆盖返回CharSequenceconvertToString(Cursor cursor)

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

    通过覆盖 convertToString(Cursor cursor) ,结果如下

    这是数据库

    // 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) {
    
        }
    
      }
    
    }
    

    我希望这个答案对其他程序员有用。 :)

    【讨论】:

    • 感谢您的回答。我了解如何操作数据库,但如何将结果显示为搜索建议?
    • 这似乎是一个非常出色的答案!我现在会尝试实现它!
    • 好的,太棒了!它还不完美(风格问题,建议排序),但它提供了建议!这正是我想要的。我所做的主要更改是使数据库类成为 SQLiteOpenHelper 的子类。赏金和接受的答案很高兴你。 :-)
    • FIELD_SUGGESTION + " TEXT UNIQUE ON CONFLICT REPLACE 可以修复重复的建议,我可以按 id 降序排列结果以获取最新查询。
    • 非常感谢!
    【解决方案4】:

    在上述方法中我注意到一个问题。

    当用户只输入一个字符(例如"H")时,在从数据库中获取条目并通过searchView.setSuggestionsAdapter(&lt;adapter&gt;)将适配器设置为searchView后,下拉列表不显示。

    只有在输入第二个字符(例如" ", "a")后,才会显示建议列表。 有其他人在观察这种行为吗?

    【讨论】:

    • 是的,我得到了同样的行为。仍在努力找出问题所在。
    • 你找出问题所在了吗?
    • AutoCompleteTextView.setThreshold(0) 将解决您的问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-10-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-18
    相关资源
    最近更新 更多