四、创建Searchable Activity
searchable activity根据搜索关键字进行搜索,并显示搜索结果。
当我们在search dialog or widget执行搜索的时候,系统就启动你的searchable activity ,并把搜索关键字用一个aciton为ACTION_SEARCH的Intent传给你的searchable activity. 你的searchable activity在Intent中通过extra的QUERY来提取搜索关键字,执行搜索并显示搜索结果.
我们需要在AndroidManifest.xml文件中声明Searchable Activity,以便在search dialog or widget执行搜索的时候,系统启动该searchable activity并把搜索关键字传给它。
4.1、声明searchable activity
如何在AndroidManifest.xml文件中声明Searchable Activity
1. 在activity的<intent-filter> 中添加一个可以接受action为ACTION_SEARCH的intent。
2. 在<meta-data>中指明search dialog or widget的配置文件(即searchable.xml)
比如,示例2:
<application ... >
<activity android:name=".SearchableActivity" >
<intent-filter>
<action android:name="android.intent.action.SEARCH" />
</intent-filter>
<meta-data android:name="android.app.searchable"
android:resource="@xml/searchable"/>
</activity>
...
</application>
<meta-data>必须包括android:name这个属性,而且其值必须为"android.app.searchable",
还必须包括android:resource这个属性,它指定了我们的search dialog的配置文件。(示例2中指定是the res/xml/searchable.xml).
注意: <intent-filter> 没有必要加入<category android:name="android.intent.category.DEFAULT" /> ,
因为系统是通过component name把ACTION_SEARCH intent explicitly显示的传递给searchable activity的。
4.2、执行搜索
当你在manifest文件中,声明好了searchable activity,在你的searchable activity,就可以参照下面的3步执行搜索了。
(1),提取搜索关键字
(2),在你的数据中进行搜索
(3),显示搜索结果
一般来说,你的搜索结果需要要在ListView进行显示,所有你的searchable activity需要继承ListActivity。
它包括了一个拥有单一ListView的layout,它为我们使用ListView提供了方便。
4.2.1、提取搜索关键字
当用户在search dialog or widget界面开始搜索的时候,系统将查找一个合适的searchable activity,并给它传送一个ACTION_SEARCH的intent,该intent把搜索关键字保存在extra的QUERY中。当activity的时候,我们必须检查保存在extra的QUERY中的搜索关键字,并提取它。
比如,示例3:
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.search);
// Get the intent, verify the action and get the query
Intent intent = getIntent();
if (Intent.ACTION_SEARCH.equals(intent.getAction())) {
String query = intent.getStringExtra(SearchManager.QUERY);
doMySearch(query);
}
}
在Action为ACTION_SEARCH的intent中,extra的QUERY总是被包含。在示例3提取处理了搜索关键字,然后让doMySearch()函数在执行真正的搜索的。
4.2.2、数据搜索
数据的存储和搜索都是和你的程序相关的。你可以以各种方式进行数据的存储和搜索,它才是你的程序更需要关注的。
但是还是有几点需要注意的:
(1),当你的数据存储在手机的数据库中时,执行full-text search (using FTS3, rather than a LIKE query)
能提供在文本数据中得到更健壮且速度更快的search。在 sqlite.org 中可以得到FTS3的更多信息, 在 SQLiteDatabase 可以看到Android上的SQLite更多信息 . 可以在 Searchable Dictionary看到一个用FTS3实现搜索的示例。
(2),如果的数据来自互联网,那么你的搜索将受制于用户的数据连接情况。这时我们就需要显示一个进度条,直到从网路返回搜索结果。
. 在 android.net 可一看到更多的network APIs ,在 Creating a Progress Dialog 可以到看到如何显示一个进度条。
如果想让的数据和搜索变得透明,那么我建议你用一个Adapter返回搜索结果。这种方式,你更容易在ListView中显示搜索结果。如果你的数据在自于数据库,那么通过CursorAdapter向ListView提供搜索结果,如果你的数据来自于其他方式,可以扩展一个BaseAdapter来使用。
4.2.3、结果显示
正如上面讨论的一样,显示搜索结果建议使用的UI是ListView,所以你的searchable activity最好继承于ListActivity,然后是使用setListAdapter()来设置和搜索结果绑定了的Adapter。这样你的搜索结果就可以显示在ListView中了。
你可以参考 Searchable Dictionary 示例,它展示了怎么进行数据库查询,怎么使用Adapter向ListView提供搜索结果。
ListView :在Android应用开发过程中属于最常用的系统组件之一,当然可能童鞋们问为什么会突然游戏开发中讲这个,呵呵,其实在游戏开发中,也会常常使用到系统组件,比如游戏排行榜,简单的游戏关卡选择等等,都可以来使用ListView来实现;
当然关于ListView我想大家都会使用了,那么这篇文章也不是跟大家讲解ListView是如果使用的,而是如何实现自定义一个【通用】适配器类;
在ListView三种适配器当中,最受大家青睐的肯定就是SimpleAdapter适配器,用过的童鞋们都很清楚,它的扩展性很强,可以将ListView中每一项都使用自定义布局,插入N多组件;但是SimpleAdapter也有弱点,那就是当ListView中每一项有Button、CheckBox等这些有事件的组件,我们想监听它们就必须自定义适配器!那么今天的重点也就是来讲解一下如何写一个自定义通用适配器类!
SimpleAdapter 构造的时候,我们知道需要五个参数来进行映射数据到ListView中,那么我们今天的自定义通用适配器其实也就是实现系统SimpleAdapter的一个自定义版;
OK,可能我说这么多,大家还是不太懂,其实今天要讲述的自定义通用适配器优点有三点:
1.使用通用适配器就不需要每次使用自定义适配器的时候,都要去重新去写一个,太累。。。。
2.构造方法与SimpleAdapter构造方法相同,五个参数也一摸一样!
3.只需要在自定义的适配器类中,将我们需要监听的组件进行设置监听即可!别的代码不需要去改动!
例如我们需要完成下图这种ListView:
首先我们来完成ListView中每项的布局:
main.xml:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="horizontal" android:layout_width="fill_parent" android:layout_height="fill_parent" > <ImageView android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/iv" /> <LinearLayout android:orientation="vertical" android:layout_width="wrap_content" android:layout_height="wrap_content" > <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="20sp" android:id="@+id/bigtv" /> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:textSize="10sp" android:id="@+id/smalltv" /> </LinearLayout> <Button android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="button" android:id="@+id/btn" /> <CheckBox android:layout_width="wrap_content" android:layout_height="wrap_content" android:id="@+id/cb" /> </LinearLayout>
修改源码:MainActivity.java:
public class MainActivity extends Activity { private SimpleAdapter adapter;// 声明适配器对象 private ListView listView; // 声明列表视图对象 private List<Map<String, Object>> list;// 声明列表容器 public static MainActivity ma; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ma = this; // 实例化列表容器 list = new ArrayList<Map<String, Object>>(); listView = new ListView(this);// 实例化列表视图 // 实例一个列表数据容器 Map<String, Object> map = new HashMap<String, Object>(); // 往列表容器中添加数据 map.put("item1_imageivew", R.drawable.icon); map.put("item1_bigtv", "BIGTV"); map.put("item1_smalltv", "SMALLTV"); // 将列表数据添加到列表容器中 list.add(map); // --使用系统适配器,无法实现组件监听; // //实例适配器 adapter = new SimpleAdapter(this, list, R.layout.main, new String[] { "item1_imageivew", "item1_bigtv", "item1_smalltv" }, new int[] { R.id.iv, R.id.bigtv, R.id.smalltv }); listView.setAdapter(adapter); // //显示列表视图 this.setContentView(listView); } }
到此,我们之前要求完成的(图1)要求的ListView,[对ListView不太熟悉的童鞋自行百度google先学习一下基础吧]
当然这里我们只是完成了界面,如果想监听(图1)中的按钮和复选框事件,那么我们肯定需要自定义一个适配器,那么下面开始介绍如何实现通用适配器:
创建一个新类,类名:“MySimpleAdapter.java”继承BaseAdapter:
/** * */ package com.himi; import java.util.List; import java.util.Map; import android.app.AlertDialog; import android.content.Context; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.ImageView; import android.widget.TextView; import android.widget.CompoundButton.OnCheckedChangeListener; /** * @author Himi * */ public class MySimpleAdapter extends BaseAdapter { private LayoutInflater mInflater; private List<Map<String, Object>> list; private int layoutID; private String flag[]; private int ItemIDs[]; public MySimpleAdapter(Context context, List<Map<String, Object>> list, int layoutID, String flag[], int ItemIDs[]) { this.mInflater = LayoutInflater.from(context); this.list = list; this.layoutID = layoutID; this.flag = flag; this.ItemIDs = ItemIDs; } @Override public int getCount() { // TODO Auto-generated method stub return list.size(); } @Override public Object getItem(int arg0) { // TODO Auto-generated method stub return 0; } @Override public long getItemId(int arg0) { // TODO Auto-generated method stub return 0; } @Override public View getView(int position, View convertView, ViewGroup parent) { convertView = mInflater.inflate(layoutID, null); for (int i = 0; i < flag.length; i++) {//备注1 if (convertView.findViewById(ItemIDs[i]) instanceof ImageView) { ImageView iv = (ImageView) convertView.findViewById(ItemIDs[i]); iv.setBackgroundResource((Integer) list.get(position).get( flag[i])); } else if (convertView.findViewById(ItemIDs[i]) instanceof TextView) { TextView tv = (TextView) convertView.findViewById(ItemIDs[i]); tv.setText((String) list.get(position).get(flag[i])); }else{ //...备注2 } } addListener(convertView); return convertView; } /** * 童鞋们只需要将需要设置监听事件的组件写在下面这方法里就可以啦! * 别的不需要修改! * 备注3 */ public void addListener(View convertView) { ((Button)convertView.findViewById(R.id.btn)).setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { new AlertDialog.Builder(MainActivity.ma) .setTitle("自定义通用SimpleAdapter") .setMessage("按钮成功触发监听事件!") .show(); } }); ((CheckBox)convertView.findViewById(R.id.cb)). setOnCheckedChangeListener(new OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { new AlertDialog.Builder(MainActivity.ma) .setTitle("自定义通用SimpleAdapter") .setMessage("CheckBox成功触发状态改变监听事件!") .show(); } }); } }
备注1:这个For循环中是对ListView中每一项中包含所有的组件进行判定每个组件的类型,从而去设置其数据!
其中 《instanceof》这个关键字可能有的童鞋不太熟习,这个是对Object 类型的判断;
这里我只是对ImageView、TextView的类型进行的数据识别,为什么我这里只写了这两种,那是因为Button、CheckBox等这些带事件响应的组件是无法通过适配器映射到ListView上的;
其实关于适配器映射的机制,这里简单说下:例如一个TextView组件,那么在ListView的每一项(List)中put()添加的时候,put()方法中第一个参数key大家知道是用于与适配器进行对应映射数据用的值,那么第二个参数其实就是put进组件的数据;其实当其数据反射在ListViw时,其实内部就是对组件进行实例化,并且对组件设置数据;
备注2 :我这里最后还有一个else{...}这里是留给童鞋们去扩展的,因为可能还有一些其他能映射的组件,所以这里留下接口,供大家扩展;
备注3:addListener(View convertView)这是我留出来的方法,童鞋们只需要将需要设置监听事件的组件写在这方法里就可以啦!
那么看一下我们使用通用监听器的效果吧:
OK,很正常!那么在来看看使用系统的SimpleAdapter 与我们自定义的MySimpleAdapter代码对比图:
怎么样!构造参数完全一样,而且我们这个比它强大,我们只要去设置下需要监听的组件监听代码就OK了。
娃哈哈,好啦,今天就到这里吧,希望此通用适配器对大家有用!
补充:大家使用自定义适配器的时候,有时候ListView每一项的焦点没有了,比如本文中是因为Button和CheckBox截获了焦点,童鞋们只要将button和checkBox的焦点设置不可见就OK啦。~
xml中focusable是这个属性; android:focusable="false"
这里也提醒一下开发游戏的童鞋们,很多游戏开发者认为开发游戏不用去学习系统组件的使用,不用去沾染xml、布局啥的,其实这么想的童鞋们你们就大错特错了,Android之所以能这么火,其组件的美观占了很重的份量,这么美的组件不用岂不是很浪费!!希望童鞋们对组件不熟悉的游戏开发者都要去学习学习下组件的使用!
本文摘自:http://blog.csdn.net/xiaominghimi/article/details/6314704
MainActivity
package org.wp.activity; import android.app.Activity; import android.os.Bundle; import android.view.Window; import android.view.WindowManager; public class MainActivity extends Activity { public static Activity INSTANCE; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); INSTANCE = this; getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN); // 全屏 requestWindowFeature(Window.FEATURE_NO_TITLE); setContentView(new MySurfaceView(this)); } @Override protected void onDestroy() { super.onDestroy(); android.os.Process.killProcess(android.os.Process.myPid()); } }
MySurfaceView
package org.wp.activity; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceHolder; import android.view.SurfaceView; public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable { private static final String TAG = "MySurfaceView"; private SurfaceHolder sfh; private Resources res; private Bitmap bmp_login; private Bitmap bmp_background; private Paint paint; private Canvas canvas; private int ScreenW, ScreenH; private int bmp_login_x, bmp_login_y; private boolean flag; private boolean flag_zh; public static String str_username = "onewayonelife"; public static String str_password = "onewayonelifeIteyeCom"; public MySurfaceView(Context context) { super(context); this.setKeepScreenOn(true); this.setFocusable(true); sfh = this.getHolder(); sfh.addCallback(this); res = this.getResources(); bmp_login = BitmapFactory.decodeResource(res, R.drawable.register); // 登录图 bmp_background = BitmapFactory.decodeResource(res, R.drawable.one_piece); // 背景图片 paint = new Paint(); paint.setAntiAlias(true); } private void draw() { canvas = sfh.lockCanvas(); if (canvas != null) { // 当点击home键 或者返回按键的时候canvas是得不到的,这里要注意 canvas.drawBitmap(bmp_background, -(bmp_background.getWidth() - ScreenW), -(bmp_background.getHeight() - ScreenH), paint); // 背景图片 canvas.drawBitmap(bmp_login, bmp_login_x, bmp_login_y, paint); // 登录图片 paint.setColor(Color.RED); canvas.drawText(str_username, bmp_login_x + 20, bmp_login_y + 42 + 15, paint); // 账号文字 int passwordLen = str_password.length(); String passwordTemp = ""; if (passwordLen > 15) { passwordLen = 15; } for (int i = 0; i < passwordLen; i++) { passwordTemp += "*"; } canvas.drawText(passwordTemp, bmp_login_x + 20, bmp_login_y + 79 + 15, paint); // 密码文字 if (flag_zh) { paint.setColor(Color.YELLOW); canvas.drawCircle(bmp_login_x + 23, bmp_login_y + 113, 4, paint); // 记住账号圆形 } sfh.unlockCanvasAndPost(canvas); } } @Override public boolean onTouchEvent(MotionEvent event) { float pointx = event.getX(); float pointy = event.getY(); // 点击账号输入框 if (pointx > bmp_login_x + 14 && pointx < bmp_login_x + 14 + 117) { // 横向点击区域在账号输入框范围 if (pointy > bmp_login_y + 42 && pointy < bmp_login_y + 42 + 15) { // 纵向点击区域在账号输入框范围 Intent intent = new Intent(); intent.putExtra("count", 1); intent.putExtra("onewayonelife", str_username); intent.setClass(MainActivity.INSTANCE, Register.class); MainActivity.INSTANCE.startActivity(intent); } } // 点击密码输入框 if (pointx > bmp_login_x + 14 && pointx < bmp_login_x + 14 + 117) { // 横向点击区域在密码输入框范围 if (pointy > bmp_login_y + 79 && pointy < bmp_login_y + 79 + 15) { // 纵向点击区域在密码输入框范围 Intent intent = new Intent(); intent.putExtra("count", 2); intent.putExtra("onewayonelife", str_password); intent.setClass(MainActivity.INSTANCE, Register.class); MainActivity.INSTANCE.startActivity(intent); } } // 点击记住账号 if (pointx > bmp_login_x + 15 && pointx < bmp_login_x + 15 + 15) { // 横向点击区域在记住账号范围 if (pointy > bmp_login_y + 104 && pointy < bmp_login_y + 104 + 15) { // 纵向点击区域在记住账号范围 flag_zh = !flag_zh; } } // 点击登录 if (pointx > bmp_login_x + 42 && pointx < bmp_login_x + 42 + 60) { // 横向点击区域在登录按键范围 if (pointy > bmp_login_y + 123 && pointy < bmp_login_y + 123 + 24) { // 纵向点击区域在登录按键范围 Intent intent = new Intent(); intent.putExtra("count", 3); intent.putExtra("onewayonelife_username", str_username); intent.putExtra("onewayonelife_password", str_password); intent.setClass(MainActivity.INSTANCE, Register.class); MainActivity.INSTANCE.startActivity(intent); } } // 点击退出按钮 if (pointx > bmp_login_x + 120 && pointx < bmp_login_x + 120 + 22) { if (pointy > bmp_login_y + 5 && pointy < bmp_login_y + 5 + 22) { try { android.os.Process.killProcess(android.os.Process.myPid()); } catch (Exception e) { e.printStackTrace(); } } } return super.onTouchEvent(event); } @Override public void run() { while (!flag) { draw(); try { Thread.sleep(100); } catch (Exception e) { Log.v(TAG, "run exception"); e.printStackTrace(); } } } @Override public void surfaceCreated(SurfaceHolder holder) { ScreenW = this.getWidth(); ScreenH = this.getHeight(); bmp_login_x = ScreenW / 2 - bmp_login.getWidth() / 2; // 登录图x坐标位置 bmp_login_y = ScreenH / 2 - bmp_login.getHeight() / 2; // 登录图y坐标位置 new Thread(this).start(); } @Override public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { } @Override public void surfaceDestroyed(SurfaceHolder holder) { } }
Register
package org.wp.activity; import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.LinearLayout; import android.widget.TextView; public class Register extends Activity { private Register rs; private byte count; private LinearLayout llay; private Button ok; private EditText et; private TextView tv; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); rs = this; Intent intent = this.getIntent(); count = (byte) intent.getIntExtra("count", 0); llay = new LinearLayout(this); ok = new Button(this); ok.setWidth(100); ok.setText("确定"); ok.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (count == 1) { MySurfaceView.str_username = et.getText().toString(); } else if (count == 2) { MySurfaceView.str_password = et.getText().toString(); } rs.finish(); } }); String temp_str = ""; String temp_username = ""; String temp_password = ""; et = new EditText(this); tv = new TextView(this); if (count != 3) { temp_str = intent.getStringExtra("onewayonelife"); if (count == 1) { rs.setTitle("请输入帐号!"); } else { rs.setTitle("请输入密码!"); } llay.addView(et); llay.addView(ok); if (temp_str != null) { et.setText(temp_str); } } else { temp_username = intent.getStringExtra("onewayonelife_username"); temp_password = intent.getStringExtra("onewayonelife_password"); rs.setTitle("您输入的信息:"); tv.setText("帐号:" + temp_username + "\n" + "密码:" + temp_password); llay.addView(tv); llay.addView(ok); } setContentView(llay); } }
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.wp.activity" android:versionCode="1" android:versionName="1.0"> <application android:icon="@drawable/icon" android:label="@string/app_name"> <activity android:name=".MainActivity" android:label="@string/app_name" android:screenOrientation="landscape"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <activity android:name=".Register" android:theme="@android:style/Theme.Dialog" android:screenOrientation="landscape" /> </application> <uses-sdk android:minSdkVersion="8" /> </manifest>