当前位置:阳光沙滩 >Android > 查看文章
阿里云优惠码

1.Android开发日常-编写一个登录界面

看完这篇文章你可以学到什么知识呢?

  • 自定义组合控件
  • 封装view,只暴露接口,模块独立,设计思想。
  • 会用到正则表达式
  • 如何禁止EditText在获取到焦点的时候拉起键盘
  • 学会登录中获取验证码的倒计时效果
  • EditText的内容添加与删除

1.1.效果图:

screenshot-1528358631512

1.2.分析

我们为了做得好一点,首先需要分析一下!其实对于UI来说,也是可以封装起来的,比如说,我们这里面涉及到了手机号码的检查

涉及到验证码的检查,涉及到是否同意协议来控制按钮是否可用,这些动作我们都可以隐藏起来。把这些所有看得见的,都当成一个View,这个View具备这些功能。

 

然后适用者暴露接口:1、获取验证码按钮被点击了;2、协议内容被点击了;3、登录按钮被点击了。

除此之外,应该没有其他动作给使用者使用了。

 

问题点:

1、因为这个界面是平板的界面,我们自己写了一个键盘输入,所以当我们的输入框获取到焦点的时候,不弹出键盘出来

2、做一个倒计时的效果

3、获取到焦点的edittext内容输入

 

1.3.实现

我们要实现这个类,要用一个很大的坑来填充这些内容,所以我们要写一个LoginUiView,继承自RelativeLayout。这个时候,需要实现一些方法:

2018-06-07_181432

有没有注意到,前两个构造函数都是this,表示调用自己的构造函数。不同的参数而已。这样子就可以确保不管是哪种方式创建这个类的,都会进入第三个类,也就是同一个入口。

这个类的全部代码如下:

package cn.com.sunofbeaches.loginkeypaddemo;

import android.content.Context;
import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.EditText;
import android.widget.RelativeLayout;
import android.widget.TextView;

import java.lang.reflect.Method;

public class LoginUiView extends RelativeLayout implements LoginKeyboard.KeyPadActionListener {


    //手机号码的规则
    public static final String REGEX_MOBILE_EXACT = "^((13[0-9])|(14[5,7])|(15[0-3,5-9])|(17[0,3,5-8])|(18[0-9])|166|198|199|(147))\\d{8}$";

    //输入框
    private EditText mPhoneNumInputBox;
    //验证码输入框
    private EditText mVerifyCodeBox;
    //登录键盘
    private LoginKeyboard mInputKeypad;
    //服务条款的文字
    private TextView mItemTextView;
    //同意协议的选择框
    private CheckBox mAgreeCheckBox;
    //确定按钮
    private TextView mCommitBtn;
    private ViewActionCallback mViewActionCallback = null;
    private TextView mGetVerifyCodeBtn;

    private boolean isVerifyCodeOk = false;
    private boolean isPhoneNumberOk = false;
    //默认已经选中的
    private boolean isItemAgreementOk = true;

    public LoginUiView(Context context) {
        this(context, null);
    }

    public LoginUiView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LoginUiView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        LayoutInflater.from(context).inflate(R.layout.login_view, this, true);
        initView();
        disableShowInput();
        initListener();
    }

    /**
     * 设置监听
     */
    private void initListener() {
        mInputKeypad.setKeyPadActionListener(this);
        mItemTextView.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                //这个是跳转到协议部分
                mViewActionCallback.onItemTipsClick();
            }
        });

        /**
         * 选择框的点击
         */
        mAgreeCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
                //如果是没有选中,确定按钮
                mCommitBtn.setEnabled(b);
                isItemAgreementOk = b;
                updateCommitBtnState();
            }
        });

        /**
         * 提交数据,登录
         */
        mCommitBtn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                if (mViewActionCallback != null) {
                    String phoneNum = mPhoneNumInputBox.getText().toString().trim();
                    String verifyCode = mVerifyCodeBox.getText().toString().trim();
                    mViewActionCallback.onCommitClick(phoneNum, verifyCode);
                }
            }
        });

        mPhoneNumInputBox.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                //当内容改变的时候,如果手机号码不正确,获取验证码不能点击
                //对数据进行检查
                String phoneNum = mPhoneNumInputBox.getText().toString().trim();
                boolean isMatch = phoneNum.matches(REGEX_MOBILE_EXACT);
                mGetVerifyCodeBtn.setEnabled(isMatch);
                isPhoneNumberOk = isMatch;
                updateCommitBtnState();
            }

            @Override
            public void afterTextChanged(Editable editable) {

            }
        });


        /**
         * 设置验证码输入框的内容变化监听
         */
        mVerifyCodeBox.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

            }

            @Override
            public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
                //当这个输入以后,去检查登录按钮是否可以点击
                if (mVerifyCodeBox.getText().toString().length() >= 4) {
                    isVerifyCodeOk = true;
                } else {
                    isVerifyCodeOk = false;
                }
                updateCommitBtnState();
            }

            @Override
            public void afterTextChanged(Editable editable) {

            }
        });
        /**
         * 获取验证码
         */
        mGetVerifyCodeBtn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                //点击以后,直接不能点,然后开始倒计时,倒计时完成以后
                // 要判断手机号码是否正确,如果不正确,则不能点击
                mGetVerifyCodeBtn.setEnabled(false);
                mSecond = 60;
                mVerifyCodeBox.requestFocus();
                startVerifyCodeBtnTextUpdate();
                if (mViewActionCallback != null) {
                    mViewActionCallback.onGetVerifyCodeClick();
                }
            }
        });
    }

    /**
     * 这个方法处理获取验证码的倒计时。
     */
    private int mSecond;

    private void startVerifyCodeBtnTextUpdate() {
        post(new Runnable() {
            @Override
            public void run() {
                if (mSecond >= 0) {
                    mGetVerifyCodeBtn.setText(mSecond + "S");
                    mSecond--;
                    postDelayed(this, 1000);
                } else {
                    String phoneNumber = mPhoneNumInputBox.getText().toString().trim();
                    boolean matches = phoneNumber.matches(REGEX_MOBILE_EXACT);
                    mGetVerifyCodeBtn.setText(R.string.get_verify_code_text);
                    mGetVerifyCodeBtn.setEnabled(matches);
                    mSecond = 60;
                }
            }
        });
    }

    private void updateCommitBtnState() {
        //满足三个条件,这个按钮才可以适用
        //1、手机号码匹配
        //2、验证码匹配
        //3、协议已经勾选同意
        mCommitBtn.setEnabled(isPhoneNumberOk && isVerifyCodeOk && isItemAgreementOk);
    }

    private void initView() {
        mAgreeCheckBox = (CheckBox) this.findViewById(R.id.agree_check_box);
        isItemAgreementOk = mAgreeCheckBox.isChecked();
        mPhoneNumInputBox = (EditText) this.findViewById(R.id.phone_number_input_box);
        mVerifyCodeBox = (EditText) this.findViewById(R.id.verify_code_input_box);
        mInputKeypad = (LoginKeyboard) this.findViewById(R.id.input_keypad_view);
        mItemTextView = (TextView) this.findViewById(R.id.use_tips_text);
        mCommitBtn = (TextView) this.findViewById(R.id.commit_button);
        mGetVerifyCodeBtn = (TextView) this.findViewById(R.id.get_verify_code_btn);
    }


    private void disableShowInput() {
        if (android.os.Build.VERSION.SDK_INT <= 10) {
            mPhoneNumInputBox.setInputType(InputType.TYPE_NULL);
            mVerifyCodeBox.setInputType(InputType.TYPE_NULL);
        } else {
            Class<EditText> cls = EditText.class;
            Method method;
            try {
                method = cls.getMethod("setShowSoftInputOnFocus", boolean.class);
                method.setAccessible(true);
                method.invoke(mPhoneNumInputBox, false);
                method.invoke(mVerifyCodeBox, false);
            } catch (Exception e) {
                e.printStackTrace();
            }
            try {
                method = cls.getMethod("setSoftInputShownOnFocus", boolean.class);
                method.setAccessible(true);
                method.invoke(mPhoneNumInputBox, false);
                method.invoke(mVerifyCodeBox, false);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }


    @Override
    public void onNumberClick(View view, String number) {
        //数字键盘被点击了
        if (getFocusEditTextView() != null) {
            int index = getFocusEditTextView().getSelectionStart();
            Editable editable = getFocusEditTextView().getText();
            editable.insert(index, number);
        }
    }


    private EditText getFocusEditTextView() {
        View focusView = findFocus();
        if (focusView instanceof EditText) {
            return (EditText) focusView;
        }
        return null;
    }

    @Override
    public void onDelClick(View view) {
        deleteContent();
    }

    /**
     * 删除内容..
     */
    private void deleteContent() {
        if (getFocusEditTextView() != null) {
            int index = getFocusEditTextView().getSelectionStart();
            if (index > 0) {
                Editable editable = getFocusEditTextView().getText();
                editable.delete(index - 1, index);
            }
        }
    }

    @Override
    public boolean onDelLongPress(View view) {
        //循环删除
        post(new Runnable() {
            @Override
            public void run() {
                deleteContent();
                if (getFocusEditTextView() != null && getFocusEditTextView().getSelectionStart() > 0) {
                    postDelayed(this, 50);
                }
            }
        });
        return true;
    }


    public void setViewActionCallback(ViewActionCallback callback) {
        this.mViewActionCallback = callback;
    }

    /**
     * 界面的操作动作
     */
    public interface ViewActionCallback {

        //确定按钮被点击了
        void onCommitClick(String phoneNum, String verifyCode);

        //获取验证码被点击了
        void onGetVerifyCodeClick();

        //打开协议文档被点击了
        void onItemTipsClick();

    }
}

 

 

代码讲解:

一开始,我们把view绑定到这个坑里头,也就是这句代码:

LayoutInflater.from(context).inflate(R.layout.auto_map_login_view, this, true);

inflate,后面两个是重点,this,表示当前这个容器,也就是我们创建的LoginUiView,true表示绑定到这个容器里。所以,这行代码的意思是把这个布局文件里的内容,加载进来,放到这个viewGrop里面,也就是放到LoginUiView里面。

键盘的输入等会再说,接着我们是初始化事件:

我们有什么事件呢?

1、监听两个输入框内容的变化,第一个输入框,只有匹配了手机号码的规则,获取验证码才可以适用,否则是disable,也就是不能点击,除非手机号码是对的;监听第二个框框是用于判断有没有输入验证码,如果没有输入,那么,登录按钮不可以点击。

2、获取验证码这个按钮,点击以后要disable,并且开始倒计时,调用暴露的接口。

3、checkbox这个被点击以后,更新登录按钮是否可以点击,其实登录按钮要满足三个条件才可以点击:手机号码是对的,验证码有输入,同意适用协议。

4、登录按钮不需要做太多的动作,因为前面的动作已经做了相关的检查了。

布局文件的代码如下:

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


    <LinearLayout
        android:id="@+id/login_title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="42px"
        android:orientation="horizontal">

        <ImageView
            android:layout_width="50px"
            android:layout_height="50px"
            android:src="@mipmap/icon_user"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_marginLeft="26px"
            android:gravity="center_vertical"
            android:text="@string/sunofbeach_account_login_text"
            android:textColor="#FD7541"
            android:textSize="32px"/>

    </LinearLayout>


    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_below="@id/login_title"
        android:layout_marginTop="50px"
        android:orientation="horizontal"
        android:paddingLeft="40px">

        <LinearLayout
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical">

            <EditText
                android:id="@+id/phone_number_input_box"
                android:layout_width="430px"
                android:layout_height="60px"
                android:background="@drawable/shape_login_input_bg"
                android:drawableLeft="@mipmap/icon_phone_number"
                android:drawablePadding="14px"
                android:hint="@string/phone_number_input_hint_text"
                android:maxLength="11"
                android:paddingLeft="34px"
                android:textColor="@color/white"
                android:textSize="22px"/>


            <RelativeLayout
                android:layout_width="430px"
                android:layout_height="60px"
                android:layout_marginTop="16px">

                <EditText
                    android:id="@+id/verify_code_input_box"
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    android:background="@drawable/shape_login_input_bg"
                    android:drawableLeft="@mipmap/icon_password"
                    android:drawablePadding="14px"
                    android:hint="@string/please_input_verify_code_text"
                    android:maxLength="8"
                    android:paddingLeft="34px"
                    android:textColor="@color/white"
                    android:textSize="22px"/>

                <TextView
                    android:id="@+id/get_verify_code_btn"
                    android:layout_width="110px"
                    android:layout_height="match_parent"
                    android:layout_alignParentRight="true"
                    android:layout_centerVertical="true"
                    android:layout_marginRight="34px"
                    android:enabled="false"
                    android:gravity="center"
                    android:text="@string/get_verify_code_text"
                    android:textColor="@drawable/selector_text_state_color"
                    android:textSize="22px"/>


                <View
                    android:layout_width="1px"
                    android:layout_height="match_parent"
                    android:layout_marginBottom="10px"
                    android:layout_marginRight="9px"
                    android:layout_marginTop="10dp"
                    android:layout_toLeftOf="@id/get_verify_code_btn"
                    android:background="@drawable/line_bg"/>

            </RelativeLayout>


            <LinearLayout
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:layout_marginTop="44px"
                android:gravity="center_vertical">

                <CheckBox
                    android:id="@+id/agree_check_box"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="22px"
                    android:button="@drawable/selector_checkbox_button"
                    android:checked="true"
                    android:padding="2px"/>

                <TextView
                    android:id="@+id/use_tips_text"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginLeft="10px"
                    android:text="@string/user_tbox_text"
                    android:textColor="#FD7541"
                    android:textSize="20px"/>
            </LinearLayout>

            <TextView
                android:id="@+id/commit_button"
                android:layout_width="match_parent"
                android:layout_height="60px"
                android:layout_marginTop="18px"
                android:background="@drawable/selector_commit_button_bg"
                android:enabled="false"
                android:gravity="center"
                android:text="@string/commit_text"
                android:textColor="@color/white"
                android:textSize="24px"/>
        </LinearLayout>

        <cn.com.sunofbeaches.loginkeypaddemo.LoginKeyboard
            android:id="@+id/input_keypad_view"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="42px"/>

    </LinearLayout>


</RelativeLayout>

效果是这样子的:

screenshot-1528358631512

这里面有一个键盘,它的代码其实是个view来的,跟上面一个,编写一个组合控件。

代码如下:

package cn.com.sunofbeaches.loginkeypaddemo;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

public class LoginKeyboard extends LinearLayout implements View.OnClickListener {

    private ImageView mDelBtn;
    private KeyPadActionListener mKeyPadActionListener = null;

    public LoginKeyboard(Context context) {
        this(context, null);
    }

    public LoginKeyboard(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LoginKeyboard(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        View.inflate(context, R.layout.include_login_keyboard, this);
        initView();
        initListener();
    }

    /**
     * 找到各个控件
     */
    private void initView() {
        this.findViewById(R.id.key_pad_number_1).setOnClickListener(this);
        this.findViewById(R.id.key_pad_number_2).setOnClickListener(this);
        this.findViewById(R.id.key_pad_number_3).setOnClickListener(this);
        this.findViewById(R.id.key_pad_number_4).setOnClickListener(this);
        this.findViewById(R.id.key_pad_number_5).setOnClickListener(this);
        this.findViewById(R.id.key_pad_number_6).setOnClickListener(this);
        this.findViewById(R.id.key_pad_number_7).setOnClickListener(this);
        this.findViewById(R.id.key_pad_number_8).setOnClickListener(this);
        this.findViewById(R.id.key_pad_number_9).setOnClickListener(this);
        this.findViewById(R.id.key_pad_number_0).setOnClickListener(this);
        mDelBtn = (ImageView) this.findViewById(R.id.key_pad_delete);
    }

    private void initListener() {
        mDelBtn.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                //把这个回调出去
                if (mKeyPadActionListener != null) {
                    //处理删除按钮被点击了
                    mKeyPadActionListener.onDelClick(view);
                }
            }
        });

        mDelBtn.setOnLongClickListener(new OnLongClickListener() {
            @Override
            public boolean onLongClick(View view) {
                //这个是删除按钮被长按了
                if (mKeyPadActionListener != null) {
                    //处理删除按钮被点击了
                    return mKeyPadActionListener.onDelLongPress(view);
                }
                return false;
            }
        });
    }

    @Override
    public void onClick(View view) {
        //数字按钮被点击了,把所点击的内容传到外面去
        if (mKeyPadActionListener != null) {
            String number = ((TextView) view).getText().toString();
            mKeyPadActionListener.onNumberClick(view, number);
        }

    }

    /**
     * 设置按钮的点击事件
     *
     * @param listener
     */
    public void setKeyPadActionListener(KeyPadActionListener listener) {
        this.mKeyPadActionListener = listener;
    }


    /**
     * 这个接口用于通知点击事件
     * 包括数字键盘被点击了,删除键盘被点击了
     * 删除按钮被长按了。
     */
    public interface KeyPadActionListener {
        void onNumberClick(View view, String number);

        void onDelClick(View view);

        boolean onDelLongPress(View view);
    }
}

其他的drawable文件:

2018-06-07_200149

从压缩包里下载吧,这里面就不在给大看这些不太重要的文件了。

1.4.使用

使用很简单,直接把登录的View的全路径名称,复制到要适用的地方即可。

2018-06-07_200316

接着,我们在Activity的代码里找到这个View,给它设置相关的接口实现即可。

package cn.com.sunofbeaches.loginkeypaddemo;

import android.app.Activity;
import android.os.Bundle;

public class LoginActivity extends Activity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        LoginUiView loginView = this.findViewById(R.id.login_view);
        loginView.setViewActionCallback(new LoginUiView.ViewActionCallback() {
            @Override
            public void onCommitClick(String phoneNum, String verifyCode) {

            }

            @Override
            public void onGetVerifyCodeClick() {
               
            }

            @Override
            public void onItemTipsClick() {

            }
        });
    }


}

到此代码结束,有问题到网站里发帖子吧!

1.5.测试

测试,手机号码的正确性,这个由测试人员来完成了,不过我们简单地使用一下。如果手机号码正确,才可以点击获取验证码!

taskpopout2

 

源码下载地址:

算了,还是不发了吧!自己懂原理了,自己写去!

 

 

7K
为您推荐
各种观点