متریال دیزاین : بخش دوازدهم : کار با TextInputLayout و Floating Label

معرفی TextInputLayout:

آموزش ساخت Floating Label متریال در اندروید

به‌نام خدا. در این مبحث به قابلیتی با عنوان Floating Label می‌پردازیم که بر روی EditText اجرا می‌شود. در گذشته با Hint آشنا شدیم. با استفاده از این ویژگی می‌توانستیم یک توضیح کوتاه به یک EditText اضافه کنیم که با پر شدن فیلد توسط کاربر، Hint نیز مخفی می‌شد. اما برای زیبایی بیشتر فیلدهایی که دارای Hint می‌باشند می‌توانیم قابلیتی پیاده سازی کنیم که با لمس EditText توسط کاربر و وارد کردن مقدار، Hint حذف نشده و با یک حالت انیمیشن به بالای فیلد منتقل شود. برای پیاده سازی این قابلیت به کتابخانه Support Design و تگ TextInputLayout نیاز داریم. ضمنا از این کامپوننت برای نمایش Error ها نیز استفاده می‌شود.

یک پروژه جدید با نام TextInputLayout و یک Empty Activity ایجاد می‌کنم.
در ابتدا لازم است کتابخانه design را به پروژه اضافه کنم:

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation 'com.android.support:appcompat-v7:27.1.1'
    implementation 'com.android.support:design:27.1.1'
}

قصد دارم دو EditText برای دریافت نام کاربری و رمز عبور کاربر به اکتیویتی اضافه کنم:

activity_main.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"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <android.support.design.widget.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/user_layout">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/user_edittext"
            android:hint="Username"/>

    </android.support.design.widget.TextInputLayout>


    <android.support.design.widget.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/pass_layout">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/pass_edittext"
            android:hint="Password"
            android:inputType="textPassword"/>

    </android.support.design.widget.TextInputLayout>

</LinearLayout>

هر EditText درون یک تگ TextInputLayout قرار می‌گیرد.
پروژه را اجرا می‌کنم:

آموزش کار با TextInputLayout در اندروید

آموزش کار با TextInputLayout در اندروید

با اجرای پروژه، اولین EditText یعنی Username در حالت Focus قرار می‌گیرد که آماده وارد کردن کاراکتر است. هر آیتمی که در حالت Focus باشد رنگ Hint و خط زیر آن قرمز و مابقی آیتم‌ها خاکستری است.
ممکن است بخواهیم آیتمی غیر از آیتم اول در حالت Focus باشد. یا بهرحال تعیین کنیم یک آیتم خاص به عنوان آیتم Focus شده نمایش داده شود. به کد زیر دقت کنید:

<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/pass_layout">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/pass_edittext"
        android:hint="Password"
        android:inputType="textPassword"/>
    <requestFocus/>

</android.support.design.widget.TextInputLayout>

من یک تگ requestFocus به TextInputLayout مربوط به آیتم Password اضافه کردم که در اجرای مجدد پروژه هم این فیلد Focus شده:

تگ requestFocus

در حال حاضر همیشه یک EditText باید در حالت Focus باشد. اما بهتر است حالتی را پیاده سازی کنیم که اگر کاربر نقطه ای از صفحه (بجز محل این دو آیتم) را لمس کند، آیتمی که Focus هست از این حالت خارج شده و در نهایت تمامی آیتم ها در حالت عادی نمایش داده شوند.

<?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"
    android:orientation="vertical"
    tools:context=".MainActivity"
    android:focusable="true"
    android:focusableInTouchMode="true"
    android:id="@+id/main_layout">

    <android.support.design.widget.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/user_layout">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/user_edittext"
            android:hint="Username"/>

    </android.support.design.widget.TextInputLayout>


    <android.support.design.widget.TextInputLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/pass_layout">

        <EditText
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:id="@+id/pass_edittext"
            android:hint="Password"
            android:inputType="textPassword"/>
        <requestFocus/>

    </android.support.design.widget.TextInputLayout>

</LinearLayout>

Layout ریشه اکتیویتی یک LinearLayout است. به این لایه دو خاصیت focusable و focusableInTouchMode با مقدار true و یک id اضافه کردم. سپس در Backend اکتیویتی این لایه را تعریف نموده و برای آن یک متد Listener می‌سازم که ورودی آن null است:

MainActivity.java:

package ir.android_studio.textinputlayout;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.LinearLayout;

public class MainActivity extends AppCompatActivity {

    LinearLayout mLayout;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mLayout = findViewById(R.id.main_layout);
        mLayout.setOnClickListener(null);

    }
}

مقدار null برای این setOnClickListener باعث می‌شود تا هنگام کلیک (لمس) صفحه که من از نوع LinearLayout ساخته ام، همه EditText ها در حالت غیر Focus قرار گیرند:

خارج شدن EditText ها از حالت فوکوس

ضمنا با اضافه شدن دو خاصیت فوق به لایه اصلی، هیچکدام از آیتم ها در ابتدای اجرای پروژه به صورت پیش فرض Focus نیستند.
قبل از ادامه آموزش، تگ requestFocus را هم غیر فعال (کامنت) می‌کنم زیرا نمی‌خواهم هیچ آیتمی فوکوس باشد.

مدیریت Error ها در TextInputLayout:

مطمئنا همه ما هنگام تکمیل فرم ها به ارورهایی برخورد کرده ایم. این فرم می‌تواند یک فرم ثبت نام در یک وب سایت یا یک اپلیکیشن باشد. به عنوان مثال در فیلد مربوط به کد پستی، اخطار “کد وارد شده باید ده رقم باشد” به کاربر اعلام می‌شود.
تصمیم دارم برای فیلد Username یک error تعریف کنم.

package ir.android_studio.textinputlayout;

import android.support.design.widget.TextInputLayout;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;

public class MainActivity extends AppCompatActivity {

    LinearLayout mLayout;
    TextInputLayout userLayout, passLayout;
    EditText userEdittext, passEdittext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mLayout = findViewById(R.id.main_layout);
        mLayout.setOnClickListener(null);

        userLayout = findViewById(R.id.user_layout);
        passLayout = findViewById(R.id.pass_layout);

        userEdittext = findViewById(R.id.user_edittext);
        passEdittext = findViewById(R.id.pass_edittext);

        userEdittext.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View view, boolean b) {

                if (userEdittext.getText().toString().isEmpty()) {
                    userLayout.setError("Enter Username!");
                } else {
                    userLayout.setErrorEnabled(false);
                }

            }
        });

    }

}

در کد بالا از متد setOnFocusChangeListener استفاده شده:

متد setOnFocusChangeListener

userEdittext.setOnFocusChangeListener(new View.OnFocusChangeListener() {
    @Override
    public void onFocusChange(View view, boolean b) {

    }
});

این متد هنگامی اجرا می‌شود که کاربر روی یک EditText ضربه بزند و آن فیلد در حالت فوکوس قرار بگیرد. متد را به اینصورت تکمیل می‌کنم:

userEdittext.setOnFocusChangeListener(new View.OnFocusChangeListener() {
    @Override
    public void onFocusChange(View view, boolean b) {

        if (userEdittext.getText().toString().isEmpty()) {
            userLayout.setError("Enter Username!");
        } else {
            userLayout.setErrorEnabled(false);
        }

    }
});

یک شرط تعیین کردم که اگر EditText خالی بود، توسط متد setError یک ارور را روی userLayout نمایش بدهد و زمانی که این شرط نقض شد (یعنی فیلد خالی نباشد) ارور غیر فعال شود.
پروژه را اجرا می‌کنم:

نمایش ارور در EditText

نمایش ارور توسط TextInputLayout

نمایش Error در EditText

ملاحظه می‌کنید با لمس EditText مربوط به Username بلافاصله اخطار با رنگ قرمز زیر آن نمایش داده می‌شود. در حال حاضر برنامه یک ایراد دارد. با وجود اینکه من کلمه test را تایپ کرده‌ام هنوز اخطار Enter Username حذف نشده و زمانی حذف خواهد شد که کاربر EditText بعدی یا جای دیگری از صفحه را لمس کند تا این آیتم از Focus خارج شود. در صورتی که به محض ورود اولین کاراکتر این ارور باید حذف شود.
با استفاده از متد addTextChangedListener و TextWatcher می‌توانیم تغییرات متن یک EditText را شنود کنیم.

متد addTextChangedListener در اندروید

userEdittext.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) {

    }

    @Override
    public void afterTextChanged(Editable editable) {

    }
});

TextWatcher سه متد دارد که نقش هرکدام از نامش پیداست. beforeTextChanged قبل از تغییر متن، onTextChanged هنگام تغییر متن و afterTextChanged بعد از تغییر متن صدا زده می‌شوند.
در اینجا من به متد دوم نیاز دارم. همان if ای که قبلا نوشتم را درون این متد تکرار می‌کنم:

package ir.android_studio.textinputlayout;

import android.support.design.widget.TextInputLayout;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.EditText;
import android.widget.LinearLayout;

public class MainActivity extends AppCompatActivity {

    LinearLayout mLayout;
    TextInputLayout userLayout, passLayout;
    EditText userEdittext, passEdittext;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mLayout = findViewById(R.id.main_layout);
        mLayout.setOnClickListener(null);

        userLayout = findViewById(R.id.user_layout);
        passLayout = findViewById(R.id.pass_layout);

        userEdittext = findViewById(R.id.user_edittext);
        passEdittext = findViewById(R.id.pass_edittext);

        userEdittext.setOnFocusChangeListener(new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View view, boolean b) {

                if (userEdittext.getText().toString().isEmpty()) {
                    userLayout.setError("Enter Username!");
                } else {
                    userLayout.setErrorEnabled(false);
                }

            }
        });

        userEdittext.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 (userEdittext.getText().toString().isEmpty()) {
                    userLayout.setError("Enter Username!");
                } else {
                    userLayout.setErrorEnabled(false);
                }

            }

            @Override
            public void afterTextChanged(Editable editable) {

            }
        });

    }

}

حالا با وارد کردن اولین کاراکتر، اخطار حذف می‌شود:

متد onTextChanged در اندروید

با استفاده از خاصیت textColorHint می‌توان رنگ Hint یا همان Floating Label را تغییر داد:

<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/user_layout"
    android:textColorHint="#36cb1f">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/user_edittext"
        android:hint="Username"/>

</android.support.design.widget.TextInputLayout>

خاصیت textColorHint در اندروید

خاصیت hintTextAppearance هم برای استایل دهی به Hint هنگامی که Focus شده، بکار می‌رود:

<style name="TextInputStyle" parent="TextAppearance.AppCompat">
    <item name="android:textColor">#434ab2</item>
    <item name="android:textSize">16dp</item>
</style>
<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/user_layout"
    android:textColorHint="#36cb1f"
    app:hintTextAppearance="@style/TextInputStyle">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/user_edittext"
        android:hint="Username"/>

</android.support.design.widget.TextInputLayout>

خاصیت hintTextAppearance در اندروید

بهتر است استایلی که تعریف می‌کنیم از TextAppearance.AppCompat ارث بری کند.
متن Error را هم به همین صورت و توسط خاصیت errorTextAppearance می‌توان استایل دهی کرد:

<style name="ErrorInputStyle" parent="TextAppearance.AppCompat">
    <item name="android:textColor">#731539</item>
    <item name="android:textSize">16dp</item>
</style>
<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/user_layout"
    android:textColorHint="#36cb1f"
    app:hintTextAppearance="@style/TextInputStyle"
    app:errorTextAppearance="@style/ErrorInputStyle">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/user_edittext"
        android:hint="Username"/>

</android.support.design.widget.TextInputLayout>

خاصیت errorTextAppearance در اندروید

در TextInputLayout قابلیت دیگری با نام Counter (شمارنده) تعریف شده. کاربرد شمارنده در مواقعی است که بخواهیم تعداد کاراکترهای مناسب را به کاربر اعلام کنیم:

<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/pass_layout"
    app:counterEnabled="true"
    app:counterMaxLength="8">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/pass_edittext"
        android:hint="Password"
        android:inputType="textPassword" />
    <!-- <requestFocus/> -->

</android.support.design.widget.TextInputLayout>

خاصیت counterEnabled برای فعال کردن این ویژگی و counterMaxLength برای تعیین تعداد کاراکتر استفاده شده است:

قابلیت Counter یا شمارنده در TextInputLayout اندروید

با وارد کردن هر کاراکتر درون این EditText یک شماره به عدد سمت چپ اضافه می‌شود.
در صورتی که تعداد کاراکتر ورودی از مقدار MaxLength بیشتر شود، رنگ شمارنده تغییر می‌کند. تعیین رنگ شمارنده در حالت عادی و حالتی که تعداد کاراکترها از مقدار تعیین شده تجاوز کند به ترتیب توسط دو خاصیت counterTextAppearance و counterOverflowTextAppearance صورت می‌پذیرد:

<style name="CounterStyle" parent="TextAppearance.AppCompat">
    <item name="android:textColor">#30c7ae</item>
</style>

<style name="OverflowStyle" parent="TextAppearance.AppCompat">
    <item name="android:textColor">#199b85</item>
</style>
<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/pass_layout"
    app:counterEnabled="true"
    app:counterMaxLength="8"
    app:counterTextAppearance="@style/CounterStyle"
    app:counterOverflowTextAppearance="@style/OverflowStyle">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/pass_edittext"
        android:hint="Password"
        android:inputType="textPassword" />
    <!-- <requestFocus/> -->

</android.support.design.widget.TextInputLayout>

خاصیت counterTextAppearance در اندروید

خاصیت counterOverflowTextAppearance در اندروید

خاصیت passwordToggleEnabled آیکونی به EditText های از نوع password اضافه می‌کند که کاربر با لمس آن، پسوردی که وارد کرده را می‌تواند مشاهده می‌کند:

<android.support.design.widget.TextInputLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:id="@+id/pass_layout"
    app:counterEnabled="true"
    app:counterMaxLength="8"
    app:counterTextAppearance="@style/CounterStyle"
    app:counterOverflowTextAppearance="@style/OverflowStyle"
    app:passwordToggleEnabled="true">

    <EditText
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:id="@+id/pass_edittext"
        android:hint="Password"
        android:inputType="textPassword" />
    <!-- <requestFocus/> -->

</android.support.design.widget.TextInputLayout>

خاصیت passwordToggleEnabled در اندروید برای نمایش پسورد

توجه : سورس پروژه درون پوشه Exercises قرار داده شده است

منابع تکمیلی:

https://developer.android.com/reference/android/support/design/widget/TextInputLayout
https://developer.android.com/reference/com/google/android/material/textfield/TextInputLayout
https://developer.android.com/reference/android/text/TextWatcher

دانلود فایل آموزشی با فرمت PDF به همراه سورس پروژه
تعداد صفحات : ۲۱
حجم : ۱/۳ مگابایت
قیمت : رایگان

سوالاتی که مشمول موارد زیر باشد تایید و پاسخ داده نخواهد شد :
۱ : جزء موارد پاسخ داده شده در مطلب "مشکلات و پرسش های رایج" باشد
۲ : سوال قبلا توسط افراد در دیدگاهها مطرح و پاسخ داده شده باشد
۳ : سوال خارج از مبحث آموزشی موجود در این صفحه باشد

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *

کد امنیتی *