欢迎大家学习
这是一个的简单的开发教程,欢迎大家学习,初学者请完全按照教程一步一步走,所有命名请按照文中教程。 第一次写博客,有问题欢迎大家指出。
开发工具:android studio版本4.1.1, 开发语言:Kotlin。
所有代码是跟着郭霖老师的第一行代码的第三版学的,不懂的可以看看书。
网络部分Retrofit,MVVM架构,推荐多看看书中viewmodel部分。
创建项目
首先创建一个项目,点击Empty Activity,然后Next。
全部和图片一致。
选择project
app目录下build.gradle中对应图片位置添加
//jvm版本
kotlinOptions { jvmTarget = 1.8}
app目录下build.gradle中对应图片位置添加
plugins {
id 'com.android.application'
id 'kotlin-android'
id 'kotlin-android-extensions'
}
//依赖
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.1'
implementation 'androidx.recyclerview:recyclerview:1.0.0'
implementation 'androidx.lifecycle:lifecycle-extensions:2.1.0'
implementation 'androidx.lifecycle:lifecycle-livedata-ktx:2.2.0'
implementation 'androidx.work:work-runtime:2.2.0'
implementation 'com.google.android.material:material:1.0.0'
implementation 'com.squareup.retrofit2:retrofit:2.6.1'
implementation 'com.squareup.retrofit2:converter-gson:2.6.1'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0'
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1'
implementation 'de.hdodenhof:circleimageview:3.0.1'
implementation 'com.youth.banner:banner:2.1.0'
implementation 'com.github.bumptech.glide:glide:4.11.0'
implementation 'androidx.navigation:navigation-fragment:2.1.0'
implementation 'androidx.navigation:navigation-ui:2.1.0'
implementation 'androidx.navigation:navigation-fragment-ktx:2.1.0'
implementation 'androidx.navigation:navigation-ui-ktx:2.1.0'
implementation 'com.permissionx.aixlibrary:aixlibrary:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
点击右上角Sync Now
创建包logic,ui,util
创建类HnbApplication
HnbApplication文件
package com.hnb.huinongbang
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
//用于全局获取context
class HnbApplication : Application() {
companion object {
@SuppressLint("StaticFieldLeak") //这是Application的contxt,不是activity或service的,所以不存在内存泄漏,故注解。
lateinit var context: Context
}
override fun onCreate() {
super.onCreate()
context = applicationContext
}
}
创建平时开发用的工具包类LogUtil,ToastUtil(先创Util包)
package com.hnb.huinongbang.util
import android.util.Log
object LogUtil {
private const val VERBOSE = 1
private const val DEBUG = 2
private const val INFO = 3
private const val WARN = 4
private const val ERROR = 5
private var level = VERBOSE
fun v(tag: String, msg: String){
if (level <= VERBOSE) {
Log.v(tag, msg)
}
}
fun d(tag: String, msg: String){
if (level <= DEBUG) {
Log.d(tag, msg)
}
}
fun i(tag: String, msg: String){
if (level <= INFO) {
Log.i(tag, msg)
}
}
fun w(tag: String, msg: String){
if (level <= WARN) {
Log.w(tag, msg)
}
}
fun e(tag: String, msg: String){
if (level <= ERROR) {
Log.e(tag, msg)
}
}
}
package com.hnb.huinongbang.util
import android.widget.Toast
import com.hnb.huinongbang.HNBApplication
object ToastUtil {
fun show(message: String){
Toast.makeText(HNBApplication.context, message, Toast.LENGTH_SHORT).show()
}
fun showLong(message: String){
Toast.makeText(HNBApplication.context, message, Toast.LENGTH_LONG).show()
}
}
先看logic包下的总体结构
创建logic内的包dao,model,network,worker
文件Repository
model下的UserResponse
package com.hnb.huinongbang.logic.model
import java.text.SimpleDateFormat
import java.util.*
//用户信息类
//code(返回状态) user(用户)
data class UserResponse(val code: Int, val data: User, val message: String)
data class User(val user_ID: Int, //id
val user_phone: String, //电话号码
val user_password: String, //密码
val user_name: String, //用户名称
val user_nickname: String, //用户昵称
val user_sex: String, //用户性别
val user_birthday: Date, //用户生日
val user_address: String, //用户住址
val user_introduce: String, //个人介绍
val user_identitysign: Int, //是否实名认证
val user_poorsign: Int, //是否贫困认证
val user_doctorsign: Int, //是否专家认证
val user_ptopsign: Int, //是否发布点对点资助
val user_admin: Int, //是否管理-员
val integral: Float, //积分
val money: Float, //钱
val user_identityNumber: String, //身份证号
val user_identityType: String, //用户身份证类型
val identityReason: String, //身份证审核备注
val poorReason: String, //贫困认证审核备注
val doctorReason: String, //专家审核备注
val ptopReason: String //点对点审核备注
){
fun getUser_birthday_year(): String? {
val birthday: Date = user_birthday
val sdf = SimpleDateFormat("yyyy")
return sdf.format(birthday)
}
fun getUser_birthday_month(): String? {
val birthday: Date = user_birthday
val sdf = SimpleDateFormat("MM")
return sdf.format(birthday)
}
fun getUser_birthday_day(): String? {
val birthday: Date = user_birthday
val sdf = SimpleDateFormat("dd")
return sdf.format(birthday)
}
fun getAnonymousName(): String? { //获取匿名
if (null == user_nickname) return " "
if (user_nickname.length <= 1) return "*"
if (user_nickname.length == 2) return user_nickname.substring(0, 1) + "*"
val cs = user_nickname.toCharArray()
for (i in 1 until cs.size - 1) {
cs[i] = '*'
}
return String(cs)
}
}
//登陆时传输到服务器的数据
data class LoginData(val name: String, val password: String)
dao下的UserDAO
package com.hnb.huinongbang.logic.dao
import android.content.Context
import androidx.core.content.edit
import com.google.gson.Gson
import com.hnb.huinongbang.HNBApplication
import com.hnb.huinongbang.logic.model.User
//用户的SharedPerferences
object UserDAO {
//存储用户
fun saveUser(user: User) {
sharedPreferences().edit {
putString("user", Gson().toJson(user))
}
}
//获取用户
fun getUser(): User {
val userJson = sharedPreferences().getString("user", "")
return Gson().fromJson(userJson, User::class.java)
}
//用户是否存在
fun isUserSaved() = sharedPreferences().contains("user")
//是否记住密码
fun isRemembered() = sharedPreferences().getBoolean("remember_password", false)
//记住密码
fun remember() = sharedPreferences().edit {
putBoolean("remember_password", true)
}
//不记住密码
fun unremember() = sharedPreferences().edit {
putBoolean("remember_password", false)
}
//获取SP
private fun sharedPreferences() = HNBApplication.context.getSharedPreferences("HNB", Context.MODE_PRIVATE)
}
network下的ServiceCreator,UserService,HNBNetwork
package com.hnb.huinongbang.logic.network
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
//Retrofit构造器
object ServiceCreator {
// 模拟器 "http://10.0.2.2/"
private const val BASE_URL = "https://www.huinongbang.club/"
private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL) //所有请求的根目录
.addConverterFactory(GsonConverterFactory.create()) //解析数据使用的转换库 --Gson直接转成对象
.build() //构建出Retrofit对象
fun <T> create(serviceClass: Class<T>): T = retrofit.create(serviceClass) //创建出相应Service接口的动态代理对象
//示例: val appservice = ServiceCreator.create(AppService::class.java)
inline fun <reified T> create(): T = create(T::class.java) //泛型实化
//示例: val appservice = ServiceCreator.create<AppService>()
}
package com.hnb.huinongbang.logic.network
import com.hnb.huinongbang.logic.model.LoginData
import com.hnb.huinongbang.logic.model.UserResponse
import retrofit2.Call
import retrofit2.http.*
interface UserService {
//登录
@POST("foremlogin")
fun login(@Query("name") name: String, @Query("password") password: String): Call<UserResponse>
}
package com.hnb.huinongbang.logic.network
import com.hnb.huinongbang.logic.model.LoginData
import com.hnb.huinongbang.util.LogUtil
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
import java.lang.RuntimeException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
//定义一个统一的网络数据源访问入口,对所有网络请求的API进行封装
object HNBNetwork {
//封装user的网络请求
private val userService = ServiceCreator.create<UserService>()
suspend fun login(loginData: LoginData) = userService.login(loginData.name, loginData.password).await()
//协程suspend
private suspend fun <T> Call<T>.await(): T {
return suspendCoroutine { continuation ->
enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val body = response.body()
LogUtil.d("Test", body.toString())
LogUtil.d("HNBNetwork", "服务器返回成功")
if (body != null) continuation.resume(body) //服务器返回成功
else continuation.resumeWithException(
//服务器返回成功,但是值为空
RuntimeException("response body is null 服务器返回成功,但是值为空")
)
}
override fun onFailure(call: Call<T>, t: Throwable) {
//服务器返回失败
LogUtil.d("HNBNetwork", "服务器返回失败")
continuation.resumeWithException(t)
}
})
}
}
}
logic包下的Repository
package com.hnb.huinongbang.logic
import androidx.lifecycle.liveData
import com.hnb.huinongbang.logic.dao.UserDAO
import com.hnb.huinongbang.logic.model.LoginData
import com.hnb.huinongbang.logic.model.User
import com.hnb.huinongbang.logic.network.HNBNetwork
import com.hnb.huinongbang.util.LogUtil
import kotlinx.coroutines.Dispatchers
import kotlin.coroutines.CoroutineContext
object Repository {
//获取用户 Dispatchers.IO函数线程类型设置,里面的代码全在子线程运行
fun login(loginData: LoginData) = fire(Dispatchers.IO) {
val userResponse = HNBNetwork.login(loginData)
if (userResponse.code == 1) { //根据状态来处理
LogUtil.d("登录模块", "登录成功,用户名:${userResponse.data.user_name}")
val user = userResponse.data
Result.success(user)
} else {
LogUtil.d("登录模块", "登录失败,${userResponse.message}")
Result.failure(RuntimeException("response status is ${userResponse.message}"))
}
}
//简化函数
private fun <T> fire(context: CoroutineContext, block: suspend () -> Result<T>) = liveData<Result<T>>(context) {
val result = try {
block()
} catch (e: Exception) {
Result.failure<T>(e)
}
emit(result)
}
// 用户操作封装
fun saveUser(user: User) = UserDAO.saveUser(user)
fun getUser() = UserDAO.getUser()
fun isUserSaved() = UserDAO.isUserSaved()
//记住密码
fun remember() = UserDAO.remember()
fun unremember() = UserDAO.unremember()
fun isRemembered() = UserDAO.isRemembered()
}
在ui包下新建login包,并新建LoginActivity
package com.hnb.huinongbang.ui.login
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.edit
import androidx.lifecycle.ViewModelProviders
import com.hnb.huinongbang.BottomActivity
import com.hnb.huinongbang.HNBApplication.Companion.context
import com.hnb.huinongbang.MainActivity
import com.hnb.huinongbang.R
import com.hnb.huinongbang.logic.Repository
import com.hnb.huinongbang.logic.model.LoginData
import com.hnb.huinongbang.logic.model.UserResponse
import com.hnb.huinongbang.logic.network.ServiceCreator
import com.hnb.huinongbang.logic.network.UserService
import com.hnb.huinongbang.util.LogUtil
import com.hnb.huinongbang.util.ToastUtil
import kotlinx.android.synthetic.main.activity_login.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response
class LoginActivity : AppCompatActivity() {
val viewModel by lazy { ViewModelProviders.of(this).get(LoginViewModel::class.java)}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login)
// 记住账号
if (Repository.isUserSaved()){
val user = Repository.getUser()
username.setText(user.user_phone)
// 记住密码功能
if (Repository.isRemembered()){
password.setText(user.user_password)
rememberPass.isChecked = true
}
}
// 登录按钮
loginBtn.setOnClickListener {
val name = username.text.toString()
val password = password.text.toString()
val loginData = LoginData(name, password)
viewModel.login(loginData)
}
//监听登录结果
viewModel.loginLiveData.observe(this, {result ->
val userResponse = result.getOrNull()
if(userResponse != null){
Repository.saveUser(userResponse)
if(rememberPass.isChecked){
Repository.remember()
}else{
Repository.unremember()
}
val intent = Intent(context, MainActivity::class.java)
startActivity(intent)
}else{
ToastUtil.show("账号或密码错误")
}
})
}
}
在创建login中创建LoginViewModel
package com.hnb.huinongbang.ui.login
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import com.hnb.huinongbang.logic.Repository
import com.hnb.huinongbang.logic.model.LoginData
class LoginViewModel : ViewModel() {
private val loginDataLiveData = MutableLiveData<LoginData>()
val loginLiveData = Transformations.switchMap(loginDataLiveData){loginData ->
Repository.login(loginData)
}
//外部调用函数
fun login(loginData: LoginData){
loginDataLiveData.value =loginData
}
}
修改layout中自动创建的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"
android:background="@drawable/login_bg">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="70dp"
android:textSize="30sp"
android:textColor="#fff"
android:text="登录 慧农帮!"/>
<EditText
android:id="@+id/username"
android:layout_width="280dp"
android:layout_height="40dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="50dp"
android:background="@drawable/logintext_bg"
android:inputType="number"
android:hint="请输入账号" />
<EditText
android:id="@+id/password"
android:layout_width="280dp"
android:layout_height="40dp"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:background="@drawable/logintext_bg"
android:inputType="textPassword"
android:hint="请输入密码" />
<LinearLayout
android:layout_width="280dp"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="10dp">
<CheckBox
android:id="@+id/rememberPass"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/logintext_bg"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="18sp"
android:text="记住密码"
android:textColor="#777777"
/>
</LinearLayout>
<TextView
android:id="@+id/loginBtn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="20dp"
android:text="登录"
android:textColor="#FAF8F8"
android:textSize="33sp"
android:textStyle="bold" />
</LinearLayout>
图片资源
login_bg.jpg
logintext.9.png
logintext_bg.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="#90ffffff" />
<corners android:radius="14dp" />
</shape>
接着是AndroidManifest.xml配置,先配置图中文件
colors.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="colorPrimary">#1296db</color>
<color name="colorPrimaryDark">#1296db</color>
<color name="colorAccent">#03DAC5</color>
<color name="button_selected">#00D9FF</color>
</resources>
dimens.xml
<resources>
<!-- Default screen margins, per the Android Design guidelines. -->
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
</resources>
strings.xml
<resources>
<string name="app_name">Huinongbang</string>
<string name="title_activity_login">LoginActivity</string>
<string name="prompt_email">Email</string>
<string name="prompt_password">Password</string>
<string name="action_sign_in">Sign in or register</string>
<string name="action_sign_in_short">Sign in</string>
<string name="welcome">"Welcome !"</string>
<string name="invalid_username">Not a valid username</string>
<string name="invalid_password">Password must be >5 characters</string>
<string name="login_failed">"Login failed"</string>
<string name="title_activity_bottom">MainActivity</string>
<string name="title_donate">捐赠</string>
<string name="title_shopping">购物</string>
<string name="title_planting">种植</string>
<string name="title_policy">政策</string>
<string name="title_my">我的</string>
</resources>
styles.xml
<resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<!-- 自定义loading dialog -->
<style name="loading_dialog" parent="android:style/Theme.Dialog">
<item name="android:windowFrame">@null</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@drawable/shape_bg_5_white</item>
<item name="android:windowIsFloating">true</item>
<item name="android:windowContentOverlay">@null</item>
</style>
<style name="text_style">
<item name="android:textSize">18sp</item>
<item name="android:paddingLeft">10dp</item>
<item name="android:paddingRight">10dp</item>
<item name="android:layout_gravity">center_vertical</item>
</style>
</resources>
themes.xml
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Huinongbang" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>
network_config.xml(配置网络,用于访问http)
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system" />
</trust-anchors>
</base-config>
</network-security-config>
最重要的AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.hnb.huinongbang">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".HNBApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:networkSecurityConfig="@xml/network_config"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name=".ui.login.LoginActivity"
android:label="@string/title_activity_login">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity" />
</application>
</manifest>
这里有一个大坑,
<uses-permission android:name="android.permission.INTERNET" />
这局代码是申请网络权限的,如果是已经安装软件再写的申请网络权限,一定要把huinongbang软件卸载了,再安装。不然会出现,无法访问网络。
到这里登录功能初步开发完成,开发第一步最麻烦的结构搭建已经完成,后续开发就是在原基础上添加功能。
测试账户:13387998888 密码123456jj
或者自行去www.huinongbang.club注册。