设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 业界资讯 技术文摘 查看内容

为你的Android应用增加本地搜索功能

2015-2-25 20:55| 发布者: joejoe0332| 查看: 1368| 评论: 0|原作者: LeoXu|来自: oschina

摘要: 介绍   搜索是各种应用程序的一个基本功能需求. 在我们的案例中,我们拥有一个餐厅的应用程序,需要它能让用户可以方便和快速的搜索菜品清单,找到他们想要的东西. 在本文中,我将会描述我在对UI进行持续的维护时 ...

介绍

  搜索是各种应用程序的一个基本功能需求. 在我们的案例中,我们拥有一个餐厅的应用程序,需要它能让用户可以方便和快速的搜索菜品清单,找到他们想要的东西. 在本文中,我将会描述我在对UI进行持续的维护时先我们现有的餐厅应用程序加入本地搜索功能过程. 我会详细讲述我选择的UI方案及其理由, 此外还涉及到像activity中加入一个GestureOverlayView.

图 1: 餐厅应用程序的搜索视图截图


搜索

  对于搜索这个功能,在我们开始编写代码之前还需要做诸多设计上面的考虑. 你想要搜索的都是些什么东西? 我们想要为用户对标题和描述一并进行搜索,以获得做大的结果集,而这是因为标题并不总是会告知这份菜品实际是什么样子的. 此外你还可以增加一些针对每个菜品的隐藏元数据的搜索. 而对于搜索activity的布局,你像让搜索结果集如何显示出来呢? 一开始我是用一个列表视图来展示结果集,就像购物车视图的activity. 然后,这样的话菜品看上去就并不会吸引人来在上面点击,因为图片很小,而当我让图片变得更大的时候,页面上能够一屏展示的结果集就少了. 因此我决定把菜品清单的主题部分显示方案锁定在了网格视图上, 但不去在一侧显示一个更大的详细视图,而是会让网格视图占据屏幕的整个显示空间,使其能很容易的不同于一般的菜品清单. 现在为了查询单项的详细视图,用户要用手机单击一项,然后它就会以一个浮动在界面之上的对话式的fragment显示出来 (见图 2). 那样用户就可以快速的在其它地方单击以关闭这一项,然后在另外一项上面单击查看另外的这一项. 搜索运行起来需要快速而且流畅,对于用户而言,他们想要尽可能快的找到他们想要的信息,否则他们可能不会找到某一项,而沮丧的离开. 最后的问题是我们准备如何处理用户隐私? 我们可以设计一个能基于近期搜索行为提供搜索建议的搜索功能,或者是一个需要输入用户私人信息的搜索功能. 这可能会带来有关其他人可能会看到你要搜索的是什么,以及你的私人信息会流向哪里,这样的担忧. 不过在我们的案例中,因为只是是一个餐厅的应用程序,所以你大可不必因为人们会知道你喜欢吃什么派而担忧,而又写应用程序是需要你慎重考虑一下用户隐私 的. 对于我们的应用程序,不需要任何私人的信息, 也不会将任何的搜索项记录日志,而且没有搜索项的历史记录.


  在我们的餐厅应用程序中实现这个功能,第一步要做的就是到我们的数据库类中添加一个方法,以构建一张存储待展示搜索结果的新表. 你可以从这儿了解到更多有关餐厅的数据库设置: 在你的Android应用中使用数据库. 使用一条 SQLite 查询,我们就能够只用几行代码轻松的查询数据库找到我们需要的数据项. 这里我们搜索的是任何包含有搜索项或者其后跟着有另外的文本的名称或者描述的内容. 我们也会返回所有的列,因为我们将会在详细视图中展示这些信息. 注意如果你的数据库很大,查询的时候就可能会有延迟,而你就会想要在查询过程中向用户显示一个进度条或者转动圆环这样的东西.


1
2
3
4
5
6
7
8
9
10
11
12
/**
* Builds a table of items matching the searchTerm in their name or description
*/
    public Cursor searchMenuItems(String searchTerm) {
        SQLiteDatabase db = getReadableDatabase();
        SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
        qb.setTables(TABLES.MENU);
        Cursor c = qb.query(db, null"("+MenuColumns.NAME+" LIKE '%"+searchTerm+"%') " +
            "OR ("+MenuColumns.DESCRIPTION+" LIKE '%" + searchTerm+"%')",
            nullnullnullnull);
        return c;
    }

代码示例 1: 查询数据库的方法


  接下来,我们需要在主activity的操作栏中设置上搜索选项. 更多有关设置操作栏的信息可以阅读这篇文章: 为 Android 设备构建动态UI. 搜索功能的处理将完全在我们的应用程序里面; 我们并不想要在搜索一开始的时候列出已经安装在设备上的应用程序清单,或者发送一个intent来让另外一个搜索应用程序来进行处理.


  向 MainActivity 类添加如下这个字符串变量. 我们会使用这个变量来向搜索的intent发送所要查询的字符串:

1
2
/* Search string label */
    public final static String SEARCH_MESSAGE= "com.example.restaurant.MESSAGE";


代码示例 2: 向搜索intent中添加扩展数据的类变量

  更新 MainActivity 的 onCreateOptionsMenu 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
     * Initialize the action menu on action bar
     */
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.action_bar, menu);
 
        //set up the search
        MenuItem searchItem = menu.findItem(R.id.action_search);
        SearchView mSearchView = (SearchView) searchItem.getActionView();
        searchItem.setShowAsActionFlags(MenuItem.SHOW_AS_ACTION_IF_ROOM
                | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
        //set up the query listener
        mSearchView.setOnQueryTextListener(new OnQueryTextListener() {
            @Override
            public boolean onQueryTextSubmit(String query) {
              //start the search intent
              Intent searchIntent = new Intent(MainActivity.this, SearchResultsActivity.class);
              searchIntent.putExtra(SEARCH_MESSAGE, query);
                startActivity(searchIntent);
                return false;
            }
            @Override
            public boolean onQueryTextChange(String query) {
                //do nothing in our case
                return true;
            }
        });
         
        return super.onCreateOptionsMenu(menu);
    }


代码示例 3: 操作栏初始化代码

  以及 SearchResultsActivity 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
public class SearchResultsActivity extends Activity{ 
 
  TextView mQueryText;
  GridView searchListResults;
  SearchAdapter adapter;
  Vector<com.example.restaurant.MenuFactory.MenuItem> searchList;
 
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
 
    setContentView(R.layout.search_query_grid_results);
    mQueryText = (TextView) findViewById(R.id.txt_query);
 
    //setup the grid view
    searchListResults = (GridView)findViewById(R.id.search_results);
    searchList= new Vector<com.example.restaurant.MenuFactory.MenuItem>();
    //get and process search query here
    final Intent queryIntent = getIntent();
    doSearchQuery(queryIntent);
    adapter= new SearchAdapter(this,searchList);
    searchListResults.setAdapter(adapter);
 
    //Listener for grid view
    searchListResults.setOnItemClickListener(new AdapterView.OnItemClickListener() {
      @Override
      public void onItemClick(AdapterView<?> parent, View v, int position, long id){
        FragmentTransaction ft = getFragmentManager().beginTransaction();
        Fragment prev = getFragmentManager().findFragmentByTag("dialog");
        if (prev != null) {
          ft.remove(prev);
        }
        ft.addToBackStack(null);
        DialogFragment newFragment = SearchResultsDialogFragment.newInstance(searchList.elementAt(position));
        newFragment.show(ft, "dialog");
 
      }
    });
  }


代码示例 4: 主要的搜索结果类 (下面还会有)

  当我们构建这个列表是,我们也将会处理没有查询到任何匹配项的情况下应该怎么做. 如果没有匹配,我们会查搜索的人显示一个消息对话框,让他们知晓,并且关闭搜索的activity,他们就不会看到一个空白的界面了.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
/**
   * Builds the found item list.
   */
  private void doSearchQuery(final Intent queryIntent) {
    //Get the query text
    String message= queryIntent.getStringExtra(MainActivity.SEARCH_MESSAGE);
    //Set the UI field
    mQueryText.setText(message);
 
    RestaurantDatabase dB= new RestaurantDatabase(this);
    MenuFactory mMF= MenuFactory.getInstance();
    Cursor c= dB.searchMenuItems(message);
    Set<String> categories = new HashSet<String>();
    while (c.moveToNext()) {
      String category = c.getString(c.getColumnIndexOrThrow(RestaurantDatabase.MenuColumns.CATEGORY));
      categories.add(category);
 
      //build a new menu item and add it to the list
      MenuItem item= mMF.new MenuItem();
      item.setCategory(category);
      item.setName(c.getString(c.getColumnIndexOrThrow(RestaurantDatabase.MenuColumns.NAME)));
      item.setDescription(c.getString(c.getColumnIndexOrThrow(RestaurantDatabase.MenuColumns.DESCRIPTION)));
      item.setNutrition(c.getString(c.getColumnIndexOrThrow(RestaurantDatabase.MenuColumns.NUTRITION)));
      item.setPrice(c.getString(c.getColumnIndexOrThrow(RestaurantDatabase.MenuColumns.PRICE)));
      item.setImageName(c.getString(c.getColumnIndexOrThrow(RestaurantDatabase.MenuColumns.IMAGENAME)));
      searchList.add(item);
    }
    c.close();
 
    //Handle the case of not finding anything
    if(searchList.size()==0){
      Intent intent = new Intent(SearchResultsActivity.this, OrderViewDialogue.class);
      intent.putExtra(OrderViewActivity.DIALOGUE_MESSAGE, "Sorry, no matching items found.");
      startActivity(intent);
      SearchResultsActivity.this.finish();
    }
  }


代码示例 4 续

  类的这一个部分是网格视图的适配器, 这里我们能够只做相对很小的修改实现对来自主菜单代码本身的重用. 我们也能够适配布局文件,因此保持UI在视觉上的一致性具有无需重头开始,只要轻松的对代码进行回收利用这中好处. 你之前可能已经意识到了,我也重用了 OrderViewDialogue, 这事我志气啊为购物车写的一个类,但是在这里也能起作用.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
   * SearchAdapter to handle the grid view of found items. Each grid item contains 
   * a view_grid_item which includes a image, name, and price. 
   */
  class SearchAdapter extends BaseAdapter {
    private Vector<com.example.restaurant.MenuFactory.MenuItem> mFoundList;
    private LayoutInflater inflater;
 
    public SearchAdapter(Context c, Vector<com.example.restaurant.MenuFactory.MenuItem> list) {
      mFoundList= list;
      inflater = LayoutInflater.from(c);
    }
 
    public int getCount() {
      return mFoundList.size();
    }
 
    public Object getItem(int position) {
      return mFoundList.get(position);
    }
 
    public long getItemId(int position) {
      return 0;
    }
 
    // create a new ItemView for each item referenced by the Adapter
    public View getView(int position, View convertView, ViewGroup parent) {
 
      View v = convertView;
      ImageView picture;
      TextView name;        
      TextView price;
 
      if(v == null) {
        v = inflater.inflate(R.layout.view_grid_item, parent, false);
        v.setTag(R.id.picture, v.findViewById(R.id.picture));
        v.setTag(R.id.grid_name, v.findViewById(R.id.grid_name));
        v.setTag(R.id.grid_price, v.findViewById(R.id.grid_price));
      }
      picture= (ImageView) v.getTag(R.id.picture);
      name= (TextView) v.getTag(R.id.grid_name);
      price= (TextView) v.getTag(R.id.grid_price);
 
      final MenuItem foundItem = (MenuItem) mFoundList.get(position);
 
      InputStream inputStream = null;
      AssetManager assetManager = null
      try {
        assetManager = getAssets();
        inputStream =  assetManager.open(foundItem.imageName);
        picture.setImageBitmap(BitmapFactory.decodeStream(inputStream));
      catch (Exception e) {
        Log.d("ActionBarLog", e.getMessage());
      finally {
      }
      name.setText(foundItem.name);
      price.setText(foundItem.price);
 
      return v;
    }
  }
}



酷毙

雷人

鲜花

鸡蛋

漂亮
  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部