FanChat学习笔记(二)——登录页

前几天总结了欢迎页里面的知识点FanChat学习笔记(一)——MVP模式的应用,今天继续学习第二个Activity,登录页。
在开始温习MVP模式的代码之前,我们先学习一个关于软键盘的技巧:


 1. android:imeOptions 
 2. TextView.OnEditorActionListener

第一个属性指的是软键盘的文本和图片的设定,github是这样介绍的:

注意配置EditText的imeOptions属性时,需要配合inputType才能起作用。

android:imeOptions="actionNext"//下一个
android:imeOptions="actionGo"//启动
android:imeOptions="actionDone"//完成
android:imeOptions="actionPrevious"//上一个
android:imeOptions="actionSearch"//搜索
android:imeOptions="actionSend"//发送

如果上面的说明还不够直观,那么看看下面的图片和说明:

actionUnspecified 未指定,对应常量EditorInfo.IME_ACTION_UNSPECIFIED.效果:这里写图片描述
actionNone 没有动作,对应常量EditorInfo.IME_ACTION_NONE 效果:这里写图片描述
actionGo 去往,对应常量EditorInfo.IME_ACTION_GO 效果:这里写图片描述
actionSearch 搜索,对应常量EditorInfo.IME_ACTION_SEARCH 效果: 这里写图片描述
actionSend 发送,对应常量EditorInfo.IME_ACTION_SEND 效果:这里写图片描述
actionNext 下一个,对应常量EditorInfo.IME_ACTION_NEXT 效果:这里写图片描述
actionDone 完成,对应常量EditorInfo.IME_ACTION_DONE 效果:
这里写图片描述

当然,不止xml文件可以设置,java代码也可以设置,这里设置一个很少用的常量:

EditText edit = new EditText(this);
        edit.setImeOptions(EditorInfo.IME_FLAG_FORCE_ASCII);

OK,我们已经把图片设置好了,那么如何由”EditText + Button” 形成一个 “输入+按键响应” 的事件呢?我们只要重写EditText的OnEditorActionListener事件即可,代码如下:

private TextView.OnEditorActionListener mOnEditorActionListener = new TextView.OnEditorActionListener() {

        @Override
        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
            if (actionId == EditorInfo.IME_ACTION_GO) {
//                TODO 待编辑点击事件
                return true;
            }
            return false;
        }
    };
mPassword.setOnEditorActionListener(mOnEditorActionListener);

OK,学习了上面的小知识后,我们继续学习登录页的Activity,该Activity的业务逻辑只有一个登陆,然后UI逻辑却有账号错误、密码错误、开始登录、登录成功、登录失败,我们先看看这些逻辑是怎么来通过MVP模式表达,书接上回,先看看业务逻辑:

package com.itheima.leon.qqdemo.presenter;

/**
 * 创建者:   Leon
 * 创建时间:  2016/10/16 21:16
 * 描述:    TODO
 */
public interface LoginPresenter {
    public static final String TAG = "LoginPresenter";

    void login(String userName, String pwd);
}

接下来看看UI逻辑:

package com.itheima.leon.qqdemo.view;

/**
 * 创建者:   Leon
 * 创建时间:  2016/10/16 21:13
 * 描述:    TODO
 */
public interface LoginView {
    public static final String TAG = "LoginView";

    void onUserNameError();

    void onPasswordError();

    void onStartLogin();

    void onLoginSuccess();

    void onLoginFailed();


}

OK,接下来还是将业务逻辑与UI逻辑进行整合,那么它们是如何判断什么时候来调用相关的UI逻辑呢?看代码:

package com.itheima.leon.qqdemo.presenter.impl;

import com.hyphenate.chat.EMClient;
import com.itheima.leon.qqdemo.adpater.EMCallBackAdapter;
import com.itheima.leon.qqdemo.presenter.LoginPresenter;
import com.itheima.leon.qqdemo.utils.StringUtils;
import com.itheima.leon.qqdemo.utils.ThreadUtils;
import com.itheima.leon.qqdemo.view.LoginView;

/**
 * 创建者:   Leon
 * 创建时间:  2016/10/16 21:17
 * 描述:    TODO
 */
public class LoginPresenterImpl implements LoginPresenter {
    public static final String TAG = "LoginPresenterImpl";


    public LoginView mLoginView;

    public LoginPresenterImpl(LoginView loginView) {
        mLoginView = loginView;
    }

    @Override
    public void login(String userName, String pwd) {
        if (StringUtils.checkUserName(userName)) {
            if (StringUtils.checkPassword(pwd)) {
                mLoginView.onStartLogin();
                startLogin(userName, pwd);
            } else {
                mLoginView.onPasswordError();
            }
        } else {
            mLoginView.onUserNameError();
        }
    }

    private void startLogin(String userName, String pwd) {
        EMClient.getInstance().login(userName, pwd, mEMCallBack);
    }

    private EMCallBackAdapter mEMCallBack = new EMCallBackAdapter() {

        @Override
        public void onSuccess() {
            ThreadUtils.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mLoginView.onLoginSuccess();
                }
            });
        }

        @Override
        public void onError(int i, String s) {
            ThreadUtils.runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    mLoginView.onLoginFailed();
                }
            });
        }
    };
}

这里,我们需要注意的是作者是如何检查用户名与密码的?还是那句话,细节有魔鬼啊!看代码:

package com.itheima.leon.qqdemo.utils;

/**
 * 创建者:   Leon
 * 创建时间:  2016/10/16 21:18
 * 描述:    TODO
 */
public class StringUtils {

    private static final String USER_NAME_REGEX = "^[a-zA-Z]\\w{2,19}$";

    private static final String PASSWORD_REGEX = "^[0-9]{3,20}$";


    public static boolean checkUserName(String userName) {
        return userName.matches(USER_NAME_REGEX);
    }

    public static boolean checkPassword(String pwd) {
        return pwd.matches(PASSWORD_REGEX);
    }
}

对于正则表达式,github已经有很清晰的注释了!还是看代码:

private static final String USER_NAME_REGEX = "^[a-zA-Z]\\w{2,19}$";//用户名的正则表达式
private static final String PASSWORD_REGEX = "^[0-9]{3,20}$";//密码的正则表达式

 1. ^ 匹配输入字符串的开始位置
 2. [a-zA-Z] 字符范围。匹配指定范围内的任意字符。
 3. \w 匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'4. $ 匹配输入字符串的结束位置

接下来可以看看Activity里面的实现了:

package com.itheima.leon.qqdemo.ui.activity;

import android.Manifest;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.PermissionChecker;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

import com.itheima.leon.qqdemo.R;
import com.itheima.leon.qqdemo.presenter.LoginPresenter;
import com.itheima.leon.qqdemo.presenter.impl.LoginPresenterImpl;
import com.itheima.leon.qqdemo.view.LoginView;

import butterknife.BindView;
import butterknife.OnClick;

/**
 * 创建者:   Leon
 * 创建时间:  2016/10/16 19:31
 * 描述:    TODO
 */
public class LoginActivity extends BaseActivity implements LoginView{

    public static final String TAG = "LoginActivity";
    private static final int REQUEST_WRITE_EXTERNAL_STORAGE = 0;
    @BindView(R.id.user_name)
    EditText mUserName;
    @BindView(R.id.password)
    EditText mPassword;
    @BindView(R.id.login)
    Button mLogin;
    @BindView(R.id.new_user)
    TextView mNewUser;

    private LoginPresenter mLoginPresenter;

    /**
     * 绑定布局
     * @return
     */
    @Override
    public int getLayoutRes() {
        return R.layout.activity_login;
    }

    /**
     * 初始化
     */
    @Override
    protected void init() {
        super.init();
        mLoginPresenter = new LoginPresenterImpl(this);
        mPassword.setOnEditorActionListener(mOnEditorActionListener);

    }

    /**
     * 实现点击事件
     * @param view
     */
    @OnClick({R.id.login, R.id.new_user})
    public void onClick(View view) {
        switch (view.getId()) {
            case R.id.login:
                startLogin();
                break;
            case R.id.new_user:
                startActivity(RegisterActivity.class);
                break;
        }
    }

    /**
     * 开始登录
     */
    private void startLogin() {
        if (hasWriteExternalStoragePermission()) {
            login();
        } else {
            applyPermission();
        }
    }

    /**
     * 判断是否已经拥有写入SD卡的权限
     * @return
     */
    private boolean hasWriteExternalStoragePermission() {
        return ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PermissionChecker.PERMISSION_GRANTED;
    }

    /**
     * 请求权限
     */
    private void applyPermission() {
        ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_WRITE_EXTERNAL_STORAGE);
    }

    private void login() {
        hideKeyBoard();
        String userName = mUserName.getText().toString().trim();
        String password = mPassword.getText().toString().trim();
        mLoginPresenter.login(userName, password);
    }

    @Override
    public void onUserNameError() {
        mUserName.setError(getString(R.string.user_name_error));
    }

    @Override
    public void onPasswordError() {
        mPassword.setError(getString(R.string.user_password_error));
    }

    @Override
    public void onStartLogin() {
        showProgress(getString(R.string.logining));
    }

    @Override
    public void onLoginSuccess() {
        hideProgress();
        toast(getString(R.string.login_success));
        startActivity(MainActivity.class);
    }

    @Override
    public void onLoginFailed() {
        hideProgress();
        toast(getString(R.string.login_failed));
    }

    /**
     * 申请权限回调
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        switch (requestCode) {
            case REQUEST_WRITE_EXTERNAL_STORAGE:
                if (grantResults[0] == PermissionChecker.PERMISSION_GRANTED) {
                    login();
                } else {
                    toast(getString(R.string.not_get_permission));
                }
                break;
        }
    }

    private TextView.OnEditorActionListener mOnEditorActionListener = new TextView.OnEditorActionListener() {

        @Override
        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
            if (actionId == EditorInfo.IME_ACTION_GO) {
                startLogin();
                return true;
            }
            return false;
        }
    };
}

现在整篇文章看上去很简洁,也很清晰,所以第一个需要学习的就是注释框架。我以前接触到的注释框架会改变当前的Activity,使用的时候需要在当前的类名前增加一个“_”,但是这里已经不用了。该注释框架叫黄油刀,github地址:https://github.com/JakeWharton/butterknife,学习博客:Android中ButterKnife(黄油刀)的详细使用。由于这个我也没有使用,这里也不能使用更多的语言来介绍了。

在该Activity还有一个知识点需要提到的就是Android6.0动态权限管理,该权限的各个方法都写好了注释,相信应该很好理解。需要强调的是:

1.Android6.0动态权限管理兼容6.0以下的版本,所以不用判断版本;
2.如果文件清单没有添加个人权限(如下),则不会弹出选择是否允许的dialog,而是系统直接返回申请权限失败,测试权限:

<!--<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>-->

最后还有一个地方也值得我们学习这种编程思维,就是接口适配,先看看作者是怎么描述的?

EMCallBack是环信的一个请求回调接口,包括请求成功的回调onSuccess,请求失败的回调onError和请求进度回调onProgress, 但在实际使用过程中,通常只使用到请求成功和失败的回调,请求进度回调通常留在那里成了一个空方法,对于一个有 代码洁癖的搬砖师来说,这是很难受的。所以我们可以创建一个适配器类实现这个接口,使用时用适配器类来替换EMCallBack 接口,这样只需要覆写我们想覆写的方法就可以了(学习代码,这里不讨论登录的进度条实时显示进度的问题,主要是学习优点)。

//EMCallBack接口的适配器
public class EMCallBackAdapter implements EMCallBack{

    @Override
    public void onSuccess() {

    }

    @Override
    public void onError(int i, String s) {

    }

    @Override
    public void onProgress(int i, String s) {

    }
}

//EMCallBack适配器的使用
private EMCallBackAdapter mEMCallBack = new EMCallBackAdapter() {

    @Override
    public void onSuccess() {
        ThreadUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mLoginView.onLoginSuccess();
            }
        });
    }

    @Override
    public void onError(int i, String s) {
        ThreadUtils.runOnUiThread(new Runnable() {
            @Override
            public void run() {
                mLoginView.onLoginFailed();
            }
        });
    }
};

OK,上面就是我从这篇Activity里面学习到的小知识的运用。老规矩,我们也得动脑袋自己进行再加工。这里我就不实现进度条实时显示进度了,我觉得第一个运行时权限可以进一步封装,如郭霖直播中所讲,封装代码如下:

 /**
     * 请求权限
     */
    public void applyPermission(String[] permissions, onRequestPermissionsListener listener) {
        this.listener = listener;
        List<String> permissionList = new ArrayList<>();
        for (String permission:permissions ) {
            if(ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)!= PermissionChecker.PERMISSION_GRANTED){
                permissionList.add(permission);
            }
        }
        if(permissionList.isEmpty()){
           listener.onSuccess();
        }else{
            ActivityCompat.requestPermissions(this,permissionList.toArray(new String[permissionList.size()]), WRITE_EXTERNAL_STORAGE);
        }

    }

    /**
     * 申请权限回调
     */
    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        List<String> defeatedValue = new ArrayList<>();
        switch (requestCode) {
            case WRITE_EXTERNAL_STORAGE:
                for (int i = 0; i < grantResults.length; i++) {
                    String value = permissions[i];
                    if(grantResults[i] != PermissionChecker.PERMISSION_GRANTED){
                        defeatedValue.add(value);
                    }
                }
                if(defeatedValue.isEmpty()){
                    listener.onSuccess();
                }else{
                    listener.onDefeated(defeatedValue);
                }
                break;
        }
    }
    private onRequestPermissionsListener listener;
    public interface onRequestPermissionsListener{
        void onSuccess();
        void onDefeated(List<String> values);
    }

调用方法:


    /**
     * 开始登录
     */
    private void startLogin() {
        applyPermission(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},new BaseActivity.onRequestPermissionsListener(){

            @Override
            public void onSuccess() {
                login();
            }

            @Override
            public void onDefeated(List<String> values) {
                for (int i = 0; i < values.size(); i++) {
                    toast(values.get(i)+":权限获取失败");
                }
            }
        });
    }

OK,Android6.0动态权限管理已经封装完毕,那么接下来我觉得接口的适配可以修改为抽象类,这样的话new的时候自动就将方法重写了,可以更懒一点,弊端暂时还没有想到,欢迎大神拍板砖!

public abstract  class EMCallBackAdapter implements EMCallBack {
    public static final String TAG = "EMCallBackAdapter";



    @Override
    public void onProgress(int i, String s) {

    }
}

调用方法还是一样,这里就不贴代码了!!!
今天就总结到这里,明天继续!

学习的项目地址:

github:https://github.com/uncleleonfan/FanChat

郭霖——Android 6.0运行时权限视频讲解

点击观看

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值