前置知识
Constraintlayout:最全面的ConstraintLayout教程 (qq.com)
ImageFilterView:ConstraintLayout官方提供圆角ImageFilterView
Shape 标签:Android-Shape标签使用 - 简书 (jianshu.com)
ImageFilterView :圆角控件之ImageFilterView详解 - 简书 (jianshu.com)
MVP架构:带你封装MVP架构(上)|青训营笔记
你会收获
- 如何编写一个美观的登录界面
- 如何提示用户查看 协议
- 如何载入跳转登录抖音,且回调回对应的页面
前言
本文接前文的 布局篇 ,会讲述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)
;
效果展示
做完上边的工作就大功告成了,下边看一下展示效果