仿写一个登录页(Java代码层)|青训营笔记

简介: 本文接前文的 布局篇 ,会讲述java代码层面相关的问题

前置知识

Constraintlayout:最全面的ConstraintLayout教程 (qq.com)

ImageFilterViewConstraintLayout官方提供圆角ImageFilterView

Shape 标签Android-Shape标签使用 - 简书 (jianshu.com)

ImageFilterView圆角控件之ImageFilterView详解 - 简书 (jianshu.com)

MVP架构带你封装MVP架构(上)|青训营笔记

你会收获

  1. 如何编写一个美观的登录界面
  2. 如何提示用户查看 协议
  3. 如何载入跳转登录抖音,且回调回对应的页面

前言

本文接前文的 布局篇 ,会讲述java代码层面相关的问题

首先我们分析以下业务逻辑,其逻辑如下

  1. 进入登录页
  2. 勾选已阅读协议的按钮才可成功触发登录点击按钮的事件
  3. 成功触发登录事件后跳转去抖音sdk的授权页
  4. 抖音授权后带着是否成功的消息返回登录页
  5. 登录页对返回的消息做判断,授权成功则存储好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层的代码

  1. 首先,我们定义三个全局变量 ,其分别是 抖音登录的相关类的变量、记录返回键点击事件的 int 变量、记录是否点击了阅读协议的勾选
  2. 然后,initView 的类中,分别设置了 登录按钮、勾选框以及用户协议的相关点击监听。
  3. 点击登录按钮后会判断是否勾选了选择框,未勾选的话就会进行动画提示,勾选了的话就会跳转到对应的登录跳转的方法
  4. 登录跳转的方法中,callerLocalEntry 是设置了回调的路径,设置的是登录页自己,这样子跳转去授权后又会跳转会当前页面了
  5. 动画提示的方法中,使用的是属性动画里面的组合动画,可以让提示行的文字左右震动
  6. 接下来是接口类重写的方法,事实上我们只需要重写 onResp(BaseResp baseResp) 即可。对回调的值进行获取和判断,成功就传入 authCodepresenter 层的获取 Token
  7. 在  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)

效果展示

做完上边的工作就大功告成了,下边看一下展示效果

1.webp.jpg


相关文章
|
6天前
|
Java 开发工具
【Azure Storage Account】Java Code访问Storage Account File Share的上传和下载代码示例
本文介绍如何使用Java通过azure-storage-file-share SDK实现Azure文件共享的上传下载。包含依赖引入、客户端创建及完整示例代码,助你快速集成Azure File Share功能。
183 5
|
29天前
|
IDE Java 关系型数据库
Java 初学者学习路线(含代码示例)
本教程为Java初学者设计,涵盖基础语法、面向对象、集合、异常处理、文件操作、多线程、JDBC、Servlet及MyBatis等内容,每阶段配核心代码示例,强调动手实践,助你循序渐进掌握Java编程。
222 3
|
1月前
|
安全 Java 应用服务中间件
Spring Boot + Java 21:内存减少 60%,启动速度提高 30% — 零代码
通过调整三个JVM和Spring Boot配置开关,无需重写代码即可显著优化Java应用性能:内存减少60%,启动速度提升30%。适用于所有在JVM上运行API的生产团队,低成本实现高效能。
186 3
|
1月前
|
Java
怎么用Java 代码示例来展示继承的实现
本文通过Java代码示例展示继承机制:Animal为父类,Cat和Dog继承其属性与方法,并实现构造函数调用、方法重写与特有功能扩展,体现代码复用与多态特性。
77 4
|
1月前
|
Java API 开发工具
【Azure Developer】Java代码实现获取Azure 资源的指标数据却报错 "invalid time interval input"
在使用 Java 调用虚拟机 API 获取指标数据时,因本地时区设置非 UTC,导致时间格式解析错误。解决方法是在代码中手动指定时区为 UTC,使用 `ZoneOffset.ofHours(0)` 并结合 `withOffsetSameInstant` 方法进行时区转换,从而避免因时区差异引发的时间格式问题。
162 3
|
16天前
|
Java 数据处理 API
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
为什么你的Java代码应该多用Stream?从循环到声明式的思维转变
191 115
|
16天前
|
安全 Java 编译器
为什么你的Java代码需要泛型?类型安全的艺术
为什么你的Java代码需要泛型?类型安全的艺术
143 98
|
1月前
|
Java
java入门代码示例
本文介绍Java入门基础,包含Hello World、变量类型、条件判断、循环及方法定义等核心语法示例,帮助初学者快速掌握Java编程基本结构与逻辑。
265 0
|
22天前
|
安全 Java 容器
告别繁琐判空:Optional让你的Java代码更优雅
告别繁琐判空:Optional让你的Java代码更优雅
|
2月前
|
人工智能 监控 安全
智慧工地解决方案,java智慧工地程序代码
智慧工地系统融合物联网、AI、大数据等技术,实现对施工现场“人、机、料、法、环”的全面智能监控与管理,提升安全、效率与决策水平。