第一行代码第三版笔记

Activity

第一行代码第三版笔记_第1张图片

主acitivity:程序运行起来首先启动的activity

manifest


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.activitytest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ThirdActivity">
            //设置可以响应打开网页的intent
            <intent-filter tools:ignore="AppLinkUrlError">//将警告忽略
                <action android:name="android.intent.action.VIEW" />//配置能够响应的action
                <category android:name="android.intent.category.DEFAULT" />//指定默认的category
                <data android:scheme="https" />//指定数据的协议必须是https
            intent-filter>
        activity>
        <activity android:name=".SecondActivity">
        <intent-filter>//要设置响应哪个activity,就在哪个activity的标签中设置intent过滤器
                <action android:name="com.example.activitytest.ACTION_START"/>//指明当前Activity可以响应com.example.activitytest.ACTION_START这个action
                <category android:name="android.intent.category.DEFAULT"/>//包含一些附加信息,更精确的指明了当前Activity能够响应的Intent中还可能带有的category,隐式Intent:只有<action><category>中的内容同时匹配Intent中指定的actio和category时,这个Activity才能响应该Intent
                //android.intent.category.DEFAULT是一种默认的category,在调用startActivity()方法的时候会自动将这个category添加到Intent中
                <category android:name="com.example.activitytest.MY_CATEGORY"/>//用于响应在主程序中添加的category
            intent-filter>
        
        
        activity>
        <activity android:name=".FirstActivity">
            <intent-filter>
                <action android:name="com.example.activitytest.ACTION_START" />

                <category android:name="android.intent.category.DEFAULT" />
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
            
        activity>
    application>

manifest>

FirstActivity.kt

package com.example.activitytest

import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.Menu
import android.view.MenuItem
import android.widget.Button
import android.widget.Toast
import kotlinx.android.synthetic.main.first_layout.*
import java.net.HttpCookie.parse
import java.net.URI
import java.util.logging.Level.parse

class FirstActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.first_layout)//加载布局
        //val button1 :Button = findViewById(R.id.button)//:的作用是:是强行指定他是什么类型的

        button.setOnClickListener {//可以使用kotlin的插件,直接使用相同名称的组件,就不用findviewByid来进行获取
            Toast.makeText(this,"你点击了按钮",Toast.LENGTH_SHORT).show()//activity本身就是context对象,直接传this
        }
        button2.setOnClickListener {
            Toast.makeText(this,"你销毁了activity",Toast.LENGTH_SHORT).show()//activity本身就是context对象,直接传this
            finish()//销毁当前的activity,效果和按back键一样
        }
        //显形intent的跳转
        StartSecondActivity.setOnClickListener {
            val intent = Intent(this,SecondActivity::class.java)//第一个参数Context要求提供一个启动Activity的上下文,this代表当前的activity
            // ;第二个参数class,用于指定要启动目标的Activity
            //跳转后要回到上个activity,只要按back来进行销毁当前的activity
            //::就相当于.
            startActivity(intent)
        }
        button4.setOnClickListener {
            val intent = Intent("com.example.activitytest.ACTION_START")//匹配Manifest中设置的action
            intent.addCategory("com.example.activitytest.MY_CATEGORY")//每个intent只能指定一个action,但能指定多个category,调用addCategory来添加一个Category
            startActivity(intent)
        }
        button3.setOnClickListener {
            val intent =Intent(Intent.ACTION_VIEW)//指定intent的action
            intent.data= Uri.parse("https://www.baidu.com")//将网址字符串解析成一个Uri对象,里面调用intent的setData方法传入Uri对象
            startActivity(intent)
        }
        button6.setOnClickListener {
            val intent = Intent(Intent.ACTION_DIAL)//设置intent的动作是拨打电话
            intent.data= Uri.parse("tel:10086")//设置响应intent动作的号码
            startActivity(intent)//启动隐式intent
        }
        button7.setOnClickListener {
            val data="Hello SecondActivity"
            val intent =Intent(this,SecondActivity::class.java)//用显式intent启动activity,::相当于.
            intent.putExtra("extra_data",data)//tongguo putExtr()方法传递一个字符串,第一个参数是键,用于之后从Intent取值,第二个参数才是真正要传递的数据
            startActivity(intent)
        }
        button8.setOnClickListener {
            val intent = Intent(this,SecondActivity::class.java)//用显式intent来启动另一个activity
            startActivityForResult(intent,1)//启动Activity并在Activity销毁的时候返回一个结果给上一个Activity,第一个参数是跳转的intent,第二个参数是请求吗。用于回调中判断数据的来源

        }

    }
//重写方法来得到返回的数据
    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {//第一个参数为启动Activity传入的请求码,第二个参数是返回数据时传入的处理结果,第三个参数data,即携带返回数据的Intent
        super.onActivityResult(requestCode, resultCode, data)
        when(requestCode){//通过请求吗来判断数据来源
            1->if (resultCode == Activity.RESULT_OK){//判断结果是否成功+
                val returnedData= data?.getStringExtra("data_return")//如果不为空,就执行getStringExtra方法,通过键来取值
                Log.d("FirstActivity","returned data is $returnedData")
            }
        }
    }

    override fun onCreateOptionsMenu(menu: Menu?): Boolean {
         menuInflater.inflate(R.menu.menu,menu)//调用父类的getMenuInflater()方法,得到MenuInflater对象,,再调用inflate方法创建菜单,
        //第一个参数是指定我通过哪一个资源文件来创建菜单,第二个参数指定我们的菜单项将添加到哪一个Menu对象当中
        return true//允许创建的菜单显示出来
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        when(item.itemId){//通过id来判断要对那个菜单做出操作
            R.id.add_item ->Toast.makeText(this,"你点击了添加菜单",Toast.LENGTH_SHORT).show()
            R.id.remove_item ->Toast.makeText(this,"你点击了移除菜单",Toast.LENGTH_SHORT).show()
        }
        return true
    }
    /*

    val book =Book();
    book.pages=500  //作用类似于set 赋值的方法
    val bookPages=book.pages  //作用类似于get 读取的方法
     */
}

first_layout.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button" />

    <Button
        android:id="@+id/button2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="销毁activity" />

    <Button
        android:id="@+id/StartSecondActivity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="显式启动另一个activity" />

    <Button
        android:id="@+id/button4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="隐式启动另一个activity" />

    <TextView
        android:id="@+id/textView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="第一个activity" />

    <Button
        android:id="@+id/button3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="打开百度" />

    <Button
        android:id="@+id/button6"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="调用系统拨号" />

    <Button
        android:id="@+id/button7"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="向SecondActivity赋值" />

    <Button
        android:id="@+id/button8"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="返回数据给上一个Activity" />
LinearLayout>

SecondActivity.kt

package com.example.activitytest

import android.app.Activity
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.second_layout.*

class SecondActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.second_layout)
        val extraData=intent.getStringExtra("extra_data")//intent实际上调用父类的getIntent()方法获取启动SecondActivity的intent,调用getStringExtra根据键获取值
        Log.d("SecondActivity","extra data is $extraData")
        button10.setOnClickListener {
            val intent = Intent()//intent用于传递数据
            intent.putExtra("data_return","Hello FirstActivity")//给intent设置要传递的数据,用键值对的方式进行设置
            setResult(Activity.RESULT_OK,intent)//向上一个Activity返回数据,第一个参数用于向上一个Acitivty返回处理结果,第二个参数是返回数据的intent
            finish()//销毁当前的Activity
        }


    }

    override fun onBackPressed() {//在返回键按下后执行的方法
        val intent =Intent()//intent用于传递数据
        intent.putExtra("data_return","Hello FirstActivity")//给intent设置要传递的数据,用键值对的方式进行设置
        setResult(Activity.RESULT_OK,intent)//向上一个Activity返回数据,第一个参数用于向上一个Acitivty返回处理结果,第二个参数是返回数据的intent
        finish()//销毁当前的Activity
    }
}

second_layout.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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SecondActivity">


    <Button
        android:id="@+id/button2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="Button 2" />



    <Button
        android:id="@+id/button10"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="1"
        android:text="返回值给上一个activity并销毁此activity" />
LinearLayout>

ThirdActivity.kt

package com.example.activitytest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class ThirdActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.third_layout)
    }
}

third_layout.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ThirdActivity">

    <Button
        android:id="@+id/button5"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button 3" />
LinearLayout>

menu.xml


<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
    android:id="@+id/add_item"
    android:title="Add"
    />
    <item
        android:id="@+id/remove_item"
        android:title="Remove"/>

menu>

创建菜单文件:在res目录下新建menu文件夹,右击res目录->new->Directory,输入文件夹名menu,在这个文件夹中右键->New->Menu resource file

可以在标签中配置标签,用于更精确地指定当前Activity能够响应地数据,只有当标签中指定地内容和Intent中携带地Data完全一致时,当前Activity才能够响应该Intent

要在某个部分实现就在那里重写方法

源码

链接:https://pan.baidu.com/s/1-a_yYYDkrIj75XVCuRBRSg
提取码:cul9

Activity的生命周期

manifest


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.activitylifecycletest">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        

        activity>
        <activity android:name=".NormalActivity" />
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>
    application>

manifest>

MainActivity.kt

package com.example.activitylifecycletest

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.PersistableBundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    private val tag="MainActivity"

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(tag,"onCreate")
        setContentView(R.layout.activity_main)
        if (savedInstanceState !=null){//如果重写的onSaveInstanceState方法保存的数据不为空
            val tempData =savedInstanceState.getString("data_key")//根据键来取值
            Log.d(tag,tempData)
        }

        startNormalActivity.setOnClickListener {
            val intent = Intent(this,NormalActivity::class.java)
            startActivity(intent)
        }
        startDialogActivity.setOnClickListener {
            val intent =Intent(this,DialogActivity::class.java)
            startActivity(intent)

        }

    }

    override fun onSaveInstanceState(outState: Bundle, outPersistentState: PersistableBundle) {//activity在回收前一定会被调用
        super.onSaveInstanceState(outState, outPersistentState)
        val tempData ="Something you just typed"//定义保存的数据
        outState.putString("data_key",tempData)//采用键值对的方式保存数据
    }

    override fun onStart() {
        super.onStart()
        Log.d(tag,"onStart")
    }

    override fun onResume() {
        super.onResume()
    Log.d(tag,"onResume")
    }

    override fun onPause() {
        super.onPause()
    Log.d(tag,"onPause")
    }

    override fun onStop() {
        super.onStop()
    Log.d(tag,"onStop")
    }

    override fun onDestroy() {
        super.onDestroy()
    Log.d(tag,"onDestory")
    }

    override fun onRestart() {
        super.onRestart()
    Log.d(tag,"onRestart")
    }
}

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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">


    <Button
        android:id="@+id/startNormalActivity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start NormalActivity" />

    <Button
        android:id="@+id/startDialogActivity"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Start DialogActivity" />
LinearLayout>

NormalActivity.kt

package com.example.activitylifecycletest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class NormalActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_normal)
    }
}

activity_normal.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=".NormalActivity">


    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This is a normal activity" />
LinearLayout>

DialogActivity.kt

package com.example.activitylifecycletest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class DialogActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_dialog)
    }
}

activity_dialog.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=".DialogActivity">

    <TextView
        android:id="@+id/textView2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="This is a dialog activity"

       />
LinearLayout>

源码

链接:https://pan.baidu.com/s/1gHJeaJBbkiD8U-bM-LO29w
提取码:tnes

Activity的启动模式

standard模式

作用

默认为standard启动模式,Activity在栈顶,再次启动还要创建一个新的Activity

部分代码

manifest


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.a20200604">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".FirstActivity">activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>
    application>

manifest>

singleTop模式

作用

在启动Activity时如果发现返回栈的栈顶已经时该Activity,则认为可以直接使用它,不会再创建新的Activity实例。可以解决重复创建栈顶Activity的问题

部分代码

manifest


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.a20200604">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        
        activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>
    application>

manifest>

singleTask

作用

使某个Activity在整个应用程序的上下文只存在一个实例,可以理解为有多个相同的activity时会销毁其余的activity,只保留一个activity

部分代码

manifest


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.a20200604">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".SecondActivity">activity>
        <activity
            android:name=".FirstActivity"
            android:launchMode="singleTask">//设置FirstActivity的启动模式是singTask
        activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>
    application>

manifest>

singleInstance

作用

会启用一个新的返回栈来管理这个Activity,无论是哪个应用程序来访问这个Activity,都共用一个返回栈,解决共享Activity实例的问题,采用两套返回栈来管理activity,一个栈空了,就出另一个栈

返回栈空了就会退出程序

部分代码


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.a20200604">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".ThirdActivity">activity>
        <activity
            android:name=".SecondActivity"
            android:launchMode="singleInstance">//设置启动模式
        
        
        activity>
        <activity android:name=".FirstActivity">activity>
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>
    application>

manifest>

代码

MainActivity.kt

package com.example.a20200604

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_first.*
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener {
            val intent = Intent(this,FirstActivity::class.java)
            startActivity(intent)
        }
    }
}

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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="跳转到FirstActivity"
        tools:layout_editor_absoluteX="142dp"
        tools:layout_editor_absoluteY="33dp" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="这是MainActivity" />
LinearLayout>

SecondActivity.kt

package com.example.a20200604

import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_second.*

class SecondActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("SecondActivity","task id is $taskId")
        setContentView(R.layout.activity_second)
        button3.setOnClickListener {

            val intent = Intent(this,FirstActivity::class.java)
            startActivity(intent)
        }
        button4.setOnClickListener {
            val intent = Intent(this,ThirdActivity::class.java)
            startActivity(intent)
                    }
button6.setOnClickListener {
    SecondActivity.actionstart(this,"data1","data2")
}
    }

    companion object{//类似java的静态方法
        fun actionstart(context: Context, data1:String, data2: String){
            val  intent=Intent(context,SecondActivity::class.java)
            intent.putExtra("param1",data1)//将所需要的值存储在intent中
            intent.putExtra("param2",data2)//将所需要的值存储在intent中
            context.startActivity(intent)//启动SecondActivity
        /*
        可以用一行代码来启动SecondActivity

         */
        }
    }


    override fun onDestroy() {
        super.onDestroy()
        Log.d("SecondActivity","onDestroy")
    }
}

activity_ second.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:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".SecondActivity">

    <Button
        android:id="@+id/button3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="回到FirstActivity"
        tools:layout_editor_absoluteX="41dp"
        tools:layout_editor_absoluteY="17dp" />

    <Button
        android:id="@+id/button4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="启动ThirdActivity" />

    <TextView
        android:id="@+id/textView3"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="这是SecondActivity" />

    <Button
        android:id="@+id/button6"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="启动SecondActivity" />
LinearLayout>

ThirdActivity.kt

package com.example.a20200604

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_third.*

class ThirdActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("ThirdActivity","Task id is $taskId")
        setContentView(R.layout.activity_third)
    button5.setOnClickListener {
        ActivityCollector.finishAll()
        android.os.Process.killProcess(android.os.Process.myPid())//杀掉当前进程的代码,killProcess()方法用于杀掉一个进程,接受一个进程id参数
        //通过myPid()方法来获取当前程序的进程id,killProcess()方法只能用于杀掉当前程序的进程,不能用于杀掉其他程序
    }
    }

}

activity_ third.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"
    android:orientation="vertical"
    tools:context=".ThirdActivity">


    <TextView
        android:id="@+id/textView4"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="这是ThirdActivity" />

    <Button
        android:id="@+id/button5"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="退出所有Activity" />
LinearLayout>

BaseActivity.kt

package com.example.a20200604

import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
//目标:知晓当前是在哪一个activity
open class BaseActivity : AppCompatActivity() {//继承AppCompatActivity,设置为可被继承
    override fun onCreate(savedInstanceState: Bundle?) {//重写onCreate方法
        super.onCreate(savedInstanceState)
    Log.d("BaseActivity",javaClass.simpleName)//打印当前实例的类名,kotlin中javaClass表示获取当前实例的class对象,相当于java中调用getClass()方法
    //再调用simpleName获取当前实例的类名
ActivityCollector.addActivity(this)//将正在创建的Activity添加到集合里
    }

    override fun onDestroy() {
        super.onDestroy()
        ActivityCollector.removeActivity(this)//集合里移除一个马上要销毁的Activity
    }
}

ActivityCollector.kt

package com.example.a20200604

import android.app.Activity
//目标:随时随地退出程序
object ActivityCollector{
    private  val activities=ArrayList()//一个专门的集合对所有的Activity进行管理,通过ArrayList来暂存Activity
    fun addActivity(activity: Activity){
        activities.add(activity)

    }
    fun removeActivity(activity: Activity){
        activities.remove(activity)
    }
    fun finishAll(){
        for (activity in activities){
           if (!activity.isFinishing){//判断Activity是否正在销毁中,如果没有正在销毁
               activity.finish()//销毁activites中的所有activity
           }
        }
        activities.clear()
    }
}

标准函数with、run和apply

with函数

格式

val result = with(obj){
// 这里是obj的上下文
"value"// with函数的返回值

}

作用

连续调用同一个对象的多个方法时,让代码变得更加精简

实例

fun main() {

    //with函数
    val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
    val builder = StringBuilder()//采用StringBuilder来构建字符串
    builder.append("Start eating fruits,\n")
    for (fruit in list) {
        builder.append(fruit).append("\n")
    }
    builder.append("Ate all fruits.")
    val result = builder.toString()
    println(result)
    println("___________________________________________________________________")
    //可用with简化如下
    val list1 = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
    val result2 = with(StringBuilder()) {//with第一个参数传入StringBuild对象,就是lambda表达式的上下文对象,所以添加字符串时就不用指定对象
        append("Start eating fruits,\n")
        for (fruit in list) {
            append(fruit).append("\n")
        }
        append("Ate all fruits.")
        toString()//lambda表达式的最后一行代码作为with函数的返回值返回
        //
        }
    println(result2)
    
}

run函数

格式

run函数不能直接调用,要调用某个对象的run方法,run函数只接受一个lambda参数,会再lambda表达式中提供调用对象的上下文

val result = obj.run{
// 这里是obj的上下文
"value"// with函数的返回值

}

实例

val list3 = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
    val result3 = StringBuilder().run {//with第一个参数传入StringBuild对象,就是lambda表达式的上下文对象,所以添加字符串时就不用指定对象
        append("Start eating fruits,\n")
        for (fruit in list3) {
            append(fruit).append("\n")
        }
        append("Ate all fruits.")
        toString()//lambda表达式的最后一行代码作为with函数的返回值返回
        //
    }
    println(result3)

apply函数

格式

类似于run,但apply函数无法指定返回值,而是会自动返回调用对象本身

val result =obj.apply{
//这里是obj的上下文
}
//result==obj

实例

 val list4 = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
    val result4 = StringBuilder().apply {//with第一个参数传入StringBuild对象,就是lambda表达式的上下文对象,所以添加字符串时就不用指定对象
        append("Start eating fruits,\n")
        for (fruit in list4) {
            append(fruit).append("\n")
        }
        append("Ate all fruits.")

        //
    }
    println(result4.toString())
/*
用Intent启动Activity和传值的时候也可以用apply函数
val intent = Intent(context,SecondActivity::class.java).apply{
putExtra("param1","data1")
putExtra("param2","data2")
}
context.startActivity(intent)
 */

定义静态方法

kotlin用单例来代替静态方法

目的:让类的某一个方法变成静态方法的调用方式

class  Util{//若是object为前缀则为单例,而class为前缀则为普通类
    fun doAction1(){//普通方法要用实例对象才能调用
        println("do action1")
    }
    companion object {//会自动在类中创建伴生类对象,代替类的实例
        fun doAction2(){//类似静态方法,可以用类名来调用,实际上就是伴生类对象.方法
            println("do action2")
        }
    }

}

实现静态方法—注解

加上@JvmStatic 注解,kotlin就会将这些方法编译成真正的静态方法,注解只能加在单例类或companion object中的方法

class  Util{//若是object为前缀则为单例,而class为前缀则为普通类
    fun doAction1(){//普通方法要用实例对象才能调用
        println("do action1")
    }
    companion object {//会自动在类中创建伴生类对象,代替类的实例
    @JvmStatic
        fun doAction2(){//类似静态方法,可以用类名来调用,实际上就是伴生类对象.方法
            println("do action2")
        }
    }

}

实现静态方法—顶层

`没有定义在任何类中的方法称为顶层方法,在kotlin中,所有的顶层方法都可以在任何位置被直接调用,直接键入顶层方法()即可,java没有顶层方法的概念,所有的方法必须定义在类中,kotlin会自动将顶层方法名kt定义成一个java类,而doSomething就是以静态方法的形式定义在HelperKt类里面,java只要用顶层方法名kt.静态方法进行调用

fun doSometing(){
    println("do something")
}
fun main() {
    doSometing()
    // Helperkt.doSomething()  java形式调用顶层方法
}

UI

采用androidx.constraintlayout.widget.ConstraintLayout布局就能用可视化用拖拽的方式添加组件,并且在旁边的设置面板上设置各种属性
第一行代码第三版笔记_第2张图片
文字的大小以sp为单位,组件大小以dp为单位

TextView

activity_main.xml


   

Button

activity_main.xml

 

可以用函数式API(lambda表达式)来写监听按钮的点击事件

button.setOnClickListener { 

        }

实现接口的方式注册按钮

package com.example.uiwidgetest

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity(),View.OnClickListener {//实现View.OnClickListener接口

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
       button.setOnClickListener(this)//this为MainActivity的实例
    }
    override fun onClick(v: View?){//重写onClick方法,?表明View类型的v变量可为空
        when(v?.id){//表示如果v对象不为空的时候,核对ID,如果ID适合就执行逻辑
            R.id.button->{//采用lambda表达式,如果id为button则

            }
        }

    }

}

EditText

常用的代码

android:maxLines:指定了EditText的最大行数为两行,这样当输入的内容超过两行,文本就会向上滚动,EditText则不会继续拉伸

val inputText=editText.text.toString():调用EditText的getText()方法获取输入内容,再调用toString()方法将内容转为字符串,text实际上调用的是getText()方法

可以在AndroidStudio的代码提示中显示使用语法糖后的优化代码调用
editText为EditText的id
第一行代码第三版笔记_第3张图片

ImageView

添加图片:将ImageView拖动到布局后,可以在提示框选中要添加的图片
第一行代码第三版笔记_第4张图片
通过点击按钮来动态改变ImageView中的图片

button.setOnClickListener{
          imageView2.setImageResource(R.drawable.tp1)
      }

ProgressBar

设置可见度,visible表示可见,invisible表示不可见但存在,gone表示不可见且不存在
android:visibility="visible"
设置为水平进度条,并设置最大的值
style="?android:attr/progressBarStyleHorizontal" android:max="100
获取进度条的当前进度,然后再现有的进度上加10作为更新的进度,progressBar为进度条的id
progressBar.progress=progressBar.progress+10

AlertDialog

能够弹出对话框,对话框置顶于所有界面元素之上,能够屏蔽其他控件的交互能力

         AlertDialog.Builder(this).apply {//构建一个对话框,采用apply标准函数
                setTitle("this is Dialog")//设置对话框的标题
                setMessage("Something important.")//设置对话框的内容
                setCancelable(false)//设置不可用Back键关闭对话框
                setPositiveButton("Ok"){ dialog, which ->//设置对话框的确定按钮的点击事件
                }
                setNegativeButton("Cancel"){ dialog, which ->//设置对话框的取消按钮的点击事件
                }
                show()//将对话框显示出来
            }

布局

采用androidx.constraintlayout.widget.ConstraintLayout

使用方法

https://www.bilibili.com/video/BV1F4411Y7it

其他技巧

自定义标题栏

activity_main.xml


<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=".MainActivity">

   <include layout="@layout/title"/>//引入其他布局

androidx.constraintlayout.widget.ConstraintLayout>

title.xml


<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">

    <Button
        android:id="@+id/BACK"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="33dp"
        android:layout_marginEnd="18dp"
        android:layout_marginRight="18dp"
        android:text="BACK"
        app:layout_constraintEnd_toStartOf="@+id/textView"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/EDIT"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="33dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:text="EDIT"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/textView"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginEnd="19dp"
        android:layout_marginRight="19dp"
        app:layout_constraintBottom_toBottomOf="@+id/BACK"
        app:layout_constraintEnd_toStartOf="@+id/EDIT"
        app:layout_constraintStart_toEndOf="@+id/BACK"
        app:layout_constraintTop_toTopOf="@+id/BACK" />
androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.kt

package com.example.a20200610study2

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        supportActionBar?.hide()//隐藏系统的标题栏,
        //调用getSupportActionBar()方法来获取ActionBar的实例
        //调用hide()方法隐藏起来
        //ActionBar可能为空,所以用?.修饰符
    }
}

自定义控件

过程:activity_main调用TitleLayout这个activity,然后TitleLayout的activity再调用title的布局,并设置布局的点击事件
MainActivity.kt

package com.example.a20200610study2

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        supportActionBar?.hide()
    }
}

activity_main.xml


<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=".MainActivity">

   <include
       android:id="@+id/include"
       layout="@layout/title" />

   <com.example.a20200610study2.TitleLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content" />

androidx.constraintlayout.widget.ConstraintLayout>

TitleLayout.kt

package com.example.a20200610study2

import android.app.Activity
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.Toast
import androidx.constraintlayout.widget.ConstraintLayout
import kotlinx.android.synthetic.main.title.view.*
//过程:activity_main调用TitleLayout这个activity,然后TitleLayout的activity再调用title的布局,并设置布局的点击事件
class TitleLayout(context: Context,attrs:AttributeSet): ConstraintLayout(context, attrs){//使用两个参数的构造方法,声明Context和AttributeSet这两个参数
//context为Activity的实例
    init{
    LayoutInflater.from(context).inflate(R.layout.title,this)//加载布局,通过LayoutInflater.from()方法来构建一个LayoutInflater对象
    //再调用inflate()方法来加载布局文件,inflate接受两个参数,第一个是加载的布局id,第二个是给加载好的布局再添加一个父布局
    BACK.setOnClickListener {
        val activity=context as Activity//用as关键字进行强制类型转换,转成Activity的类型
        activity.finish()
    }
    EDIT.setOnClickListener {
        Toast.makeText(context,"你点击了按钮",Toast.LENGTH_LONG).show()
    }
}
}

title.xml


<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">

    <Button
        android:id="@+id/BACK"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginLeft="16dp"
        android:layout_marginTop="33dp"
        android:layout_marginEnd="18dp"
        android:layout_marginRight="18dp"
        android:text="BACK"
        app:layout_constraintEnd_toStartOf="@+id/textView"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/EDIT"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="33dp"
        android:layout_marginEnd="16dp"
        android:layout_marginRight="16dp"
        android:text="EDIT"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@+id/textView"
        app:layout_constraintTop_toTopOf="parent" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginEnd="19dp"
        android:layout_marginRight="19dp"
        app:layout_constraintBottom_toBottomOf="@+id/BACK"
        app:layout_constraintEnd_toStartOf="@+id/EDIT"
        app:layout_constraintStart_toEndOf="@+id/BACK"
        app:layout_constraintTop_toTopOf="@+id/BACK" />
androidx.constraintlayout.widget.ConstraintLayout>

ListView

MainActivity.kt

package com.example.a20200610study3

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.ArrayAdapter
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    private val data = listOf("1","2","3","4","5","6","7","8","9","10","11","12","13","14","15","16","17","18","19","20")//初始化集合
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val adapter=ArrayAdapter<String>(this,android.R.layout.simple_list_item_1,data)//通过适配器来将数据传递给listView
        //指定泛型为String,第一个参数为activity的实例,第二个参数为list子项布局的id(android内置的布局文件,只有一个textview,可用于显示一段文本)
        // 第三个参数为数据源
        listView.adapter=adapter//调用ListView的setAdapter()的方法,传进适配器对象,建立ListView与数据的关联

    }
}

activity_main.xml


<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=".MainActivity">

    <ListView
        android:id="@+id/listView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:layout_editor_absoluteX="1dp"
        tools:layout_editor_absoluteY="1dp" />

androidx.constraintlayout.widget.ConstraintLayout>

定制ListView的界面

fruit_ item.xml


<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent">
<ImageView
    android:layout_width="40dp"
    android:layout_height="40dp"
    android:layout_gravity="center_vertical"
    android:id="@+id/fruitImage"
    android:layout_marginLeft="10dp"
    />
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/fruitName"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp"/>
LinearLayout>

MainActivity.kt

package com.example.a20200611study

import android.app.Activity
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
import kotlinx.android.synthetic.main.activity_main.*
//目标:定制ListView界面
//todo 理解全部代码
class MainActivity : AppCompatActivity() {
    private val  fruitList= ArrayList<Fruit>()//定义list集合存储水果

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()//初始化水果数据
        val adapter = FruitAdapter(this,R.layout.fruit_item,fruitList)//创建FruitAdapter对象
        listView.adapter=adapter//FruitAdapter对象作为适配器传递给ListView


    }
    private  fun initFruits(){//定义初始化水果的方法
        repeat(2){//执行添加数据两遍
            fruitList.add(Fruit("苹果",R.drawable.tp1))//使用构造函数将水果的名称和图片传入,然后把创建好的对象传入水果列表中
            fruitList.add(Fruit("香蕉",R.drawable.tp2))
            fruitList.add(Fruit("橘子",R.drawable.tp5))
            fruitList.add(Fruit("橙子",R.drawable.tp10))
            fruitList.add(Fruit("李子",R.drawable.tp11))
            fruitList.add(Fruit("桃",R.drawable.tp12))
            fruitList.add(Fruit("三明治",R.drawable.tp6))
            fruitList.add(Fruit("樱桃",R.drawable.tp7))
            fruitList.add(Fruit("甜品",R.drawable.tupian))
            fruitList.add(Fruit("沙琪玛",R.drawable.tp7))
        }
}

}
class Fruit(val name:String,val imageId:Int)//类有两个资源,一个是水果的名称,一个是图片的id
class FruitAdapter(activity: Activity,val resourceId:Int,data:List<Fruit>):ArrayAdapter<Fruit>(activity,resourceId,data){//定义主构造函数
    //将Activity的实例,listView子项布局的id和数据源传递进来
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {//重写getView()方法,这个方法再每个子项被滚动到屏幕内的时候会被调用
        val view=LayoutInflater.from(context).inflate(resourceId,parent,false)//layoutInflater来为这个子项加载传入的的布局
    //inflate()方法传入三个参数;第一个是加载的布局id,第二个是给加载好的布局再添加一个父布局,第三个是false表示只让我们在父布局中声明layout属性生效,但不会为这个view
    //添加父布局
        val fruitImage:ImageView=view.findViewById(R.id.fruitImage)
        val fruitName:TextView=view.findViewById(R.id.fruitName)
        val fruit =getItem(position)//获取当前项的Fruit实例
        if (fruit !=null){
            fruitImage.setImageResource(fruit.imageId)//设置显示的图片
            fruitName.text=fruit.name//设置显示的文字

        }
        return view//将布局返回
    }
}


action_main.xml


<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=".MainActivity">

    <ListView
        android:id="@+id/listView"
        android:layout_width="409dp"
        android:layout_height="729dp"
        tools:layout_editor_absoluteX="1dp"
        tools:layout_editor_absoluteY="1dp" />
androidx.constraintlayout.widget.ConstraintLayout>

提升ListView的运行效率

MainActivity.kt

package com.example.a20200611study

import android.app.Activity
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.TextView
import kotlinx.android.synthetic.main.activity_main.*
//目标:定制ListView界面
//todo 理解全部代码
class MainActivity : AppCompatActivity() {
    private val  fruitList= ArrayList<Fruit>()//定义list集合存储水果

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()//初始化水果数据
        val adapter = FruitAdapter(this,R.layout.fruit_item,fruitList)//创建FruitAdapter对象
        listView.adapter=adapter//FruitAdapter对象作为适配器传递给ListView


    }
    private  fun initFruits(){//定义初始化水果的方法
        repeat(2){//执行添加数据两遍
            fruitList.add(Fruit("苹果",R.drawable.tp1))//使用构造函数将水果的名称和图片传入,然后把创建好的对象传入水果列表中
            fruitList.add(Fruit("香蕉",R.drawable.tp2))
            fruitList.add(Fruit("橘子",R.drawable.tp5))
            fruitList.add(Fruit("橙子",R.drawable.tp10))
            fruitList.add(Fruit("李子",R.drawable.tp11))
            fruitList.add(Fruit("桃",R.drawable.tp12))
            fruitList.add(Fruit("三明治",R.drawable.tp6))
            fruitList.add(Fruit("樱桃",R.drawable.tp7))
            fruitList.add(Fruit("甜品",R.drawable.tupian))
            fruitList.add(Fruit("沙琪玛",R.drawable.tp7))
        }
}

}
class Fruit(val name:String,val imageId:Int)//类有两个资源,一个是水果的名称,一个是图片的id
class FruitAdapter(activity: Activity,val resourceId:Int,data:List<Fruit>):ArrayAdapter<Fruit>(activity,resourceId,data){//定义主构造函数
    //将Activity的实例,listView子项布局的id和数据源传递进来
    inner class ViewHolder(val fruitImage:ImageView,val fruitName:TextView)//内部类ViewHolder,用于对ImageView和TextView的空间实例进行缓存
    // kotlin采用inner class关键字来定义内部类
    override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {//重写getView()方法,这个方法再每个子项被滚动到屏幕内的时候会被调用
    val view:View
        val viewHolder:ViewHolder//用于缓存控件的实例
    //convertView将之前加载好的布局进行缓存,以便之后进行重用
    if (convertView==null){
view=LayoutInflater.from(context).inflate(resourceId,parent,false)//使用layoutInflater去加载布局
        val fruitImage:ImageView=view.findViewById(R.id.fruitImage)
        val fruitName:TextView=view.findViewById(R.id.fruitName)
        viewHolder=ViewHolder(fruitImage,fruitName)//创建ViewHolder对象,并将空间的实例存放在ViewHolder里
        view.tag=viewHolder//调用View的setTag()方法,把ViewHolder重新取出
    }else{//如果convertView不为空
        view=convertView//则直接对convertView进行重用
        viewHolder=view.tag as ViewHolder//view强制类型转换为ViewHolder
    }
//    val view=LayoutInflater.from(context).inflate(resourceId,parent,false)//layoutInflater来为这个子项加载传入的的布局
//    //inflate()方法传入三个参数;第一个是加载的布局id,第二个是给加载好的布局再添加一个父布局,第三个是false表示只让我们在父布局中声明layout属性生效,但不会为这个view
//    //添加父布局

        val fruit =getItem(position)//获取当前项的Fruit实例
        if (fruit !=null){
          viewHolder.fruitImage.setImageResource(fruit.imageId)
            viewHolder.fruitName.text=fruit.name

        }
        return view//将布局返回
    }
}


ListView的点击事件

指定需要声明参数的小技巧:按住Ctrl键点击所要编写的函数,来查看源码,查看待实现的方法中接收的参数就是我们在参数列表中声明的参数

     //点击事件
        listView.setOnItemClickListener { _, _, position, _ ->//为listView注册点击事件监听器,kotlin允许我们将没有用到的参数使用下划线来代替,参数位置不能改变
       // listView.setOnItemClickListener { parent, view, position, id ->//为listView注册点击事件监听器
            val fruit =fruitList[position]//通关position参数判断用户点击的是哪一个子项,获取相应的水果
            Toast.makeText(this,fruit.name,Toast.LENGTH_LONG).show()
        }
//

RecyclerView

RecycleRView为增强版的ListView

将RecyclerView库引入我们的项目中,当不能确定最新的版本号的时候,可以填入1.0.0,若有新的版本,AS会主动提醒你,并告诉你最新的版本号

第一行代码第三版笔记_第5张图片

第一行代码第三版笔记_第6张图片

dependencies {
    implementation 'androidx.recyclerview:recyclerview:1.0.0'
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

gradle修改完,要点击右上角的Sync Now进行同步

RecyclerView的基本使用

fruit_ item.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/fruitImage"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp" />

    <TextView
        android:id="@+id/fruitName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:layout_marginLeft="10dp" />
LinearLayout>

MainActivity.kt

package com.example.a20200611study
//变量识别不了的原因:变量的位置放错误;方法的入口参数错误
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_main.*
//目标:定制ListView界面

class MainActivity : AppCompatActivity() {
    private val  fruitList= ArrayList<Fruit>()//定义list集合存储水果

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()//初始化水果数据
        val layoutManager= LinearLayoutManager(this)//创建LinearLayoutManager的对象,可以用于指定RecyclerView的布局方式
        recycleView.layoutManager=layoutManager//将LinearLayoutManager的对象设置到recycleView当中
        val  adapter = FruitAdapter(fruitList)//创建FruitAdapter的实例。并将水果数据传入FruitAdapter的构造函数中
        recycleView.adapter=adapter//调用RecyclerView的 setAdapter()方法来完成适配器设置,完成Recycler与数据之间的关联,数据通过adapter来进行传递

    }

    private  fun initFruits(){//定义初始化水果的方法
        repeat(2){//执行添加数据两遍
            fruitList.add(Fruit("苹果",R.drawable.tp1))//使用构造函数将水果的名称和图片传入,然后把创建好的对象传入水果列表中
            fruitList.add(Fruit("香蕉",R.drawable.tp2))
            fruitList.add(Fruit("橘子",R.drawable.tp5))
            fruitList.add(Fruit("橙子",R.drawable.tp10))
            fruitList.add(Fruit("李子",R.drawable.tp11))
            fruitList.add(Fruit("桃",R.drawable.tp12))
            fruitList.add(Fruit("三明治",R.drawable.tp6))
            fruitList.add(Fruit("樱桃",R.drawable.tp7))
            fruitList.add(Fruit("甜品",R.drawable.tupian))
            fruitList.add(Fruit("沙琪玛",R.drawable.tp7))

        }

    }
    class Fruit(val name:String,val imageId:Int)//类有两个资源,一个是水果的名称,一个是图片的id
    class FruitAdapter(val fruitList: List<Fruit>):RecyclerView.Adapter<FruitAdapter.ViewHolder>(){//适配器继承RecycleView.Adapter
        //并指定泛型为FruitAdapter.ViewHolder,viewHolder是FruitAdapter的一个内部类

        inner class ViewHolder(view:View): RecyclerView.ViewHolder(view){//内部类ViewHolder,用于对ImageView和TextView的空间实例进行缓存
        // kotlin采用inner class关键字来定义内部类,继承RecyclerView.ViewHolder,ViewHolder的主构造函数传入一个VIew参数,这个参数为RecyclerView子项的最外层布局
        val fruitImage:ImageView=view.findViewById(R.id.fruitImage)
            val fruitName:TextView=view.findViewById(R.id.fruitName)

        }
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {//用于创建ViewHolder实例,
          
            val view= LayoutInflater.from(parent.context).inflate(R.layout.fruit_item,parent,false)//加载fruit_item布局
            return ViewHolder(view)//将加载的布局传入到构造函数中,返回ViewHolder实例
        }

        override fun getItemCount()= fruitList.size//告诉Recycler一共有多少子项,直接返回数据源的长度


        override fun onBindViewHolder(holder: ViewHolder, position: Int) {//对RecycleView子项的数据进行赋值,会在每个子项被滚动到屏幕内的时候执行
            val fruit = fruitList[position]//通关position参数获取当前项Fruit实例
            holder.fruitImage.setImageResource(fruit.imageId)//将数据设置到ViewHolder的Imageview
            holder.fruitName.text=fruit.name//将数据设置到ViewHolder的Textview
        }

}
}


activity_main.xml


<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=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recycleView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:layout_editor_absoluteX="1dp"
        tools:layout_editor_absoluteY="1dp" />
androidx.constraintlayout.widget.ConstraintLayout>

实现横向滚动和瀑布流布局

横向滚动

MainActivity.kt

 override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()//初始化水果数据
        val layoutManager= LinearLayoutManager(this)//创建LinearLayoutManager的对象,可以用于指定RecyclerView的布局方式
        layoutManager.orientation=LinearLayoutManager.HORIZONTAL//调用LinearLayoutManager的setOritation()方法设置布局的排列方向,默认是纵向,添加参数使其
        //横向滚动
        recycleView.layoutManager=layoutManager//将LinearLayoutManager的对象设置到recycleView当中
        val  adapter = FruitAdapter(fruitList)//创建FruitAdapter的实例。并将水果数据传入FruitAdapter的构造函数中
        recycleView.adapter=adapter//调用RecyclerView的 setAdapter()方法来完成适配器设置,完成Recycler与数据之间的关联,数据通过adapter来进行传递

    }

fruit_item.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="80dp"
    android:orientation="vertical"
    android:layout_height="wrap_content">

    <ImageView
        android:id="@+id/fruitImage"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp" />

    <TextView
        android:id="@+id/fruitName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp" />
LinearLayout>

瀑布流布局

fruit_item.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:orientation="vertical"
    android:layout_height="wrap_content"
    android:layout_margin="5dp"
    >

    <ImageView
        android:id="@+id/fruitImage"
        android:layout_width="40dp"
        android:layout_height="40dp"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp" />

    <TextView
        android:id="@+id/fruitName"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:gravity="left"
        android:layout_marginTop="10dp" />
LinearLayout>

MainActivity.kt

package com.example.a20200611study
//变量识别不了的原因:变量的位置放错误;方法的入口参数错误
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import kotlinx.android.synthetic.main.activity_main.*
//目标:定制ListView界面
class MainActivity : AppCompatActivity() {
    private val  fruitList= ArrayList<Fruit>()//定义list集合存储水果

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initFruits()//初始化水果数据
  val layoutManager =StaggeredGridLayoutManager(3,StaggeredGridLayoutManager.VERTICAL)//创建StaggeredGridLayoutManager的实例
        //第一个参数为指定布局的列数,传入3表示会把布局分为3列;第二个参数用于指定布局的排列方向,表示布局纵向排列
        recycleView.layoutManager=layoutManager//将创建号的实例设置到RecyclerView当中
        val  adapter = FruitAdapter(fruitList)//创建FruitAdapter的实例。并将水果数据传入FruitAdapter的构造函数中
        recycleView.adapter=adapter//调用RecyclerView的 setAdapter()方法来完成适配器设置,完成Recycler与数据之间的关联,数据通过adapter来进行传递

    }

    private  fun initFruits(){//定义初始化水果的方法
        repeat(2){//执行添加数据两遍
            fruitList.add(Fruit(getRandomLengthString("苹果"),R.drawable.tp1))//使用构造函数将水果的名称和图片传入,然后把创建好的对象传入水果列表中
            //名字改为getRandomLengthString方法生成,保证各水果的名称长短差距比较大,子项的高度不同
            fruitList.add(Fruit(getRandomLengthString("香蕉"),R.drawable.tp2))
            fruitList.add(Fruit(getRandomLengthString("橘子"),R.drawable.tp5))
            fruitList.add(Fruit(getRandomLengthString("橙子"),R.drawable.tp10))
            fruitList.add(Fruit(getRandomLengthString("李子"),R.drawable.tp11))
            fruitList.add(Fruit(getRandomLengthString("桃"),R.drawable.tp12))
            fruitList.add(Fruit(getRandomLengthString("三明治"),R.drawable.tp6))
            fruitList.add(Fruit(getRandomLengthString("樱桃"),R.drawable.tp7))
            fruitList.add(Fruit(getRandomLengthString("甜品"),R.drawable.tupian))
            fruitList.add(Fruit(getRandomLengthString("沙琪玛"),R.drawable.tp7))

        }

    }
    private fun  getRandomLengthString(str:String):String{
        val n =(1..20).random()//调用range对象的random()函数来创造一个1到20之间的随机数
        val builder =StringBuilder()
        repeat(n){//重复随机数的次数
            builder.append(str)
        }
        return builder.toString()
    }

    class Fruit(val name:String,val imageId:Int)//类有两个资源,一个是水果的名称,一个是图片的id
    class FruitAdapter(val fruitList: List<Fruit>):RecyclerView.Adapter<FruitAdapter.ViewHolder>(){//适配器继承RecycleView.Adapter
        //并指定泛型为FruitAdapter.ViewHolder,viewHolder是FruitAdapter的一个内部类

        inner class ViewHolder(view:View): RecyclerView.ViewHolder(view){//内部类ViewHolder,用于对ImageView和TextView的空间实例进行缓存
        // kotlin采用inner class关键字来定义内部类,继承RecyclerView.ViewHolder,ViewHolder的主构造函数传入一个VIew参数,这个参数为RecyclerView子项的最外层布局
        val fruitImage:ImageView=view.findViewById(R.id.fruitImage)
            val fruitName:TextView=view.findViewById(R.id.fruitName)

        }
        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {//用于创建ViewHolder实例,

            val view= LayoutInflater.from(parent.context).inflate(R.layout.fruit_item,parent,false)//加载fruit_item布局
            return ViewHolder(view)//将加载的布局传入到构造函数中,返回ViewHolder实例
        }

        override fun getItemCount()= fruitList.size//告诉Recycler一共有多少子项,直接返回数据源的长度


        override fun onBindViewHolder(holder: ViewHolder, position: Int) {//对RecycleView子项的数据进行赋值,会在每个子项被滚动到屏幕内的时候执行
            val fruit = fruitList[position]//通关position参数获取当前项Fruit实例
            holder.fruitImage.setImageResource(fruit.imageId)//将数据设置到ViewHolder的Imageview
            holder.fruitName.text=fruit.name//将数据设置到ViewHolder的Textview
        }

}
}

RecyclerView的点击事件

MainActivity.kt

  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {//用于创建ViewHolder实例,
            //todo
            val view= LayoutInflater.from(parent.context).inflate(R.layout.fruit_item,parent,false)//加载fruit_item布局
            /********************************点击事件**************************************************/
            val viewHolder=ViewHolder(view)
            viewHolder.itemView.setOnClickListener {//itemView表示的是最外层布局
                val  position =viewHolder.adapterPosition//获取用户点击的postion
                val  fruit=fruitList[position]//通过postion拿到相应的Fruit实例
                Toast.makeText(parent.context,"你点击了项 ${fruit.name}",Toast.LENGTH_SHORT).show()
            }
            viewHolder.fruitImage.setOnClickListener {//给图片设置的单击事件监听器
                val  position =viewHolder.adapterPosition//获取用户点击的postion
                val  fruit=fruitList[position]//通过postion拿到相应的Fruit实例
                Toast.makeText(parent.context,"你点击了项 ${fruit.name}",Toast.LENGTH_SHORT).show()
            }

            return viewHolder//返回ViewHolder实例
            /********************************点击事件**********************************************/

        }

制作 9-Patch图片

注意:图片必须要png格式的

制作精美的聊天界面

build.gradle(Module:app)

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.example.a20200612study2"
        minSdkVersion 16
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.3.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}

第一行代码第三版笔记_第7张图片

msg_ left_ item.xml


<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="10dp"
    >
<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="left"
    android:background="@drawable/qipaozuo"
    >
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:id="@+id/leftMsg"
        android:layout_margin="10dp"
        android:textColor="#000"
        />
LinearLayout>
FrameLayout>

msg_ right_ item.xml


<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="10dp"
    >
    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="right"
        android:background="@drawable/qipao"
        >
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:id="@+id/rightMsg"
            android:layout_margin="10dp"
            android:textColor="#000"
            />
    LinearLayout>
FrameLayout>

MainActivity.kt

package com.example.a20200612study2

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.msg_left_item.view.*
import kotlinx.android.synthetic.main.msg_right_item.view.*
import java.text.FieldPosition

class MainActivity : AppCompatActivity() ,View.OnClickListener{
    private val msgList=ArrayList<Msg>()
    private var adapter:MsgAdapter?=null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        initMsg()
        val  layoutManager= LinearLayoutManager(this)
        recyclerView.layoutManager=layoutManager//给RecyclerView指定LayoutManager
        adapter=MsgAdapter(msgList)//给RecyclerView指定适配器
        recyclerView.adapter=adapter
        send.setOnClickListener(this)
    }
    class Msg(val content:String,val type:Int){//content为消息内容,Type表示消息的类型
        companion object{//定义常量的关键字是const,在单例类、companion object或顶层方法才能实用const关键字
            const val TYPE_RECEIVED=0//TYPE_RECEIVED表示收到消息
            const val TYPE_SENT=1;//TYPE_SENT表示发送消息
        }
    }
    class MsgAdapter(val msgList: List<Msg>):RecyclerView.Adapter<RecyclerView.ViewHolder>(){
        inner class LeftViewHolder(view: View):RecyclerView.ViewHolder(view){//创建viewHolder用于缓存布局的控件
            val leftMsg:TextView=view.findViewById(R.id.leftMsg)
        }
        inner class RightViewHolder(view:View):RecyclerView.ViewHolder(view){//创建viewHolder用于缓存布局的控件
            val rightMsg:TextView=view.findViewById(R.id.rightMsg)
        }
        override  fun  getItemViewType(position:Int):Int{
            val msg=msgList[position]//根据postion来获取消息
            return msg.type//返回消息的类型
        }
        override fun onCreateViewHolder(parent:ViewGroup,viewType:Int)=if (viewType==Msg.TYPE_RECEIVED){//根据不同的viewType创建不同的界面
            val view= LayoutInflater.from(parent.context).inflate(R.layout.msg_left_item,parent,false)
            LeftViewHolder(view)//根据不同的viewType来加载不同的ViewHolder
        }else{
            val view=LayoutInflater.from(parent.context).inflate(R.layout.msg_right_item,parent,false)
            RightViewHolder(view)//根据不同的viewType来加载不同的ViewHolder
        }
        override  fun onBindViewHolder(holder:RecyclerView.ViewHolder,position:Int){
            val msg=msgList[position]
            when(holder){//判断ViewHolder类型
                is LeftViewHolder->holder.leftMsg.text=msg.content//is相当于if,如果是leftViewHolder就将内容显示到左边的消息布局
                is RightViewHolder->holder.rightMsg.text=msg.content//is相当于if,如果是RightViewHolder就将内容显示到右边的消息布局
                else -> throw  IllegalArgumentException()
            }
        }
override  fun getItemCount()=msgList.size
    }

    override fun onClick(v: View?) {
     when(v){
         send->{
             val content=inputText.text.toString()//获取EditText中的内容
             if (content.isNotEmpty()){//如果内容不为空字符串
                 val msg = Msg(content,Msg.TYPE_SENT)//创建一个新的Msg对象
                 msgList.add(msg)//Msg对象添加到msgList列表中去
                 adapter?.notifyItemInserted(msgList.size-1)//调用适配器的方法,当有新消息时,刷新RecyclerView中的显示
                 //notifyDataSetChanged()方法,将RecyclerView 中所有可谏的元素全部刷新
                 recyclerView.scrollToPosition(msgList.size-1)//将RecyclerView定位到最后一行
                 inputText.setText("")//清空输入框中的内容
             }
         }
     }
    }
    private  fun initMsg(){//初始化几条数据在RecyclerView zhong xianshi
        val  msg1=Msg("你好",Msg.TYPE_RECEIVED)
        msgList.add(msg1)
        val msg2=Msg(" 你好,你是谁?",Msg.TYPE_SENT)
        msgList.add(msg2)
        val  msg3=Msg("我是tom,很高兴和你谈话",Msg.TYPE_RECEIVED)
        msgList.add(msg3)
    }
}

activity_main.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@drawable/qipao"
    tools:context=".MainActivity">
    <androidx.recyclerview.widget.RecyclerView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:id="@+id/recyclerView"
        android:layout_weight="1"
        />
  <LinearLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content">
      <EditText
          android:id="@+id/inputText"
          android:layout_height="wrap_content"
          android:layout_width="0dp"
          android:layout_weight="1"
          android:width="0dp"
          android:hint="请输入内容"
          android:maxLines="2"/>
      <Button
          android:layout_width="wrap_content"
          android:layout_height="wrap_content"
          android:id="@+id/send"
          android:text="send"/>
  LinearLayout>


LinearLayout>

Fragment

Fragment的简单使用

android:name属性来显式声明要添加的Fragment类名,注意一定要将类的包名也加上

left_ fragment.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <Button
        android:layout_gravity="center_horizontal"
        android:id="@+id/left_button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button" />
LinearLayout>

LeftFragment.kt

package com.example.myapplication

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment

//在这里一定要使用androidX的Fragment,因为他可以让Fragment的特性在所有Android系统中保持一致,系统内置的在9.0版本被废除。
class LeftFragment : Fragment() {
    //仅仅重写了onCreateView方法
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        //通过LayoutInflater将方才定义的left_fragment加载进来
        return inflater.inflate(R.layout.left_fragment, container, false)
    }
}

right_fragment.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#00ff00"
    android:orientation="vertical">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="This is right fragment"
        android:textSize="24sp" />
LinearLayout>

RightFragment.kt

package com.example.myapplication

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
//同样方式使用Fragment加载右布局
class RightFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.right_fragment, container, false)
    }
}

another_right_fragment.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ffff00"
    android:orientation="vertical">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:text="This is another right fragment"
        android:textSize="24sp" />
LinearLayout>

AnotherRightFragment.kt

package com.example.myapplication

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment

class AnotherRightFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.another_right_fragment, container, false)
    }
}

activity_main.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    
    <fragment
        android:id="@+id/left_frag"
        android:name="com.example.myapplication.LeftFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />
    
    <FrameLayout
        android:id="@+id/frame_layout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />
LinearLayout>

MainActivity.kt

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.left_fragment.*
//完成动态添加Fragment的功能,五步走战略
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //第一步,创建添加Fragment的实例
        left_button.setOnClickListener{
            replaceFragment(AnotherRightFragment())
        }
        replaceFragment(RightFragment())
    }

    fun replaceFragment(fragment:Fragment){
        //第二步,获取FragmentManager
        val fragmentManager = supportFragmentManager
        //第三步,开启一个事务
        val transaction = fragmentManager.beginTransaction()
        //第四步,向容器内添加或者替换Fragment,一般使用replace来代替,传递id和添加的Fragment实例
        transaction.replace(R.id.frame_layout,fragment)
        //第五步,提交事务
        transaction.commit()
    }
}

在Fragment中实现返回栈

MainActivity.kt

fun replaceFragment(fragment:Fragment){
        //第二步,获取FragmentManager
        val fragmentManager = supportFragmentManager
        //第三步,开启一个事务
        val transaction = fragmentManager.beginTransaction()
        //第四步,向容器内添加或者替换Fragment,一般使用replace来代替,传递id和添加的Fragment实例
        transaction.replace(R.id.frame_layout,fragment)
        transaction.addToBackStack(null)//将一个事物添加到返回栈中,接收一个名字用户描述返回栈的状态,一般传入null即可
        //第五步,提交事务
        transaction.commit()
    }

Fragment和Activity之间的交互

Activity调用Fragment里的方法

val fragment =supportFragmentManager.findFragmentById(R.id.left_frag) as LeftFragment//获取Fragment的实例
//可直接使用布局文件中定义的Fragment 的id名称来自动获取相应的Fragment实例
val  fragment2 =left_frag as LeftFragment

Fragment中调用Activity里面的方法

MainActivity.kt

 if (activity !=null){//由于getActivity()方法有可能返回null,需要判空处理
            val mainActivity=activity as MainActivity//调用getActivity()方法来得到和当前Fragment相关联的Activity实例
            //当Fragment中需要使用Context对象时,也可以调用getActivity()方法,因为获取到的Activity本身就是一个Context对象
            
        }

不同的Fragment之间的通信

Fragment找Activity,Activity获取另一个Fragment实例

Fragment的生命周期

有四种状态:运行、暂停、停止和销毁四种状态。

运行状态:Fragment相关联的Activity正在运行;

暂停:当一个Activity进入暂停状态(另一个为占满屏幕的Activity进入栈顶),则与此相关联的Fragment也暂停;

停止:Activity停止或者FragmentTransaction的remove和replace移除被调用,但在此之间调用了addToBackStack方法;

终止:Activity被销毁或者FragmentTransaction的remove和replace移除被调用。未调用addToBackStack方法。

回调方法

onAttach:Fragment和Activity建立关联;

onCreateView:为Fragment创建视图时调用;

onActivityCreated:确保与Fragment相关联的Activity已经创建完毕时调用;

onDestroyView:与Fragment关联的视图被移除时调用;

onDetach:两者解除关联时调用。
第一行代码第三版笔记_第8张图片

体验Fragment的生命周期

RightFragment.kt

package com.example.myapplication

import android.content.Context
import android.nfc.Tag
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlin.math.log

//同样方式使用Fragment加载右布局
class RightFragment : Fragment() {
    companion object{//类似java的静态方法
        const val TAG="RightFragment"//用const关键字来定义常量


    }

    override fun onAttach(context: Context) {
        super.onAttach(context)
        Log.d(TAG,"onAttach")

    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d(TAG,"onCreate")
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        Log.d(TAG,"onCreateView")
        return inflater.inflate(R.layout.right_fragment, container, false)////通过LayoutInflater来加载.right_fragment布局
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        Log.d(TAG,"onActivityCreated")
    }

    override fun onStart() {
        super.onStart()
        Log.d(TAG,"onStart")
    }

    override fun onResume() {
        super.onResume()
        Log.d(TAG,"onResume")
    }

    override fun onPause() {
        super.onPause()
        Log.d(TAG,"onPause")
    }

    override fun onStop() {
        super.onStop()
        Log.d(TAG,"onStop")
    }

    override fun onDestroyView() {
        super.onDestroyView()
        Log.d(TAG,"onDestroyView")
    }

    override fun onDestroy() {
        super.onDestroy()
        Log.d(TAG,"onDestroy")
    }

    override fun onDetach() {
        super.onDetach()
        Log.d(TAG,"onDetach")
    }
}

日志运行结果见下图

第一行代码第三版笔记_第9张图片

在Fragment中可通过onSaveInstanceState()方法来保存数据

动态加载布局的技巧

使用限定符

在新建res目录下新建具有限定符的目录,用于根据不同的情况来加载不同的视图

第一行代码第三版笔记_第10张图片

layout-large目录下的activity_main.xml


<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    
    <FrameLayout
        android:layout_width="0dp"
        android:id="@+id/leftFrag"
        android:name="com.example.myapplication.LeftFragment"
        android:layout_height="match_parent"
        android:background="@color/colorPrimaryDark"
        android:layout_weight="1"/>
    <FrameLayout
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3"
        android:background="@color/colorAccent"
        android:name="com.example.myapplication.RightFragment"
        android:id="@+id/rightFrag"/>
LinearLayout>

layout目录下的activity_main.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal">
    
    
    <fragment
        android:id="@+id/left_frag"
        android:name="com.example.myapplication.LeftFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

LinearLayout>

MainActivity.kt

package com.example.myapplication

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.left_fragment.*
//完成动态添加Fragment的功能,五步走战略
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        //第一步,创建添加Fragment的实例
//        left_button.setOnClickListener{
//            replaceFragment(AnotherRightFragment())
//        }
//        replaceFragment(RightFragment())
        /***************Fragment和Activity之间的交互*****************************/
//        val fragment =supportFragmentManager.findFragmentById(R.id.left_frag) as LeftFragment//获取Fragment的实例
//        //可直接使用布局文件中定义的Fragment 的id名称来自动获取相应的Fragment实例
//        val  fragment2 =left_frag as LeftFragment
        //____________________________________________________________
//        if (activity !=null){//由于getActivity()方法有可能返回null,需要判空处理
//            val mainActivity=activity as MainActivity//调用getActivity()方法来得到和当前Fragment相关联的Activity实例
//            //当Fragment中需要使用Context对象时,也可以调用getActivity()方法,因为获取到的Activity本身就是一个Context对象
//
//        }


        /***************Fragment和Activity之间的交互*****************************/
    }

//replaceFragment()方法用于替换Fragment 的视图
//    fun replaceFragment(fragment:Fragment){
//        //第二步,获取FragmentManager
//        val fragmentManager = supportFragmentManager
//        //第三步,开启一个事务
//        val transaction = fragmentManager.beginTransaction()
//        //第四步,向容器内添加或者替换Fragment,一般使用replace来代替,传递id和添加的Fragment实例
//        transaction.replace(R.id.frame_layout,fragment)
//        transaction.addToBackStack(null)//将一个事物添加到返回栈中,接收一个名字用户描述返回栈的状态,一般传入null即可
//        //第五步,提交事务
//        transaction.commit()
//    }
}

运行效果如图

第一行代码第三版笔记_第11张图片
第一行代码第三版笔记_第12张图片

Android中常见的限定符

板上是双屏,手机上是单屏。常用限定符如下:

屏幕大小的small、normal、large和xlarge;

分辨率大小的ldpi(<120)、mdpi(120160)、hdpi(160240)、xhdpi(240320)以及xxhdpi(320480);

方向land(横屏)和port(竖屏)

使用最小宽度限定符

使用large解决了单双页判断问题,但large多大是个问题?我们在这里使用最小宽度限定符来解决。其是对屏幕的宽度指定一个最小值(以dp为单位),然后以其为临界点。
我们建立layout-sw600dp文件夹和activity_main.xml布局,代码和上面的一模一样。此意味着,当屏幕宽度大于等于600dp时,会加载layout-sw600dp/activity_main布局,否则加载layout/activity_main布局。

声明

部分代码与文字源于《第一行代码》第三版之探究Fragment(六),若想看详情,请点击查看

Fragment的最佳实践:一个简易版的新闻应用

第一行代码第三版笔记_第13张图片

activity_main


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/newsTitleLayout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <fragment
        android:id="@+id/newsTitleFrag"
        android:name="com.example.a20200616study.NewsTitleFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

FrameLayout>

activity_main(sw600dp)


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <fragment
        android:id="@+id/newsTitleFrag"
        android:name="com.example.a20200616study.NewsTitleFragment"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="1" />

    <FrameLayout
        android:id="@+id/newsContentLayout"
        android:layout_width="0dp"
        android:layout_height="match_parent"
        android:layout_weight="3" >

        <fragment
            android:id="@+id/newsContentFrag"
            android:name="com.example.a20200616study.NewsContentFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    FrameLayout>

LinearLayout>

activity_ news_ content.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <fragment
        android:id="@+id/newsContentFrag"
        android:name="com.example.a20200616study.NewsContentFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />

LinearLayout>

news_ content frag.xml


<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:id="@+id/contentLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:visibility="invisible" >

        <TextView
            android:id="@+id/newsTitle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:gravity="center"
            android:padding="10dp"
            android:textSize="20sp" />

        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="#000" />

        <TextView
            android:id="@+id/newsContent"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_weight="1"
            android:padding="15dp"
            android:textSize="18sp" />

    LinearLayout>

    <View
        android:layout_width="1dp"
        android:layout_height="match_parent"
        android:layout_alignParentLeft="true"
        android:background="#000" />

RelativeLayout>

news item.xml



<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/newsTitle"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:maxLines="1"
    android:ellipsize="end"
    android:textSize="18sp"
    android:paddingLeft="10dp"
    android:paddingRight="10dp"
    android:paddingTop="15dp"
    android:paddingBottom="15dp" />

news_ title_ frag.xml


<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent">
    
    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/newsTitleRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />


LinearLayout>

News.kt

package com.example.a20200616study

class News(val title: String, val content: String)//创建构造函数,存放标题和内容

NewsContentActivity.kt

package com.example.a20200616study

import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_news_content.*
import kotlinx.android.synthetic.main.news_content_frag.*

class NewsContentActivity : AppCompatActivity() {

    companion object {//类似java的静态方法
        fun actionStart(context: Context, title: String, content: String) {//函数传入标题和内容字段
            val intent = Intent(context, NewsContentActivity::class.java).apply {//::相当于.  进行跳转功能
                putExtra("news_title", title);//用键值对的方式进行传值
                putExtra("news_content", content)
            }
            context.startActivity(intent)//用context指明跳转前的activity
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_news_content)
        val title = intent.getStringExtra("news_title") // 获取传入的新
闻标题
        val content = intent.getStringExtra("news_content") // 获取传入
的新闻内容
        if (title != null && content != null) {
            val fragment = newsContentFrag as NewsContentFragment//强制类型转换
            fragment.refresh(title, content) //刷新NewsContentFragment界面
        }
    }

}

NewsContentFragment.kt

package com.example.a20200616study

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.news_content_frag.*
/*
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.news_content_frag.*
 */



class NewsContentFragment : Fragment() {//继承Fragment

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.news_content_frag, container, false)//加载news_content_frag布局
    }

    fun refresh(title: String, content: String) {//创建刷新的构造方法,传入标题和内容等字段
        contentLayout.visibility = View.VISIBLE//将布局设为可见
        newsTitle.text = title // 刷新新闻的标题
        newsContent.text = content // 刷新新闻的内容
    }

}

NewsTitleFragment.kt

package com.example.a20200616study

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.activity_news_content.*
import kotlinx.android.synthetic.main.news_title_frag.*
import java.util.*
import kotlin.collections.ArrayList

class NewsTitleFragment : Fragment() {//继承Fragment

    private var isTwoPane = false//创建变量用来标记是否为双页

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.news_title_frag, container, false)//加载news_title_frag的视图
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        isTwoPane = activity?.findViewById<View>(R.id.newsContentLayout) != null//获取Fragment组件,指定泛型为view,并用?.来保证非空
        val layoutManager = LinearLayoutManager(activity)//根据activity来获取layoutManager变量
        newsTitleRecyclerView.layoutManager = layoutManager//为新闻列表绑定布局管理器
        val adapter = NewsAdapter(getNews())//创建适配器对象
        newsTitleRecyclerView.adapter = adapter//用adapter将新闻与recycler绑定在一起
    }

    private fun getNews(): List<News> {//传入new
        val newsList = ArrayList<News>()//用数组来存放新闻集合
        for (i in 1..50) {//用for in 来生成随机数
            val news =
                News("This is news title $i", getRandomLengthString("This is news content $i. "))//传入内容
            newsList.add(news)//将内容传入新闻集合中
        }
        return newsList//返回以供调用
    }

    private fun getRandomLengthString(str: String): String {//生成长度随机
        val n = Random().nextInt(20) + 1//在20之内的随机数
        return str * n//返回以供调用
    }

    inner class NewsAdapter(val newsList: List<News>) ://内部类NewAdapter,传入newsList集合用于存放消息
        RecyclerView.Adapter<NewsAdapter.ViewHolder>() {//继承RecyclerView

        inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
            val newsTitle: TextView = view.findViewById(R.id.newsTitle)//加载标题组件
        }

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {//重写onCreateViewHolder方法
            val view =
                LayoutInflater.from(parent.context).inflate(R.layout.news_item, parent, false)//加载news_item视图用于加载标题
            val holder = ViewHolder(view)//将news_item视图变成变量来方便调用
            holder.itemView.setOnClickListener {//设置标题项目时间点击事件监听器
                val news = newsList[holder.adapterPosition]//根据标题的位置来确定新闻
                if (isTwoPane) {
                    // 如果是双页模式,则刷新NewsContentFragment中的内容
                    val fragment = newsContentFrag as NewsContentFragment//强制类型转换
                    fragment.refresh(news.title, news.content) //刷新NewsContentFragment界面
                } else {
                    // 如果是单页模式,则直接启动NewsContentActivity
                    NewsContentActivity.actionStart(parent.context, news.title, news.content);//将NewsConenetActivity要做启动前的activity
                }
            }
            return holder//返回news_item视图变成变量来方便调用
        }

        override fun onBindViewHolder(holder: ViewHolder, position: Int) {
            val news = newsList[position]//根据位置来确定新闻
            holder.newsTitle.text = news.title//绑定新闻的标题
        }

        override fun getItemCount() = newsList.size//返回列表的长度

    }
}

String.kt

package com.example.a20200616study

fun String.lettersCount(): Int {
    var count = 0
    for (char in this) {
        if (char.isLetter()) {
            count++
        }

    }
    return count
}

operator fun String.times(n: Int) = this.repeat(n)//循环n边

MainActivity.kt

package com.example.a20200616study

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

BroadcastReceiver

概念

标准广播(normal broadcasts):广播发出后,同一时刻的所有BroadcastReceiver会收到这条广播,无法被截断

有序广播(ordered broadcasts):广播发出后,同时刻只有一个BroadcastReceiver会收到这条广播,有先后顺序,可被截断

接收系统广播

动态注册监听时间变化

当有相应的广播发出时,相应的BroadcastReceiver就能收到该广播,在内部进行处理

静态注册BroadcastReceiver:在AndroidManifest.xml中注册

动态注册BroadcastReceiver:在代码中注册

动态注册的方式编写一个能够监听事件变化的程序

package com.example.a20200627study

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.ResultReceiver
import android.widget.Toast

class MainActivity : AppCompatActivity() {
    lateinit var timeChangeReceiver: TimeChangeReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val intentFilter=IntentFilter()//创建IntentFilter的实例
        intentFilter.addAction("android.intent.action.TIME_TICK")//添加action,当系统时间发生变化时,
        // 系统发出的正是一条值为android.intent.action.TIME_TICK的广播
        //BroadcastReceiver想要监听什么广播,就要在这里添加相应的action
        timeChangeReceiver=TimeChangeReceiver()//创建一个TimeChangeReceiver的实例
        registerReceiver(timeChangeReceiver,intentFilter)//注册广播,传入广播接收器和intentFilter的实例
        //TimeChangeReceiver就会收到所有值为android.intent.action.TIME_TICK的广播
    }

    override fun onDestroy() {
        super.onDestroy()
        unregisterReceiver(timeChangeReceiver)//销毁的时候取消注册
    }
    inner class TimeChangeReceiver :BroadcastReceiver(){//内部类继承BroadcastReceiver
        override fun onReceive(p0: Context?, p1: Intent?) {//重写父类的onReceiver()方法,当广播来临(系统事件发生变化),onReceiver()方法就会得到执行
            Toast.makeText(applicationContext,"Time has changed",Toast.LENGTH_SHORT).show()
        }

    }
}

静态注册实现开机启动

创建BroadcastReceiver

第一行代码第三版笔记_第14张图片

新建BroadcastReceiver

Exporte属性表示是否允许这个BroadcastReceiver接收本程序以外的广播,Enabled属性表示启用这个BroadcastReceiver

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YBG0YMre-1593337413497)(C:\Users\11200\AppData\Roaming\Typora\typora-user-images\image-20200627104601281.png)]

manifest.xml


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.a20200627study2">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>//声明接收系统的开机广播所需的权限
    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        /****************静态注册manifest*********************/
        
             <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>  //android系统启动完成后会发出android.intent.action.BOOT_COMPLETED的广播,声明action
            intent-filter>
        receiver>
           /****************静态注册manifest*********************/
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>
    application>

manifest>

BootCompleteReceiver.kt

package com.example.a20200627study2

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast

class BootCompleteReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {
        // This method is called when the BroadcastReceiver is receiving an Intent broadcast.
       Toast.makeText(context,"Boot Complete",Toast.LENGTH_LONG).show()

    }
}

注意:BroadcastReceiver不允许开启线程,当onReceive()方法运行了较长时间而没有结束时,程序就会出现错误

发送自定义广播

manifest.xml


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.a20200627study3">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        
            <intent-filter>
                <action android:name="com.example.a20200627study3.MyBroadcastReceiver"/> //指明MyBroadcastReceiver接收的广播值
        intent-filter>
            
        receiver>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>
    application>

manifest>

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">
<Button
    android:id="@+id/button"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
   android:text="Send Broadcast"
    />

LinearLayout>

MyBroadcastReceiver.kt

package com.example.a20200627study3

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast

class MyBroadcastReceiver : BroadcastReceiver() {//定义一个BroadcastReceiver来接收此广播

    override fun onReceive(context: Context, intent: Intent) {//执行接收广播后执行的逻辑
        // This method is called when the BroadcastReceiver is receiving an Intent broadcast.
        Toast.makeText(context,"received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show()
    }
}

MainActivity.kt

package com.example.a20200627study3

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener {
            val intent =Intent("com.example.a20200627study3.MyBroadcastReceiver")//构建要广播的值,广播是通过intent来进行发送的
            intent.setPackage(packageName)//传入当前程序的包名,packageName==getPackageName() 用于获取当前应用程序的包名,指定这条广播是发送给哪个应用程序
            //变成显式广播,因静态注册的广播接收器无法接收隐式广播,而自定义广播都是隐式广播
            sendBroadcast(intent)//将广播发送出去
            //所有监听com.example.a20200627study3.MyBroadcastReceiver这条广播的BroadcastReceiver就会收到消息,为标准广播
        }
    }
}

发送有序广播

manifest.xml


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.a20200627study3">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <receiver
            android:name=".AnotherBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter android:priority="100">//设置优先级,优先级比较高的广播接收其会先收到广播,这里将AntherBroadcastReceiver 的优先级设置为100,以保证一定会在MyBroadcastReceiver之前收到广播
                <action android:name="com.example.a20200627study3.MY_BROADCAST" />
            intent-filter>
        receiver>
        <receiver
            android:name=".MyBroadcastReceiver"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="com.example.a20200627study3.MY_BROADCAST"/>
            intent-filter>

        receiver>

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            intent-filter>
        activity>
    application>

manifest>

MyBroadcastReceiver.kt

package com.example.a20200627study3

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast

class MyBroadcastReceiver : BroadcastReceiver() {//定义一个BroadcastReceiver来接收此广播

    override fun onReceive(context: Context, intent: Intent) {//执行接收广播后执行的逻辑
        // This method is called when the BroadcastReceiver is receiving an Intent broadcast.
        Toast.makeText(context,"received in MyBroadcastReceiver",Toast.LENGTH_SHORT).show()
    }
}

AnotherBroadcastReceiver.kt

package com.example.a20200627study3

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toast

class AnotherBroadcastReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {//执行接收广播后执行的逻辑
        // This method is called when the BroadcastReceiver is receiving an Intent broadcast.

        Toast.makeText(context,"received in AntherBroadcastReceiver",Toast.LENGTH_SHORT).show()
        abortBroadcast()//表示将这条广播截断,后面的广播接收器将无法收到这条广播
    }
}

MainActivity.kt

package com.example.a20200627study3

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        button.setOnClickListener {
            val intent =Intent("com.example.a20200627study3.MY_BROADCAST")//构建要广播的值,广播是通过intent来进行发送的
            intent.setPackage(packageName)//传入当前程序的包名,packageName==getPackageName() 用于获取当前应用程序的包名,指定这条广播是发送给哪个应用程序
            //变成显式广播,因静态注册的广播接收器无法接收隐式广播,而自定义广播都是隐式广播
           sendOrderedBroadcast(intent,null)//发送有序广播,第一个参数为intent,第二个参数为与权限相关的字符串
            //所有监听com.example.a20200627study3.MyBroadcastReceiver这条广播的BroadcastReceiver就会收到消息,为标准广播
        }
    }
}

广播的最佳实践:实现强制下线功能

Manifest.xml


<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.a20200628study">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".LoginActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>//将主程序设置为LoginActivity
                <category android:name="android.intent.category.LAUNCHER"/>//将首次启动的activity设置为LoginActivity
            intent-filter>
        activity>
        <activity android:name=".MainActivity">
        activity>
    application>

manifest>

ActivityCollector.kt

package com.example.a20200628study

import android.app.Activity

object ActivityCollector{//类似java的静态方法,用于管理所有的Activity,
private val activities=ArrayList<Activity>()//定义一个集合来存放所有活动,泛型的类型指定为activity
    fun addActivity(activity: Activity){//创建添加activity的方法
        activities.add(activity)//添加传入的activity到集合中
    }
    fun removeActivity(activity: Activity){//创建移除activity的方法
        activities.remove(activity)//移除集合中传入的activity
    }
    fun finishALL(){//创建关闭所有activity的方法
        for (activity in activities){//用for in 来进行循环,创建的变量名为activity,循环的变量为activities
            if(!activity.isFinishing){//如果acitivity没有关闭
                activity.finish()//关闭没有关闭的activity
            }
        }
        activities.clear()//清除集合
    }

}

BaseActivity.kt

package com.example.a20200628study

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.Bundle
import android.os.PersistableBundle
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity

open class BaseActivity : AppCompatActivity(){//创建BaseActivity类作为所有Activity的父类
    //在BaseActivity中动态注册一个广播接收器,就能使所有的activity都进行注册,因为所有的activity都继承BaseActivity
lateinit var receiver:ForceOfflineReceiver//lateinit关键字:修饰后,可以晚些对这个变量进行初始化,不用一开始就将它赋值为null,并且继承自ForceOfflineReceiver
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        ActivityCollector.addActivity(this)//当BaseActivity启动时,添加BaseActivity到集合中
    }

    override fun onResume() {//处于栈顶时
        super.onResume()
        val intentFilter=IntentFilter()//创建IntentFileter的实例
        intentFilter.addAction("com.example.a20200628study.FORCE_OFFLINE")//创建要传递的广播
        receiver=ForceOfflineReceiver()//对ForceOfflineReceiver进行初始化
        registerReceiver(receiver,intentFilter)//注册ForceOfflineReceiver广播
    }

    override fun onPause() {//失去栈顶位置时
        super.onPause()
  unregisterReceiver(receiver)//取消BroadcastReceiver的注册
    }

    override fun onDestroy() {
        super.onDestroy()
        ActivityCollector.removeActivity(this)//当BaseActivity销毁时,将集合中的BaseActivity移除
    }
    inner class ForceOfflineReceiver :BroadcastReceiver(){//创建强制下线的广播类
        override fun onReceive(context: Context,intent: Intent) {//当接收到广播时,要处理的逻辑
            AlertDialog.Builder(context).apply {//构建一个对话框
                setTitle("Warning")//设置对话框的标题
                setMessage("You are forced to be offline,please try to login again.")//设置对话框的文本信息
                setCancelable(false)//将对话框设为不可取消
                setPositiveButton("Ok"){_,_ ->//给对话框注册确定按钮
                    ActivityCollector.finishALL()//销毁所有Activity
                    val i=Intent(context,LoginActivity::class.java)//::相当于.  编写要跳转的activity
                    context.startActivity(i)//重写启动 LoginActivity
                }
                show()//显示对话框
            }
        }

    }
}

activity_login.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textSize="18sp"
            android:text="Account:"/>
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:id="@+id/accountEdit"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"/>
    LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:textSize="18sp"
            android:text="passWord:"/>
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/passwordEdit"
            android:layout_gravity="center_vertical"
            android:inputType="textPassword"/>/
    LinearLayout>
    <Button
        android:layout_width="200dp"
        android:layout_height="60dp"
        android:id="@+id/login"
        android:layout_gravity="center_horizontal"
        android:text="Login"
        />
LinearLayout>

LoginActivity.kt

package com.example.a20200628study

import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_login.*

class LoginActivity :BaseActivity() {//继承BaseActivity
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
 setContentView(R.layout.activity_login)
    login.setOnClickListener {//当点击登录按钮时要执行的操作
        val account=accountEdit.text.toString()//获取账户信息到变量account
        val password=passwordEdit.text.toString()//获取密码信息到变量password
        //如果账号事admin 且密码是123456,就认为登录成功
        if (account=="admin"&& password=="123456"){
            val intent = Intent(this,MainActivity::class.java)
            startActivity(intent)//进行activity跳转
            finish()//关闭本activity
        }else{
            Toast.makeText(this,"账号或密码不合法",Toast.LENGTH_SHORT).show()
        }
    }
    }
}

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">
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/forceOffline"
        android:text="Send force offline broadcast"/>

LinearLayout>

MainActivity.kt

package com.example.a20200628study

import android.app.Activity
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
   forceOffline.setOnClickListener {
       val intent = Intent("com.example.a20200628study.FORCE_OFFLINE")//用inent来传递广播值
       sendBroadcast(intent)//发送广播
       //强制用户下线的逻辑不在MainActivity里,而是写在接收这条广播的BroadcastReceiver,强制下线的功能就不会依附于任何界面
   }
    }
}

数据存储全方案,详解持久化技术

持久化技术简介

将内存中的瞬时数据保存到存储设备中

文件存储

将数据存储到文件中

       fun save(inputText:String){
            val output=openFileOutput("data", Context.MODE_PRIVATE)//获取文件输出流对象,用于写入数据,第一个参数为文件名,第二个参数为文件操作模式;
            //MODE_PRIVATE表示当指定相同文件名,所写入的内容将会覆盖原文件中的内容
            //MODE_APPEND 则表示如果该文件已存在,就往文件里面追加聂荣,不存在就创建新的文件
            val writer=BufferedWriter(OutputStreamWriter(output))//获取字节输入流对象,参数是输出流对象
            writer.use {//use函数为kotlin的内置扩展函数,会保证在Lambda表达式中的代码全部执行完毕之后自动将外层的流关闭,就不用编写finall来手动关闭流
                it.write(inputText)//用字节输入流来写入inputText数据
            }
        }

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">

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/editText"
    android:hint="Type something here"
    />

LinearLayout>

MainActivity.kt

package com.example.a20200701study

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*
import java.io.BufferedWriter
import java.io.IOException
import java.io.OutputStreamWriter

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

    }

    override fun onDestroy() {//保证activity销毁之前一定会调用这个方法
        super.onDestroy()
        val inputText=editText.text.toString()//获取EditText中输入的内容
        save(inputText)//把输入的内容存储到文件中
    }
    private fun save(inputText:String){
        try {
            val output=openFileOutput("data", Context.MODE_PRIVATE)
            val writer=BufferedWriter(OutputStreamWriter(output))
            writer.use {
                it.write(inputText)
            }
        }catch (e:IOException){
            e.printStackTrace()
        }
    }
}

运行avd后,在编辑框输入内容后,退出程序,可用在Device File Explorer中查看存储的信息

第一行代码第三版笔记_第15张图片

从文件中读取数据

 fun load():String{
        val content = StringBuilder()//创建字符串构建器对象
        try {
            val input =openFileInput("data")//创建文件输入流,用于读取数据,参数是要读取的文件名
            val reader=BufferedReader(InputStreamReader(input))//创建字节输入流,参数是文件输入流
            reader.use{//利用字节输入流来读取数据
                reader.forEachLine {//forEachLine函数是Kotlin提供的一个内置扩展函数,,它会将读到的每行内容都回调到Lambda表达式中
                    content.append(it)//冰洁到StringBuilder对象当中
                }
            }
        }catch (e:IOException){
            e.printStackTrace()
        }
        return content.toString()//将读取的内容返回即可
    }

简单的文件存储与读取的案例

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">

<EditText
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/editText"
    android:hint="Type something here"
    />

LinearLayout>

MainActivity.kt

package com.example.a20200701study

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_main.*
import java.io.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val inputText =load()//读取文本中存储的文本内容,传给inputText变量
        if (inputText.isNotEmpty()){}//如果读取到的内容不为空
        editText.setText(inputText)//调用EditText的setText()方法将内容填充到EditText里
        editText.setSelection(inputText.length)//调用setSelection()方法将输入光标移动到文本的末尾位置以便继续输入
        Toast.makeText(this,"Restoring succeeded",Toast.LENGTH_SHORT).show()

    }

    override fun onDestroy() {//保证activity销毁之前一定会调用这个方法
        super.onDestroy()
        val inputText=editText.text.toString()//获取EditText中输入的内容
        save(inputText)//把输入的内容存储到文件中
    }
    private fun save(inputText:String){
        try {
            val output=openFileOutput("data", Context.MODE_PRIVATE)
            val writer=BufferedWriter(OutputStreamWriter(output))
            writer.use {
                it.write(inputText)
            }
        }catch (e:IOException){
            e.printStackTrace()
        }
    }
    //__________________________________________________________________________
    fun load():String{
        val content = StringBuilder()//创建字符串构建器对象
        try {
            val input =openFileInput("data")//创建文件输入流,用于读取数据,参数是要读取的文件名
            val reader=BufferedReader(InputStreamReader(input))//创建字节输入流,参数是文件输入流
            reader.use{//利用字节输入流来读取数据
                reader.forEachLine {//forEachLine函数是Kotlin提供的一个内置扩展函数,,它会将读到的每行内容都回调到Lambda表达式中
                    content.append(it)//冰洁到StringBuilder对象当中
                }
            }
        }catch (e:IOException){
            e.printStackTrace()
        }
        return content.toString()//将读取的内容返回即可
    }

    //___________________________________________________________________________
}

SharedPreferences存储

SharedPreferences是采用键值对的方式来存储数据的,以键取值

将数据存储到SharedPreferences中

步骤

1.获取SharedPreferences对象

2.调用SharedPreferences对象的edit()方法获取一个SharedPreferences.Editor对象

3.向SharedPreferences.Editor对象中添加数据,比如添加一个布尔类型数据就使用putBoolean()方法,添加一个字符串则使用putString()方法,以此类推

4.调用apply()方法将添加的数据提交,从而完成数据存储操作

获取SharedPreferences对象

1.Context类中的getSharedPreferences()方法

第一个参数用于指定SharedPreferences文件名称,如果指定的文件不存在则会创建一个,SharedPreferences文件都存放在/data/data//shared_prefs目录下

第二个参数指定操作模式。MODE_PRIVATE 表示只有当前的应用程序才可以对这个SharedPreferences文件进行读写,可直接传入0,效果类似


2.Activity类中的getPreferences()方法

类似于第一种方法,但只接收一个操作模式参数,它会自动将当前Activity的类名作为SharedPreferences的文件名

实例

保存

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">

<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/saveButton"
    android:text="Save Data"
    />

LinearLayout>

MainActivity.kt

package com.example.a20200701study2

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        saveButton.setOnClickListener {
            val editor=getSharedPreferences("data", Context.MODE_PRIVATE).edit()//指定getSharedPreferences的文件名以及操作模式,和获得editor对象
            editor.putString("name","Tom")
            editor.putInt("age",28)
            editor.putBoolean("marride",false)
            editor.apply()
        }
    }
}

存储结果

第一行代码第三版笔记_第16张图片

从SharedPreferences中读取数据

每种get方法都对应了SharedPreferences中的一种put方法,比如读取一个布尔型数据就使用getBoolean()方法,读取一个字符串就使用getString

get方法接收两个参数,第一个参数是键,由键找值,第二个参数是默认值,即表示当传入的键找不到对应的值时会以什么样的默认值进行返回

实例

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"
    android:orientation="vertical"
    tools:context=".MainActivity">

<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/saveButton"
    android:text="Save Data"
    />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/restoreButton"
        android:text="Restore Data"
        />

LinearLayout>

MainActivity.kt

package com.example.a20200701study2

import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        saveButton.setOnClickListener {
            val editor=getSharedPreferences("data", Context.MODE_PRIVATE).edit()//指定getSharedPreferences的文件名以及操作模式,和获得editor对象
            editor.putString("name","Tom")
            editor.putInt("age",28)
            editor.putBoolean("marride",false)
            editor.apply()
        }
        restoreButton.setOnClickListener {
            val prefs=getSharedPreferences("data",Context.MODE_PRIVATE)//通过getSharedPreferences来获得SharedPreferences对象
            val name=prefs.getString("name","")//获取字符串数据,以键取值,如果没有找到相应的键就用空字符串来代替
            val age=prefs.getInt("age",0)//获取整型数据,以键取值,如果没有找到相应的键就用0来代替
            val  married = prefs.getBoolean("married",false)//获取布尔数据,以键取值,如果没有找到相应的键就用false来代替
            Log.d("MainActivity","name is $name")
            Log.d("MainActivity","age is $age")
            Log.d("MainActivity","married is $married")
        }
    }
}

实现记住密码功能

CheckBox 为复选框组件,用户可以通过点击的方式进行选中和取消,用于是否记住密码

activity_main.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal">
        <CheckBox
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:id="@+id/rememberPass"
            />
        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textSize="18sp"
            android:text="Remember password"/>
LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textSize="18sp"
            android:text="Account:"/>
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:id="@+id/accountEdit"
            android:layout_weight="1"
            android:layout_gravity="center_vertical"/>
    LinearLayout>
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:orientation="horizontal">
        <TextView
            android:layout_width="90dp"
            android:layout_height="wrap_content"
            android:layout_gravity="center_vertical"
            android:textSize="18sp"
            android:text="passWord:"/>
        <EditText
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_weight="1"
            android:id="@+id/passwordEdit"
            android:layout_gravity="center_vertical"
            android:inputType="textPassword"/>/
    LinearLayout>
    <Button
        android:layout_width="200dp"
        android:layout_height="60dp"
        android:id="@+id/login"
        android:layout_gravity="center_horizontal"
        android:text="Login"
        />
LinearLayout>

LoginActivity.kt

package com.example.a20200628study

import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_login.*

class LoginActivity :BaseActivity() {//继承BaseActivity
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
 setContentView(R.layout.activity_login)
    //_______________________________________________________________
    val prefs=getPreferences(Context.MODE_PRIVATE)//获取SharedPreferences对象,名称默认为activity的名称
    val isRemember=prefs.getBoolean("remember_password",false)//获取remmber_password这个键对应的值,一开始不存在对应的值,就会使用默认值false
    if(isRemember){//如果isRemember为true
        //将账号和密码都设置到文本框中
        val account=prefs.getString("account","")//以键取值,找不到时默认为空
        val password=prefs.getString("password","")//以键取值,找不到时默认为空
        accountEdit.setText(account)//设置账号
        passwordEdit.setText(password)//设置密码
        rememberPass.isChecked=true//复选框设置为选中
    }
    //____________________________________________________________________________

    login.setOnClickListener {//当点击登录按钮时要执行的操作
        val account=accountEdit.text.toString()//获取账户信息到变量account
        val password=passwordEdit.text.toString()//获取密码信息到变量password
        //如果账号事admin 且密码是123456,就认为登录成功
        if (account=="admin"&& password=="123456"){
            //________________________________________________________________________
            val editor=prefs.edit()
            if(rememberPass.isChecked){//检查复选框是否被选中,如果选中复选框
                editor.putBoolean("remember_password",true)
                editor.putString("account",account)
                editor.putString("password",password)
            }else{
                editor.clear()//如果复选框没有被选中,就将SharedPreferences文件中的数据全部清除掉
            }
            editor.apply()
            //___________________________________________________________________________________
            val intent = Intent(this,MainActivity::class.java)
            startActivity(intent)//进行activity跳转
            finish()//关闭本activity
        }else{
            Toast.makeText(this,"账号或密码不合法",Toast.LENGTH_SHORT).show()
        }
    }
    }
}

SQLite数据库存储

创建数据库

SQLiteOpenHelper是抽象类,需要帮助类来继承,而且需要实现onCreate()和onUpgrade()这两个重写方法

getReadableDatabase()getWritableDatabase()这两个方法都可以创建或打开一个现有的数据库(如果数据库存在就直接打开,否则药创建一个新的数据库),并返回一个可对数据库进行读写操作的对象,不同的是,当数据库不可写入的时候(磁盘空间已满),getReadableDatabase()方法返回的对象将以只读的方式打开数据库,而getWritableDatabase()方法将出现异常

SQLiteOpenHelper一般使用参数少的构造方法,这个构造方法接收四个参数,第一是Context,第二十数据库名,第三个参数是允许我们在查询数据的时候返回一个自定义的Cursor,一般传入null,第四个参数为当前数据库的版本号,可用于是对数据库进行升级操作

构建出SQLiteOpenHelper的实例之后,再调用它的getReadableDatabase()或getWritableDatabase()方法就能够创建数据库

数据库文件存放在/data/data//databases/目录

重写的onCreate()方法可以处理一些创建表的逻辑

实现点击按钮后创建一个BookStore.db的数据库

MyDatabaseHelper.kt

package com.example.a20200701study3

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toast

class MyDatabaseHelper(val context:Context,name:String,version:Int):SQLiteOpenHelper(context,name,null,version) {//继承SQLiteOpenHelper
    private val createBook="create table book("+//将建表语句定义成一个字符串变量
            " id integer primary key autoincrement,"+//peimary key 将id列设为主键,并用autoincrement关键字表示id列是自增长的
            "author text,"+//text表示文本类型
            "price real,"+//real表示浮点型
            "pages integet,"+//integer表示整型
            "name text)"
    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(createBook)//调用SQLiteDatabase()方法去执行这条建表语句
        Toast.makeText(context,"Create succeeded",Toast.LENGTH_SHORT).show()
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
    }
}

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">
<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Create Database"
    android:id="@+id/createDatabase"
    />


LinearLayout>

MainActivity.kt

package com.example.a20200701study3

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
val dbHelper =MyDatabaseHelper(this,"BookStore.db",1)//构建MyDatabaseHelper对象,并通过构造函数的参数将数据库名指定为BookStore.db
        //版本号指定为1
        createDatabase.setOnClickListener {
            dbHelper.writableDatabase//调用getWritableDatabase()方法来创建数据库,如果当前程序没有BookStore.ad这个数据库
            //于是会创建该数据库并调用MyDatabaseHelper中的onCreatge()方法
        }
            }
    }

查看创建的数据库

下载Database Navigator 插件,然后通过Device File Explorer 在/data/data/com.example.20200701study3/databases目录下导出BookStore.db到系统磁盘中,通过DB Browser来打开导出的数据库文件

第一行代码第三版笔记_第17张图片

第一行代码第三版笔记_第18张图片

第一行代码第三版笔记_第19张图片

第一行代码第三版笔记_第20张图片

数据库中的表存在的时候,是无法再创建新的表,只能在onUpgrade中判断如果存在表就删除,再重新创建

添加表

MyDatabaseHelper.kt

package com.example.a20200701study3

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toast

class MyDatabaseHelper(val context:Context,name:String,version:Int):SQLiteOpenHelper(context,name,null,version) {//继承SQLiteOpenHelper
    private val createBook="create table book("+//将建表语句定义成一个字符串变量
            " id integer primary key autoincrement,"+//peimary key 将id列设为主键,并用autoincrement关键字表示id列是自增长的
            "author text,"+//text表示文本类型
            "price real,"+//real表示浮点型
            "pages integet,"+//integer表示整型
            "name text)"
    //——————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    private val createCategory="create table Category("+//添加一张Category表用于记录图书的分类
            "id integer primary key autoincrement,"+//设置id为主线
            "category_name text,"+//设置分类名
            "category_code integer)"//设置分类代码

    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(createBook)//调用SQLiteDatabase()方法去执行这条建表语句
        db.execSQL(createCategory)
        Toast.makeText(context,"Create succeeded",Toast.LENGTH_SHORT).show()
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        db.execSQL("drop table if exists Book")//如果数据库中已经存在Book表就用drop来进行删除
        db.execSQL("drop table if exists Category")//如果数据库中已经存在Category表就用删除此表
        onCreate(db)//重新执行onCreate方法来创建数据库中的表


    }
}

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">
<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Create Database"
    android:id="@+id/createDatabase"
    />


LinearLayout>

MainActivity.kt

package com.example.a20200701study3

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
val dbHelper =MyDatabaseHelper(this,"BookStore.db",2)//构建MyDatabaseHelper对象,并通过构造函数的参数将数据库名指定为BookStore.db
        //版本号指定为2,让MyDatabaseHelper中的onUpgrade()方法得到执行,表示对数据库进行升级
        createDatabase.setOnClickListener {
            dbHelper.writableDatabase//调用getWritableDatabase()方法来创建数据库,如果当前程序没有BookStore.ad这个数据库
            //于是会创建该数据库并调用MyDatabaseHelper中的onCreatge()方法
        }
            }
    }

添加数据

调用SQLIteOpenHelper的getReadableDatabase()或getWritable-Database()方法都会返回一个SQLitedatabase对象,用这个对象就能对数据进行添加查询更新删除的操作

SQLitedatabase的insett()方法用于添加数据,第一个参数是表名,指明要添加数据到哪一张表;第二个参数用于再未指定添加数据的情况下给某些可为空的列自动赋值NULL,一般我们用不到这个功能,直接传入null。第三个参数是一个ContentValues对象,它提供一系列put()方法的重载,用于向ContentValues中添加数据,只需要将表中的每个列名以及相应的待添加数据传入即可

MyDatabaseHelper.kt

package com.example.a20200701study3

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toast

class MyDatabaseHelper(val context:Context,name:String,version:Int):SQLiteOpenHelper(context,name,null,version) {//继承SQLiteOpenHelper
    private val createBook="create table book("+//将建表语句定义成一个字符串变量
            " id integer primary key autoincrement,"+//peimary key 将id列设为主键,并用autoincrement关键字表示id列是自增长的
            "author text,"+//text表示文本类型
            "price real,"+//real表示浮点型
            "pages integet,"+//integer表示整型
            "name text)"
    //——————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    private val createCategory="create table Category("+//添加一张Category表用于记录图书的分类
            "id integer primary key autoincrement,"+//设置id为主线
            "category_name text,"+//设置分类名
            "category_code integer)"//设置分类代码

    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(createBook)//调用SQLiteDatabase()方法去执行这条建表语句
        db.execSQL(createCategory)
        Toast.makeText(context,"Create succeeded",Toast.LENGTH_SHORT).show()
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        db.execSQL("drop table if exists Book")//如果数据库中已经存在Book表就用drop来进行删除
        db.execSQL("drop table if exists Category")//如果数据库中已经存在Category表就用删除此表
        onCreate(db)//重新执行onCreate方法来创建数据库中的表


    }
}

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:orientation="vertical"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Create Database"
    android:id="@+id/createDatabase"
    />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/addData"
        android:text="Add Data"
        />


LinearLayout>

MainActivity.kt

package com.example.a20200701study3

import android.content.ContentValues
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
val dbHelper =MyDatabaseHelper(this,"BookStore.db",2)//构建MyDatabaseHelper对象,并通过构造函数的参数将数据库名指定为BookStore.db
        //版本号指定为2,让MyDatabaseHelper中的onUpgrade()方法得到执行,表示对数据库进行升级
        createDatabase.setOnClickListener {
            dbHelper.writableDatabase//调用getWritableDatabase()方法来创建数据库,如果当前程序没有BookStore.ad这个数据库
            //于是会创建该数据库并调用MyDatabaseHelper中的onCreatge()方法
        }
        addData.setOnClickListener {
            val db =dbHelper.writableDatabase//获取了SQLiteDatabase对象
            val values1= ContentValues().apply{//使用ContentValues对添加的数据进行组装
                //开始组装第一条数据,因为id设置为自增长,所有不需要手动赋值
                put("name","The Da Vinci Code")
                put("author","Dan Brown")
                put("pages",454)
                put("price",16.96)
            }
            db.insert("Book",null,values1)//插入第一条数据
            val values2=ContentValues().apply {
                //开始组装第二条数据
                put("name","The lOST Symbol")
                put("author","Dan Brown")
                put("pages",510)
                put("price",19.95)
            }
            db.insert("Book",null,values2)//插入第二条数据
        }
            }
    }


这个窗口是设置查询条件的,不需要任何查询条件,直接点击窗口下方的NoFilter按钮即可

第一行代码第三版笔记_第21张图片

更新数据

SQLiteDatabase中有个update()方法用于数据的更新,第一个参数为表名,指定更新哪张表里的数据,第二个参数是ContentValues对象,要把更新的数据再这里组装进去;第三、四个参数用于约束更新某一行或某几行中的数据,不指定的话默认会更新所有行

MainActivity.kt

updateData.setOnClickListener {
            val db =dbHelper.writableDatabase
            val values =ContentValues()//构建ContentValues对象
            values.put("price",10.99)//指定数据
            db.update("Book",values,"name=?", arrayOf("The Da Vinci Code"))//更新数据,第三、四参数来指定具体更新哪几行
            /*
            第三个参数对应的是SQL语句的where部分,表示更新所有name等于?的行,而?是一个占位符,
            第四个参数提供一个字符串数组为第三个参数中的每个占位符指定相应的内容,arrayof()方法是创建数组的方法
            将The Da Vinci Code这本书的价格改成10.99
             */
        }

删除数据

delete()方法,第一个参数为表名。第二、三个参数用于约束删除某一行或某几行的数据,不指定的话默认会删除所有行

 deleteData.setOnClickListener {
            val db=dbHelper.writableDatabase
            db.delete("Book","pages>?", arrayOf("500"))//指定仅删除页数超过500页的输
        }

查询数据

query()方法进行查询

最短的方法需要传入7个参数,第一个参数(table)为表名,表示我们希望从哪张表中查询数据;第二个参数(columns)为用于指定去查询哪几列,如果不指定则会默认查询所有列;第三、四个(selection、selectionArgs)参数用于约束查询某一行或某几行的数据,不指定则会默认查询所有行的数据;第五个参数(groupBy)用于指定需要去group by的列,不指定则表示不对查询结果进行group by操作;第六个参数(having)用于对group by之后的数据进行进一步的过滤,不指定则表示不进行过滤;第七个参数(orderBy)用于指定查询结果的排序方式,不指定则表示使用默认排序方式

query()方法不必所有参数都进行传入,只需要传入需要的参数,调用query()方法后会返回一个Cursor对象,查询到的所有数据都将从这个对象中取出

        queryData.setOnClickListener {
            val db =dbHelper.writableDatabase
            //查询Book表中所有数据
            val cursor=db.query("Book",null,null,null,null,null,null)//只指明要查询的表,不指定就参数设空,返回Cursor对象
            if (cursor.moveToFirst()){//如果数据指针移动到第一行位置
                do {
                    //遍历Cursor对象,取出数据并打印
                    val name =cursor.getString(cursor.getColumnIndex("name"))//通过Cursor的getColumnIndex()方法获取某一列再表中对应的位置索引
                    //然后将这个索引传入相应的取值方法中,得到数据库读取到的数据
                    val author=cursor.getString(cursor.getColumnIndex("author"))
                    val pages=cursor.getInt(cursor.getColumnIndex("pages"))
                    val price=cursor.getDouble(cursor.getColumnIndex("price"))
                    Log.d("MainAcitivity","book name is $name")
                    Log.d("MainAcitivity","book author is $author")
                    Log.d("MainAcitivity","book pages is $pages")
                    Log.d("MainAcitivity","book price is $price")
                }while (cursor.moveToNext())//如果数据指针移动到下一个位置
            }
            cursor.close()//关闭Cursor
        }

使用SQL操作数据库

添加数据

db.execSQL("insert into Book(name,author,pages,price)values(?,?,?,?)"),
arrayOf("The Da Vinci Code","Dan Brown","454","16.96")
)

db.execSQL("insert into Book(name,author,pages,price)values(?,?,?,?)"),
arrayOf("The Lost Symbol","Dan Brown","510","19.95")
)

更新数据

db.execSQL("update Book set price=?where name=?",arrayOf("10.99","The Da Vinci Code"))

删除数据

db.execSQL("delete from Book where pages>?",arrayOf("500"))

查询数据

val cursor=db.rawQuery("select * from Book",null)

SQLite数据库的最佳实践

使用事务

事务的特性可以保证一系列的操作要么全部完成,要么一个都不会完成

完整代码

MyDatabaseHelper.kt

package com.example.a20200701study3

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toast

class MyDatabaseHelper(val context:Context,name:String,version:Int):SQLiteOpenHelper(context,name,null,version) {//继承SQLiteOpenHelper
    private val createBook="create table book("+//将建表语句定义成一个字符串变量
            " id integer primary key autoincrement,"+//peimary key 将id列设为主键,并用autoincrement关键字表示id列是自增长的
            "author text,"+//text表示文本类型
            "price real,"+//real表示浮点型
            "pages integet,"+//integer表示整型
            "name text)"
    //——————————————————————————————————————————————————————————————————————————————————————————————————————————————————
    private val createCategory="create table Category("+//添加一张Category表用于记录图书的分类
            "id integer primary key autoincrement,"+//设置id为主线
            "category_name text,"+//设置分类名
            "category_code integer)"//设置分类代码

    override fun onCreate(db: SQLiteDatabase) {
        db.execSQL(createBook)//调用SQLiteDatabase()方法去执行这条建表语句
        db.execSQL(createCategory)
        Toast.makeText(context,"Create succeeded",Toast.LENGTH_SHORT).show()
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
        db.execSQL("drop table if exists Book")//如果数据库中已经存在Book表就用drop来进行删除
        db.execSQL("drop table if exists Category")//如果数据库中已经存在Category表就用删除此表
        onCreate(db)//重新执行onCreate方法来创建数据库中的表


    }
}

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:orientation="vertical"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
<Button
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Create Database"
    android:id="@+id/createDatabase"
    />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/addData"
        android:text="Add Data"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/updateData"
        android:text="Updata Data"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/deleteData"
        android:text="Delete Data"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/queryData"
        android:text="Query Data"
        />
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/replaceData"
        android:text="Replace Data"
        />
LinearLayout>

MainActivity.kt

package com.example.a20200701study3

import android.content.ContentValues
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import kotlinx.android.synthetic.main.activity_main.*
import java.lang.Exception
import java.lang.NullPointerException

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val dbHelper = MyDatabaseHelper(
            this,
            "BookStore.db",
            2
        )//构建MyDatabaseHelper对象,并通过构造函数的参数将数据库名指定为BookStore.db
        //版本号指定为2,让MyDatabaseHelper中的onUpgrade()方法得到执行,表示对数据库进行升级
        /***********************创建数据库***********************************************/
        createDatabase.setOnClickListener {
            dbHelper.writableDatabase//调用getWritableDatabase()方法来创建数据库,如果当前程序没有BookStore.ad这个数据库
            //于是会创建该数据库并调用MyDatabaseHelper中的onCreatge()方法
        }
        /***********************添加数据***********************************************/
        addData.setOnClickListener {
            val db = dbHelper.writableDatabase//获取了SQLiteDatabase对象
            val values1 = ContentValues().apply {//使用ContentValues对添加的数据进行组装
                //开始组装第一条数据,因为id设置为自增长,所有不需要手动赋值
                put("name", "The Da Vinci Code")
                put("author", "Dan Brown")
                put("pages", 454)
                put("price", 16.96)
            }
            db.insert("Book", null, values1)//插入第一条数据
            val values2 = ContentValues().apply {
                //开始组装第二条数据
                put("name", "The lOST Symbol")
                put("author", "Dan Brown")
                put("pages", 510)
                put("price", 19.95)
            }
            db.insert("Book", null, values2)//插入第二条数据
        }
        /*************************更新数据***********************************************/
        updateData.setOnClickListener {
            val db = dbHelper.writableDatabase//获取了SQLiteDatabase对象
            val values = ContentValues()//构建ContentValues对象
            values.put("price", 10.99)//指定数据
            db.update("Book", values, "name=?", arrayOf("The Da Vinci Code"))//更新数据,第三、四参数来指定具体更新哪几行
            /*
            第三个参数对应的是SQL语句的where部分,表示更新所有name等于?的行,而?是一个占位符,
            第四个参数提供一个字符串数组为第三个参数中的每个占位符指定相应的内容,arrayof()方法是创建数组的方法
            将The Da Vinci Code这本书的价格改成10.99
             */
        }
        /*************************删除数据************************************/
        deleteData.setOnClickListener {
            val db = dbHelper.writableDatabase//获取了SQLiteDatabase对象
            db.delete("Book", "pages>?", arrayOf("500"))//指定仅删除页数超过500页的输
        }
        /**********************查询数据*************************************/
        queryData.setOnClickListener {
            val db = dbHelper.writableDatabase//获取了SQLiteDatabase对象
            //查询Book表中所有数据
            val cursor =
                db.query("Book", null, null, null, null, null, null)//只指明要查询的表,不指定就参数设空,返回Cursor对象
            if (cursor.moveToFirst()) {//如果数据指针移动到第一行位置
                do {
                    //遍历Cursor对象,取出数据并打印
                    val name =
                        cursor.getString(cursor.getColumnIndex("name"))//通过Cursor的getColumnIndex()方法获取某一列再表中对应的位置索引
                    //然后将这个索引传入相应的取值方法中,得到数据库读取到的数据
                    val author = cursor.getString(cursor.getColumnIndex("author"))
                    val pages = cursor.getInt(cursor.getColumnIndex("pages"))
                    val price = cursor.getDouble(cursor.getColumnIndex("price"))
                    Log.d("MainAcitivity", "book name is $name")
                    Log.d("MainAcitivity", "book author is $author")
                    Log.d("MainAcitivity", "book pages is $pages")
                    Log.d("MainAcitivity", "book price is $price")
                } while (cursor.moveToNext())//如果数据指针移动到下一个位置
            }
            cursor.close()//关闭Cursor
        }
        /*************************替换数据*************************************************/
        replaceData.setOnClickListener {
            val db = dbHelper.writableDatabase//获取了SQLiteDatabase对象
            db.beginTransaction()//开启事物
            try {
                db.delete("Book", null, null)//删除Book的表
                if (true) {//就是一定真,一定会执行if语句里面的逻辑
                    //手动抛出一个异常,让事务失败
                    throw  NullPointerException()
                }
                val values = ContentValues().apply {//使用ContentValues对添加的数据进行组装
                    put("name", "Game of Thrones")
                    put("author", "George Martin")
                    put("pages", 720)
                    put("price", 20.85)
                }
                db.insert("Book", null, values)//为数据库插入数据
                db.setTransactionSuccessful()//事物已经执行成功
            } catch (e: Exception) {
                e.printStackTrace()
            } finally {
                db.endTransaction()//结束事物
            }
        }
    }
}

升级数据库的最佳写法

每个数据库版本都会对应一个版本号,当指定的数据库版本号大于当前数据库版本号的时候,就会进入onUpgrade()方法中执行更新操作,这里需要为每一个版本号赋予其所对应的数据库变动,然后再onUpgrade()方法中对当前数据库的版本号进行判断,再执行相应的改变就可以了

MyDatabaseHelper.kt

package com.example.a20200711sqlitetest

import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper

class MyDatabaseHelper(val context: Context,name:String,version:Int): SQLiteOpenHelper(context,name,null,version) {
    private  val createBook="create table Book("+
            "id integer primary key autoincrement,"+
            "author text,"+
            "price real,"+
            "pages integer,"+
            "name text,"+
            "category_id integer)"//给book表中添加一个category_id字段

    private val createCategory="create table Category("+
            "id integer primary key autoincrement,"+
            "category_name text,"+
            "category_code integer)"

    override fun onCreate(db: SQLiteDatabase) {//当用户直接安装第二版,就会进入onCreate()方法,将两个表一起创建

      db.execSQL(createBook)//数据库执行创建book表
        db.execSQL(createCategory)//数据库执行创建Category表
    }

    override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersiohn: Int) {//当用户使用第二版的程序覆盖安装第一版程序时
        //就会进入升级数据库的操作中,由于book表已经存在,只需要创建一张Category表即可
        if (oldVersion<=1){//如果用户的数据库的旧版本号小于等于1,就只会创建一张Category表
            db.execSQL(createCategory)
//每当升级一个数据库版本的时候,onUpgrade()方法里都一定要写一个相应的if判断语句,为了保证App在跨版本升级的时候,每一次的数据库修改都能被全部执行

        }
        if (oldVersion<=2){//如果用户之前已经安装了某一版本的程序,现在需要覆盖安装,就会进入升级数据库的操作中
            //如果当前数据库的版本号时2,就执行alter命令,为Book表新增一个category_id列
            db.execSQL("alter table Book add column category_id integer")
        }
    }

}

你可能感兴趣的:(第一行代码第三版笔记)