在当今的手机当中,除了打电话和发短信,最长用的功能就是听歌,看视频,玩游戏,看新闻这些多媒体应用了。
今天抛开手机应用本身,站在开发者角度去实现一些主流的功能,例如:在我们玩微信,玩QQ时,当我们的这些应用在后台运行时,这个时候来消息,
通常我们的状态栏(也就是手机的最上方)会出现一个消息提醒,当然像很多很多其他的应用,今日头条有新新闻时,上方也会弹出消息提醒,等等。
-------------Notification。
当我们新下载一个应用,注册账号时,通常可以更换联系人头像,当点击更换时,通常有两种方式可以实现选择自己想要更换的相片,一种是:直接
进入相机拍照,另一种是:从当前图库中有的图片选择。 ----------------调用摄像头和相册功能。
一 Notification
首先我们来看下Notification功能,如果我们想实现一个Notification功能,我们应该怎么做?
上面我们了解了通知的基本概念,下面我们就来看一下通知的使用方法吧,首先通知的用法还是很灵活的,既可以在活动中创建,同时可以在广播中
创建,也可以在Service中创建,在活动中创建的还是比较少的,因为一般都只有当应用在后台时才需要使用通知。
1.首先我们需要一个NotificationManager来对通知进行管理,可以调用Context.getSystemService()来获得。
NotificationManager manager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
显示一个通知的方式为:manager.notify(1,notification);//第一个参数为id,要保证每个通知所指定的id都是不同的,第二个参数则是
Notification对象。
2.需要使用Builder构造器来构造一个Notification对象,但是几乎所有的android系统每个版本都会对通知做一些大大小小的修改,api不稳定的
问题在通知上显得尤其严重,那么我们最好的办法就是使用support库中提供的api,support-v4就提供了一个NotificationCompat类,我们
可以使用这个类来构造notification对象。
Notification notification = new NotificationCompat.Builder(context).build();
当然上面只是创建了一个空的Notification对象并没有实际的作用,我们可以在build()之前设置任意多的方法来丰富这个notification对象。
下面我以一个简单的例子来创建一个包含Notification常用设置的通知。
效果图:
注册一个按钮发送通知消息 下拉状态栏后通知显示的状态
这里帖上创建通知的代码及布局文件:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.ruiwei.multimedia_practice.MainActivity">
<Button
android:id="@+id/send_notification"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Send normal notification" />
</RelativeLayout>
public class MainActivity extends AppCompatActivity {
private Button send_notification;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
send_notification = (Button) findViewById(R.id.send_notification);
send_notification.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
//当点击通知按钮能执行某项意图,我们可以使用setContentIntent,setContenIntent接收一个参数(pandingIntent);
Intent intent = new Intent(MainActivity.this,Main2Activity.class);
PendingIntent pi = PendingIntent.getActivity(MainActivity.this,0,intent,0);
//首先获取notification manager 的对象,通过context.getSystemService().
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification notification = new NotificationCompat.Builder(MainActivity.this)
.setContentTitle("This is content title") //设置通知title
//.setContentText("You have been working for three years and can apply for a 15k pay raise. Please apply.")
.setWhen(System.currentTimeMillis())//设置通知显示时间
.setSmallIcon(R.mipmap.samll_icon)//设置通知显示图标
//.setLargeIcon(BitmapFactory.decodeResource(getResources(),R.mipmap.samll_icon))
.setContentIntent(pi) //设置通知的延迟事件
//.setSound(Uri.fromFile(new File("/system/media/audio/ringtone/"))) 设置通知铃声
.setVibrate(new long[]{0,1000,1000,1000}) //设置发送通知时手机振动
.setLights(Color.BLUE,1000,1000) //设置呼吸灯提醒
.setAutoCancel(true) //当通知被点击后,自动被移除掉当前通知状态
//设置通知中的content 为大图片
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(getResources(),R.drawable.d)))
. setPriority(NotificationCompat.PRIORITY_MAX)
.build();
notificationManager.notify(1,notification);
}
});
}
}
通知的常用属性都在上面有设置,还有一个setPriority()方法,setPriority方法接收一个参数用于设置这条通知的重要程度。
一共有5个常量值可选:
PRIORITY_DEFAULT,默认的与不设置一样。
PRIORITY_MIN表示最低的重要程度系统只会在特定的场景才会显示这条通知、
PRIORITY_LOW表示较低的重要程度,系统可能会讲这类通知缩小,或者改变其显示的顺序,并将其放在更重要的通知之后。
PRIORITY_HIGHT表示较高的重要程度,系统可能会将改通知放大,或改变其显示的顺序,将其放在比较靠前的位置。
PRIORITY_MAX表示最高的重要程度,这类通知必须让用户立刻看见。甚至需要用户做出相应操作。
具体写法:
Notification notification = new NotificationCompat.Builder(this).
. . .
.setPriority(NotificationCompat.PRIORITY_MAX);
. . .
二 调用摄像头拍照
如何才能在应用程序里调用手机的摄像头进行拍照?
新建一个CameraAlbumTest项目,然后修改activity_main.xml的代码:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/activity_main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.ruiwei.cameraalbumtest.MainActivity">
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/entry_camera"
android:text="camera"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/display_picture"
android:layout_below="@+id/entry_camera"
/>
</RelativeLayout>
可以看见,布局中只有两个控件,一个是button和一个imageView.button是用于打开摄像头进行拍照的,而ImageView则是用于将拍摄
的图片显示出来。我把调用摄像头并显示出来的代码帖在下面,再做分析:
public class MainActivity extends AppCompatActivity {
private Button entry_camera;
private ImageView display_picture;
private Uri imageUri;
public static final int TAKE_PHOTE =1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
entry_camera = (Button) findViewById(R.id.entry_camera);
display_picture = (ImageView) findViewById(R.id.display_picture);
entry_camera.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
File outputImage = new File(getExternalCacheDir(),"outputImage.jpg");
try {
if(outputImage.exists()){
outputImage.delete();
}
outputImage.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
if(Build.VERSION.SDK_INT>=24){
imageUri = FileProvider.getUriForFile(MainActivity.this,"com.ruiwei.cameraalbumtest.fileprovider",outputImage);
}else{
imageUri = Uri.fromFile(outputImage);
}
Intent intent = new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
startActivityForResult(intent,TAKE_PHOTE);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch(requestCode){
case TAKE_PHOTE:
if(requestCode == RESULT_OK){
try {
Bitmap bitmap = BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
display_picture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
break;
default:
break;
}
}
}
上面的代码其实不是很复杂,在MainActivity中要做的第一件事就是获取两个控件button 和 imagevies的实例,
并给button注册点击事件,然后在点击事件里开始处理调用摄像头的逻辑,我们重点看一下这一步代码。
首先:这里创建了一个File对象,用于存放摄像头拍下的照片,这里我们把图片命名为outputimage.jpg,并将它存放在当前应用的缓存数据目录下。
调用getExternCacheDir()方法就能得到这个目录。具体的路径是/sdcard/Android/data/<package name>/cache.只所以要使用缓存目录来存放
图片是因为从android6.0开始,读写SD卡别列为危险权限,如果将图片存放在SD卡的任何其他目录,都要进行运行时权限的处理才行。
其次:接着会进行一个判断,如果运行设备的系统低于android7.0,就调用Uri.fromFile()方法将File对象转换成Uri对象,这个Uri对象标识着outputImage
这张图片的本地真是路径,否则就调用FileProvider的getUriForFile()方法将File对象转换成一个封装过得Uri对象。
getUriForFile()方法接收三个参数,第一个为为context对象,第二个可以是任意唯一的字符串,第三个则是我们刚刚创建的File对象。之所以要进行这样
一层转换,是因为android7.0系统开始,直接使用本地真是路径的Uri是认为不安全的。会抛出一个FileUriExposedException异常。而FileProvider则是一个
特殊的内容提供器,它使用了和内容提供器类似的机制对数据进行保护,可以选择性的将封装过得Uri共享给外部,从而提高了应用的安全性。
然后:接下来会构建出一个Intent对象,并将这个Intent的action指定为:android.media.aciton.IMAGE_CAPTURE,再调用Intent的putExtra()方法
指定图片的输出地址,这里填入了刚刚得到的URI对象,最后调用startActivityResult来启动这个活动。
最后:由于刚才我们是使用startActivityResult来启动活动的,因此拍完照会有结果返回到onActivityResult()方法中.如果拍照成功,就可以调用
Bitmap.factory的decodeStream方法将outputImage.jpg这张图片解析成Bitmap对象,然后把他设置成imageView中显示出来。
我们需要注意的两点是:
刚才用到了FileProvider,自然的要再AndroidManifest.xml中对内容提供器进行注册下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.ruiwei.cameraalbumtest">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
<provider
android:name = "android.support.v4.content.FileProvider"
android:authorites="com.ruiwei.cameraabumtest.fileprovider"
android:exported="flase"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
</manifest>
android :name 属性的值是固定的。
android:authorities属性的值必须要和刚才FileProvider.getUriForFile()方法中的第二个参数一致。
另外这里还在<provider>标签的内部使用了<meta-data>来指定Uri共享的路径,并引用了一个@xml/file_paths资源,
这个资源需要我们在res/目录下创建一个目录,接着创建一个file文件名为:file_paths.xml
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path=""/>
</paths>