1、通知
2、调用摄像头和相册
3、播放音频、视频
4、infix函数
-
1、通知
通知就是当某个应用程序希望向用户发出一些提示信息,而该应用程序又不在前台运行时,就可以借助通知来实现。
——
每发出一条通知,就意味着自己的应用程序有着更高的打开率,因此有太多的应用会想尽办法给用户发送通知,虽然Android系统有禁止通知的功能,但也许有些通知是需要用户关心的,
——
于是在Android8.0就引入了通知渠道这一概念。就是每条通知都要属于一个对应的渠道,每个用户可以自由的创建当前应用的通知渠道,从而控制这些通知渠道的重要程度,是否响铃震动或者关闭等等。
以下代码等均为android.x版本
class TestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel("normal", "Normal", NotificationManager.IMPORTANCE_DEFAULT)
notificationManager.createNotificationChannel(channel)
}
button.setOnClickListener {
val notify = NotificationCompat.Builder(this, "normal")
.setContentTitle("this is content title")
.setContentText("this is content text")
.setSmallIcon(R.drawable.ic_login_user)
.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.ic_login_pwd))
.build()
notificationManager.notify(1, notify)
}
}
}
创建了通知渠道和通过点击发送一条通知。
用NotificationCompat是因为之前的Android各api的通知都有部分改动,而Android.x整合到了一起,并且提供NotificationCompat适配低版本。
——
在正常显示通知之后, 你会发现这条通知并没有点击效果,那怎么能让它具有点击效果呢, 如下:
新建一个通知跳转页面
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="com.hdsx.guangxihighway.ui.welcome.PendActivity">
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="this is notification layout"
android:textSize="30dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
之后只需要再原来的通知条件上增加Intent就可以
class TestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
val notificationManager =
getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel("normal", "Normal", NotificationManager.IMPORTANCE_DEFAULT)
notificationManager.createNotificationChannel(channel)
}
button.setOnClickListener {
val intent = Intent(this, PendActivity::class.java)
val p = PendingIntent.getActivity(this, 0, intent, 0)
val notify = NotificationCompat.Builder(this, "normal")
.setContentTitle("this is content title")
.setContentText("this is content text")
.setSmallIcon(R.drawable.ic_login_user)
.setLargeIcon(BitmapFactory.decodeResource(resources, R.drawable.ic_login_pwd))
.setContentIntent(p)
.setAutoCancel(true)
.build()
notificationManager.notify(1, notify)
}
}
}
setContentIntent设置跳转Intent,
而setAutoCancel的作用是 点击了这条通知后让其自动取消掉。
当然也可以直接通过manager.cancel(标识)取消
———
而通知并不是只有这些,如果想要丰富通知的内容,不让其显示的那么单调,可以通过setStyle方法来构建。具体可以看看API等等,此处不过多讲解。
——
需要注意的是通知的重要程度,
通知渠道的重要等级越高,发出的通知就越容易获得用户的注意。比如高重要等级的通知可以发出横幅,发出声音。而低等级的通知不仅可能会在某些情况下被隐藏,而且可能会被改变显示顺序。
当然这也不代表开发者就可以随心所欲了,开发者只能在创建通知渠道的时候为它指定初始的重要等级。如果用户对其不认可的话,可以随时进行修改。而开发者对此修改无权干涉。
举例就类似微信,你正在别的App操作,来了一个消息给你推出了一个横幅。你可以对这个横幅进行控制。
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val channel =
NotificationChannel("important", "Important", NotificationManager.IMPORTANCE_HIGH)
notificationManager.createNotificationChannel(channel)
}
.
.
.
-
2、调用摄像头和相册
假设应用要求用户上传一张图片作为头像,这时打开摄像头直接拍照是最为简单便捷的,那怎么能在应用内完成这一操作呢?
———
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.hdsx.guangxihighway.ui.welcome.TestActivity">
<Button
android:id="@+id/btnTakePhoto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="take photo" />
<ImageView
android:id="@+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal" />
</LinearLayout>
拍照则有按钮和显示,声明布局文件
class TestActivity : AppCompatActivity() {
//请求的code
val takePhoto = 1
//
lateinit var imageUri: Uri
//用来存放摄像头拍下的图片
lateinit var outputImage: File
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
btnTakePhoto.setOnClickListener {
//externalCacheDir获取应用的缓存目录。
//从Android6.0开始读写Sd卡就列为了危险的权限,访问的话需要运行时权限。而缓存目标即可跳转这个权限
//在Android10系统开始,公用的Sd卡目录已经不允许直接访问了,而是要是用作用域存储才行。具体见文章:https://mp.weixin.qq.com/s/_CV68KeQolJQqvUFo10ZVw
outputImage = File(externalCacheDir, "output_image.jpg")
if (outputImage.exists()) {
outputImage.delete()
}
outputImage.createNewFile()
//而这块主要是7.0的版本特性// 7.0的真实路径的Uri是被认为不安全的。
// 所以要通过特殊的Contentprovider来进行保护
imageUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
FileProvider.getUriForFile(
this,
"com.hdsx.guangxihighway.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))
}
}
}
/**
* 因为拍照有可能存下一些照片旋转的问题,如果横屏的话,那照片拍出来是横屏的,回归到竖屏的话就会有90度的旋转
* 而前置和后置摄像头旋转的度数也不同、
*/
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()
return rotateBitmap
}
}
关于一些参数的使用和特性,我都在注释里做了讲解。
——
7.0的特性文件,如下:
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="com.hdsx.guangxihighway.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
@xml - 在res目录下新建个xml文件夹,并且新增Xml
<?xml version="1.0" encoding="utf-8"?>
<xml xmlns:android="http://schemas.android.com/apk/res/android">
<external-path
name="my_images"
path="/" />
</xml>
external-path就是用来指定Uri共享的,name属性可以随便填写
而"/"表示将整个SD卡进行共享,当然你也可以仅共享存在output_image.jpg这张图片的路径。
————
当然拍照方便,但如果手机本身就有很多张图片,我不需要拍照,直接调用相册就可以。
————
相册选择图片,
布局 ,新增一个按钮用来做选择图片的操作
<Button
android:id="@+id/btnSelectPhoto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="select photo" />
btnSelectPhoto.setOnClickListener {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.addCategory(Intent.CATEGORY_OPENABLE)
intent.type = "image/*"
startActivityForResult(intent, selectPhoto)
}
修饰type为image类型,
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when (requestCode) {
selectPhoto ->
if (resultCode == Activity.RESULT_OK && data != null) {
data.data?.let {
val bitmap = getBitmapFromUri(it)
imageView.setImageBitmap(bitmap)
}
}
}
}
private fun getBitmapFromUri(uri: Uri) = contentResolver.openFileDescriptor(uri, "r")?.use {
BitmapFactory.decodeFileDescriptor(it.fileDescriptor)
}
————
-
3、播放音频、视频
在Android中播放音频一般是由MediaPlayer类实现的
来操作一下:
在Assets文件夹下放置 music.mp3
在当前页面新增三个按钮来控制音频的状态
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.hdsx.guangxihighway.ui.welcome.TestActivity">
<Button
android:id="@+id/paly"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="paly" />
<Button
android:id="@+id/pause"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="pause" />
<Button
android:id="@+id/stop"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="stop" />
</LinearLayout>
准备做好后,接着书写逻辑代码
class TestActivity : AppCompatActivity() {
private val mediaPlayer = MediaPlayer()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
initMediaPlayer()
play.setOnClickListener {
if (mediaPlayer.isPlaying) mediaPlayer.start()
}
pause.setOnClickListener {
if (mediaPlayer.isPlaying) mediaPlayer.pause()
}
stop.setOnClickListener {
if (mediaPlayer.isPlaying) {
mediaPlayer.reset()
initMediaPlayer()
}
}
}
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()
}
}
————
播放视频
视频的播放是由VideoView类来实现的。
在res资源下新建raw文件夹,并且放入video.mp4视频文件
接着创建布局文件,
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.hdsx.guangxihighway.ui.welcome.TestActivity">
<Button
android:id="@+id/play"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="play" />
<Button
android:id="@+id/pause"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="pause" />
<Button
android:id="@+id/replay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="replay" />
</LinearLayout>
准备做好后,接着书写代码
class TestActivity : AppCompatActivity() {
private val videoView = VideoView(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
val uri = Uri.parse("android.resource://$packageName/${R.raw.video}")
videoView.setVideoURI(uri)
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()
}
}
————
-
4、infix函数
举个例子,我通过mapOf创建一个map
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
我们能发现在构建键值对的时候 直接使用的 to,那 to是不是kotlin的一个函数呢,答案肯定是: 不是。其实也可以看成 A.to(B) 只不过是省略了小数点和括号。
在比如:
if ("Hello World".startsWith("Hello")) {
这肯定是包含的,我们加上infix函数
//1
infix fun String.begin(str: String) = startsWith(str)
//2
if ("Hello World".begin("Hello")) {
//3
if ("Hello World" begin "Hello") {
随着递进是不是发现了被infix修饰的高阶函数, 可以省略掉 (小数点和括号)。它就是这样的作用,非常特殊。
所以对其使用有比较 严格的两个限制:
(1)infix函数是不能定义变成顶层函数的,它必须是某个类的成员函数,可以使用扩展函数的方法将它定义到某个类中
(2)它必须只能有一个接收参数,参数类型没有限制。
在比如list:
//1
val list = listOf("1", "2", "3")
if (list.contains("1")){}
//2
infix fun <T> Collection<T>.has(element: T) = contains(element)
//3
if (list has "1") {}
最后在说下Map to的实现:
//1
val map = mapOf("a" to 1, "b" to 2, "c" to 3)
//2
infix fun <A, B> A.with(that: B): Pair<A, B> = Pair(this, that)
//3
val map = mapOf("a" with 1, "b" with 2, "c" with 3)
总结
多媒体的简单应用
infix函数用法