본문 바로가기
프로젝트

[안드로이드 스튜디오 프로젝트] 자기소개앱_5 (ViewPager 적용)

by sojung118 2024. 9. 12.

[ 실행 화면 ]

 

 

 


 

이번 구현 목록은 키워드 중 자격증 키워드를 눌렀을때 나오는 xml창 구현하기!

아마 이전 포스팅까지는 자격증이 아니었던 것 같은데 쓸 내용을 생각하다보니 자격증이 생각이나서..

한개의 키워드를 자격증 키워드로 변경시켰다.

해당 xml에서 오른쪽 또는 왼쪽으로 화면을 슬라이드 했을 때 사진들이 차례로 나왔으면 좋겠다고 생각했고

인터넷 서치 결과 ViewPager을 적용하면 화면 구현이 가능하다는 걸 알게됐다.

참고 사이트는 아래 사이트이다!

 

https://haruple.tistory.com/163

 

안드로이드 스튜디오 ViewPager2를 활용한 가로 슬라이더

안녕하세요, 하루플 입니다. 위 이미지와 같은 가로 슬라이더 앱 만지면서 많이 보셨죠! 단순히 RecyclerView를 쓰면 원하는 이미지에서 멈추지 않습니다. 그냥 ListView를 가로로 돌린 것 뿐이죠. 그

haruple.tistory.com

 

위 사이트에서 구현하는 방법을 확인하고 xml 내용을 바꿔서 화면 구성을 해줬당 ㅎㅎ

 


 

 

본격적으로 코드를 작성하기 전에, ViewPager2와 CircleIndicator이 build.gradle.kts에 있는지 확인해야한다.

나의 경우 둘 다 없었어서, libs.versions.toml에 다음과 같이 추가해준 후, build.gradle.kts 파일도 수정해줬다.

 

 

[ libs.versions.toml ]

[versions]
agp = "8.6.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
appcompat = "1.7.0"
material = "1.12.0"
activity = "1.9.2"
constraintlayout = "2.1.4"
viewpager2Version = "1.1.0"
circleindicatorVersion = "2.1.6"

[libraries]
junit = { group = "junit", name = "junit", version.ref = "junit" }
ext-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" }
espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" }
appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
material = { group = "com.google.android.material", name = "material", version.ref = "material" }
activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version.ref = "constraintlayout" }
viewpager2 = { group = "androidx.viewpager2", name = "viewpager2", version.ref = "viewpager2Version" }
circleindicator = { group = "me.relex", name = "circleindicator", version.ref = "circleindicatorVersion" }



[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }

 

 

내가 추가해 준 부분은 [ versions ] 부분에서 viewPager2Version과 circleindicatorVersion을 추가했고

[ libraries ] 에서 viewPager2와 circleindicator도 위와 같이 추가해줬다.

이렇게 추가해 준 후 build.gradle.kts 파일로 넘어가자! 

 

 

 

[ build.gradle.kts ]

plugins {
    alias(libs.plugins.android.application)
}

android {
    namespace = "com.example.introducemyself"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.introducemyself"
        minSdk = 27
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
}


dependencies {

    implementation(libs.appcompat)
    implementation(libs.material)
    implementation(libs.activity)
    implementation(libs.constraintlayout)
    implementation(libs.viewpager2) // ViewPager2 추가
    implementation(libs.circleindicator) //circleindicator 추가
    testImplementation(libs.junit)
    androidTestImplementation(libs.ext.junit)
    androidTestImplementation(libs.espresso.core)
}

 

implementation (libs.viewPager2)

implementation (libs.circleindicator) 을 추가해주고 상단에 뜨는 sync now를 눌러주면 된다!

 


 

* 달라진 파일들만 업로드

 

1. activity_license.xml (새로 생성)

<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/license_layout"
    android:background="#F2EDED"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_margin="10dp"
            android:orientation="horizontal"
            android:layout_width="match_parent"
            android:layout_height="48dp">

            <TextView
                android:fontFamily="@font/font"
                android:textSize="25sp"
                android:gravity="center"
                android:layout_width="0dp"
                android:layout_weight="8"
                android:layout_height="match_parent"
                android:text="※ 자격증 목록 ※"/>


            <Button
                android:textStyle="bold"
                android:text="←"
                android:backgroundTint="#BA8E71"
                android:layout_weight="2"
                android:id="@+id/btn_home3"
                android:layout_width="0dp"
                android:layout_height="match_parent" />

        </LinearLayout>

        <FrameLayout
            android:layout_gravity="center"
            android:layout_width="400dp"
            android:layout_height="500dp">

            <androidx.viewpager2.widget.ViewPager2
                android:id="@+id/viewpager"
                android:layout_gravity="center"
                android:clipToPadding="false"
                android:clipChildren="false"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:scaleType="fitXY"
                android:layout_marginBottom="10dp"/>

            <me.relex.circleindicator.CircleIndicator3
                android:id="@+id/indicator"
                android:layout_width="match_parent"
                android:layout_height="48dp"
                android:layout_marginBottom="10dp"
                android:layout_gravity="bottom"/>

        </FrameLayout>






    </LinearLayout>

</androidx.drawerlayout.widget.DrawerLayout>

 

 

여기에서 기존 xml들과 달리 ViewPager를 사용하기 위해서 추가한 부분은

<FrameLayout
    android:layout_gravity="center"
    android:layout_width="400dp"
    android:layout_height="500dp">

    <androidx.viewpager2.widget.ViewPager2
        android:id="@+id/viewpager"
        android:layout_gravity="center"
        android:clipToPadding="false"
        android:clipChildren="false"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:scaleType="fitXY"
        android:layout_marginBottom="10dp"/>

    <me.relex.circleindicator.CircleIndicator3
        android:id="@+id/indicator"
        android:layout_width="match_parent"
        android:layout_height="48dp"
        android:layout_marginBottom="10dp"
        android:layout_gravity="bottom"/>

</FrameLayout>

 

 

이 부분이다! 참고로 ViewPager를 쓰기 위해서는 이후에 만들 frame_xml과 FrameLayout 크기가 동일해야한다.

현재 여기서는 FrameLayout을 width 400dp, height 500dp로 주었다.

CircleIndicator은 몇개의 창들 중 내가 몇번째 사진을 보고 있는지 확인해주는 느낌인데,

흰색으로 설정해놔서 내 화면에서는 잘 안보이는듯... 참고하기~

 

 

 

2. MainActivity.java (자격증 버튼 클릭 시 LicenseActivity.java로 넘어가기)

package com.example.introducemyself;

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

import androidx.activity.EdgeToEdge;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.drawerlayout.widget.DrawerLayout;

public class MainActivity extends AppCompatActivity {

    Button btn_study;
    Button btn_language;
    Button btn_bowling;
    TextView me_bowling;
    TextView me_study;
    Button btn_drawer;
    TextView me_programming;
    //DrawerLayout과 View 2개를 만들어준다.

    private DrawerLayout drawerLayout;
    private View drawerView;


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


        drawerLayout = findViewById(R.id.drawer_layout);
        drawerView = findViewById(R.id.drawer);
        btn_bowling=findViewById(R.id.btn_bowling);
        btn_bowling.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Intent 객체 생성
                Intent intent = new Intent(MainActivity.this, BowlingActivity.class);
                startActivity(intent);
            }
        });

        btn_language=findViewById(R.id.btn_language);
        btn_language.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Intent 객체 생성
                Intent intent = new Intent(MainActivity.this, LanguageActivity.class);
                startActivity(intent);
            }
        });

        btn_study=findViewById(R.id.btn_study);
        btn_study.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Intent 객체 생성
                Intent intent = new Intent(MainActivity.this, LicenseActivity.class);
                startActivity(intent);
            }
        });


        me_bowling=findViewById(R.id.me_bowling);
        me_bowling.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Intent 객체 생성
                Intent intent = new Intent(MainActivity.this, LanguageActivity.class);
                startActivity(intent);
            }
        });

        me_study=findViewById(R.id.me_study);
        me_study.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Intent 객체 생성
                Intent intent = new Intent(MainActivity.this, LicenseActivity.class);
                startActivity(intent);
            }
        });

        me_programming=findViewById(R.id.me_programming);
        me_programming.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Intent 객체 생성
                Intent intent = new Intent(MainActivity.this, BowlingActivity.class);
                startActivity(intent);
            }
        });

        btn_drawer = findViewById(R.id.btn_drawer);
        btn_drawer.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                drawerLayout.openDrawer(drawerView);
            }
        });

        drawerLayout.setDrawerListener(listener);
        drawerView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                return true;
            }
        });




    }

    //drawerlayout을 왼쪽,오른쪽으로 슬라이드했을떄 이곳에서 상태값을 받아옴
    //추가 기능을 해주고싶을때 여기서 만들기
    DrawerLayout.DrawerListener listener = new DrawerLayout.DrawerListener() {
        @Override
        public void onDrawerSlide(@NonNull View drawerView, float slideOffset) {

        }

        //오픈이 완료됐을때
        @Override
        public void onDrawerOpened(@NonNull View drawerView) {

        }

        //슬라이드 닫혔을때
        @Override
        public void onDrawerClosed(@NonNull View drawerView) {

        }

        //상태가 change 됐을때
        @Override
        public void onDrawerStateChanged(int newState) {

        }
    };



}

 

여기는 특별히 달라진 부분이 없다.

 

 

 

3. LicenseActivity.java

package com.example.introducemyself;

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

import androidx.activity.EdgeToEdge;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.Insets;
import androidx.core.view.ViewCompat;
import androidx.core.view.WindowInsetsCompat;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import androidx.viewpager2.widget.ViewPager2;

import me.relex.circleindicator.CircleIndicator3;

public class LicenseActivity extends AppCompatActivity {


    private ViewPager2 mPager;
    private FragmentStateAdapter pagerAdapter;
    private int num_page = 5;
    private CircleIndicator3 mIndicator;
    Button btn_home3;

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

        btn_home3=findViewById(R.id.btn_home3);
        btn_home3.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //Intent 객체 생성
                Intent intent = new Intent(LicenseActivity.this, MainActivity.class);
                startActivity(intent);
            }
        });

        /**
         * 가로 슬라이드 뷰 Fragment
         */

        //ViewPager2
        mPager = findViewById(R.id.viewpager);
        //Adapter
        pagerAdapter = new MyAdapter(this, num_page);
        mPager.setAdapter(pagerAdapter);
        //Indicator
        mIndicator = findViewById(R.id.indicator);
        mIndicator.setViewPager(mPager);
        mIndicator.createIndicators(num_page,0);
        //ViewPager Setting
        mPager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL);

        /**
         * 이 부분 조정하여 처음 시작하는 이미지 설정.
         * 2000장 생성하였으니 현재위치 1002로 설정하여
         * 좌 우로 슬라이딩 할 수 있게 함. 거의 무한대로
         */

        mPager.setCurrentItem(1000); //시작 지점
        mPager.setOffscreenPageLimit(5); //최대 이미지 수

        mPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                super.onPageScrolled(position, positionOffset, positionOffsetPixels);
                if (positionOffsetPixels == 0) {
                    mPager.setCurrentItem(position);
                }
            }

            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);
                mIndicator.animatePageSelected(position%num_page);
            }
        });


    }
}

 

최대 이미지 수나 시작 지점을 내가 몇개의 화면을 돌릴 것인지에 따라 수정시켜줘야한다.

 

 

 

4. MyAdapter.java

package com.example.introducemyself;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;

public class MyAdapter extends FragmentStateAdapter {

    public int mCount;

    public MyAdapter(FragmentActivity fa, int count) {
        super(fa);
        mCount = count;
    }

    @NonNull
    @Override
    public Fragment createFragment(int position) {
        int index = getRealPosition(position);

        if(index==0) return new Fragment_1();
        else if(index==1) return new Fragment_2();
        else if(index==2) return new Fragment_3();
        else if(index==3) return new Fragment_4();
        else return new Fragment_5();
    }

    @Override
    public int getItemCount() {
        return 2000;
    }

    public int getRealPosition(int position) { return position % mCount; }

}

 

 

여기서도 몇개의 화면을 돌리느냐에 따라 if문 있는 부분을 수정해주면 된다.

 

 

 

5. frame_?.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:orientation="vertical">

    <LinearLayout
        android:orientation="vertical"
        android:layout_width="400dp"
        android:layout_height="500dp">

        <TextView
            android:fontFamily="@font/font"
            android:textSize="25sp"
            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="40dp"
            android:text="1. 컴퓨터활용능력 1급"/>
        <ImageView
            android:src="@mipmap/license1"
            android:text="1"
            android:scaleType="centerCrop"
            android:layout_width="400dp"
            android:layout_height="470dp"
            android:textSize="50dp"
            android:gravity="center"
            android:textColor="#000000"
            android:background="#FFCDD2"
            />


    </LinearLayout>


</LinearLayout>

 

내가 여러개의 화면으로 나타낼 화면들을 각각의 frame xml로 구성해야한다.

나는 화면을 총 5개 구성할거니까 frame_1.xml 부터 frame_5.xml까지 총 5개의 xml을 만들어줬다.

여기서 크기는 위 activity_license.xml에서 쓴 FrameLayout 크기와 동일해야한다.

그래서 똑같이 width 400dp, height 500dp로 준 것을 볼 수 있다!!

 

 

 

6. Fragment_?.java

package com.example.introducemyself;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;

import androidx.fragment.app.Fragment;

public class Fragment_5 extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        ViewGroup rootView = (ViewGroup) inflater.inflate(
                R.layout.frame_5, container, false);

        return rootView;
    }
}

 

 

나의 경우 화면을 5개로 구성했기 때문에 Fragment_1.java~Fragment_5.java 까지 존재한다.

Fragment_? 부분을 화면에 맞는 숫자로 변경시키고,

각 파일마다 R.layout.frame_? 부분에서 frame_1~frame_5까지 설정해주면 된다. 

 

 

 

 

그리고 java 파일이 추가되었으니까 마지막으로 수정해줄 AndroidManifest.xml

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

    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.Introducemyself"
        tools:targetApi="31">

        <activity
            android:name=".BowlingActivity"
            android:exported="true">

        </activity>

        <activity
            android:name=".LanguageActivity"
            android:exported="true">

        </activity>

        <activity
            android:name=".Fragment_1"
            android:exported="true">

        </activity>
        <activity
            android:name=".Fragment_2"
            android:exported="true">

        </activity>
        <activity
            android:name=".Fragment_3"
            android:exported="true">

        </activity>
        <activity
            android:name=".Fragment_4"
            android:exported="true">

        </activity>
        <activity
            android:name=".Fragment_5"
            android:exported="true">

        </activity>

        <activity
            android:name=".LicenseActivity"
            android:exported="true">

        </activity>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>