1. Native关键字 2011-11-11
private static native void rebootNative(String reason) throws IOException ;
类似这种有native修饰的函数,其定义都是跨语言的。
以本平台为例,若此段代码存在于\droid\frameworks\base\core\java\android\os\Power.java下。则对应的定义在\droid\frameworks\base\core\jni\android_os_Power.cpp下。
2. 使用振动 2011-11-08
//获取振动对象
mVibrator01 = ( Vibrator )getApplication().getSystemService
(Service.VIBRATOR_SERVICE);
//启动振动
mVibrator01.vibrate( new long[]{100,10,100,1000},-1);
//取消振动
mVibrator01.cancel();
vibrate函数参数解释
第一个传入的是振动周期的定义,是个数组,第一个元素表示停顿时间,第二个元素表示振动时长,以此类推。
第二个传入的是振动次数,为-1时表示只振动一次,为0时表示无限振动。
同时要设置权限,如下:
<uses-permission android:name="android.permission.VIBRATE"></uses-permission>
否则就会报SecurityException异常。
3. Canvas、path、paint的绘图使用 2011-11-09
Canvas就是一张画布。
Canvas.translate就是让默认的坐标原点设置到新的位置上。
Paint就是设置会在画布上的图案文字是什么样式、颜色等等。setColor、setStyle等等方法。
Path就是绘制图形的路径。moveTo、lineTo、close等等方法。以下代码既是绘制罗盘的片段。OnDraw是重载的,另外在view.invalidate()调用后也会调用OnDraw进行重绘。
protected void onDraw(Canvas canvas) {
Paint paint = mPaint;
canvas.drawColor(Color.WHITE);
//设置paint的图形(指针)样式
paint.setAntiAlias(true);
paint.setColor(Color.BLACK);
paint.setStyle(Paint.Style.FILL);
int w = canvas.getWidth();
int h = canvas.getHeight();
int cx = w / 2;
int cy = h / 2;
Log.d(TAG, "-----cx--"+cx+"-------cy--=="+cy);
//设置画布坐标原点
canvas.translate(cx, 150);
Paint textpaint = new Paint();
//设置画布上文字样式
textpaint.setAntiAlias(true);
textpaint.setColor(Color.BLACK);
textpaint.setTextSize(22);
textpaint.setTextScaleX(1.0f);
//画出文字
canvas.drawText(getResources().getString(R.string.compass_accuracy)
+ ": " + tmpAccuracy , -130, -80, textpaint); //add by robin
Log.d(TAG,"tmpAccuracy "+tmpAccuracy);
if (mValues != null && isOpenCompass) {
canvas.drawText(getResources().getString(R.string.compass_accuracy)
+ ": " + mValues[0] + "\u00b0", -130, -120, textpaint);
Log.d(TAG,"******mValues[0] "+mValues[0]);
canvas.rotate(-mValues[0]);
} else {
canvas.drawText(getResources().getString(R.string.compass_rotary), -130, -120, textpaint);
}
//画出指针
canvas.drawPath(mPath, mPaint);
}
4. 屏幕亮度调整的使用2011-11-09
1)设置屏幕亮度
private void setBrightness(int brightness) {
WindowManager.LayoutParams lp = getWindow().getAttributes();
float f = ((float) brightness) / ((float) 255);
lp.screenBrightness = f;
getWindow().setAttributes(lp);
}
2)获取屏幕亮度
try {
brightness = Settings.System.getInt(getContentResolver(), Settings.System.SCREEN_BRIGHTNESS);
5. Compass使用 2011-11-09
以下代码是电子罗盘。
public class CompassSingleTest extends SingleAbstractBackButtonActivity implements SensorEventListener{
private static final String TAG = "CompassSingleTest";
private SensorManager mSensorManager;
private SampleView mSampleView;
private float[] mValues = new float[3];
private boolean isOpenCompass = false;
private int count = 0;
private Vibrator mVibrator;
private boolean isVibrate = true;
private long[] pattern = {150,100};
private boolean isCalibration = false;
private int tmpAccuracy; //add by robin
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//取得sensormanager对象
mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
mVibrator = (Vibrator) getSystemService(Service.VIBRATOR_SERVICE);
mSampleView = new SampleView(this);
mParentLinearLayout.addView(mSampleView);
setTitle(R.string.compass_title);
}
@Override
protected void onPause() {
mVibrator.cancel();
//注销SensorEventListener这个listener
mSensorManager.unregisterListener(this);
super.onPause();
}
@Override
protected void onResume() {
Sensor mSensor = mSensorManager
.getDefaultSensor(Sensor.TYPE_ORIENTATION);
//注册SensorEventListener这个listener
mSensorManager.registerListener(this, mSensor,
SensorManager.SENSOR_DELAY_GAME);
super.onResume();
}
@Override
protected void doStop() {
// TODO Auto-generated method stub
}
private class SampleView extends View {
//省略
//… …
}
@Override
protected void onAttachedToWindow() {
mAnimate = true;
super.onAttachedToWindow();
}
@Override
protected void onDetachedFromWindow() {
mAnimate = false;
super.onDetachedFromWindow();
}
}
//精度改变侦听函数,需要重写
public void onAccuracyChanged(Sensor sensor, int accuracy) {
Log.d(TAG, "onAccuracyChanged accuracy : "+accuracy + "--------type : " + sensor.getType());
//精度改变的处理代码
//… …
}
//方向改变侦听函数,需要重写
public void onSensorChanged(SensorEvent event) {
mValues = event.values;
Log.d(TAG, "sensorChanged (" + mValues[0] + ", " + mValues[1] + ", "
+ mValues[2] + ")");
if (mSampleView != null) {
Log.d(TAG, "mSampleView.invalidate");
mSampleView.invalidate();
}
}
6. AudioManager使用 2011-11-11
AudioManager 类位于 android.Media 包中,该类提供访问控制音量和钤声模式的操作。
由于 AudioManager 该类方法过多,这里只讲述几个比较常用到的方法:
方法:adjustVolume(int direction, int flags)
解释:这个方法用来控制手机音量大小,当传入的第一个参数为 AudioManager.ADJUST_LOWER 时,可将音量调小一个单位,传入 AudioManager.ADJUST_RAISE 时,则可以将音量调大一个单位。
方法:getMode()
解释:返回当前音频模式。
方法:getRingerMode()
解释:返回当前的铃声模式。
方法:getStreamVolume(int streamType)
解释:取得当前手机的音量,最大值为7,最小值为0,当为0时,手机自动将模式调整为“震动模式”。
方法:setRingerMode(int ringerMode)
解释:改变铃声模式
实例代码如下:
package com.terry;
import android.media.AudioManager;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
public class viewHolder {
public static ImageButton downButton;
public static ImageButton upButton;
public static ImageButton normalButton;
public static ImageButton muteButton;
public static ImageButton vibrateButton;
public static ProgressBar myProgressBar;
public static ImageView myImageView;
public static AudioManager audiomanage;
}
package com.terry;
import android.app.Activity;
import android.media.AudioManager;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ProgressBar;
public class AudioManagerActivity extends Activity {
//音量变量
private int volume=0;
//声音模式
private int mode;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
findview();
//通过getStreamVolume 获得当前音量大小
volume=viewHolder.audiomanage.getStreamVolume(AudioManager.STREAM_RING);
//把当前音量的值 设置给进度条
viewHolder.myProgressBar.setProgress(volume);
//得到当前的声音模式
mode=viewHolder.audiomanage.getRingerMode();
setImageState();
viewHolder.downButton=btnListener(viewHolder.downButton);
viewHolder.upButton=btnListener(viewHolder.upButton);
viewHolder.muteButton=btnListener(viewHolder.muteButton);
viewHolder.normalButton=btnListener(viewHolder.normalButton);
viewHolder.vibrateButton=btnListener(viewHolder.vibrateButton);
}
//找到控件
void findview(){
viewHolder.downButton=(ImageButton)findViewById(R.id.downButton);
viewHolder.upButton=(ImageButton)findViewById(R.id.upButton);
viewHolder.muteButton=(ImageButton)findViewById(R.id.muteButton);
viewHolder.normalButton=(ImageButton)findViewById(R.id.normalButton);
viewHolder.vibrateButton=(ImageButton)findViewById(R.id.vibrateButton);
viewHolder.myImageView=(ImageView)findViewById(R.id.myImage);
viewHolder.myProgressBar=(ProgressBar)findViewById(R.id.myProgress);
viewHolder.audiomanage=(AudioManager)getSystemService(AUDIO_SERVICE);
}
//按钮 的单击事件
ImageButton btnListener(ImageButton b){
b.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
switch (v.getId()) {
case R.id.downButton:
viewHolder.audiomanage.adjustVolume(AudioManager.ADJUST_LOWER, 0);
volume=viewHolder.audiomanage.getStreamVolume(AudioManager.STREAM_RING);
viewHolder.myProgressBar.setProgress(volume);
mode=viewHolder.audiomanage.getRingerMode();
setImageState();
break;
case R.id.upButton:
viewHolder.audiomanage.adjustVolume(AudioManager.ADJUST_RAISE, 0);
volume=viewHolder.audiomanage.getStreamVolume(AudioManager.STREAM_RING);
viewHolder.myProgressBar.setProgress(volume);
mode=viewHolder.audiomanage.getRingerMode();
setImageState();
break;
case R.id.muteButton:
viewHolder.audiomanage.setRingerMode(AudioManager.RINGER_MODE_SILENT);
volume=viewHolder.audiomanage.getStreamVolume(AudioManager.STREAM_RING);
viewHolder.myProgressBar.setProgress(volume);
viewHolder.myImageView.setImageDrawable(getResources().getDrawable(R.drawable.mute));
break;
case R.id.normalButton:
viewHolder.audiomanage.setRingerMode(AudioManager.RINGER_MODE_NORMAL);
volume=viewHolder.audiomanage.getStreamVolume(AudioManager.STREAM_RING);
viewHolder.myProgressBar.setProgress(volume);
viewHolder.myImageView.setImageDrawable(getResources().getDrawable(R.drawable.normal));
break;
case R.id.vibrateButton:
viewHolder.audiomanage.setRingerMode(AudioManager.RINGER_MODE_VIBRATE);
volume=viewHolder.audiomanage.getStreamVolume(AudioManager.STREAM_RING);
viewHolder.myProgressBar.setProgress(volume);
viewHolder.myImageView.setImageDrawable(getResources().getDrawable(R.drawable.vibrate));
break;
}
}
});
return b;
}
}
7. MediaPlayer使用 2011-11-11
使用MediaPlayer播放音频或者视频的最简单例子:
1. public class MediaPlayerStudy extends Activity {
2.
3. private Button bplay,bpause,bstop;
4.
5. private MediaPlayer mp = new MediaPlayer();
6.
7.
8.
9. @Override
10.
11. public void onCreate(Bundle savedInstanceState) {
12.
13. super.onCreate(savedInstanceState);
14.
15. setContentView(R.layout.main);
16.
17.
18.
19. bplay = (Button)findViewById(R.id.play);
20.
21. bpause = (Button)findViewById(R.id.pause);
22.
23. bstop = (Button)findViewById(R.id.stop);
24.
25. bplay.setOnClickListener(new OnClickListener(){
26.
27. @Override
28.
29. public void onClick(View v) {
30.
31. try {
32.
33. mp.setDataSource("/sdcard/test.mp3");
34.
35. mp.prepare();
36.
37. mp.start();
38.
39. } catch (IllegalArgumentException e) {
40.
41. e.printStackTrace();
42.
43. } catch (IllegalStateException e) {
44.
45. e.printStackTrace();
46.
47. } catch (IOException e) {
48.
49. e.printStackTrace();
50.
51. }
52.
53. mp.setOnCompletionListener(new OnCompletionListener(){
54.
55. @Override
56.
57. public void onCompletion(MediaPlayer mp) {
58.
59. mp.release();
60.
61. }
62.
63. });
64.
65. }
66.
67. });
68.
69.
70.
71. bpause.setOnClickListener(new OnClickListener(){
72.
73. @Override
74.
75. public void onClick(View v) {
76.
77. if(mp != null){
78.
79. mp.pause();
80.
81. }
82.
83. }
84.
85. });
86.
87.
88.
89. bstop.setOnClickListener(new OnClickListener(){
90.
91. @Override
92.
93. public void onClick(View v) {
94.
95. if(mp != null){
96.
97. mp.stop();
98.
99. }
100.
101. }
102.
103. });
104.
105. }
106.
107.
108.
109. @Override
110.
111. protected void onDestroy() {
112.
113. if(mp != null)
114.
115. mp.release();
116.
117. super.onDestroy();
118.
119. }
120.
121. }
122.
123.
程序说明:
这个例子只是描述了MediaPlayer的基本使用步骤和方式,MediaPlayer还有多种使用方式和方法,并不只局限于例子所介绍的一种。具体来看:
1)如何获得MediaPlayer实例:
可以使用直接new的方式:
MediaPlayer mp = new MediaPlayer();
也可以使用create的方式,如:
MediaPlayer mp = MediaPlayer.create(this, R.raw.test);//这时就不用调用setDataSource了
2) 如何设置要播放的文件:
MediaPlayer要播放的文件主要包括3个来源:
a. 用户在应用中事先自带的resource资源
例如:MediaPlayer.create(this, R.raw.test);
b. 存储在SD卡或其他文件路径下的媒体文件
例如:mp.setDataSource("/sdcard/test.mp3");
c. 网络上的媒体文件
例如:mp.setDataSource("http://www.citynorth.cn/music/confucius.mp3");
MediaPlayer的setDataSource一共四个方法:
setDataSource (String path)
setDataSource (FileDescriptor fd)
setDataSource (Context context, Uri uri)
setDataSource (FileDescriptor fd, long offset, long length)
3)对播放器的主要控制方法:
Android通过控制播放器的状态的方式来控制媒体文件的播放,其中:
prepare()和prepareAsync() 提供了同步和异步两种方式设置播放器进入prepare状态,需要注意的是,如果MediaPlayer实例是由create方法创建的,那么第一次启动播放前不需要再调用prepare()了,因为create方法里已经调用过了。
start()是真正启动文件播放的方法,
pause()和stop()比较简单,起到暂停和停止播放的作用,
seekTo()是定位方法,可以让播放器从指定的位置开始播放,需要注意的是该方法是个异步方法,也就是说该方法返回时并不意味着定位完成,尤其是播放的网络文件,真正定位完成时会触发OnSeekComplete.onSeekComplete(),如果需要是可以调用setOnSeekCompleteListener(OnSeekCompleteListener)设置监听器来处理的。
release()可以释放播放器占用的资源,一旦确定不再使用播放器时应当尽早调用它释放资源。
reset()可以使播放器从Error状态中恢复过来,重新会到Idle状态。
4)设置播放器的监听器:
MediaPlayer提供了一些设置不同监听器的方法来更好地对播放器的工作状态进行监听,以期及时处理各种情况,
如: setOnCompletionListener(MediaPlayer.OnCompletionListener listener)、
setOnErrorListener(MediaPlayer.OnErrorListener listener)等,设置播放器时需要考虑到播放器可能出现的情况设置好监听和处理逻辑,以保持播放器的健壮性。
8. 横竖屏转换实现的几种方法 2011-11-12
1)每次都重启activity。
2)在xml文件中进行相应配置,可以使得不用重启activity,只要根据当前是否横屏加载对应的xml文件即可。
9. 飞行模式使用 2011-11-15
在Android中设置飞行状态是用BroadCast的,可以通过发送action为”Intent.ACTION_AIRPLANE_MODE_CHANGED”的广播来打开或状态飞行模式.
首先,修改飞行模式需要android.permission.WRITE_SETTINGS权限,请自行添加.
下面是完整代码:
| package com.hello; import android.app.Activity; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.widget.TextView; import android.widget.Toast; import android.provider.Settings; //虽然只用到Settings.System类,但还是不建议直接导入该类,因为会跟java.lang.System同名冲突 //当然也可以不导,直接用android.provider.Settings.System public class HelloWorldActivity extends Activity { /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); ContentResolver cr = getContentResolver(); if(Settings.System.getString(cr,Settings.System.AIRPLANE_MODE_ON).equals("0")){ //获取当前飞行模式状态,返回的是String值0,或1.0为关闭飞行,1为开启飞行 //如果关闭飞行,则打开飞行 Settings.System.putString(cr,Settings.System.AIRPLANE_MODE_ON, "1"); Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); sendBroadcast(intent); }else{ //否则关闭飞行 Settings.System.putString(cr,Settings.System.AIRPLANE_MODE_ON, "0"); Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); sendBroadcast(intent); }
}
}
补:飞行模式下,ServiceState会变成STATE_POWER_OFF,所有无线电相关的service都会关闭。
10. Menu的使用 2011-11-18Android系统里面有3种类型的菜单:options menu,context menu,sub menu。
options menu 按Menu键就会显示,用于当前的Activity。 它包括两种菜单项: 因为options menu在屏幕底部最多只能显示6个菜单项,这些菜单项称为icon menu,icon menu只支持文字(title) 以及icon,可以设置快捷键,不支持checkbox以及radio控件,所以不能设置checkable选项。 而多于6的菜单项会以“more” icon menu来调出,称为expanded menu。它不支持icon,其他的特性都和icon menu一样!
在Activity里面,一般通过以下函数来使用options menu: Activity::onCreateOptionsMenu (Menu menu) 创建options menu,这个函数只会在menu第一次显示时调用。 Activity::onPrepareOptionsMenu (Menu menu) 更新改变options menu的内容,这个函数会在menu每次显示时调用。 Activity::onOptionsItemSelected (MenuItem item) 处理选中的菜单项。
context menu 要在相应的view上按几秒后才显示的,用于view,跟某个具体的view绑定在一起。 这类型的菜单不支持icon和快捷键!
在Activity里面,一般通过以下函数来使用context menu: Activity::registerForContextMenu(View view) 为某个view注册context menu,一般在Activity::onCreate里面调用。 Activity::onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) 创建context menu,和options menu不同,context meun每次显示时都会调用这个函数。 Activity::onContextItemSelected(MenuItem item) 处理选中的菜单项。
sub menu 以上两种menu都可以加入子菜单,但子菜单不能嵌套子菜单,这意味着在Android系统,菜单只有两层,设计时需要注意的!同时子菜单不支持icon。
xml形式的menu定义及应用 上述的三种类型的menu都能够定义为xml资源,但需要手动地使用MenuInflater来得到Menu对象的引用。 一个菜单,对应一个xml文件,因为要求只能有一个根节点<menu>。官方说<?xml>声明可以不写,但我觉得还是写上好些,很多时候那个<?xml>声明主要是为了声明编码格式utf-8之类的。xml文件保存为res/menu/some_file.xml。Java代码引用资源: R.menu.some_file
接下来介绍相关的节点和属性(所有的属性都定义为android空间内,例如android:icon="@drawable/icon"): 效值:container,system,secondary,和alternative orderInCategory:定义这组菜单在菜单中的默认次序,int值 checkableBehavior:这组菜单项是否checkable。有效值:none,all(单选/单选按钮radio button),single(非单选/复选类型checkboxes) visible:这组菜单是否可见 true or false enabled:这组菜单是否可用,true or false
<item> 菜单项,可以嵌入<menu>作为子菜单。相关属性包括: id: item id menuCategory: 用来定义menu类别 orderInCategory: 用来定义次序,与一个组在一起(Used to define the order of the item, within a group) title: 标题 titleCondensed:标题摘要, 当原标题太长的时候,需要用简短的字符串来代替title icon: icon 图标 alphabeticShortcut: 字母快捷键 numericShortcut:数学快捷键 checkable:是否为checkbox, true or false checked:是否设置为checked状态,true or false visible: 是否可见, true or false enabled:是否可用,true or false
xml示例: <?xml version="1.0" encoding="utf-8"?> <menu xmlns:android="http://schemas.android.com/apk/res/android"> <item android:id="@+id/item1" android:title="Item 1" android:icon="@drawable/icon" android:checkable="true" android:checked="false" />
<group android:id="@+id/group_1" android:checkableBehavior="single"> <item android:id="@+id/group_item1" android:title="Item 1 in group" /> <item android:id="@+id/group_item2" android:title="Item 2 in group" android:checked="true" /> </group>
<item android:id="@+id/submenu" android:title="Sub Menu"> <menu> <item android:id="@+id/submenu_item" android:title="Sub Menu Item" /> </menu> </item>
<item android:id="@+id/item3" android:title="item 3" android:checkable="true" android:checked="true" />
</menu> Java代码 public void onCreate(Bundle savedInstanceState) { ... registerForContextMenu(editText); }
@Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo);
getMenuInflater().inflate(R.menu.menu1, menu); } 效果图
由于这是contextMenu,所以可以看到即使xml定义里面的item1.seticon了,但还是没有显示出来的,即那语句是无效的! 另外,要明确的是,要显示radio,需要用group,而group里面的item设置了checked = true即选中。而 checkable和checked的区别,一开始我是很困惑的,但写了代码并运行后,明白它们的区别了: checkable=true表示这个item是checkbox,checked则表示是否选中。所以对于checkbox item,最好先写 checkable="true",然后再写checked。
Java实现 用Java来实现以上的效果图,就比较麻烦些: private static final int MENU_GROUPITEM1 = Menu.FIRST + 8; private static final int MENU_GROUPITEM2 = Menu.FIRST + 9; private static final int MENU_ITEM1 = Menu.FIRST + 10;
public void onCreate(Bundle savedInstanceState) { ... registerForContextMenu(findViewById(R.id.edittext)); }
@Override public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) { super.onCreateContextMenu(menu, v, menuInfo);
menu.add(1,MENU_ITEM1,Menu.NONE, "Item 1").setCheckable(true).setChecked(false);
// Group ID int groupId = 0; // The order position of the item int menuItemOrder = Menu.NONE;
menu.add(groupId, MENU_GROUPITEM1, menuItemOrder, "Item 1 in group"); menu.add(groupId, MENU_GROUPITEM2, menuItemOrder, "Item 2 in group") .setChecked(true); menu.setGroupCheckable(groupId, true, true); //这句要写在group item的最后
SubMenu subMenu = menu.addSubMenu("Sub Menu 1"); subMenu.add("Sub Menu Item") .setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { Toast.makeText(HelloDemo.this, "Sub Menu Item selected", Toast.LENGTH_SHORT).show(); return true; //true表示完成当前item的click处理,不再传递到父类处理 } });
menu.add("Item 3").setCheckable(true).setChecked(true); } 在编写过程中,发现groupId的影响很大,不推荐使用Menu.add(int titleRes)和add(CharSequence title)方法来添加MenuItem,因为没有指定groupID,默认为0,这样子和后面的menu group 一组了,导致执行完menu.setGroupCheckable(groupId, true, true)后同一group的Item都变成radio。
OptionsMenu的Java实现 @Override public boolean onCreateOptionsMenu(Menu menu) { // Group ID int groupId = 0; // The order position of the item int menuItemOrder = Menu.NONE;
menu.add(groupId, MENU_COPY, menuItemOrder, "Copy") .setIcon(R.drawable.icon); menu.add(groupId, MENU_EDIT, menuItemOrder, "Edit"); menu.add(groupId, MENU_PASTE, menuItemOrder, "Paste"); menu.add(groupId, MENU_DELETE, menuItemOrder, "Delete"); menu.add(groupId, MENU_OK, menuItemOrder, "Ok"); menu.add(groupId, MENU_CANCEL, menuItemOrder, "Cancel"); menu.add(groupId, MENU_TEST, menuItemOrder, "Test"); menu.add(groupId, MENU_DEMO, menuItemOrder, "Demo"); // .setIcon(R.drawable.icon); more expand menu 不支持icon, setIcon不会报错,但运行时还是看不到icon的
//return super.onCreateOptionsMenu(menu); return true; //true表示要显示menu; false表示不显示menu }
处理菜单点击事件 方法一: 利用菜单自带的监听器功能,直接监听,就象处理控件事件一样,像上面的ContextMenu的subMenu.add("Sub Menu Item")设置MenuItem.OnMenuItemClickListener。
方法二: 在Activity和View都直接提供了一个菜单点击统一处理函数, Activity::onOptionsItemSelected (MenuItem item) ; Activity::onContextItemSelected(MenuItem item) ;
@Override public boolean onOptionsItemSelected(MenuItem item) { switch(item.getItemId()){ case MENU_COPY: Toast.makeText(this, "Copy Item selected", Toast.LENGTH_SHORT).show(); break;
default: break; } return false;//false表示继续传递到父类处理 } 效果图
动态菜单 对于OptionsMenu,一般可以使用onPrepareOptionsMenu来改变。
另外,使用函数android.view.Menu.addIntentOptions(int groupId,int itemId,int order,ComponentName caller, Intent[] specifics, Intent intent,int flags,MenuItem[] outSpecificItems) Specifics 以action+uri的具体方式来增加激活相应activity的菜单项 Intent 以categroy+uri这种一般形式来增加激活相应activity的菜单项 参数Intent和Specifics的区别是,一个用categroy+uri来匹配activity,一个用action+uri来匹配activity。
//按Action查找 Intent[] specifics = new Intent[1]; specifics[0] = new Intent(Intent.ACTION_EDIT, uri);
//按Category查找,Action设为null Intent intent = new Intent(null, uri); intent.addCategory(Intent.CATEGORY_ALTERNATIVE);
MenuItem[] items = new MenuItem[1]; menu.addIntentOptions(Menu.CATEGORY_ALTERNATIVE, 0, 0, null, specifics, intent, 0, items); 转自:http://android.blog.51cto.com/268543/306424
|
11. BT的使用 2011-11-26
1)首先,要操作蓝牙,先要在AndroidManifest.xml里加入权限
<uses-permissionandroid:name="android.permission.BLUETOOTH_ADMIN" />
<uses-permissionandroid:name="android.permission.BLUETOOTH" />
2)需要用到的几个类
1.BluetoothAdapter 顾名思义,蓝牙适配器,直到我们建立bluetoothSocket连接之前,都要不断操作它
BluetoothAdapter里的方法很多,常用的有以下几个:
cancelDiscovery() 根据字面意思,是取消发现,也就是说当我们正在搜索设备的时候调用这个方法将不再继续搜索
disable()关闭蓝牙
enable()打开蓝牙,这个方法打开蓝牙不会弹出提示,更多的时候我们需要问下用户是否打开,一下这两行代码同样是打开蓝牙,不过会提示用户:
Intemtenabler=new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabler,reCode);//同startActivity(enabler);
getAddress()获取本地蓝牙地址
getDefaultAdapter()获取默认BluetoothAdapter,实际上,也只有这一种方法获取BluetoothAdapter
getName()获取本地蓝牙名称
getRemoteDevice(String address)根据蓝牙地址获取远程蓝牙设备
getState()获取本地蓝牙适配器当前状态(感觉可能调试的时候更需要)
isDiscovering()判断当前是否正在查找设备,是返回true
isEnabled()判断蓝牙是否打开,已打开返回true,否则,返回false
listenUsingRfcommWithServiceRecord(String name,UUID uuid)根据名称,UUID创建并返回BluetoothServerSocket,这是创建BluetoothSocket服务器端的第一步
startDiscovery()开始搜索,这是搜索的第一步
2.BluetoothDevice看名字就知道,这个类描述了一个蓝牙设备
createRfcommSocketToServiceRecord(UUIDuuid)根据UUID创建并返回一个BluetoothSocket
这个方法也是我们获取BluetoothDevice的目的——创建BluetoothSocket
这个类其他的方法,如getAddress(),getName(),同BluetoothAdapter
3.BluetoothServerSocket如果去除了Bluetooth相信大家一定再熟悉不过了,既然是Socket,方法就应该都差不多,
这个类一种只有三个方法
两个重载的accept(),accept(inttimeout)两者的区别在于后面的方法指定了过时时间,需要注意的是,执行这两个方法的时候,直到接收到了客户端的请求(或是过期之后),都会阻塞线程,应该放在新线程里运行!
还有一点需要注意的是,这两个方法都返回一个BluetoothSocket,最后的连接也是服务器端与客户端的两个BluetoothSocket的连接
close()这个就不用说了吧,翻译一下——关闭!
4.BluetoothSocket,跟BluetoothServerSocket相对,是客户端
一共5个方法,不出意外,都会用到
close(),关闭
connect()连接
getInptuStream()获取输入流
getOutputStream()获取输出流
getRemoteDevice()获取远程设备,这里指的是获取bluetoothSocket指定连接的那个远程蓝牙设备
1) 使用流程
1、获取本地蓝牙适配器
BluetoothAdapter
mAdapter= BluetoothAdapter.getDefaultAdapter();
2、打开蓝牙
if(!mAdapter.isEnabled()){
//弹出对话框提示用户是后打开
Intent enabler = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(enabler, REQUEST_ENABLE);
//不做提示,强行打开
// mAdapter.enable();
}
3、搜索设备
1)刚才说过了mAdapter.startDiscovery()
是第一步,可以你会发现没有返回的蓝牙设备,怎么知道查找到了呢?向下看,不要急
2)定义BroadcastReceiver,关于BroadcastReceiver不多讲了,不是今天的讨论内容,代码如下
BroadcastReceiver mReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
//找到设备
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent
.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
if (device.getBondState() != BluetoothDevice.BOND_BONDED) {
Log.v(TAG, "find device:" + device.getName()
+ device.getAddress());
}
}
//搜索完成
else if (BluetoothAdapter.ACTION_DISCOVERY_FINISHED
.equals(action)) {
setTitle("搜索完成");
if (mNewDevicesAdapter.getCount() == 0) {
Log.v(TAG,"find over");
}
}
//执行更新列表的代码
}
};
这样,没当查找到新设备或是搜索完成,相应的操作都在上段代码的两个if里执行了,不过前提是你要先注册
BroadcastReceiver,具体代码如下
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(mReceiver, filter);
filter = new IntentFilter(BluetoothAdapter.ACTION_DISCOVERY_FINISHED);
registerReceiver(mReceiver, filter);
(这段代码,一般写在onCreate()里..)
3建立连接,首先Android sdk(2.0以上版本)支持的蓝牙连接是通过BluetoothSocket建立连接(说的不对请高人指正),服务器端(BluetoothServerSocket)和客户端(BluetoothSocket)需指定同样的UUID,才能建立连接,因为建立连接的方法会阻塞线程,所以服务器端和客户端都应启动新线程连接
1)服务器端:
//UUID格式一般是"xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"可到
//http://www.uuidgenerator.com 申请
BluetoothServerSocket serverSocket = mAdapter. listenUsingRfcommWithServiceRecord(serverSocketName,UUID);
serverSocket.accept();
2)客户端:
//还记得我们刚才在BroadcastReceiver获取了BLuetoothDevice么?
BluetoothSocket clienSocket=dcvice. createRfcommSocketToServiceRecord(UUID);
clienSocket.connect();
4、数据传递,通过以上操作,就已经建立的BluetoothSocket连接了,数据传递无非是通过流的形式
1)获取流
inputStream = socket.getInputStream();
outputStream = socket.getOutputStream();
2)写出、读入
这是基础的东西,在这就不多赘述了
12. Bluetoothadapter的使用 2011-11-26
使用BluetoothAdapter类,你能够在Android设备上查找周边的蓝牙设备然后配对(绑定),蓝牙通讯是基于唯一地址MAC来相互传输的,考虑到安全问题Bluetooth通讯时需要先配对。然后开始相互连接,连接后设备将会共享同桓鯮FCOMM通道以便相互传输数据,目前这些实现在Android 2.0或更高版本SDK上实现。
一、查找发现 findding/discovering devices
对于Android查找发现蓝牙设备使用BluetoothAdapter类的startDiscovery()方法就可以执行一个异步方式获取周边的蓝牙设备,因为是一个异步的方法所以我们不需要考虑线程被阻塞问题,整个过程大约需要12秒时间,这时我们紧接着注册一个BroadcastReceiver 对象来接收查找到的蓝牙设备信息,我们过滤ACTION_FOUND这个 Intent动作来获取每个远程设备的详细信息,通过附加参数在Intent字段EXTRA_DEVICE 和 EXTRA_CLASS, 中包含了每个BluetoothDevice 对象和对象的该设备类型 BluetoothClass ,示例代码
private final BroadcastReceiver cwjReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (BluetoothDevice.ACTION_FOUND.equals(action)) {
BluetoothDevice device = intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE);
myArrayAdapter.add(device.getName() + " android123 " + device.getAddress()); //获取设备名称和mac地址
}
}
};
// 注册这个 BroadcastReceiver
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_FOUND);
registerReceiver(cwjReceiver, filter);
最后android123提醒大家需要注意的是,记住在Service或Activity中重写onDestory()方法,使用unregisterReceiver方法反注册这个BroadcastReceiver对象保证资源被正确回收。
一些其他的状态变化有 ACTION_SCAN_MODE_CHANGED 额外参数 EXTRA_SCAN_MODE 和 EXTRA_PREVIOUS_SCAN_MODE以及SCAN_MODE_CONNECTABLE_DISCOVERABLE、SCAN_MODE_CONNECTABLE和SCAN_MODE_NONE,
二、配对绑定 bnded/paired device
在Android中配对一个蓝牙设备可以调用BluetoothAdapter类的getBondedDevices()方法可以获取已经配对的设备,该方法将会返回一个BluetoothDevice数组来区分每个已经配对的设备,示例代码如下:
Set<BluetoothDevice> pairedDevices = cwjBluetoothAdapter.getBondedDevices();
if (pairedDevices.size() > 0) //如果获取的结果大于0,则开始逐个解析
{
for (BluetoothDevice device : pairedDevices) {
myArrayAdapter.add(device.getName() + " android123 " + device.getAddress()); //获取每个设备的名称和MAC地址添加到数组适配器myArrayAdapter中。
}
}
很多网友不明白如何让自己的手机被其他蓝牙设备发现如何设置,下面我们就一起来说说
三、允许发现 enabling discoverability
如果需要用户确认操作,不需要获取底层蓝牙服务实例,可以通过一个Intent来传递ACTION_REQUEST_DISCOVERABLE参数,这里通过startActivityForResult来强制获取一个结果,重写startActivityForResult()方法获取执行结果,返回结果有RESULT_OK和RESULT_CANCELLED分别代表开启和取消(失败),当然最简单的方法是直接执行,示例代码如下
Intent cwjIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE);
cwjIntent.putExtra(BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION, 300);
startActivity(cwjIntent);
四、建立通讯 establishing
对于建立一个蓝牙通讯,必须经过以下四个步骤:获取本地蓝牙设备、查找远程设备、配对(已配对设备将会忽略这步的细节)、连接设备和传输数据.
在Android平台中首先我们需要查找本地活动的蓝牙适配器,通过BluetoothAdapter类的getDefaultAdapter() 方法获得一个系统默认可用的蓝牙设备,示例代码如下
BluetoothAdapter cwjBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (cwjBluetoothAdapter == null) {
// Android开发网提示大家本机没有找到蓝牙硬件或驱动存在问题
}
当然有了这步仍然不能建立连接,因为我们还不知道手机中的蓝牙功能是否被开启,可以通过cwjBluetoothAdapter的.isEnabled方法来判断,如果没有开启,我们可以通过下面的代码提醒用户启用:
if (!cwjBluetoothAdapter.isEnabled()) {
Intent TurnOnBtIntent = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
startActivityForResult(TurnOnBtIntent, REQUEST_ENABLE_BT);
}
我们通过startActivityForResult()方法发起的Intent将会在onActivityResult()回调方法中获取用户的选择,比如用户单击了Yes开启,那么将会收到RESULT_OK 的结果,如果RESULT_CANCELED则代表用户不愿意开启蓝牙,当然android123提醒大家还可以通过其他方式来开启,比如说用BluetoothDevice获取蓝牙服务接口对象,是用enable()方法来开启,无需询问用户,这时就需要用到android.permission.BLUETOOTH_ADMIN权限。
如何判断系统蓝牙的状态呢? 建立BroadcastReceiver对象,接收ACTION_STATE_CHANGED动作,在EXTRA_STATE和EXTRA_PREVIOUS_STATE包含了现在状态和过去的状态,最终的结果定义是STATE_TURNING_ON正在开启, STATE_ON已经开启, STATE_TURNING_OFF正在关闭和 STATE_OFF已经关闭。
13. Bluetoothsocket的使用 2011-11-26
一、连接设备
蓝牙通讯分为server服务器端和client客户端,它们之间使用BluetoothSocket 类的不同方法来获取数据,
1. 作为服务器
如果一个设备需要和两个或多个设备连接时,就需要作为一个server来传输,在android中提供了BluetoothServerSocket类来处理用户发来的信息,服务器端套接字在接受(accepted) 一个客户发来的BluetoothSocket时作出相应的响应。示例代码如下:
private class AcceptThread extends Thread {
private final BluetoothServerSocket cwjServerSocket;
public AcceptThread() {
BluetoothServerSocket tmp = null; //使用一个临时对象代替,因为cwjServerSocket定义为final
try {
tmp = myAdapter.listenUsingRfcommWithServiceRecord(NAME, CWJ_UUID); //服务仅监听
} catch (IOException e) { }
cwjServerSocket = tmp;
}
public void run() {
BluetoothSocket socket = null;
while (true) { //保持连接直到异常发生或套接字返回
try {
socket = cwjServerSocket.accept(); //如果一个连接同意
} catch (IOException e) {
break;
}
if (socket != null) {
manageConnectedSocket(socket); //管理一个已经连接的RFCOMM通道在单独的线程。
cwjServerSocket.close();
break;
}
}
}
public void cancel() { //取消套接字连接,然后线程返回
try {
cwjServerSocket.close();
} catch (IOException e) { }
}
}
在这里android开发网提醒大家需要注意的是服务器一般处理多个任务不嫩阻塞,必须使用异步的方法这里我们开了一个线程,目前Android的虚拟机上层没有提供I/O模型,这里我们以后会讲解高负载情况下性能优化解决方案。
2. 作为客户端
以便初始化一个连接到远程设备,首先必须获取本地的BluetoothDevice对象,相关的方法在我们 Android蓝牙API之BluetoothAdapter类 的两篇文章中有讲到,这里不再赘述,相关的示例代码如下:
private class ConnectThread extends Thread {
private final BluetoothSocket cwjSocket;
private final BluetoothDevice cwjDevice;
public ConnectThread(BluetoothDevice device) {
BluetoothSocket tmp= null;
cwjDevice= device;
try {
tmp= device.createRfcommSocketToServiceRecord(CWJ_UUID); //客户端创建
} catch (IOException e) { }
cwjSocket= tmp;
}
public void run() {
myAdapter.cancelDiscovery(); //取消发现远程设备,这样会降低系统性能
try {
cwjSocket.connect();
} catch (IOException connectException) {
try {
cwjSocket.close();
} catch (IOException closeException) { }
return;
}
manageConnectedSocket(cwjSocket); //管理一个已经连接的RFCOMM通道在单独的线程。
}
public void cancel() {
try {
cwjSocket.close();
} catch (IOException e) { }
}
}
14. 摄像头实现 2011-11-26
1)添加权限
如果你想在自己的应用中使用摄像头,需要在AndroidManifest.xml中增加以下代码:
uses-permission android:name="android.permission.CAMERA"/>
2)设定摄像头布局
除了拍照外,没有多余摄像头功能。下面我们一起看一下本文示例将要用到的布局文件。
LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical">
SurfaceView android:id="@+id/surface_camera"
android:layout_width="fill_parent" android:layout_height="10dip"
android:layout_weight="1">
SurfaceView>
LinearLayout>
该布局非常简单,只有一个LinearLayout视图组,在它下面只有一个SurfaceView视图,也就是我们的摄像头屏幕。
3)摄像头实现代码
现在我们已经查看了摄像头的xml代码,下面再来看一下Android代码。让我们创建一个名为“CameraView”的Activity类,实现SurfaceHolder.Callback接口:
public class CamaraView extends Activity implements SurfaceHolder.Callback
接口SurfaceHolder.Callback被用来接收摄像头预览界面变化的信息。它实现了三个方法:
surfaceChanged
当预览界面的格式和大小发生改变时,该方法被调用。
surfaceCreated
初次实例化,预览界面被创建时,该方法被调用。
surfaceDestroyed
当预览界面被关闭时,该方法被调用。
下面我们一起看一下在摄像头应用中如何使用这个接口,首先看一下在Activity类中的onCreate方法。
super.onCreate(icicle);
getWindow().setFormat(PixelFormat.TRANSLUCENT);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
setContentView(R.layout.camera);
mSurfaceView = (SurfaceView) findViewById(R.id.surface_camera);
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
下面我们逐一对代码进行一下说明。
getWindow().setFormat(PixelFormat.TRANSLUCENT);
requestWindowFeature(Window.FEATURE_NO_TITLE);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN);
通过上述代码,我们告诉屏幕两点信息:
1、摄像头预览界面将通过全屏显示,没有“标题(title)”;
2、屏幕格式为“半透明”。
setContentView(R.layout.camera_surface );
mSurfaceView = (SurfaceView) findViewById(R.id.surface_camera);
在以上代码中,我们通过setContentView来设定Activity的布局为前面我们创建的camera_surface,并创建一个SurfaceView对象,从xml文件中获得它。
mSurfaceHolder = mSurfaceView.getHolder();
mSurfaceHolder.addCallback(this);
mSurfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
通过以上代码,我们从surfaceview中获得了holder,并增加callback功能到“this”。这意味着我们的操作(activity)将可以管理这个surfaceview。
我们看一下callback功能时如何实现的:
public void surfaceCreated(SurfaceHolder holder) {
mCamera = Camera.open();
mCamera是“Camera”类的一个对象。在surfaceCreated方法中我们“打开”摄像头。这就是启动它的方式。
public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) {
if (mPreviewRunning) {
mCamera.stopPreview();
}
Camera.Parameters p = mCamera.getParameters();
p.setPreviewSize(w, h);
mCamera.setParameters(p);
try {
mCamera.setPreviewDisplay(holder);
} catch (IOException e) {
e.printStackTrace();
}
mCamera.startPreview();
mPreviewRunning = true;
}
该方法让摄像头做好拍照准备,设定它的参数,并开始在Android屏幕中启动预览画面。我使用了一个“semaphore”参数来防止冲突:当mPreviewRunning为true时,意味着摄像头处于激活状态,并未被关闭,因此我们可以使用它。
public void surfaceDestroyed(SurfaceHolder holder) {
mCamera.stopPreview();
mPreviewRunning = false;
mCamera.release();
}
通过这个方法,我们停止摄像头,并释放相关的资源。正如大家所看到的,我们在这儿设置mPreviewRunning为false,以此来防止在surfaceChanged方法中的冲突。原因何在?因为这意味着我们已经关闭了摄像头,而且我们不能再设置其参数或在摄像头中启动图像预览。
最后我们看一下本例中最重要的方法:
Camera.PictureCallback mPictureCallback = new Camera.PictureCallback() {
public void onPictureTaken(byte[] imageData, Camera c) {
}
};
当拍照时,该方法被调用。举例来说,你可以在界面上创建一个OnClickListener,当你点击屏幕时,调用PictureCallBack方法。这个方法会向你提供图像的字节数组,然后你可以使用Android提供的Bitmap和BitmapFactory类,将其从字节数组转换成你想要的图像格式。