这里用一个Activity实现登录界面和输入验证码界面(一个Activity中添加两个Fragment)
Activity
Activity的xml
用帧布局来实现
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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="match_parent"
tools:context=".ui.login.LoginActivity"
android:id="@+id/frame">
</FrameLayout>
Activity
public class LoginActivity extends BaseActivity<LoginView, LoginPresenter> implements LoginView{
public static int TYPE_LOGIN = 0;
public static int TYPE_BIND = 1;
@BindView(R.id.frame)
FrameLayout frame;
private int type;
public static String TAG = "loginFragment";
@Override
protected LoginPresenter initPresenter() {
return new LoginPresenter();
}
@Override
protected int initLayoutId() {
return R.layout.activity_login;
}
// 其他Activity到这个Activity掉这个方法就可以
// context 其他Activity的上下文
// type 上面有参数,TYPE_LOGIN 登录界面,TYPE_BIND 绑定界面,
// 因为后面登录和绑定手机号用同一个界面,所以用type区分一下
public static void startAct(Context context,int type){
Intent intent = new Intent(context, LoginActivity.class);
intent.putExtra(Constants.TYPE,type);
context.startActivity(intent);
}
@Override
protected void initView() {
super.initView();
// 一开始就显示登录界面
addLoginFragment();
}
private void addLoginFragment() {
FragmentManager manager = getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
getIntentData();
LoginFragment loginFragment=LoginFragment.newInstance(type);
// 给LoginFragment添加标记,后面在输入验证界
// 面需要通过标记来获取LoginFragment的对象
transaction.add(R.id.frame, loginFragment,TAG);
transaction.addToBackStack(null);
transaction.commit();
}
private void getIntentData() {
type = getIntent().getIntExtra(Constants.TYPE, TYPE_LOGIN);
}
// 友盟第三方登录的内容
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
UMShareAPI.get(this).onActivityResult(requestCode, resultCode, data);
}
private long exitTime = 0;
// 手机返回键的监听
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN){
if((System.currentTimeMillis()-exitTime) > 2000){
ToastUtil.showShort("再按一次退出程序");
exitTime = System.currentTimeMillis();
} else {
finish();
System.exit(0);
}
return true;
}
return super.onKeyDown(keyCode, event);
}
// 友盟第三方登录的内容
@Override
protected void onDestroy() {
super.onDestroy();
//内存泄漏解决方案
UMShareAPI.get(this).release();
}
}
登录.gif
LoginFragment
主要是从onViewClicked()方法开始的
public class LoginFragment extends BaseFragment<LoginFraView, LoginFraPresenter> implements LoginFraView {
private static final String TAG = "LoginFragment";
@BindView(R.id.phone_number)
EditText phoneNumber;
@BindView(R.id.send_code)
Button sendCode;
@BindView(R.id.wechat)
ImageButton wechat;
@BindView(R.id.qq)
ImageButton qq;
@BindView(R.id.sina)
ImageButton sina;
@BindView(R.id.user_agreement)
TextView userAgreement;
@BindView(R.id.back)
ImageView back;
@BindView(R.id.ll)
LinearLayout ll;
@BindView(R.id.ll_three)
LinearLayout llThree;
private EditText phone_number;
private int type;
private static int TIME = 20;
private int time=TIME;
String mCode;
private Handler mHandler;
private VerifyCodeNewFragment verifyCodeNewFragment;
public LoginFragment() {
// Required empty public constructor
}
// 其他界面通过这个方法可以获取到LoginFragment 的对象
public static LoginFragment newInstance(int type) {
LoginFragment loginFragment = new LoginFragment();
Bundle bundle = new Bundle();
bundle.putInt(Constants.TYPE, type);
loginFragment.setArguments(bundle);
return loginFragment;
}
@Override
protected LoginFraPresenter initPresenter() {
return new LoginFraPresenter();
}
@Override
protected int initLayoutId() {
return R.layout.fragment_login;
}
@Override
protected void initView() {
super.initView();
// 设置 EditText 中 hint 的字体大小
SpannableString spannableString = new SpannableString("请输入手机号");
AbsoluteSizeSpan sizeSpan = new AbsoluteSizeSpan(16, true);
spannableString.setSpan(sizeSpan, 0, spannableString.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
phoneNumber.setHint(spannableString);
getArgumentsData();
setAgreement();
// 登录和绑定手机号用同一个fragmnet
// 所以显示或隐藏一些view
showOrHideView();
}
private void showOrHideView() {
if (type== LoginActivity.TYPE_LOGIN){
// 登录
back.setVisibility(View.INVISIBLE);
ll.setVisibility(View.VISIBLE);
llThree.setVisibility(View.VISIBLE);
}else {
// 绑定
back.setVisibility(View.VISIBLE);
ll.setVisibility(View.INVISIBLE);
llThree.setVisibility(View.INVISIBLE);
}
}
private void getArgumentsData() {
Bundle arguments = getArguments();
type = arguments.getInt(Constants.TYPE);
}
@Override
protected void initListener() {
super.initListener();
// 监听EditText中的内容,符合手机号就改变“发送验证码”的背景图片
phoneNumber.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
String string = s.toString();
if (string.matches("[1][34578][0-9]{9}")) {
sendCode.setBackgroundResource(R.drawable.shape_send_code_bright);
} else {
sendCode.setBackgroundResource(R.drawable.shape_send_code_dark);
}
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
// 给布局中的“用户协议”设置前景色和下划线
private void setAgreement() {
SpannableStringBuilder spannableStringBuilder = new SpannableStringBuilder(getResources().getString(R.string.useragreement));
//点击事件
ClickableSpan clickableSpan = new ClickableSpan() {
@Override
public void onClick(View widget) {
//跳转页面,webview展示协议
//webView有很多坑,所以我们不直接用webView
AgreementActivity.startAct(getActivity(),"https://api.banmi.com/app2017/agreement.html","");
}
};
spannableStringBuilder.setSpan(clickableSpan, 11, 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
//下划线
UnderlineSpan underlineSpan = new UnderlineSpan();
spannableStringBuilder.setSpan(underlineSpan, 11, 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
//前景色
ForegroundColorSpan foregroundColorSpan = new ForegroundColorSpan(getResources().getColor(R.color.c_fa6a13));
spannableStringBuilder.setSpan(foregroundColorSpan, 11, 15, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
//需要设置这个ClickableSpan才会有效果
userAgreement.setMovementMethod(LinkMovementMethod.getInstance());
userAgreement.setText(spannableStringBuilder);
}
@OnClick({R.id.phone_number, R.id.send_code, R.id.wechat, R.id.qq, R.id.sina})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.send_code:
// 获取验证码
getVerifyCode();
// 跳到输入验证码界面
submit();
break;
case R.id.wechat:
basePresenter.login(SHARE_MEDIA.WEIXIN);
showLoading();
break;
case R.id.qq:
basePresenter.login(SHARE_MEDIA.QQ);
break;
case R.id.sina:
basePresenter.login(SHARE_MEDIA.SINA);
break;
}
}
private void getVerifyCode() {
// 如果倒计时还在进行中就不要请求验证码了
// 如果不是,让存放验证码的mCode="",然后请求验证码
// 成功后,把验证码带到输入验证码界面(下面的submit()方法)
if (time>0&&time<TIME){
return;
}
mCode="";
basePresenter.getVerifyCode();
}
private void submit() {
String phonenumber = phoneNumber.getText().toString().trim();
if (TextUtils.isEmpty(phonenumber)) {
ToastUtil.showShort("不能为空");
return;
}
if (phonenumber.matches("[1][34578][0-9]{9}")) {
FragmentManager manager = getActivity().getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
// 把验证码带到输入验证码界面
verifyCodeNewFragment = VerifyCodeNewFragment.newInstance(mCode);
// 如果倒计时还没结束,就不重新倒计时,否则,重新开始倒计时
if (time>0&&time<TIME){
}else {
// 开始倒计时
startCountDown();
}
transaction.add(R.id.frame, verifyCodeNewFragment);
transaction.addToBackStack(null);
transaction.commit();
// 关闭软键盘 (Tools工具类在最后有)
Tools.closeKeyBoard(getActivity());
} else {
ToastUtil.showShort("手机号不正确");
}
}
public void startCountDown() {
if (mHandler==null){
mHandler = new Handler();
}
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
// 当倒计时成负数时停止倒计时并把时间初始化
if (time<=0){
time=TIME;
return;
}
time--;
if (verifyCodeNewFragment!=null){
// 把倒计时带到输入验证码界面并显示
verifyCodeNewFragment.setCountDownTime(time);
}
// 通过递归重复运行
startCountDown();
}
},1000);
}
@Override
public Activity getAct() {
return getActivity();
}
@Override
public void toastShort(String string) {
}
// 登录成功回到主页面(这个方法在v层的接口中,
// p层进行三方登录,登录成功之后通过v层接口通知view进入主页面)
@Override
public void goToMainActivity() {
MainActivity.starAct(getActivity());
getActivity().finish();
hideLoading();
}
// 请求验证码接口返回的数据
// 请求成功
@Override
public void onSuccess(VerifyCodeBean verifyCodeBean) {
String code = verifyCodeBean.getData();
mCode=code;
if (verifyCodeNewFragment!=null){
verifyCodeNewFragment.setData(code);
}
}
// 请求失败
@Override
public void onFail(String string) {
}
}
VerifyCodeNewFragment(输入验证码界面)
public class VerifyCodeNewFragment extends BaseFragment<VerifyCodeNewView, VerifyCodeNewPresenter> implements VerifyCodeNewView {
private static final String TAG = "VerifyCodeNewFragment";
@BindView(R.id.back)
ImageButton back;
@BindView(R.id.count_down)
TextView countDown;
// IdentifyingCodeView 自定义view 下面有
@BindView(R.id.icv)
IdentifyingCodeView icv;
@BindView(R.id.tv)
TextView tv;
int mTime;
private String mCode;
public VerifyCodeNewFragment() {
// Required empty public constructor
}
public static VerifyCodeNewFragment newInstance(String code){
VerifyCodeNewFragment verifyCodeNewFragment = new VerifyCodeNewFragment();
Bundle bundle = new Bundle();
bundle.putString(Constants.VERIFY_CODE,code);
verifyCodeNewFragment.setArguments(bundle);
return verifyCodeNewFragment;
}
@Override
protected VerifyCodeNewPresenter initPresenter() {
return new VerifyCodeNewPresenter();
}
@Override
protected int initLayoutId() {
return R.layout.fragment_verify_code_new;
}
@Override
protected void initView() {
super.initView();
Bundle bundle = getArguments();
mCode = bundle.getString(Constants.VERIFY_CODE, "");
setData(mCode);
}
@Override
protected void initData() {
super.initData();
// basePresenter.getVerifyCode();
}
@Override
protected void initListener() {
super.initListener();
icv.setOnEditorActionListener(new IdentifyingCodeView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
return false;
}
@Override
public void onTextChanged(String s) {
// 每输入一个验证码就运行一次
autoLogin();
}
});
}
private void autoLogin() {
// 如果输入的验证码的长度>=4
if (icv.getTextContent().length()>=4){
String textContent = icv.getTextContent();
// 如果输入的验证码和请求回来的验证码一样就登陆
if (textContent.equals(mCode)){
// Toast 自动登录
ToastUtil.showShort(getResources().getString(R.string.login_success));
// 让输入框的背景变成灰色
icv.setBackgroundEnter(false);
tv.setText(BaseApp.getRes().getString(R.string.wait_please));
// 跳到MainActivity并finish当前的Activity
MainActivity.starAct(getActivity());
getActivity().finish();
// 保存用户的头像、用户名、个性签名等信息
SpUtil.setParam(Constants.AVATAR_HD,"");
SpUtil.setParam(Constants.USER_NAME,getString(R.string.tourist));
SpUtil.setParam(Constants.PERSONALIZED_SIGNATURE,"");
SpUtil.setParam(Constants.GENDER,"");
}else {
ToastUtil.showShort(getString(R.string.code_error));
}
}
}
@OnClick({R.id.back, R.id.count_down})
public void onViewClicked(View view) {
switch (view.getId()) {
case R.id.back:
pop();
break;
case R.id.count_down:
// 重新发送按钮
// 如果倒计时结束就重新请求验证码
// 并让LoginFragment 开始倒计时
if (mTime<=0){
// 设置按钮可点击
countDown.setClickable(false);
// 请求验证码
basePresenter.getVerifyCode();
// 开始倒计时
LoginFragment fragment = (LoginFragment) getActivity().getSupportFragmentManager().findFragmentByTag(LoginActivity.TAG);
fragment.startCountDown();
}
break;
}
}
/**
* 碎片手动弹栈
*/
private void pop() {
FragmentManager manager = getActivity().getSupportFragmentManager();
//获取回退栈中碎片的数量
/*int backStackEntryCount = manager.getBackStackEntryCount();
Logger.println("回退栈Fragmnet数量:"+backStackEntryCount);*/
//弹栈
manager.popBackStack();
}
@Override
public void onSuccess(VerifyCodeBean verifyCodeBean) {
String data = verifyCodeBean.getData();
tv.setText(BaseApp.getRes().getString(R.string.verify_code)+data);
}
@Override
public void onFail(String string) {
}
@Override
public void onDestroyView() {
super.onDestroyView();
}
@Override
public void showLoading() {
}
@Override
public void hideLoading() {
}
public void setCountDownTime(int time) {
mTime=time;
if (countDown!=null){
if (time<=0){
countDown.setClickable(true);
countDown.setTextColor(BaseApp.getRes().getColor(R.color.c_fa6a13));
countDown.setText(getResources().getString(R.string.count_down));
}else {
countDown.setText(getResources().getString(R.string.count_down)+"("+time+"s)");
countDown.setTextColor(BaseApp.getRes().getColor(R.color.c_999999));
}
}
}
public void setData(String code) {
if (!TextUtils.isEmpty(code)&&tv!=null){
tv.setText(getResources().getString(R.string.verify_code)+code);
}
}
}
xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.login.fragment.VerifyCodeNewFragment"
android:orientation="vertical"
android:background="@color/c_ffffff">
<!-- TODO: Update blank fragment layout -->
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="@dimen/dp_25"
android:layout_marginLeft="@dimen/dp_20"
android:layout_marginTop="@dimen/dp_35"
android:layout_marginRight="@dimen/dp_15">
<ImageButton
android:id="@+id/back"
android:layout_width="@dimen/dp_25"
android:layout_height="@dimen/dp_25"
android:src="@mipmap/back_white"/>
<TextView
android:id="@+id/count_down"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/count_down"
android:textSize="@dimen/sp_14"
android:textColor="@color/c_999999"
android:layout_alignParentRight="true"
android:layout_centerVertical="true"/>
</RelativeLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="请输入验证码"
android:layout_marginTop="@dimen/dp_44"
android:layout_marginLeft="@dimen/dp_23"
android:textSize="@dimen/sp_27"
android:textColor="@color/c_373737"/>
<com.example.lenovo.everywheretravel.widget.IdentifyingCodeView
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/dp_83"
android:id="@+id/icv"
app:icv_et_number="4"
app:icv_et_width="@dimen/dp_44"
app:icv_et_divider_drawable="@drawable/divider_icv"
app:icv_et_text_color="@color/c_636363"
app:icv_et_text_size="@dimen/sp_20"
app:icv_et_height="@dimen/dp_56"
android:layout_width="wrap_content"
android:layout_height="@dimen/dp_56">
</com.example.lenovo.everywheretravel.widget.IdentifyingCodeView>
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="@dimen/sp_14"
android:textColor="@color/c_fa6a13"
android:layout_gravity="center_horizontal"
android:layout_marginTop="@dimen/dp_33"/>
</LinearLayout>
IdentifyingCodeView (自定义验证码输入框)
/**
* 类名: IdentifyingCodeView
* 创建时间: 2017/10/30 20:07
* 描述: 自定义验证码输入框
*
*/
public class IdentifyingCodeView extends RelativeLayout {
private LinearLayout containerEt;
private EditText et;
// 输入框数量
private int mEtNumber;
// 输入框的宽度
private int mEtWidth;
private int mEtHeight;
//输入框分割线
private Drawable mEtDividerDrawable;
//输入框文字颜色
private int mEtTextColor;
//输入框文字大小
private float mEtTextSize;
//输入框获取焦点时背景
private int mEtBackgroundDrawableFocus;
//输入完成点击完成时的背景
private int mEtBackgroundEnter = R.drawable.shape_icv_et_bg_enter;
// 输入框没有焦点时背景
private int mEtBackgroundDrawableNormal;
//存储TextView的数据 数量由自定义控件的属性传入
private TextView[] mTextViews;
/**
* edittext 软键盘监听
* actionDone 完成 对应 EditorInfo.IME_ACTION_DONE
actionGo 前进 对应 EditorInfo.IME_ACTION_GO
actionNext 下一项 对应 EditorInfo.IME_ACTION_NEXT
actionNone 无动作 对应 EditorInfo.IME_ACTION_NONE
actionPrevious 上一项 对应 EditorInfo.IME_ACTION_PREVIOUS
actionSearch 搜索 对应 EditorInfo.IME_ACTION_SEARCH
actionUnspecified 未指定 对应 EditorInfo.IME_ACTION_UNSPECIFIED
actionSend 发送 对应 EditorInfo.IME_ACTION_SEND
*/
private OnEditorActionListener actionListener;
public IdentifyingCodeView(Context context) {
this(context, null);
}
public IdentifyingCodeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public IdentifyingCodeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs, defStyleAttr);
}
//初始化 布局和属性
private void init(Context context, AttributeSet attrs, int defStyleAttr) {
LayoutInflater.from(context).inflate(R.layout.layout_identifying_code, this);
containerEt = (LinearLayout) this.findViewById(R.id.container_et);
et = (EditText) this.findViewById(R.id.et);
et.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
return actionListener.onEditorAction(v,actionId,event);
}
});
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.IdentifyingCodeView, defStyleAttr, 0);
mEtNumber = typedArray.getInteger(R.styleable.IdentifyingCodeView_icv_et_number, 1);
mEtWidth = typedArray.getDimensionPixelSize(R.styleable.IdentifyingCodeView_icv_et_width, 42);
mEtHeight = typedArray.getDimensionPixelSize(R.styleable.IdentifyingCodeView_icv_et_height, 42);
mEtDividerDrawable = typedArray.getDrawable(R.styleable.IdentifyingCodeView_icv_et_divider_drawable);
mEtTextSize = typedArray.getDimensionPixelSize(R.styleable.IdentifyingCodeView_icv_et_text_size, 16);
mEtTextColor = typedArray.getColor(R.styleable.IdentifyingCodeView_icv_et_text_color, Color.WHITE);
mEtBackgroundDrawableFocus = typedArray.getResourceId(R.styleable.IdentifyingCodeView_icv_et_bg_focus, R.drawable.shape_icv_et_bg_normal);
mEtBackgroundDrawableNormal = typedArray.getResourceId(R.styleable.IdentifyingCodeView_icv_et_bg_normal, R.drawable.shape_icv_et_bg_normal);
//释放资源
typedArray.recycle();
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
initTextViews(getContext(), mEtNumber, mEtWidth,mEtHeight, mEtDividerDrawable, mEtTextSize, mEtTextColor);
initEtContainer(mTextViews);
setListener();
}
//初始化TextView
private void initTextViews(Context context, int etNumber, int etWidth, int etHeight, Drawable etDividerDrawable, float etTextSize, int etTextColor) {
// 设置 editText 的输入长度
et.setCursorVisible(false);//将光标隐藏
et.setFilters(new InputFilter[]{new InputFilter.LengthFilter(etNumber)}); //最大输入长度
// 设置分割线的宽度
if (etDividerDrawable != null) {
etDividerDrawable.setBounds(0, 0, etDividerDrawable.getMinimumWidth(), etDividerDrawable.getMinimumHeight());
containerEt.setDividerDrawable(etDividerDrawable);
}
mTextViews = new TextView[etNumber];
for (int i = 0; i < mTextViews.length; i++) {
TextView textView = new TextView(context);
textView.setTextSize(TypedValue.COMPLEX_UNIT_PX,etTextSize);
textView.setTextColor(etTextColor);
textView.setWidth(etWidth);
textView.setHeight(etHeight);
if (i == 0) {
textView.setBackgroundResource(mEtBackgroundDrawableFocus);
} else {
textView.setBackgroundResource(mEtBackgroundDrawableNormal);
}
textView.setGravity(Gravity.CENTER);
textView.setFocusable(false);
mTextViews[i] = textView;
}
}
//初始化存储TextView 的容器
private void initEtContainer(TextView[] mTextViews) {
for (int i = 0; i < mTextViews.length; i++) {
containerEt.addView(mTextViews[i]);
}
}
private void setListener() {
// 监听输入内容
et.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 (actionListener != null){
actionListener.onTextChanged(charSequence.toString());
}
}
@Override
public void afterTextChanged(Editable editable) {
String inputStr = editable.toString();
if (inputStr != null && !inputStr.equals("")) {
setText(inputStr);
et.setText("");
}
}
});
// 监听删除按键
et.setOnKeyListener(new OnKeyListener() {
@Override
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_DEL && event.getAction() == KeyEvent.ACTION_DOWN) {
onKeyDelete();
return true;
}
return false;
}
});
}
// 给TextView 设置文字
public void setText(String inputContent) {
for (int i = 0; i < mTextViews.length; i++) {
TextView tv = mTextViews[i];
if (tv.getText().toString().trim().equals("")) {
tv.setText(inputContent);
// 添加输入完成的监听
if (inputCompleteListener != null) {
inputCompleteListener.inputComplete();
}
tv.setBackgroundResource(mEtBackgroundDrawableNormal);
if (i < mEtNumber - 1) {
mTextViews[i + 1].setBackgroundResource(mEtBackgroundDrawableFocus);
}
break;
}
}
}
// 监听删除
public void onKeyDelete() {
for (int i = mTextViews.length - 1; i >= 0; i--) {
TextView tv = mTextViews[i];
if (!tv.getText().toString().trim().equals("")) {
tv.setText("");
// 添加删除完成监听
if (inputCompleteListener != null) {
inputCompleteListener.deleteContent();
}
tv.setBackgroundResource(mEtBackgroundDrawableFocus);
if (i < mEtNumber - 1) {
mTextViews[i + 1].setBackgroundResource(mEtBackgroundDrawableNormal);
}
break;
}
}
}
/**
* 获取输入文本
*
* @return
*/
public String getTextContent() {
StringBuffer buffer = new StringBuffer();
for (TextView tv : mTextViews) {
buffer.append(tv.getText().toString().trim());
}
return buffer.toString();
}
/**
* 删除所有内容
*/
public void clearAllText() {
for (int i = 0; i < mTextViews.length; i++) {
if (i == 0) {
mTextViews[i].setBackgroundResource(mEtBackgroundDrawableFocus);
} else {
mTextViews[i].setBackgroundResource(mEtBackgroundDrawableNormal);
}
mTextViews[i].setText("");
}
}
/**
* 获取输入的位数
*
* @return
*/
public int getTextCount() {
return mEtNumber;
}
// 输入完成 和 删除成功 的监听
private InputCompleteListener inputCompleteListener;
public void setInputCompleteListener(InputCompleteListener inputCompleteListener) {
this.inputCompleteListener = inputCompleteListener;
}
public interface InputCompleteListener {
void inputComplete();
void deleteContent();
}
public interface OnEditorActionListener {
boolean onEditorAction(TextView v, int actionId, KeyEvent event);
void onTextChanged(String s);
}
public void setOnEditorActionListener(OnEditorActionListener actionListener) {
this.actionListener = actionListener;
}
public void setEditable(boolean b){
this.et.setFocusable(b);
this.et.setFocusableInTouchMode(b);
}
/**
* 设置不允许输入后的背景
* @param b true,白色背景,false灰色背景
*/
public void setBackgroundEnter(boolean b){
if (!b){
for (int i = 0; i < mTextViews.length; i++) {
mTextViews[i].setBackgroundResource(mEtBackgroundEnter);
}
}else {
boolean flag = false;
for (int i = 0; i < mTextViews.length; i++) {
String trim = mTextViews[i].getText().toString().trim();
if (!TextUtils.isEmpty(trim)){
mTextViews[i].setBackgroundResource(mEtBackgroundDrawableNormal);
}else {
if (!flag){
mTextViews[i].setBackgroundResource(mEtBackgroundDrawableFocus);
flag = true;
}else {
mTextViews[i].setBackgroundResource(mEtBackgroundDrawableNormal);
}
}
}
}
}
}
layout_identifying_code.xml
<?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/container_et"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:showDividers="middle">
</LinearLayout>
<com.example.lenovo.everywheretravel.widget.OwnEditText
android:id="@+id/et"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/transparent"
android:imeOptions="actionDone"
android:singleLine="true"
android:inputType="number"/>
</RelativeLayout>
OwnEditText
public class OwnEditText extends AppCompatEditText {
private OnKeyListener keyListener;
public OwnEditText(Context context) {
super(context);
}
public OwnEditText(Context context, AttributeSet attrs) {
super(context, attrs);
}
public OwnEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
return new MyInputConnection(super.onCreateInputConnection(outAttrs), true);
}
private class MyInputConnection extends InputConnectionWrapper {
/**
* Initializes a wrapper.
* <p>
* <p><b>Caveat:</b> Although the system can accept {@code (InputConnection) null} in some
* places, you cannot emulate such a behavior by non-null {@link InputConnectionWrapper} that
* has {@code null} in {@code target}.</p>
*
* @param target the {@link InputConnection} to be proxied.
* @param mutable set {@code true} to protect this object from being reconfigured to target
* another {@link InputConnection}. Note that this is ignored while the target is {@code null}.
*/
public MyInputConnection(InputConnection target, boolean mutable) {
super(target, mutable);
}
@Override
public boolean sendKeyEvent(KeyEvent event) {
if (keyListener != null) {
keyListener.onKey(OwnEditText.this, event.getKeyCode(), event);
}
return super.sendKeyEvent(event);
}
@Override
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
// 在删除时,输入框无内容,或者删除以后输入框无内容
if (beforeLength == 1 || afterLength == 0 || beforeLength == 0) {
return sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL))
&& sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_DEL));
}
return super.deleteSurroundingText(beforeLength, afterLength);
}
}
// 设置监听回调
public void setSoftKeyListener(OnKeyListener listener) {
keyListener = listener;
}
}
R.drawable.shape_icv_et_bg_normal
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<!-- 大小 -->
<corners android:radius="2dp" />
<stroke
android:width="1dp"
android:color="#C7C7CD" />
</shape>
R.drawable.shape_icv_et_bg_enter
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="2dp" />
<stroke
android:width="1dp"
android:color="#aaa" />
<solid android:color="@color/c_eaeaea"/>
</shape>
在values文件夹下创建attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 自定义验证码输入框-->
<declare-styleable name="IdentifyingCodeView">
<!--输入框的数量-->
<attr name="icv_et_number" format="integer" />
<!--输入框的宽度-->
<attr name="icv_et_width" format="dimension|reference" />
<!--输入框的高度-->
<attr name="icv_et_height" format="dimension|reference" />
<!--输入框之间的分割线-->
<attr name="icv_et_divider_drawable" format="reference" />
<!--输入框文字颜色-->
<attr name="icv_et_text_color" format="color|reference" />
<!--输入框文字大小-->
<attr name="icv_et_text_size" format="dimension|reference" />
<!--输入框获取焦点时边框-->
<attr name="icv_et_bg_focus" format="reference" />
<!--输入框没有焦点时边框-->
<attr name="icv_et_bg_normal" format="reference" />
</declare-styleable>
</resources>
LoginFragment中关闭软件盘用到的工具类Tools
Tools
https://www.jianshu.com/p/c0d7d31d55e2
https://www.jianshu.com/p/ea54614dd660
UIUtils
https://www.jianshu.com/p/1f170351a7c7
再来一个验证码输入框的自定义 View
AuthCodeView
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
import android.support.v7.widget.AppCompatEditText;
import android.text.Editable;
import android.text.InputFilter;
import android.text.TextPaint;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
public class AuthCodeView extends AppCompatEditText implements View.OnKeyListener, TextWatcher {
private String TAG = "AuthCodeView";
private Paint mPaint;
// 一个密码所占的宽度
private int mPasswordItemWidth;
// 密码的个数默认为 4 位数
private int mPasswordNumber = 4;
// 背景边框颜色
private int mBgColor = Color.parseColor("#ffffff");
// 背景边框大小
private int mBgSize = 3;
// 背景边框圆角大小
private int mBgCorner = 0;
// 密码圆点的颜色
private int mPasswordColor = mBgColor;
// 密码圆点的半径大小
private int mPasswordRadius = 4;
// Item 之间的间隔
private int itemDistance = 0;
private int paddingTop = dip2px(16) * -1;
// 是否为密码模式,如果不是密码模式,就明文展示
private boolean isPassWord = false;
private int iTextSize = 14;
// 判断是否已经回调给前面了
private volatile boolean isHaveNotice = false;
// 输入框有内容时的背景
private Bitmap inputBitmap =
BitmapFactory.decodeResource(this.getResources(), R.drawable.bg_num_input);
// 输入框没有内容时的背景
private Bitmap normalBitmap =
BitmapFactory.decodeResource(this.getResources(), R.drawable.bg_num_normal);
public AuthCodeView(Context context) {
this(context, null);
}
public AuthCodeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AuthCodeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// 初始化一些布局必须参数
initAttributeSet(context, attrs);
// 初始化画笔 + 个数计算
init();
// 设置输入模式是密码
if (isPassWord) {
setInputType(EditorInfo.TYPE_TEXT_VARIATION_PASSWORD);
} else {
setInputType(EditorInfo.TYPE_CLASS_NUMBER);
}
// 不显示光标
setCursorVisible(false);
// 获取焦点
this.setFocusable(true);
this.setFocusableInTouchMode(true);
this.requestFocus();
setOnKeyListener(this);
}
private OnInputPasswordEditListener onInputPasswordEditListener;
@Override
public boolean onKey(View view, int keyCode, KeyEvent keyEvent) {
if (keyCode == KeyEvent.KEYCODE_DEL) {
isHaveNotice = false;
}
return false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
mPasswordItemWidth = getMeasuredHeight() - paddingTop;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
super.onSizeChanged(w, h, oldw, oldh);
// 平均下来的距离
itemDistance =
(getWidth() - (mPasswordNumber * mPasswordItemWidth)) / (mPasswordNumber + 1);
}
@Override
protected void onDraw(Canvas canvas) {
// 绘制背景
drawBg(itemDistance, canvas);
drawHidePassword(canvas);
}
public interface OnInputPasswordEditListener {
void onFinishInput();
}
public void setOnInputPasswordEditListener(OnInputPasswordEditListener
onInputPasswordEditListener) {
this.onInputPasswordEditListener = onInputPasswordEditListener;
}
private void initAttributeSet(Context context, AttributeSet attrs) {
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.AuthCodeView);
mBgSize = typedArray.getInteger(R.styleable.AuthCodeView_bgSize, 1);
mPasswordNumber = typedArray.getInteger(R.styleable.AuthCodeView_passwordNumber, 6);
mBgCorner = typedArray.getInteger(R.styleable.AuthCodeView_bgCorner, 5);
mPasswordRadius = typedArray.getInteger(R.styleable.AuthCodeView_passwordRadius, 4);
mPasswordColor = typedArray.getColor(R.styleable.AuthCodeView_passwordColor, mBgColor);
// mBgColor = typedArray.getColor(R.styleable.AuthCodeView_bgColor, mBgColor);
isPassWord = typedArray.getBoolean(R.styleable.AuthCodeView_isPassword, true);
iTextSize = typedArray.getInteger(R.styleable.AuthCodeView_iTextSise, 16);
typedArray.recycle();
mBgSize = dip2px(mBgSize);
mBgCorner = dip2px(mBgCorner);
mPasswordRadius = dip2px(mPasswordRadius);
iTextSize = sp2px(context, iTextSize);
this.setFilters(new InputFilter[]{new InputFilter.LengthFilter(mPasswordNumber)});
}
private void init() {
mPaint = new Paint();
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setColor(mBgColor);
// 设置画笔的字体
mPaint.setTypeface(Typeface.createFromAsset(this.getResources().getAssets(),
"fonts/gilroy_medium.otf"));
}
private void drawBg(int startX, Canvas canvas) {
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(Color.BLACK);
mPaint.setStrokeWidth(3);
int passwordLength = getText().toString().trim().length();
for (int i = 0; i < mPasswordNumber; i++) {
RectF rectF = new RectF(startX, paddingTop / 2,
startX + mPasswordItemWidth, mPasswordItemWidth + paddingTop / 2);
// canvas.drawRoundRect(rectF, mBgCorner, mBgCorner, mPaint);
// canvas.drawLine(rectF.left, rectF.bottom, rectF.right, rectF.bottom, mPaint);
if (i < passwordLength) {
canvas.drawBitmap(inputBitmap,
new Rect(0, 0, inputBitmap.getWidth(), inputBitmap.getHeight()),
rectF, null);
} else {
canvas.drawBitmap(normalBitmap,
new Rect(0, 0, normalBitmap.getWidth(), normalBitmap.getHeight()),
rectF, null);
}
startX = startX + mPasswordItemWidth + itemDistance;
}
}
private void drawHidePassword(Canvas canvas) {
int passwordLength = getText().toString().trim().length();
mPaint.setColor(mPasswordColor);
mPaint.setStyle(Paint.Style.FILL);
if (!isPassWord) {
// 不是密码模式
mPaint.setTextSize(iTextSize);
String text = getText().toString();
// 1 2 3
for (int i = 0; i < passwordLength; i++) {
int cx = itemDistance + i * mPasswordItemWidth + mPasswordItemWidth / 2
+ i * itemDistance;
String aaa = text.substring(i, i + 1);
// 文字处理
TextPaint paint = this.getPaint();
// 计算baseLine
int center = getMeasuredHeight() / 2 + Math.abs(paddingTop / 4);
int baseLine = (int) (center + (paint.getFontMetrics().bottom -
paint.getFontMetrics().top) / 2 - paint
.getFontMetrics().bottom);
int fontWidth = (int) mPaint.measureText(aaa);
canvas.drawText(aaa, cx - fontWidth / 2, baseLine, mPaint);
}
} else {
// 密码处理
for (int i = 0; i < passwordLength; i++) {
int cx = itemDistance + i * mPasswordItemWidth + mPasswordItemWidth / 2
+ i * itemDistance;
canvas.drawCircle(cx, getHeight() / 2, mPasswordRadius, mPaint);
}
}
if (passwordLength == mPasswordNumber && !isHaveNotice) {
if (onInputPasswordEditListener != null) {
onInputPasswordEditListener.onFinishInput();
isHaveNotice = true;
}
}
}
public void cleanData() {
this.setText("");
isHaveNotice = false;
}
private int dip2px(int dip) {
return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
dip, getResources().getDisplayMetrics());
}
public int px2sp(Context context, float pxValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (pxValue / fontScale + 0.5f);
}
public int sp2px(Context context, float spValue) {
final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;
return (int) (spValue * fontScale + 0.5f);
}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
int len = charSequence.length();
// 通过监听文本发生变化来判断当前文本的长度,用来弥补系统自带输入法的软键盘delete键无法监听到的问题。
if (len == mPasswordNumber) {
isHaveNotice = false;
}
}
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {
}
@Override
public void afterTextChanged(Editable editable) {
}
public void releaseBitmap() {
try {
if (inputBitmap != null && !inputBitmap.isRecycled()) {
inputBitmap.recycle();
}
if (normalBitmap != null && !normalBitmap.isRecycled()) {
normalBitmap.recycle();
}
} catch (Throwable t) {
t.printStackTrace();
HLog.d(TAG + " releaseBitmap exception:" + t.getMessage());
}
}
}
使用:
<com.example.demo.widget.AuthCodeView
android:id="@+id/safe_check_authcode"
android:layout_width="@dimen/dp230dp"
android:layout_height="@dimen/dp65dp"
android:layout_centerHorizontal="true"
android:focusable="true"
android:inputType="number"
app:bgColor="#ffffff"
app:iTextSise="23"
app:isPassword="false"
app:passwordColor="@color/white"
app:passwordNumber="4" />
Activity
// 获取到键盘的输入后
authCodeView.setText(phone.toString());
// 在 destroy 时需要注意,要调用 releaseBitmap 方法
@Override
protected void onDestroy() {
super.onDestroy();
if (authCodeView != null) {
authCodeView.releaseBitmap();
}
}
网友评论