移动开发笔记(十一)使用通知 调用摄像头和相册播放多媒体 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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
      掌握基于腾讯人工智能(AI)的车牌识别技术,使用车牌识别技术实现一个完整的停车场管理系统,项目包括网页调用摄像头拍照,车牌拍照识别,上传车牌图片识别,用户管理,车辆管理(临时车与包月车),车辆出场,入场管理,停车费收费管理,按照临时车或包月车自动计算停车费,系统参数设置,修改用户密码及安全退出等功能,该系统采用Jsp技术,使用SSM框架,Mysql数据库,ajax技术及人工智能等相关技术实现。重要通知:本课程根据腾讯AI车牌识别新接口,更新了新接口源代码,发布程序,购买了课程的同学可以下载新程序,包括(运行程序及源代码),更新时间:2021-2-17项目开发技术:java,jsp,mysql,MyBatis,SpringMVC,jquery,ajax,json项目运行环境:jdk1.7及以上版本,tomcat6.0及以上版本,mysql5.5及以上版本项目开发工具: 本项目开发工具是Eclipse,也支持myEclipse,Intellij Idea等其他版本开发工具相关课程学习顺序本校课程是培养JAVA软件工程师及JSP WEB网络应用程序开发,android工程师的全套课程,课程学习顺序如下:JAVA初级工程师:    1、计算机基础    2、HTML语言基础    3、C语言从入门到精通+贪吃蛇游戏    4、贪吃蛇游戏    5、SQL SERVER数据库基础    6、JAVA从入门到精通+推箱子游戏+QQ即时通讯软件    7、推箱子游戏;    8、仿QQ即时通讯软件;JAVA中级工程师:    9、SQLSERVER数据库高级    10、SQLSERVER从入门到精通(基础+高级)              11、JavaScript从入门到精通,    12、JSP从入门到精通+点餐系统,    13、JSP从入门到精通+在线视频学习教育平台,    14、JSP从入门到精通+大型电商平台;    15、XML从入门到精通,    16、数据结构(JAVA版),JAVA高级工程师:    17、Oracle数据库从入门到精通,    18、ajax+jquery从入门到精通,    19、EasyUI从入门到精通,SSH框架:    20、Struts2从入门到精通课程,    21、Hibernate从入门到精通课程,    22、Spring从入门到精通课程;    23、Echarts从入门到精通,    24、Excel基于POI的导入导出工作流框架:    25、Activiti流程框架从入门到精通    26、JBPM流程框架从入门到精通SSM框架:    27、MyBatis从入门到精通    28、Spring MVC从入门到精通面试题:    29、职业生涯规划及面试题集锦商业项目:    30、微信公众号在线支付系统    31、微信生活缴费在线支付系统    32、支付宝生活缴费在线支付系统    33、在线考试系统    34、手机订餐管理系统,    35、CRM客户关系管理系统    36、大型房地产CRM销售管理系统    37、CMPP2,CMPP3移动网关系统人工智能:    38、人脸识别在线考试系统    39、人脸识别系统项目实战    40、车牌识别系统项目实战    41、身份证识别系统项目实战    42、营业执照识别系统项目实战          43、名片识别管理系统
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

一天发火两次

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值