본문 바로가기

프로그래밍/안드로이드

[안드로이드] 텍스트 뷰(TextView)


1. 기본속성




우리가 자주 사용하는 위젯 3대장이라고 보면 된다.

Button과 EditTexts는 TextView로부터 상속으며, TextView는 View를 상속받는다.


아래에서 제공되는 속성들은 모두 "레이아웃 상"에서 속성 조절이 가능한 부분이다.



<수평 입력>

1. EditText (속성값 X)

자동 개행됨. 위젯 높이도 자동으로 늘어남. 

입력될 문자열의 길이를 알 수 없을 때 가장 합리적


2. EditText (singleLine 속성)

오른쪽으로 자동 스크롤이 되지만, 한줄로만 제한되므로 엔터를 눌러도 개행되지 않음.

반드시 한 줄로만 입력받아야 할 때 사용


3. EditText (singleLine, scrollHorizontally 속성)

오른쪽으로 "스크롤"되어 위젯 폭보다 더 긴 문자열 입력 가능


<입력문자 제한>



입력문자 제한을 걸면 해당 값 이외에는 입력이 되지 않는다. numeric(숫자)에 대한 주요 속성값으로 

integer(아라비아 숫자만 입력)

signed (마이너스 부호 허용, 숫자 중간에 올 수 없음)

decimal (소수점 허용, 반드시 하나만 와야 한다)


만약 -12.34를 쓰려고 한다면 | 연산자를 이용해서 signed | decimal을 하면 되겠다.


<커서 및 포커스>



포커스란 커서가 ① 문자열 끝을 선택해주느냐 ② 문자열 전체를 선택해주느냐를 결정한다.

selectAllOnFocus를 하면 클릭시 "전체 문장" 클릭이 된다.

cursorVisible "true"값을 주면 커서 끝에 커서가 생긴다.


<자동 링크>



문자열에 포함된 링크를 자동으로 해석해준다.

web(웹 주소 인식), email(이메일인식) phone(번호인식), map(지도의 주소 인식), all(모든패턴 인식)


<글자의 모양>


<줄간, 자간>

<줄간>

a*m + e의 공식으로 줄간이 적용된다.

기본줄간이 a이며, Multiplier가 배수에 해당되고, Extra가 여분을 뜻한다.


letterSpacing 속성을 이용해 글자 사이의 간격(字間)을 조정한다.


<글꼴 기준 크기>


지정한 줄 만큼 높이를 강제로 차지한다

반드시 1줄 이상, 3줄 이하로 적어야 한다면

minLines = "1", maxLines = "3" 을 이용할 수도 있겠다.

위 속성을 쓰려면 layout_height는 반드시 "wrap_content"여야 한다.


<대소문자 변환>

직관적으로 알 수 있다.

characters는 모든 글자 하나하나가 대문자로 표기된다.

words는 단어 단위로 대문자로 표기된다.

sentences는 문장 단위로 대문자로 표기된다.


<힌트>



저렇게 회식 글씨로 표기된다. 입력을 누르면 사라진다.


2. bufferType


버퍼 타입(buffer type)이란 "텍스트뷰가 가진 문자열로 어떤 작업을 할 수 있는지 정의하는 값"이다.

이를 XML문서 내에서 bufferType 속성으로 지정한다. 이 속성에는 3가지 종류가 있다.


normal : 단순한 문자열, 읽기 전용 // TextView의 디폴트 버퍼 타입이 noraml이라 실행중 편집 불가

editable : 편집가능한 문자열  // EditText의 디폴트 버터 타입이 editable이라 실행중 편집 가능

spannable : 문자열에 부가정보를 같이 기록


여기서 spannable의 역할이 가장 모호하다. 편집은 안되지만, 문자열 중간에 "표식 삽입"이 가능하다.


span이란 뜻은 문자 서식, 문단 서식, 이미지 정보 등 문자열 표현과 동작에 사용되는 "추가 정보"를 의미




위에처럼 EditText 1개, TextView 2개를 삽입했고,

TextView에는 bufferType="spannable"을 설정했다.


스팬정보는 XML 문서에서 초기화할 수 없으며 실행 중에 코드로 초기화해야 한다.

에딧텍스트 1개, 텍스트 뷰 2개를 삽입했으니 소스코드 역시 마찬가지일 것이다.





setSpan 메서드를 보면 4개의 매개변수가 있다는 것을 알 수 있다.


void setSpan (Object what, int start, int end, int flags)


Object what : 서식을 어떻게 설정할 것인가?

int start, int end : 어디서부터 어디까지 적용할 것인가?

int flags : 스팬(span)의 앞뒤로 새로운 문자가 삽입될 때 영역이 확장(inclusive) or 제외(exclusive)?


<출력결과>



URLSpan은 커스텀 링크(custom link)로써, 임의의 위치에 링크를 걸 수 있고

클릭할 때 onClick 메서드가 호출되어서 필요한 동작을 할 수 있다.


글자 순서 10~13부터 profile(프로필), 18~21부터 call(연락처) 링크를 걸어 놓았다.

    클릭 후에 토스트 메시지를 띄우는 것이므로 메시지가 출력될 것이다.




URLSpan이 동작하려면 

setMovementMetho 메서드로 포커스 이동 시에 어떤 동작을 할 건지 지정해야 한다.


여기서 한 가지 궁금증이 생긴다.  왜 spannable 기능이 있는 것일까?

현재 나의 지식으로는 워드프로세스의 스타일 서식처럼 문자열에 부가정보를 준다고만 알아둬야 겠다.



<최상위의 버퍼타입 Editable>


다시 한번 복습을 하면, 

버퍼타입은 실행 중에 코드에서도 텍스트뷰의 문자열을 읽거나 변경할 수 있는 메서드이다.


최상위 버퍼타입 Editable 버퍼 타입은 스팬(span) 배치 뿐만 아니라 실행 중에 편집도 가능하다.

Editable 인터페이스는 다음 메서드가 추가로 정의되어 있다.


Editable insert (int where, CharSequence text)

Editable delete (int st, int en)

Editable append (char text)

void clear()

Editable replace (int st, int en, CharSequence text)


임의의 위치에 문자를 삽입, 삭제, 추가, 대체하는 메서드이며, 편집을 지원한다.


public class ExerciseExam extends AppCompatActivity {

EditText mEdit; // EditText타입의 mEdit 변수 선언
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_excercise_exam);

mEdit = (EditText) findViewById(R.id.edit);
}
public void mOnClick(View v){
Editable edit = mEdit.getText();
switch (v.getId()){
case R.id.insert:
edit.insert(0, "INS");
break;

case R.id.delete:
edit.delete(2, 5);
break;

case R.id.append:
edit.append("APP");
break;
case R.id.replace:
edit.replace(2,5, "REP");
break;
case R.id.clear:
edit.clear();
break;
}

}
}


복잡한 코드구현 없이 간단하게 문자열을 다룰 수 있다.

매개변수는 대충 봐도 무슨 역할을 할 수 있는지 알 수 있을 것이다.

숫자는 시작 위치를 뜻하며, delete의 경우 2,5이니 3,4,5를 지운다.



문자열의 길이가 5미만일 때 삭제(delete) 또는 대체(replace)하면 버퍼의 범위를 벗어나 다운된다.



3. 문자열 변경 리스너



텍스트가 변경되는 시점에 특정 작업하려면 아래 메스드를 리스너로 등록한다.


void addTextChangeListener (TextWatcher watcher)


TextWatcher 객체 생성 후, 리스너로 등록하면 TextWatcher 인터페이스의 다음 메서드가 호출된다.


void befroeTextChanged (CharSequence s, int start, int count, int after)

void afterTextChanged (Editable s)

void onTextChanged (CharSequence s, int start, int befroe, int count)


편집 전, 후, 완료 후에 각각의 메서드가 호출되며 어디가 얼만큼 편집되었는지 인수로 알려준다

특별한 동작은 하지 않고, 편집되는 시점을 정확하게 잡아내는 기법을 보여준다.


다음은 실습예제이다.



먼저 레이아웃부터 생각을 해보자

그램과 원은 사용자가 직업 입력해야 하는 부분이니 4개의 EditText가 필요하다.

"그램에", "원일 때"가 2세트가 있으니 총 4개의 TextView가 필요하다.


즉, <EditText> <TextView> <EditText> <TextView>를 담는 수평(horizontal) 레이아웃 2개가 필요하다.

이후 사용자가 텍스트를 입력할 때마다 2번째, 4번째 줄에서 "실행 중에" 실시간으로 결과값을 보여준다.


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<EditText
android:id="@+id/gram"
android:layout_width="0px"
android:layout_weight="1"
android:layout_height="wrap_content"
android:inputType="number"
android:selectAllOnFocus="true"
android:text="1000"
/>
<TextView
android:layout_width="0px"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="그램에"
/>
<EditText
android:id="@+id/won"
android:layout_width="0px"
android:layout_weight="1"
android:layout_height="wrap_content"
android:inputType="number"
android:selectAllOnFocus="true"
android:text="1000"
/>
<TextView
android:layout_width="0px"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="원일 때"
/>
</LinearLayout>
<TextView
android:id="@+id/price"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:textColor="#ff0008"
android:text="그램당 1원"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
>
<EditText
android:id="@+id/gram2"
android:layout_width="0px"
android:layout_weight="1"
android:layout_height="wrap_content"
android:inputType="number"
android:selectAllOnFocus="true"
android:text="1000"
/>
<TextView
android:layout_width="0px"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="그램에"
/>
<EditText
android:id="@+id/won2"
android:layout_width="0px"
android:layout_weight="1"
android:layout_height="wrap_content"
android:inputType="number"
android:selectAllOnFocus="true"
android:text="1000"
/>
<TextView
android:layout_width="0px"
android:layout_weight="1"
android:layout_height="wrap_content"
android:text="원일 때"
/>
</LinearLayout>

<TextView
android:id="@+id/price2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="30sp"
android:textColor="#ff0000"
android:text="그램당 1원"
/>
</LinearLayout>
public class ExerciseExam extends AppCompatActivity {

EditText mGram, mWon;
TextView mPrice;
EditText mGram2, mWon2;
TextView mPrice2;

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

mGram = (EditText)findViewById(R.id.gram);
mWon = (EditText)findViewById(R.id.won);
mPrice = (TextView)findViewById(R.id.price);
mGram.addTextChangedListener(mWatcher);
mWon.addTextChangedListener(mWatcher);

mGram2 = (EditText)findViewById(R.id.gram2);
mWon2 = (EditText)findViewById(R.id.won2);
mPrice2 = (TextView)findViewById(R.id.price2);
mGram2.addTextChangedListener(mWatcher2);
mWon2.addTextChangedListener(mWatcher2);

};

TextWatcher mWatcher = new TextWatcher() {
public void afterTextChanged(Editable s){}
public void beforeTextChanged(CharSequence s, int start, int count, int after){}
public void onTextChanged(CharSequence s, int start, int before, int count){
int gram, won;
try{
gram = Integer.parseInt(mGram.getText().toString());
}
catch (NumberFormatException e){
return ;
}
try {
won = Integer.parseInt(mWon.getText().toString());
}
catch (NumberFormatException e){
return;
}

float price = (float)won / gram;
mPrice.setText(String.format("그램당 %.4f원", price));
}
};

TextWatcher mWatcher2 = new TextWatcher(){
public void afterTextChanged(Editable s){}
public void beforeTextChanged(CharSequence s, int start, int count, int after){}
public void onTextChanged(CharSequence s, int start, int before, int count){
int gram, won;
try{
gram = Integer.parseInt(mGram2.getText().toString());
}
catch (NumberFormatException e){
return ;
}
try {
won = Integer.parseInt(mWon2.getText().toString());
}
catch (NumberFormatException e){
return;
}
float price = (float)won / gram;
mPrice2.setText(String.format("그램당 %.4f원", price));
}
};
}





<bufferType.xml>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="1234567890"
/>
<Button
android:id="@+id/insert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="Insert"
/>
<Button
android:id="@+id/delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="Delete"
/>
<Button
android:id="@+id/append"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="Append"
/>
<Button
android:id="@+id/replace"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="Replace"
/>
<Button
android:id="@+id/clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="Clear"
/>
</LinearLayout>


<bufferType.java>

public class ExerciseExam extends AppCompatActivity {

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

EditText Edit = (EditText)findViewById(R.id.edit);
Spannable espan = Edit.getText();
espan.setSpan(new StyleSpan(Typeface.ITALIC), 1, 7, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
espan.setSpan(new BackgroundColorSpan(0xffff0000), 8, 11, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);
espan.setSpan(new UnderlineSpan(), 12, 17, Spannable.SPAN_EXCLUSIVE_INCLUSIVE);

TextView Text = (TextView)findViewById(R.id.text);
Spannable tspan = (Spannable)Text.getText();
tspan.setSpan(new RelativeSizeSpan(0.5f), 0, 5, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tspan.setSpan(new ForegroundColorSpan(0xff0000ff), 5, 9, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
tspan.setSpan(new RelativeSizeSpan(1.5f), 9, 12, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

TextView link = (TextView)findViewById(R.id.textlink);
Spannable lspan = (Spannable)link.getText();

URLSpan profile = new URLSpan(""){public void onClick(View v){Toast.makeText(v.getContext(), "이 사람의 프로필을 검색.", 0).show();}};
lspan.setSpan(profile, 10, 13, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);

URLSpan call = new URLSpan(""){public void onClick(View v){Toast.makeText(v.getContext(), "이 사람의 연락처 찾기.", 0).show();}};

lspan.setSpan(call, 17, 21, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
link.setMovementMethod(LinkMovementMethod.getInstance());

}
}



<Editable.xml>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="1234567890"
/>
<Button
android:id="@+id/insert"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="Insert"
/>
<Button
android:id="@+id/delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="Delete"
/>
<Button
android:id="@+id/append"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="Append"
/>
<Button
android:id="@+id/replace"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="Replace"
/>
<Button
android:id="@+id/clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:onClick="mOnClick"
android:text="Clear"
/>
</LinearLayout>



<Editable.java>

public class ExerciseExam extends AppCompatActivity {

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

mEdit = (EditText) findViewById(R.id.edit);
}
public void mOnClick(View v){
Editable edit = mEdit.getText();
switch (v.getId()){
case R.id.insert:
edit.insert(0, "INS");
break;

case R.id.delete:
edit.delete(2, 5);
break;

case R.id.append:
edit.append("APP");
break;
case R.id.replace:
edit.replace(2,5, "REP");
break;
case R.id.clear:
edit.clear();
break;
}

}
}



<문자열 변경리스너. xml>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<EditText
android:id="@+id/edit"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="echo:"/>
</LinearLayout>


<문자열 변경리스너. java>

public class ExerciseExam extends AppCompatActivity {

EditText mEdit;
TextView mText;

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

mEdit = (EditText) findViewById(R.id.edit);
mText = (TextView) findViewById(R.id.text);
mEdit.addTextChangedListener(mWatcher);
}

TextWatcher mWatcher = new TextWatcher() {
public void afterTextChanged(Editable s) { }
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) { }
public void onTextChanged(CharSequence charSequence, int start, int before1, int count) {
mText.setText("echo:"+ start);
}
};
}