Android开发仿掘金Web端登录界面(Kotlin)

1,603 阅读4分钟

我正在参与掘金创作者训练营第6期, 点击了解活动详情

Android开发仿掘金Web端登录界面(Kotlin)

前言

各位大佬好,给大家分享一下用Android原生实现掘金Web端的登录界面效果,有哪些可以优化希望大佬们可以指正,那我们开始吧

最终效果图

LPDS_GIF_20220905_182520.gif

前期准备

我们需要先把需要的资源给download下来,我用Chrome来进行这一步

  • 开启Chrmoe的调试模式: 按F12开启或者在设置->更多工具->开发工具

1662367049960.jpg

  • 开是网络抓包:网络->图片

1662367135318.jpg

这样我们就看到了所需要的图片资料了,我们另存一下放入我们的项目

代码

配置Gradle

  • 我们来配置ViewBinding。在build.gradle中的android添加如下代码:
viewBinding {
    enabled = true
}
  • 我们需要添加一些依赖
//glide库
implementation 'com.github.bumptech.glide:glide:4.13.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0'

LoginDialog

我们创建一个LoginDialog.kt文件,并且继承与DialogFragment用于展示登录的UI,具体操作如下

  • dailog_login.xml

layout目录下创建dailog_login.xml文件,用于显示登录的布局,具体代码如下:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    //设置这个布局的高度是自适应的
    android:layout_height="wrap_content">

    //CardView来优化布局(可以快速设置圆角、阴影等操作)
    <androidx.cardview.widget.CardView
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        //这里设置88dp是因为最上面的图片高度是96dp,我们这是88dp就可以实现完成重叠效果
        android:layout_marginTop="88dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="@+id/dialog_top_img">

        //为了方便布局在CardView里面添加一个约束布局
        <androidx.constraintlayout.widget.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="16dp">

            <ImageView
                android:id="@+id/imageView3"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                app:layout_constraintBottom_toBottomOf="@+id/textView2"
                app:layout_constraintEnd_toEndOf="@+id/edit_user"
                app:layout_constraintTop_toTopOf="parent"
                app:srcCompat="@drawable/ic_baseline_close_24" />

            <TextView
                android:id="@+id/textView2"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="手机登录"
                android:textColor="@color/black"
                android:textSize="20sp"
                android:textStyle="bold"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <EditText
                android:id="@+id/edit_user"
                android:layout_width="0dp"
                android:layout_height="50dp"
                android:layout_marginStart="16dp"
                android:layout_marginTop="16dp"
                android:layout_marginEnd="16dp"
                android:background="@drawable/bg_edit"
                android:ems="11"
                android:hint="请输入手机号码"
                android:inputType="number"
                android:maxLength="11"
                android:paddingStart="100dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/textView2" />

            <EditText
                android:id="@+id/edit_pwd"
                android:layout_width="0dp"
                android:layout_height="50dp"
                android:layout_marginStart="16dp"
                android:layout_marginTop="16dp"
                android:layout_marginEnd="16dp"
                android:background="@drawable/bg_edit"
                android:ems="11"
                android:hint="请输入密码"
                android:inputType="number"
                android:maxLength="4"
                android:paddingStart="10dp"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toBottomOf="@+id/edit_user" />

            <TextView
                android:id="@+id/tv_code"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginEnd="16dp"
                android:text="获取验证码"
                android:textColor="#007fff"
                android:textSize="16sp"
                app:layout_constraintBottom_toBottomOf="@+id/edit_pwd"
                app:layout_constraintEnd_toEndOf="@+id/edit_pwd"
                app:layout_constraintTop_toTopOf="@+id/edit_pwd" />

            <LinearLayout
                android:id="@+id/linearLayout"
                android:layout_width="80dp"
                android:layout_height="0dp"
                android:orientation="horizontal"
                app:layout_constraintBottom_toBottomOf="@+id/edit_user"
                app:layout_constraintStart_toStartOf="@+id/edit_user"
                app:layout_constraintTop_toTopOf="@+id/edit_user">

                <TextView
                    android:id="@+id/textView5"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:gravity="center"
                    android:text="+86"
                    android:textColor="#000000" />

                <ImageView
                    android:id="@+id/imageView2"
                    android:layout_width="wrap_content"
                    android:layout_height="match_parent"
                    android:layout_weight="1"
                    android:scaleType="center"
                    app:srcCompat="@drawable/ic_down" />
            </LinearLayout>

            <TextView
                android:id="@+id/btn_login"
                android:layout_width="0dp"
                android:layout_height="50dp"
                android:layout_marginTop="16dp"
                android:background="@drawable/bg_btn"
                android:gravity="center"
                android:text="登录"
                android:textColor="@color/white"
                android:textSize="16sp"
                app:layout_constraintEnd_toEndOf="@+id/edit_pwd"
                app:layout_constraintStart_toStartOf="@+id/edit_pwd"
                app:layout_constraintTop_toBottomOf="@+id/edit_pwd" />

            <TextView
                android:id="@+id/textView6"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="其他登录方式"
                android:textColor="#007fff"
                android:textSize="16sp"
                app:layout_constraintStart_toStartOf="@+id/btn_login"
                app:layout_constraintTop_toBottomOf="@+id/btn_login" />

            <TextView
                android:id="@+id/textView7"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginTop="16dp"
                android:text="登录即表示同意"
                android:textSize="16sp"
                app:layout_constraintStart_toStartOf="@+id/btn_login"
                app:layout_constraintTop_toBottomOf="@+id/textView6" />

            <TextView
                android:id="@+id/textView8"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="4dp"
                android:text="用户协议"
                android:textColor="#007fff"
                android:textSize="16sp"
                app:layout_constraintStart_toEndOf="@+id/textView7"
                app:layout_constraintTop_toTopOf="@+id/textView7" />

            <TextView
                android:id="@+id/textView10"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:layout_marginStart="2dp"
                android:text="、"
                android:textSize="16sp"
                app:layout_constraintStart_toEndOf="@+id/textView8"
                app:layout_constraintTop_toTopOf="@+id/textView8" />

            <TextView
                android:id="@+id/textView9"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="隐私政策"
                android:textColor="#007fff"
                android:textSize="16sp"
                app:layout_constraintStart_toEndOf="@+id/textView10"
                app:layout_constraintTop_toTopOf="@+id/textView7" />

        </androidx.constraintlayout.widget.ConstraintLayout>
    </androidx.cardview.widget.CardView>

    <ImageView
        android:id="@+id/dialog_top_img"
        android:layout_width="142dp"
        android:layout_height="96dp"
        android:elevation="2dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/ic_login_2" />

</androidx.constraintlayout.widget.ConstraintLayout>
  • bg_edit.xml

drawable目录下创建bg_edit.xml的资源文件,设置EditText的样式,代码如下:

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
    //不对焦的样式
    <item android:state_window_focused="false"
        android:drawable="@drawable/bg_edit_nofocused"/>
     //对焦的样式
    <item android:state_focused="true"
        android:drawable="@drawable/bg_edit_focused" />
</selector>
  • bg_edit_nofocused
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <corners android:radius="2px"/>
    <solid android:color="@color/white"/>
    <stroke android:color="#e4e6eb" android:width="1dp"/>
</shape>
  • bg_edit_focused
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    <solid android:color="@color/white"/>
    <stroke android:color="#007fff" android:width="1dp"/>
</shape>
  • bg_btn.xml

drawable目录下创建bg_btn.xml的资源文件,设置TextView的样式,不用Button是因为设置background较为麻烦,代码如下

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">

    <solid android:color="#007fff"/>
    <corners android:radius="2px"/>
</shape>
  • LoginDialo

LoginDialog.kt中代码具体如下:

class LoginDialog : DialogFragment() {
    //使用viewBinding
    lateinit var mBinding: DialogLoginBinding

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        //创建布局
        mBinding = DialogLoginBinding.inflate(layoutInflater)
        return mBinding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //初始化Dialog的相关配置
        initDialog()
    }

    /**
     * 初始化dialog相关配置
     *
     */
    private fun initDialog() {
        //设置Dialog的显示大小
        setDialogSize()

        //设置window的背景为透明色
        dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
        
        //设置点击空白和返回键不消失
        dialog?.setCanceledOnTouchOutside(false)
        
        //设置dialog的动画
        dialog?.window?.setWindowAnimations(R.style.dialog_base_anim)
    }

    /**
     * 设置dialog的大小
     *
     */
    private fun setDialogSize(){
        val window = dialog?.window
        window?.let {

            //获取屏幕信息
            val wm = requireContext().getSystemService(Context.WINDOW_SERVICE) as? WindowManager
            val display = wm?.defaultDisplay
            val point = Point();
            display?.getSize(point);


            val layoutParams = it.attributes;

            //设置宽度为屏幕的百分之90
            layoutParams.width =  (point.x * 0.9).toInt()
            //设置高度为自适应
            layoutParams.height = WindowManager.LayoutParams.WRAP_CONTENT
            it.attributes = layoutParams
        }
    }
}

MainActivity

  1. 我们修改一下MainActivity,实现展示一个登录Button,点击后弹出登录界面,具体代码如下:
class MainActivity : AppCompatActivity() {

    lateinit var mBinding: ActivityMainBinding
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        mBinding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(mBinding.root)
        
        mBinding.button.setOnClickListener { 
            LoginDialog().show(supportFragmentManager, "")
        }
    }
}

运行一下

这个时候我们的UI大致就完成了,我们运行看一下,是不是我们所有期望的那样

Screenshot_20220905_171401_com.juejin.login.jpg

登录逻辑

我们完成了UI相关的功能,接下来我们需要开始写,登录相关的逻辑了

  • 点击不同输入框显示不同UI

在web端中,当我们点击输入手机号和请输入密码时,最上面的UI是显示不同,我们先把这个一部分功能实现以下:

我们添加一个initView()方法,专门初始化View相关操作,具体代码如下:

private fun initView() {
    //设置焦点变化监听
    mBinding.editUser.onFocusChangeListener =
        View.OnFocusChangeListener { v, hasFocus ->
            //该控件获取了焦点
            if(hasFocus){
                //设置获取焦点后的UI
                Glide.with(this).load(R.drawable.ic_login_2).into(mBinding.dialogTopImg)
            }
        }

    mBinding.editPwd.onFocusChangeListener =
        View.OnFocusChangeListener { v, hasFocus ->
            //该控件获取了焦点
            if(hasFocus){
                //设置获取焦点后的UI
                Glide.with(this).load(R.drawable.ic_login_1).into(mBinding.dialogTopImg)
            }
        }

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        //让输入框获取焦点
        mBinding.editUser.requestFocus()
    }
}

获取验证码

我们知道点击获取验证码会出现一个验证是否为人为操作,当操作完成后发送验证码,并且会有一个60s间隔,并且需要显示出实际秒数,我们使用Captcha库来完成验证,使用CountDownTimer来实现倒计时的效果

添加验证拼图

  • 添加倒计时
val timeDown = object : CountDownTimer(60 * 1000, 1000) {
    override fun onTick(millisUntilFinished: Long) {
        mBinding.tvCode.text = "${millisUntilFinished / 1000}s"
    }

    override fun onFinish() {
        //设置验证码可点击
        mBinding.tvCode.isEnabled = true
        //恢复text
        mBinding.tvCode.text = "获取验证码"
    }

}
  • 添加依赖
implementation 'com.luozm.captcha:captcha:1.1.2'
  • 添加布局
<com.luozm.captcha.Captcha
    android:id="@+id/capt_cha"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:elevation="2dp"
    android:visibility="gone"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintEnd_toEndOf="@+id/cardView"
    app:layout_constraintStart_toStartOf="@+id/cardView"
    app:layout_constraintTop_toTopOf="@+id/cardView"
    //随便找一个图片就行了
    app:src="@drawable/ic_captcha" />
  • 添加事件监听
mBinding.captCha.setCaptchaListener(object : Captcha.CaptchaListener {
    /**
     * 验证通过回调
     *
     * @param time
     * @return
     */
    override fun onAccess(time: Long): String {
        //设置验证码不可点击
        mBinding.tvCode.isEnabled = false
        //开始倒计时
        timeDown.start()
        //关闭图片验证
        mBinding.captCha.visibility = View.GONE
        return "验证通过,耗时" + time + "毫秒";
    }

    /**
     * 验证失败回调
     *
     * @param failCount
     * @return
     */
    override fun onFailed(failCount: Int): String {
        return "验证失败,已失败" + failCount + "次";
    }

    override fun onMaxFailed(): String {
        Toast.makeText(
            this@LoginDialog.requireContext(),
            "验证超过次数,你的帐号被封锁",
            Toast.LENGTH_SHORT
        ).show();
        return "验证失败,帐号已封锁";
    }

})
  • 点击发送验证码
mBinding.tvCode.setOnClickListener {
    //显示图片验证
    mBinding.captCha.visibility = View.VISIBLE
}

登录

  • 添加登录判断逻辑

我们还是在initView方法中添加代码:

mBinding.btnLogin.setOnClickListener {
    //登录按钮不可交互
    mBinding.btnLogin.isEnabled =false

    //修改UI
    mBinding.btnLogin.text = "登录中..."

    //开始验证
    //判断手机号格式是否正确,这里只做了长度的按断,其实可以用正则来判断,我这里知识简单判断
    if(mBinding.editUser.text.toString().length < 11){
        Toast.makeText(
            this@LoginDialog.requireContext(),
            "账号格式错误",
            Toast.LENGTH_SHORT
        ).show();
        //登录按钮可交互
        mBinding.btnLogin.isEnabled =true
        //修改UI
        mBinding.btnLogin.text = "登录"
        return@setOnClickListener
    }
    if(mBinding.editPwd.text.toString().length < 4){
        Toast.makeText(
            this@LoginDialog.requireContext(),
            "验证码错误",
            Toast.LENGTH_SHORT
        ).show();
        //登录按钮可交互
        mBinding.btnLogin.isEnabled =true
        //修改UI
        mBinding.btnLogin.text = "登录"
        return@setOnClickListener
    }

    Toast.makeText(
        this@LoginDialog.requireContext(),
        "登录成功",
        Toast.LENGTH_SHORT
    ).show();

    dismiss()

}

总结

到这里我们模仿掘金Web端登录就成功了,如果想看源码在这里传送门