본문 바로가기

Android

[안드로이드]커스텀 EditText 만들기

반응형

Android에서 사용자 입력을 받기 위한 EditText. 프로젝트 진행 중 기획 상 기본 EditText 기능만으로는 부족한 부분이 있어
텍스트를 지우는 버튼이 있는 커스텀 EditText 만들게 되었다. 커스텀 EditText를 사용하는 방법에 대해 정리해 본다.

 

1. 기능 소개(요구사항)

InputEditText는 다음과 같은 기능을 갖는 커스텀 EditTex

  • 텍스트가 있을 때만 표시되는 지우기(X) 아이콘
  • 포커스 여부에 따른 배경 변경
  • 커스텀 폰트 적용 (Pretendard Variable)
  • 힌트 색상 및 텍스트 사이즈 설정
  • 외부에서 setOnFocusChangeListener, setOnTouchListener 오버라이딩 가능

2. 구현 코드

public class InputEditText extends AppCompatEditText implements TextWatcher, View.OnTouchListener, View.OnFocusChangeListener {
    ...
}

(전체 코드는 아래에서 제공) 이 클래스는 AppCompatEditText를 상속하고, TextWatcher, OnTouchListener, OnFocusChangeListener를 implements하여 다양한 동작을 컨트롤한다.

 

3. 주요 포인트 살펴보기

1) 포커스에 따른 배경 변경

@Override
public void onFocusChange(View view, boolean hasFocus) {
    if (hasFocus) {
        this.setBackgroundDrawable(ContextCompat.getDrawable(getContext(), R.drawable.edit_text_select));
    } else {
        this.setBackgroundDrawable(ContextCompat.getDrawable(getContext(), R.drawable.edit_text_default));
    }
    ...
}

EditText에 포커스가 가면 선택된 배경으로, 포커스가 빠지면 기본 배경으로 바뀌어 시각적으로 사용자에게 상태를 알려준다.

2) 텍스트가 있을 때만 보이는 지우기 버튼

@Override
public final void onTextChanged(CharSequence s, int start, int before, int count) {
    if (isFocused()) {
        setClearIconVisible(s.length() > 0);
    }
}

텍스트가 있을 때만 지우기 버튼이 나타나고, 지우기 버튼은 우측 Drawable로 붙여졌다. onTouch()에서 터치 여부를 체크해 텍스트를 비워준다.

3)  커스텀 폰트 및 텍스트 스타일 적용

Typeface typeface = Typeface.createFromAsset(context.getAssets(), "fonts/pretendard_variable.ttf");
setTypeface(typeface);

 자신만의 폰트를 쉽게 적용할 수 있고, 텍스트 크기나 패딩, 힌트 색상도 함께 지정할 수 있다

 

4. 사용 방법

  1. InputEditText.java 파일을 프로젝트에 추가한다.
  2. res/drawable 폴더에 edit_text_default.xml, edit_text_select.xml, icon_x_btn.xml 등의 리소스를 정의해준다.
  3. Pretendard Variable 폰트를 assets/fonts 폴더에 넣는다.(다른 폰트도 가능)
  4. XML에서 사용: (InputEditText가 있는 package명을 입력)
    <com.yourpackage.InputEditText
        android:id="@+id/input_edit_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="이메일을 입력해주세요"
        android:textColor="@color/text_color"
        android:textColorHint="@color/hint_color"/>
  5. Activity 또는 Fragment에서 설정 추가:
InputEditText inputEditText = findViewById(R.id.input_edit_text);
inputEditText.setOnFocusChangeListener((v, hasFocus) -> {
    // 필요 시 추가 동작 처리
});

 

5. 마무리

EditText를 커스텀하여 만든 InputEditText는 사용자 편의성을 고려한 커스텀 위젯으로, 간단하지만 편리한 기능을 제공해준다.이 클래스를 기반으로 다른 기능을 추가하여 개발을 진행하면 유용할 것같다.

 

5. 전체 소스

1) InputEditText 소스 

package com.yourpackage.customview;

import android.content.Context;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.TextWatcher;
import android.text.style.AbsoluteSizeSpan;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.widget.EditText;

import androidx.appcompat.widget.AppCompatEditText;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;

import com.yourpackage.R; // ← 패키지 경로는 본인 프로젝트에 맞게 수정

public class InputEditText extends AppCompatEditText implements TextWatcher, View.OnTouchListener, View.OnFocusChangeListener {

    private Drawable clearDrawable;
    private OnFocusChangeListener onFocusChangeListener;
    private OnTouchListener onTouchListener;

    private static Context context = null;

    public InputEditText(final Context context) {
        super(context);
        init(context);
    }

    public InputEditText(final Context context, final AttributeSet attrs) {
        super(context, attrs);
        init(context);
    }

    public InputEditText(final Context context, final AttributeSet attrs, final int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context);
    }

    @Override
    public void setOnFocusChangeListener(OnFocusChangeListener onFocusChangeListener) {
        this.onFocusChangeListener = onFocusChangeListener;
    }

    @Override
    public void setOnTouchListener(OnTouchListener onTouchListener) {
        this.onTouchListener = onTouchListener;
    }

    private void init(final Context context) {
        this.context = context;

        this.setBackgroundDrawable(ContextCompat.getDrawable(getContext(), R.drawable.edit_text_default));

        this.setPadding(30, 8, 30, 8);
        this.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 14);
        this.setHintTextColor(getResources().getColor(R.color.hint_color, null));

        Typeface typeface = Typeface.createFromAsset(context.getAssets(), "fonts/pretendard_variable.ttf");
        setTypeface(typeface);

        // Clear (X) 아이콘 설정
        Drawable tempDrawable = ContextCompat.getDrawable(getContext(), R.drawable.icon_x_btn);
        clearDrawable = DrawableCompat.wrap(tempDrawable);
        DrawableCompat.setTintList(clearDrawable, null);
        clearDrawable.setBounds(0, 0, clearDrawable.getIntrinsicWidth(), clearDrawable.getIntrinsicHeight());

        setClearIconVisible(false);

        super.setOnTouchListener(this);
        super.setOnFocusChangeListener(this);
        addTextChangedListener(this);
    }

    private static SpannableString getResizedSpan(CharSequence text, float size) {
        SpannableString ss = new SpannableString(text);
        ss.setSpan(new AbsoluteSizeSpan((int) TypedValue.applyDimension(
                        TypedValue.COMPLEX_UNIT_DIP, size, context.getResources().getDisplayMetrics())),
                0, ss.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        return ss;
    }

    @Override
    public void onFocusChange(final View view, final boolean hasFocus) {
        if (hasFocus) {
            this.setBackgroundDrawable(ContextCompat.getDrawable(getContext(), R.drawable.edit_text_select));
        } else {
            this.setBackgroundDrawable(ContextCompat.getDrawable(getContext(), R.drawable.edit_text_default));
        }

        if (onFocusChangeListener != null) {
            onFocusChangeListener.onFocusChange(view, hasFocus);
        }
    }

    @Override
    public boolean onTouch(final View view, final MotionEvent motionEvent) {
        final int x = (int) motionEvent.getX();
        if (clearDrawable.isVisible() && x > getWidth() - getPaddingRight() - clearDrawable.getIntrinsicWidth()) {
            if (motionEvent.getAction() == MotionEvent.ACTION_UP) {
                setError(null);
                setText(null);
            }
            return true;
        }

        if (onTouchListener != null) {
            return onTouchListener.onTouch(view, motionEvent);
        } else {
            return false;
        }
    }

    @Override
    public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
        if (isFocused()) {
            setClearIconVisible(s.length() > 0);
        }
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count, int after) {}

    @Override
    public void afterTextChanged(Editable s) {}

    public void setClearIconVisible(boolean visible) {
        clearDrawable.setVisible(visible, false);
        setCompoundDrawables(null, null, visible ? clearDrawable : null, null);
    }
}

2) res/drawable/edit_text_default.xml

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <solid android:color="#FFFFFF" />
    <stroke android:width="1dp" android:color="#CCCCCC" />
    <corners android:radius="8dp" />
</shape>

3) res/drawable/edit_text_select.xml  

<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
    <solid android:color="#FFFFFF" />
    <stroke android:width="2dp" android:color="#4285F4" />
    <corners android:radius="8dp" />
</shape>

 

4) res/drawable/icon_x_btn.xml (png 이미지로 대체 가능)

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path
        android:fillColor="#999999"
        android:pathData="M18.3,5.71L12,12l6.3,6.29c0.39,0.39 0.39,1.02 0,1.41 -0.39,0.39 -1.02,0.39 -1.41,0L12,13.41l-6.29,6.29c-0.39,0.39 -1.02,0.39 -1.41,0 -0.39,-0.39 -0.39,-1.02 0,-1.41L10.59,12 4.29,5.71c-0.39,-0.39 -0.39,-1.02 0,-1.41 0.39,-0.39 1.02,-0.39 1.41,0L12,10.59l6.29,-6.29c0.39,-0.39 1.02,-0.39 1.41,0 0.39,0.39 0.39,1.02 0,1.41z" />
</vector>

 

위 res파일은 디자인 파일이 있다면 알맞게 변경하여 사용하면 된다.

반응형