<나인패치>
이미지가 나타나는 영역보다 원본 이미지가 작으면 시스템이 이미지 크기를 자동으로 늘려준다. 이 과정에서 왜곡이 발생하는데, 이 왜곡을 해결하는 방법을 나인패치라고 한다.
나인(nine)패치 이미지는 파일 확장자 앞에 '.9'가 붙어야 한다. (ex. ABC.9.png)
아래 레포지토리에 들어가서 button_image 파일 2개를 다운받는다.
파일을 drawable 폴더에 넣어준 뒤, 레이아웃에 버튼 6개를 추가한다.
상위 3개 버튼은 일반 이미지로, 하위 3개 버튼은 나인패치 이미지로 background를 설정해준다.
그 다음 버튼의 width를 wrap_content로 설정해준 뒤 텍스트의 길이를 각각 다르게 설정하여 이미지가 깨지는지 확인한다.
나인패치 이미지는 멀쩡하지만, 일반 이미지로 설정한 상단의 버튼 3개는 이미지가 깨진 것을 확인할 수 있었다.
<새로운 뷰 만들기>
API에서 제공하는 뷰를 사용해서 새로운 뷰를 정의할 수 있다.
API에서 제공하는 뷰를 사용하려면 API의 뷰를 상속해야한다.
개발자가 뷰의 상태에 따라 추가적인 코드를 넣을 수 있도록 콜백 메서드가 호출된다.
- onMeasure() : 뷰가 스스로의 크기를 정할 때 자동으로 호출됨. (폭과 높이 값이 파라미터로 전달됨)
- setMeasuredDimension() : 뷰의 크기 값을 반환하고 싶을 때 사용하는 메서드.
- onDraw() : 뷰가 화면에 보일 때 자동으로 호출됨. 만약 새로운 뷰를 클래스로 적용한 뒤 onDraw()를 재정의하면 다른 모양으로 보이는 뷰를 만들 수 있는 것이다. 새로 정의한 뷰는 뷰가 화면에 보이기 전에 호출된다.
- invalidate() : 이 메서드를 호출하면 onDraw() 메서드가 다시 호출되기 때문에 뷰의 그래픽을 다시 그릴 수 있다. (ex. 손가락으로 화면에 있는 뷰를 터치하여 이동시키려고 할 때 invalidate()를 호출하여 이동한 좌표에서 뷰의 그래픽을 다시 그리게 만들 수 있다.)
1. MainActivity가 위치한 폴더에 새로운 클래스 파일을 만든다. (MyButton)
2. MyButton.java 클래스를 작성한다.
AppCompatButton을 extends 했기 때문에 2개의 생성자를 필수로 추가해주어야한다.
package com.example.chapter7_2;
public class MyButton extends AppCompatButton {
// 첫번째 생성자 : 이 뷰를 소스 코드에서 new 연산자로 생성하는 경우에 사용됨.
public MyButton(@NonNull Context context) {
super(context);
init(context);
}
// 두번째 생성자 : 이 뷰를 XML 레이아웃에 추가하는 경우에 사용됨.
public MyButton(@NonNull Context context, @Nullable AttributeSet attrs) {
// AttributeSet 객체 : XML 레이아웃에서 태그에 추가하는 속성을 전달받기 위한 것
super(context, attrs);
init(context);
}
private void init(Context context) { // 뷰의 배경색과 글자색 설정
setBackgroundColor(Color.CYAN);
setTextColor(Color.BLACK);
float textSize = getResources().getDimension(R.dimen.text_size);
setTextSize(textSize);
/* setTextSize에 바로 값을 넣을 경우 픽셀 단위 설정만 가능함.
그래서 dimens.xml에 sp 단위의 크기 값을 정의한 다음, getDimension으로 가져오면 픽셀값으로 자동 변환된
값을 넣을 수 있다. (sp 말고도 dp 등 다른 단위의 크기 값을 정의할 수 있다.)
*/
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
Log.d("MyButton","onDraw() 호출됨");
}
@Override
public boolean onTouchEvent(MotionEvent event) {
Log.d("MyButton","onTouchEvent() 호출됨");
int action = event.getAction();
switch(action){
case MotionEvent.ACTION_DOWN: // 버튼이 눌린 경우
setBackgroundColor(Color.BLUE);
setTextColor(Color.RED);
break;
case MotionEvent.ACTION_OUTSIDE:
case MotionEvent.ACTION_CANCEL:
case MotionEvent.ACTION_UP:
setBackgroundColor(Color.CYAN);
setTextColor(Color.BLACK);
break;
}
invalidate();
return super.onTouchEvent(event);
}
}
- getDimension
float textSize = getResources().getDimension(R.dimen.text_size);
setTextSize(textSize);
여기서 getDiemension()의 파라미터로 sp 단위의 값을 넣어주기 위해 dimen 파일을 만들어준다.
res/values 폴더 내부에 dimens.xml을 만든다.
<dimens.xml>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="text_size">16sp</dimen>
</resources>
3. activity_main.xml
Design 탭에서 최상위 레이아웃을 RelativeLayout으로 바꾼다.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity" >
<!--직접 정의한 위젯이므로 XML 레이아웃에 추가할 때 패키지 이름까지 함께 넣어야 한다.-->
<com.example.chapter7_2.MyButton
android:id="@+id/button"
android:layout_width="200dp"
android:layout_height="80dp"
android:layout_centerInParent="true"
android:text="시작하기" />
<!--상대 레이아웃에서는 layout_centerInParent 속성 값을 true로 하여 화면 가운데에 배치.-->
</RelativeLayout>
Button을 Design 탭에서 드래그로 추가한 뒤, Code 탭에서 <패키지경로.패키지명 으로 바꿔서 작성한다.
4. 실행 결과
<카드뷰>
카드뷰 : 프로필과 같은 간단 정보를 넣기 위해 각 영역을 구분하는 역할을 하는 뷰.
위에서 했던 새로운 뷰 만들기 방식으로 카드뷰를 만들 것이다.
1. 새 레이아웃 xml 파일, 자바 클래스 파일을 생성한다. (layout1.xml, Layout1.java)
2. layout1.xml
카드뷰를 넣어서 이런 느낌으로 만들어준다.
이미지뷰에 들어갈 이미지는 Mip Map의 ic_launcher를 넣어준다.
코드
<CardView> 태그 부분을 한번 보고 넘어가자.
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:orientation="horizontal">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardCornerRadius="10dp"
app:cardElevation="5dp"
app:cardUseCompatPadding="true">
<!-- cardCornerRadius : 모서리를 둥글게 만듦 -->
<!-- cardElevation : 뷰가 올라온 느낌이 들게 만듦 -->
<!-- cardUseCompatPadding : 기본 패딩이 적용되도록 함. (true로 설정)-->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal">
<ImageView
android:id="@+id/imageView"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_marginRight="5dp"
app:srcCompat="@mipmap/ic_launcher" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="이름"
android:textSize="30sp" />
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:text="전화번호"
android:textColor="#3C5BD0"
android:textSize="25sp" />
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
</LinearLayout>
- cardCornerRadius : 모서리를 둥글게 만듦
- cardElevation : 뷰가 올라온 느낌이 들게 만듦
- cardUseCompatPadding : 기본 패딩이 적용되도록 함. (true로 설정)
3. Layout1.java
LinearLayout을 상속받도록 작성한다.
Generate -> Constructor로 필수생성자를 2개 생성한다.
코드 간략 설명
- 2개의 생성자에 각각 init() 메서드를 실행하는 코드를 추가한다.
- init() 메서드를 작성한다. 인플레이션을 진행한 후, layout1.xml의 이미지뷰와 텍스트뷰를 참조한다.
- setImage(), setName(), setMobile() 이렇게 3개의 메서드를 만든다.
package com.example.chapter7_3;
public class Layout1 extends LinearLayout {
ImageView imageView;
TextView textView1;
TextView textView2;
public Layout1(Context context) {
super(context);
init(context);
}
public Layout1(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
init(context);
}
private void init(Context context){
// 인플레이션 진행하기
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.layout1, this, true);
// 인플레이션 과정이 끝나면 XML 레이아웃 안에 있는 뷰를 참조할 수 있다.
imageView = findViewById(R.id.imageView);
textView1 = findViewById(R.id.textView);
textView2 = findViewById(R.id.textView2);
}
public void setImage(int resId){ // resId는 resourceId이다.
imageView.setImageResource(resId);
// setImageResource() : 이미지뷰에 보이는 이미지를 바꿀 수 있는 메서드 중의 하나.
}
public void setName (String name){
textView1.setText(name);
}
public void setMoblie (String mobile){
textView2.setText(mobile);
}
}
init()
private void init(Context context){
// 인플레이션 진행하기
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.layout1, this, true);
// 인플레이션 과정이 끝나면 XML 레이아웃 안에 있는 뷰를 참조할 수 있다.
imageView = findViewById(R.id.imageView);
textView1 = findViewById(R.id.textView);
textView2 = findViewById(R.id.textView2);
}
LayoutInflater 객체는 시스템 서비스로 제공되므로 getSystemService 메서드를 호출하면서 파라미터로 Context.LAYOUT_INFLATER_SERVICE 상수를 전달하면 객체가 반환된다.
4. activity_main.xml
아래와 같이 버튼 2개와 위에서 만든 layout1을 배치한다. (경로.이름)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/main"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="첫번째 이미지"
android:textSize="20sp" />
<Button
android:id="@+id/button2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="두번째 이미지"
android:textSize="20sp" />
<com.example.chapter7_3.Layout1
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/layout1"
/>
</LinearLayout>
5. MainActivity.java
간단하다. 기본 이미지, 이름, 전화번호가 세팅되고 버튼을 누르면 이미지가 바뀐다.
package com.example.chapter7_3;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Layout1 layout1 = findViewById(R.id.layout1);
layout1.setImage(R.drawable.ic_launcher_foreground);
layout1.setName("강예지");
layout1.setMoblie("010-1234-5678");
Button button1 = findViewById(R.id.button);
Button button2 = findViewById(R.id.button2);
button1.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
layout1.setImage(R.drawable.images);
}
});
button2.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
layout1.setImage(R.drawable.images2);
}
});
}
}
6. 실행결과
'TIL > 안드로이드 스튜디오' 카테고리의 다른 글
7장 - 스피너🔻 (0) | 2025.01.14 |
---|---|
7장 - 리싸이클러뷰 (0) | 2025.01.14 |
도전!12 - 서비스에서 수신자로 메시지 보내기 (0) | 2025.01.10 |
도전!11 - 서비스 실행하고 화면에 보여주기 (0) | 2025.01.10 |
리소스&매니패스트&그래들 이해하기 (0) | 2025.01.09 |