二、TextView
使用Spannable
设置复合文本
Spannable 对文字的编辑减少TextView的拼接并且达到改变一串字符中的部分文字的字体颜色,背景颜色,字体大小,样式 增加文字的超链接。
如:核心代码
具体主要介绍setSpan()
中的what
和flags
参数:
1、Object what
主要是对文字的一些属性的设置,大小,颜色,已经文字的背景颜色:
BackgroundColorSpan
:背景颜色
ClickableSpan
:设置可点击的部分文字
ForegroundColorSpan
:* 改变文字字体的颜色*
MaskFilterSpan(MaskFilter filter)
:修饰效果,如模糊(BlurMaskFilter
)、浮雕(EmbossMaskFilter
)
MetricAffectingSpan
直接子类: AbsoluteSizeSpan
, LocaleSpan
, RelativeSizeSpan
, ReplacementSpan
, ScaleXSpan
, StyleSpan
, SubscriptSpan
, SuperscriptSpan
, TextAppearanceSpan
, TypefaceSpan
间接子类: DynamicDrawableSpan
, ImageSpan
RasterizerSpan
:光栅效果
StrikethroughSpan
:中划线 (删除线)
SuggestionSpan
:占位符
UnderlineSpan
:下划线AbsoluteSizeSpan
绝对大小(文本字体)DynamicDrawableSpan
设置图片,基于文本基线或底部对齐。
ImageSpan
:图片包裹,可以在一段文字中添加一个图片RelativeSizeSpan
相对大小(文本字体)ReplacementSpan
父类,一般不用
SubscriptSpan
:下标- 数学公式 用到
SuperscriptSpan
:上标-数学公式会用到
ScaleXSpan(3.8f)
: 基于x轴的缩放
StyleSpan(Typeface.BOLD_ITALIC)
:字体样式:粗体、斜体等
TextAppearanceSpan(this, [Android](http://lib.csdn.net/base/android).R.style.TextAppearance_Medium)
: 文本外貌(包括字体、大小、样式和颜色)
TypefaceSpan("monospace")
:文本字体
URLSpan("http://orgcent.com")
:文本超链接
2、flags:对一段文字设置一些标识
如:
Spannable.SPAN_EXCLUSIVE_INCLUSIVE
SPAN_COMPOSING
:临时申请流行的一个输入法的构成的文本信息 标示:该段文字可以被删除或者替换SPAN_EXCLUSIVE_EXCLUSIVE
:包括内容不会扩大,只会包含起点和终点,长度绝对不能为0,如果被删除的所有它们涵盖文字也会自动从缓冲区中删除。SPAN_EXCLUSIVE_INCLUSIVE
:可以扩大的跨度,非0,包括文字插入他们的终点,而不是在他们的出发点,如果为0的时候就像一个点。SPAN_INCLUSIVE_EXCLUSIVE
:可以扩大的跨度,非0,包括文字插入他们的出发点,而不是在他们的终点,如果为0的时候就像一个点。SPAN_INCLUSIVE_INCLUSIVE
:跨度类型的扩大包括文本插入他们的起点或终点。SPAN_INTERMEDIATE
:标志将被设置为中间跨度的变化,这意味着有保证是另一个变化。通常情况下,它是用于选择自动使用与第一偏移量被更新时选择设置。SPAN_MARK_MARK
:像文本标记长度为0的跨度与SPAN_MARK_MARK
型:他们仍然在原来的偏移该偏移处插入文本时SPAN_MARK_POINT
:SPAN_MARK_POINT==SPAN_INCLUSIVE_INCLUSIVE
SPAN_PARAGRAPH
:必须是它的端点的缓冲区或后立即\ n字符的开始或结束,而如果它被删除,锚,端点被拉到下一个的\ n \ñ如下中的缓冲区(或结束缓冲区)。 即以换行符为起始点和终点。
SPAN_POINT_MARK
:SPAN_POINT_MARK==SPAN_EXCLUSIVE_EXCLUSIVE
。
SPAN_POINT_MARK_MASK
:
SPAN_POINT_POINT
:长度为0的跨度型SPAN_POINT_POINT
像游标:它们被插入文本被插入时,在其偏移的长度推进。在概念上的文字插入点之前。SPAN_PRIORITY
:指定由SPAN_PRIORITY
位图确定的顺序变更通知 - 较高的数字先走。你可能并不需要设置的,通常使用在当文本变化的时候,能有机会更新数据,在其他回调的方法里面进行处理之前。
SPAN_PRIORITY_SHIFT
:确定的顺序变更通知 - 较高的数字先走。你可能并不需要设置的,通常使用在当文本变化的时候,能有机会更新数据 在其他回调的方法里面进行处理之前。
SPAN_USER
:指定由SPAN_USER
位域是呼叫者使用存储相关的标量数据与他们的跨度对象。
SPAN_USER_SHIFT
:位编号SPAN_USER_SHIFT
的,可用于呼叫者使用存储相关的标量数据与他们的跨度对象。
三、实现跑马灯效果的TextView
1、走马灯的效果主要是通过android:singleLine,android:ellipsize,android:marqueeRepeatLimit,android:focusable
属性来配置的。
分析 :
-
android:singleLine=true
表示使用单行文字,多行文字也就无所谓使用Marquee
效果了。
-
android:marqueeRepeatLimit
:设置走马灯滚动的次数。
-
android:ellipsize
:设置了文字过长时如何切断文字,可以有none
,start
,middle
,end
, 如果使用走马灯效果则设为marquee
.
-
android:focusable
,Android的缺省行为是在控件获得Focus
时才会显示走马灯效果
注意:
显示跑马灯效果的前提条件就是你的文本内容要比显示文本的外部组件长,即外部组件无法完整的显示内部的文本内容。
2、因此要实现跑马灯效果有两种设置方式:
-
a、
layout_width=”"
设置为成比文本内容短的固定值,最好不要写成wrap_content
或者fill_parent
。
-
b、如果
layout_width=”"
设置wrap_content
或者fill_parent
,那么可以增加上android:paddingLeft="15dip"
,android:paddingRight="15dip"
使两端的距离加大而无法全部显示文本内容,但是这有一个缺陷就是在手机的屏幕变大时,距离没有变大,外部组件又可以正常显示内部文本,于是又无法显示跑马灯效果,因此建议第一种设置为佳。
3、另外还可以设置滚动的次数:
android:marqueeRepeatLimit=”"
;android:marqueeRepeatLimit=”marquee_forever”
表示一直滚动。
当有些情况下需要是文字一直滚动以引起用户注意,这是可以使用派生TextView
,重载onFocusChanged
,onWindowFocusChanged
,isFocused
这三个方法。
修改一下本例,添加一个ScrollAlwaysTextView类:
四、杀掉当前进程(当退出当前应用程序时,即:执行销毁所有活动后)代码,确保程序完全退出!!!
其中:
killProcess()
方法用于杀掉一个进程,它接收一个进程id
参数,我们可以通过myPid()
方法来获取当前程序的id
。需要注意的是,killProcess()
方法只能用于杀掉当前程序的进程,我们不能使用这个方法杀掉其他程序
五、使应用程序完全移除虚拟机(可以移除隐藏的多线程)
步骤:
首先,需要一个NotificationManager
来对通知进行管理,可以调用Context
的getSystemService()
方法获取到。getSystemService()
方法接收一个字符串参数用于确定获取系统的哪个服务,这里传入Context.NOTIFICATION_SERVICE
即可,获取NotificationManager
的实例。
接下来需要使用一个Builder
构造器来创建Notification
对象,但问题在于,几乎Android
系统的每一个版本都会对通知这部分功能进行或多或少的修改,API不稳定问题在通知上面突显得尤为严重。而,support
库中提供的兼容API。support-v4
库中提供一个NotificationCompat
类,使用这个类的构造器来创建Notification
对象,就可以保证我们的程序在所有Android系统版本上都能正常工作。代码如下:
当然,上述代码只是创建了一个空的Notification
对象,并没有什么实际作用,我们可以在最终的build()方法之前连缀任意多的设置方法来创建一个丰富的Notification对象,如下
以上工作完成之后,只需要调用NotificationManager
的notify()
方法就可以让通知显示出来。notify()
方法接收两个参数,第一个参数是id,要保证为每个通知所指定的id都是不同的。第二个参数是Notification
对象,这里直接将我们刚刚创建好的Notification
对象传入即可。因此,显示一个通知就可以写成:
到此已经完成了通知栏的创建!!!
实现点击功能:
使用PendingIntent
,和Intent
有些类似。都可以用于启动活动、启动服务以及发送广播等。Intent
更加倾向于去立即执行某个动作,而PendingIntent
更加倾向于在某个适合的时机去执行某个动作。
PendingIntent
,主要提供了几个静态方法用于获取PendingIntent
的实例,可以根据需求来选择是使用getActivity()
方法、getBroadcast()
、还是getService()
方法。这几个方法所接受的参数都是相同的,第一个参数Context;第二个参数一般用不到,通常都是0即可;第三个参数是一个Intent
对象,我们可以通过这个对象构建出PendingIntent
的"意图";第四个参数用于确定PendingIntent
的行为,有FLAG_ONE_SHOT
、FLAG_NO_CREATE
、FLAG_CANCEL_CURRENT
和FLAG_UPDATE_CURRENT
这4中值可选,通常情况下这个参数传入0就行了。
我们可以在NotificationCompat.Builder
。这个构造器还可以连缀一个setContentIntent()
方法,接收到参数正是一个PendingIntent
对象。当用户点击这条通知时就会执行相应的逻辑。核心如下:
一种是在NotificationCompat.Builder
中再连缀一个setAutoCancel()
方法。
另一种调用NotificationManager
的cancel()
方法取消,即,manager.cancel(1);
通知的进阶技巧:
1、使用音频提示用户通知到来:
setSound(Uri.fromFile(new File("/system/media/audio/ringtones/Luna.ogg")))
2、使用震动提示用户通知到来:
使用vibrate
这个属性。它是一个长整形的数据,用于设置手机静止和震动的时长,以毫秒为单位。下标为0的值表示手机静止的时长,下标为1的值表示手机震动的时长,下标为2 的值表示手机静止的时长,以此类推。所以,如果想要让手机在通知到来的时候立刻震动1秒,然后静止1秒,再震动1秒,代码就可以写成:
setVibrate(new long[]{0,1000,1000,1000})
不过,想要控制手机震动还需要声明权限,
<use-permission android:name="android.permission.VIBRATE"/>
3、使用LED灯提示用户通知到来:
使用setLights()
方法。有3个参数,第一个参数用于指定LED灯的颜色,第二个参数用于指定LED灯亮的时长,以毫秒值为单位,第三个参数用于指定LED灯暗去的时长,也是以毫秒值为单位。
.setLights(Color.GREEN,1000,1000)
4、你也可以直接使用通知的默认效果,它会根据当前手机的环境来决定播放什么铃声,以及如何震动。
setDefaults(NotificationCompat.DEFAULT_ALL)
注意:以上所涉及的这些进阶技巧都要在手机上运行才能看到效果,模拟机无法实现!!!
通知的高级功能:
1、setStyle()
方法。
可以实现显示多行文字:
setStyle(new NotificationCompat.BigTextStyle().bigText("长文字信息"))
可以显示一张大图片:
2、setPriority()
方法:
用于设置这条通知的重要程度:一共5个常量可选:
PRIORITY_DEFAULT
:表示默认的重要程度,和不设置效果是一样的;
PRIORITY_MIN
:表示最低的重要程度,系统可能只会在特定的场景才显示这条通知,比如用户下拉状态栏的时候;
PRIORITY_LOW
:表示较低的重要程度,系统可能会将这类通知缩小,或改变其显示的顺序,将其排在比较靠前 的位置;
PRIORITY_MAX
:表示最高的重要程度,这类通知消息必须要让用户立刻看到,甚至需要用户做出响应的操作。
如:
核心代码:
public class MainActivity extends AppCompatActivity{
public static final int TAKE_PHOTO=1;
privte ImageView picture;
private Uri imageUri;
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Button takePhoto=(Button)findViewById(R.id.take_photo);//打开摄像头按钮
picture=(ImageView)findViewById(R.id.picture);//显示图片
takePhoto.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
//创建File对象,用于存储拍照后的照片
//应用关联缓存目录,就是SD卡中专门用于存放当前应用缓存数据的位置,
//调用getExternalCacheDir()方法可以获得这个目录,具体路径:
// /sdcard/Android/data/<package name>/cache
//Android 6.0系统以后,只有应用关联目录不需要进行运行时权限处理,
//其他都是需要!!!
File outputImage=new File(getExternalCacheDir(),"output_image.jpg");
try{
if(outputImage.exists()){
outputImage.delete();
}
outputImage.createNewFile();
}catch(IOException e){
e.printStackTrace();
}
//运行设备的系统版本不低于Android 7.0,调用FileProvider的getUriForFile()方法
//将File对象转换成一个封装过的Uri对象。getUriForfile()方法接收3个参数,第一个参数
//为Context对象,第二个参数可以是任意唯一的字符串,第三个参数则是我们刚刚创建的
//File对象。之所以要进行这样一层转换,是因为从Android7.0系统开始,直接使用本地真实
//路径的Uri被认为是不安全的,会抛出一个FileUriExposedException异常。而
//FileProvider则是一种特殊的内容提供器,它使用了和内容提供器类似的机制来对数据进行
//保护,可以选择地将封装的Uri共享给外部,从而提高了应用的安全性。
//而低于Android7.0版本的,就调用Uri的fromFile()方法将File对象转换成Uri对象,这个
//Uri对象标识着output_image.jpg这张图片的本地真实路径。
if(Build.VERSION.SDK_INT >= 24){//系统版本不低于Android 7.0
imageUri=FileProvider.getUriForFile(MainActivity.this,
"com.example.cameraalbumtest.fileprovider",outputImage);
}else{//系统版本低于Android 7.0
imageUri=Uri.fromFile(outputImage);
}
//启动相机程序
Intent intent=new Intent("android.media.action.IMAGE_CAPTURE");
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri);//设置图片保存的位置
startActivityForResult(intent,TAKE_PHOTO);
}
});
}
protected void onActivityResult(int requestCode,int resultCode,Intent data){
switch(requestCode){
case TAKE_PHOTO:
if(resultCode==RESULT_OK){
try{
//将拍摄的照片显示出来
Bitmap bitmap=BitmapFactory.decodeStream(getContentResolver().
openInputStream(imageUri));
picture.setImageBitmap(bitmap);
}catch(FileNotFoundException e){
e.printStackTrace();
}
}
break;
default:
break;
}
}
}
注意:
上述提到了内容提供器,那么我们自然要在AndroidManifest.xml中对内容提供器进行注册,如下:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="com.example.cameraalbumtest.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths"/>
</provider>
其中,android:name 属性的值是固定的,android:authorities 属性的值必须要和刚才FileProvider.getUriForFile()方法中的第二个参数一致。另外,这里还在<provider>标签的内部使用<meta-data>来指定Uri的共享路径,并引用了一个@xml/file_paths资源。当然,这个资源现在还是不存在的,下面我们就来创建它。
右击res目录——>New——>Directory,创建一个xml目录,接着右击xml目录——>New——>File,创建一个file_paths.xml文件。然后修改file_paths.xml文件中的内容,如下:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="my_images" path="" />
</paths>
其中,external-path就是用来指定Uri共享的,name属性的值可以随便填,path属性的值表示共享的具体路径。这里设置空值就表示将整个SD卡进行共享,当然你也可以仅共享我们存放output_image.jpg这张图片的路径。
另外还有一点要注意,在Android 4.4系统之前,访问SD卡的应用关联目录也是要声明权限的,从4.4系统开始不再需要权限声明。那么我们为了能够兼容老版本系统的手机,还需要在AndroidManifest.xml中声明一个访问SD卡的权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
到此代码就编写完成!!!
八、从相册中选择照片
点击按钮,给控件ImageView设置图片,核心代码:
public class MainActivity extends AppCompatActivity{
...
public static final int CHOOSE_PHOTO=2;
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//打开本地图库
Button chooseFromAlbum=(Button)findViewById(R.id.choose_from_album);
...
chooseFromAlum.setOnClickListener(new View.OnClickListener(){
public void onClick(View v){
//判断是否没有获取权限
if(ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE)!=PackageManager.
PERMISSION_GRANTED){
ActivityCompat.requestPermissions(MainActivity.this,new
String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},1);
}else{
openAlbum();
}
}
});
}
private void openAlbum(){
Intent intent=new Intent("android.intent.action.GET_CONTENT");
intent.setType("image/*");
startActivityForResult(intent,CHOOSE_PHOTO);//打开相册
}
public void onRequestPermissionsResult(int requestCode,String[] permission,
int[] grantResults){
switch(requestCode){
case 1:
if(grantResults.length > 0 && grantResults[0]==PackageManager.
PERMISSION_GRNTED){
openAlbum();
}else{
Toast.makeText(this,"You denied the permission",Toast.LENGTH_SHORT).
show();
}
break;
default:
break;
}
}
//Android系统从4.4版本开始,选择相册中的图片不再返回图片真实的Uri了,而是一个封装过的Uri,
//因此如果是4.4版本以上的手机就需要对这个Uri进行解析才行。
protected void onActivityResult(int requestCode,int resultCode,Intent data){
switch(requestCode){
...
case CHOOSE_PHOTO:
if(resultCode== RESULT_OK){
//判断手机系统版本号
if(Build.VERSION.SDK_INT>=19){
//4.4及以上系统使用这个方法处理图片
handleImageOnKitKat(data);
}else{
//4.4以下系统使用这个方法处理图片
handleImageBeforeKitKat(data);
}
}
break;
default:
break;
}
}
//4.4及以上系统使用这个方法处理图片
//这里有好几种判断:如果返回的Uri是document类型的话,那就取出document id进行处理,如果
//不是,那就使用普通的方式处理。另外,如果Uri的authority是media格式的话,document id
//还需要再进行一次解析,要通过字符串分割的方式取出后半部分才能得到真正的数字id。取出的id
//用于构建新的Uri和条件语句,然后把这些值作为参数传入到getImagePath()方法当中,就可以获取到
//图片的真实路径了。拿到图片的路径之后,再调用displayImage()方法将图片显示到界面上。
(19)
private void handleImageOnKitKat(Intent data){
String imagePath=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;
imagePath=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));
imagePath=getImagePath(contentUri,null);
}
}else if("content".equalsIgnoreCase(uri.getScheme())){
//如果是content类型的Uri,则使用普通方式处理
imagePath=getImagePath(uri,null);
}else if("file".equalsIgnoreCase(uri.getScheme())){
//如果是file类型的Uri,直接获取图片路径即可
imagePath=uri.getPath();
}
displayImage(imagePath);//根据图片路径显示图片
}
//4.4以下系统使用这个方法处理图片
private void handleImageBeforeKitKat(Intent data){
Uri uri=data.getData();
String imagePath=getImagePath(uri.null);
displayImage(imagePath);
}
private String getImagePath(Uri uri,String selection){
String path=null;
//通过Uri和selection来获取真实的图片路径
Cursor cursor=getContentResolver().query(uri,null,selection,null,null);
if(cursor != null){
if(cursor.moveToFirst()){
path=cursor.getString(cursor.getColumnIndex(
MediaStore.Images.Media.DATA));
}
cursor.close();
}
return path;
}
private void displayImage(String imagePath){
if(imagePath != null){
Bitmap bitmap=BitmapFactory.decodeFile(imagePath);
picture.setImageBitmap(bitmap);
}else{
Toast.makeText(this,"failed to get image" , Toast.LENGTH_SHORT).show();
}
}
}
注意:配置权限,还有加载有些图片即使经过裁剪后体积仍然很大,直接加载到内存中有可能会导致程序崩溃。更好的做法是根据项目的需求先对图片进行适当的压缩,然后再加载到内存中。(第二部分有对图片的处理代码)。
到此代码就编写完成!!!
九、播放音频
Android中播放音频文件一般都是使用MediaPlayer类来实现的,常用的控制方法有:
setDataSource();//设置要播放的音频文件的位置
prepare(); //在开始播放之前调用这个方法完成准备工作
start(); //开始或继续播放播放音频
pause(); //暂停播放音频
reset(); //将MediaPlayer对象重置到刚刚创建的状态
seekTo(); //从指定的位置开始播放音频
stop(); //停止播放音频。调用这个方法后的MediaPlayer对象无法再播放音频
release(); //释放掉与MediaPlayer对象相关的资源
isPlaying(); //判断当前MediaPlayer是否正在播放音频
getDuration(); //获取载入的音频文件的时长
播放音频文件的核心代码:
MediaPlayer player=new MediaPlayer();//1、创建MediaPlayer对象
player.setDataSource("音频文件的路径");//2、设置音频文件的路径
player.prepare(); //3、进入准备状态
player.start(); //4、开始播放音频(1——4必须按循序)
player.pause(); //暂停播放
player.reset(); //停止播放
注意:播放完音频注意释放资源
if(player!=null){
player.stop();//停止播放
player.release();//释放资源
}
同时,注意配置权限
<users-permission android:name="android:permission.WRITE_EXTERNAL_STORAGE"
十、播放视频
主要使用VideoView类来实现的。这个类将视频的 显示 和控制 集于一身,常用的控制方法有:
setVideoPath(); //设置要播放的视频文件的位置
start(); //开始或继续播放视频
pause(); //暂停播放视频
resume(); //将视频重头开始播放
seekTo(); //从指定的位置开始播放视频
isPlaying(); //判断当前是否正在播放视频
getDuration(); //获取载入的视频文件的时长
播放视频的核心代码:
VideoView video=new VideoView();//1、创建MediaPlayer对象
video.setVideoPath("视频文件的路径");//2、设置视频文件的路径
video.start(); //3、开始播放音频(1——3必须按循序)
video.pause(); //暂停播放
video.resume(); //停止播放
播放完视频注意释放资源:
if(video!=null){
video.suspend();//释放所占用资源
}
注意:配置权限:
<users-permission android:name="android:permission.WRITE_EXTERNAL_STORAGE"
特别提示,VideoView并不是一个万能的视频播放工具类,它在视频格式的支持以及播放效率方面都存在着较大的不足。所以,如果想要仅仅使用VideoView就编写出一个功能非常强大的视频播放器是不太现实的。但是如果只是用于播放一些游戏的片头动画,或者某个应用的视频宣传,使用Videoview还是绰绰有余。