第3章 Activity

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

manifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.activitytest"><applicationandroid: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.activitytestimport 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.parseclass 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对象,直接传thisfinish()//销毁当前的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中设置的actionintent.addCategory("com.example.activitytest.MY_CATEGORY")//每个intent只能指定一个action,但能指定多个category,调用addCategory来添加一个CategorystartActivity(intent)}button3.setOnClickListener {val intent =Intent(Intent.ACTION_VIEW)//指定intent的actionintent.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来启动另一个activitystartActivityForResult(intent,1)//启动Activity并在Activity销毁的时候返回一个结果给上一个Activity,第一个参数是跳转的intent,第二个参数是请求吗。用于回调中判断数据的来源}}
//重写方法来得到返回的数据override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {//第一个参数为启动Activity传入的请求码,第二个参数是返回数据时传入的处理结果,第三个参数data,即携带返回数据的Intentsuper.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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid:id="@+id/button"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Button" /><Buttonandroid:id="@+id/button2"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="销毁activity" /><Buttonandroid:id="@+id/StartSecondActivity"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="显式启动另一个activity" /><Buttonandroid:id="@+id/button4"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="隐式启动另一个activity" /><TextViewandroid:id="@+id/textView2"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="第一个activity" /><Buttonandroid:id="@+id/button3"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="打开百度" /><Buttonandroid:id="@+id/button6"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="调用系统拨号" /><Buttonandroid:id="@+id/button7"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="向SecondActivity赋值" /><Buttonandroid:id="@+id/button8"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="返回数据给上一个Activity" />
</LinearLayout>

SecondActivity.kt

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

second_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".SecondActivity"><Buttonandroid:id="@+id/button2"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_weight="1"android:text="Button 2" /><Buttonandroid: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.activitytestimport androidx.appcompat.app.AppCompatActivity
import android.os.Bundleclass ThirdActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.third_layout)}
}

third_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"android:orientation="vertical"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".ThirdActivity"><Buttonandroid:id="@+id/button5"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Button 3" />
</LinearLayout>

menu.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<itemandroid:id="@+id/add_item"android:title="Add"/><itemandroid: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

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.activitylifecycletest"><applicationandroid: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=".DialogActivity"android:theme="@style/Theme.AppCompat.Dialog"//给当前Activity指定主题,采用对话框式主题></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.activitylifecycletestimport 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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><Buttonandroid:id="@+id/startNormalActivity"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Start NormalActivity" /><Buttonandroid:id="@+id/startDialogActivity"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Start DialogActivity" />
</LinearLayout>

NormalActivity.kt

package com.example.activitylifecycletestimport androidx.appcompat.app.AppCompatActivity
import android.os.Bundleclass NormalActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_normal)}
}

activity_normal.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".NormalActivity"><TextViewandroid: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.activitylifecycletestimport androidx.appcompat.app.AppCompatActivity
import android.os.Bundleclass DialogActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_dialog)}
}

activity_dialog.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".DialogActivity"><TextViewandroid: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

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.a20200604"><applicationandroid: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

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.a20200604"><applicationandroid: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"android:launchMode="singleTop"//设置FirstActivity的启动模式是singleTop></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

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.a20200604"><applicationandroid: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><activityandroid: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,一个栈空了,就出另一个栈

返回栈空了就会退出程序

部分代码

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.a20200604"><applicationandroid: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><activityandroid: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.a20200604import 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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><Buttonandroid: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" /><TextViewandroid:id="@+id/textView"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="这是MainActivity" />
</LinearLayout>

SecondActivity.kt

package com.example.a20200604import 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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".SecondActivity"><Buttonandroid: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" /><Buttonandroid:id="@+id/button4"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="启动ThirdActivity" /><TextViewandroid:id="@+id/textView3"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="这是SecondActivity" /><Buttonandroid:id="@+id/button6"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="启动SecondActivity" />
</LinearLayout>

ThirdActivity.kt

package com.example.a20200604import 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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".ThirdActivity"><TextViewandroid:id="@+id/textView4"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="这是ThirdActivity" /><Buttonandroid:id="@+id/button5"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="退出所有Activity" />
</LinearLayout>

BaseActivity.kt

package com.example.a20200604import 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.a20200604import android.app.Activity
//目标:随时随地退出程序
object ActivityCollector{private  val activities=ArrayList<Activity>()//一个专门的集合对所有的Activity进行管理,通过ArrayList来暂存Activityfun 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 {//会自动在类中创建伴生类对象,代替类的实例@JvmStaticfun doAction2(){//类似静态方法,可以用类名来调用,实际上就是伴生类对象.方法println("do action2")}}}

实现静态方法—顶层

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

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

第4章 UI

采用androidx.constraintlayout.widget.ConstraintLayout布局就能用可视化用拖拽的方式添加组件,并且在旁边的设置面板上设置各种属性

文字的大小以sp为单位,组件大小以dp为单位

TextView

activity_main.xml

<TextViewandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="Hello World!"android:gravity="center"//指定文字的对齐方式,可以用|来同时指定多个值/>

Button

activity_main.xml

 <Buttonandroid:id="@+id/button"android:layout_width="match_parent"android:layout_height="wrap_content"android:textAllCaps="false"//保留指定的原始文字内容android:text="Button" />

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

button.setOnClickListener { }

实现接口的方式注册按钮

package com.example.uiwidgetestimport 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

ImageView

添加图片:将ImageView拖动到布局后,可以在提示框选中要添加的图片

通过点击按钮来动态改变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

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><include layout="@layout/title"/>//引入其他布局</androidx.constraintlayout.widget.ConstraintLayout>

title.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid: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" /><Buttonandroid: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" /><TextViewandroid: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.a20200610study2import androidx.appcompat.app.AppCompatActivity
import android.os.Bundleclass 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.a20200610study2import androidx.appcompat.app.AppCompatActivity
import android.os.Bundleclass MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)supportActionBar?.hide()}
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><includeandroid:id="@+id/include"layout="@layout/title" /><com.example.a20200610study2.TitleLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content" /></androidx.constraintlayout.widget.ConstraintLayout>

TitleLayout.kt

package com.example.a20200610study2import 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

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"><Buttonandroid: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" /><Buttonandroid: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" /><TextViewandroid: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.a20200610study3import 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

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><ListViewandroid: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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"android:layout_height="match_parent">
<ImageViewandroid:layout_width="40dp"android:layout_height="40dp"android:layout_gravity="center_vertical"android:id="@+id/fruitImage"android:layout_marginLeft="10dp"/><TextViewandroid: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.a20200611studyimport 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

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><ListViewandroid: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.a20200611studyimport 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:Viewval 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会主动提醒你,并告诉你最新的版本号

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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="wrap_content"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/fruitImage"android:layout_width="40dp"android:layout_height="40dp"android:layout_gravity="center_vertical"android:layout_marginLeft="10dp" /><TextViewandroid: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)//类有两个资源,一个是水果的名称,一个是图片的idclass 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的Imageviewholder.fruitName.text=fruit.name//将数据设置到ViewHolder的Textview}}
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><androidx.recyclerview.widget.RecyclerViewandroid: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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="80dp"android:orientation="vertical"android:layout_height="wrap_content"><ImageViewandroid:id="@+id/fruitImage"android:layout_width="40dp"android:layout_height="40dp"android:layout_gravity="center_horizontal"android:layout_marginTop="10dp" /><TextViewandroid: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

<?xml version="1.0" encoding="utf-8"?>
<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"><ImageViewandroid:id="@+id/fruitImage"android:layout_width="40dp"android:layout_height="40dp"android:layout_gravity="center_horizontal"android:layout_marginTop="10dp" /><TextViewandroid: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)//类有两个资源,一个是水果的名称,一个是图片的idclass 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的Imageviewholder.fruitName.text=fruit.name//将数据设置到ViewHolder的Textview}}
}

RecyclerView的点击事件

MainActivity.kt

  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {//用于创建ViewHolder实例,//todoval 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//获取用户点击的postionval  fruit=fruitList[position]//通过postion拿到相应的Fruit实例Toast.makeText(parent.context,"你点击了项 ${fruit.name}",Toast.LENGTH_SHORT).show()}viewHolder.fruitImage.setOnClickListener {//给图片设置的单击事件监听器val  position =viewHolder.adapterPosition//获取用户点击的postionval  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 29buildToolsVersion "29.0.3"defaultConfig {applicationId "com.example.a20200612study2"minSdkVersion 16targetSdkVersion 29versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"}buildTypes {release {minifyEnabled falseproguardFiles 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'
}

msg_ left_ item.xml

<?xml version="1.0" encoding="utf-8"?>
<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:padding="10dp">
<LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="left"android:background="@drawable/qipaozuo"><TextViewandroid: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

<?xml version="1.0" encoding="utf-8"?>
<FrameLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="vertical"android:padding="10dp"><LinearLayoutandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_gravity="right"android:background="@drawable/qipao"><TextViewandroid: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.a20200612study2import 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.FieldPositionclass MainActivity : AppCompatActivity() ,View.OnClickListener{private val msgList=ArrayList<Msg>()private var adapter:MsgAdapter?=nulloverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)initMsg()val  layoutManager= LinearLayoutManager(this)recyclerView.layoutManager=layoutManager//给RecyclerView指定LayoutManageradapter=MsgAdapter(msgList)//给RecyclerView指定适配器recyclerView.adapter=adaptersend.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 xianshival  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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"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.RecyclerViewandroid:layout_width="match_parent"android:layout_height="0dp"android:id="@+id/recyclerView"android:layout_weight="1"/><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"><EditTextandroid:id="@+id/inputText"android:layout_height="wrap_content"android:layout_width="0dp"android:layout_weight="1"android:width="0dp"android:hint="请输入内容"android:maxLines="2"/><Buttonandroid: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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"><Buttonandroid: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.myapplicationimport 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

<?xml version="1.0" encoding="utf-8"?>
<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"><TextViewandroid: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.myapplicationimport 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

<?xml version="1.0" encoding="utf-8"?>
<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"><TextViewandroid: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.myapplicationimport android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragmentclass 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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"><!--使用Fragment标签在布局中添加Fragment,通过android:name来显式声明要添加的Fragment名,注意加上包名--><fragmentandroid:id="@+id/left_frag"android:name="com.example.myapplication.LeftFragment"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1" /><!--将右侧的Fragment替换为FrameLayout,默认摆在左上角,无需任何定位。--><FrameLayoutandroid:id="@+id/frame_layout"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1" />
</LinearLayout>

MainActivity.kt

package com.example.myapplicationimport 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){//第二步,获取FragmentManagerval 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){//第二步,获取FragmentManagerval 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:两者解除关联时调用。

体验Fragment的生命周期

RightFragment.kt

package com.example.myapplicationimport 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")}
}

日志运行结果见下图

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

动态加载布局的技巧

使用限定符

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

layout-large目录下的activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"android:layout_height="match_parent"><!-- 包含两个Fragment,为双页模式--><!--在布局文件中文件名带有 large ,large就是一个限定符,那些屏幕被认为是large的设备会自动加载layout-large文件夹的布局小屏幕的设备则会加载layout文件夹的布局--><FrameLayoutandroid: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"/><FrameLayoutandroid: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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"><!--使用Fragment标签在布局中添加Fragment,通过android:name来显式声明要添加的Fragment名,注意加上包名--><!-- 只包含一个Fragment,为单页模式--><fragmentandroid: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.myapplicationimport 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()
//    }
}

运行效果如图


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的最佳实践:一个简易版的新闻应用

activity_main

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"android:id="@+id/newsTitleLayout"android:layout_width="match_parent"android:layout_height="match_parent" ><fragmentandroid:id="@+id/newsTitleFrag"android:name="com.example.a20200616study.NewsTitleFragment"android:layout_width="match_parent"android:layout_height="match_parent"/></FrameLayout>

activity_main(sw600dp)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="horizontal"android:layout_width="match_parent"android:layout_height="match_parent" ><fragmentandroid:id="@+id/newsTitleFrag"android:name="com.example.a20200616study.NewsTitleFragment"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="1" /><FrameLayoutandroid:id="@+id/newsContentLayout"android:layout_width="0dp"android:layout_height="match_parent"android:layout_weight="3" ><fragmentandroid: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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><fragmentandroid:id="@+id/newsContentFrag"android:name="com.example.a20200616study.NewsContentFragment"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>

news_ content frag.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayoutandroid:id="@+id/contentLayout"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"android:visibility="invisible" ><TextViewandroid:id="@+id/newsTitle"android:layout_width="match_parent"android:layout_height="wrap_content"android:gravity="center"android:padding="10dp"android:textSize="20sp" /><Viewandroid:layout_width="match_parent"android:layout_height="1dp"android:background="#000" /><TextViewandroid:id="@+id/newsContent"android:layout_width="match_parent"android:layout_height="0dp"android:layout_weight="1"android:padding="15dp"android:textSize="18sp" /></LinearLayout><Viewandroid:layout_width="1dp"android:layout_height="match_parent"android:layout_alignParentLeft="true"android:background="#000" /></RelativeLayout>

news item.xml

<?xml version="1.0" encoding="utf-8"?>
<!--android:padding表示给控件的周围加上补白,不至于文本内容紧靠在边缘上
android:maxLines设置为1表示单行显示
android:ellipsize用于设定当文本内容超出控件宽度时文本的缩略方式,end为在尾部进行缩略-->
<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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"android:layout_height="match_parent"><!--用于显示新闻列表--><androidx.recyclerview.widget.RecyclerViewandroid:id="@+id/newsTitleRecyclerView"android:layout_width="match_parent"android:layout_height="match_parent"/></LinearLayout>

News.kt

package com.example.a20200616studyclass News(val title: String, val content: String)//创建构造函数,存放标题和内容

NewsContentActivity.kt

package com.example.a20200616studyimport 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.a20200616studyimport 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() {//继承Fragmentoverride 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.a20200616studyimport 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.ArrayListclass NewsTitleFragment : Fragment() {//继承Fragmentprivate 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> {//传入newval 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>() {//继承RecyclerViewinner 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 {// 如果是单页模式,则直接启动NewsContentActivityNewsContentActivity.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.a20200616studyfun String.lettersCount(): Int {var count = 0for (char in this) {if (char.isLetter()) {count++}}return count
}operator fun String.times(n: Int) = this.repeat(n)//循环n边

MainActivity.kt

package com.example.a20200616studyimport androidx.appcompat.app.AppCompatActivity
import android.os.Bundleclass MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)}
}

第6章 BroadcastReceiver

概念

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

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

接收系统广播

动态注册监听时间变化

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

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

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

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

package com.example.a20200627studyimport 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.Toastclass MainActivity : AppCompatActivity() {lateinit var timeChangeReceiver: TimeChangeReceiveroverride 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想要监听什么广播,就要在这里添加相应的actiontimeChangeReceiver=TimeChangeReceiver()//创建一个TimeChangeReceiver的实例registerReceiver(timeChangeReceiver,intentFilter)//注册广播,传入广播接收器和intentFilter的实例//TimeChangeReceiver就会收到所有值为android.intent.action.TIME_TICK的广播}override fun onDestroy() {super.onDestroy()unregisterReceiver(timeChangeReceiver)//销毁的时候取消注册}inner class TimeChangeReceiver :BroadcastReceiver(){//内部类继承BroadcastReceiveroverride fun onReceive(p0: Context?, p1: Intent?) {//重写父类的onReceiver()方法,当广播来临(系统事件发生变化),onReceiver()方法就会得到执行Toast.makeText(applicationContext,"Time has changed",Toast.LENGTH_SHORT).show()}}
}

静态注册实现开机启动

创建BroadcastReceiver

新建BroadcastReceiver

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

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

manifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.a20200627study2">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>//声明接收系统的开机广播所需的权限<applicationandroid: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*********************/<receiverandroid:name=".BootCompleteReceiver"//指明具体注册哪一个BroadcastReceiverandroid:enabled="true"android:exported="true"><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.a20200627study2import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toastclass 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

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.a20200627study3"><applicationandroid: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  //采用静态注册的方式注册BroadcastReceiverandroid:name=".MyBroadcastReceiver"android:enabled="true"android:exported="true"><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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity">
<Buttonandroid:id="@+id/button"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Send Broadcast"/></LinearLayout>

MyBroadcastReceiver.kt

package com.example.a20200627study3import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toastclass 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.a20200627study3import 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

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.a20200627study3"><applicationandroid: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"><receiverandroid: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><receiverandroid: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.a20200627study3import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toastclass 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.a20200627study3import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.widget.Toastclass 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.a20200627study3import 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

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.a20200628study"><applicationandroid: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.a20200628studyimport android.app.Activityobject ActivityCollector{//类似java的静态方法,用于管理所有的Activity,
private val activities=ArrayList<Activity>()//定义一个集合来存放所有活动,泛型的类型指定为activityfun 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,循环的变量为activitiesif(!activity.isFinishing){//如果acitivity没有关闭activity.finish()//关闭没有关闭的activity}}activities.clear()//清除集合}}

BaseActivity.kt

package com.example.a20200628studyimport 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.AppCompatActivityopen class BaseActivity : AppCompatActivity(){//创建BaseActivity类作为所有Activity的父类//在BaseActivity中动态注册一个广播接收器,就能使所有的activity都进行注册,因为所有的activity都继承BaseActivity
lateinit var receiver:ForceOfflineReceiver//lateinit关键字:修饰后,可以晚些对这个变量进行初始化,不用一开始就将它赋值为null,并且继承自ForceOfflineReceiveroverride 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()//销毁所有Activityval i=Intent(context,LoginActivity::class.java)//::相当于.  编写要跳转的activitycontext.startActivity(i)//重写启动 LoginActivity}show()//显示对话框}}}
}

activity_login.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="60dp"android:orientation="horizontal"><TextViewandroid:layout_width="90dp"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:textSize="18sp"android:text="Account:"/><EditTextandroid:layout_width="0dp"android:layout_height="wrap_content"android:id="@+id/accountEdit"android:layout_weight="1"android:layout_gravity="center_vertical"/></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="60dp"android:orientation="horizontal"><TextViewandroid:layout_width="90dp"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:textSize="18sp"android:text="passWord:"/><EditTextandroid: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><Buttonandroid: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.a20200628studyimport android.content.Intent
import android.os.Bundle
import android.widget.Toast
import kotlinx.android.synthetic.main.activity_login.*class LoginActivity :BaseActivity() {//继承BaseActivityoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_login)login.setOnClickListener {//当点击登录按钮时要执行的操作val account=accountEdit.text.toString()//获取账户信息到变量accountval 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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><Buttonandroid: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.a20200628studyimport 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,强制下线的功能就不会依附于任何界面}}
}

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

持久化技术简介

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

文件存储

将数据存储到文件中

       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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/editText"android:hint="Type something here"/></LinearLayout>

MainActivity.kt

package com.example.a20200701studyimport 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.OutputStreamWriterclass 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中查看存储的信息

从文件中读取数据

 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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><EditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/editText"android:hint="Type something here"/></LinearLayout>

MainActivity.kt

package com.example.a20200701studyimport 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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/saveButton"android:text="Save Data"/></LinearLayout>

MainActivity.kt

package com.example.a20200701study2import 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()}}
}

存储结果

从SharedPreferences中读取数据

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

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

实例

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="vertical"tools:context=".MainActivity"><Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/saveButton"android:text="Save Data"/><Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/restoreButton"android:text="Restore Data"/></LinearLayout>

MainActivity.kt

package com.example.a20200701study2import 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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="match_parent"><LinearLayoutandroid:layout_width="match_parent"android:layout_height="wrap_content"android:orientation="horizontal"><CheckBoxandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:id="@+id/rememberPass"/><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="18sp"android:text="Remember password"/>
</LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="60dp"android:orientation="horizontal"><TextViewandroid:layout_width="90dp"android:layout_height="wrap_content"android:layout_gravity="center_horizontal"android:textSize="18sp"android:text="Account:"/><EditTextandroid:layout_width="0dp"android:layout_height="wrap_content"android:id="@+id/accountEdit"android:layout_weight="1"android:layout_gravity="center_vertical"/></LinearLayout><LinearLayoutandroid:layout_width="match_parent"android:layout_height="60dp"android:orientation="horizontal"><TextViewandroid:layout_width="90dp"android:layout_height="wrap_content"android:layout_gravity="center_vertical"android:textSize="18sp"android:text="passWord:"/><EditTextandroid: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><Buttonandroid: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.a20200628studyimport 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() {//继承BaseActivityoverride 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这个键对应的值,一开始不存在对应的值,就会使用默认值falseif(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()//获取账户信息到变量accountval 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.a20200701study3import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toastclass MyDatabaseHelper(val context:Context,name:String,version:Int):SQLiteOpenHelper(context,name,null,version) {//继承SQLiteOpenHelperprivate 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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity">
<Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="Create Database"android:id="@+id/createDatabase"/></LinearLayout>

MainActivity.kt

package com.example.a20200701study3import 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//版本号指定为1createDatabase.setOnClickListener {dbHelper.writableDatabase//调用getWritableDatabase()方法来创建数据库,如果当前程序没有BookStore.ad这个数据库//于是会创建该数据库并调用MyDatabaseHelper中的onCreatge()方法}}}

查看创建的数据库

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

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

添加表

MyDatabaseHelper.kt

package com.example.a20200701study3import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toastclass MyDatabaseHelper(val context:Context,name:String,version:Int):SQLiteOpenHelper(context,name,null,version) {//继承SQLiteOpenHelperprivate 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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity">
<Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="Create Database"android:id="@+id/createDatabase"/></LinearLayout>

MainActivity.kt

package com.example.a20200701study3import 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.a20200701study3import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toastclass MyDatabaseHelper(val context:Context,name:String,version:Int):SQLiteOpenHelper(context,name,null,version) {//继承SQLiteOpenHelperprivate 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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:orientation="vertical"android:layout_height="match_parent"tools:context=".MainActivity">
<Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="Create Database"android:id="@+id/createDatabase"/><Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/addData"android:text="Add Data"/></LinearLayout>

MainActivity.kt

package com.example.a20200701study3import 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按钮即可

更新数据

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

MainActivity.kt

updateData.setOnClickListener {val db =dbHelper.writableDatabaseval 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.writableDatabasedb.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.a20200701study3import android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.widget.Toastclass MyDatabaseHelper(val context:Context,name:String,version:Int):SQLiteOpenHelper(context,name,null,version) {//继承SQLiteOpenHelperprivate 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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:orientation="vertical"android:layout_height="match_parent"tools:context=".MainActivity">
<Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:text="Create Database"android:id="@+id/createDatabase"/><Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/addData"android:text="Add Data"/><Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/updateData"android:text="Updata Data"/><Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/deleteData"android:text="Delete Data"/><Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/queryData"android:text="Query Data"/><Buttonandroid:layout_width="match_parent"android:layout_height="wrap_content"android:id="@+id/replaceData"android:text="Replace Data"/>
</LinearLayout>

MainActivity.kt

package com.example.a20200701study3import 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.NullPointerExceptionclass 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.a20200711sqlitetestimport android.content.Context
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelperclass 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")}}}

第8章 跨程序共享数据,探究ContentProvider

简介

ContentProvider用于在不同的应用程序之间实现数据共享的功能

运行时权限

Android权限机制详解

权限分为普通权限与危险权限

普通权限只需要在AndroidManifest.xml文件中添加权限声明,危险权限则需要进行运行时权限处理

在程序运行时申请权限

Android6.0之前会自动拒绝危险权限的申请

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.a20200711runtimepermissiontest"><uses-permission android:name="android.permission.CALL_PHONE"/><applicationandroid: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=".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

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><Buttonandroid:id="@+id/makeCall"android:layout_width="match_parent"android:layout_height="wrap_content"android:text="Make Call"/></LinearLayout>

MainActivity.kt

package com.example.a20200711runtimepermissiontestimport android.content.Intent
import android.net.Uri
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)makeCall.setOnClickListener {try {val intent=Intent(Intent.ACTION_CALL)//构建一个隐式Intent,Intent的action指定为Intent.ACTION_CALL//为系统内置打电话的动作intent.data= Uri.parse("tel:10086")//data部分指定的协议式tel,号码是10086//action是Intent.Action_DIAL表示打开拨号面板,不需要声明权限//Intent.ACTION_CALL表示直接拨打电话,必须声明权限startActivity(intent)//启动隐式intent}catch (e:SecurityException){e.printStackTrace()}}}
}

权限申请优化

运行时,需要权限再进行申请

MainActivity.kt

package com.example.a20200711runtimepermissiontestimport android.Manifest
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.activity_main.*class MainActivity : AppCompatActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)makeCall.setOnClickListener {//checkSelfPermission用来判断用户是否给应用授权,第一个参数时Content,第二个参数时权限名,使用方法的返回值和PackageManager.PERMISSION_GRANTED做比较//相等就说明用户已经授权,不等就表示用户没有授权if (ContextCompat.checkSelfPermission(this,Manifest.permission.CALL_PHONE)!=PackageManager.PERMISSION_GRANTED){ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE),1)//没有授权的话// 调用ActivityCompat.requestPermissions()向用户申请授权,第一个参数时Activity的实例,第二个参数时String的数组,将要申请的权限名放在数组中即可,// 第三个参数是请求码,唯一即可}else{call()//执行打电话的逻辑操作}}}override fun onRequestPermissionsResult(//用户申请权限的结果会回调到这个方法requestCode: Int,permissions: Array<out String>,grantResults: IntArray//表示授权的结果) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)when(requestCode){1->{if (grantResults.isNotEmpty()&&grantResults[0]== PackageManager.PERMISSION_GRANTED){call()}else{Toast.makeText(this,"You denied the permission",Toast.LENGTH_SHORT).show()}}}}private fun call(){//将拨打电话的逻辑封装到call()方法中try {val intent=Intent(Intent.ACTION_CALL)intent.data= Uri.parse("tel:10086")startActivity(intent)}catch (e:SecurityException){e.printStackTrace()}}
}

访问其他程序中的数据

ContentProvider的用法:1.使用现有ContentProvider读取和操作相应程序中的数据;2.创建自己的ContentProvider,给程序的数据提供外部访问接口

ContentResolver的基本用法

通过Context中的getContentResolver()方法获取该类的实例

ContentResolver的insert()方法用于添加数据,update()方法用于更新数据,delete()方法用于删除数据,query()方法用于查询数据


ContentResolver的Uri参数(内容Uri)用于ContentProvider中的数据建立了唯一标识符,由authority(用于对不同的应用程序做区分,为了避免冲突,会采用应用包名的方式进行命名)和path用于同一应用程序中不同的表做区分的,通常添加到authority的后面;

例: content://com.example.app.provider/table1

​ comtent://com.example.app.provider/table2


解析成Uri对象才可以作为参数传入,调用Uri.parse()方法就可以将内容URI字符串解析成Uri对象了

val uri =Uri.parse)("content://com.example.app.provider/table1")

使用这个Uri对象查询table1表中的数据

val cursor =contentResolver.query{uri,//指定查询某个应用程序下的某一张表projection,//指定查询的列名selection,//指定where的约束条件selectionArgs.//为where中的占位符提供具体的值sortOrder)//指定查询结果的排序方式}

查询完成后返回的仍然是一个Cursor对象,数据从Cursor对象中逐个读取出来,读取的思路仍是通过移动游标的位置遍历Cursor的所有行,然后取出每一行中相对应的数据

while(cursor.moveToNext()){val column1=cursor.getString(cursor.getColumnIndex("column1"))
val column2=cursor.getInt(cursor.getColumnIndex("column2"))
}
cursor.close()

向table1表中添加一条数据,将待添加的数据组装到ContentValues中,values就是ContentValues中的值

val values=contentValuesOf("column1"to"text","column2"to 1)
contentResolver.insert(uri,values)

更新数据(把column1的值清空),使用selection和selectionArgs参数来对想要更新的数据进行约束,以防止所有的行都会受影响

val values=contentValuesOf("column1"to"")
contentResolver.update(uri,values,"column1=?and column2=?",arrayOf("text","1"))

删除数据

contentResolver.delete(uri,"column2=?",arrayOf("1"))

读取系统联系人

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:orientation="vertical"android:layout_height="match_parent"tools:context=".MainActivity"><ListViewandroid:layout_width="match_parent"android:layout_height="match_parent"android:id="@+id/contactsView"/></androidx.constraintlayout.widget.ConstraintLayout>

MainActivity.kt

package com.example.a20200714duqulianxirenstudyimport android.Manifest
import android.content.pm.PackageManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.ContactsContract
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import kotlinx.android.synthetic.main.activity_main.*class MainActivity : AppCompatActivity() {private val contactsList=ArrayList<String>()private lateinit var adapter:ArrayAdapter<String>override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)adapter=ArrayAdapter(this,android.R.layout.simple_list_item_1,contactsList)//选择listview的样式contactsView.adapter=adapter//数组与listview通过适配器绑定在一起if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS)!=PackageManager.PERMISSION_GRANTED){ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_CONTACTS),1)}else{readContacts()}}override fun onRequestPermissionsResult(requestCode: Int,permissions: Array<out String>,grantResults: IntArray) {super.onRequestPermissionsResult(requestCode, permissions, grantResults)when(requestCode){1->{if (grantResults.isNotEmpty()&&grantResults[0]==PackageManager.PERMISSION_GRANTED){readContacts()}else{Toast.makeText(this,"You denied the permission",Toast.LENGTH_SHORT).show()}}}}private fun readContacts(){//查询联系人数据contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,null,null,null)?.apply {//查询系统的联系人数据//ContactsContract.CommonDataKinds.Phone类已经做好封装,提供一个CONTENT_URI常量,是Uri.parse()方法解析出来的结果//然后对query()方法返回的Cursor对象进行遍历,这里使用?.操作符和apply函数来简化遍历代码while (moveToNext()){val displayName=getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME))//将联系人的姓名(ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME这个常量)逐个取出//获取联系人手机号val number=getString(getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER))//将联系人的手机号逐个取出
contactsList.add("$displayName\n$number")//数据添加到ListView的数据源里}adapter.notifyDataSetChanged()//通知刷新ListViewclose()//将Cursor对象关闭}}
}

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.example.a20200714duqulianxirenstudy"><uses-permission android:name="android.permission.READ_CONTACTS"/><applicationandroid: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=".MainActivity"><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity></application></manifest>

创建自己的ContentProvider

创建ContentProvider的步骤

实现跨程序共享数据的功能:新建一个类去继承ContentProvider的方式去实现

类继承ContentProvider的时候重写的6个抽象方法

package com.example.a20200716test2import android.content.ContentProvider
import android.content.ContentValues
import android.database.Cursor
import android.net.Uriclass MyProvider : ContentProvider() {/*初始化ContentProvider的时候调用,在这里完成对数据库的创建和升级等操作返回true表示ContentProvider初始化成功,返回false则表示失败*/override fun onCreate(): Boolean {TODO("Not yet implemented")}/*向ContentProvider中添加一条数据,uri参数用于确定要添加的表,待添加的数据保存在values参数中添加完成后,返回一个用于表示这条信记录的URI*/override fun insert(p0: Uri, p1: ContentValues?): Uri? {TODO("Not yet implemented")}/*从ContentProvider中查询数据,uri参数用于确定查询哪张表,projection参数用于确定查询那些列,selection和selectionAges参数用于约束那些行,sortOrder参数用于对结果进行排序,查询的结果存放在Cursor对象中返回*/override fun query(p0: Uri,p1: Array<out String>?,p2: String?,p3: Array<out String>?,p4: String?): Cursor? {TODO("Not yet implemented")}/*更新ContentProvider中已有的数据,uri参数用于确定更新拿一张表中的数据,新数据保存在values参数中,selection和selectionArgs参数用于约束更新哪些行,受影响的行数将作为返回值返回*/override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int {TODO("Not yet implemented")}/*从ContentProvider中删除数据,uri 参数用于确定删除哪一张表中的数据,selection和selectionArgs参数用于约束删除哪些行,被删除的行数将作为返回值返回*/override fun delete(p0: Uri, p1: String?, p2: Array<out String>?): Int {TODO("Not yet implemented")}/*根据传入的内容URI返回相应的MIME类型*/override fun getType(p0: Uri): String? {TODO("Not yet implemented")}}

uri参数是调用ContentResolver的增删改查方法时传递过来的,对uri参数进行解析从而分析调用芳期望访问的表和数据

例:content://com.example.app.provider/table1/1 表示调用方期望访问的是com.example.app这个应用的table1表中id为1的数据


内容URI以路径结尾表示期望访问该表中所有数据,以id结尾表示期望访问该表中拥有相应id的数据,可以使用通配符分别匹配这两种格式的内容的URI:

*表示匹配任意长度的任意字符

​ 例:content://com.example.app.provider/*

#表示匹配任意长度的数字

​ 例: content://com.example.app.provider/table1/#


UriMatcher可以实现匹配内容URI的功能,UriMatcher的addURI() 参数分别是authority path和一个自定义代码 ,调用UriMatcher的match()能传入一个Uri对象,返回值为某个能够匹配这个Uri对象所对应的自定义代码,利用这个代码就能判断出调用方期待访问的是哪张表中的数据

package com.example.a20200716test2import android.content.ContentProvider
import android.content.ContentValues
import android.content.UriMatcher
import android.database.Cursor
import android.net.Uriclass MyProvider : ContentProvider() {private val table1Dir = 0private val table1Item = 1private val table2Dir = 2private val table2Item = 3private val uriMatcher = UriMatcher(UriMatcher.NO_MATCH)//创建UriMatch的实例init {//UriMatcher的实例调用addURI()方法来将期待匹配的内容URI格式传递进去,传入的路径参数可用通配符uriMatcher.addURI("com.exxample.app.provider", "table1", table1Dir)uriMatcher.addURI("com.example.app.provider", "table1/#", table1Item)uriMatcher.addURI("com.example.app.provider", "table2", table2Dir)uriMatcher.addURI("com.example.app.provider", "table2/#", table2Item)}/*初始化ContentProvider的时候调用,在这里完成对数据库的创建和升级等操作返回true表示ContentProvider初始化成功,返回false则表示失败*/override fun onCreate(): Boolean {TODO("Not yet implemented")}/*向ContentProvider中添加一条数据,uri参数用于确定要添加的表,待添加的数据保存在values参数中添加完成后,返回一个用于表示这条新记录的URI*/override fun insert(p0: Uri, p1: ContentValues?): Uri? {TODO("Not yet implemented")}/*从ContentProvider中查询数据,uri参数用于确定查询哪张表,projection参数用于确定查询那些列,selection和selectionAges参数用于约束那些行,sortOrder参数用于对结果进行排序,查询的结果存放在Cursor对象中返回*/override fun query(uri: Uri,projection: Array<out String>?,selection: String?,selectionArgs: Array<out String>?,sortOrder: String?): Cursor? {when (uriMatcher.match(uri)) {//通过match()方法对传入的Uri对象进行匹配//UriMatcher中某个内容URI格式成功匹配了该Uri对象,就会返回相应的自定义代码//就能判断调用方期望访问的是什么数据table1Dir -> {//查询table1表中所有数据}table1Item -> {//查询table1表中的单条数据}table2Dir -> {//查询table2表中所有数据}table2Item -> {//查询table2表中的单条数据}}}/*更新ContentProvider中已有的数据,uri参数用于确定更新拿一张表中的数据,新数据保存在values参数中,selection和selectionArgs参数用于约束更新哪些行,受影响的行数将作为返回值返回*/override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int {TODO("Not yet implemented")}/*从ContentProvider中删除数据,uri 参数用于确定删除哪一张表中的数据,selection和selectionArgs参数用于约束删除哪些行,被删除的行数将作为返回值返回*/override fun delete(p0: Uri, p1: String?, p2: Array<out String>?): Int {TODO("Not yet implemented")}/*根据传入的内容URI返回相应的MIME类型MIME字符串的格式:必须以vnd开头如果内容URI以路径结尾,则后接android.cursor.dir/;如果内容URI以id结尾,则后接android.cursor.item/最后接上vnd.<authority>.<path>对于content://com.example.app.provider/table1这个内容URI,对应的MINE类型为vnd.android.cursor.dir/vnd.com.example.app.provider.table1
对于content://com.example.app.provider/table1/1这个内容URI,对应的MINE类型为vnd.android.cursor.item/vnd.com.example.app.provider.table1*/override fun getType(uri: Uri)=when(uriMatcher.match(uri)) {table1Dir->"vnd.android.cursor.dir/vnd.com.example.app.provider.table1"table1Item->"vnd.android.cursor.item/vnd.com.example.app.provider.table1"table2Dir->"vnd.android.cursor.dir/vnd.com.example.app.provider.table2"table1Item->"vnd.android.cursor.item/vnd.com.example.app.provider.table2"else->null}}

实现跨程序数据共享

第一行代码第三版笔记相关推荐

  1. Android 学习之《第一行代码》第二版 笔记(二十三)Material Design 实战 —— 卡片式布局

    实现基础: Android 学习之<第一行代码>第二版 笔记(二十二)Material Design 实战 -- 悬浮按钮和可交互提示 卡片式布局 卡片式布局是 Materials Des ...

  2. 第一行代码-android-第三版-pdf扫描-思维导图-课件-源码

    第一行代码-android-第三版-pdf扫描-思维导图-课件-源码 一帮公众号各种要你关注, 各种压缩包层层套娃要密码, 还要进群, 真他妈日了gou了,找了半天 分享给大家, 毫无套路! pdf扫 ...

  3. 第一行代码 (第三版) 第八,九,十章

    一: 泛型和委托 1.泛型 泛型类: class MyClass<T>{ fun method(param: T) : T { return param } } 泛型方法: class M ...

  4. 第一行代码 第三版 第11章网络技术 11.6.1 Retrofit 应用 报错:android.system.ErrnoException: isConnected failed: ECONNRE

    在学习第11章 11.6.1Retrofit用法,这节的时候发生的报错:书上关于这个地方并没有说. 我搜索问题的关键语句是: java.net.ConnectException: Failed to ...

  5. Android build.gradle文件详解(转述自《Android第一行代码》第二版)

    Android build.gradle文件详解 1. 最外层目录下的build.gradle文件 1.1 repostories 1.2 dependencies 2. app目录下的build.g ...

  6. 第一行代码android的读后感,《第一行代码Android》读书笔记

    自学android一段时间了,一开始是看看视频,跟着打打代码,后来也有跟着团队一起做项目,一直都很零散,并没有真正系统的学习过,虽然能跟得上项目,但总觉得基础不牢固扎实,之前有读过郭霖老师博客里的几篇 ...

  7. 《第一行代码Android》读书笔记

    自学android一段时间了,一开始是看看视频,跟着打打代码,后来也有跟着团队一起做项目,一直都很零散,并没有真正系统的学习过,虽然能跟得上项目,但总觉得基础不牢固扎实,之前有读过郭霖老师博客里的几篇 ...

  8. 安卓第一行代码第3版pdf_SPECFEM2D用户手册——第3章 网格生成——3.1 如何使用SPECFEM2D...

    参考资料 manual_SPECFEM2D.pdf 数值实现 Julia 1.4.2/MATLAB 2019a 备用系统 Ubuntu 64 地球物理局 地震波动力学实验室 谱元组 译# 声明 # 欢 ...

  9. 《第一行代码》书籍阅读笔记

    注:书籍阅读笔记,方便查看 第1章 开始启程,你的第一行Android代码 第2章 先从看得到的入手,探究活动 1.隐藏标题栏 在onCreate()方法中添加: requestWindowFeatu ...

最新文章

  1. orcad快捷键_在orcad同一页面的连接关系应该怎么处理呢?
  2. python: 多线程实现的两种方式及让多条命令并发执行
  3. springboot-web开发
  4. mysql 过程 的函数的区别是什么意思_Mysql中存储过程和函数的区别是什么
  5. Python 爬虫利器 Beautiful Soup 4 之文档树的搜索
  6. CentOS thrift python demo
  7. ModelCoder状态机中的State逻辑
  8. git 合并其他分支代码到自己的分支
  9. ISA 发布内网 NLB
  10. win10一键改字体
  11. 基于嵌入式linux电子相册代码,基于嵌入式ARM_Linux的电子相册设计方案.docx
  12. html弹窗复制,js复制弹窗美化
  13. 奥地利邮政服务推出加密收藏邮票
  14. Swift语言编写一个简单的条形码扫描APP
  15. unctf2020部分wp
  16. Unity iOS包打出的app名称空格丢失
  17. 日常开单送货VBA模块
  18. html 与 css 画哆啦A梦
  19. 小程序权限设置:小程序下载图片保存到相册拒绝权限后,再次打开权限的解决方案
  20. postfix smtpd_recipient_restrictions配置错误导致smtpd问题

热门文章

  1. java中的双冒号操作符
  2. 微信小程序实现微信支付
  3. Uderstanding and using Pointers 读书笔记
  4. IDEA 2019.3 导入导出设置
  5. PowerShell:无法加载文件 XXXXXXX,因为在此系统上禁止运行脚本
  6. 升级Mac内置的vim
  7. MySQL 生成累计乘积
  8. PXC 配置笔记-从MySQL直接转成PXC集群
  9. 关于AttributeError: module ‘torch.nn‘ has no attribute ‘Moudle‘的解决方法
  10. linux 进程的作用,ubuntu9.10这些进程有什么作用?