Constraint Layout을 동적으로 생성한 후에 동적으로 뷰를 추가해보겠습니다.
이걸 해보겠다고 8시간을 구글링 했는데 결국 스택오버플로우에서 답을 찾았습니다.
(앞으로 구글링은 영어로 해야하려나 봅니다...ㅋㅋ)
stackoverflow.com/questions/45263159/constraintlayout-change-constraints-programmatically
우선 다음과 같이 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:id="@+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"
tools:context=".NewSheetActivity">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/rcv_act_new_sheet_top_flow"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<ScrollView
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:id="@+id/lyt_act_new_sheet_chord_group_cont"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/rcv_act_new_sheet_top_flow"
app:layout_constraintBottom_toTopOf="@id/lyt_act_new_sheet_bot_buttons"
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/lyt_act_new_sheet_chord_cont"
android:orientation="vertical">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/test_container"
>
</androidx.constraintlayout.widget.ConstraintLayout>
</LinearLayout>
</ScrollView>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:id="@+id/lyt_act_new_sheet_bot_buttons"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
>
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:onClick="onNewButtonClick"
android:text="+" />
<Button
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:id="@+id/btn_act_new_sheet_delete"
android:text="-"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
구조는 단순하게 보면
<Constraint Layout>
<RecyclerView>
<ScrollView>
<Linear Layout>
<Linear Layout>
<ScrollView>
<Linear Layout>
<Button>
<Button>
<Linear Layout>
<Constraint Layout>
다음과 같이 만들었습니다.
이 중에 스크롤 뷰에 담겨있는 리니어 레이아웃에 동적으로 ConstraintView를 추가하고
그 뷰에 동적으로 커스텀 뷰를 넣을 예정입니다.
커스텀뷰는 마치 Horizontal 리니어 레이아웃을 쓴 것처럼
왼쪽부터 차곡차곡 데이터를 쌓도록 할 것입니다.
리니어 레이아웃 대신 ConstraintView를 사용한 이유는
중간에 데이터를 추가하거나 삭제할 수도 있기 떄문입니다.
왼쪽부터 데이터를 쌓다가 해당 라인이 다 차면 자동으로 다음줄에 추가를 시키도록 하기 위해
2차원 벡터를 선언해 각 줄에 들어가는 뷰를 관리하도록 할 것이나
지금 포스팅에서는 해당 내용의 구현은 생략하겠습니다.
Vector<Vector<ItemChord>> chordList;
/// 중략 ///
/// onCreate 메소드에서 ///
chordList = new Vector<Vector<ItemChord>>();
chordList.add(new Vector<ItemChord>());
벡터를 선언해줍니다.
제가 8시간동한 삽질한 것은 커스텀 뷰를 넣고나서 제약조건을 거는 방법이었습니다.
구글링을 해보면 크게 2가지 방법이 나왔습니다.
LayoutParams 객체로 설정하는 방법
ConstraintSet 객체로 설정하는 방법
저는 둘 다 해봤지만 결국 후자로 성공하였기 때문에 후자의 방법을 소개하고자 합니다.
우선 ConstraintView를 담을 리니어 레이아웃을 참조하기 위해 변수를 선언합니다.
그 안에 담을 ConstraintLayout을 위한 변수도 선언합니다.
LinearLayout sheetContainer;
ConstraintLayout chordContainer;
클래스 밖에 전역으로 리니어 레이아웃을 선언해줍니다.
sheetContainer = (LinearLayout) findViewById(R.id.lyt_act_new_sheet_chord_cont);
OnCreate 메소드에서 xml파일에 있는 뷰를 참조합니다.
저는 + 버튼을 눌렀을 때 Constraint View 를 동적으로 생성할 계획입니다.
따라서 + 버튼의 클릭 메소드를 달아줍니다.
저는 따로 함수를 분리할 생각으로 따로 함수를 작성하고,
xml 코드 상에서 클릭시 사용할 함수를 연결해주었습니다.
버튼 클릭 메소드 안에 코드를 작성합니다.
ItemChord itemChord = new ItemChord(this);
itemChord.setId(View.generateViewId());
우선 Constraint Layout에 넣어줄 객체를 생성합니다.
ConstraintSet를 사용할 때는 레이아웃 내 모든 뷰에 ID가 있어야합니다.
(어차피 ID는 필요하기 때문에 설정을 해주어야합니다.)
객체를 만들면 아이디도 생성해서 부여합니다.
if (chordContainer == null) {
chordContainer = new ConstraintLayout(this);
chordContainer.setBackgroundColor(Color.GREEN);
chordContainer.setLayoutParams(new ConstraintLayout.LayoutParams(ConstraintLayout.LayoutParams.WRAP_CONTENT, ConstraintLayout.LayoutParams.WRAP_CONTENT));
sheetContainer.addView(chordContainer);
}
Constraint Layout을 동적으로 생성합니다.
가로 크기는 담겨있는 뷰 사이즈에 맞게 WRAP_CONTENT로 설정합니다.
생성한 레이아웃을 기존 부모 레이아웃에 넣어줍니다.
ConstraintSet constraintSet = new ConstraintSet();
chordContainer.addView(itemChord, chordList.get(0).size());
constraintSet.clone(chordContainer);
constraintSet 객체를 생성합니다.
말 그대로 제약을 설정하는 객체입니다.
컨테이너에 뷰를 담고나서, 컨테이너의 상태를 클론을 떠옵니다.
클론을 뜨고나서 뷰를 추가하면 안됩니다.
뷰를 추가할 때는 아까 만들었던 이차원벡터의 첫번째 행의 크기값을 인덱스로 넣어줍니다.
if (chordList.get(0).isEmpty()) {
constraintSet.connect(itemChord.getId(), ConstraintSet.LEFT, ConstraintSet.PARENT_ID, ConstraintSet.LEFT, 0);
constraintSet.connect(itemChord.getId(), ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, 0);
}
else {
constraintSet.connect(itemChord.getId(), ConstraintSet.LEFT, chordContainer.getChildAt(chordList.get(0).size()-1).getId(), ConstraintSet.RIGHT,0);
constraintSet.connect(itemChord.getId(), ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, 0);
}
아까 만들었던 이차원벡터의 첫번째 줄 사이즈를 기준으로
사이즈가 없다면 해당 라인의 첫번째 객체이므로 부모 컨테이너(Parent)에 붙여주어야합니다.
해당 라인의 첫번째 객체가 아니라면 다른 객체를 기준으로 붙여주어야 합니다.
A객체 오른쪽에 B 객체를 붙인다고 하면 다음과 같이 코딩합니다.
constraintSet.connect(B.ID, ConstraintSet.LEFT, A.ID, ConstraintSet.RIGHT, 0);
이 순서를 반대로 하면 안됩니다.
사각형 모양의 A객체와 B객체 사이에 선을 이어준다고 상상하고
A객체의 오른쪽에서 시작해서 B객체의 왼쪽까지 선을 연결한다고 보면 됩니다.
이때 기준점 역할을 하는 A는 뒤에 써주고, 붙는 대상인 B를 먼저 써주어야합니다.
constraintSet.applyTo(chordContainer);
chordList.get(0).add(itemChord);
마지막으로 설정한 ConstraintSet을 Constraint 레이아웃에 적용합니다.
그리고 이차원벡터에 방금 추가한 뷰도 넣어줍니다.
다음 사진처럼 하단의 + 버튼을 누를 때마다 오른쪽으로 아이템이 추가되고
자동으로 제약조건이 설정됩니다.
'Android > Java' 카테고리의 다른 글
[안드로이드] getWidth() 와 getMeasuredWidth() (0) | 2021.03.20 |
---|---|
[안드로이드] 프래그먼트(Fragment) (0) | 2021.01.03 |
[안드로이드] 인텐트 활용 : 데이터 교환(2) (0) | 2021.01.02 |
[안드로이드] 인텐트 활용 : 데이터 교환(1) (0) | 2021.01.01 |
[안드로이드] 인텐트의 개념 (0) | 2021.01.01 |