지난 시간
앱을 만들기 전 기본적으로 하는 밑작업을 끝냈다.
이제부터 본격적으로 코딩을 시작한다.
카드 지갑 앱 만들기
1. 프로젝트 만들기
난 삼성을 사용하는 Galaxy Android 유저이기 때문에 Android Studio라는 IDE를 이용해서 앱을 만들 것이다.

기본 액티비티를 선택해 준다.

앱 이름이 정하고 언어를 정한다.
보통 안드로이드 스튜디오를 이용해서 앱을 만들 땐 두 가지 언어를 사용한다.
바로 자바나 코틀린이다. 원래는 자바를 많이 사용했지만 요즘은, 그리고 앞으로는 코틀린을 많이 사용하게 될 것 같다.
하지만 난 이번 앱은 자바로 만든다.
Finish를 눌러주면 프로젝트 생성이 끝난다.
2. 메인 화면 만들기

프로젝트가 만들어졌다 이제 우리는 이곳에 코딩을 시작할 것이다.
오늘의 팁이라고 창이 뜨는데 무시해도 된다.
자바 코드를 코틀린 코드로 변환하는 기능에 대해 설명하고 있다. 그 반대의 기능은 없다.
우린 애초에 자바를 사용하기에 close 해준다.
그리고 옆에 Manifest 파일과 layout의 위치 gradle, Activity의 위치등은 기억하도록 하자.
2 - 1. 화면 디자인 하기

코드를 작성하기 전에 layout에 들어가서 메인 화면을 디자인해줄 것이다.
디자인 틀은 지난 시간 피그마에 했기 만들어 놓았기 때문에 똑같이 따라 만들면 된다.
먼저 툴바를 만들어주겠다.

리소스 디렉터리를 만든다.

리소스 타입 중에 메뉴를 선택하고 OK를 누른다.

메뉴 리소스를 만들고

적당한 이름을 넣어준다.

그러면 이렇게 메뉴 파일이 만들어진다.

피그마에 그렸던 디자인처럼 메뉴 아이템 2개를 만들고 설정해 준다.
이제 메뉴 icon을 설정해줘야 하는데 아이콘을 설정하는 방법엔 2가지가 있다.
첫 번째는 이미지를 직접 가져와 넣는 것이고 두 번째론 에셋을 이용하는 것이다.

난 2번째 방법인 에셋을 이용할 것이기 때문에 Vector Asset에 들어가 준다.

그러면 위 화면에 나오는데 여기서 Clip art를 누른다.

그럼 안드로이드에서 기본으로 제공하는 벡터 에셋이 나온다.

원하는 에셋을 선택하고 그에 맞게 이름을 변경해 준다.
난 추가로 크기를 변경해 주었는데 기본은 24dp 지만 48dp로 변경했다.

마찬가지로 설정 버튼도 똑같이 만들어준다.

기본적인 툴바가 완성됐다.
메인으로 돌아와 피그마에서 툴바를 적용시키고 그렸던 카드가 한 장도 없는 버전의 화면을 만들어보자.

간단하게 툴바를 적용시키고 벡터 에셋 아이콘을 하나 더 추가해 버튼과 텍스트를 달아서 만들어줬다.
다음으로 카드가 있을 때의 메인 화면을 만들어 줄 것이다.

카드를 보여줄 카드 아이템을 레이아웃 폴더에 만든다.

카드뷰를 이용하면 간단하게 카드 아이템을 만들 수 있다.
이제 카드가 여러 장일 때 왼쪽으로 슬라이스 하면 카드가 넘어가는 기능을 구현해야 한다.

그전에 개발을 더 쉽게 하기 위한 뷰바인딩을 설정해 준다.
데이터 바인딩을 써도 되지만 난 뷰 바인딩을 사용하겠다.
sync Now를 눌러준다.
카드 아이템을 다루기 위한 파일을 만든다.

자바 파일을 선택하고

클래스를 만든다.
package com.example.cardwallet;
public class CardItem {
private String Number; // 카드 번호
private String Kind; // 카드 종류
private String ExpirationDate; // 만료일
private String OwnersName; // 소유자
private int SecurityCode; // 보안 코드
private int Password; // 비밀 번호
}
이제부터 보이는 부분이 없는 코드 부분은 사진이 아닌 코드로 대체하겠다.
Card가 가지는 정보에 필요한 부분들의 변수를 선언해 준다.

다음으로 마우스 우클릭이나 위에 바에서 Code 부분에 들어가 Generate를 눌러준다.

그럼 이런 창이 뜬다 우리가 필요한 코드를 자동으로 만들어준다.

첫 번째로 constructor를 만들 것이다.
모든 변수를 Ctrl + 클릭하고 Ok 버튼을 누른다.
추가로 Getter, Setter, equal, toString 등을 더 만들어줬다.
public class CardItem {
private String Number; // 카드 번호
private String Kind; // 카드 종류
private String ExpirationDate; // 만료일
private String OwnersName; // 소유자
private int SecurityCode; // 보안 코드
private int Password; // 비밀 번호
public CardItem(String number, String kind, String expirationDate, String ownersName, int securityCode, int password) {
Number = number;
Kind = kind;
ExpirationDate = expirationDate;
OwnersName = ownersName;
SecurityCode = securityCode;
Password = password;
}
public String getNumber() {
return Number;
}
public void setNumber(String number) {
Number = number;
}
public String getKind() {
return Kind;
}
public void setKind(String kind) {
Kind = kind;
}
public String getExpirationDate() {
return ExpirationDate;
}
public void setExpirationDate(String expirationDate) {
ExpirationDate = expirationDate;
}
public String getOwnersName() {
return OwnersName;
}
public void setOwnersName(String ownersName) {
OwnersName = ownersName;
}
public int getSecurityCode() {
return SecurityCode;
}
public void setSecurityCode(int securityCode) {
SecurityCode = securityCode;
}
public int getPassword() {
return Password;
}
public void setPassword(int password) {
Password = password;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CardItem cardItem = (CardItem) o;
return SecurityCode == cardItem.SecurityCode && Password == cardItem.Password && Objects.equals(Number, cardItem.Number) && Objects.equals(Kind, cardItem.Kind) && Objects.equals(ExpirationDate, cardItem.ExpirationDate) && Objects.equals(OwnersName, cardItem.OwnersName);
}
@Override
public int hashCode() {
return Objects.hash(Number, Kind, ExpirationDate, OwnersName, SecurityCode, Password);
}
@Override
public String toString() {
return "CardItem{" +
"Number='" + Number + '\'' +
", Kind='" + Kind + '\'' +
", ExpirationDate='" + ExpirationDate + '\'' +
", OwnersName='" + OwnersName + '\'' +
", SecurityCode=" + SecurityCode +
", Password=" + Password +
'}';
}
}
이렇게 Item 데이터 클래스를 다 만들었다.
이제 다시 MainActivity로 돌아와 코드를 작성해 보자
private static final String TAG = "CardMainActivity"; // 에러 메시지 태그
private ArrayList<CardItem> cardItems;
private ActivityMainBinding binding;
private ViewPager2 pager;
전역 변수로 Log를 사용할 때 Tag를 편하게 해 줄 에러 메시지 태그와 바인딩을 선언해 준다.
또 나중에 사용할 ViewPager를 설정해 준다.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
Toolbar toolbar = binding.customToolbar;
setSupportActionBar(toolbar);
ActionBar abar = getSupportActionBar();
abar.setDisplayHomeAsUpEnabled(true); // 뒤로가기 버튼 활성화
abar.setDisplayShowCustomEnabled(true); // 사용자 커스텀 활성화
//abar.setDisplayShowTitleEnabled(false); // Title 없애기
}
onCreate함수에 toolbar에 대한 기본적인 코드를 작성해 줬다.
@Override
// 옵션 메뉴가 만들어질때 호출
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
Log.d(TAG, "Flow Code onCreateOptionsMenu");
getMenuInflater().inflate(R.menu.menu_card, menu); //XML로 정의된 메뉴 정보를 인플레이션 하여 메모리에 로딩
return true;
}
@Override
// 메뉴가 화면에 표시되기 전에 호출
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
Log.d(TAG, "Flow Code onPrepareOptionsMenu");
return super.onPrepareOptionsMenu(menu);
}
@Override
// 메뉴가 선택될때 호출
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
super.onOptionsItemSelected(item);
Log.d(TAG, "Flow Code onOptionsItemSelected");
int curId = item.getItemId();
if (curId == android.R.id.home) { //툴바 뒤로가기버튼 눌렸을 때 동작
finish();
return true;
} else if (curId == R.id.menu_add) {
Log.d(TAG, "Menu Add Clicked");
Toast.makeText(this, "추가 메뉴를 선택하셨습니다.", Toast.LENGTH_SHORT).show();
} else if (curId == R.id.menu_more) {
Log.d(TAG, "Menu More Clicked");
Toast.makeText(this, "설정 메뉴를 선택하셨습니다.", Toast.LENGTH_SHORT).show();
}
return super.onOptionsItemSelected(item);
}
toolbar에 존재하는 옵션메뉴들에 대한 함수들이다.
해당 함수의 역할에 따라 오버라이딩해서 맞춤형으로 사용할 수 있다.
툴바 설정까지 다 맞췄으니 이제 카드가 넘어가는 기본적인 기능을 만들어야 한다.
우리는 ViewPager와 Recycle View를 함께 사용할 것이다.
그러기 위해선 어댑터를 만들어줘야 한다.
public class MainAdapter extends RecyclerView.Adapter<MainAdapter.MyViewHolder> {
private static final String TAG = "CardMainAdapter"; # 오류 로그 태그
private static ArrayList<CardItem> cardList = null; # 내부 카드 리스트
public MainAdapter(ArrayList<CardItem> cardList) {
MainAdapter.cardList = cardList;
}
public static class MyViewHolder extends RecyclerView.ViewHolder {
TextView cardName;
MaterialCardView card;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
card = itemView.findViewById(R.id.Card);
cardName = itemView.findViewById(R.id.CardTitleTextView);
}
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card, parent, false);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
CardItem cardItem = cardList.get(position);
String kind = cardItem.getKind();
holder.cardName.setText(kind);
holder.card.setCardBackgroundColor(ContextCompat.getColor(holder.itemView.getContext(), getCardColor(cardItem.getNumber())));
}
@Override
public int getItemCount() {
return cardList.size();
}
}
각 함수에 대해 설명한다.
- MainAdapter(ArrayList<CardItem> cardList): MainAdapter 클래스의 생성자다. 이 생성자는 CardItem 객체의 ArrayList를 받아와서 클래스 내부의 cardList 멤버 변수에 할당한다.
- public static class MyViewHolder extends RecyclerView.ViewHolder: MyViewHolder는 RecyclerView.ViewHolder를 상속하는 정적 내부 클래스다. 이 클래스는 RecyclerView에서 각 아이템의 뷰를 보유한다.
- onCreateViewHolder(): RecyclerView에서 새로운 ViewHolder 객체를 생성하는 메서드다. LayoutInflater를 사용하여 XML 레이아웃 파일인 item_card.xml에서 뷰를 생성하고 이를 MyViewHolder에 전달하여 ViewHolder 객체를 만든다.
- onBindViewHolder(): RecyclerView에서 ViewHolder에 데이터를 바인딩하는 메서드다. 여기서는 cardList에서 현재 위치에 해당하는 CardItem 객체를 가져와서 해당 ViewHolder의 뷰에 데이터를 설정한다.
- getItemCount(): RecyclerView에 표시할 아이템의 총개수를 반환하는 메서드다. 여기서는 cardList의 크기를 반환하여 RecyclerView에 몇 개의 아이템을 표시할지 결정한다.
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager_card"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/custom_toolbar"
tools:listitem="@layout/item_card" />
MainActivitiy로 가기 전에 xml에 Viewpager를 선언해 준다.
cardMainAdapter = new MainAdapter(cardItems);
pager = binding.viewpagerCard;
pager.setAdapter(cardMainAdapter);
pager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL); // 방향을 가로로
그럼 다시 MainActicity로 돌아와서 onCreate 함수에 어댑터 연결 코드를 만들어준다.
바인딩으로 xml에서 선언한 viewpager_card 부분을 가져와 어댑터를 설정하고 방향으로 가로로 바꿔준다.
private ArrayList<CardItem> getTestData() {
ArrayList<CardItem> cardItems = new ArrayList<>(Arrays.asList(
new CardItem("1234567890123456", "VISA", "12/25", "Jonh", 123, 4567),
new CardItem("1231567898765432", "MASTER", "01/29", "Sujan", 987, 6543),
new CardItem("1238567898765432", "SAMSUNG", "03/15", "James", 564, 6543),
new CardItem("1236567898765432", "WOO RI", "04/08", "donald", 876, 6543)
));
return cardItems;
}
추가로 테스트 데이터를 만들어 실험해 준다.
카드 앱 실행해 보기
MainActivitiy
package com.example.cardwallet;
import androidx.annotation.NonNull;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import com.example.cardwallet.databinding.ActivityMainBinding;
import androidx.appcompat.widget.Toolbar;
import androidx.viewpager2.widget.ViewPager2;
import java.util.ArrayList;
import java.util.Arrays;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "CardMainActivity"; // 에러 메시지 태그
private ArrayList<CardItem> cardItems;
private ActivityMainBinding binding;
private MainAdapter cardMainAdapter;
private ViewPager2 pager;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
Toolbar toolbar = binding.customToolbar;
setSupportActionBar(toolbar);
ActionBar abar = getSupportActionBar();
if (abar != null) {
abar.setDisplayHomeAsUpEnabled(true);
abar.setDisplayShowCustomEnabled(true);
//abar.setDisplayShowTitleEnabled(false); // Title 없애기
}
cardItems = getTestData();
cardMainAdapter = new MainAdapter(cardItems);
pager = binding.viewpagerCard;
pager.setAdapter(cardMainAdapter);
pager.setOrientation(ViewPager2.ORIENTATION_HORIZONTAL); // 방향을 가로로
}
@Override
// 옵션 메뉴가 만들어질때 호출
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
Log.d(TAG, "Flow Code onCreateOptionsMenu");
getMenuInflater().inflate(R.menu.menu_card, menu); //XML로 정의된 메뉴 정보를 인플레이션 하여 메모리에 로딩
return true;
}
@Override
// 메뉴가 화면에 표시되기 전에 호출
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
Log.d(TAG, "Flow Code onPrepareOptionsMenu");
return super.onPrepareOptionsMenu(menu);
}
@Override
// 메뉴가 선택될때 호출
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
super.onOptionsItemSelected(item);
Log.d(TAG, "Flow Code onOptionsItemSelected");
int curId = item.getItemId();
if (curId == android.R.id.home) { //툴바 뒤로가기버튼 눌렸을 때 동작
finish();
return true;
} else if (curId == R.id.menu_add) {
Log.d(TAG, "Menu Add Clicked");
// Toast.makeText(this, "추가 메뉴를 선택하셨습니다.", Toast.LENGTH_SHORT).show();
} else if (curId == R.id.menu_more) {
Log.d(TAG, "Menu More Clicked");
// Toast.makeText(this, "설정 메뉴를 선택하셨습니다.", Toast.LENGTH_SHORT).show();
}
return super.onOptionsItemSelected(item);
}
private ArrayList<CardItem> getTestData() {
ArrayList<CardItem> cardItems = new ArrayList<>(Arrays.asList(
new CardItem("1234567890123456", "VISA", "12/25", "Jonh", 123, 4567),
new CardItem("1231567898765432", "MASTER", "01/29", "Sujan", 987, 6543),
new CardItem("1238567898765432", "SAMSUNG", "03/15", "James", 564, 6543),
new CardItem("1236567898765432", "WOO RI", "04/08", "donald", 876, 6543)
));
return cardItems;
}
}
MainAdapter
package com.example.cardwallet;
import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.google.android.material.card.MaterialCardView;
import java.util.ArrayList;
public class MainAdapter extends RecyclerView.Adapter<MainAdapter.MyViewHolder> {
private static final String TAG = "CardMainAdapter";
private static ArrayList<CardItem> cardList = null;
public MainAdapter(ArrayList<CardItem> cardList) {
MainAdapter.cardList = cardList;
}
public static class MyViewHolder extends RecyclerView.ViewHolder {
TextView cardName;
MaterialCardView card;
public MyViewHolder(@NonNull View itemView) {
super(itemView);
card = itemView.findViewById(R.id.Card);
cardName = itemView.findViewById(R.id.CardTitleTextView);
}
}
@NonNull
@Override
public MyViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_card, parent, false);
return new MyViewHolder(itemView);
}
@Override
public void onBindViewHolder(@NonNull MyViewHolder holder, int position) {
CardItem cardItem = cardList.get(position);
Log.d(TAG, cardItem.getKind() + " 카드 번호");
// 예시: 카드 이름과 색을 각각 설정
String kind = cardItem.getKind();
holder.cardName.setText(kind);
Log.d(TAG, cardItem.getNumber() + " 카드 번호");
holder.card.setCardBackgroundColor(ContextCompat.getColor(holder.itemView.getContext(), getCardColor(cardItem.getNumber())));
}
@Override
public int getItemCount() {
return cardList.size();
}
private int getCardColor(String cardNumber) {
int number = cardNumber.charAt(0);
Log.d(TAG, number+"번호");
switch (number) {
case 1:
return R.color.CARMINE;
case 2:
return R.color.RUBY;
case 3:
return R.color.ROSE_RED;
case 4:
return R.color.VERMILION;
case 5:
return R.color.MERCURY;
case 6:
return R.color.BURGUNDY;
case 7:
return R.color.MEDIUM_GREY;
case 8:
return R.color.CHARCOAL;
case 9:
return R.color.SILVER;
default:
return R.color.LIGHT_GREY;
}
}
}
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:elevation="150dp"
tools:context=".MainActivity">
<androidx.appcompat.widget.Toolbar
android:id="@+id/custom_toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:menu="@menu/menu_card"
app:title="@string/app_name"
app:titleTextColor="@color/black" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager_card"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="@+id/custom_toolbar"
tools:listitem="@layout/item_card" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/backgroundImageView"
android:layout_width="333dp"
android:layout_height="258dp"
android:src="@drawable/ic_baseline_add_card_48"
app:layout_constraintBottom_toTopOf="@+id/addButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/custom_toolbar"
android:visibility="gone" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/TextView"
android:layout_width="300dp"
android:layout_height="60dp"
android:text="빠른 실행에 카드를 추가하고 \n 간편하게 사용하세요."
android:textAlignment="center"
android:textColor="@color/black"
android:textSize="20dp"
app:layout_constraintBottom_toTopOf="@+id/addButton"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/backgroundImageView"
app:layout_constraintVertical_bias="0.0"
android:visibility="gone" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/addButton"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="추가"
android:textSize="15dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/custom_toolbar"
app:layout_constraintVertical_bias="0.8"
android:visibility="gone"/>
</androidx.constraintlayout.widget.ConstraintLayout>
item_card.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">
<com.google.android.material.card.MaterialCardView
android:id="@+id/Card"
android:layout_width="250dp"
android:layout_height="150dp"
android:backgroundTint="@color/CONCRETE"
app:cardCornerRadius="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/CardTitleTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingStart="16dp"
android:paddingEnd="16dp"
android:text="Samsung Card"
android:textColor="@color/white"
android:textSize="22sp"
android:textStyle="bold" />
</com.google.android.material.card.MaterialCardView>
</androidx.constraintlayout.widget.ConstraintLayout>
위가 최종 코드이다.
핸드폰과 연결해 실행해 보자.

잘 되는 것을 확인할 수 있었다.
'카드 지갑 앱' 카테고리의 다른 글
카드 지갑 앱 제작기 1 (1) | 2024.03.08 |
---|