국비교육(22-23)

70일차(1)/Android App(13) : Custom Adapter 생성, Serializable 인터페이스 구현

서리/Seori 2023. 1. 16. 18:07

70일차(1)/Android App(13) : Custom Adapter 생성, Serializable 인터페이스 구현

 

- Custom Adapter 만들어서 view연결하기

- Class에 Serializable 인터페이스 구현하기

 

 

- 새 모듈 생성

 

MainActivity

package com.example.step06customadapter;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ListView;

import androidx.appcompat.app.AppCompatActivity;

import java.util.ArrayList;
import java.util.List;

public class MainActivity extends AppCompatActivity implements AdapterView.OnItemClickListener {

    List<CountryDto> countries;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //아답타에 연결할 모델 객체 생성
        countries=new ArrayList<>();
        //셈플데이터
        countries.add(new CountryDto(R.drawable.austria,
                "오스트리아", "어쩌구.. 저쩌구.."));
        countries.add(new CountryDto(R.drawable.belgium,
                "벨기에", "어쩌구.. 저쩌구.."));
        countries.add(new CountryDto(R.drawable.brazil,
                "브라질", "어쩌구.. 저쩌구.."));
        countries.add(new CountryDto(R.drawable.france,
                "프랑스", "어쩌구.. 저쩌구.."));
        countries.add(new CountryDto(R.drawable.germany,
                "독일", "어쩌구.. 저쩌구.."));
        countries.add(new CountryDto(R.drawable.greece,
                "그리스", "어쩌구.. 저쩌구.."));
        countries.add(new CountryDto(R.drawable.israel,
                "이스라엘", "어쩌구.. 저쩌구.."));
        countries.add(new CountryDto(R.drawable.italy,
                "이탈리아", "어쩌구.. 저쩌구.."));
        countries.add(new CountryDto(R.drawable.japan,
                "일본", "그지 같은 나라~"));
        countries.add(new CountryDto(R.drawable.korea,
                "대한민국", "어쩌구.. 저쩌구.."));
        countries.add(new CountryDto(R.drawable.poland,
                "폴란드", "어쩌구.. 저쩌구.."));
        countries.add(new CountryDto(R.drawable.spain,
                "스페인", "어쩌구.. 저쩌구.."));
        countries.add(new CountryDto(R.drawable.usa,
                "미국", "어쩌구.. 저쩌구.."));

        //ListView에 연결할 아답타
        CountryAdapter adapter=new CountryAdapter(this, R.layout.listview_cell, countries);

        //listView의 참조값 얻어오기
        ListView listView=findViewById(R.id.listView);

        //아답타를 ListView에 연결하기
        listView.setAdapter(adapter);

        //ListView의 셀을 클릭했을 때 동작할 리스너 등록
        listView.setOnItemClickListener(this);
    }

    @Override
    public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
        //DetailActivity로 이동
        Intent intent=new Intent(this, DetailActivity.class);
        /*
            Intent 객체에 어떤 정보를 담아서 다른 액티비티에 전달할 수 있다.
            여기서는 어떤 객체를 담아야 할까?
            클릭한 cell의 index에 해당하는 CountryDto 객체를 담아야 한다.
         */
        CountryDto dto=countries.get(i);
        /*
            intent에 담을 데이터를 Serializable type으로 만들어 놓고 담는다.

            .putExtra( key, Serializable type value )
         */
        intent.putExtra("dto", dto);

        startActivity(intent);
    }
}

 

- ListView의 셀에 국가 이미지를 각각의 국가명과 함께 넣고,

 클릭하면 해당 국가의 설명이 있는 페이지로 이동하는 app을 만들기!

 

- 이 경우 아답타를 커스텀으로 만들어야 한다.

- 문자열을 출력할 때는 이미 만들어진 arrayAdapter를 import해서 사용하면 되는데

  이미지를 출력할 준비가 된 아답타는 없어서 우리가 직접 만들어야 한다.

 

 

- 패키지 우클릭- java 클래스 - CountryDto 클래스생성

CountryDto

package com.example.step06customadapter;

import java.io.Serializable;

public class CountryDto implements Serializable {
    //필드
    private int resId; //출력할 이미지 리소스 아이디 R.id.austria 등등의 값
    private String name; //나라의 이름
    private String content; //나라에 대한 자세한 설명
    //디폴트 생성자
    public CountryDto(){}

    public CountryDto(int resId, String name, String content) {
        this.resId = resId;
        this.name = name;
        this.content = content;
    }

    public int getResId() {
        return resId;
    }

    public void setResId(int resId) {
        this.resId = resId;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

 

- 화면에서 우클릭하거나 / alt+insert 를 누르면 Generate가 있다.

 

- 필드를 모두 선택해서 생성자 만들기

 

- 똑같이 setter / getter 만들어주기

 


레이아웃 폴더 우클릭- Layout Resource file

listview_cell 생성

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="120dp">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="100dp"
        android:layout_height="wrap_content"
        android:layout_marginStart="50dp"
        android:layout_marginTop="10dp"
        android:layout_marginBottom="10dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/austria" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="50dp"
        android:text="TextView"
        android:textSize="30sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintStart_toEndOf="@+id/imageView"
        app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

- constraint layout의 크기 조절하기. 가로는 폭 전체 사용, 세로는 100dp 로 고정한 것이다.

- constraint layout은 UI를 배치할때 제약조건을 추가함으로서 레이아웃을 결정한다.

 

- 셀에 imageView 요소를 추가하고 위아래로 여백 넣어서 고정해주기!

 

- textview 도 간격을 주고 위아래 0 으로 조정하면 자동으로 가운데 정렬된다. 해당 요소에 걸리는 제약조건을 추가한 것.

- 글자크기는 30sp로! (다른 UI들은 대부분 dp 단위를 사용. 텍스트만 sp 단위 사용)

 

- 이렇게 셀 역할을 하는 UI를 하나 만들어보았다.

- 이렇게 국가별로 셀을 만들고, 각각의 셀을 클릭하면 국가 activity로 이동하도록 할 예정!

 


 

- Button, ImageView, xxxLayout 등등.. 
  모든 UI는 부모중에 항상 View 클래스가 있다.
- 우리가 만든 constraintLayout은 이 객체가 만약 생성되면(new) View가 된다.

 


- 만들어진 constraint Layout 을 ListView가 사용하게 할 것이다.

- 이 셀 하나하나는 모두 View이다! 이 안에 imageView, textView를 하나씩 넣을 것이다.

- constraint Layout 로 셀을 하나하나 만들면 각각의 셀을 구성할 수 있는 view를 만들 수 있다.

 

 

- 이 셀 안에 view를 공급하는 주체는 Adapter이다.

- ListView 입장에서 Adapter는 셀을 공급하는 공급자가 된다.

 

 

- 모델은 Adapter에 데이터를 공급한다. List<CountryDto>를 adapter에 연결한다!

- 아답타에 뷰를 만들어내는 레이아웃은 listview_cell.xml이다. 이 레이아웃 안에는 Constraint Layout이 있다.

  이 Layout 안에는 imageView, textView가 포함되어 있는 것이다.

- ListView는 저 아답타와 리스트를 가지고 셀을 만들어서 공급한다.

 

- Adapter를 우리가 custom으로 만든다. 하지만 이 아답타는 특별한 일을 하기 때문에 맘대로 만들 수는 없다.

- 인터페이스를 구현하거나, 클래스를 상속해서 어떤 특정한 형식으로 만들어야 한다.

 


 

이제 만든 셀을 사용해서 View를 공급하는 adapter를 만들것이다.

CountryAdapter java 클래스 생성

package com.example.step06customadapter;

import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

/*
    ListView에 연결할 아답타 클래스 정의하기
    - BaseAdapter 추상 클래스를 상속받아서 만든다.
 */
public class CountryAdapter extends BaseAdapter {
    //필드
    Context context;
    int layoutRes;
    List<CountryDto> list;

    //생성자
    public CountryAdapter(Context context, int layoutRes, List<CountryDto> list){
        //생성자의 인자로 전달된 값을 필드에 저장한다.
        this.context=context;
        this.layoutRes=layoutRes;
        this.list=list;
    }
    /*
        아래 4개의 메소드는 ListView가 필요시 호출하는 메소드이다.
        따라서 적절한 값을 리턴하도록 우리가 프로그래밍해야한다.
     */
    @Override
    public int getCount() {
        //모델의 개수
        return list.size();
    }

    @Override
    public Object getItem(int i) {
        // i번째 인덱스에 해당하는 모델을 리턴해야 한다.
        return list.get(i);
    }

    @Override
    public long getItemId(int i) {
        // i번째 인덱스에 해당하는 모델의 id(primary key 값) 리턴하기(없다면 그냥 인덱스를 아이디 값으로 사용)
        return i;
    }

    //인자로 전달된 position에 해당하는 cell view를 만들어서 리턴하거나
    //이미 만들어진 cell view의 내용만 만들어서 리턴해준다.
    @Override
    public View getView(int i, View view, ViewGroup viewGroup) {

        Log.e("CountryAdapter", "getView() 호출됨 i:"+i);

        //1. res/layout/listview_cell.xml 문서를 전개해서 View 객체를 만든다.
        if(view == null){

            Log.e("CountryAdapter", "view가 null이어서 cell view 를 새로 만듭니다.");

            //레이아웃 전개자(레이아웃 xml 문서를 이용해서 View를 만드는 객체) 객체의 참조값 얻어오기
            LayoutInflater inflater=LayoutInflater.from(context);
            //listView_cell.xml 문서를 전개해서 새 xml문서를 만든다.
            view=inflater.inflate(layoutRes, viewGroup, false);
        }
        //2. i 에 해당하는 CountryDto 객체의 참조값을 얻어온다.
        CountryDto dto=list.get(i);
        //3. 만든 view 객체 안에 있는 imageView, textView의 참조값을 얻어온다.
        ImageView imageView=view.findViewById(R.id.imageView);
        TextView textView=view.findViewById(R.id.textView);
        //4. imageView, textView 에 정보를 출력한다.
        imageView.setImageResource(dto.getResId());
        textView.setText(dto.getName());
        // i번째 인덱스에 해당하는 View를 리턴해준다.
        return view;
    }
}

 

- BaseAdapter 라는 추상클래스를 상속받는다.

 

- 클래스 뒤에서 alt+enter 하면 해야하는 행동이 나온다.(메소드 받기)

- 4개의 메소드 전체를 override 해주고, 기본 생성자도 하나 만들어준다.

 

 

- 이전에 adapter를 사용했던 사례를 보면, 인자 3개를 받는다.

- 1번째 인자: context type

- 2번째 인자: layout Resource

- 3번째 인자: model(data)를 연결

 

- 이 ArrayAdapter도 이미 만들어져 있는 것을 사용했지만, 따지고 보면 BaseAdapter를 상속받아서 만들어진 것이다.

 

- 인자를 똑같이 넣어준다. 이 자리에는 listview_cell 이 들어간다.(정수값으로 관리되고 있는 레이아웃!)

 

- 이것도 필드에서 만든 것이다.

- 필드를 만들고, 생성자에서 들어온 값을 필드에 저장한다.

 


- 아래 4개의 메소드는 ListView가 필요시 호출하는 메소드이다.
- 따라서 적절한 값을 리턴하도록 우리가 프로그래밍해야 한다.

 (현재 리턴값은 null, 0으로 들어가 있다)

 

1) getCount : 모델의 갯수 리턴 (셀에 출력할 데이터의 개수)

- LIst<CountryDto> 의 개수이므로 list.size();

 

 

2) getItem : i번째 인덱스에 해당하는 모델을 리턴해야 한다.

- list.get(i) . 해당 메소드는 object를 리턴하게 되어 있으므로 countryDto가 들어갈 수 있다.

 

3) getItemId : i번째 인덱스에 해당하는 모델의 id(primary key 값) 리턴하기(없다면 그냥 인덱스를 아이디 값으로 사용)

- 지금은 id가 존재하지 않는다.

- 있다면 위와 같이 List.get(i).getId() 형태로 가져왔을 것이지만,

  지금은 DB에서 가져오는 값이 아니므로 그냥 인덱스( i ) 만 가져온다.

 

4) getView : i번째 인덱스에 해당하는 View를 리턴해준다.

listview_cell.xml 을 가져와서 view를 만들어서 그 view에 data를 출력해서 이곳에 넣어 리턴해주어야 한다.

 

 

- 그런데 listView에 셀이 여러개이면 전부 만들어야 할까?

- View를 위아래로 스크롤하려면 View를 많이 만들어야 한다. 100개정도라고 가정한다면?

- 하지만 이걸 100개씩 만드는 대신, 최소한의 갯수만 만들고

  만들어진 view를 재활용해서 데이터만 다르게 출력하면 되지않을까?

 

- getView(int, view, ViewGroup) 에서 인자 view는 전달될 수도 있고 아닐 수도 있다.

- view가 만들어져 있다면 전달될 것이고, 아니면 직접 만들어야 한다.

→ 이 view 값이 null일 경우와 아닌 경우를 분기해야 한다.

 

- 레이아웃 전개자가 필요하므로 LayoutInflater 객체를 얻어낸다.

 

- LayoutInflater : 레이아웃 xml 문서를 사용해서 View를 만드는 객체이다.

 

- inflate() 메소드 4개 중 이 메소드를 활용해서 전개할 것이다.

- listview_cell 의 참조값인 정수값을 전달해주면 이 전개자 객체가 알아서 view를 만들어서 리턴해준다.

 

- view는 null일 수도 아닐 수도 있다. null이면 새로 만들고 아니면 이전 것을 재활용한다!

 

- view 안에 imageView와 textView가 들어있다.

- 참조값은 findViewById() 로 찾아온다.

- 이것은 액티비티에서 사용하던 메소드와는 좀 다르다. View 자체가 가지고있는 메소드를 사용하는 것이다.

- 여기서 참조값을 알아내는 이유는 dto의 내용을 view에 전달하기위한 것!

 

- 각각의 imageView, textView에서 dto에 들어있는 값을 받아서 view를 최종적으로 리턴하면 된다.

 


 

- 이제 액티비티의 레이아웃을 구성할 예정!

 

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <ListView
        android:layout_width="0dp"
        android:layout_height="0dp"
        android:layout_marginStart="1dp"
        android:layout_marginTop="1dp"
        android:layout_marginEnd="1dp"
        android:layout_marginBottom="1dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        android:id="@+id/listView"/>
</androidx.constraintlayout.widget.ConstraintLayout>

 

- 레거시-listView 에서 추가해주고 위치 대략 조정하기

 

- MainActivity 에도 샘플데이터(국가별 정보)를 넣어준다.

 

- 리스트뷰에 연결한 아답타 추가

 

- setAdapter() : 우리가 만든 아답타를 가져와주는 메소드

- BaseAdapter 를 상속받았기 때문에 이곳에 넣어서 사용할 수가 있다.

 

- run 해보면 국가 이미지가 이렇게 출력된다.

 

- 폰의 크기에 따라 다르지만 대략 10~11개정도의 view 가 필요하다.

- 그 다음(아래)부터는 cellview를 새로 만들 필요가 없다.

- 만들어진 뷰에 다른 내용만 출력하면 된다.

 

- 이 내용이 countryAdapter에 들어있다.

- null일 때만 새로 만들고, null이 아니면 위에 있는 cell을 재활용한다.

- if문을 건너뛸 수도 있는데, 건너뛴다는 의미는 이미 만들어진 view에 참조값이 전달된다는 뜻이다.

 

- 빨강: null인 경우, 새로 view를 만들어서 리턴

- 파랑: null이 아닌 경우, 이미 만들어진 view에 내용만 바꾸어서 내용을 출력함

- view를 최소한의 갯수만 만들어서 재활용하면 된다.

 

 

- 해당 cell이 생성되는지 재활용되는지 확인하기 위해 Log 로 출력해보면 이렇다.

- 추가로 로딩하면 아래쪽 view들도 나온다. 이후에 위아래로 왔다갔다 하면 새로 만들지는 않고 재활용만 한다!

 


 

- 리스트에 액티비티를 연결해서 목록을 클릭하면 상세내용을 보여줄 수 있도록 하기

 

new-Activity-Empty Activity

DetailActivity

package com.example.step06customadapter;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

public class DetailActivity extends AppCompatActivity {

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

        // Detail 액티비티에 전달된 intent 객체의 참조값 얻어오기(MainActivity에서 생성한)
        Intent intent=getIntent();
        // intent 객체에 dto라는 키값으로 담긴 데이터를 얻어와서 원래 type으로 casting 한다.
        CountryDto dto=(CountryDto)intent.getSerializableExtra("dto");
        // activity_detail.xml을 전개했을 때 생성되는 UI의 참조값 얻어오기
        ImageView imageView=findViewById(R.id.imageView);
        TextView textView=findViewById(R.id.textView);
        Button confirmBtn=findViewById(R.id.confirmBtn);
        //imageView, TextView에 필요한 정보 출력하기
        imageView.setImageResource(dto.getResId());
        textView.setText(dto.getContent());
        //버튼에 리스너를 익명 클래스를 이용해서 등록하기
        confirmBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //액티비티 종료하기
                //DetailActivity.this.finish();
                finish();
            }
        });
    }
}

 

activity_detail.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".DetailActivity">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        app:srcCompat="@drawable/austria" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="50dp"
        android:text="TextView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/imageView" />

    <Button
        android:id="@+id/confirmBtn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginBottom="100dp"
        android:text="확인"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

 

- imageView를 하나 추가해주고, 좌우 가운데정렬, 위에서 50 떨어지도록 배치한 것

- infer 를 눌러서 배치하고 일부 수정하는것도 편하다.

 

 

- 만들어진 제약조건이 필요 없을 경우, x 아이콘을 누르면 삭제된다.

 

 

- 현재 레이아웃으로는 가로로 눕혔을때는 이렇게 나오는데, 이따가 조정해줄 것!

 


 

- 메인액티비티에 내용 추가! 클래스에 OnItemClickListener 를 구현한다.

 

listView.setOnItemClickListener(this);

- implement 하면 this 로 리스너 등록 가능

 

- 아래 override한 메소드에서 intent 객체를 생성해서 연결한다.

- 이제 클릭하면 액티비티(화면) 이동이 일어난다. 하지만 현재는 전체 모두 오스트리아 페이지로만 이동한다.

 

- A Activity → B Activity 로 이동할 때 어떤 정보를 전달해야 하는 경우가 있다.

- 지금도 해당 국가를 클릭시 그 국가에 대한 내용(CountryDto)를 전달해야 한다.

 

- Intent 객체에 어떤 정보를 담아서 다른 액티비티에 전달할 수 있다.
- 여기서는 어떤 객체를 담아야 할까?
- 클릭한 cell의 index 에 해당하는 CountryDto 객체를 담아야 한다.

 

- intent 객체에 점찍어보면 put 으로 시작하는 메소드들이 있다.

 

- 클릭한 셀의 인덱스 번호가 dto에 전달되면 그 i (인덱스번호) 에 해당하는 값을 가져와준다.

 

- dto라는 키값으로 연결하고 싶은데 이렇게 할 수 없다. CountryDto 타입을 받아주는 putExtra 메소드는 없기 때문에...

→ 그러면 CountryDto를 이 중 하나의 데이터 타입으로 바꾸면 된다.

 

- Serializable 은 인터페이스이다. 구현할 추상 메소드가 존재하지 않는 빈 인터페이스이다.

- 타입을 바꿀 수 있다면 이 메소드에 담아서 사용할 수 있다.

 

- 이렇게 Dto 에 가서 바로 implement Serializable 로 구현해주면 된다.

 

- Serializable은 이처럼 비어 있는 인터페이스이다. 객체를 직렬화한다고도 한다.

 

.putExtra( key, Serializable type value )

- 만든 클래스를 intent에 담고 싶으면 이렇게 serializable 을 사용하면 된다.

 


 

- detailActivity 수정

 

- intent가 액티비티 사이의 매개체 역할을 하는 것이다.

- intent에 putExtra() 로 Dto를 담아두었으니, getIntent를 사용해서 가져오면 된다.

 

CountryDto dto=(CountryDto)intent.getSerializableExtra("dto");

 

- get으로 가져올 수 있다.

- 이 가져온 값을 CountryDto에 다시 담아주면서 casting하면 된다.

 

- 이제 각각의 UI의 참조값을 얻어오고, 버튼에 리스너를 등록하면 된다.

 

 

 

- 이제 list에서 특정 국가를 클릭하면 특정 국가의 정보 화면(activity) 이 나온다.

 

 

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.MyAndroid">
        <activity
            android:name=".DetailActivity"
            android:exported="false"
            android:screenOrientation="portrait">
            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:screenOrientation="fullSensor">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>

            <meta-data
                android:name="android.app.lib_name"
                android:value="" />
        </activity>
    </application>

</manifest>

 

android:screenOrientation="portrait"

 

- 이 속성값이 추가되면, 디바이스를 세로 방향으로밖에 쓰지 못한다!

 

- "portrait" 로 속성값을 작성해주면 세로형으로 고정된다.

- 위와 같이 작성하면 메인은 가로/세로 모두로 사용할 수 있고,

  Detail화면은 세로로밖에 사용할 수 없다.

 

 

- 이처럼 화면을 가로로 돌려도 세로로만 고정되어 사용된다.