不积跬步,无以至千里;不积小流,无以成江海。要沉下心来,诗和远方的路费真的很贵!
build.gradle文件中的plugins代码块中加入以下语句。id 'kotlin-android-extensions'
import kotlinx.android.synthetic.main.activity_main.*
Fragment进行学习时,遇到了直接使用控件id来使用控件的问题。其总是报空指针,加入?.后,虽然不会出现崩溃的情况,但是却也不能显示出控件,证明其确实未加载。Kotlin中直接使用id操作控件的情况,必须在布局文件加载完毕后使用才可以,因为只有加载完毕了,其才存在;布局文件都没加载,如何来的id,怎么去使用,肯定是报空的。所以在Fragment中,我们习惯性在onCreateView方法中使用id来操作控件肯定是会报空的,因为view都还没有return。()的属于类,是继承;没有()的属于接口,是实现。class MainActivity : AppCompatActivity(), View.OnClickListener{...}
Java语法一致。fun initClick() {
//跳转页面功能
btn_register.setOnClickListener(this)
//网络请求功能
btn_login.setOnClickListener(this)
//文本控件操作功能
btn_reset.setOnClickListener(this)
}
switch了,而是使用when。override fun onClick(v: View?) {
when (v?.id) {
//点击注册,跳转进入注册页面
R.id.btn_register -> {
//创建跳转对象
val intent = Intent(this, MainActivity2::class.java)
//进行页面跳转
startActivity(intent)
}
//点击监听,得到网络请求数据
R.id.btn_login -> {
val intent = Intent(this, HomeActivity::class.java)
//进行页面跳转
startActivity(intent)
getDataByInternet()
}
R.id.btn_reset -> {
edit_username.setText("")
edit_password.setText("")
}
}
}
Intent进行界面跳转。val intent = Intent(this, HomeActivity::class.java)
//进行页面跳转
startActivity(intent)
get类方法,直接属性调用,都不需要get了;set类方法可有两种操作。Log.i("testData", edit_username.text.toString())
Log.i("testData", edit_password.text.toString())
//setText Java版本
edit_username.setText("")
edit_password.setText("")
// = 版本
edit_username.text = ""
edit_password.text = ""
Retrofit进行网络数据的请求。/** FileName: User
* Author: lvjunkai
* Date: 2022/8/2 14:17
* Description: 用户类,和后端接口的数据格式一致
*/
class User(
val id : Int,
val username : String,
val password : String,
val nickname : String,
val avatar : String,
val sex : String,
val age : Int,
val phone : String,
val email : String,
val islogin : String
)
/** FileName: ApiService
* Author: lvjunkai
* Date: 2022/8/2 15:31
* Description: 网络请求接口
*/
interface ApiService {
@GET("user")
fun listUser(): Call<List<User>>
@GET("recipe")
fun listRecipe() : Call<List<Recipe>>
}
private lateinit var retrofit: Retrofit
private lateinit var api: ApiService
/**
* 建立网络请求链接
*/
fun initSocket() {
// builder模式构建Retrofit对象
retrofit = retrofit2.Retrofit.Builder()
.baseUrl("https://www.fastmock.site/mock/d6931ad23f0e1bdb2061d1c5363c45cb/KotlinLearning/")
.addConverterFactory(GsonConverterFactory.create())
.build()
// 创建接口层的代理对象,内部通过动态代理创建了ApiService的代理对象
api = retrofit.create(ApiService::class.java)
}
/**
* 网络请求得到数据的具体实现
*/
fun getDataByInternet() {
// 执行异步请求
api.listUser().enqueue(object : Callback<List<User>> {
override fun onResponse(call: Call<List<User>>, response: Response<List<User>>) {
Log.e("data", response.body().toString())
response.body()?.let { it1 -> Log.e("data", it1.size.toString()) }
for (i in 0 until (response.body()?.size!!)) {
response.body()?.get(i)?.let { it1 -> Log.e("onResponse", it1.id.toString()) }
response.body()?.get(i)?.let { it1 -> Log.e("onResponse", it1.username) }
response.body()?.get(i)?.let { it1 -> Log.e("onResponse", it1.password) }
response.body()?.get(i)?.let { it1 -> Log.e("onResponse", it1.nickname) }
response.body()?.get(i)?.let { it1 -> Log.e("onResponse", it1.avatar) }
response.body()?.get(i)?.let { it1 -> Log.e("onResponse", it1.sex) }
response.body()?.get(i)?.let { it1 -> Log.e("onResponse", it1.age.toString()) }
response.body()?.get(i)?.let { it1 -> Log.e("onResponse", it1.phone) }
response.body()?.get(i)?.let { it1 -> Log.e("onResponse", it1.email) }
response.body()?.get(i)?.let { it1 -> Log.e("onResponse", it1.islogin) }
}
}
override fun onFailure(call: Call<List<User>>, t: Throwable) {
Log.e("onFailure", t.toString())
}
})
}
Fragment为例子,进行常量变量总结。lateinit varprivate lateinit var homeFragment: HomeFragment
变量延迟加载,是声明变量时不加载,使用其时才加载,即懒汉式加载。无论其有无声明,在没有创建前使用它,会报错。
if(homeFragment == null){
homeFragment = HomeFragment()
}
上述代码中,未创建
homeFragment,就使用其来判断是否为空,故会报错。
private var myFragment : MyFragment? = null
?代表该对象可为空。
myFragment?:let {
myFragment = MyFragment()
fragmentTransaction.add(R.id.fragment,myFragment!!)
}
myFragment?.let { fragmentTransaction.show(it) }
valprivate val homeFragment : HomeFragment = HomeFragment()
private val homeFragment3 : HomeFragment
init {
homeFragment3 = HomeFragment()
}
常量要么在声明时直接初始化,要么在构造函数
init中初始化。
C语言提供了全局变量的定义,其又称:外部变量。Activity中结合Fragment显示界面。Activity中,因为Fragment的跳转等都是在Activity中执行的。Fragment。private var homeFragment : HomeFragment? = null
private var recipeFragment : RecipeFragment? = null
private var discussionFragment : DiscussionFragment? = null
private var myFragment : MyFragment? = null
/**
* 设置底部栏监听
*/
private fun initClick() {
ll_home.setOnClickListener(this)
ll_recipe.setOnClickListener(this)
ll_discussion.setOnClickListener(this)
ll_person.setOnClickListener(this)
}
Fragment。//创建的时候显示首页
ll_home?.isSelected = true
ll_recipe?.isSelected = false
ll_discussion?.isSelected = false
ll_person?.isSelected = false
initHomeFragment()
Fragment。Fragment显示都需要一个新的事务,因为事务提交了,就失效了,需要一个新的。private fun initHomeFragment() {
//每一个Fragment都需要一个单独的事务
//因为提交后,事务虽然创建方式一样,但是本质已经不同
var fragmentTransaction : FragmentTransaction = supportFragmentManager.beginTransaction()
//若是homeFragment为空
homeFragment?:let {
homeFragment = HomeFragment()
fragmentTransaction.add(R.id.fragment, homeFragment!!)
}
//隐藏事务中所有的Fragment
hideAllFragment(fragmentTransaction)
//显示需要显示的Fragment
homeFragment?.let { fragmentTransaction.show(it) }
//提交事务,事务一定要提交,不然无效
fragmentTransaction.commit()
}
Fragment。/**
* 隐藏所有Fragment
*/
private fun hideAllFragment(fragmentTransaction: FragmentTransaction) {
homeFragment?.let { fragmentTransaction.hide(homeFragment!!) }
recipeFragment?.let { fragmentTransaction.hide(recipeFragment!!) }
discussionFragment?.let { fragmentTransaction.hide(discussionFragment!!) }
myFragment?.let { fragmentTransaction.hide(myFragment!!) }
}
Fragment。override fun onClick(v: View?) {
when (v?.id) {
R.id.ll_home -> {
ll_home?.isSelected = true
ll_recipe?.isSelected = false
ll_discussion?.isSelected = false
ll_person?.isSelected = false
initHomeFragment()
}
R.id.ll_recipe -> {
ll_home?.isSelected = false
ll_recipe?.isSelected = true
ll_discussion?.isSelected = false
ll_person?.isSelected = false
initRecipeFragment()
}
R.id.ll_discussion -> {
ll_home?.isSelected = false
ll_recipe?.isSelected = false
ll_discussion?.isSelected = true
ll_person?.isSelected = false
initDiscussionFragment()
}
R.id.ll_person -> {
ll_home?.isSelected = false
ll_recipe?.isSelected = false
ll_discussion?.isSelected = false
ll_person?.isSelected = true
initMyFragment()
}
else -> { }
}
}
RecyclerView实现方剂数据的展示。
package com.example.myapplication.model
/** FileName: Recipe
* Author: lvjunkai
* Date: 2022/8/3 11:36
* Description: 方剂数据类 网络请求数据
*/
class Recipe(
val id: Int,
val name: String,
val song: String,
val medicines: String,
val function: String,
val cure: String,
val type: String,
val childtype: String,
val book: String
)
@GET("recipe")
fun listRecipe() : Call<List<Recipe>>
item布局。RecyclerView,子布局如下:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/tv_recipe_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_marginTop="8dp"
android:gravity="center"
android:text="大承气汤"
android:textColor="@color/black"
android:textSize="15dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/tv_recipe_function"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:text="峻下热结"
app:layout_constraintStart_toStartOf="@+id/tv_recipe_name"
app:layout_constraintTop_toBottomOf="@+id/tv_recipe_name" />
<TextView
android:id="@+id/tv_recipe_book"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginRight="8dp"
android:text="《伤寒论》"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
androidx.constraintlayout.widget.ConstraintLayout>
item适配器。package com.example.myapplication.adapter
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.myapplication.R
import com.example.myapplication.model.Recipe
/** FileName: RecipeAdapter
* Author: lvjunkai
* Date: 2022/8/4 9:52
* Description: 方剂数据适配器
*/
class RecipeAdapter(private val recipeList : List<Recipe>) : RecyclerView.Adapter<RecipeAdapter.MyViewHolder>(){
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecipeAdapter.MyViewHolder {
//加载子布局
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_recipe,parent,false)
return MyViewHolder(view)
}
override fun onBindViewHolder(holder: RecipeAdapter.MyViewHolder, position: Int) {
//操作控件
val item = recipeList[position]
with(holder){
tv_recipe_name.text = item.name
tv_recipe_function.text = item.function
tv_recipe_book.text = item.book
}
}
override fun getItemCount(): Int {
//返回数据数目
return recipeList.size
}
inner class MyViewHolder(view : View) : RecyclerView.ViewHolder(view){
//获取控件
val tv_recipe_name : TextView = view.findViewById(R.id.tv_recipe_name)
val tv_recipe_function : TextView = view.findViewById(R.id.tv_recipe_function)
val tv_recipe_book : TextView = view.findViewById(R.id.tv_recipe_book)
}
}
RecyclerView控件。package com.example.myapplication.fragment
import android.os.Bundle
import android.util.Log
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.myapplication.R
import com.example.myapplication.adapter.RecipeAdapter
import com.example.myapplication.model.Recipe
import com.example.myapplication.tools.ApiService
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
class RecipeFragment : Fragment() {
//控件
private lateinit var rcv_recipe: RecyclerView
//网络连接
private lateinit var retrofit: Retrofit
//网络接口
private lateinit var api: ApiService
//适配器
private var recipeAdapter: RecipeAdapter? = null
//数据列表
private var recipeList = ArrayList<Recipe>()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//加载布局
val view = LayoutInflater.from(container?.context)
.inflate(R.layout.fragment_recipe, container, false)
//获取控件RecyclerView
rcv_recipe = view.findViewById(R.id.rcv_recipe)
initSocket()
initData()
return view
}
/**
* 初始化网络连接
*/
private fun initSocket() {
// builder模式构建Retrofit对象
retrofit = retrofit2.Retrofit.Builder()
.baseUrl("https://www.fastmock.site/mock/d6931ad23f0e1bdb2061d1c5363c45cb/KotlinLearning/")
.addConverterFactory(GsonConverterFactory.create())
.build()
// 创建接口层的代理对象,内部通过动态代理创建了ApiService的代理对象
api = retrofit.create(ApiService::class.java)
}
/**
* 加载网络数据并显示
*/
private fun initData() {
//异步加载网络数据
api.listRecipe().enqueue(object : Callback<List<Recipe>> {
override fun onResponse(call: Call<List<Recipe>>, response: Response<List<Recipe>>) {
//打印数据数量
response.body()?.let { it1 -> Log.e("size", it1.size.toString()) }
for (i in 0 until (response.body()?.size!!)) {
//循环加入数据列表
response.body()?.get(i)?.let { it1 ->
recipeList.add(it1)
}
}
//设置垂直布局
val linearLayoutManager = LinearLayoutManager(activity)
linearLayoutManager.orientation = LinearLayoutManager.VERTICAL
rcv_recipe.layoutManager = linearLayoutManager
//添加下划线
rcv_recipe.addItemDecoration(DividerItemDecoration(activity, LinearLayoutManager.VERTICAL))
//加载适配器
recipeAdapter = RecipeAdapter(recipeList)
rcv_recipe.adapter = recipeAdapter
}
override fun onFailure(call: Call<List<Recipe>>, t: Throwable) {
Log.e("onFailure", t.toString())
}
})
}
}
注意:适配器加载不放在onCreateView中,是因为异步请求,可能网络请求的线程还没有执行完毕,就进行了适配器加载,导致出现没有数据的情况,需要进行线程同步。但是放在网络请求线程中,可以保证其在同一个线程,必然在数据请求完毕后,再进行适配器的加载,必然有数据。若是就不要放在同一个线程中,那么就进行线程的同步,这个在下文序号16进行阐述。
RecyclerView的点击事件就是item的点击响应。item的子布局设置一个点击监听即可。layout_recipe.setOnClickListener(View.OnClickListener {
val intent = Intent(context,RecipeActivity::class.java)
context.startActivity(intent)
})
layout_recipe是item子布局的id,对其设置点击事件,内部代码为点击其所执行的具体操作。Activity传递到Activity。例子——登录界面(MainActivity.kt)跳转到主页面(HomeActivity.kt),传递过去登录用户信息并打印在日志栏中。
MainActivity.kt
//声明Bundle传递对象
private var bundle : Bundle ?= null
//创建传递对象
bundle = Bundle()
//加入第一个用户数据
response.body()?.get(0)?.id?.let { bundle?.putInt("id", it) }
response.body()?.get(0)?.username?.let { bundle?.putString("username", it) }
response.body()?.get(0)?.password?.let { bundle?.putString("password", it) }
response.body()?.get(0)?.nickname?.let { bundle?.putString("nickname", it) }
response.body()?.get(0)?.avatar?.let { bundle?.putString("avatar", it) }
response.body()?.get(0)?.sex?.let { bundle?.putString("sex", it) }
response.body()?.get(0)?.age?.let { bundle?.putInt("age", it) }
response.body()?.get(0)?.phone?.let { bundle?.putString("phone", it) }
response.body()?.get(0)?.email?.let { bundle?.putString("email", it) }
response.body()?.get(0)?.islogin?.let { bundle?.putString("islogin", it) }
//创建跳转对象
intent = Intent(this@MainActivity, HomeActivity::class.java)
//传递对象不为空,跳转对象不为空,就存入
bundle?.let { intent?.putExtras(it) }
//进行页面跳转
startActivity(intent)
bundle对象中一定存在数据。而且由于不在主线程进行跳转,所以要使用@MainActivity告知Intent对象跳转的起点位于MainActivity。HomeActivity.kt//获取传递过来的数据存储对象
//自动判断类型,无需手动书写
var bundle = this.intent.extras
var sb = StringBuilder()
sb.append(bundle?.getInt("id"))
sb.append(bundle?.getString("username"))
sb.append(bundle?.getString("password"))
sb.append(bundle?.getString("nickname"))
sb.append(bundle?.getString("avatar"))
sb.append(bundle?.getString("sex"))
sb.append(bundle?.getInt("age"))
sb.append(bundle?.getString("phone"))
sb.append(bundle?.getString("email"))
sb.append(bundle?.getString("islogin"))
Log.e("bundle",sb.toString())
Kotlin语言中,String无法使用+号进行连接,因此此处使用StringBuilder来连接字符串。注:要是需要线程安全的,可以使用StringBuffer来连接字符串。Activity 传递到Fragment。例子——主页面(HomeActivity.kt)传递登录用户信息到个人主页(即最后一个Fragment,即MyFragment.kt),然后在个人主页面进行用户信息显示。
MainActivity.kt
//声明Bundle传递对象
private var bundle : Bundle ?= null
//获取传递过来的数据存储对象
//自动判断类型,无需手动书写
bundle = this.intent.extras
private fun initMyFragment() {
var fragmentTransaction : FragmentTransaction = supportFragmentManager.beginTransaction()
myFragment?:let {
myFragment = MyFragment()
//将数据传递给fragment
myFragment?.arguments = bundle
fragmentTransaction.add(R.id.fragment,myFragment!!)
}
//隐藏事务中所有的Fragment
hideAllFragment(fragmentTransaction)
//显示需要显示的Fragment
myFragment?.let { fragmentTransaction.show(it) }
//提交事务,事务一定要提交,不然无效
fragmentTransaction.commit()
}
bundle是用户从登录页面Activity传递过来的,主页面Activity接收一下,直接就传递给了Fragment,没有经过其他处理。(PS:哪个Fragment需要数据,就在哪个Fragment的arguments中赋值即可)
传递过来的具体数据格式即为第1小点中传递过来的数据格式。
MyFragment.kt
package com.example.myapplication.fragment
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import com.example.myapplication.R
import kotlinx.android.synthetic.main.fragment_my.*
import java.lang.StringBuilder
class MyFragment : Fragment() {
//声明Bundle存储对象
private var bundle : Bundle ?= null
private var sb : StringBuilder ?= null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
//获取数据存储对象
bundle = this.arguments
//连接字符串
sb = StringBuilder()
sb?.append(bundle?.getInt("id"))
sb?.append(",")
sb?.append(bundle?.getString("username"))
sb?.append(",")
sb?.append(bundle?.getString("password"))
sb?.append(",")
sb?.append(bundle?.getString("nickname"))
sb?.append(",")
sb?.append(bundle?.getString("avatar"))
sb?.append(",")
sb?.append(bundle?.getString("sex"))
sb?.append(",")
sb?.append(bundle?.getInt("age"))
sb?.append(",")
sb?.append(bundle?.getString("phone"))
sb?.append(",")
sb?.append(bundle?.getString("email"))
sb?.append(",")
sb?.append(bundle?.getString("islogin"))
return inflater.inflate(R.layout.fragment_my, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
//传递过来的数据显示在界面上
tv_test.text = sb.toString()
}
}
Fragment中调用id直接操作控件,需要在View返回之后,所以在onViewCreated方法中使用,因为onViewCreated方法在onCreateView方法之后执行。Fragment的布局文件,然后通过findViewById得到控件。
13的传递是item点击到一个activity界面,所以数据是从Fragment传递到Activity。以上例点击事件为例子,点击item,跳转到另一个界面,并传递过去方剂数据。
RecipeAdapter.kt
//页面跳转逻辑
var intent = Intent(context,RecipeActivity::class.java)
//数据存储对象
var bundle = Bundle()
//将数据保存进去
bundle.putInt("id",item.id)
bundle.putString("name",item.name)
bundle.putString("song",item.song)
bundle.putString("medicines",item.medicines)
bundle.putString("function",item.function)
bundle.putString("cure",item.cure)
bundle.putString("type",item.type)
bundle.putString("childtype",item.childtype)
bundle.putString("book",item.book)
//将数据对象保存进跳转对象
intent.putExtras(bundle)
//实现跳转传递
context.startActivity(intent)
RecipeActivity.kt//获取传递过来的数据对象
var bundle = this.intent.extras
//将数据放入文本控件显示
tv_id.text = bundle?.getInt("id").toString()
tv_name.text = bundle?.getString("name")
tv_song.text = bundle?.getString("song")
tv_medicines.text = bundle?.getString("medicines")
tv_function.text = bundle?.getString("function")
tv_cure.text = bundle?.getString("cure")
tv_type.text = bundle?.getString("type")
tv_childtype.text = bundle?.getString("childtype")
tv_book.text = bundle?.getString("book")

Fragment传递到Fragment。Activity的Fragment的数据,可以通过Activity进行传递;位于不同Activity的Fragment,可以通过Fragment本身和Activity作为中间媒介进行传递。implementation 'com.github.bumptech.glide:glide:4.11.0' //Glide
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
<uses-permission android:name="android.permission.INTERNET" />
//加载控件
Glide.with(img_avatar)
//形成bitmap类型
.asBitmap()
//加载图片网址
.load(bundle?.getString("avatar"))
//预加载
.placeholder(R.mipmap.person_selected)
//加载错误显示图片
.error(R.drawable.btn_background)
//放入控件
.into(img_avatar)
activity?.let {
Glide.with(it)
//形成bitmap类型
.asBitmap()
//加载图片网址
.load(bundle?.getString("avatar"))
//预加载
.placeholder(R.mipmap.person_selected)
//加载错误显示图片
.error(R.drawable.btn_background)
//放入控件
.into(img_avatar)
}

CircleImageView)。implementation 'de.hdodenhof:circleimageview:3.1.0'
ImageView控件改为de.hdodenhof.circleimageview.CircleImageView控件即可。