To Do
Design
Buttion이나, 등등 모양 바꾸고 싶다면,
drawable에 png 추가한 다름 android:background(”png)로 추가하기
Splash
[안드로이드] Splash 화면 구현하기
1. 먼저 Splash 화면이 나올 activity 파일 만들기 - app 우클릭 -> New -> Activity -> Empty Views Activity - 나는 파일명으로 SplashActivity 라고 했다 2. Splash 화면에 넣을 이미지를 다운 받아서 drawable 파일에 넣기 - 나는 네이버 웹툰 이미지를 다운 받아서 drawable 파일에 넣었다 3. activity_splash.xml - 그냥 이미지를 넣으면 내가 원하는 화면에 꽉차는게 안되서 - ImageView 안에 코드를 추가 시켜 주었다 android:scaleType="centerCrop" - 이러면 이미지가 꽉차게 된다 4. SplashActivity 코드 설정 class SplashActivity : AppCompat..
→ 이 링크에서 Theme 수정 전까지 따라하면 된다. 단 이러면 기본 로고(default splash screen)가 뜨고, 우리가 원하는 splash 화면이 뜨는 현상이 발생한다.
Tab 전환 애니메이션
res에 anim 폴더를 만들고, 애니메이션을 넣는다.
Main Menu 만들기
SearchView 만들기
오늘 많이 했따!!
Some Tips
setContentView : layout 연결 OnCreatView load
// Fragment에서 onCreateView를 통해 레이아웃 로드 예시 override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { // Fragment의 onCreateView 메서드에서 inflater를 사용하여 fragment_layout.xml 파일을 로드하여 뷰로 반환 return inflater.inflate(R.layout.fragment_layout, container, false) }
OnCreat load
// Activity에서 setContentView를 통해 레이아웃 로드 예시 override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) // activity_main.xml 파일을 로드하여 화면에 표시 }
adapt의 개념?: view와 data를 이어주는 객체
원래 메인
package com.example.project1 import android.content.pm.PackageManager import android.os.Bundle import android.util.Log import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import com.example.project1.contact.Frag1 import com.example.project1.databinding.ActivityMainBinding import com.example.project1.diary.Frag3 import com.example.project1.photobox.Frag2 class MainActivity : AppCompatActivity() { private var mBinding: ActivityMainBinding? = null private val binding get() = mBinding!! override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mBinding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) binding.bottomNavi.setOnItemSelectedListener { item -> when (item.itemId) { R.id.action_airplane -> { setFrag(0) } R.id.action_airport_shuttle -> { setFrag(1) } R.id.action_bluetooth -> { setFrag(2) } else -> { false } } } setFrag(0) } private fun setFrag(fragNum: Int): Boolean { val ft = supportFragmentManager.beginTransaction() when (fragNum) { 0 -> { ft.replace(R.id.main_frame, Frag1()).commit() } 1 -> { ft.replace(R.id.main_frame, Frag2()).commit() } 2 -> { ft.replace(R.id.main_frame, Frag3()).commit() } } return true } override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { Log.d("test", "permission granted") } else{ Log.d("test", "permission denied") Toast.makeText(applicationContext, "권한이 거부되어 이 기능을 사용할 수 없어요. 설정에서 허락해 주세요", Toast.LENGTH_SHORT).show() } } }
Frag2 keep
package com.example.project1.photobox import android.app.Activity import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle import android.provider.MediaStore import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.Button import android.widget.EditText import android.widget.GridView import android.widget.ImageView import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import org.json.JSONArray import org.json.JSONObject import java.io.BufferedReader import java.io.InputStreamReader import android.Manifest; import android.view.animation.AnimationUtils import android.widget.SearchView import com.example.project1.R @Suppress("DEPRECATION") class Frag2 : Fragment() { private val PERMISSION_REQUEST_CODE = 1001 // 권한 요청 코드 정의 private val PICK_IMAGE_REQUEST = 1 private lateinit var gridView: GridView private lateinit var myGridAdapter: MyGridAdapter override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_frag2, container, false) // Apply animation here val animation = AnimationUtils.loadAnimation(requireContext(), R.anim.animation3) view.startAnimation(animation) gridView = view.findViewById(R.id.gridView) myGridAdapter = MyGridAdapter(requireContext()) gridView.adapter = myGridAdapter val selectImageButton = view.findViewById<Button>(R.id.select_image_button) selectImageButton.setOnClickListener { openGallery() } // Fragment가 생성될 때 저장된 이미지 데이터를 로드 readImageDataFromJsonFile() return view } private fun openGallery() { if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE) } else { val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) startActivityForResult(intent, PICK_IMAGE_REQUEST) } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == PERMISSION_REQUEST_CODE) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { openGallery() } else { // Handle permission denied Toast.makeText(requireContext(), "Permission denied", Toast.LENGTH_SHORT).show() } } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == PICK_IMAGE_REQUEST && resultCode == Activity.RESULT_OK && data != null) { val selectedImageUri = data.data if (selectedImageUri != null) { myGridAdapter.addImage(selectedImageUri) } } } data class ImageData(val imageUri: Uri, val description: String, val tag: String) inner class MyGridAdapter(private val context: Context) : BaseAdapter() { val imageUris = ArrayList<Uri>() val imageDatas = ArrayList<ImageData>() override fun getCount(): Int { return imageUris.size } override fun getItem(position: Int): Any { return imageUris[position] } override fun getItemId(position: Int): Long { return position.toLong() } override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val imageView = convertView as? ImageView ?: ImageView(context).apply { layoutParams = ViewGroup.LayoutParams(250, 250) scaleType = ImageView.ScaleType.CENTER_CROP setPadding(0, 0, 0, 0) } val imageUri = getItem(position) as Uri imageView.setImageURI(imageUri) val description = imageDatas.find { it.imageUri == imageUri }?.description ?: "" val tag = imageDatas.find { it.imageUri == imageUri }?.tag ?: "" imageView.setOnClickListener { showImageDialog(imageUri, description, tag) } return imageView } fun addImage(uri: Uri) { val dialogView = LayoutInflater.from(context).inflate(R.layout.image_text_input_dialog, null) val dialogImageView = dialogView.findViewById<ImageView>(R.id.dialogImageView) val editTextDescription = dialogView.findViewById<EditText>(R.id.editTextDescription) val editTextTag = dialogView.findViewById<EditText>(R.id.editTextTag) dialogImageView.setImageURI(uri) val alertDialog = AlertDialog.Builder(context) .setView(dialogView) .setPositiveButton("Select") { dialog, _ -> val description = editTextDescription.text.toString() val tag = editTextTag.text.toString() imageDatas.add(ImageData(uri, description, tag)) imageUris.add(uri) notifyDataSetChanged() dialog.dismiss() // 이미지 데이터가 추가될 때마다 JSON 파일에 저장 writeImageDataToJsonFile() } .setNegativeButton("Cancel") { dialog, _ -> dialog.dismiss() } .show() } private fun showImageDialog(imageUri: Uri, description: String, tag: String) { val dialogView = LayoutInflater.from(context).inflate(R.layout.image_dialog, null) val dialogImageView = dialogView.findViewById<ImageView>(R.id.dialogImageView) val textViewDescription = dialogView.findViewById<TextView>(R.id.textViewDescription) val textViewTag = dialogView.findViewById<TextView>(R.id.textViewTag) dialogImageView.setImageURI(imageUri) // 이미지 설정 textViewDescription.text = "가고 싶은 곳: $description" // 텍스트 설정 textViewTag.text = "여행지: $tag" // 텍스트 설정 AlertDialog.Builder(context) .setView(dialogView) .setPositiveButton("Close") { dialog, _ -> dialog.dismiss() } .setNegativeButton("Delete") { dialog, _ -> val position = imageUris.indexOf(imageUri) if (position != -1) { imageUris.removeAt(position) imageDatas.removeAt(position) notifyDataSetChanged() // 이미지가 삭제될 때마다 JSON 파일에 저장 writeImageDataToJsonFile() } dialog.dismiss() } .show() } } // ImageData 객체를 JSON 형식으로 변환 private fun ImageData.toJson(): String { val jsonObject = JSONObject() jsonObject.put("imageUri", imageUri.toString()) jsonObject.put("description", description) jsonObject.put("tag", tag) return jsonObject.toString() } // JSON 파일에 ImageData 리스트를 저장 private fun writeImageDataToJsonFile() { val jsonArray = JSONArray() for (imageData in myGridAdapter.imageDatas) { jsonArray.put(JSONObject(imageData.toJson())) } try { val fileOutputStream = requireContext().openFileOutput("imageData.json", Context.MODE_PRIVATE) fileOutputStream.write(jsonArray.toString().toByteArray()) fileOutputStream.close() } catch (e: Exception) { e.printStackTrace() } } // JSON 파일에서 ImageData 리스트를 읽어옴 private fun readImageDataFromJsonFile() { try { val fileInputStream = requireContext().openFileInput("imageData.json") val inputStreamReader = InputStreamReader(fileInputStream) val bufferedReader = BufferedReader(inputStreamReader) val stringBuilder = StringBuilder() var line: String? while (bufferedReader.readLine().also { line = it } != null) { stringBuilder.append(line) } fileInputStream.close() val jsonArray = JSONArray(stringBuilder.toString()) for (i in 0 until jsonArray.length()) { val jsonObject = jsonArray.getJSONObject(i) val imageUri = Uri.parse(jsonObject.getString("imageUri")) val description = jsonObject.getString("description") val tag = jsonObject.getString("tag") myGridAdapter.imageDatas.add(ImageData(imageUri, description, tag)) myGridAdapter.imageUris.add(imageUri) } myGridAdapter.notifyDataSetChanged() } catch (e: Exception) { e.printStackTrace() } } }
New Frag2
package com.example.project1.photobox import android.Manifest import android.app.Activity import android.content.Context import android.content.Intent import android.content.pm.PackageManager import android.net.Uri import android.os.Bundle import android.provider.MediaStore import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.Button import android.widget.EditText import android.widget.GridView import android.widget.ImageView import android.widget.TextView import android.widget.Toast import androidx.appcompat.app.AlertDialog import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import com.example.project1.R import org.json.JSONArray import org.json.JSONObject import java.io.BufferedReader import java.io.InputStreamReader import androidx.appcompat.widget.SearchView class Frag2 : Fragment() { private val PERMISSION_REQUEST_CODE = 1001 // 권한 요청 코드 정의 private val PICK_IMAGE_REQUEST = 1 private lateinit var gridView: GridView private lateinit var myGridAdapter: MyGridAdapter private val imageDataList = ArrayList<ImageData>() private var filteredImageDataList = ArrayList<ImageData>() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_frag2, container, false) val animation = AnimationUtils.loadAnimation(requireContext(), R.anim.animation3) view.startAnimation(animation) gridView = view.findViewById<GridView>(R.id.gridView) myGridAdapter = MyGridAdapter(requireContext()) gridView.adapter = myGridAdapter // Fragment가 생성될 때 이미지 데이터를 로드 readImageDataFromJsonFile() // SearchView 설정 val searchView = view.findViewById<SearchView>(R.id.search_view) searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { // 검색 버튼을 눌렀을 때 처리 return false } override fun onQueryTextChange(newText: String?): Boolean { // 검색어가 변경될 때마다 호출되는 처리 filterImages(newText) return true } }) // Select Image 버튼 설정 val selectImageButton = view.findViewById<Button>(R.id.select_image_button) selectImageButton.setOnClickListener { openGallery() } return view } private fun openGallery() { if (ContextCompat.checkSelfPermission(requireContext(), Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(requireActivity(), arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), PERMISSION_REQUEST_CODE) } else { val intent = Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) startActivityForResult(intent, PICK_IMAGE_REQUEST) } } override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == PERMISSION_REQUEST_CODE) { if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { openGallery() } else { // Handle permission denied Toast.makeText(requireContext(), "Permission denied", Toast.LENGTH_SHORT).show() } } } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if (requestCode == PICK_IMAGE_REQUEST && resultCode == Activity.RESULT_OK && data != null) { val selectedImageUri = data.data if (selectedImageUri != null) { myGridAdapter.addImage(selectedImageUri) } } } data class ImageData(val imageUri: Uri, val description: String, val tag: String) inner class MyGridAdapter(private val context: Context) : BaseAdapter() { var imageDataList = ArrayList<ImageData>() fun setImageDataList(imageDataList: List<ImageData>) { this.imageDataList = ArrayList(imageDataList) notifyDataSetChanged() } override fun getCount(): Int { return imageDataList.size } override fun getItem(position: Int): Any { return imageDataList[position] } override fun getItemId(position: Int): Long { return position.toLong() } override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { val imageView = convertView as? ImageView ?: ImageView(context).apply { layoutParams = ViewGroup.LayoutParams(250, 250) scaleType = ImageView.ScaleType.CENTER_CROP setPadding(0, 0, 0, 0) } val imageData = getItem(position) as ImageData imageView.setImageURI(imageData.imageUri) imageView.setOnClickListener { showImageDialog(imageData) } return imageView } fun addImage(uri: Uri) { val dialogView = LayoutInflater.from(context).inflate(R.layout.image_text_input_dialog, null) val dialogImageView = dialogView.findViewById<ImageView>(R.id.dialogImageView) val editTextDescription = dialogView.findViewById<EditText>(R.id.editTextDescription) val editTextTag = dialogView.findViewById<EditText>(R.id.editTextTag) dialogImageView.setImageURI(uri) val alertDialog = AlertDialog.Builder(context) .setView(dialogView) .setPositiveButton("Select") { dialog, _ -> val description = editTextDescription.text.toString() val tag = editTextTag.text.toString() imageDataList.add(ImageData(uri, description, tag)) notifyDataSetChanged() dialog.dismiss() // 이미지 데이터가 추가될 때마다 JSON 파일에 저장 writeImageDataToJsonFile() } .setNegativeButton("Cancel") { dialog, _ -> dialog.dismiss() } .show() } private fun showImageDialog(imageData: ImageData) { val dialogView = LayoutInflater.from(context).inflate(R.layout.image_dialog, null) val dialogImageView = dialogView.findViewById<ImageView>(R.id.dialogImageView) val textViewDescription = dialogView.findViewById<TextView>(R.id.textViewDescription) val textViewTag = dialogView.findViewById<TextView>(R.id.textViewTag) dialogImageView.setImageURI(imageData.imageUri) // 이미지 설정 textViewDescription.text = "가고 싶은 곳: ${imageData.description}" // 텍스트 설정 textViewTag.text = "여행지: ${imageData.tag}" // 텍스트 설정 AlertDialog.Builder(context) .setView(dialogView) .setPositiveButton("Close") { dialog, _ -> dialog.dismiss() } .show() } } // ImageData 객체를 JSON 형식으로 변환 private fun ImageData.toJson(): String { val jsonObject = JSONObject() jsonObject.put("imageUri", imageUri.toString()) jsonObject.put("description", description) jsonObject.put("tag", tag) return jsonObject.toString() } // JSON 파일에 ImageData 리스트를 저장 private fun writeImageDataToJsonFile() { val jsonArray = JSONArray() for (imageData in myGridAdapter.imageDataList) { jsonArray.put(JSONObject(imageData.toJson())) } try { val fileOutputStream = requireContext().openFileOutput("imageData.json", Context.MODE_PRIVATE) fileOutputStream.write(jsonArray.toString().toByteArray()) fileOutputStream.close() } catch (e: Exception) { e.printStackTrace() } } // JSON 파일에서 ImageData 리스트를 읽어옴 private fun readImageDataFromJsonFile() { try { val fileInputStream = requireContext().openFileInput("imageData.json") val inputStreamReader = InputStreamReader(fileInputStream) val bufferedReader = BufferedReader(inputStreamReader) val stringBuilder = StringBuilder() var line: String? while (bufferedReader.readLine().also { line = it } != null) { stringBuilder.append(line) } fileInputStream.close() val jsonArray = JSONArray(stringBuilder.toString()) for (i in 0 until jsonArray.length()) { val jsonObject = jsonArray.getJSONObject(i) val imageUri = Uri.parse(jsonObject.getString("imageUri")) val description = jsonObject.getString("description") val tag = jsonObject.getString("tag") imageDataList.add(ImageData(imageUri, description, tag)) } // 필터링 없이 모든 이미지를 일단 추가 filteredImageDataList.addAll(imageDataList) myGridAdapter.setImageDataList(filteredImageDataList) } catch (e: Exception) { e.printStackTrace() } } // SearchView를 통해 이미지 필터링 private fun filterImages(query: String?) { if (query.isNullOrBlank()) { // 검색어가 비어있으면 모든 이미지를 보여줌 myGridAdapter.setImageDataList(filteredImageDataList) } else { // 검색어가 있으면 해당 검색어를 포함하는 tag를 가진 이미지만 필터링 filteredImageDataList.clear() for (imageData in imageDataList) { if (imageData.tag.equals(query, ignoreCase = true)) { filteredImageDataList.add(imageData) } } myGridAdapter.setImageDataList(filteredImageDataList) } } }
Next ToDo
- 검색창 만들기 → 아직 완전하지 않다.. 보완하기
- CardView 디자인 더 이쁘게 할 수 있는 방법 생각해 보기.
ex. Using Grid layout
- 컨셉 설정하기
