开始开发时间2022.1月
基于视频教程开发
视频教程地址:https://www.bilibili.com/video/BV16Z4y1H7jj?spm_id_from=333.999.0.0
视频下方有源码
这个笔记将会解决后端数据问题、解决数据库版本等等问题。如果没有人需要,就不更新了,在csdn写笔记挺麻烦的。
UI界面私人定制
环境搭建
1 工具
1.1 使用Adobe xd设计原型图
1.2 使用PxCook生成设计安卓代码
1.3 PxCook需要基于安装Adobe air
2 创建项目
3 选择模拟器型号
选择苹果尺寸和苹果分辨率
4 下载Api(系统镜像)
颜色配置文件
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="colorPrimary">#6200EE</color>
<color name="colorPrimaryDark">#3700B3</color>
<color name="colorAccent">#03DAC5</color>
<color name="white">#ffffff</color>
<color name="black">#000000</color>
<color name="dividser">#e8e7e7</color>
<color name="skin_text_white">#ffffff</color>
<color name="skin_topbar_bg_color">#344261</color>
</resources>
尺寸配置文件
所有尺寸的配置都在此配置
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="dimen_90dp">90dp</dimen>
<dimen name="dimen_44dp">44dp</dimen>
<dimen name="size_20sp">20sp</dimen>
</resources>
启动页面UI
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@mipmap/splash"
tools:context=".MainActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_marginBottom="@dimen/dimen_90dp"
android:paddingLeft="@dimen/dimen_44dp"
android:paddingRight="@dimen/dimen_44dp">
<Button
android:id="@+id/btn_login"
android:layout_width="100dp"
android:layout_height="40dp"
android:layout_alignParentLeft="true"
android:text="@string/login"
android:textColor="#ffffff"
android:background="@drawable/shape_login_btn"
android:textSize="@dimen/size_20sp" />
<Button
android:id="@+id/btn_register"
android:layout_width="100dp"
android:layout_height="40dp"
android:background="@drawable/shape_register_btn"
android:layout_alignParentRight="true"
android:text="@string/register"
android:textColor="#ffffff"
android:textSize="20sp" />
</RelativeLayout>
</RelativeLayout>
还需:1.编写values相关配置 2.引入相关资源文件,其中背景图存放到mipmap-xxxhdpi文件下
矩形按钮
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- @rectangle:圆角矩形-->
<corners android:radius="9dp" />
<solid android:color="#c75fe768" />
</shape>
无状态条
android:theme="@style/Theme.AppCompat.NoActionBar">
运行图片
登录页面UI
创建activity文件夹,其中空活动:LoginActivity
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
tools:context=".activity.LoginActivity">
<ImageView
android:layout_width="match_parent"
android:layout_height="329dp"
android:scaleType="fitXY"
android:src="@mipmap/login" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="278dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="230dp"
android:layout_marginLeft="18dp"
android:layout_marginRight="18dp"
android:background="@drawable/shape_login_form"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="43dp"
android:paddingRight="31dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:src="@mipmap/account" />
<EditText
android:id="@+id/et_account"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:background="@null"
android:hint="@string/account_hint"
android:textColor="#000000"
android:text="root"
android:textColorHint="#bcbcbc"
android:textSize="18sp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="23dp"
android:layout_marginBottom="23dp"
android:background="#e8e7e7" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:src="@mipmap/pwd" />
<EditText
android:id="@+id/et_pwd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:background="@null"
android:text="123456"
android:inputType="textPassword"
android:hint="@string/pwd_hint"
android:textColor="#000000"
android:textColorHint="#bcbcbc"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
<Button
android:id="@+id/btn_login"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginLeft="18dp"
android:layout_marginTop="67dp"
android:layout_marginRight="18dp"
android:background="@drawable/shape_big_login_btn"
android:text="@string/login"
android:textColor="#ffffff"
android:textSize="24sp" />
</LinearLayout>
</RelativeLayout>
阴影边框
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 阴影边 -->
<item>
<shape android:shape="rectangle">
<padding
android:bottom="2dp"
android:left="1.5dp"
android:right="2dp"
android:top="1.5dp" />
<solid android:color="#F2F2F2" />
<corners android:radius="8dp" />
</shape>
</item>
<!-- 中心白色背景 -->
<item>
<shape
android:shape="rectangle"
android:useLevel="false">
<!-- 实心 -->
<solid android:color="#ffffff" />
<corners android:radius="10dp" />
<padding
android:bottom="10dp"
android:left="10dp"
android:right="10dp"
android:top="10dp" />
</shape>
</item>
</layer-list>
// 原理:两个卡片叠加实现
运行图片
注册页面
在activity文件夹下创建空activity,名字:RegisterActivity
//便捷:复制登录页面,修改即可
布局文件
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#ffffff"
tools:context=".activity.LoginActivity">
<ImageView
android:layout_width="match_parent"
android:layout_height="329dp"
android:scaleType="fitXY"
android:src="@mipmap/login" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="278dp"
android:orientation="vertical">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="230dp"
android:layout_marginLeft="18dp"
android:layout_marginRight="18dp"
android:background="@drawable/shape_login_form"
android:gravity="center"
android:orientation="vertical"
android:paddingLeft="43dp"
android:paddingRight="31dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:src="@mipmap/account" />
<EditText
android:id="@+id/et_account"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:background="@null"
android:hint="@string/account_hint"
android:textColor="#000000"
android:textColorHint="#bcbcbc"
android:textSize="18sp" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="23dp"
android:layout_marginBottom="23dp"
android:background="@color/divider" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal">
<ImageView
android:layout_width="25dp"
android:layout_height="25dp"
android:src="@mipmap/pwd" />
<EditText
android:id="@+id/et_pwd"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:background="@null"
android:inputType="textPassword"
android:hint="@string/pwd_hint"
android:textColor="@color/black"
android:textColorHint="#bcbcbc"
android:textSize="18sp" />
</LinearLayout>
</LinearLayout>
<Button
android:id="@id/btn_register"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginLeft="18dp"
android:layout_marginTop="67dp"
android:layout_marginRight="18dp"
android:background="@drawable/shape_big_register_btn"
android:text="@string/register"
android:textColor="@color/white"
android:textSize="24sp" />
</LinearLayout>
</RelativeLayout>
运行图片
启动页面跳转
package com.ttit.myapp;
import androidx.appcompat.app.AppCompatActivity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.ttit.myapp.activity.BaseActivity;
import com.ttit.myapp.activity.LoginActivity;
import com.ttit.myapp.activity.RegisterActivity;
public class MainActivity extends BaseActivity {
private Button btnLogin;
private Button btnRegister;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//登录按钮
btnLogin = findViewById(R.id.btn_login);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Intent intent = new Intent(MainActivity.this, LoginActivity.class);
// startActivity(intent);
navigateTo(LoginActivity.class);
}
});
//注册按钮
btnRegister = findViewById(R.id.btn_register);
btnRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
// Intent intent = new Intent(MainActivity.this, RegisterActivity.class);
// startActivity(intent);
navigateTo(RegisterActivity.class);
}
});
}
}
跳转的封装方法
//活动跳转
public void navigateTo(Class cls) {
Intent in = new Intent(mContext, cls);
startActivity(in);
}
登录逻辑
易错改正:记得继承类
登录验证
package com.ttit.myapp.activity;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import com.ttit.myapp.R;
import com.ttit.myapp.util.StringUtils;
public class LoginActivity extends BaseActivity {
private Button btnLogin;
private EditText etAccount;
private EditText etPwd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//登录验证
etAccount = findViewById(R.id.et_account);
etPwd = findViewById(R.id.et_pwd);
btnLogin = findViewById(R.id.btn_login);
btnLogin.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String account = etAccount.getText().toString().trim();
String pwd = etPwd.getText().toString().trim();
login(account, pwd);
}
});
}
private void login(String account, String pwd) {
if (StringUtils.isEmpty(account)) {
showToast("请输入账号");
return;
}
if (StringUtils.isEmpty(pwd)) {
showToast("请输入密码");
return;
}
}
}
活动工具类
1.编写活动跳转方法
2.编写提示信息方法
package com.ttit.myapp.activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
public abstract class BaseActivity extends AppCompatActivity {
public Context mContext;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mContext = this;
}
//提示信息
public void showToast(String msg) {
Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
}
//活动跳转
public void navigateTo(Class cls) {
Intent in = new Intent(mContext, cls);
startActivity(in);
}
}
字符串是否为空
创建工具类
package com.ttit.myapp.util;
public class StringUtils {
public static boolean isEmpty(String str) {
if (str == null || str.length() <= 0) {
return true;
} else {
return false;
}
}
}
测试运行
是否跳转成功以及验证是否成功
登录接口
1 导入依赖
// https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '3.10.0'
//网址:https://mvnrepository.com/artifact/com.squareup.okhttp3/okhttp/3.10.0
简写版:
implementation 'com.squareup.okhttp3:okhttp:3.10.0'
//点击synoc now 效果:开始下载依赖
2 打开网络权限
3 使用okhttp
- 请求数据
- 请求类型(请求头设置)
- 请求编码
- 请求地址
- 发送请求
- 请求结果处理
1 请求代码
private void login(String account, String pwd) {
if (StringUtils.isEmpty(account)) {
showToast("请输入账号");
return;
}
if (StringUtils.isEmpty(pwd)) {
showToast("请输入密码");
return;
}
// 第一步创建OKHttpClient
OkHttpClient client = new OkHttpClient.Builder()
.build();
Map m = new HashMap();
m.put("mobile", account);
m.put("password", pwd);
JSONObject jsonObject = new JSONObject(m);
String jsonStr = jsonObject.toString();
RequestBody requestBodyJson =
RequestBody.create(MediaType.parse("application/json;charset=utf-8")
, jsonStr);
//第三步创建Rquest
Request request = new Request.Builder()
.url(AppConfig.BASE_URl + "/app/login")
.addHeader("contentType", "application/json;charset=UTF-8")
.post(requestBodyJson)
.build();
//第四步创建call回调对象
final Call call = client.newCall(request);
//第五步发起请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("onFailure", e.getMessage());
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String result = response.body().string();
//ui必须在主线程处理 //原因:若在子线程处理会报运行时错误
runOnUiThread(new Runnable() {
@Override
public void run() {
showToast(result);
}
});
}
});
}
2 在其中,需要封装请求的基础地址:
package com.ttit.myapp.util;
/**
*@author wuzhideren
*/
public class AppConfig {
public static final String BASE_URl = "http://192.168.31.32:8080/renren-fast";
}
3 需要取消只能发送https的限制:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4MCuz2Y0-1643208228100)(视频资讯app.assets/image-20220117191714496.png)]
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<!--禁用掉明文流量请求的检查-->
<base-config cleartextTrafficPermitted="true" />
</network-security-config>
// 原因:在27版本以上默认只能发送https请求
4 测试运行
注册接口
注册逻辑类
package com.ttit.myapp.activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import com.ttit.myapp.R;
import com.ttit.myapp.api.Api;
import com.ttit.myapp.api.ApiConfig;
import com.ttit.myapp.api.TtitCallback;
import com.ttit.myapp.util.StringUtils;
import java.util.HashMap;
public class RegisterActivity extends BaseActivity {
private Button btnRegister;
private EditText etAccount;
private EditText etPwd;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
//登录验证
etAccount = findViewById(R.id.et_account);
etPwd = findViewById(R.id.et_pwd);
btnRegister = findViewById(R.id.btn_register);
btnRegister.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
String account = etAccount.getText().toString().trim();
String pwd = etPwd.getText().toString().trim();
register(account, pwd);
}
});
}
private void register(String account, String pwd) {
if (StringUtils.isEmpty(account)) {
showToast("请输入账号");
return;
}
if (StringUtils.isEmpty(pwd)) {
showToast("请输入密码");
return;
}
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("mobile", account);
params.put("password", pwd);
Api.config(ApiConfig.REGISTER, params).postRequest(this,new TtitCallback() {
@Override
public void onSuccess(final String res) {
runOnUiThread(new Runnable() {
@Override
public void run() {
showToast(res);
}
});
}
@Override
public void onFailure(Exception e) {
Log.e("onFailure", e.toString());
}
});
}
}
子线程方法
//处理子线程ui
public void showToastSync(String msg) {
//令子线程可以有ui显示,而不报错
Looper.prepare();
Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
Looper.loop();
}
//作用:处理子线程ui
测试运行
本地登录信息
登录实体类
package com.ttit.myapp.entity;
public class LoginResponse {
/**
* msg : success
* code : 0
* expire : 604800
* token : eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiJ9.eyJzdWIiOiI2IiwiaWF0IjoxNTkyNDg2OTQzLCJleHAiOjE1OTMwOTE3NDN9.f5sxyG60GyDlj0FcZEmPAADiLHX_pATrvicxbADqvRqYurYQC5s0KAjw5XgHS4gpk-qUSwWtcJpY_nJjYf_2Dw
*/
private String msg;
private int code;
private int expire;
private String token;
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public int getExpire() {
return expire;
}
public void setExpire(int expire) {
this.expire = expire;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
//扩展-快速生成方法:使用GsonFormat插件,把json格式字符串快速转化实体类。
Gson依赖
implementation 'com.google.code.gson:gson:2.8.5'
api文件夹
TtitCallback
package com.ttit.myapp.api;
public interface TtitCallback {
void onSuccess(String res);
void onFailure(Exception e);
}
ApiConfig
package com.ttit.myapp.api;
public class ApiConfig {
public static final String BASE_URl = "http://192.168.31.32:8080/renren-fast";
public static final String LOGIN = "/app/login"; //登录
public static final String REGISTER = "/app/register";//注册
}
Api
package com.ttit.myapp.api;
import static android.content.Context.MODE_PRIVATE;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import com.ttit.myapp.activity.LoginActivity;
import com.ttit.myapp.util.StringUtils;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import okhttp3.Call;
import okhttp3.Callback;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class Api {
private static OkHttpClient client;
private static String requestUrl;
private static HashMap<String, Object> mParams;
public static Api api = new Api();
public Api() {
}
public static Api config(String url, HashMap<String, Object> params) {
client = new OkHttpClient.Builder()
.build();
requestUrl = ApiConfig.BASE_URl + url;
mParams = params;
return api;
}
public void postRequest(Context context, final TtitCallback callback) {
SharedPreferences sp = context.getSharedPreferences("sp_ttit", MODE_PRIVATE);
String token = sp.getString("token", "");
JSONObject jsonObject = new JSONObject(mParams);
String jsonStr = jsonObject.toString();
RequestBody requestBodyJson =
RequestBody.create(MediaType.parse("application/json;charset=utf-8")
, jsonStr);
//第三步创建Rquest
Request request = new Request.Builder()
.url(requestUrl)
.addHeader("contentType", "application/json;charset=UTF-8")
.addHeader("token", token)
.post(requestBodyJson)
.build();
//第四步创建call回调对象
final Call call = client.newCall(request);
//第五步发起请求
call.enqueue(new Callback() {
@Override
public void onFailure(Call call, IOException e) {
Log.e("onFailure", e.getMessage());
callback.onFailure(e);
}
@Override
public void onResponse(Call call, Response response) throws IOException {
final String result = response.body().string();
callback.onSuccess(result);
}
});
}
}
存储逻辑类
private void login(String account, String pwd) {
if (StringUtils.isEmpty(account)) {
showToast("请输入账号");
return;
}
if (StringUtils.isEmpty(pwd)) {
showToast("请输入密码");
return;
}
HashMap<String, Object> params = new HashMap<String, Object>();
params.put("mobile", account);
params.put("password", pwd);
Api.config(ApiConfig.LOGIN, params).postRequest(this,new TtitCallback() {
@Override
public void onSuccess(final String res) {
Log.e("onSuccess", res);
//json格式字符串转化为类对象
Gson gson = new Gson();
LoginResponse loginResponse = gson.fromJson(res, LoginResponse.class);
if (loginResponse.getCode() == 0) {
String token = loginResponse.getToken();
insertVal("token", token);
showToastSync("登录成功");
} else {
showToastSync("登录失败");
}
}
@Override
public void onFailure(Exception e) {
}
});
}
SP存储方法
//处理子线程ui
public void showToastSync(String msg) {
//令子线程可以有ui显示,而不报错
Looper.prepare();
Toast.makeText(mContext, msg, Toast.LENGTH_SHORT).show();
Looper.loop();//易错修正:其后代码不会立即执行
}