본문 바로가기

Android Studio

안드로이드 카드 뒤집기 - Android Flip Animation

반응형

위 이미지처럼 클릭했을 때 뒤면이 앞면으로 회전하는 걸 구현해 보려 합니다.

사용버전 - Android Studio 3.5.3

카드 이미지는 구글에서 받으시기 바랍니다.

앞면으로 사용될 이미지 4장과 뒷면 이미지 한 장이 필요하며 이미지 파일의 이름은 

소스 파일의 파일명과 같이 맞혀 주시면 됩니다.

 

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"
    android:background="@color/card_view_background"
    tools:context=".MainActivity">

    <include
        android:id="@+id/include"
        layout="@layout/card_view"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.0"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <Button
        android:id="@+id/re_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="다시하기"
        android:layout_marginTop="10dp"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/include" />

</androidx.constraintlayout.widget.ConstraintLayout>

card_view.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:paddingTop="10dp"
    android:paddingBottom="10dp"
    tools:context=".MainActivity">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/image_1"
            android:layout_width="120dp"
            android:layout_height="200dp"
            android:layout_marginEnd="4dp"
            android:gravity="center"
            android:textSize="26sp"
            android:textStyle="bold"
            android:textColor="@color/text_transparent"
            android:background="@drawable/card_back" />

        <TextView
            android:id="@+id/image_2"
            android:layout_width="120dp"
            android:layout_height="200dp"
            android:layout_marginEnd="4dp"
            android:gravity="center"
            android:textSize="26sp"
            android:textStyle="bold"
            android:textColor="@color/text_transparent"
            android:background="@drawable/card_back"  />

    </LinearLayout>

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:layout_marginTop="10dp"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/image_3"
            android:layout_width="120dp"
            android:layout_height="200dp"
            android:layout_marginEnd="4dp"
            android:gravity="center"
            android:textSize="26sp"
            android:textStyle="bold"
            android:textColor="@color/text_transparent"
            android:background="@drawable/card_back"  />

        <TextView
            android:id="@+id/image_4"
            android:layout_width="120dp"
            android:layout_height="200dp"
            android:layout_marginEnd="4dp"
            android:gravity="center"
            android:textSize="26sp"
            android:textStyle="bold"
            android:textColor="@color/text_transparent"
            android:background="@drawable/card_back" />

    </LinearLayout>

</LinearLayout>

values/colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="colorPrimary">#008577</color>
    <color name="colorPrimaryDark">#00574B</color>
    <color name="colorAccent">#D81B60</color>

    <color name="text_color">#000000</color>
    <color name="text_transparent">#00000000</color>

    <color name="card_view_background">#D1E9E2</color>
</resources>

카드 이미지를 ImageView대신 TextView를 사용 했는데 카드에 숫자를 표시하기 위해 TextView로

사용했습니다.

카드의 아이디는 image_1 ~ 4번까지 사용했는데 setOnClickListener를 한 번에 처리하기 위해 뒤에 번호를

붙여 처리했습니다.

xml은 별도의 설명 없이도 충분히 알 수 있는 내용이라 생략하겠습니다.

 

 

화면은 이런 모양입니다.

 

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private TextView[] cardText = new TextView[4];
    private TextView clickTextView;
    private boolean[] viewClickCheck = new boolean[4];

    private List<Integer> listNum = new ArrayList<>();

    int[] imagesources = {R.drawable.aa, R.drawable.ab, R.drawable.ac, R.drawable.ad};

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

        for (int i = 1; i < 5; i++) {
            listNum.add(i);
        }
        Collections.shuffle(listNum);

        for (int i = 0; i < cardText.length; i++) {
            String viewName = "image_" + (i + 1);
            cardText[i] = findViewById(getResources().getIdentifier(viewName, "id", getPackageName()));
            cardText[i].setText(String.valueOf(listNum.get(i)));
            viewClickCheck[i] = false;
        }

        textViewOnClickListener();

        findViewById(R.id.re_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                reViewInit();
            }
        });
    }

    private void textViewOnClickListener() {
        for (TextView v : cardText) {
            v.setOnClickListener(cardClick(v));
        }
    }

    private void reViewInit() {
        listNum.clear();

        for (int i = 1; i < 5; i++) {
            listNum.add(i);
        }
        Collections.shuffle(listNum);

        for (int i = 0; i < cardText.length; i++) {          
            cardText[i].setText(String.valueOf(listNum.get(i)));
            cardText[i].setBackgroundResource(R.drawable.card_back);
            cardText[i].setTextColor(getResources().getColor(R.color.text_transparent));
            viewClickCheck[i] = false;
        }

        textViewOnClickListener();
    }

    private View.OnClickListener cardClick(final TextView textView) {
        return new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                final double d = Math.random() * 4;
                clickTextView = findViewById(view.getId());

                float scale = getApplicationContext().getResources().getDisplayMetrics().density;
                final float distance = clickTextView.getCameraDistance() * (scale + (scale / 3));

                clickTextView.setCameraDistance(distance);
                clickTextView.animate().withLayer()
                        .rotationY(90)
                        .setDuration(150)
                        .withEndAction(new Runnable() {
                            @Override
                            public void run() {
                                if (!viewClickCheck[Integer.parseInt(clickTextView.getText().toString()) - 1]) {
                                    clickTextView.setBackgroundResource(imagesources[(int) d]);
                                    clickTextView.setTextColor(getResources().getColor(R.color.text_color));
                                    viewClickCheck[Integer.parseInt(clickTextView.getText().toString()) - 1] = true;
                                } else {
                                    clickTextView.setBackgroundResource(R.drawable.card_back);
                                    clickTextView.setTextColor(getResources().getColor(R.color.text_transparent));
                                    viewClickCheck[Integer.parseInt(clickTextView.getText().toString()) - 1] = false;
                                }
                                clickTextView.setRotationY(-90);
                                clickTextView.animate().withLayer()
                                        .rotationY(0)
                                        .setDuration(250)
                                        .start();
                            }
                        }).start();
            }
        };
    }
}

MainActivity의 전체 소스코드입니다.

4장의 카드가 나오며 클릭 시 카드가 회전하면서 앞면과 숫자를 보여 줍니다.

앞면으로 변한 카드를 클릭하면 다시 뒤면으로 표시되는 구조입니다.

숫자는 1~4번이 랜덤 하게 들어가며 "다시하기" 버튼을 이용해 리셋할 수 있습니다.

일단 한번 실행시켜 보신 후 다시 글을 보시면 이해하기 더 쉬울 것 같습니다.

 

 

private TextView[] cardText = new TextView[4];
private TextView clickTextView;
private boolean[] viewClickCheck = new boolean[4];

private List<Integer> listNum = new ArrayList<>();

int[] imagesources = {R.drawable.aa, R.drawable.ab, R.drawable.ac, R.drawable.ad};

TextView [] cardText = new TextView[4]; -> TextView의 아이디를 넣을 곳

TextView clickTextView; -> 클릭된 카드의 숫자를 받아 오는 곳

boolean [] viewClickCheck = new boolean [4]; -> 카드가 앞면 또는 뒷면인지 구분하는 곳

List <Integer> listNum = new ArrayList<>(); -> 카드에 들어갈 숫자를 담을 곳

int [] imagesources = {R.drawable.aa, R.drawable.ab, R.drawable.ac, R.drawable.ad}; -> 카드의 앞면을

4장의 이미지를 이용해 랜덤 하게 표시하기 위한 곳

 

for (int i = 1; i < 5; i++) {
    listNum.add(i);
}
Collections.shuffle(listNum);

1~4번까지 숫자를 listNum에 넣어 Collections.shuffle(listNum)를 이용해 무작위로 섞어 줍니다.

 

for (int i = 0; i < cardText.length; i++) {
    String viewName = "image_" + (i + 1);
    cardText[i] = findViewById(getResources().getIdentifier(viewName, "id", getPackageName()));
    cardText[i].setText(String.valueOf(listNum.get(i)));
    viewClickCheck[i] = false;
}

4장의 카드만큼 for문을 돌려 위에서 지정한  TextView [] cardText 에 아이디를 지정해 줍니다.

String viewName = "image_" + (i + 1) -> TextView의 아디를 만들어 viewName에 넣어 줍니다.

getResources().getIdentifier를 이용해 Textview의 아이디를 가져 옵니다.

이렇게 찾은 아이디 값을 cardText배열에 하나씩 넣어 주면 됩니다.

listNum에 넣어 둔 숫자들을 cardText[i].setText(String.valueOf(listNum.get(i))) 로 화면에 표시합니다.

기본 Textview의 글자 색상은 투명으로 화면에는 보이지 않습니다.

viewClickCheck[i] = false -> 카드가 뒷면이면 false 앞면이면 true 입니다.

 

private void textViewOnClickListener() {
        for (TextView v : cardText) {
         v.setOnClickListener(cardClick(v));
    }
}

cardText배열에 들어 있는 각각의 TextView에 setOnClickListener를 달아 줍니다.

카드 클릭 시 private View.OnClickListener cardClick로 넘어가 클릭에 관련된 처리를 합니다.

final double d = Math.random() * 4;
clickTextView = findViewById(view.getId());

d = Math.random() * 4 -> 4장의 이미지를 랜덤 하게 표시하기 위한 것입니다.

clickTextView = findViewById(view.getId()) -> 현재 클릭된  Textview의 아이디를 clickTextView에 저장 합니다.

float scale = getApplicationContext().getResources().getDisplayMetrics().density;
final float distance = clickTextView.getCameraDistance() * (scale + (scale / 3));
clickTextView.setCameraDistance(distance);

카드 클릭 시 회전하면 위쪽과 아래쪽 카드 모서리가 잘려서 돌게 됩니다.

setCameraDistance 카메라의 거리 값을 주는 것인데 회전 시 잘리는 이미지를 방지해 줍니다.

clickTextView.animate().withLayer()
                        .rotationY(90)
                        .setDuration(150)
                        .withEndAction(new Runnable() {

클릭 시 처음 이미지가 회전하는 각도 및 회전하는 시간을 설정할 수 있습니다.

처음 회전이 완료되면 withEndAction이 다음 이미지를 출력합니다.

.withEndAction(new Runnable() {
                            @Override
                            public void run() {
                                if (!viewClickCheck[Integer.parseInt(clickTextView.getText().toString()) - 1]) {
                                    clickTextView.setBackgroundResource(imagesources[(int) d]);
                                    clickTextView.setTextColor(getResources().getColor(R.color.text_color));
                                    viewClickCheck[Integer.parseInt(clickTextView.getText().toString()) - 1] = true;
                                } else {
                                    clickTextView.setBackgroundResource(R.drawable.card_back);
                                    clickTextView.setTextColor(getResources().getColor(R.color.text_transparent));
                                    viewClickCheck[Integer.parseInt(clickTextView.getText().toString()) - 1] = false;
                                }
                                clickTextView.setRotationY(-90);
                                clickTextView.animate().withLayer()
                                        .rotationY(0)
                                        .setDuration(250)
                                        .start();
                            }
                        }).start();

현재 화면의 카드가 앞면 또는 뒷면인지를 구분한 후 카드의 이미지 교체 및 Textview의 글자 색상을 변경해 줍니다.

!viewClickCheck[Integer.parseInt(clickTextView.getText().toString()) - 1]

viewClickCheck 가 false면 (뒷면) 카드 이미지및 글자색상을 바꿔주며  앞면이 되었으니 viewClickCheck를 true로 변경해 줍니다.

Textview의 숫자를  viewClickCheck의 배열 순서로 사용했습니다.

나머지 밑의 코드는 반대의 경우이니 따로 설명하지 않겠습니다.

 

현재 코드의 문제점!

카드 클릭 후 앞면 또는 뒤면 회전하는 동안 다른 이미지로 교체되기 전에 다른 카드를 클릭하면 현재 카드 이미지가 사라지는 문제가 있습니다.

직접 문제를 해결해 보시기 바라며 여러 방법이 있겠지만 한 가지 해결 방법으론 카드 회전하는 동안 다른 카드 클릭을 막은 후 회전이 끝나면 다시 클릭이 되도록 하는 것입니다.

참고하시며 코딩해 보시기 바랍니다.

 

카드 이미지는 구글에 많이 있으니 따로 첨부하지 않겠습니다.

 

초보탈출 그날까지~~

반응형