안드로이드 앱 개발 공부/자꾸 까먹어서 적어두는 구현방법

[Android] viewpager 간단하게 indicator 만들기 JAVA

플래시🦥 2024. 10. 1.
반응형

 

viewpager를 만들어서 이미지나 어떠한 뷰를 보여줄 때 해당 뷰가 몇 번째인지 보여주는 indicator가 있다.

주로  잘만들어져 있는 외부 라이브러리를 가져와서 사용할 수 있지만 아주 간단하게 구현할 수 있는 방법이 있다. 

 

아래에서 설명할 이 방법은 처음 커스텀 뷰를 만들 때 접근해본 방식이다. 

처음에 커스텀 뷰를 만들어야 할 때 어떤방식으로 코드를 짜야하는지 막막하기만 했는데, 온갖 방법들을 서치하고 알아보면서 여러 가지 코드를 작성해 보고 수정해 본 결과 간단하게 만들 수 있는 코드를 짤 수 있었다. 

어떤 식으로 코드를 구성해야 할 지 초보자에게 조금은 도움이 될 수 있을 것이다. 

 

 

( 이 글에서 뷰페이저는 만드는 방법은 스킵하겠다. )

indicator를 만들 수 있는 간단한 방법을 글로 설명하면 아래와 같다.

 

1.indicator에 사용할 drawable을 만든다. 

2. indicaotr뷰의 크기를 가져온다.

3. GradientDrawable의 사이즈를 가져온 뷰의 크기로 설정해 준다. 

4. padding을 설정해준다.

5. addView 한다. 

6. 뷰페이저를 넘길 때마다 호출하여 인디케이터를 업데이트시켜준다. 

 

 

1. indicator에 사용할 ImageDrawable  만들기 

이것도 간단하다. 이 방법에서는 간단하게 원형으로 만들거라 drawble에 아래 xml 코드를 원하는 색깔별 2개를 만들어 주면 된다. 아래 코드는 파란색 원형 모양을 만드는 코드이고, 난 파란원, 회색원 총 2개 만들었다. 

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

    <solid
        android:color="@color/color_006bdc"/>

</shape>

 

 

2. xml 코드에서 추가해 줄 class파일 만들기 

보통 우리가 화면에 뷰를 추가해 줄 때 <ConstraintLayout></> 이런 식으로 xml코드에 추가해서 사용한다. 이때 우리가 사용할 뷰를 만들어 주기 위해 class 파일을 원하는 이름으로 만들어 주면 된다. 

public class CircleIndicator extends LinearLayout {
	private Context context;

    public CircleIndicator(Context context) {
        super(context);
        this.context = context;
    }

    public CircleIndicator(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
         this.context = context;
    }
  
}

기본 틀은 이렇게 해주면 된다. 

원하는 이름의 클래스 만들어 주고 LinearLayout을 상속해 온다. 

여기서 좀 더 레벨업을 하려면 위 생성자들도 사용을 하는데 우선은 아주 간단하게 흐름을 파악할 수 있는 코드니까 여기는 가볍게 생략한다. context를 나중에 사용하긴 한다. 

 

그리고 여기에 public 함수를 하나 만들어 줄 건데, 이 함수에서 위에서 언급한 2~5번의 작업이 모두 들어간다. 

 

public void createDot(int cnt, int defCircle, int selectCircle, int posi, int width, int height) {
}

 

createDot함수에 매개변수에 필요한 값들을 모두 받아올 수 있도록 했다. 

cnt는 아이템의 개수이다. 즉 뷰페이저의 개수

defCircle과 selectCircle은 위에서 만든 drawableImage이다. 선택되었을 때와 선택되지 않았을 때 이미지를 바꾸어 주어야 하기 때문에 받아온다. 

posi는 시작 아이템의 index를 받아온다. 뷰페이저의 시작 화면이 무조건 0이 아닐 수 있기 때문에 받아왔다. 

그리고 width와 height는 인디케이터의 뷰 크기이다. 

화면에 보여줄 뷰를 추가할 때 사이즈를 정해야 하기 때문에 받아왔다.

 

처음 해야 할 일은 필요한 변수 생성이다. 

private Drawable mDefaultCircle;
private Drawable mSelectCircle;
private ArrayList<ImageView> dots = new ArrayList<>();

private float temp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, getResources().getDisplayMetrics());  //dp to px

 

변수의 활용은 아래 코드를 진행하며 확인하면 된다. 

 

그다음으로 할 일은 추가하기 위한 drawable객체 생성이다. 

GradientDrawable draw = (GradientDrawable) ContextCompat.getDrawable(this.getContext(), defCircle);
assert draw != null;
draw.setSize(height, height);
GradientDrawable draw2 = (GradientDrawable) ContextCompat.getDrawable(this.getContext(), selectCircle);
assert draw2 != null;
draw2.setSize(height, height);

mDefaultCircle = draw;
mSelectCircle = draw2;

 

GradientDrawable을 선택된 거 아닌 거 두 개를 만들어서 사이즈를 설정해 주는 것이다. 사이즈는 가로세로 동일하게 뷰의 세로 크기에 맞춰서 설정했다. 

 

그리고 추가될 원들의 padding을 설정해 줬다. 

뷰에 그릴 때 가장 왼쪽의 인디케이터에는 오른쪽에만 패딩이 필요하고 중간에 있는 것들은 양쪽, 가장 마지막은 오른쪽에만 추가하도록 해봤다. 그리고 addView를 바로바로 해줬다. 

for (int i = 0; i < cnt; i++) {
    ImageView imageView = new ImageView(context);

    if(i==0){
        imageView.setPadding(0, 0, (int) temp, 0);//첫 circle은 오른쪽 padding만
    }else if(i==cnt-1){
        imageView.setPadding((int) temp, 0, 0, 0);//마지막 circle은 왼쪽 padding만
    }else{
        imageView.setPadding((int) temp, 0, (int) temp, 0);
    }

    dots.add(imageView);
    this.addView(dots.get(i));

}

 

 

거의 다 왔다. 

그다음 할 일은 뷰를 업데이트해 줄 함수를 만들어 주는 것이다. 

 

해당 position에 따라 인디케이터의 색상을 바꿔줘야 하기 때문에 매개변수로 들어온 위치의 인디케이터는 파랑, 아닌 인디케이터느 회색으로 보이도록 업데이트해주는 함수이다. 

public void selectDot(int posi) {
    for (int i = 0; i < dots.size(); i++) {
        if (i == posi) {
            dots.get(i).setImageDrawable(mSelectCircle);
        } else {
            dots.get(i).setImageDrawable(mDefaultCircle);
        }
    }
}

 

 

이러면 간단하게 뷰를 구성해 주는 클래스파일은 작성이 끝났다. 

 

이걸 가지고 xml에 추가해 주고, 아래 예시는 constraintlayout에 가이드라인에 맞춰서 뷰의 위치와 크기를 잡아 주었다. 

<패키지.CircleIndicator
    android:id="@+id/indicatorLay"
    android:layout_width="0dp"
    android:layout_height="0dp"
    android:gravity="center"
    app:layout_constraintBottom_toTopOf="@+id/guidelineindicatorBottom"
    app:layout_constraintEnd_toStartOf="@+id/guidelineindicatorEnd"
    app:layout_constraintStart_toStartOf="@+id/guidelineindicatorStart"
    app:layout_constraintTop_toTopOf="@+id/guidelineindicatorTop" />

 

그리고 activity에서 아래 코드를 추가해서 뷰를 생성해 준다. 

binding.indicatorLay.createDotPanel(viewPagerItem.size(),R.drawable.indicator_dot_off,R.drawable.indicator_dot_on,0,binding.indicatorLay.getWidth(),binding.indicatorLay.getHeight());   //CircleIndicator

위 예시 코드의 viewPagerItem는 여기서는 보여주지 않은 코드에서 뷰페이저에 들어갈 아이템의 정보를 담은 arraylist이다. 

그리고 맨 처음 만들어둔 drawableImage 두 개(선택했을 때와 안 했을 때)를 넘겨주고, 0은 처음 선택되어 있을 인디케이터의 position이고 그다음은 indicator의 width와 height를 넘겨주었다. 그러면 기본 세팅은 끝난다. 

그러면 마지막으로 할 일은 뷰페이저의 페이지가 선택되었을 때 만들어둔 selectDot함수를 호출하면 끝난다. 

 binding.viewPager.registerOnPageChangeCallback(new ViewPager2.OnPageChangeCallback() {
            @Override
            public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
                super.onPageScrolled(position, positionOffset, positionOffsetPixels);
            }

            @Override
            public void onPageSelected(int position) {
                super.onPageSelected(position);

                binding.indicatorLay.selectDot(position);   //CircleIndicator

            }

            @Override
            public void onPageScrollStateChanged(int state) {
                super.onPageScrollStateChanged(state);
            }
        });

 

 

그러면 이렇게 간단한 indicator를 만들 수 있다.

완성본

 

 

 

CircleIndicator 전체 코드 


public class CircleIndicator extends LinearLayout {

    private Context context;

    private Drawable mDefaultCircle;
    private Drawable mSelectCircle;
    private ArrayList<ImageView> dots = new ArrayList<>();

    private float temp = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5f, getResources().getDisplayMetrics());  //dp to px


    public CircleIndicator2(Context context) {
        super(context);
        this.context = context;
    }

    public CircleIndicator2(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        this.context = context;
    }

    public void createDotPanel(int cnt, int defCircle, int selectCircle, int posi, int width, int height) {
        this.removeAllViews();
        width = height*2;
        float newpadding = (float) (height/2);  //circle height 기준 여백 생성

        temp = newpadding/2;

        GradientDrawable draw = (GradientDrawable) ContextCompat.getDrawable(this.getContext(), defCircle);
        assert draw != null;
        draw.setSize(height, height);
        GradientDrawable draw2 = (GradientDrawable) ContextCompat.getDrawable(this.getContext(), selectCircle);
        assert draw2 != null;
        draw2.setSize(height, height);

        mDefaultCircle = draw;
        mSelectCircle = draw2;

        for (int i = 0; i < cnt; i++) {
            ImageView imageView = new ImageView(context);

            if(i==0){
                imageView.setPadding(0, 0, (int) temp, 0);//첫 circle은 오른쪽 padding만
            }else if(i==cnt-1){
                imageView.setPadding((int) temp, 0, 0, 0);//마지막 circle은 왼쪽 padding만
            }else{
                imageView.setPadding((int) temp, 0, (int) temp, 0);
            }

            dots.add(imageView);
            this.addView(dots.get(i));

        }
        selectDot(posi);
    }

    public void selectDot(int posi) {
        for (int i = 0; i < dots.size(); i++) {
            if (i == posi) {
                dots.get(i).setImageDrawable(mSelectCircle);
            } else {
                dots.get(i).setImageDrawable(mDefaultCircle);
            }
        }
    }


}

 

 

 

이 코드는 어떤 식으로 코드를 짤 수 있을지 생각해 보고 응용해서 더 업데이트된 뷰를 만들 수 있는 입문 판이라고 생각하면 된다

728x90
반응형

댓글