달달한 스토리

728x90
반응형

서론이 길어서 급하신 분들은 구분선부터 봐주세요!

 

꽤나 오랜만에 글을 쓴다.

 

요즘 프로젝트를 만드느라 정신이 없기 때문이다.

 

거의 매일 일기를 쓰다시피 했는데,

 

이제는 일기 쓰는 시간에도 코딩을 하고 싶어서,

 

일기를 자연스레 쓰지 않았던 것 같다.

 

그러다가 문득 영상을 보았는데,

 

Today I Learned라고 오늘 나는 무엇을 배웠는지,

 

기록해나가는 연습이라고 한다.

 

이게 참 좋은게 복습을 하는 중요한 효과로 작용을 할 거라는 기대를 한다.

 

그냥 단순히 개발 포스팅보다는

 

정확하진 않더라도, 내가 나중에 누군가에게

 

설명하고 배운 것을 정리할 수 있는 시간이 되었으면 한다.

 

 

Fragment 만들기

 

본론으로 넘어가겠다.

 

우선 나 같은 경우는 프로젝트를 만들던 도중

 

프래그먼트 위에서 뷰 페이저를 사용해서 또 프래그먼트를 올리는

 

작업을 할 생각이었는데,

 

이게 구현이 가능할까?라는 생각이 들었다.

 

커뮤니티에서 물어보니까 가능하다고 하는 사람이 없어서

 

직접 찾아보기로 했다.

 

우선 뷰 페이저를 예제로만 사용해봤지 기억이 나지 않아 개발자 페이지를 들어갔다.

 

 

 

 

 

그렇다고 한다. 여러 기능을 추가하고, 오래전부터 뷰페이저 2로 이전을 했다고 한다.

 

나만 몰랐다..

 

근데 중요한 것은 뷰페이저 2를 쓰면 되겠는데,

 

아직 내가 한국어로 된 블로그 글로만 많이 익혔지,

 

아직 API문서를 보는데 서툴기도 했다.

 

그래서 뷰 페이저 2를 프래그먼트 위에 올리는 작업을 찾아봤는데,

 

예제가 많이 없었다. 오히려 뷰 페이저 1 예제는 많았다.

 

그렇게 몇 시간을 헤매었는지 모르겠다.

 

모든 예제들이 프래그먼트에 프래그먼트를 올리는 것이 아닌,

 

액티비티에 올리는 것 밖에 없었다.

 

이때까지 둘 다 되는지 몰랐는데,

 

프래그먼트에도 올릴 수 있다고 하더라... (독학이라 물어볼 사람이 없음..)

 

그래서 뷰 페이저 2를 바로 적용해 보기로 했다.

 

(홍드로이드님의 깃허브를 참고했다.)

 


코드 정리

 

코드를 정리해보았다.

 

우선 MainActivity.java에 밑에 깔릴 프래그먼트를 구현해보자.

 

<------MainActivity.java------>

public class MainActivity extends AppCompatActivity {

    HomeFragment fragment; //홈프래그먼트를 선언한다.

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

        fragment = new HomeFragment(); //초기화해준다.

        getSupportFragmentManager().beginTransaction().replace(R.id.container, fragment).commit();
        ////프래그먼트를 여러개 사용할 수 있으므로, 비긴트랜잭션을 사용한다.
        //간단히 프래그먼트에서 사용되는 트랜잭션이란 어떤 대상에 대해 추가, 제거, 변경등의 작업들이
        //발생하는 것을 묶어서 이야기 하는 것이다.
        // 끝에 커밋을 해줘야지 작동한다.
    }
}

(값을 안넣으니 초기화가 아닌 객체화가 어울릴 것 같네요!)

우선 편의를 위해서 아래 깔릴 프래그먼트를 하나만 만들겠다.

 

그전에 메인 액티비티에서 HomeFragment를 지정해주고, 트랜잭션을 해주겠다.

 

<------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">

    <FrameLayout
        android:id="@+id/container"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">
    </FrameLayout>

</androidx.constraintlayout.widget.ConstraintLayout>

 

activity_main.xml은 프래그먼트를 화면에 출력할 수 있게 하기 위해,

 

프레임 레이아웃을 넣어주고, id값을 container로 지정해준다.

 

그리고 밑에 깔릴 프래그먼트이다.

 

<------HomeFragment.java------>

public class HomeFragment extends Fragment {

    private ViewGroup viewGroup; //뷰그룹 객체 선언

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        viewGroup = (ViewGroup) inflater.inflate(R.layout.home_fragment, container, false);

        //뷰그룹 인플레이션 한 뒤 viewGroup에 리턴해 줍니다.



        //그리고 이 프래그먼트에 메서드를 하나 임의로 호출합니다.
        setInit(); //뷰페이저2 실행 메서드

        return viewGroup;
    }

}

 이렇게 인플레이션을 하고, viewGroup으로 다시 리턴해준다.

 

인플레이션 간단 설명 : xml에 있는 여러 코드들이 메모리에 올라가고, 그 올라간 메모리에서

 

UI들이 출력되는 과정을 말한다.

 

한마디로 여기서 프래그먼트를 프레임 레이아웃(container)에 출력해주는

 

과정이라고 할 수 있다(인플레이션)

 

그러고 나서,

 

setInit():메서드를 임의로 호출한다.(뷰 페이저 실행 메서드이다.) 

 

그리고 밑에

 

public class HomeFragment extends Fragment {

   중략....

        return viewGroup;
    }

    private void setInit() { //뷰페이저2 실행 메서드

        /* setup infinity scroll viewpager */
        ViewPager2 viewPageSetUp = viewGroup.findViewById(R.id.viewPager2); //여기서 뷰페이저를 참조한다.
        FragPagerAdapter SetupPagerAdapter = new FragPagerAdapter(getActivity()); //프래그먼트에서는 getActivity로 참조하고, 액티비티에서는 this를 사용해주세요.
        viewPageSetUp.setAdapter(SetupPagerAdapter); //FragPagerAdapter를 파라머티로 받고 ViewPager2에 전달 받는다.
        viewPageSetUp.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL); //방향은 가로로
        viewPageSetUp.setOffscreenPageLimit(3); //페이지 한계 지정 갯수
        // 무제한 스크롤 처럼 보이기 위해서는 0페이지 부터가 아니라 1000페이지 부터 시작해서 좌측으로 이동할 경우 999페이지로 이동하여 무제한 처럼 스크롤 되는 것 처럼 표현하기 위함.
        viewPageSetUp.setCurrentItem(1000);

        final float pageMargin = (float) getResources().getDimensionPixelOffset(R.dimen.pageMargin); //페이지끼리 간격
        final float pageOffset = (float) getResources().getDimensionPixelOffset(R.dimen.offset); //페이지 보이는 정도

        viewPageSetUp.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);

            }
        });
        viewPageSetUp.setPageTransformer(new ViewPager2.PageTransformer() {
            @Override
            public void transformPage(@NonNull View page, float position) {
                float offset = position * - (2 * pageOffset + pageMargin);
                if(-1 > position) {
                    page.setTranslationX(-offset);
                } else if(1 >= position) {
                    float scaleFactor = Math.max(0.7f, 1 - Math.abs(position - 0.14285715f));
                    page.setTranslationX(offset);
                    page.setScaleY(scaleFactor);
                    page.setAlpha(scaleFactor);
                } else {
                    page.setAlpha(0f);
                    page.setTranslationX(offset);
                }
            }
        });

    }



}

뷰 페이저를 실행하는 코드를 먼저 작성하겠다.

 

프래그먼트 위에 뷰 페이저가 있고, 그 뷰페이저 안에서 여러 프래그먼트가

 

올라가게 하려는 것이다.

 

뷰 페이저 안에서 프래그먼트를 움직이게 하려면,

 

어뎁터를 사용해야 한다.

 

보통 리사이클 러뷰에서도 많이 사용되지만,

 

뷰 페이저에서도 많이 사용된다.

 

뷰 페이저 1 에서는 FragmentPagerAdapter가 많이 사용되었다고 하지만,

 

뷰 페이저에서는 FragmentStateAdapter로 사용한다고 개발자 홈페이지 명시되어 있다.

 

(사실 이 부분이 많이 헷갈렸다.

 

그전에 홈 메인 프래그먼트(밑에 깔릴 프래그먼트)에  xml에 뷰 페이저를 지정해주자.

 

<------home_fragment.xml------>

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewPager2"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>


</androidx.constraintlayout.widget.ConstraintLayout>

 

아이디를 viewPager2로 지정해준다.

 

혹시나, 뷰 페이저가 없다고 나오시는 분들은 

build:gradle:app에 

 

 dependencies {
        implementation "androidx.viewpager2:viewpager2:1.0.0"
    }
    

 

이 라이브러리를 추가해주시면 되겠다.

 

그러고 나서 이 프래그먼트 위에 뷰 페이저에 담길 프래그먼트 3개를 만들어 보자.

 

이 부분은 설명 없이 바로 올리겠다.

 

<------Frag1~3------>

 

public class Frag1 extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_1, container, false );

        setInit(view);

        return view;

    }

    private void setInit(View _view) {
        TextView textView = _view.findViewById(R.id.textView);
    }
}

 

public class Frag2 extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_2, container, false );

        setInit(view);

        return view;

    }

    private void setInit(View _view) {
        TextView textView = _view.findViewById(R.id.textView); //여기에 각자 텍스트 뷰 참조 가능
    }
}
public class Frag3 extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_3, container, false );

        setInit(view);

        return view;

    }

    private void setInit(View _view) {
        TextView textView = _view.findViewById(R.id.textView);
    }
}

 

그러고 xml 파일들이다.

 

<------fragment_1~3------>

 

<?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">

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="나는 자팍이야"
        android:textColor="#000000"
        android:textSize="24sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

 

<?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">

    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="헤헿ㅎ"
        android:textColor="#000000"
        android:textSize="24sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

 

<?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">

    <TextView
        android:id="@+id/textView4"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="헤헿ㅎ"
        android:textColor="#000000"
        android:textSize="24sp"
        android:textStyle="bold"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />


</androidx.constraintlayout.widget.ConstraintLayout>

 

여기까지 오느라 고생했다...

 

나와 같은 초보자는 머리가 아플 것이다..ㅠㅠ

 

그리고 나서 마지막으로 뷰 페이저와 프래그먼트들이 잘 세팅(구현)이 될 수 있게,

 

어뎁터! 를 만들어 줘야 한다.

 

이게 아까 말한 FragmentStateAdapter이다.

 

<------FragPagerAdapter------>

 

public class FragPagerAdapter extends FragmentStateAdapter { //뷰페이저2에서는 FragmentStateAdapter를 사용한다.
    // Real Fragment Total Count
    private final int mSetItemCount = 3; //프래그먼트 갯수 지정

    public FragPagerAdapter(@NonNull FragmentActivity fragmentActivity) {
        super(fragmentActivity);
    }


    @NonNull
    @Override
    public Fragment createFragment(int position) {
        int iViewIdx = getRealPosition(position);
        switch( iViewIdx ) {
            case 0    : { return new Frag1(); } //프래그먼트 순서에 맞게 넣어주세요.
            case 1    : { return new Frag2(); }
            case 2    : { return new Frag3(); }
//            case 3    : { return new Frag4(); }
//            case 4    : { return new Frag5(); }
//            case 5    : { return new Frag6(); }
            default   : { return new Frag1(); } //기본으로 나와있는 프래그먼트
        }

    }

    public int getRealPosition(int _iPosition){
        return _iPosition % mSetItemCount;
    }

    @Override
    public long getItemId(int position) {
        return super.getItemId(position);
    }

    @Override
    public int getItemCount() {
        return Integer.MAX_VALUE;
    }
}

 

이렇게 어뎁터까지 연결해주면 끝이 난다.

 

어디 한 번 결과물을 보자.

 

 

 

 

이렇게 프래그먼트 위에 올린 뷰 페이저와 

뷰페이저와 그 위에 프래그먼트를 연결해주는 어뎁터까지 모두 완료했다.

 

오늘 이 과정을 밟으면서, 많은 시간을 보냈지만,

 

이렇게 정리하게 되어 기쁘다.

 

마지막으로 뷰 페이저의 추가하면 더 유용한 기능을 소개하겠다.

 

android:clipChildren="false" // 그림의 영역을 넘어가게 할 수 있다. true, false로 기능이 나뉜다.
android:clipToPadding="false" //프래그먼트를 넘길 때, 패딩 여백을 주냐 안주냐로 이 또한  true, false로 나뉜다.

 

필요할 때 쓰면 좋겠다.

 

오늘도 재밌는 코딩 공부를 끝냈고,

 

이제는 잘 시간이다.

 

오늘의 첫 TIL 괜찮은 시작이다.

 

꾸준하길... ㄷ

 

혹시나 잘 모르신 분들을 위해 깃 헙에 올려두었으니, 유용하게 활용하시길 바랍니다.

 

홍드로이드님의 코드에서 많이 활용하였음을 밝힙니다!

 

https://github.com/qjsqjsaos/TIL__Viewpager2_on_fragment_with_FragmentStateAdapter

 

qjsqjsaos/TIL__Viewpager2_on_fragment_with_FragmentStateAdapter

Today I learned about ViewPager on Fragment with FragmentStateAdapter. - qjsqjsaos/TIL__Viewpager2_on_fragment_with_FragmentStateAdapter

github.com

 

728x90
반응형

공유하기

facebook twitter kakaoTalk kakaostory naver band
loading