1.使用通知
1.1创建通知渠道
首先需要一个NotificationManager对通知进行管理
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
接下来使用NotifactionChannel类创建一个通知渠道,并调用NotifiactionManager的createNotificationChannel()方法创建。由于NotifiactionManager类和createNotificationChannel()方法都是Androidd 8.0系统新增API,所以我们还需要镜像版本判断才行
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.0) {
val channel =NotificationChannel(channelId,channelName,importance)
manager.createNotificationChannel(channel)
}
通知的重要等级主要有IMPORTANCE_HIGH,IMPORTANCE_DEFAULT,IMPORTANCE_LOW,IMPORTANCE_MIN这几种,对应由高到低。
1.2通知基本用法
创建一个NotificationTest项目
修改MainActivity
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val manager =getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
//判断Android版本
if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.O){
val channel = NotificationChannel("normal","Normal",NotificationManager.IMPORTANCE_DEFAULT)
manager.createNotificationChannel(channel)
}
sendNotice.setOnClickListener {
//添加点击读取消息页面
val intent=Intent(this,NotificationActivity::class.java)
val pi= PendingIntent.getActivity(this,0,intent,0)
//
val notifiaction = NotificationCompat.Builder(this,"normal")
.setContentTitle("这是一条通知的标题")
.setContentText("这是一条通知的内容")
.setSmallIcon(R.drawable.icon)
.setLargeIcon(BitmapFactory.decodeResource(resources,R.drawable.icon))
.setContentIntent(pi)
.build()
manager.notify(1,notifiaction)
}
}
}
右键com.example.notificationtest包新建NotificationActivity。
PendingIntent简单理解为延迟执行Intent
PendingIntent的用法很简单,它主要提供几个静态方法用于获取PendingIntent实例,可以选择使用getActivity()方法,getBroadcast()方法,还是getService()方法。第四个参数用于确定PendingIntent的行为,有FLAG_ONE_SHOT,FLAG_NO_CREATE,FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT这四种值可以选,通常情况传入0就可以了。
点击消息后系统上的图标消失有两种解决方式:
第一种:
val notification = NotificationCompat.Builder(this,"normal")
...
.setAutoCancel(true)
.build()
第二种:
class NotificationActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_notification)
val manager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
manager.cancel(1)
}
}
创建通知时候我们给每条通知指定的id为1,所以我们在cancel()方法中传入该通知的id就行了。
1.3通知的进阶技巧
1.3.1setStyle()方法 允许构建富文本的通知内容
val notifiaction = NotificationCompat.Builder(this,"normal")
...
.setStyle(NotificationCompat.BigTextStyle().bigText("这是一条很长的内容,内容绝对不会被省略!!这是一条很长的内容,内容绝对不会被省略!!这是一条很长的内容,内容绝对不会被省略!!这是一条很长的内容,内容绝对不会被省略!!这是一条很长的内容,内容绝对不会被省略!!"))
.build()
1.3.2显示一张大照片
val notifiaction = NotificationCompat.Builder(this,"normal")
...
.setStyle(NotificationCompat.BigPictureStyle().bigPicture(BitmapFactory.decodeResource(resources,R.drawable.q)))
.build()
事先准备好一张图片,通过BitmapFactory的decodeRsource()方法将图片解析成Bitmap对象,再传入bigPicture()方法中。
1.3.3创建一个新的通知
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
...
if(Build.VERSION.SDK_INT >=Build.VERSION_CODES.O){
...
val channel2 = NotificationChannel("normal","Normal",NotificationManager.IMPORTANCE_HIGH)
manager.createNotificationChannel(channel2)
}
sendNotice.setOnClickListener {
//添加点击读取消息页面
val intent=Intent(this,NotificationActivity::class.java)
val pi= PendingIntent.getActivity(this,0,intent,0)
//
val notifiaction = NotificationCompat.Builder(this,"important")
...
}
2.调用摄像头和相册
首先创建一个CrameraAlbumTest项目,然后修改activity_main.xml中的代码
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical">
<Button
android:id="@+id/takePhotoBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="照相"
></Button>
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
></ImageView>
</LinearLayout>
修改MainActivity中的代码
class MainActivity : AppCompatActivity() {
val takePhoto =1
lateinit var imageUri :Uri
lateinit var outputImage : File
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
takePhotoBtn.setOnClickListener {
//创建File对象,用于储存拍照后的照片
outputImage = File(externalCacheDir,"output_image.jpg")
if(outputImage.exists()){
outputImage.delete()
}
outputImage.createNewFile()
imageUri = if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N){
FileProvider.getUriForFile(this,"com.example.cameraalbumtest.fileprovider",outputImage)
}else{
Uri.fromFile(outputImage)
}
//启动相机程序
val intent = Intent("android.media.action.IMAGE_CAPTURE")
intent.putExtra(MediaStore.EXTRA_OUTPUT,imageUri)
startActivityForResult(intent,takePhoto)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when(requestCode){
takePhoto ->{
if(resultCode == Activity.RESULT_OK){
//将拍到的照片显现出来
val bitmap = BitmapFactory.decodeStream(contentResolver.openInputStream(imageUri))
imageView.setImageBitmap(rotateIfRequired(bitmap))
}
}
}
}
private fun rotateIfRequired(bitmap : Bitmap) : Bitmap{
val exif =ExifInterface(outputImage.path)
val orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION,ExifInterface.ORIENTATION_NORMAL)
return when(orientation){
ExifInterface.ORIENTATION_ROTATE_90 ->rotateBitmap(bitmap,90)
ExifInterface.ORIENTATION_ROTATE_180 ->rotateBitmap(bitmap,180)
ExifInterface.ORIENTATION_ROTATE_270 ->rotateBitmap(bitmap,270)
else -> bitmap
}
}
private fun rotateBitmap(bitmap: Bitmap,degree:Int) : Bitmap{
val matrix = Matrix()
matrix.postRotate(degree.toFloat())
val rotateBitmap = Bitmap.createBitmap(bitmap,0,0,bitmap.width,bitmap.height,matrix,true)
bitmap.recycle()//将不再需要的bitmap对象回收
return rotateBitmap
}
}
调用getExternalCacheDir()方法可以得到这个目录,具体路径是/sdcard/Android/data/< package name>/cache .
如果允许的设备系统版本低于Android 7.0,就调用Uri的fromFile()方法将File对象转换成Uri对象,这个Uri对象标识这output_image.jpg这张照片的本地真实路径。否则就调用FileProvider的getUriForFile()方法将File对象转换成一个封装的Uri对象。
getUriForFile()方法接收3个参数;第一个要求传入Context对象,第二个参数是唯一的字符串,第三个是刚创建的File对象
FileProvider是一种特殊的ContentProvider,它使用了和ContentProvider类似的机制来来对数据保护
在AndroidManifest.xml中注册ContentProvider
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.cameraalbumtest">
<application ...>
<activity android:name=".MainActivity">
...
</activity>
<provider
android:authorities="com.example.cameraalbumtest.fileprovider"
android:name="androidx.core.content.FileProvider"
android:exported="false"
android:grantUriPermissions="true"
>
<meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths"></meta-data>
</provider>
</application>
android:name属性的值是固定的,而android:authorities属性的值必须和刚才FileProvider.getUriForFile()方法中第二个参数一致。 < provider>标签的内部使用< meta_data>指定Uri的共享路径,并引用一个@xml/file_paths资源。
右键res目录创建一个xml目录,在创建一个file_path.xml文件,内写
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="my_images"
path="/"></external-path>
</paths>
external—path就是用来指定Uri共享的,name属性的值可以随便填,path属性值表示共享的具体路径。使用一个但斜杠表示将整个SD卡进行共享。
2.2使用相册
修改activity_main.xml中的代码
...
<Button
android:id="@+id/fromAlbumBtn"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="相册"
></Button>
...
修改MainActivity中的代码
class MainActivity : AppCompatActivity() {
...
val fromAlbum =2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
...
//相册
fromAlbumBtn.setOnClickListener {
//打开文件选择器
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
//指定只显示图片
intent.type="image/*"
startActivityForResult(intent,fromAlbum)
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when(requestCode){
...
fromAlbum ->{
if (resultCode == Activity.RESULT_OK && data!=null){
data.data?.let { uri->
//将选择的照片显示
val bitmap = getBitmapFromUri(uri)
imageView.setImageBitmap(bitmap)
}
}
}
}
}
private fun getBitmapFromUri(uri : Uri) =contentResolver.openFileDescriptor(uri,"r")?.use {
BitmapFactory.decodeFileDescriptor(it.fileDescriptor)
}
...
3.播放多媒体
3.1播放音频
方法名 | 功能描述 |
---|---|
setDataSource() | 设置要播放的音频文件的位置 |
prepare() | 在开始播放之前调用,以完成准备工作 |
start() | 开始或继续播放音频 |
pause() | 暂停播放音频 |
reset() | 将MediaPlayer对象重置到刚刚创建的状态 |
seekTo() | 从指定的位置开始播放音频 |
stop() | 停止播放音频。调用后的MediaPlayer对象无法再播放音频 |
release() | 释放与MediaPlayer对象相关的资源 |
isPlaying() | 判断当前MediaPlayer是否正在播放音频 |
getDuration() | 获取载入的音频文件的时长 |
首先创建新项目MediaPlayer
修改activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
>
<ImageView
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:src="@drawable/bg"
></ImageView>
<Button
android:id="@+id/play"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="播放"
></Button>
<Button
android:id="@+id/pause"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="暂停"
></Button>
<Button
android:id="@+id/stop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="停止播放"
></Button>
</LinearLayout>
在app/src/main目录下创建assets目录,将music.mp3文件复制到其中
修改MainActivity
class MainActivity : AppCompatActivity() {
private val mediaPlayer =MediaPlayer()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initMediaPlayer()
play.setOnClickListener {
if (!mediaPlayer.isPlaying){
mediaPlayer.start()
}
}
stop.setOnClickListener {
if (mediaPlayer.isPlaying){
mediaPlayer.reset()
initMediaPlayer()
}
}
pause.setOnClickListener {
if (mediaPlayer.isPlaying){
mediaPlayer.pause()
}
}
}
private fun initMediaPlayer(){
val assetManager = assets
val fd = assetManager.openFd("music.mp3")
mediaPlayer.setDataSource(fd.fileDescriptor,fd.startOffset,fd.length)
mediaPlayer.prepare()
}
override fun onDestroy() {
super.onDestroy()
mediaPlayer.stop()
mediaPlayer.release()
}
}
在initMediaPlayer()方法中,通过getAssets()方法得到了一个AssetManager的实例,AssetManager可以读取assets目录下的任何资源。接着我们调用openFd()方法将音频文件句柄打开,后又依次调用了setDataSource()方法和prepare()方法,为MediaPlayer做好播前准备。
3.2播放视频
方法名 | 功能描述 |
---|---|
setVideoPath() | 设置要播放的视频文件的位置 |
start() | 开始或继续播放视频 |
pause() | 暂停播放视频 |
resume() | 将视频从头开始播放 |
seekTo() | 从指定的位置开始播放视频 |
isPlaying() | 判断当前是否正在播放视频 |
getDuration() | 获取载入的视频文件的时长 |
首先我们新建PlayVideoTest项目
右键app/src/main/res ->新建raw目录,将准备好多video.mp4放在里面
修改activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:orientation="vertical"
>
<VideoView
android:id="@+id/videoView"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
></VideoView>
<Button
android:id="@+id/play"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="播放"
></Button>
<Button
android:id="@+id/pause"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="暂停"
></Button>
<Button
android:id="@+id/replay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="重播"
></Button>
</LinearLayout>
修改MainActivity类
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val url = Uri.parse("android.resource://$packageName/${R.raw.video}")
videoView.setVideoURI(url)
play.setOnClickListener {
if (!videoView.isPlaying){
videoView.start()
}
}
pause.setOnClickListener {
if(videoView.isPlaying){
videoView.pause()
}
}
replay.setOnClickListener {
if (videoView.isPlaying){
videoView.resume()
}
}
}
override fun onDestroy() {
super.onDestroy()
videoView.suspend()//释放所占的资源
}
}
首先在onCreate()方法中调用Uri.parse()方法,将raw目录下的video.mp4文件解析成了一个Uri对象。然后调用VideoView的setVideoURI()方法将Uri对象传入。
最后在onDestory()方法中调用suspend()方法,将VideoView所占资源释放。
4.Kotlin 课堂 : 使用infix构建函数
to并不是kotlin关键字,之所以我们能使用 A to B 这样的语法结构,是因为Kotlin提供了一种高级语法糖个性:infix函数。比如 A to B 这样的写法,实际上等于 A.to(B)
4.1从简单的例子来看
String startsWith()函数可以判断一个字符串是否以某个指定参数开头,借助infix函数后
新建一个infix.kt文件,编写下列代码
infix fun String.beginsWith(prefix : String) = startsWith(prefix)
这样我们就可以这样使用begins With()函数了
if( "Hello Kotlin" beginsWith "Hello" ){
//处理具体的逻辑
}
infix函数的语法堂的特殊性:1.不能定义成顶层函数,它必须是某个类的成员函数,可以使用扩展函数的方式将它定义到某个类中。2.infix函数必须接收且只能接收一个参数,参数类型没有限制
4.2复杂的例子
val list = listOf("Apple","Banana","Orange","Pear","Grape")
if(list.contains("Banana")){
//处理具体逻辑
}
在infix.kt文件中添加代码
infix fun <T> Collection<T>.has(element : T) = contains(element)
可以直接用
val list = listOf("Apple","Banana","Orange","Pear","Grape")
if(list has "Banana"){
//处理具体逻辑
}
这里调用Collection接口中的contains()函数。
4.3 A to B 原理
按Ctrl(Mac 是command)查看源码 :
public infix fun <A,B> A.to(that : B) : Pair<A,B> =Pair(this ,that)
5.Git时间:版本控制工具进阶
这次我们选择在PlayVideoTest项目中创建
打开cmd,在项目的根目录下进行,执行 git init命令。
G:\Android stdio\work\PlayVideoTest>git init
Initialized empty Git repository in G:/Android stdio/work/PlayVideoTest/.git/
5.1忽略文件
代码仓库下存在一个.gitignore文件,在Android studio 创建项目时会自动帮我们创建两个.gitignore文件,一个在app模块下,一个在根目录下。
根目录下的.gitignore文件内容:
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
除了*.iml表示任意以.iml结尾的文件,其他都是指定的具体文件名或目录名,上面配置得到所有内容都不会被添加到版本控制当中。
app模块下的.gitignore文件内容:
/build
由于app模块下大部分都是我们编写的代码,所以默认情况下build目录不会被添加到版本控制当中。
我们也可修改来满足特殊需求,比如我app模块下单所有测试文件都只是给我自己使用,我并不想把他加入到版本控制当中,可以这样修改gitignore文件:
/build
/src/test
/src/androidTest
现在我们可以提交代码
git add .
然后执行commit命令完成提交
git commit -m "First commit"
5.2查看修改内容
查看文件修改情况,只需要status命令就行
git status
查看所有文件修改内容
git diff
查看MainActivity.java文件修改内容
git diff app/src/main/java/com/example/playvideotest/MainActivity.kt
变更部最左侧加号代表新添加内容,左侧减号代表删除内容。
5.3撤销未提交的修改
如果我们想撤销这个修改可以使用checkout命令
git checkout app/src/main/java/com/example/playvideotest/MainActivity.kt
再使用git status命令检查一下
这种撤销方式只适用于还没有执行过add命令的文件。
当我们修改完MainActivity.java后执行git add .命令
我们再输入git status检查一下
MainActivity仍然处于添加状态,所以内容无法撤销掉。
我们应该先对其取消添加,使用的命令时reset命令
git reset HEAD app/src/main/java/com/example/playvideotest/MainActivity.kt
再允许一遍 git status命令,就会发现MainActivity.java变回未添加的状态
5.4查看提交记录
git log
如果提交的记录非常多的话,只想查看其中一条记录,我们可以命令指定该记录id
git log c243070fcf69bc063b184cdafa19617649dab2ee
如果想查看最近几次的提交,比如-1表示我们只想看最后一次的提交记录。
git log -1