这是我参与「第四届青训营 」笔记创作活动的的第13天
前置知识
Constraintlayout:最全面的ConstraintLayout教程 (qq.com)
ImageFilterView:ConstraintLayout官方提供圆角ImageFilterView_沙漠一只雕得儿得儿的博客-CSDN博客_constraintlayout 圆角
Shape 标签:Android-Shape标签使用 - 简书 (jianshu.com)
ImageFilterView :圆角控件之ImageFilterView详解 - 简书 (jianshu.com)
MVP架构:带你封装MVP架构(上)|青训营笔记 - 掘金 (juejin.cn)
你会收获
- 如何编写一个美观的登录界面
- 如何提示用户查看 协议
- 如何载入跳转登录抖音,且回调回对应的页面
前言
本文接前文的 布局篇 ,会讲述java代码层面相关的问题
首先我们分析以下业务逻辑,其逻辑如下
- 进入登录页
- 勾选已阅读协议的按钮才可成功触发登录点击按钮的事件
- 成功触发登录事件后跳转去抖音sdk的授权页
- 抖音授权后带着是否成功的消息返回登录页
- 登录页对返回的消息做判断,授权成功则存储好Token跳转去个人主页,否则留在登录页
确定完对应逻辑后,开工!
编码流程
导入抖音SDK的依赖
要使用抖音的sdk,需要先去 抖音开放平台 (open-douyin.com) 进行申请。申请了之后创建对应的移动应用且审批通过后,即可获得对应的 key 和 secret 。
做完上面的一步后,我们便可导入依赖
//项目级build.gradle
repositories {
maven { url 'https://artifact.bytedance.com/repository/AwemeOpenSDK' }
}
//app/build.gradle
dependencies {
//抖音
implementation 'com.bytedance.ies.ugc.aweme:opensdk-china-external:0.1.9.0'
implementation 'com.bytedance.ies.ugc.aweme:opensdk-common:0.1.9.0'
}
//AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.xxx.xxxx">
<queries>
<!--允许查询抖音和抖音极速版的软件包信息-->
<package android:name="com.ss.android.ugc.aweme" />
<package android:name="com.ss.android.ugc.aweme.lite" />
</queries>
</manifest>
做完上面的,便完成了依赖的导入
Application层初始化
// 抖音授权
Context context = getApplicationContext();
String clientkey = context.getString(R.string.value_client_key);//对应的key需要就是申请得到的
DouYinOpenApiFactory.init(new DouYinOpenConfig(clientkey));
V层编码(Activity层)
我们阅读文档会知道,回调的activity需要继承一个接口,需要重写的方法中就可以获得到授权的对应信息。话不多说,直接上代码。
public class LoginActivity extends BaseActivity<LoginPresenter, ActivityLoginBinding> implements ILoginView, IApiEventHandler {
DouYinOpenApi douYinOpenApi;
private long mExitTime = 0;
private boolean is_clicked_user_agreement = false;
/**
* 初始化presenter,也是与Activity的绑定
*
* @return 返回new的Presenter层的值
*/
@Override
protected LoginPresenter createPresenter() {
return new LoginPresenter(this);
}
/**
* 载入view的一些操作
*/
@Override
protected void initView() {
getBinding().btnLogin.setOnClickListener(v -> {
if (is_clicked_user_agreement) {
sendAuth();
} else {
showAnimator();
}
});
getBinding().checkBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
is_clicked_user_agreement = b;
}
});
getBinding().tvUserAgreement.setOnClickListener(view -> ToastUtil.showToast("待添加用户协议"));
}
/**
* 载入数据操作
*/
@Override
protected void initData() {
douYinOpenApi = DouYinOpenApiFactory.create(this);
douYinOpenApi.handleIntent(getIntent(), this);
}
@Override
public boolean sendAuth() {
Authorization.Request request = new Authorization.Request();
request.scope = "user_info,trial.whitelist"; // 用户授权时必选权限
// 用户授权时可选权限(默认选择)
request.optionalScope1 = "fans.list," +
"following.list," +
"video.list," +
"video.data";
// request.optionalScope0 = mOptionalScope1; // 用户授权时可选权限(默认不选)
request.state = "ww"; // 用于保持请求和回调的状态,授权请求后原样带回给第三方。
request.callerLocalEntry = "com.qxy.potato.module.mine.activity.LoginActivity";
return douYinOpenApi.authorize(request); // 优先使用抖音app进行授权,如果抖音app因版本或者其他原因无法授权,则使用wap页授权
}
private void showAnimator() {
RxToast.info(MyUtil.getString(R.string.read_and_click), 500);
ObjectAnimator animatorLeft = ObjectAnimator.ofFloat(getBinding().checkBox,
"translationX", -20f, 0f, 20f, 0f);
ObjectAnimator animatorRight = ObjectAnimator.ofFloat(getBinding().tvUserAgreement,
"translationX", -20f, 0f, 20f, 0f);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.setDuration(300);
animatorSet.play(animatorLeft).with(animatorRight);
animatorSet.start();
}
/**
* 成功登录的操作
*/
@Override
public void loginSuccess() {
ActivityUtil.startActivity(HomeActivity.class, true);
}
/**
* 登录失败的操作
*
* @param msg
*/
@Override
public void loginFailed(String msg) {
ToastUtil.showToast(msg);
}
/**
* Called when the activity has detected the user's press of the back
* key. The {@link #getOnBackPressedDispatcher() OnBackPressedDispatcher} will be given a
* chance to handle the back button before the default behavior of
* {@link Activity#onBackPressed()} is invoked.
*
* @see #getOnBackPressedDispatcher()
*/
@Override
public void onBackPressed() {
int OVER_TIME = 2000;
if ((System.currentTimeMillis() - mExitTime) > OVER_TIME) {
ToastUtil.showToast(getResources().getString(R.string.double_quit) + getResources().getString(R.string.app_name));
mExitTime = System.currentTimeMillis();
} else {
super.onBackPressed();
ActivityUtil.closeAllActivity();
}
}
@Override
public void onReq(BaseReq baseReq) {
}
@Override
public void onResp(BaseResp baseResp) {
if (baseResp.getType() == CommonConstants.ModeType.SEND_AUTH_RESPONSE) {
Authorization.Response response = (Authorization.Response) baseResp;
if (baseResp.isSuccess()) {
LogUtil.d("onRES");
presenter.getAccessToken(response.authCode);
} else {
ToastUtil.showToast("授权失败");
}
}
}
@Override
public void onErrorIntent(Intent intent) {
}
}
下面我来浅析以下V层的代码
-
首先,我们定义三个全局变量 ,其分别是 抖音登录的相关类的变量、记录返回键点击事件的 int 变量、记录是否点击了阅读协议的勾选
-
然后,
initView
的类中,分别设置了 登录按钮、勾选框以及用户协议的相关点击监听。 -
点击登录按钮后会判断是否勾选了选择框,未勾选的话就会进行动画提示,勾选了的话就会跳转到对应的登录跳转的方法
-
登录跳转的方法中,
callerLocalEntry
是设置了回调的路径,设置的是登录页自己,这样子跳转去授权后又会跳转会当前页面了 -
动画提示的方法中,使用的是属性动画里面的组合动画,可以让提示行的文字左右震动
-
接下来是接口类重写的方法,事实上我们只需要重写
onResp(BaseResp baseResp)
即可。对回调的值进行获取和判断,成功就传入authCode
到presenter
层的获取Token
-
在
onBackPressed()
中,我们设定滑动两次才退出APP,因为此时的登录页已经是 APP 唯一的一个页面了
P层编码(Presenter层)
public class LoginPresenter extends BasePresenter<ILoginView> {
MMKV mmkv = MMKV.defaultMMKV();
public LoginPresenter(ILoginView baseView) {
super(baseView);
}
/**
* 获取到 AccessToken 并存储
*
* @param authCode 授权码
*/
public void getAccessToken(String authCode) {
HashMap<String, String> map = new HashMap<>();
map.put(MyUtil.getString(R.string.client_secret), MyUtil.getString(R.string.value_client_secret));
map.put(MyUtil.getString(R.string.code), authCode);
map.put(MyUtil.getString(R.string.grant_type), MyUtil.getString(R.string.authorization_code));
map.put(MyUtil.getString(R.string.client_key), MyUtil.getString(R.string.value_client_key));
addDisposable(apiServer.PostAccessToken(map),
new BaseObserver<BaseBean<AccessToken>>(baseView, true) {
@Override
public void onError(String msg) {
baseView.loginFailed(msg);
}
@Override
public void onSuccess(BaseBean<AccessToken> o) {
mmkv.encode(GlobalConstant.ACCESS_TOKEN, o.data.getAccess_token());
mmkv.encode(GlobalConstant.REFRESH_TOKEN, o.data.getRefresh_token());
mmkv.encode(GlobalConstant.OPEN_ID, o.data.getOpen_id());
mmkv.encode(GlobalConstant.IS_LOGIN, true);
LogUtil.d(o.data.getAccess_token());
baseView.loginSuccess();
}
});
}
}
关于P层的代码中,发起了网络请求后,若请求成功,则存储到 Token
值以及 open_id
值,然后执行V层的 loginSuccess()
;否则执行 loginFailed(msg)
;
效果展示
做完上边的工作就大功告成了,下边看一下展示效果