본문 바로가기
TIL/Android

view와 layout

by J1-H00N 2024. 3. 20.

새로운 프로젝트를 만들 때 주의할 점!

Empty Project는 무조건 Kotlin으로만 만들어지고, xml 파일도 자동 생성이 안된다.

Androin Jetpack Compose의 사용량이 늘면서 xml 대신 Compose를 사용해 개발하는 경우를 위해 Empty View Project로 구분한 듯 하다. 만약 자바를 사용해서 개발하고 싶다면 Empty View Project를 사용하다록 하자.

 

view : 안드로이드에서 눈에 보이는 모든 요소

개발자가 배치하는 모든 view는 class로 제공되는데, 모두 View라는 클래스를 상속받고 있다.

View 클래스는 모든 UI 요소들의 부모 클래스로 Layout과 Widget으로 나뉜다.

 

Layout

  • Container 또는 View Group이라고도 한다.
  • 다른 뷰들을 포함하고 내부의 뷰들을 통합 관리하고 내부 뷰들이 배치되는 모양을 결정한다.
  • 안드로이드 화면을 구성할 때 배치되는 뷰들이 어디에 좌표된다는 좌표를 설정하지 않는다. => 단말기, 액정마다 환경이 다르므로
  • 따라서 개발자는 배치되는 형식을 결정하고, 안드로이드 OS가 단말기에 적합한 좌표를 계산하고 뷰들을 배치한다.
    • Parent와 Child
      • 안드로이드 화면을 구성하기 우해 layout을 먼저 배치하고 그 위에 다른 View들을 배치하게 된다.
      • 이때 layout을 Patent, 배치되는 뷰들을 Child라고 부른다.
      • 모든 뷰들은 단 하나의 Parent를 가질 수 있으며 모든 layout은 다수의 Child를 가질 수 있다.
  • layout의 종류
    • LinearLayout
      • 방향성을 가지고 view를 배치하는 layout
      • 가로 혹은 세로방향으로 배치할 수 있으며 한 칸에 하나의 view만 배치할 수 있다.
      • 안드로이드에서 가장 많이 사용하는 layout으로 여러 linear layout을 조합하여 다양한 모양을 만들 수 있다. 
      • 주요 속성
        • orientation: 배치되는 방향을 결정한다.
        • weight: LinearLayout 안에 배치되는 뷰들의 비율을 결정한다.
    • FrameLayout
      • 내부에 배치된 뷰들이 같은 자리에 계속 배치되는 layout이다.
      • 화면을 구성하기 보단 탭 등과 같은 기능을 만들 때 사용하는 경우가 많다.
      • 주요 속성은 없다.
      • FrameLayout에 배치되는 뷰들은 기본적으로 모두 좌측 상단에 배치된다.
      • margin 속성이나 layout_gravity 속성을 이용해 배치되는 위치를 결정하여 사용한다.
      • 여러개를 겹쳐놓고 안보이게 해 탭의 기능을 구현할 수도 있다.
    • TableLayout
      • 안드로이드에서 기본 제공하는 layout에는 없기 때문에 직접 코드를 변경하거나 기본 레이아웃 위에 Palette에 있는 TableLayout을 넣어주면 된다. 
      • TableRow가 배치되어 있다.
      • TableRow에 뷰를 배치하면 배치한 뷰의 개수만큼 모든 TableRow에 칸이 생겨난다.
      • 실제 Table과 유사하기 때문에 하나의 칸의 길이가 바뀌면 같은 행의 모든 칸의 길이도 바뀐다.
      • 주요 속성
        • stretchColumns: TableRow 안의 뷰들이 가로로 늘어날 비율을 설정한다. 늘어날 뷰는 index로 설정한다. 만약 모든 뷰의 비율을 같게 하고 싶다면 *을 입력한다. 
        • shrinkColumns: 하나의 column에 너무 많은 뷰를 넣으면 각 뷰는 최소 사이즈를 가지기 때문에 일부 뷰가 화면에 담기지 않을 수 있다. 이때 TableRow 안의 뷰들이 모두 화면에 보일 수 있도록 줄어들게 한다. 줄어들 뷰는 index로 설정한다. 만약 모든 뷰를 줄인다면 *을 입력한다.
      • 배치되는 view의 주요 속성 
        • layout_column: 칸의 개수는 가장 많은 뷰를 가지고 있는 컬럼에 맞춰지고, 모든 뷰는 기본적으로 좌측 정렬이다. 이때 뷰의 위치를 설정한다.
        • layout_span: 뷰가 차지할 칸의 개수를 설정한다. 단, 다른 행을 침범할 수는 없다
    • RelativeLayout
      • Parent나 다른 뷰와의 관계를 설정하여 배치하는 layout이다.
      • RelativeLayout에는 특별한 속성이 없지만 배치되는 뷰들의 속성을 이용해 배치를 결정하게 된다.
      • 배치되는 뷰들의 주요 속성
        • layout_alignParentTop/Bottom/Left/Right: 자신의 상단/하단/좌측/우측을 parent의 상단/하단/좌측/우측 부분과 일치시킨다.
        • layout_alignTop/Bottom/Left/Right: 자신의 상단/하단/좌측/우측을 지정된 view의 상단/하단/좌측/우측 부분에 일치시킨다. 지정 view는 id로 설정한다. ex) @id/button
        • layout_above/below/toRightOf/toLeftOf: 지정된 view 상단/하단/우측/좌측에 배치한다. 단, margin을 신경쓰지 않는다. 이 특징 때문에 의도하지 않은 위치에 view가 놓일 수 있어 다른 layout을 쓸 것을 권고한다.(ConstraintLayout)
        • layout_alignWithParentMissing: 다른 뷰를 정렬 기준으로 설정하였을 경우, 기준으로 설정한 뷰가 없을 때 parent를 기준으로 정렬하게 된다.
        • layout_alignBaseLine: 자신의 BaseLine 부분과 지정된 뷰의 BaseLine을 일치시킨다.
        • layout_centerHorizontal: 세로 방향의 중앙에 정렬한다.
        • layout_centerVertical: 가로 방향의 중앙에 정렬한다.
        • layout_centerParent: 가로 세로 모두 중앙에 정렬한다. == centerHorizontal + centerVertical
    • ConstraintLayout
      • 초기에 있던 layout이 아니라 오래된 애플리케이션은 RelativeLayout을 사용하고 있는 경우가 있다.
      • RelativeLayout을 개선한 layout으로 보다 유연하게 화면을 구성할 수 있다.
      • RelativeLayout처럼 부모와의 관계나 다른 view와의 관계를 설정하지만, 제약 조건을 사용한다는 차이가 있다.
      • 제약 조건
        • 실선 제약 조건: 지정된 기준으로부터 얼마큼 떨어진 위치에 있는지 좌표를 설정한다.
        • 스프링 제약 조건: 지정된 기준으로부터 얼마큼 떨어진 위치에 있는지 비율을 설정한다.
      • Design에서 다른 view를 끌고 오면 해당 위치에 설정할 수 있으나 Attribute의 Constraint에서 상단과 하단, 좌측과 우측 기준을 최소 하나씩은 정해줘야 한다.
      • 상하좌우 모두 0으로 맞추면 자동으로 스프링 제약 조건으로 바뀐다.
      • 실선과 스프링을 같이 쓸 수도 있다.(실선은 고정, 스프링으로 나머지 부분 가변적으로 설정)
    • GridLayout
      • Grid를 설정하여 view를 배치하는 layout이다.
      • TableLayout을 보완하기 위해 제공되는 layout이다.
      • 많이 사용이 되지 않아 기본 layout에 포함되어 있지 않아 legacy에서 찾아볼 수 있다.
      • 주요 속성
        • rowCount: 그리드 layout의 줄의 개수
        • columnCount: 그리드 layout의 열의 개수 
      • 배치되는 view의 주요 속성
        • layout_column/row: view가 배치될 칸/줄의 위치
        • layout_column/rowSpan: view가 차지할 칸/줄의 수
        • layout_columnWeight/Height: 남은 공간을 차지할 가로/세로 비율
      • TableLayout과의 차이점으로 다른 행을 침범할 수 있다.
      • 하지만 동적이고 복잡한 레이아웃을 짤 때는 성능이 떨어진다는 단점이 있어 RecyclerView을 더 자주 쓴다.
  • Include Other Layout
    • layout에서 다른 layout을 포함시킬 수 있는 개념이다.
    • 다수의 화면을 구성할 때 중복되는 부분이 있을 경우 사용한다. 
    • Palette > Containers > include를 드래그하면 포함시킬 수 있는 layout 목록이 뜨고, 원하는 것을 추가하면 된다.
    • 주요 속성
      • layout: 위와 같이 드래그 하지 않고 해당 속성에서 원하는 layout 파일을 지정할 수도 있다.
    • layout을 모듈화하여 유지보수성을 높인다.
    • 포함시킨 모듈 내 view를 조작하기 위해서는 포함시킨 layout에 id를 배정해서 아래와 같이 사용해야 한다.
    • id가 배정된 layout은 기존 layout에 영향을 끼치지 않는다.
    ActivityMainBinding activityMainBinding;
    SecondBinding secondBinding;
    ThirdBinding thirdBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        activityMainBinding = ActivityMainBinding.inflate(getLayoutInflater());
        secondBinding = SecondBinding.inflate(getLayoutInflater());
        thirdBinding = ThirdBinding.inflate(getLayoutInflater());
        setContentView(activityMainBinding.getRoot());
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        activityMainBinding.textView2.setText("첫 번째 문자열입니다.");
        // 동작 안함
//        secondBinding.textView.setText("두 번째 문자열입니다.");
//        thirdBinding.textView3.setText("세 번째 문자열입니다.");
        activityMainBinding.secondLayout.textView.setText("두 번째 문자열입니다.");
        activityMainBinding.thirdLayout.textView3.setText("세 번째 문자열입니다.");
    }

 

Space

  • layout은 아니지만 layout을 이용해 화면을 구성할 때 보조 수단으로 사용하는 view이다.
  • 화면을 구성할 때 여백이 필요할 경우 사용한다.

 

Widget

  • 문자열 입력, 문자열 출력 등의 어떠한 기능을 가지고 있고 사용자와 상호작용을 하는 뷰들을 통칭한다.

 

view의 주요 속성

  • id: xml이나 코드에서 view를 지칭하기 위해 사용하는 속성
  • layout_width: 뷰의 가로 길이
  • layout_height: 뷰의 세로 길이
  • layout_margin: 뷰의 외부 여백
  • padding: 뷰의 내부 여백
  • background: 뷰의 배경 지정

 

버튼을 클릭하면 텍스트가 바뀌는 기능 구현해보기

xml에 있는 view를 id로 지정하기: findViewById(경로)

// ex)
    Button btn1;
    TextView text1;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        setContentView(R.layout.activity_main); // res폴더/layout/activity_main.xml
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

        btn1 = findViewById(R.id.button);
        text1 = findViewById(R.id.textView);
    }

 

클릭 리스너 만들기

        btn1 = findViewById(R.id.button);
        text1 = findViewById(R.id.textView);

        BtnListener listener1 = new BtnListener();
        btn1.setOnClickListener(listener1);
    }

    // 버튼을 누르면 동작할 리스너
    class BtnListener implements View.OnClickListener{

        @Override
        public void onClick(View v) {
            text1.setText("버튼을 눌렀습니다.");
        }
    }

 

View Binding

안드로이드 스튜디오에 추가되어있는 라이브러리

기존의 android x를 통해 View의 주소값을 자동으로 받아오는 기능은 Kotlin 밖에 되질 않아 이를 자바에서 사용할 수 있도록 추가한 라이브러리

layout 폴더에 있는 xml 파일을 관리하는 클래스를 자동 생성하여 이를 통해 View를 관리할 수 있다.

layout 폴더에 있는 xml 파일당 하나의 클래스가 생성되며 이 클래스에는 xml 파일에 배치한 View들을 관리할 수 있는 기능이 구현되어 있다.

그러면 위와 같이 버튼, 텍스트 뷰 등의 뷰들 하나하나의 id를 가져올 필요 없이 간편하게 관리할 수 있는 것이다.

이를 사용하기 위해서는 app 수준의 gradle 파일에 다음과 같이 추가해준다.

    viewBinding {
        enable = true
    }

이렇게 하면 아래와 같이 번거로운 작업이 사라진다.

public class MainActivity extends AppCompatActivity {

//    이렇게 일일이 가져올 필요 없어짐
//    Button button;
//    TextView textView;

    // ViewBinding 객체를 담을 변수
    // layout xml 파일 이름이 activity_main이므로 앞 부분이 아래와 같은것. 예를 들어 abc.xml이라면 AbcBinding이 된다.
    ActivityMainBinding activityMainBinding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        EdgeToEdge.enable(this);
        // ViewBinding 객체를 추출한다.
        activityMainBinding = ActivityMainBinding.inflate(getLayoutInflater());
        // 이제 객체는 activityMainBinidng에 있으므로 경로를 통해 가져오지 않는다.
        setContentView(activityMainBinding.getRoot()); // setContentView(R.layout.activity_main);
        ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main), (v, insets) -> {
            Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars());
            v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom);
            return insets;
        });

//		  이처럼 따로 가져오지 않아도 된다.
//        button = findViewById(R.id.button);
//        textView = findViewById(R.id.textView);
//
        ButtonClickListener buttonClickListener = new ButtonClickListener();
//        button.setOnClickListener(buttonClickListener);
        activityMainBinding.button.setOnClickListener(buttonClickListener);
    }

    class ButtonClickListener implements View.OnClickListener{

        @Override
        public void onClick(View v) {
//            textView.setText("버튼을 눌렀습니다.");
            activityMainBinding.textView.setText("버튼을 눌렀습니다.");
        }
    }
}

 

 

 

'TIL > Android' 카테고리의 다른 글

Menu  (0) 2024.04.09
권한  (0) 2024.04.05
Adapter View  (0) 2024.04.04
Widget  (0) 2024.03.28
안드로이드 기초  (0) 2024.03.19