移動開發筆記(十一)使用通知 調用攝像頭和相冊播放多媒體 Kotlin

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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章