不定期更新Android开发的基础知识
读取联系人
- 在AndroidManifest.xml申明权限
< uses-permission android:name="android.permission.READ_CONTACTS" />
- 申请权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.READ_CONTACTS},1);
}else
{readContacts();//调用具体的方法
}
- 判断结果
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
switch (requestCode){
case 1:
if (grantResults.length>0&&grantResults[0]==PackageManager.PERMISSION_GRANTED){
readContacts();
}else{
Toast.makeText(this, "请求失败", Toast.LENGTH_SHORT).show();
}
}
}
- 读取联系人
private void readContacts(){
Cursor cursor=null;
cursor=getContentResolver().query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null);
if (cursor!=null){
while(cursor.moveToNext()){
String name=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME));
String number=cursor.getString(cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
Log.d("============","name:"+name+" number="+number);
}
cursor.close();
}
}
获取缩略图(bitmap)
/**
* 请求相机拍照
*/
private void dispatchTakePictureIntent() {
Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(takePictureIntent, 1);
}
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1 && resultCode == RESULT_OK) {
Bundle extras = data.getExtras();
Bitmap imageBitmap = (Bitmap) extras.get("data");//获取bitmap
mImageView.setImageBitmap(imageBitmap);
}
}
保存全尺寸照片
- 启动相机
private void takeAPhoto(){
//创建file对象,用来储存拍照后的图片
File pictureFile=new File(getExternalCacheDir(),"a.jpg");
if (pictureFile.exists())
pictureFile.delete();
else {
try {
pictureFile.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
//创建内容提供器的指定路径 第二个参数的名字任意
imageUri= FileProvider.getUriForFile(this,"my_test_photo",pictureFile);
//启动相机
Intent intent=new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);
startActivityForResult(intent,1);
}
}
控制相机
- 创建内容提供器
< !--
android:name="android.support.v4.content.FileProvider" name是固定的
android:grantUriPermissions="true" 这个是必须的
-->
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="my_test_photo" //这个要和上面的哪个名字相同
android:exported="false"
android:grantUriPermissions="true">
<!--
android:name="android.support.FILE_PROVIDER_PATHS" 固定的
android:resource="@xml/path" 共享的路径
-->
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/path"></meta-data>
</provider>
- path的代码
< ?xml version="1.0" encoding="utf-8"?>
< paths xmlns:android="http://schemas.android.com/apk/res/android">
< !--
name:好像没有什么用
path:表示共享的具体路径,设置空值表示共享整个SD卡
-->
< external-path
name="my_images"
path=""/>
</paths>
- 获取结果(重写Activity的onActivityResult方法)
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode==RESULT_OK)
{
try {
Bitmap bitmap= BitmapFactory.decodeStream(getContentResolver().openInputStream(imageUri));
picture.setImageBitmap(bitmap);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
}
从图库中加载图片
- 申请权限
private void checkPermission(){
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PackageManager.PERMISSION_GRANTED){
ActivityCompat.requestPermissions(this,new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
}else{
openAlbum();
}
}
- 判断是否同意权限
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode==RESULT_OK&&permissions.length>0){
openAlbum();
}else{
Toast.makeText(this, "申请权限失败", Toast.LENGTH_SHORT).show();
}
}
- 打开相册
private void openAlbum(){
Intent intent=new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent,2);
}
- 加载图片
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (resultCode==RESULT_OK){
loadImage(data);
}
}
/**
* 获得图片的路径
* @param uri
* @param selection
* @return
*/
private String getImagePath(Uri uri,String selection){
String path=null;
Cursor cursor=getActivity().getContentResolver().query(uri,null,selection,null,null);
if (cursor!=null){
if (cursor.moveToNext()){
path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
Log.d("================",path);
}
cursor.close();
}
return path;
}
/**
* 加载图片
* @param data
*/
private void loadImage(Intent data){
String path=null;
Uri uri=data.getData();
if (DocumentsContract.isDocumentUri(this,uri)){
//如果是document类型的Uri,通过document id处理
String docId=DocumentsContract.getDocumentId(uri);
if ("com.android.providers.media.documents".equals(uri.getAuthority())){
String id=docId.split(":")[1];//解析出数字格式的id
String selection= MediaStore.Images.Media._ID+"="+id;
path=getImagePath(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,selection);
}else if("com.android.providers.downloads.documents".equals(uri.getAuthority())){
Uri contentUri= ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"),Long.valueOf(docId));
path=getImagePath(contentUri,null);
}
}else if ("content".equalsIgnoreCase(uri.getScheme())){
//如果是content类型的Uri,则用普通方式来处理
path=getImagePath(uri,null);
}else if ("file".equalsIgnoreCase(uri.getScheme())){
//如果是file类型的Uri,直接获取图片路径就可
path=uri.getPath();
}
showPicture(path);
}
/**
* 显示图片
* @param path
*/
private void showPicture(String path){
if (path!=null){
Bitmap bitmap= BitmapFactory.decodeFile(path);
imageView.setImageBitmap(bitmap);
}else
Toast.makeText(this, "加载图片出错", Toast.LENGTH_SHORT).show();
}
拨打电话
Intent intent =new Intent(Intent.ACTION_DIAL);
intent.setData(Uri.parse("tel:10086"));
startActivity(intent);
启动系统的浏览器
Intent intent=new Intent(Intent.ACTION_VIEW);
intent.setData(Uri.parse("htttp://www.baidu.com"));
startActivity(intent);
修改图标和名称
1、把图标放在以mipmap开头的目录下 2.然后在AndroidManifest.xml文件,把< application>标签中的android:icon属性指定为我们想要的图标 3.在strings.xml文件中,设置自己的应用名
<resources>
<string name="app_name">应用的名字</string>
</resources>
录制视频
- 使用相机程序来录制视频
private void dispatchTakeVideoIntent() {
Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
if (takeVideoIntent.resolveActivity(getPackageManager()) != null) {
startActivityForResult(takeVideoIntent, 1);
}
}
- 查看视频
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == 1 && resultCode == RESULT_OK) {
Uri videoUri = data.getData();
videoView.setVideoURI(videoUri);
videoView.start();
}
}
响铃和振动
<uses-permission android:name="android.permission.VIBRATE"/>
响铃代码
MediaPlayer mp = new MediaPlayer();
mp.setDataSource(this, RingtoneManager
.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION));//这里我用的通知声音,还有其他的,大家可以点进去看
mp.prepare();
mp.start();
mp.setLooping(true); //循环播放
震动代码
//取得震动服务的句柄
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
//按照指定的模式去震动。
// vibrator.vibrate(1000);
//数组参数意义:第一个参数为等待指定时间后开始震动,震动时间为第二个参数。后边的参数依次为等待震动和震动的时间
//第二个参数为重复次数,-1为不重复,0为一直震动
vibrator.vibrate( new long[]{1000,1000},0);
一般我们在用到震动以及响铃的时候都要考虑到用户设置的手机模式
AudioManager audio = (AudioManager) this.getSystemService(Context.AUDIO_SERVICE);//获取当前手机模式
switch (audio.getRingerMode()) {
case AudioManager.RINGER_MODE_SILENT://静音
//do sth
break;
case AudioManager.RINGER_MODE_NORMAL://响铃
//do sth
break;
case AudioManager.RINGER_MODE_VIBRATE://震动
//do sth
break;
}
Android的通知
参考链接:www.jianshu.com/p/4a8fc0b78…
- 创建通知
NotificationManager manager=(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
Notification notification=new new NotificationCompat.Builder(context).build();
- 设置属性(通过在build()方法前连缀任意多的方法)
Notification notification=new NotificationCompat.Builder(context)
.setContentTitle("这是标题")
.setContentText("这是内容")
.setSmallIcon(R.drawable.flight) //设置通知的小图标
.setWhen() //用于指定通知被创建的时间
.setLargeIcon() //用于设置通知的大图标
.setCustomBigContentView(remoteViews) //设置RemoteViews
.setAutoCancel(true) //设置通知点击后取消
.setContentIntent() //设置PendingIntent
.setSound(uri) //设置通知到来时的音频
.setVibrate(new long[]{}) //设置手机的震动,偶数项表示静止时间 ,奇数项表示震动时间
.setLights() //设置LED灯,参数 1.指定LED的颜色 2.指定LED亮的时长 3.指定LED暗的时长
.setDefaults(NotificationCompat.DEFAULT_ALL) //设置手机的默认通知模式
.setStyle(new NotificationCompat.BigTextStyle().bigText("大文本")) //设置大文本
.setStyle(new NotificationCompat.BigPictureStyle().bigPicture(bitmap))//设置大图片
.setPriority()//设置通知的重要程度
.build();
manager.notify(1,notification); //让通知显示出来
注意:要控制手机振动要申明权限
<!--声明控制手机振动--> <uses-permission android:name="android.permission.VIBRATE"/>
- setPriority(int mode) 从上到下越来越重要
mode有五种值可以选 PRIORITY_MIN PRIORITY_LOW PRIORITY_DEFAULT PRIORITY_HIGH PRIORITY_MAX
- PendingIntent
// 获得一个PendingIntent,该待定意图发生时,作用相当于Context.sendBroadcast(Intent)
PendingIntent.getBroadcast(Context context, int requestCode, Intent intent, int flags)
// 获得一个PendingIntent,该待定意图发生时,作用相当于Context.startActivity(Intent)
PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags)
PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags, Bundle options)
//获得一个PendingIntent,该待定意图发生时,作用相当于 Context.startService(Intent)
PendingIntent.getService(Context context, int requestCode, Intent intent, int flags)
第四个参数的取值 //如果新请求的 PendingIntent 发现已经存在时,取消已存在的,用新的 PendingIntent 替换 int FLAG_CANCEL_CURRENT //如果新请求的 PendingIntent 发现已经存在时,忽略新请求的,继续使用已存在的。日常开发中很少使用 int FLAG_NO_CREATE //表示 PendingIntent 只能使用一次,如果已使用过,那么 getXXX(…) 将会返回 NULL //也就是说同类的通知只能使用一次,后续的通知单击后无法打开。 int FLAG_ONE_SHOT //如果新请求的 PendingIntent 发现已经存在时, 如果 Intent 有字段改变了,这更新已存在的 PendingIntent int FLAG_UPDATE_CURRENT
//用于启动Activity的PendingIntent
Intent intent=new Intent(this,MainActivity.class);
PendingIntent pendingIntent=PendingIntent.getActivity(this,0,intent,0);
- 自定义通知
//获取RemoteView对象
RemoteViews remoteViews=new RemoteViews(getPackageName(),R.layout.notifition_my);
//设置标题
remoteViews.setTextViewText(R.id.title,"标题" );
//设置文本内容
remoteViews.setTextViewText(R.id.text,"内容");
//设置图片,不太了解
remoteViews.setImageViewResource(R.id.icon,R.drawable.car);
notifition_my.xml文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:text="标题"
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_weight="1"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_weight="1"
android:id="@+id/icon"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/text"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Button
android:layout_marginRight="0dp"
android:background="@drawable/before"
android:layout_weight="1"
android:id="@+id/before"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/play"
android:background="@drawable/play"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:layout_marginLeft="0dp"
android:id="@+id/after"
android:background="@drawable/after"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
最后调用
setCustomBigContentView(remoteViews) //设置RemoteViews
- Android O以后的改变
/**
* android 8 以后要用
* android以后不能设置重要性
* 创建的通知Notification对象必须包含以下内容:
* 小图标,由 setSmallIcon()设置
* 标题,由 setContentTitle()设置
* 详细文本,由 setContentText()设置
* 有效的通知渠道 ID,由 setChannelId()设置(或者初始化的时候输入)
*/
String id="id";
//android 8 以后要用这个方法创建channel
NotificationChannel channel=new NotificationChannel(id,"name",NotificationManager.IMPORTANCE_HIGH);
NotificationManager manager=(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
channel.setDescription("指定用户在系统设置中看到的描述");
//设置channel
manager.createNotificationChannel(channel);
Notification m= new NotificationCompat.Builder(this,id)
.setContentTitle("这是标题")
.setContentText("这是内容")
.setSmallIcon(R.drawable.car)
.build()
;
manager.notify(1,m);
更新通知
要将通知设置为能够更新,请通过调用 NotificationManager.notify() 发出带有通知 ID 的通知。 要在发出之后更新此通知,请更新或创建 NotificationCompat.Builder 对象,从该对象构建 Notification 对象,并发出与之前所用 ID 相同的 Notification。如果之前的通知仍然可见,则系统会根据 Notification 对象的内容更新该通知。相反,如果之前的通知已被清除,系统则会创建一个新通知。
删除通知
除非发生以下情况之一,否则通知仍然可见:
用户单独或通过使用“全部清除”清除了该通知(如果通知可以清除)。
用户点击通知,且您在创建通知时调用了 setAutoCancel()。
您针对特定的通知 ID 调用了 cancel()。此方法还会删除当前通知。
您调用了 cancelAll() 方法,该方法将删除之前发出的所有通知。
启动 Activity 时保留导航
一般情况下,当用户点击我们的通知的会启动指定的Activity,但当用户按
back
键时会直接回到主屏幕。有时我们不想让它回到主屏幕而是回到指定Activity.有两种情况
1、常规 Activity
2、特殊 Activity : 仅当从通知中启动时,用户才会看到此 Activity。 从某种意义上说,Activity 是通过提供很难显示在通知本身中的信息来扩展通知。
通知中显示进度
- 显示限定形式的进度栏
效果图
String id="id3";
//android 8 以后要用这个方法创建channel
NotificationChannel channel=new NotificationChannel(id,"name3", NotificationManager.IMPORTANCE_HIGH);
NotificationManager manager=(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//设置channel
manager.createNotificationChannel(channel);
NotificationCompat.Builder builder= new NotificationCompat.Builder(this,id)
.setContentTitle("这是标题3")
.setContentText("正在下载")
.setSmallIcon(R.drawable.person);
new Thread(() -> {
for (int i=0;i<=100;i++){
builder.setProgress(100,i,false);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
manager.notify(3,builder.build());
}
//下载完成
builder.setContentText("下载完成");
//删除进度栏
builder.setProgress(0,0,false);
manager.notify(3,builder.build());
}).start();
显示非限定形式的
String id="id3";
//android 8 以后要用这个方法创建channel
NotificationChannel channel=new NotificationChannel(id,"name3", NotificationManager.IMPORTANCE_HIGH);
NotificationManager manager=(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//设置channel
manager.createNotificationChannel(channel);
NotificationCompat.Builder builder= new NotificationCompat.Builder(this,id)
.setContentTitle("这是标题3")
.setContentText("正在下载")
.setSmallIcon(R.drawable.person)
;
new Thread(() -> {
for (int i=0;i<=100;i++){
builder.setProgress(0,0,true);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
manager.notify(3,builder.build());
}
//下载完成
builder.setContentText("下载完成");
//删除进度栏
builder.setProgress(0,0,false);
manager.notify(3,builder.build());
}).start();
锁定屏幕通知
随着 Android 5.0(API 级别 21)的发布,通知现在还可显示在锁定屏幕上。您的应用可以使用此功能提供媒体播放控件以及其他常用操作。 用户可以通过“设置”选择是否将通知显示在锁定屏幕上,并且您可以指定您应用中的通知在锁定屏幕上是否可见。
设置可见性 您的应用可以控制在安全锁定屏幕上显示的通知中可见的详细级别。 调用 setVisibility() 并指定以下值之一:
VISIBILITY_PUBLIC 显示通知的完整内容。
VISIBILITY_SECRET 不会在锁定屏幕上显示此通知的任何部分。
VISIBILITY_PRIVATE 显示通知图标和内容标题等基本信息,但是隐藏通知的完整内容。
设置 VISIBILITY_PRIVATE 后,您还可以提供其中隐藏了某些详细信息的替换版本通知内容。例如,短信 应用可能会显示一条通知,指出“您有 3 条新短信”,但是隐藏了短信内容和发件人。要提供此替换版本的通知,请先使用 NotificationCompat.Builder 创建替换通知。创建专用通知对象时,请通过 setPublicVersion() 方法为其附加替换通知。
添加操作按钮
通知最多可以提供三个操作按钮,允许用户快速响应
String id="id4";
Intent intent=new Intent(this, NavigationActivity.class);
PendingIntent pendingIntent=PendingIntent.getActivity(this,0,intent,0);
//android 8 以后要用这个方法创建channel
NotificationChannel channel=new NotificationChannel(id,"name4", NotificationManager.IMPORTANCE_HIGH);
NotificationManager manager=(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//设置channel
manager.createNotificationChannel(channel);
Notification m= new NotificationCompat.Builder(this,id)
.setContentTitle("这是标题")
.setContentText("这是内容")
//设置按钮操作
.addAction(R.drawable.person,"联系人",pendingIntent)
.setSmallIcon(R.drawable.person)
.setAutoCancel(true)
.build()
;
manager.notify(4,m);
添加直接回复操作
Android 7.0(API级别24)中引入的直接回复操作允许用户直接在通知中输入文本,该通知会在不打开活动的情况下传送到您的应用。
String id="id5";
Intent intent=new Intent(this, NavigationActivity.class);
PendingIntent pendingIntent=PendingIntent.getActivity(this,0,intent,0);
RemoteInput remoteInput = new RemoteInput.Builder("key")//保存回复信息的key
.setLabel("回复")
.build();
NotificationCompat.Action action=new NotificationCompat.Action.Builder(R.drawable.person,"联系人",pendingIntent).addRemoteInput(remoteInput).build();
//android 8 以后要用这个方法创建channel
NotificationChannel channel=new NotificationChannel(id,"name5", NotificationManager.IMPORTANCE_HIGH);
NotificationManager manager=(NotificationManager) getSystemService(NOTIFICATION_SERVICE);
//设置channel
manager.createNotificationChannel(channel);
Notification m= new NotificationCompat.Builder(this,id)
.setContentTitle("这是标题")
.setContentText("这是内容")
.setSmallIcon(R.drawable.person)
.addAction(action)
.setAutoCancel(true)
.build()
;
manager.notify(5,m);
在NavigationActivity
中获取回复信息
Intent intent=getIntent();
Bundle remoteInput = RemoteInput.getResultsFromIntent(intent);
if (remoteInput != null) {
String key= remoteInput.getCharSequence("key").toString();
Log.d("============","值为"+key);
}
设置系统范围的类别
Android使用一些预定义的系统范围类别来确定当用户启用“ 请勿打扰”模式时是否使用给定通知打扰用户 。 如果您的通知属于中定义的预先定义的通知类别之一NotificationCompat-如
CATEGORY_ALARM, CATEGORY_REMINDER, CATEGORY_EVENT,或 CATEGORY_CAL
L-你应该通过合适的类别声明它,调用setCategory()。
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("My notification")
.setContentText("Hello World!")
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setCategory(NotificationCompat.CATEGORY_MESSAGE);
消息传递应用的最佳做法
见文档
添加大图像
要在通知中添加图像,请传递NotificationCompat.BigPictureStyleto 的实例 setStyle()
Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.new_post)
.setContentTitle(imageTitle)
.setContentText(imageDescription)
.setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(myBitmap))
.build();
要在折叠通知时使图像显示为缩略图(如图1所示),请调用setLargeIcon()并传递图像,但也可以调用 BigPictureStyle.bigLargeIcon()并传递它,null以便在扩展通知时大图标消失:
Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.new_post)
.setContentTitle(imageTitle)
.setContentText(imageDescription)
.setLargeIcon(myBitmap)
.setStyle(new NotificationCompat.BigPictureStyle()
.bigPicture(myBitmap)
.bigLargeIcon(null))//显示大图后,把大图标隐藏
.build();
效果如图(官方文档的图片)
添加大块文本
应用于NotificationCompat.BigTextStyle通知的扩展内容区域中的显示文本:
Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.new_mail)
.setContentTitle(emailObject.getSenderName())
.setContentText(emailObject.getSubject())
.setLargeIcon(emailObject.getSenderAvatar())
.setStyle(new NotificationCompat.BigTextStyle()
.bigText(emailObject.getSubjectAndSnippet()))
.build();
效果如图(官方文档的图)
创建收件箱样式的通知
NotificationCompat.InboxStyle如果要添加多个简短摘要行(例如来自传入电子邮件的摘要),请应用于通知。这允许您添加多个内容文本,每个内容文本被截断为一行,而不是由提供的一行连续文本 NotificationCompat.BigTextStyle。 要添加新行,请addLine() 最多调用6次。如果添加超过6行,则只显示前6行
Notification notification = new NotificationCompat.Builder(mContext, CHANNEL_ID)
.setSmallIcon(R.drawable.new_mail)
.setContentTitle("5 New mails from " + sender.toString())
.setContentText(subject)
.setLargeIcon(aBitmap)
.setStyle(new NotificationCompat.InboxStyle()
.addLine(messageSnippet1)
.addLine(messageSnippet2))
.build();
使用媒体控件创建通知
使用于NotificationCompat.MediaStyle显示媒体播放控件和跟踪信息。 调用addAction()多达五次,最多可显示五个独立的图标按钮。并使用 setLargeIcon()设置专辑封面。 与其他通知样式不同,MediaStyle还允许您通过指定也应出现在折叠视图中的三个操作按钮来修改折叠大小的内容视图。为此,请提供操作按钮索引setShowActionsInCompactView()。 如果通知表示活动的媒体会话,则还使用附加 MediaSession.Token到通知 setMediaSession()。Android然后将其识别为表示活动媒体会话的通知并相应地做出响应(例如,通过在锁定屏幕中显示专辑图片)。
Notification notification = new NotificationCompat.Builder(context, CHANNEL_ID)
// Show controls on lock screen even when user hides sensitive content.
.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)
.setSmallIcon(R.drawable.ic_stat_player)
// Add media control buttons that invoke intents in your media service
.addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0
.addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1
.addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2
// Apply the media style template
.setStyle(new NotificationCompat.MediaStyle()
.setShowActionsInCompactView(1 /* #1: pause button */)
.setMediaSession(mMediaSession.getSessionToken()))
.setContentTitle("Wonderful music")
.setContentText("My Awesome Band")
.setLargeIcon(albumArtBitmap)
.build();
效果如图(官方文档图片)
创建一组通知
从Android 7.0(API级别24)开始,您可以在组中显示相关通知
如果满足以下所有条件,则应使用通知组:
子通知是完整的通知,可以单独显示,而无需组摘要。
单独显示子通知有一个好处。例如: 它们是可操作的,具有针对每个通知的特定操作。 用户应该看到的每个通知中都有更多信息。
//要创建通知组,请为该组定义唯一标识符字符串。然后,对于组中所需的每个通知,只需调用setGroup(),传递组名称即可
String GROUP_KEY_WORK_EMAIL = "com.android.example.WORK_EMAIL";
Notification newMessageNotification = new NotificationCompat.Builder(MainActivity.this, CHANNEL_ID)
.setSmallIcon(R.drawable.new_mail)
.setContentTitle(emailObject.getSenderName())
.setContentText(emailObject.getSubject())
.setLargeIcon(emailObject.getSenderAvatar())
.setGroup(GROUP_KEY_WORK_EMAIL)
.build();
默认情况下,通知会根据发布时间进行排序,但您可以通过调用来更改顺序 setSortKey()。 如果通知组的警报应由其他通知处理,请调用setGroupAlertBehavior()。例如,如果您只希望组的摘要发出噪音,则组中的所有子项都应具有组警报行为GROUP_ALERT_SUMMARY。其他选项是GROUP_ALERT_ALL和GROUP_ALERT_CHILDREN。
设置组摘要
在Android 7.0(API级别24)及更高版本上,系统会使用每个通知中的文本片段自动为您的组构建摘要。用户可以展开此通知以查看每个单独的通知。要兼容旧版本,必须创建一个充当摘要的额外通知。这似乎是唯一的通知,系统隐藏了所有其他通知。因此,此摘要应包含来自所有其他通知的摘要,用户可以点按该摘要以打开您的应用。
要添加组摘要,请执行以下操作:
1、创建一个包含该组描述的新通知 – 通常最好使用收件箱样式通知。
2、通过调用将摘要通知添加到组中setGroup()。
3、通过调用指定它应该用作组摘要 setGroupSummary(true)。
//组的id
int SUMMARY_ID = 0;
String GROUP_KEY_WORK_EMAIL = "com.android.example.WORK_EMAIL";
Notification newMessageNotification1 =
new NotificationCompat.Builder(MainActivity.this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notify_email_status)
.setContentTitle(emailObject1.getSummary())
.setContentText("You will not believe...")
.setGroup(GROUP_KEY_WORK_EMAIL)
.build();
Notification newMessageNotification2 =
new NotificationCompat.Builder(MainActivity.this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_notify_email_status)
.setContentTitle(emailObject2.getSummary())
.setContentText("Please join us to celebrate the...")
.setGroup(GROUP_KEY_WORK_EMAIL)
.build();
Notification summaryNotification =
new NotificationCompat.Builder(MainActivity.this, CHANNEL_ID)
.setContentTitle(emailObject.getSummary())
//设置内容文本以支持运行API级别<24的设备
.setContentText("Two new messages")
.setSmallIcon(R.drawable.ic_notify_summary_status)
//使用InboxStyle模板
.setStyle(new NotificationCompat.InboxStyle()
.addLine("Alex Faarborg Check this out")
.addLine("Jeff Chang Launch Party")
.setBigContentTitle("2 new messages")
.setSummaryText("janedoe@example.com"))
.setGroup(GROUP_KEY_WORK_EMAIL)
//设置这个通知为组摘要
.setGroupSummary(true)
.build();
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(this);
notificationManager.notify(emailNotificationId1, newMessageNotification1);
notificationManager.notify(emailNotificationId2, newMessageNotification2);
notificationManager.notify(SUMMARY_ID, summaryNotification);
效果如图
打开通知通道设置
创建通知通道后,无法以编程方式更改通知通道的可视和听觉行为 – 只有用户可以从系统设置更改通道行为。可以Intent使用使用该ACTION_CHANNEL_NOTIFICATION_SETTINGS操作的通知通道打开系统设置
Intent intent = new Intent(Settings.ACTION_CHANNEL_NOTIFICATION_SETTINGS);
intent.putExtra(Settings.EXTRA_APP_PACKAGE, getPackageName());
intent.putExtra(Settings.EXTRA_CHANNEL_ID, myNotificationChannel.getId());
startActivity(intent);
删除通知渠道
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// channel的id
String id = "my_channel_01";
mNotificationManager.deleteNotificationChannel(id);
禁用通知点
从8.0(API级别26)开始,当关联的应用程序具有活动通知时,通知标记(也称为通知点)将显示在启动器图标上。用户可以长按应用程序图标以显示通知(以及任何应用程序快捷方式)。有时不需要通知点,就可以通过调用setShowBadge(false)禁用它们。
如图
String id = "my_channel_01";
CharSequence name = getString(R.string.channel_name);
String description = getString(R.string.channel_description);
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel mChannel = new NotificationChannel(id, name, importance);
mChannel.setDescription(description);
mChannel.setShowBadge(false);//禁用通知点
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.createNotificationChannel(mChannel);
设置自定义通知计数
默认情况下,每个通知都会增加长按菜单上显示的数字(如图所示),但可以为应用覆盖此数字.要设置自定义号码,请调用setNumber()
Notification notification = new NotificationCompat.Builder(MainActivity.this, CHANNEL_ID)
.setContentTitle("New Messages")
.setContentText("You've received 3 new messages.")
.setSmallIcon(R.drawable.ic_notify_status)
.setNumber(messageCount)
.build();
修改通知的长按菜单图标
长按菜单显示与通知关联的大图标或小图标(如果可用)。默认情况下,系统显示大图标,但您可以调用Notification.Builder.setBadgeIconType()并传入BADGE_ICON_SMALL常量以显示小图标。
Notification notification = new NotificationCompat.Builder(MainActivity.this, CHANNEL_ID)
.setContentTitle("New Messages")
.setContentText("You've received 3 new messages.")
.setSmallIcon(R.drawable.ic_notify_status)
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL)
.build();
为内容区域创建自定义布局
使用
NotificationCompat.DecoratedCustomViewStyle
创建自定义布局,可以仍使用系统装饰作为通知图标,时间戳,子文本和操作按钮。注意:如果您要为媒体播放控件创建自定义通知,请遵循相同的建议,但请使用 NotificationCompat.DecoratedMediaCustomViewStyle 该类
// 自定义布局
RemoteViews notificationLayout = new RemoteViews(getPackageName(), R.layout.notification_small);
RemoteViews notificationLayoutExpanded = new RemoteViews(getPackageName(), R.layout.notification_large);
//设置自定义布局
Notification customNotification = new NotificationCompat.Builder(context, CHANNEL_ID)
.setSmallIcon(R.drawable.notification_icon)
.setStyle(new NotificationCompat.DecoratedCustomViewStyle())
.setCustomContentView(notificationLayout)
.setCustomBigContentView(notificationLayoutExpanded)
.build();
请注意,通知的背景颜色可能因设备和版本而异。因此,应始终应用支持库样式,例如 TextAppearance_Compat_Notification 文本和 TextAppearance_Compat_Notification_Title 自定义布局中的标题。这些样式适应颜色变化,因此您不会最终使用黑底黑字或白底白字
<TextView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="1"
android:text="@string/notification_title"
android:id="@+id/notification_title"
style="@style/TextAppearance.Compat.Notification.Title" />
还要避免在RemoteViews对象上设置背景图像,因为文本可能变得不可读
创建完全自定义的通知布局
如果您不希望使用标准通知图标和标题装饰通知,请按照上述步骤使用setCustomBigContentView(),但不要调用setStyle()。
注意:建议不要使用未修改的通知,因为您的通知与其他通知不匹配,并且可能会在将不同样式应用于通知区域的不同设备上导致严重的布局兼容性问题。
Menu
参考:
1、blog.csdn.net/CodingEndin… 2.blog.csdn.net/yanzhenjie1…
Menu的分类
- 普通菜单
- 上下文菜单
- 弹出菜单
Menu的使用
普通菜单
- 在res目录下创建一个menu的文件夹
- 建立menu文件
编写代码:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<item android:id="@+id/build"
android:title="设置"
android:icon="@drawable/build"
app:showAsAction="always"
/>
<item android:id="@+id/edit"
android:title="编辑"
android:icon="@drawable/edit"
app:showAsAction="always"
/>
<item android:id="@+id/add"
android:title="增加"
>
</item>
<item android:id="@+id/delete"
android:title="删除"/>
</menu>
app:showAsAction ,这个属性是设置菜单该怎么显示,取值有5种,主要应用的有ifRoom、never、always 这三种, ifRoom 表示 如果上有显示空间就显示,如果没有空间就展示在menu里,never 表是总是显示在溢出菜单里,always 表示总是显示
- 分组显示
<?xml version="1.0" encoding="UTF-8" ?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
app:showAsAction="never"
android:title="黄大佬">
<menu>
<item android:id="@+id/java" android:title="java牛逼"></item>
<item android:id="@+id/php" android:title="php大佬"></item>
</menu>
</item>
<item
app:showAsAction="never"
android:title="我">
<menu >
<item android:id="@+id/java_me" android:title="java菜鸡"></item>
<item android:id="@+id/php_me" android:title="完全不会"></item>
</menu>
</item>
</menu>
在代码中处理
@Override
public boolean onCreateOptionsMenu(Menu menu) {
//在这里加载menu布局
MenuInflater inflater=getMenuInflater();
inflater.inflate(R.menu.navigation,menu);
return true;//必须返回true,否则不会显示菜单
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
//在这里处理点击事件
}
return super.onOptionsItemSelected(item);//这样可以避免activity中有Fragment且Fragment中也创建了menu时的冲突
}
图一
图二
动态改变menu
menu的创建顺序:onCreateOptionsMenu(Menu menu) —–> onPrepareOptionsMenu(Menu menu) 在创建好menu后,menu被始终作为打开状态,要改变menu,要调用
invalidateOptionsMenu()
方法,使系统调用onPrepareOptionsMenu(Menu menu)
,如何在onPrepareOptionsMenu(Menu menu)方法中就行相关的操作
boolean isChange=false;//判断menu是否需要改变
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){//在这里处理点击事件
case R.id.navigation_home:
isChange=true;
invalidateOptionsMenu();//请求系统调用onPrepareOptionsMenu(Menu menu)方法
break;
case 1://添加项的id
Toast.makeText(this, "执行", Toast.LENGTH_SHORT).show();
break;
}
return true;
}
/**
* 用来在运行时更新Menu
* @param menu 旧的menu,通过对他的操作来更新
* @return
*/
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if (isChange) {
menu.removeItem(R.id.navigation_home);//删除指定的menu的项
menu.findItem(R.id.navigation_notifications);//获取指定的MenuItem对象
menu.add(0,1,0,"hhhhhh");//添加新项
}
return super.onPrepareOptionsMenu(menu);
}
}
add方法说明
public abstract MenuItem add(int groupId, int itemId, int order,CharSequence title)
- groupId:菜单项的分组ID,该参数一般用于带选项按钮的菜单。参数值可以是负整数、0和正整数。
- itemId:当前添加的菜单项的ID。该参数值可以是负整数、0和正整数。
- order:菜单显示顺序,参数值必须是0和正整数,不能为负整数,采用从左到右,从上到下显示。
- title:字符串。
特殊用法
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
>
<item android:id="@+id/add"
android:title="购物"
app:showAsAction="always"
app:actionProviderClass="com.example.androidbase.Menu.MyActionProvider"
/>
<item android:title="搜寻"
android:id="@+id/delete"
app:showAsAction="ifRoom|collapseActionView"
android:icon="@drawable/search"
app:actionViewClass="android.support.v7.widget.SearchView"
//注意如果用app:actionViewClass 属性,而不是 android:actionViewClass,里面的类必须是依赖包下的,不然会报错
/>
</menu>
- 添加一个动作视图 : app:actionViewClass 实现动作的类
该showAsAction属性设置为 “ifRoom|collapseActionView”或 “never|collapseActionView”。该collapseActionView 标志指示当用户未与其进行交互时如何显示小部件:如果小部件位于应用栏上,则该应用应该将该小部件显示为图标。如果控件位于溢出菜单中,则应用程序应该将该控件显示为菜单项。当用户与操作视图交互时,它会展开以填充应用栏。
如图: 未点击时:使用图片
点击后:转化为SearchView
获取SearchView的实例
@Override
public boolean onCreateOptionsMenu(Menu menu) {
Log.d("==========","onCreate");
//在这里加载menu布局
MenuInflater inflater=getMenuInflater();
inflater.inflate(R.menu.pop_menu,menu);
MenuItem item=menu.findItem(R.id.delete);
//注意:用item.getActionView()获取的ActionProvider是为widget下的,不能转化为依赖包下的类
SearchView searchView=(SearchView)item.getActionView();//获取实例,可以通过这个实例对SearchView进行操作
return true;
}
app:actionProviderClass
- 自定义ActionProvider
/**
* 为了兼容ToolBar和ActionBar,应该检查v4包下的ActionProvider
*/
public class MyActionProvider extends ActionProvider implements View.OnClickListener{
private onMenuClickListener listener=null;
private TextView text=null;
public MyActionProvider(Context context) {
super(context);
}
@Override
public View onCreateActionView() {
View view= LayoutInflater.from(getContext()).inflate(R.layout.action_provider,null,false);//加载布局文件
view.setOnClickListener(this);
text=(TextView)view.findViewById(R.id.text);
return view;//返回View
}
@Override
public void onClick(View v) {
if (listener!=null)F
listener.onClick(v);
}
interface onMenuClickListener{
void onClick(View view);//点击事件
}
public void setListener(onMenuClickListener listener) {
this.listener = listener;
}
public void setValue(int value){
text.setText(value+"");
}
}
布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/bar_day"
>
<ImageView
android:src="@drawable/shop"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
<TextView
android:id="@+id/text"
android:layout_marginLeft="-10dp"
android:layout_marginTop="-5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/colorAccent"
/>
</LinearLayout>
效果如图
获取自定义ActionProvider的实例
@Override
public boolean onCreateOptionsMenu(Menu menu) {
Log.d("==========","onCreate");
//在这里加载menu布局
MenuInflater inflater=getMenuInflater();
inflater.inflate(R.menu.pop_menu,menu);
MenuItem item=menu.findItem(R.id.add);//获取有actionProviderClass属性的MenuItem
//用这种方法来加载依赖包下的ActionProvider
provider=(MyActionProvider) MenuItemCompat.getActionProvider(item);//获取自定义ActionProvider的实例
provider.setListener(new MyActionProvider.onMenuClickListener() {
@Override
public void onClick(View view) {
provider.setValue(i);//设置TextView中的值,图中为54
i++;
}
});
return true;
}
注意:如果要动态初始化设置value不应该在onCreateOptionsMenu()直接调用,因为ActionProvider还没有加载初始化完成,所以如果我们动态初始化设置value,需要在onWindowFocusChanged()中:
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
provider.setValue(5);
}
使用公有父类构建选项菜单(照抄)
如果应用包含多个Activity,且其中某些Activity具有相同的选项菜单,则可考虑创建一个仅实现onCreateOptionsMenu和 onOptionsItemSelected方法的Activity。然后,将这个Activity作为每个具有相同选项菜单的Activity的父类。通过这种方式,每个子类均会继承父类的菜单行为
父类Activity中的XML代码:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/option_menu_parent"
android:title="父类菜单项"/>
</menu>
父类Activity中的Java代码:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.option_menu_parent,menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.option_menu_parent:
Toast.makeText(this,"父类菜单项",Toast.LENGTH_SHORT).show();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
子类Activity中的XML代码:
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/option_menu_child"
android:title="子类菜单项"/>
</menu>
子类Activity中的Java代码:
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);//调用这一句保证父类的菜单项可以正常加载
getMenuInflater().inflate(R.menu.option_menu_child,menu);//加载子类自己的菜单项
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()){
case R.id.option_menu_child:
Toast.makeText(this,"子类菜单项",Toast.LENGTH_SHORT).show();
return true;
default:
return super.onOptionsItemSelected(item);//为了保证父类菜单项的点击行为可
//以被正确执行,当然,如果我们想要改变父类菜单项的行为,
//也可以在switch语句块中添加case进行重写。
}
}
Activity+Fragment构建的选项菜单
和Activity中加载Menu差不多
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.option_menu_fragment_2,menu);
}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setHasOptionsMenu(true);//这样才能让菜单显示出来
}
上下文菜单和上下文操作模式
通常上下文菜单是以浮动菜单的形式呈现的,用户长按(按住)一个支持上下文菜单的View时,菜单将以浮动列表的形式出现 步骤如下: 1.调用registerForContextMenu(View v)方法,注册需要和上下文菜单关联的View。如果将ListView或GridView作为参数传入,那么每个列表项将会有相同的浮动上下文菜单。(但是如果是RecyclerView则要就行特殊处理) 2.在Activity或Fragment中重写onCreateContextMenu方法,加载Menu资源 3.在Activity或Fragment中重写onContextItemSelected方法,实现菜单项的点击逻辑。
弹出菜单
PopupMenu是依赖View存在的模态菜单。如果空间足够,它将显示在相应View的下方,否则显示在其上方,当用户选择菜单项或触摸菜单以外的区域时,系统就会清除弹出菜单。可以将弹出菜单的使用拆分为以下四个步骤:
- 实例化PopupMenu,它的构造方法需要两个参数,分别为Context以及PopupMenu依赖的View对象。
- 使用MenuInflater将Menu资源加载到PopupMenu.getMenu()返回的Menu对象中。
- 调用setOnMenuItemClickListener方法为PopupMenu设置点击监听器。
- 调用PopupMenu.show()将弹出菜单显示出来。
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_menu);
button=(Button)findViewById(R.id.start);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
addPopMenu(v);
}
});
}
//创建PopMenu
private void addPopMenu(View view){
PopupMenu popupMenu=new PopupMenu(this,view);
getMenuInflater().inflate(R.menu.pop_menu,popupMenu.getMenu());
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
switch (item.getItemId()){
case R.id.add:
Toast.makeText(MenuActivity.this, "add", Toast.LENGTH_SHORT).show();
break;
case R.id.delete:
Toast.makeText(MenuActivity.this, "delete", Toast.LENGTH_SHORT).show();
break;
}
return false;
}
});
popupMenu.show();
}
效果图
系统状态栏和导航栏
调暗系统条
Android 4.0(API级别14)及更高版本上调暗系统栏(即状态和导航栏)
//调暗
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_LOW_PROFILE;
decorView.setSystemUiVisibility(uiOptions);
//显示
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(0);
效果图
隐藏状态栏
隐藏状态栏(以及可选的导航栏)使内容可以使用更多的显示空间,从而提供更加身临其境的用户体验。(Android 4.1(API级别16)及更高)
//隐藏
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
//建议隐藏ActionBar
ActionBar actionBar = getActionBar();
actionBar.hide();
//显示
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(0);
注意:
1、一旦UI标志被清除(例如,通过导航远离活动),如果您想再次隐藏栏,您的应用程序需要重新使用这个方法
2、在活动的onCreate()方法中隐藏系统栏 并且用户按下Home,系统栏将重新出现。当用户重新打开活动时,onCreate() 将不会被调用,因此系统栏将保持可见。如果您希望系统UI更改在用户导入和导出活动时保持不变,请在onResume() 或中设置UI标记 onWindowFocusChanged()
3、setSystemUiVisibility() 只有在您调用它的视图可见时,该方法才有效。 导航远离视图会导致标记设置setSystemUiVisibility() 被清除。
效果图(图中先隐藏了ActionBar)
使内容出现在状态栏后面
在Android 4.1及更高版本中,您可以将应用程序的内容设置为显示在状态栏后面,以便在状态栏隐藏和显示时不会调整内容的大小
//隐藏
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
//显示
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(0);
效果图
可以通过将android:fitsSystemWindows=true属性添加到XML布局文件来处理控件被遮盖的情况,这会调整父级的填充ViewGroup 以为系统窗口留出空间
未使用前
使用后
隐藏导航栏
隐藏导航栏使用
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
//隐藏了导航栏和状态栏
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_HIDE_NAVIGATION|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE ;
decorView.setSystemUiVisibility(uiOptions);
//显示
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(0);
效果图
使内容出现在导航栏后面
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN|View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION|View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decorView.setSystemUiVisibility(uiOptions);
//显示
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(0);
SYSTEM_UI_FLAG_LAYOUT_STABLE来帮助应用程序保持稳定的布局,一般要加上(上同)
Lean back
Lean back
模式是全屏模式之一,特点: 当用户想要恢复系统栏时,他们只需在任何地方点击屏幕。
//开启全屏模式
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN|View.SYSTEM_UI_FLAG_HIDE_NAVIGATION|View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decorView.setSystemUiVisibility(uiOptions);
效果图
Immersive
当用户需要带回系统栏时,从隐藏系统栏的任何边缘滑动
//开启全屏模式
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_IMMERSIVE|View.SYSTEM_UI_FLAG_FULLSCREEN|View.SYSTEM_UI_FLAG_HIDE_NAVIGATION|View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decorView.setSystemUiVisibility(uiOptions);
效果如图
Sticky immersive
如果用户使用系统栏从边缘滑动,系统栏会出现,但它们是半透明的
//开启全屏模式
View decorView = getWindow().getDecorView();
int uiOptions = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY|View.SYSTEM_UI_FLAG_FULLSCREEN|View.SYSTEM_UI_FLAG_HIDE_NAVIGATION|View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
decorView.setSystemUiVisibility(uiOptions);
效果如图
用代码取消全屏模式
//方式1
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(0);
//方式2
View decorView = getWindow().getDecorView();
decorView.setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN);
1、要在状态之间提供无缝转换,请保持所有UI控件的可见性与系统栏同步。一旦应用程序进入沉浸式模式,任何UI控件也应该与系统栏一起隐藏,然后在系统UI重新出现时再次出现。为此,请实现 View.OnSystemUiVisibilityChangeListener接收回调,如 响应UI可见性更改中所述。
2、实施onWindowFocusChanged()。如果获得窗口焦点,则可能需要重新隐藏系统栏。如果您失去了窗口焦点,例如由于应用程序上方显示的对话框或弹出菜单,您可能希望取消之前安排的任何待处理的“隐藏”操作Handler.postDelayed()或类似的操作。
3、实现GestureDetector检测 onSingleTapUp(MotionEvent),允许用户通过触摸您的内容手动切换系统栏的可见性。简单的单击侦听器不是最佳解决方案,因为即使用户在屏幕上拖动手指(假设单击目标占据整个屏幕),它们也会被触发。
4、注意: 使用该SYSTEM_UI_FLAG_IMMERSIVE_STICKY标志时,滑动会导致系统UI暂时显示为半透明状态,但不会清除任何标记,并且不会触发系统UI可见性更改侦听器。
响应UI可见性更改
注意:Sticky immersive模式下不会触发系统UI可见性更改侦听器
View decorView = getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener
(new View.OnSystemUiVisibilityChangeListener() {
@Override
public void onSystemUiVisibilityChange(int visibility) {
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
//系统栏显示
Log.d("============","系统栏显示");
} else {
//系统栏未显示
Log.d("===========","系统栏未显示");
}
}
});
Android Dialog
AlertDialog
1、 普通AlertDialog
//获取AlertDialog对象
AlertDialog.Builder alertDialog = new AlertDialog.Builder(DialogActivity.this);
//设置标题
alertDialog.setTitle("标题");
//设置内容
alertDialog.setMessage("这是内容");
//设置是否可以用back键退出
alertDialog.setCancelable(false);
//设置图标
alertDialog.setIcon(R.drawable.build);
//设置确定的事件
alertDialog.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(DialogActivity.this, "点击确定", Toast.LENGTH_SHORT).show();
}
});
//设置取消的事件
alertDialog.setNegativeButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(DialogActivity.this, "点击取消", Toast.LENGTH_SHORT).show();
}
});
//设置说明文字
alertDialog.setNeutralButton("详情", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(DialogActivity.this, "这是详情", Toast.LENGTH_SHORT).show();
}
});
//展示AlertView
alertDialog.show();
效果:
1、 单选AlertDialog
String[] data = {"选项1", "选项2", "选项3", "选项4", "选项5"};
AlertDialog.Builder alert = new AlertDialog.Builder(DialogActivity.this);
//设置标题
alert.setTitle("标题");
//设置单选框初始化时选中那个
int select = 1;
//设置单选的点击事件
alert.setSingleChoiceItems(data, select, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(DialogActivity.this, "选中项目" + i, Toast.LENGTH_SHORT).show();
}
});
alert.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(DialogActivity.this, "确定", Toast.LENGTH_SHORT).show();
}
});
alert.show();
效果
1、 多选AlertDialog
final String[] data = {"选择1", "选择2", "选择3", "选择4", "选择5"};
//确定每个选项的选择情况
boolean[] isSelect = {false, false, false, false, false};
AlertDialog.Builder alter = new AlertDialog.Builder(DialogActivity.this);
alter.setTitle("标题");
alter.setCancelable(false);
//设置多选点击事件
alter.setMultiChoiceItems(data, isSelect, new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i, boolean b) {
if (b) {
save.add(new Integer(i));
Toast.makeText(DialogActivity.this, "你选中了项目" + i + b, Toast.LENGTH_SHORT).show();
} else {
save.remove(new Integer(i));
Toast.makeText(DialogActivity.this, "没选中" + i + b, Toast.LENGTH_SHORT).show();
}
}
});
alter.setPositiveButton("确定", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
if (save.size() == 0) {
Toast.makeText(DialogActivity.this, "您什么都没选", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(DialogActivity.this, "确定", Toast.LENGTH_SHORT).show();
}
}
});
alter.show();
效果:
1、 列表AlertDialog
AlertDialog.Builder listDialog = new AlertDialog.Builder(DialogActivity.this);
//创建存储数据的数组
String[] data = {"列表1", "列表2", "列表3", "列表4"};
//显示列表,并为列表增加点击事件
listDialog.setItems(data, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
Toast.makeText(DialogActivity.this, "选中了列表" + i, Toast.LENGTH_SHORT).show();
}
});
listDialog.show();
效果
ProgressDialog
final ProgressDialog progressDialog = new ProgressDialog(DialogActivity.this);
progressDialog.setTitle("标题");
progressDialog.setMessage("这是内容");
progressDialog.setIcon(R.drawable.edit);
//设置进度条的样式
progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
//设置最大值
progressDialog.setMax(100);
progressDialog.setButton("取消", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
progressDialog.dismiss();
}
});
progressDialog.setCancelable(false);
progressDialog.show();
//通过setProgress(process)来跟新属性
效果
DatePickerDialog
DatePickerDialog date = new DatePickerDialog(DialogActivity.this);
date.setOnDateSetListener(new DatePickerDialog.OnDateSetListener() {
@Override
public void onDateSet(DatePicker datePicker, int i, int i1, int i2) {
Toast.makeText(DialogActivity.this, i + "年" + i1 + "月" + i2 + "日", Toast.LENGTH_SHORT).show();
}
});
date.show();
效果
TimePickerDialog
TimePickerDialog dialog=new TimePickerDialog(DialogActivity.this,
new TimePickerDialog.OnTimeSetListener() {
@Override
public void onTimeSet(TimePicker timePicker, int i, int i1) {
Toast.makeText(DialogActivity.this, i+"时"+i1+"秒", Toast.LENGTH_SHORT).show();
}
},c.get(Calendar.HOUR_OF_DAY),c.get(Calendar.MINUTE),true
);
dialog.show();
效果
Toast的使用
简单用法
Toast.makeText(context, "走路", Toast.LENGTH_SHORT).show();
自定义Toast
demo:
public class myToast {
Toast my;
View view;
public myToast(Context context,View view){
my=new Toast(context);
this.view=view;
}
public Toast getToast(){
my.setView(view);
return my;
}
}
使用:
View view=inflater.inflate(R.layout.toast,null);
myToast m=new myToast(context.this,view);
PopupWindow的使用
参考:www.jianshu.com/p/f809549b9…
www.jianshu.com/p/1a009b964…
- 获取布局
//获取布局
View contentView= LayoutInflater.from(this).inflate(R.layout.pop_windows,null);
- 创建一个PopupWindow
//这里ViewGroup.LayoutParams.MATCH_PARENT表示PopupWindow的宽度为match_parent
//这里ViewGroup.LayoutParams.WRAP_CONTENT表示PopupWindow的高度为mrap_content
popupWindow=new PopupWindow(contentView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
注意: 高度,和宽度是必须的,也可以不通过构造函数设置,使用setXXX()方法直接设置,如果在contentView中设置了具体的宽高,会被构造方法或setXXX()方法中设置的宽高代替
- 显示PopupWindow
//第一种方式:相对于父控件控制其位置,x,y表示其x,y方向的偏移量
//在Gravity.BOTTOM模式下,y才有效,正表示向上位移
popupWindow.showAtLocation(constraintLayout, Gravity.BOTTOM,0,0);
//第二种方法:在指定控件正下面
popupWindow.showAsDropDown(View view);
//第三种方法:在指定控件的下面,可以指定偏移量
popupWindow.showAsDropDown(View view,int x,int y);
- 其他方法
popupWindow.setTouchable(true);//设置是否接收事件
popupWindow.setFocusable(true);//设置是否获取焦点,一般用于在 popupWindow中存在EditView
//设置popupWindow以外的区域是否可点击,注意:
//1.popupWindow.setBackgroundDrawable(new BitmapDrawable());是必要的
//2.要放在showAtLocation()方法前面
popupWindow.setBackgroundDrawable(new BitmapDrawable());
popupWindow.setOutsideTouchable(true);
popupWindow.setAnimationStyle(int id);//设置动画
popupWindow.dismiss();//取消popupWindow
- demo
private void generatePop(){
//获取布局
View contentView= LayoutInflater.from(this).inflate(R.layout.pop_windows,null);
//设置点击事件
contentView.findViewById(R.id.java).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(PopWindowsActivity.this, "java", Toast.LENGTH_SHORT).show();
}
});
contentView.findViewById(R.id.C).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(PopWindowsActivity.this, "C", Toast.LENGTH_SHORT).show();
}
});
//创建一个PopupWindow,view ,height,width是必要的
popupWindow=new PopupWindow(contentView, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
//设置是否接收事件
popupWindow.setTouchable(true);
//设置是否获取焦点,一般用于在 popupWindow中存在EditView
popupWindow.setFocusable(true);
//设置popupWindow以外的区域是否可点击,注意:
//1.popupWindow.setBackgroundDrawable(new BitmapDrawable());是必要的
//2.要放在showAtLocation()方法前面
popupWindow.setBackgroundDrawable(new BitmapDrawable());
popupWindow.setOutsideTouchable(true);
//显示popupWindow
//第一种方式:相对于父控件控制其位置,x,y表示其x,y方向的偏移量
//在Gravity.BOTTOM模式下,y才有效,正表示向上位移
popupWindow.showAtLocation(constraintLayout, Gravity.BOTTOM,0,0);
//第二种方法:在指定控件正下面
//popupWindow.showAsDropDown(View view);
//第三种方法:在指定控件的下面,可以指定偏移量
//popupWindow.showAsDropDown(View view,int x,int y);
//设置动画
//popupWindow.setAnimationStyle();
}
设置透明背景
- popupWindows的大小设置为
wrap_content
- 设置窗口的透明度
/**
* 设置添加屏幕的背景透明度
*
* @param bgAlpha
*/
public void backgroundAlpha(float bgAlpha) {
WindowManager.LayoutParams lp = activity.getWindow().getAttributes();
lp.alpha = bgAlpha; //0.0-1.0
activity.getWindow().setAttributes(lp);
}
//启动PopupWindows时开启
backgroundAlpha(0.5f);
- 实现
PopupWindow.OnDismissListener
接口,监听PopupWindows的取消事件
动画
PopupWindow的动画显示效果是通过setAnimationStyle(int id)方法设置的,其中id为一个style的id,所以我们要在styles.xml文件中设置一个动画样式:
<style name="AnimationStyle" parent="Animation.Design.BottomSheetDialog">
<item name="android:windowEnterAnimation">@anim/dropdown_in</item>
<item name="android:windowExitAnimation">@anim/dropdown_out</item>
</style>
//dropdown_in
<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:duration="100" android:zAdjustment="top" android:interpolator="@android:interpolator/decelerate_quad" android:fillAfter="true" android:fillBefore="true" android:fromXScale="100%" android:fromYScale="0%" android:pivotX="0" android:pivotY="0" android:toXScale="100%" android:toYScale="100%" /></set>
//dropdown_out
<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android"> <scale android:duration="100" android:interpolator="@android:interpolator/accelerate_quad" android:fillAfter="true" android:fillBefore="true" android:fromXScale="100%" android:fromYScale="100%" android:pivotX="0" android:pivotY="0" android:toXScale="100%" android:toYScale="0%" /></set>
注意
1、 popupWindows中不能放Fragment,也不能用ViewPager
存放Fragment,不然会出现错误或者显示不了Fragment
设置
设置是使用在 XML 文件中声明的 Preference 类的各种子类构建而成,而不是使用 View 对象构建用户界面。Preference 对象是单个设置的构建基块。每个 Preference 均作为项目显示在列表中,并提供适当的 UI 供用户修改设置.每个 Preference 都有一个相应的键值对,可供系统用来将设置保存在应用设置的默认 SharedPreferences 文件中。当用户更改设置时,系统会为您更新 SharedPreferences 文件中的相应值。您只应在需要读取值以根据用户设置确定应用的行为时,才与关联的 SharedPreferences 文件直接交互.文件名是
包名+"_preferences"
,例如:com.example.newandroidbase_preferences 每个设置保存在 SharedPreferences 中的值可能是以下数据类型之一:
- 布尔值
- 浮点型
- 整型
- 长整型
- 字符串
- Set< String>
PreferenceFragment
在 Android 3.0以上谷歌推荐使用PreferenceFragment代替PreferenceActivity
- 加载preference.xml文件
public class SettingFragment extends PreferenceFragment {
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preference);//加载文件
}
}
- preference.xml文件
可以在运行时实例化新的 Preference 对象,不过还是应该使用 Preference 对象的层次结构在 XML 中定义设置列表。使用 XML 文件定义设置的集合是首选方法, 每个 Preference 子类均可以使用与类名(如 < CheckBoxPreference>)匹配的 XML 元素来声明。 您必须将 XML 文件保存在 res/xml/ 目录中。尽管您可以随意命名该文件,但它通常命名为 preferences.xml
<?xml version="1.0" encoding="UTF-8" ?>
//PreferenceScreen必须是根标签
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
>
<PreferenceCategory android:title="第一组">//用来定义组
<CheckBoxPreference
android:key="choose" //相对于 id
android:title="选择" //大标题
android:summary="小标题" //小标题
android:defaultValue="true" //设置默认值
/>
<EditTextPreference
android:text="编辑框"
android:key="edit"
android:defaultValue="编辑"
android:summary="副标题" //EditTextPreference中的大标题和小标题不兼容,只能有一个,两个都存在的话,用小标题
/>
<Preference android:title="只是一个文本框" android:summary="小标题" android:key="text">
<intent android:action="android.intent.action.VIEW" android:data="http://baidu.com"/>
</Preference>
</PreferenceCategory>
<PreferenceCategory android:title="第二组" android:key="second" android:summary="小标题" >
<ListPreference android:title="列表"
android:key="list"
android:entries="@array/list" //显示的值
android:entryValues="@array/value" //保存的值
android:summary="小标题"
android:defaultValue="1"
/>
</PreferenceCategory>
//PreferenceScreen中镶嵌 PreferenceScreen时,点击这个PreferenceScreen会再开启一个新的窗口
<PreferenceScreen android:title="设置组">
<SwitchPreference android:title="Switch" android:summary="小标题" android:key="switch" android:defaultValue="false"/>
<RingtonePreference
android:key="ringtone"
android:title="系统的铃声"
android:summary="小标题"
/>
<MultiSelectListPreference android:title="多选" android:key="multi"
android:entries="@array/muti_list"
android:entryValues="@array/muti_value"
android:summary="小标题"
/>
</PreferenceScreen>
</PreferenceScreen>
- array的文件
<?xml version="1.0" encoding="UTF-8" ?>
<resources>
<!--显示的值-->
<string-array name="list">
<item>a</item>
<item>b</item>
<item>c</item>
<item>d</item>
</string-array>
<!--真正保存的值-->
<string-array name="value">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</string-array>
<string-array name="muti_list">
<item>江西</item>
<item>重庆</item>
<item>杭州</item>
<item>北京</item>
</string-array>
<string-array name="muti_value">
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
</string-array>
</resources>
CheckBoxPreference
SwitchPreference
ListPreference
MultiSelectListPreference
多个PreferenceScreen嵌套
点击设置组
- 将此片段添加到 Activity(跟添加普通的Fragment一样)
getFragmentManager().beginTransaction()
.add(R.id.content, new SettingFragment())
.commit();
设置默认值
创建的首选项可能会为应用定义一些重要行为,因此在用户首次打开应用时,您有必要使用每个 Preference 的默认值初始化相关的 SharedPreferences 文件。 首先,您必须使用 android:defaultValue 属性为 XML 文件中的每个 Preference 对象指定默认值。该值可以是适合相应 Preference 对象的任意数据类型,再到Activity的 onCreate() 方法调用 setDefaultValues()方法
PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false);
此方法采用三个参数:
- 应用 Context。
- 要为其设置默认值的首选项 XML 文件的资源 ID。
- 一个布尔值,用于指示是否应该多次设置默认值。 如果该值为 false,则仅当过去从未调用此方法时(或者默认值共享首选项文件中的 KEY_HAS_SET_DEFAULT_VALUES为 false 时),系统才会设置默认值。即在应用卸载之前只会调用一次
注意:只要将第三个参数设置为 false,您便可在每次启动 Activity 时安全地调用此方法,而不必通过重置为默认值来替代用户已保存的首选项。 但是,如果将它设置为 true,则需要使用默认值替代之前的所有值。
创建标头文件
用来适配大屏的设备
读取首选项
默认情况下,应用的所有首选项均保存到一个可通过调用静态方法 PreferenceManager.getDefaultSharedPreferences() 从应用内的任何位置访问的文件中。 这将返回 SharedPreferences 对象,其中包含与 PreferenceActivity 中所用 Preference 对象相关的所有键值对
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
Set<String> strings = sharedPref.getStringSet("multi", null);
boolean choose=sharedPref.getBoolean("choose",false);
String edit=sharedPref.getString("edit","没有");
String value=sharedPref.getString("list","没有");
Log.d("====","多选的值为:"+strings.toString()+" 选择的值为:"+choose+" 编辑栏的值为:"+edit+" 列表的值:"+value);
侦听首选项变更
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
//注册监听器,记得要在不使用时 注销
sharedPref.registerOnSharedPreferenceChangeListener((preferences,key)->{
if (key.equals("choose")){
Log.d("=======","选择改变为:"+preferences.getBoolean(key,false));
}
});
注意:目前,首选项管理器不会在您调用 registerOnSharedPreferenceChangeListener() 时存储对侦听器的强引用。 但是,您必须存储对侦听器的强引用,否则它将很容易被当作垃圾回收。 我们建议您将对侦听器的引用保存在只要您需要侦听器就会存在的对象的实例数据中。
例如,在以下代码中,调用方未保留对侦听器的引用。 因此,侦听器将容易被当作垃圾回收,并在将来某个不确定的时间失败:
prefs.registerOnSharedPreferenceChangeListener(
// Bad! The listener is subject to garbage collection!
new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// listener implementation
}
});
有鉴于此,请将对侦听器的引用存储在只要需要侦听器就会存在的对象的实例数据字段中:
SharedPreferences.OnSharedPreferenceChangeListener listener =
new SharedPreferences.OnSharedPreferenceChangeListener() {
public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
// listener implementation
}
};
prefs.registerOnSharedPreferenceChangeListener(listener);
构建自定义Preference
public class MyPreference extends DialogPreference {
String DEFAULT_VALUE="默认值";
String value=null;
EditText text=null;
public MyPreference(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public MyPreference(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init(){
setDialogLayoutResource(R.layout.my_preference);
setPositiveButtonText("确定");//设置dialog的PositiveButton的值
setNegativeButtonText("取消");//设置dialog的NegativeButton的值
}
//在dialog关闭时调用,true表示用户确定,false表示用户取消
@Override
protected void onDialogClosed(boolean positiveResult) {
if (positiveResult){//用户确定保存
Log.d("==============","执行");
if (text!=null){
value=text.getText().toString();
persistString(value);//保存值,注意如果value为null,则不会保存
}
}
}
/**
* 创建Dialog的布局
* @return
*/
@Override
protected View onCreateDialogView() {
View view=super.onCreateDialogView();
if (view!=null){
text= view.findViewById(R.id.edit);
}
return view;
}
@Override
protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) {
if (restorePersistedValue) {//true,表示不是默认值,defaultValue为null
//获取当前值,如果没有就获取到 DEFAULT_VALUE 这个默认值
String mCurrentValue = this.getPersistedString(DEFAULT_VALUE);
Log.d("===========","mCurrentValue :"+mCurrentValue);
} else {//false,表示是默认值,
String mCurrentValue = (String) defaultValue;//获取默认值
persistString(mCurrentValue);//把默认值写入文件中
}
}
//解析preference.xml中defaultValue的值,并返回其值
@Override
protected Object onGetDefaultValue(TypedArray a, int index) {
return a.getString(index);
}
/**
* 保存数据
* @return
*/
@Override
protected Parcelable onSaveInstanceState() {
final Parcelable superState = super.onSaveInstanceState();
if (isPersistent()) {//不清楚
return superState;
}
final SavedState myState = new SavedState(superState);
myState.value = value;
return myState;
}
/**
* 获取保存的数据
* @param state
*/
@Override
protected void onRestoreInstanceState(Parcelable state) {
if (state == null || !state.getClass().equals(SavedState.class)) {//如果不是我们自定义的SavedState就退出
// Didn't save state for us in onSaveInstanceState
super.onRestoreInstanceState(state);
return;
}
SavedState myState = (SavedState) state;
super.onRestoreInstanceState(myState.getSuperState());//不清楚这一步的作用
text.setText(myState.value);
}
/**
* 序列化要保存的数据
*/
private static class SavedState extends BaseSavedState {
String value;
public SavedState(Parcelable superState) {
super(superState);
}
public SavedState(Parcel source) {
super(source);
// 获取数据
value = source.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
// 写入数据
dest.writeString(value);
}
public static final Parcelable.Creator<SavedState> CREATOR =
new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}
preference文件中
<com.example.newandroidbase.Setting.MyPreference
android:title="我的"
android:defaultValue="哈哈"
android:key="me"
android:summary="小标题" />
布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>