美文网首页AndroidAndroid开发安卓资源收集
从登陆模版学习,Google Android Templates

从登陆模版学习,Google Android Templates

作者: Clone丶记忆 | 来源:发表于2016-07-20 16:36 被阅读2005次

    Google Android Templates

    缘由:从eclipse时代到“死丢丢”时代,一直存在一个我未曾深入观察过的东西——Google Android Templates。一次偶然我在android studio上面结识了她,遂望一窥究竟。

    How to Creat?标题栏File→New→Activity→Login Activity,接下来在弹出的对话框中一直点击Next直到点击Filish。当然上面所说的都是在你的model里面完成的。如下图:


    • image
    • 现象:

      • 1.运行时权限申请
      • 2.两个输入框的提示性文字有动画效果(且输入内容后未消失)。焦点转移后能看到明显的色彩变化。
      • 3.点击登录按钮后有针对email地址输入框的错误提示文字。
      • 4.键盘下面有个独特的@符号,一般情况下的英文输入法是没有单独放在这么明显地方的@符号。
      • 5.符合效验标准后,点击登录按钮会有进度圈旋转。

    source code

    • activity_login.xml
      <ProgressBar
      android:id="@+id/login_progress"
      style="?android:attr/progressBarStyleLarge"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:layout_marginBottom="8dp"
      android:visibility="gone"/>

            <ScrollView
                android:id="@+id/login_form"
                android:layout_width="match_parent"
                android:layout_height="match_parent">
        
                <LinearLayout
                    android:id="@+id/email_login_form"
                    android:layout_width="match_parent"
                    android:layout_height="wrap_content"
                    android:orientation="vertical">
        
                    <android.support.design.widget.TextInputLayout
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content">
        
                        <AutoCompleteTextView
                            android:id="@+id/email"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="@string/prompt_email"
                            android:inputType="textEmailAddress"
                            android:maxLines="1"
                            android:singleLine="true"/>
                        <!-android:inputType="textEmailAddress" 说明软件盘中会有@符号 -->
                    </android.support.design.widget.TextInputLayout>
        
                    <android.support.design.widget.TextInputLayout
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content">
        
                        <EditText
                            android:id="@+id/password"
                            android:layout_width="match_parent"
                            android:layout_height="wrap_content"
                            android:hint="@string/prompt_password"
                            android:imeActionId="@+id/login"
                            android:imeActionLabel="@string/action_sign_in_short"
                            android:imeOptions="actionUnspecified"
                            android:inputType="textPassword"
                            android:maxLines="1"
                            android:singleLine="true"/>
        
                    </android.support.design.widget.TextInputLayout>
        
                    <Button
                        android:id="@+id/email_sign_in_button"
                        style="?android:textAppearanceSmall"
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:layout_marginTop="16dp"
                        android:text="@string/action_sign_in"
                        android:textStyle="bold"/>
        
                </LinearLayout>
            </ScrollView>
      
      • 上面代码中包含控件:ProgressBar、ScrollView、LinearLayout、TextInputLayout、EditText、Button。
      • 分析:
        • ProgressBar是我们登录后出现的进度条。
        • ScrollView可滑动界面,内部只能包含一个控件
        • LinearLayout是ScrollView可滑动界面下面的最上层控件,有且仅有一个
        • TextInputLayout文本输入布局
        • EditText输入框
        • Button按钮,点击后执行登陆操作
          • 在上面的控件中, 我们不是特别常用的仅仅有TextInputLayout,而且根据界面效果来看,我们大胆猜测TextInputLayout可能产生了EditText的提示文字(hint)发生了变化,故此我们需要查看相关资料官方API文档,根据文章总结,TextInputLayout是一个显示在EditText上方的浮动标签。跟ScrollView一样,TextInputLayout只接受一个子元素。子元素需要是一个EditText元素
          • 展示效果:一个单一的EditText 在输入文字的时候会隐藏hint,而被包含在TextInputLayout中的EditText则会让hint变成一个在EditText上方的浮动标签。同时还包括一个漂亮的material动画。
          • 处理错误:TextInputLayout可以处理错误,我们先检查输入的信息是否正常,如果不符合我们的要求我们可以设置错误XXX.setError();
          • 样式:TextInputLayout,所有色彩展示都是在style.xml中设置,具体的请自行搜索。
    • LoginActivity.java

       /**
         * A login screen that offers login via email/password.
         */
        public class LoginActivity extends AppCompatActivity implements LoaderCallbacks<Cursor> {
        
            /**
             * Id to identity READ_CONTACTS permission request.
             */
            private static final int REQUEST_READ_CONTACTS = 0;
        
            /**
             * A dummy authentication store containing known user names and passwords.
             * TODO: remove after connecting to a real authentication system.
             */
            private static final String[] DUMMY_CREDENTIALS = new String[]{
                    "foo@example.com:hello", "bar@example.com:world"
            };
            /**
             * Keep track of the login task to ensure we can cancel it if requested.
             */
            private UserLoginTask mAuthTask = null;
        
            // UI references.
            private AutoCompleteTextView mEmailView;
            private EditText mPasswordView;
            private View mProgressView;
            private View mLoginFormView;
        
            @Override
            protected void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.activity_login);
                // Set up the login form.
                mEmailView = (AutoCompleteTextView) findViewById(R.id.email);
                populateAutoComplete();
        
                mPasswordView = (EditText) findViewById(R.id.password);
                mPasswordView.setOnEditorActionListener(new TextView.OnEditorActionListener() {
                    @Override
                    public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
                        if (id == R.id.login || id == EditorInfo.IME_NULL) {
                            attemptLogin();
                            return true;
                        }
                        return false;
                    }
                });
        
                Button mEmailSignInButton = (Button) findViewById(R.id.email_sign_in_button);
                mEmailSignInButton.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        attemptLogin();
                    }
                });
        
                mLoginFormView = findViewById(R.id.login_form);
                mProgressView = findViewById(R.id.login_progress);
            }
        
            private void populateAutoComplete() {
                if (!mayRequestContacts()) {
                    return;
                }
        
                getLoaderManager().initLoader(0, null, this);
            }
        
            private boolean mayRequestContacts() {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                    return true;
                }
                if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
                    return true;
                }
                if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
                    Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
                            .setAction(android.R.string.ok, new View.OnClickListener() {
                                @Override
                                @TargetApi(Build.VERSION_CODES.M)
                                public void onClick(View v) {
                                    requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
                                }
                            });
                } else {
                    requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
                }
                return false;
            }
        
            /**
             * Callback received when a permissions request has been completed.
             */
            @Override
            public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
                                                   @NonNull int[] grantResults) {
                if (requestCode == REQUEST_READ_CONTACTS) {
                    if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                        populateAutoComplete();
                    }
                }
            }
        
            /**
             * Attempts to sign in or register the account specified by the login form.
             * If there are form errors (invalid email, missing fields, etc.), the
             * errors are presented and no actual login attempt is made.
             */
            private void attemptLogin() {
                if (mAuthTask != null) {
                    return;
                }
        
                // Reset errors.
                mEmailView.setError(null);
                mPasswordView.setError(null);
        
                // Store values at the time of the login attempt.
                String email = mEmailView.getText().toString();
                String password = mPasswordView.getText().toString();
        
                boolean cancel = false;
                View focusView = null;
        
                // Check for a valid password, if the user entered one.
                if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
                    mPasswordView.setError(getString(R.string.error_invalid_password));
                    focusView = mPasswordView;
                    cancel = true;
                }
        
                // Check for a valid email address.
                if (TextUtils.isEmpty(email)) {
                    mEmailView.setError(getString(R.string.error_field_required));
                    focusView = mEmailView;
                    cancel = true;
                } else if (!isEmailValid(email)) {
                    mEmailView.setError(getString(R.string.error_invalid_email));
                    focusView = mEmailView;
                    cancel = true;
                }
        
                if (cancel) {
                    // There was an error; don't attempt login and focus the first
                    // form field with an error.
                    focusView.requestFocus();
                } else {
                    // Show a progress spinner, and kick off a background task to
                    // perform the user login attempt.
                    showProgress(true);
                    mAuthTask = new UserLoginTask(email, password);
                    mAuthTask.execute((Void) null);
                }
            }
        
            private boolean isEmailValid(String email) {
                //TODO: Replace this with your own logic
                return email.contains("@");
            }
        
            private boolean isPasswordValid(String password) {
                //TODO: Replace this with your own logic
                return password.length() > 4;
            }
        
            /**
             * Shows the progress UI and hides the login form.
             */
            @TargetApi(Build.VERSION_CODES.HONEYCOMB_MR2)
            private void showProgress(final boolean show) {
                // On Honeycomb MR2 we have the ViewPropertyAnimator APIs, which allow
                // for very easy animations. If available, use these APIs to fade-in
                // the progress spinner.
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
                    int shortAnimTime = getResources().getInteger(android.R.integer.config_shortAnimTime);
        
                    mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
                    mLoginFormView.animate().setDuration(shortAnimTime).alpha(
                            show ? 0 : 1).setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
                        }
                    });
        
                    mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
                    mProgressView.animate().setDuration(shortAnimTime).alpha(
                            show ? 1 : 0).setListener(new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
                        }
                    });
                } else {
                    // The ViewPropertyAnimator APIs are not available, so simply show
                    // and hide the relevant UI components.
                    mProgressView.setVisibility(show ? View.VISIBLE : View.GONE);
                    mLoginFormView.setVisibility(show ? View.GONE : View.VISIBLE);
                }
            }
        
            @Override
            public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
                return new CursorLoader(this,
                        // Retrieve data rows for the device user's 'profile' contact.
                        Uri.withAppendedPath(ContactsContract.Profile.CONTENT_URI,
                                ContactsContract.Contacts.Data.CONTENT_DIRECTORY), ProfileQuery.PROJECTION,
        
                        // Select only email addresses.
                        ContactsContract.Contacts.Data.MIMETYPE +
                                " = ?", new String[]{ContactsContract.CommonDataKinds.Email
                        .CONTENT_ITEM_TYPE},
        
                        // Show primary email addresses first. Note that there won't be
                        // a primary email address if the user hasn't specified one.
                        ContactsContract.Contacts.Data.IS_PRIMARY + " DESC");
            }
        
            @Override
            public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
                List<String> emails = new ArrayList<>();
                cursor.moveToFirst();
                while (!cursor.isAfterLast()) {
                    emails.add(cursor.getString(ProfileQuery.ADDRESS));
                    cursor.moveToNext();
                }
        
                addEmailsToAutoComplete(emails);
            }
        
            @Override
            public void onLoaderReset(Loader<Cursor> cursorLoader) {
        
            }
        
            private void addEmailsToAutoComplete(List<String> emailAddressCollection) {
                //Create adapter to tell the AutoCompleteTextView what to show in its dropdown list.
                ArrayAdapter<String> adapter =
                        new ArrayAdapter<>(LoginActivity.this,
                                android.R.layout.simple_dropdown_item_1line, emailAddressCollection);
        
                mEmailView.setAdapter(adapter);
            }
        
            private interface ProfileQuery {
                String[] PROJECTION = {
                        ContactsContract.CommonDataKinds.Email.ADDRESS,
                        ContactsContract.CommonDataKinds.Email.IS_PRIMARY,
                };
        
                int ADDRESS = 0;
                int IS_PRIMARY = 1;
            }
        
            /**
             * Represents an asynchronous login/registration task used to authenticate
             * the user.
             */
            public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {
        
                private final String mEmail;
                private final String mPassword;
        
                UserLoginTask(String email, String password) {
                    mEmail = email;
                    mPassword = password;
                }
        
                @Override
                protected Boolean doInBackground(Void... params) {
                    // TODO: attempt authentication against a network service.
        
                    try {
                        // Simulate network access.
                        Thread.sleep(2000);
                    } catch (InterruptedException e) {
                        return false;
                    }
        
                    for (String credential : DUMMY_CREDENTIALS) {
                        String[] pieces = credential.split(":");
                        if (pieces[0].equals(mEmail)) {
                            // Account exists, return true if the password matches.
                            return pieces[1].equals(mPassword);
                        }
                    }
        
                    // TODO: register the new account here.
                    return true;
                }
        
                @Override
                protected void onPostExecute(final Boolean success) {
                    mAuthTask = null;
                    showProgress(false);
        
                    if (success) {
                        finish();
                    } else {
                        mPasswordView.setError(getString(R.string.error_incorrect_password));
                        mPasswordView.requestFocus();
                    }
                }
        
                @Override
                protected void onCancelled() {
                    mAuthTask = null;
                    showProgress(false);
                }
            }
        }
      
      • 分析:
        • 方法列表:
          • onCreate() ——来自activity,初始化控件,事件绑定。
          • populateAutoComplete() ——调用mayRequestContacts(),成功后调用接口(LoaderCallbacks)下面的方法
          • mayRequestContacts() ——动态获取PERMISSION_GRANTED(通讯录权限)
          • onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) ——顾名思义是返回权限申请的结果
          • attemptLogin() ——登录事件
          • isEmailValid(String email) ——是否是email地址的标准
          • isPasswordValid(String password) ——是否符合密码标准
          • showProgress(final boolean show) ——加载进度条
          • onCreateLoader(int i, Bundle bundle) ——接口LoaderCallbacks
          • onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) ——接口LoaderCallbacks
          • onLoaderReset(Loader<Cursor> cursorLoader) ——接口LoaderCallbacks
          • addEmailsToAutoComplete(List<String> emailAddressCollection) ——
        • 内部接口:
          • ProfileQuery
            • 接口内常量 String[] PROJECTION、ADDRESS、IS_PRIMARY
        • 内部类:
          • UserLoginTask extends AsyncTask<Void, Void, Boolean>
            • 构造函数:UserLoginTask(String email, String password)
            • 方法:
              • doInBackground(Void... params)
              • onPostExecute(final Boolean success)
              • onCancelled()
        • 流程梳理:
          • 1.加载XML布局→找到mEmailView控件(email输入框)→申请通讯录权限
            • 申请权限成功→遍历通讯录→获取主要的email→有,加载到界面、无,无操作
            • 申请权限失败→无操作
          • 2.找到其他控件→事件绑定→等待用户执行操作
          • 3.操作界面→执行登录事件
        • 上面我们列出了方法列表,并且将我们涉及到主体流程、较新技能等的方法均有加粗标记。
      • 解析:

      申请权限:

          private boolean mayRequestContacts() {
            //当系统版本低于android_M时,跳过权限检查
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
                    return true;
                }
            //当系统版本大于等于android_M时,执行权限申请代码
            if (checkSelfPermission(READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) {
            //当自身已经被允许的权限中包含了READ_CONTACTS时,返回True
                    return true;
                }
            //当自身已经被允许权限中没有READ_CONTACTS时,申请通讯录读取权限READ_CONTACTS
            //shouldShowRequestPermissionRationale ==> 是否需要调用系统的权限申请界面
            if (shouldShowRequestPermissionRationale(READ_CONTACTS)) {
                Snackbar.make(mEmailView, R.string.permission_rationale, Snackbar.LENGTH_INDEFINITE)
                        .setAction(android.R.string.ok, new View.OnClickListener() {
                            @Override
                            @TargetApi(Build.VERSION_CODES.M)
                            public void onClick(View v) {
                                //展示请求权限界面,第一个参数是权限数组,第二个是请求码
                                requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
                            }
                        });
                } else {
                    requestPermissions(new String[]{READ_CONTACTS}, REQUEST_READ_CONTACTS);
                }
                return false;
            }
      

      申请权限返回的响应

          @Override
              public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
             //请求码 对应上面请求的请求码
             if (requestCode == REQUEST_READ_CONTACTS) {
                if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {    //权限申请成功
                    populateAutoComplete(); //读取联系人列表内的email
                }
            }}
      

      登录事件

          private void attemptLogin() {
          if (mAuthTask != null) {    //登录信息提交的异步任务已经实例化,则无需进行操作,第一次执行attemptLogin()时,mAuthTask并未初始化
            return;
            }
      
           // Reset errors.重设用户名和密码框的错误提示
            mEmailView.setError(null);  //setError方法是TextView下面的方法,主要是提示一个错误信息,内部有系统集成的错误提示图标,原理是在TextView的右边出现一个Drawable
            mPasswordView.setError(null);
      
            // Store values at the time of the login attempt.
            String email = mEmailView.getText().toString();
            String password = mPasswordView.getText().toString();
      
            boolean cancel = false; //是否退出执行登陆进程
            View focusView = null;  //焦点View,当某个输入框输入信息不符合标准时,不执行登陆进程,并锁定焦点到那个输入控件
      
            // 当用户名不为空,判断密码是否符合标准
            if (!TextUtils.isEmpty(password) && !isPasswordValid(password)) {
                mPasswordView.setError(getString(R.string.error_invalid_password));
                focusView = mPasswordView;
                cancel = true;
            }
      
            // Check for a valid email address.
            if (TextUtils.isEmpty(email)) {
                mEmailView.setError(getString(R.string.error_field_required));
                focusView = mEmailView;
                cancel = true;
            } else if (!isEmailValid(email)) {
                mEmailView.setError(getString(R.string.error_invalid_email));
                focusView = mEmailView;
                cancel = true;
            }
      
            if (cancel) {
                //在上面的操作中出现错误了,不执行具体的登录,并且把焦点切换到上面去
                // There was an error; don't attempt login and focus the first
                // form field with an error.
                focusView.requestFocus();
            } else {
                //开启滚动条,执行登录的异步任务
                // Show a progress spinner, and kick off a background task to
                // perform the user login attempt.
                showProgress(true);
                mAuthTask = new UserLoginTask(email, password);
                mAuthTask.execute((Void) null);
            }
          }
      

      登录的异步任务

            public class UserLoginTask extends AsyncTask<Void, Void, Boolean> {
      
                private final String mEmail;
                private final String mPassword;
      
                UserLoginTask(String email, String password) {
                    mEmail = email;
                    mPassword = password;
                }
      
                @Override
                protected Boolean doInBackground(Void... params) {  //后台任务,耗时操作此处执行,该处代码执行在子线程
                    // TODO: attempt authentication against a network service.
      
                    try {
                        // Simulate network access.
                        Thread.sleep(2000); //模拟耗时操作
                    } catch (InterruptedException e) {
                        return false;
                    }
      
                    for (String credential : DUMMY_CREDENTIALS) {
                        String[] pieces = credential.split(":");
                        if (pieces[0].equals(mEmail)) {
                            // Account exists, return true if the password matches.
                            return pieces[1].equals(mPassword);
                        }
                    }
      
                    // TODO: register the new account here.
                    return true;
                }
      
                @Override
                protected void onPostExecute(final Boolean success) {   //执行完毕耗时操作调用这里
                    mAuthTask = null;
                    showProgress(false);
      
                    if (success) {
                        finish();
                    } else {
                        mPasswordView.setError(getString(R.string.error_incorrect_password));
                        mPasswordView.requestFocus();
                    }
                }
      
                @Override
                protected void onCancelled() {  //退出异步任务调用这里
                    mAuthTask = null;
                    showProgress(false);
                }
            }
      

    总结:

    • TextInputLayout,这个控件包含EditText后,会产生提示文字的动画效果,且提示文字不会消失。
    • 纵向布局中,为了保证界面能完整展示,最好在外层套上ScrollView。
    • 一个简单的登录流程,耗时操作不能在主线程执行,AsyncTask异步任务执行完毕后,会回归主线程。
    • 需要数据交互的地方,数据需要做效验。
    • (重点)android6.0以及以后加入了权限申请,我们这里是动态权限申请,也是最容易被用户接受的。
    • 整个登陆界面的操作流程。

    相关文章

      网友评论

        本文标题:从登陆模版学习,Google Android Templates

        本文链接:https://www.haomeiwen.com/subject/elivjttx.html