Android项目实战——注册功能

每款App中必不可少的肯定有注册功能,而且实现的具体方式各有千秋,为了在以后的开发中提供参考,下面就针对我个人项目中的注册功能,做一个大概的记录,其中肯定有不规范的地方,大家可以多提提建议。


这个项目的源码地址:https://github.com/yeaper/android_sample/tree/master/Sun


为了方便开发,项目使用了Bmob后端云平台,官网:http://www.bmob.cn/

同时,还用到了:

1)图片加载控件 Fresco:http://www.fresco-cn.org/

2)加载圈控件 Rotateloading:在 app目录下的 build.gradle文件中,添加依赖compile 'com.victor:lib:1.0.4'

3)依赖注入工具 ButterKnife:https://github.com/JakeWharton/butterknife/


这些工具的配置就不详细说明了,大家可以查看详细文档。


1、注册界面的布局 activity_reg.xml

xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@color/bg_grey">

    layout="@layout/toolbar"/>

            android:id="@+id/reg_account"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:singleLine="true"
        android:background="@drawable/shape_form"
        android:inputType="number"
        android:hint="@string/phone_number"
        android:textSize="16sp"
        android:textColor="@color/black"
        android:layout_marginLeft="30dp"
        android:layout_marginRight="30dp"
        android:layout_marginTop="30dp"
        android:layout_gravity="center_vertical"/>

            android:id="@+id/reg_password"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:inputType="textPassword"
        android:hint="@string/password"
        android:textSize="16sp"
        android:textColor="@color/black"
        android:singleLine="true"
        android:background="@drawable/shape_form"
        android:layout_marginLeft="30dp"
        android:layout_marginRight="30dp"
        android:layout_marginTop="15dp"
        android:layout_gravity="center_vertical"/>

            android:id="@+id/reg_confirm_password"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:inputType="textPassword"
        android:hint="@string/password_confirm"
        android:textSize="16sp"
        android:textColor="@color/black"
        android:singleLine="true"
        android:background="@drawable/shape_form"
        android:layout_marginLeft="30dp"
        android:layout_marginRight="30dp"
        android:layout_marginTop="15dp"
        android:layout_gravity="center_vertical"/>

            android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="30dp"
        android:layout_marginRight="30dp"
        android:layout_marginTop="10dp"
        android:text="@string/reg_upload_header"
        android:textSize="14sp"
        android:textColor="@color/deepgrey"/>

            android:id="@+id/reg_header_view"
        android:layout_width="72dp"
        android:layout_height="72dp"
        app:roundAsCircle="true"
        android:layout_marginLeft="30dp"
        android:layout_marginRight="30dp"
        android:layout_marginTop="10dp" />

            android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginLeft="30dp"
        android:layout_marginRight="30dp"
        android:layout_marginTop="10dp"
        android:text="@string/reg_tip"
        android:textSize="14sp"
        android:textColor="@color/deepgrey"/>

    


其中,toolbar布局如下,采用白色风格,标题居中

xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:theme="@style/toolbar_theme">
            android:id="@+id/toolbar"
        android:layout_width="match_parent"
        android:layout_height="?attr/actionBarSize"
        android:background="@color/colorPrimary">
        
                    android:id="@+id/toolbar_title"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center_horizontal"
            android:textColor="@color/white"
            android:textSize="18sp"/>
    

这是toolbar的style,在styles.xml文件中声明

 

还有 com.facebook.drawee.view.SimpleDraweeView,就是图片加载控件,通过设置属性

app:roundAsCircle="true"

让它可以显示圆形图片


2、创建验证工具类 VerifyUtil

public class VerifyUtil {

    /**
     * 判断手机号码格式
     */
    public static boolean isMobile(String mobiles) {

        Pattern p = Pattern.compile("^((1[3,5,8][0-9])|(14[5,7])|(17[0,6,7,8])|(18[0,5-9]))\\d{8}$");
        Matcher m = p.matcher(mobiles);

        return m.matches();
    }

    /**
     * 检查设备是否存在SDCard
     * @return
     */
    public static boolean hasSdcard() {
        String state = Environment.getExternalStorageState();
        // 有存储的SDCard
        return state.equals(Environment.MEDIA_MOUNTED);
    }

    /**
     * 判断网络连接是否正常
     * @param context
     * @return
     */
    public static boolean isConnect(Context context) {
        // 获取手机所有连接管理对象(包括对wi-fi,net等连接的管理)
        try {
            ConnectivityManager connectivity = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
            if (connectivity != null) {
                // 获取网络连接管理的对象
                NetworkInfo info = connectivity.getActiveNetworkInfo();
                if (info != null&& info.isConnected()) {
                    // 判断当前网络是否已经连接
                    if (info.getState() == NetworkInfo.State.CONNECTED) {
                        return true;
                    }
                }
            }
        } catch (Exception e) {
            // TODO: handle exception
            Log.v("error",e.toString());
        }
        return false;
    }
}


手机号验证的正则表达式是我结合网上资料和目前的新出号码写的,可以验证2G、3G、部分4G号码。


3、创建图片类工具 ImageUtil

public class ImageUtil {

    //保存头像到本地
    public static Uri saveImage(Bitmap bm, String fileName, String path){
        if(VerifyUtil.hasSdcard()){
            File foder = new File(path);
            File headImage = null;
            try{
                if (!foder.exists()) {
                    foder.mkdirs();
                }
                headImage = new File(path, fileName);
                if (!headImage.exists()) {
                    headImage.createNewFile();
                }else {
                    headImage.delete();
                    headImage.createNewFile();
                }
                BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(headImage));
                bm.compress(Bitmap.CompressFormat.JPEG, 80, bos);
                bos.flush();
                bos.close();

            }catch (IOException e){
                e.printStackTrace();
            }
            return Uri.fromFile(headImage);
        }else{
            return null;
        }
    }
}


保存完头像后返回的Uri,是为了方便在布局中进行头像加载。


4、自定义加载提示框

public class LoadingDialog extends Dialog {

    private Context mContext;
    private View customView;
    private RotateLoading loadView;

    public LoadingDialog(Context context, int themeResId) {
        super(context, themeResId);
        this.mContext = context;
    }

    /**
     * 初始化自定义的Dialog布局
     * @param msg
     */
    public void initDialog(String msg){
        LayoutInflater inflater = LayoutInflater.from(mContext);
        // 得到加载 view
        customView = inflater.inflate(R.layout.loading_dialog, null);
        // 加载圈
        loadView = (RotateLoading) customView.findViewById(R.id.rotateLoading);
        // 提示文字
        TextView tip = (TextView) customView.findViewById(R.id.loading_tip);
        // 加载圈转动
        loadView.start();
        // 设置提示信息
        tip.setText(msg);

        setContentView(customView);
    }
}


其中,布局文件 loading_dialog.xml 如下

xml version="1.0" encoding="utf-8"?>
xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:id="@+id/loading_dialog"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="10dp"
    android:background="@android:color/transparent">

    layout="@layout/loading" />

            android:id="@+id/loading_tip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="15sp"
        android:textColor="@color/grey"
        android:layout_gravity="center_horizontal"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="5dp"/>


加载圈布局loading.xml如下:

xml version="1.0" encoding="utf-8"?>
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/rotateLoading"
    android:layout_width="70dp"
    android:layout_height="70dp"
    android:layout_centerInParent="true"
    android:layout_gravity="center"
    app:loading_color="@color/colorAccent"
    app:loading_speed="11"
    app:loading_width="5dp" />

而且 Dialog 的风格 themeResId 如下,去掉标题栏:



5、在RegActivity中,实现上传头像、注册用户


public class RegActivity extends AppCompatActivity {

    ActionBar actionbar;
    @BindView(R.id.toolbar)
    Toolbar toolbar;
    @BindView(R.id.toolbar_title)
    TextView toolbar_title;

    @BindView(R.id.reg_account)
    EditText regAccount;
    @BindView(R.id.reg_password)
    EditText regPassword;
    @BindView(R.id.reg_confirm_password)
    EditText regConfirmPassword;
    @BindView(R.id.reg_header_view)
    SimpleDraweeView headerView;

    Context c;
    LoadingDialog loadingDialog;
    // 是否已保存头像
    boolean isSaveImage = false;

    private String TAG = "RegActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_reg);
        ButterKnife.bind(this);
        c = this;

        initToolbar();
        initHeaderView();
        initLoadDialog();
    }

    /**
     * 初始化头部
     */
    public void initToolbar() {
        // ToolBar
        toolbar.setTitleTextColor(getResources().getColor(R.color.white));
        // 设置标题
        toolbar_title.setText(R.string.reg_title);
        setSupportActionBar(toolbar);

        actionbar = getSupportActionBar();
        if (actionbar != null) {
            // 设置返回按钮
            actionbar.setDisplayHomeAsUpEnabled(true);
            // 去掉 ActionBar 自带标题
            actionbar.setTitle(null);
        }
    }

    /**
     * 初始化上传头像的图片
     */
    public void initHeaderView(){
        headerView.setImageURI(Uri.parse("res://com.yyp.sun/" + R.drawable.upload_image));
    }

    /**
     * 初始化 LoadDialog
     */
    public void initLoadDialog(){
        loadingDialog = new LoadingDialog(c, R.style.loading_dialog);
        // 不能自己取消
        loadingDialog.setCancelable(false);
        loadingDialog.initDialog("注册中...");
    }

    /**
     * 点击监听
     * @param v
     */
    @OnClick({R.id.reg_singup, R.id.reg_header_view})
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.reg_singup:
                if(VerifyUtil.isConnect(c)){
                    signUp();
                }else {
                    ToastUtil.showToast(c, "请检查网络设置");
                }
                break;
            case R.id.reg_header_view:
                uploadHeaderView();
                break;
            default:
                break;
        }
    }

    /**
     * 上传头像
     */
    private void uploadHeaderView() {
        Intent intentFromGallery = new Intent();
        // 设置文件类型
        intentFromGallery.setType("image/*");
        intentFromGallery.setAction(Intent.ACTION_GET_CONTENT);
        // 进入相册选择图片
        startActivityForResult(intentFromGallery, SunInfo.CODE_GALLERY_REQUEST);
    }

    /**
     * 裁剪原始的图片
     */
    public void cropRawPhoto(Uri uri) {

        Intent intent = new Intent("com.android.camera.action.CROP");
        intent.setDataAndType(uri, "image/*");

        // 设置裁剪
        intent.putExtra("crop", "true");

        // aspectX , aspectY :宽高的比例
        intent.putExtra("aspectX", 1);
        intent.putExtra("aspectY", 1);

        // outputX , outputY : 裁剪图片宽高
        intent.putExtra("outputX", 127);
        intent.putExtra("outputY", 127);
        intent.putExtra("return-data", true);
        // 进入编辑器剪裁图片
        startActivityForResult(intent, SunInfo.CODE_RESULT_REQUEST);
    }

    /**
     * 注册
     */
    public void signUp() {
        String account = regAccount.getText().toString();
        String password = regPassword.getText().toString();
        String confirmPsd = regConfirmPassword.getText().toString();

        if(TextUtils.isEmpty(account) || TextUtils.isEmpty(password) || TextUtils.isEmpty(confirmPsd)){
            ToastUtil.showToast(c, "请仔细填写");
        }else{
            if (!VerifyUtil.isMobile(account)){
                ToastUtil.showToast(c, "手机号无效");
            }else {
                if (!password.equals(confirmPsd)){
                    ToastUtil.showToast(c, "密码不一致");
                }else{
                    if(password.length() <= 7){
                        ToastUtil.showToast(c, "密码不安全");
                    }else {
                        if(!isSaveImage){
                            ToastUtil.showToast(c, "请重新选取头像");
                        }else{
                            // 显示加载圈
                            loadingDialog.show();

                            final UserInfo user = new UserInfo();
                            user.setUsername(account);
                            user.setPassword(password);
                            user.setSex("");
                            user.setMobilePhoneNumber(account);
                            user.setMobilePhoneNumberVerified(true);
                            // 上传头像
                            final BmobFile bmobFile = new BmobFile(new File(SunInfo.BASE_FILE_URL+SunInfo.HEAD_IMAGE_URL+SunInfo.HEAD_IMAGE_NAME));
                            bmobFile.uploadblock(new UploadFileListener() {

                                @Override
                                public void done(BmobException e) {
                                    if(e==null){
                                        ToastUtil.showToast(c, "头像上传成功");
                                        // bmobFile.getFileUrl()--返回的上传文件的完整地址
                                        user.setAvatarUrl(bmobFile.getFileUrl());
                                        user.setAvatarName(bmobFile.getFilename());
                                        // 注册
                                        user.signUp(new SaveListener() {
                                            @Override
                                            public void done(UserInfo userInfo, BmobException e) {
                                                if(e == null){
                                                    ToastUtil.showToast(c, "注册成功");
                                                    Intent goLogin = new Intent(c, LoginActivity.class);
                                                    startActivity(goLogin);
                                                    finish();
                                                }else {
                                                    loadingDialog.dismiss();
                                                    ToastUtil.showToast(c, "注册失败");
                                                }
                                            }
                                        });
                                    }else{
                                        loadingDialog.dismiss();
                                        Log.e(TAG, "头像上传失败"+e.getMessage());
                                    }
                                }

                                @Override
                                public void onProgress(Integer value) {
                                    // 返回的上传进度(百分比)
                                }
                            });
                        }
                    }
                }
            }
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        switch (item.getItemId()) {
            case android.R.id.home:
                //监听返回按钮
                finish();
                break;
            default:
                break;
        }

        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {

        // 用户没有进行有效的操作,直接返回
        if (resultCode == RESULT_CANCELED) {
            return;
        }

        switch (requestCode) {
            case SunInfo.CODE_GALLERY_REQUEST:
                // 剪裁图片
                cropRawPhoto(intent.getData());
                break;
            case SunInfo.CODE_RESULT_REQUEST:
                if (intent != null) {
                    // 图片拿到后,先保存到本地,再进行设置
                    Bitmap bitmap = intent.getExtras().getParcelable("data");
                    Uri uri = ImageUtil.saveImage(bitmap, SunInfo.HEAD_IMAGE_NAME, SunInfo.BASE_FILE_URL+SunInfo.HEAD_IMAGE_URL);
                    // 判断是否保存了头像
                    if(uri == null){
                        isSaveImage = false;
                    }else{
                        isSaveImage = true;
                        headerView.setImageURI(uri);
                    }
                }
                break;
        }

        super.onActivityResult(requestCode, resultCode, intent);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if(loadingDialog != null){
            loadingDialog.dismiss();
        }
    }
}


选择头像使用Intent操作,主要分两步:


1)选择拍照还是从相册获取

2)对图片进行剪裁并保存到本地


上传头像用户注册的函数调用参考Bmob官方文档:http://docs.bmob.cn/data/Android/b_developdoc/doc/index.html


注意:

1)为了防止内存泄漏,在 onDestory 方法中要关闭加载提示框

2)RegActivity 使用的是 NoActionBar 的风格

3)剪裁图片的宽高尽量都设置在 100 以上,因为一些低配手机在剪裁图片的宽高低于100时,图片周围会产生黑边


6、下面看看实现的效果


Android项目实战——注册功能_第1张图片



Android项目实战——注册功能_第2张图片




Android项目实战——注册功能_第3张图片



以上就是整体过程,谢谢!




你可能感兴趣的:(Android开发实战)