안드로이드 쇼핑몰 오픈소스 - andeuloideu syopingmol opeunsoseu

웹이든 앱이든 개발자들이 한 번씩은 경험해 보는 것이 쇼핑몰일 것이다. 그만큼 기본적으로 할 수 있어야 하는 부분들을 할 수 있고, 개발실력 향상에 도움이 되어서가 아닌가 생각된다.

안드로이드 개발 실력을 스스로 확인하기위해, 약 한 달의 시간에 걸쳐 천천히 쇼핑몰 개발을 진행하였다.

아래는 개발하기 전에 세웠던 목표들이다.

  • 로딩화면에서 애니메이션을 넣어 로그인 화면 전환을 부드럽게 만들어보자.
  • RecyclerView를 사용하자.
  • BottomNavigationBar 를 사용하자.
  • SQLite를 써서 개발해보자.
  • 자동로그인 기능도 구현해보자.

결론적으로 위 목표를 모두 실현했다.

왼쪽부터 로그인 화면, 회원가입 화면, 홈 화면이다.

왼쪽부터 쇼핑 홈 화면, 장바구니 화면, 개인정보 화면이다.//youtu.be/Suzz_FGzeu0

코드

- Login Activity -

우선, 프로젝트를 막 시작하며 Animation을 구현하면서 써놓았던 글이 있는데, 거기에 로고와 약간의 수정만 한 후 커스텀 로그인 폼을 추가해서 로딩 화면을 완성했다. 

private PreferenceManager pManager; ... private void startAnime() { logoAni = new TranslateAnimation(0, 0, 0, -250); // from 어디서 to 어디까지 이동할건지. 가운데를 중심으로 위, 왼쪽: - 아래, 오른쪽: + logoAni.setDuration(2000); // 지속시간 logoAni.setFillAfter(true); // 이동 후 이동한 자리에 남아있을건지 logoAni.setStartOffset(1500); // 딜레이 logoAni.setInterpolator(new AccelerateDecelerateInterpolator()); // interpolator 설정. AccelerteDecelerate : 시작지점에 가속했다 종료시점에 감속 loginFormAni = new AlphaAnimation(0, 1); loginFormAni.setDuration(1000); loginFormAni.setStartOffset(3000); imgView.setAnimation(logoAni); // 애니메이션 세팅 loginForm.setAnimation(loginFormAni); } public void loginCheck() { String loginId = pManager.getString(this, "user_id"); if(loginId.length() != 0) { // preference가 비어있지 않으면 바로 Main실행. startMainActivity(); } } private boolean accountCheck() { //db에 접근해서 id, pw 확인 후 일치 시 true, 불일치시 false return. String idValue = String.valueOf(id.getText()); String pwValue = String.valueOf(pw.getText()); if(idValue.equals("") || pwValue.equals("")) { return false; } String dbId = dbHelper.getUserId(idValue); String dbPw = dbHelper.getUserPw(idValue); if(dbId.equals(idValue)) { if(dbPw.equals(pwValue)) { Toast.makeText(this, "환영합니다.", Toast.LENGTH_SHORT).show(); return true; } Toast.makeText(this, "비밀번호를 확인해주세요.", Toast.LENGTH_SHORT).show(); return false; } setEmptyEt(); Toast.makeText(this, "존재하지 않는 아이디입니다.", Toast.LENGTH_SHORT).show(); return false; } private void setEmptyEt() { id.setText(""); pw.setText(""); } ...

- Register Activity -

public void onOkBtnClick(View v) { if(isEmpty()) { // 비어있는 칸 없는지 확인. 있으면 true 반환 return; } else if(isDuplicateId()) { // ID값 중복된 것 있는지 확인. 있으면 true 반환 Toast.makeText(this, "이미 있는 ID 입니다. ID를 변경해주세요.", Toast.LENGTH_SHORT).show(); return; } else if(!isCorreectPw()) { // PW, PwChk 값 같은지 확인. 같으면 true 반환 Toast.makeText(this, "비밀번호와 비밀번호 확인의 값이 다릅니다. 다시 확인해주세요.", Toast.LENGTH_SHORT).show(); return; } // Email 포맷 체크, %@% 아니면 DB에 안들어감. // DB에 값 집어넣기 UserBean userBean = new UserBean(); userBean.setName(String.valueOf(name.getText())); userBean.setEmail(String.valueOf(email.getText())); userBean.setId(String.valueOf(id.getText())); userBean.setPassword(String.valueOf(pw.getText())); userBean.setGender(String.valueOf(selectGender)); userBean.setYears(String.valueOf(selectYears)); dbHelper.insertUser(userBean); Toast.makeText(this, "회원가입이 완료되었습니다.", Toast.LENGTH_SHORT).show(); finish(); }

- Main Activity -

@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 첫 화면 지정 FragmentTransaction transaction = fragmentManager.beginTransaction(); transaction.replace(R.id.frameLayout, homeFragment).commitAllowingStateLoss(); BottomNavigationView bottomNav = findViewById(R.id.bottom_navigation_view); bottomNav.setOnNavigationItemSelectedListener(new ItemSelectListener()); } private class ItemSelectListener implements BottomNavigationView.OnNavigationItemSelectedListener{ @Override public boolean onNavigationItemSelected(@NonNull MenuItem menuItem) { FragmentTransaction transaction = fragmentManager.beginTransaction(); switch (menuItem.getItemId()) { case R.id.nav_home: transaction.replace(R.id.frameLayout, homeFragment).commitAllowingStateLoss(); break; case R.id.nav_shop: transaction.replace(R.id.frameLayout, shopFragment).commitAllowingStateLoss(); break; case R.id.nav_cart: transaction.replace(R.id.frameLayout, cartFragment).commitAllowingStateLoss(); break; case R.id.nav_my: transaction.replace(R.id.frameLayout, myFragment).commitAllowingStateLoss(); break; } return true; } } @Override public void onBackPressed() { ActivityCompat.finishAffinity(this); // app 종료 }

마지막 onBackPressed()의 ActivityCompat.finishAffinity(this) 는, RootActivity까지 나가 finish()를 하지 말고 바로 종료시키고 싶었기 때문에 넣었다.

- Home Fragment -

private static final int INTERVAL_TIME = 3800; private View rootView; private ViewFlipper viewFlipper; private GridView gridView; private HomeGridAdapter adapter; private ArrayList<ProductBean> data; private ProductDBHelper dbHelper; int images[] = { R.drawable.slide_image_1, R.drawable.slide_image_2, R.drawable.slide_image_3, R.drawable.slide_image_4 }; // 메인. 슬라이드 형식 화면 절반치 광고, 아래에 상품 6개 정도 보여주기 public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { view = inflater.inflate(R.layout.activity_home_fragment, container, false); viewFlipper = view.findViewById(R.id.imageSlide); for(int image : images) flipperImages(image); showProduct(); return view; } private void flipperImages(int image) { ImageView imageView = new ImageView(getContext()); imageView.setBackgroundResource(image); viewFlipper.addView(imageView); viewFlipper.setFlipInterval(INTERVAL_TIME); viewFlipper.setAutoStart(true); viewFlipper.setInAnimation(getContext(), R.anim.slide_in_anim); viewFlipper.setOutAnimation(getContext(), R.anim.slide_out_anim); } private void showProduct() { dbHelper = ProductDBHelper.getInstance(getContext()); data = dbHelper.getRandomProduct(); gridView = view.findViewById(R.id.gridView); adapter = new HomeGridAdapter(getContext(), data); gridView.setAdapter(adapter); }

Fragment를 이렇게 많이쓴 프로젝트는 처음이었다. Activity와 달리 onCreateView를 사용해야 하며, findViewById를 할 때에는 따로 view를 inflate 한 후 사용해야 한다는 점이 어색했었다.

- UserDBHelper -

public class UserDBHelper extends SQLiteOpenHelper { private static UserDBHelper dbHelper = null; public static final String DATABASE_NAME = "userdb"; public static final String TABLE_NAME = "user"; public static final int DB_VERSION = 1; public static final String COL_0 = "serialNumber"; public static final String COL_1 = "name"; public static final String COL_2 = "email"; public static final String COL_3 = "id"; public static final String COL_4 = "password"; public static final String COL_5 = "gender"; public static final String COL_6 = "years"; public static UserDBHelper getInstance(Context context){ if(dbHelper == null){ dbHelper = new UserDBHelper(context.getApplicationContext()); } return dbHelper; } private UserDBHelper(Context context){ super(context, DATABASE_NAME, null, DB_VERSION); } @Override public void onCreate(SQLiteDatabase db) { String sql = "create table " + TABLE_NAME + " (" + COL_0 + " integer primary key autoincrement, " + COL_1 + " text not null," + COL_2 + " text not null check (email like '%@%')," + COL_3 + " text not null unique," + COL_4 + " text not null," + COL_5 + " text not null," + COL_6 + " text not null " + ")"; db.execSQL(sql); } @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { String sql = "drop table " + TABLE_NAME; db.execSQL(sql); onCreate(db); } public long insertUser(UserBean user){ SQLiteDatabase db = getWritableDatabase(); ContentValues value = new ContentValues(); value.put(COL_1, user.getName()); value.put(COL_2, user.getEmail()); value.put(COL_3, user.getId()); value.put(COL_4, user.getPassword()); value.put(COL_5, user.getGender()); value.put(COL_6, user.getYears()); return db.insert(TABLE_NAME, null, value); } public ArrayList<UserBean> getAllUser(){ SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.query(TABLE_NAME, null, null, null, null, null, null); ArrayList<UserBean> result = new ArrayList<>(); while(cursor.moveToNext()){ UserBean user = new UserBean(); user.setSerialNumber(cursor.getInt(cursor.getColumnIndex(COL_0))); user.setName(cursor.getString(cursor.getColumnIndex(COL_1))); user.setEmail(cursor.getString(cursor.getColumnIndex(COL_2))); user.setId(cursor.getString(cursor.getColumnIndex(COL_3))); user.setPassword(cursor.getString(cursor.getColumnIndex(COL_4))); user.setGender(cursor.getString(cursor.getColumnIndex(COL_5))); user.setYears(cursor.getString(cursor.getColumnIndex(COL_6))); result.add(user); } return result; } public ArrayList<UserBean> getUserById(String id){ SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.query(TABLE_NAME, null, COL_3 + "=?", new String[] {id}, null, null, null); ArrayList<UserBean> result = new ArrayList<>(); while(cursor.moveToNext()){ UserBean user = new UserBean(); user.setSerialNumber(cursor.getInt(cursor.getColumnIndex(COL_0))); user.setName(cursor.getString(cursor.getColumnIndex(COL_1))); user.setEmail(cursor.getString(cursor.getColumnIndex(COL_2))); user.setId(cursor.getString(cursor.getColumnIndex(COL_3))); user.setPassword(cursor.getString(cursor.getColumnIndex(COL_4))); user.setGender(cursor.getString(cursor.getColumnIndex(COL_5))); user.setYears(cursor.getString(cursor.getColumnIndex(COL_6))); result.add(user); } return result; } public String getUserId(String id){ SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.query(TABLE_NAME, null, COL_3 + "=?", new String[] {id}, null, null, null); String result = ""; while(cursor.moveToNext()){ result = cursor.getString(cursor.getColumnIndex(COL_3)); } return result; } public String getUserPw(String id){ SQLiteDatabase db = getReadableDatabase(); Cursor cursor = db.query(TABLE_NAME, null, COL_3 + "=?", new String[] {id}, null, null, null); String result = ""; while(cursor.moveToNext()){ result = cursor.getString(cursor.getColumnIndex(COL_4)); } return result; } public long deleteUser(UserBean bean){ SQLiteDatabase db = getWritableDatabase(); String serial = String.valueOf(bean.getSerialNumber()); return db.delete(TABLE_NAME, COL_0 + "=?", new String[] {serial}); } }

SQLite를 이용하면서, rowQurey와 SQLiteDatabase에서 제공하는 query를 섞어서 사용했다. 그냥 이것저것 다양한 방법을 사용해보고 싶었다.

후기

사실 프로젝트를 진행하면서 코드의 퀄리티보다는 완성에만 집중했었던 것 같다. 때문에 RecyclerView가 필요없는 부분에서도 써놓았던 코드를 Ctrl + C, V 하여 작성하는 참사가 발생했었다. 지금에야 GridView를 사용하는 방법으로 리사이클링을 했지만, 지금 보면 왜 그랬었을까 싶다. 그래도 개발에 대해서 많은 생각을 키울 수 있었던 프로젝트였다.

개발기간 : 2019.09.24. ~ 2019.10.22.

//github.com/gurdlwl/Android_ShoppingMall

gurdlwl/Android_ShoppingMall

android 쇼핑몰. Contribute to gurdlwl/Android_ShoppingMall development by creating an account on GitHub.

github.com

Toplist

최신 우편물

태그