TO DO
JSON 파일에 ImageDatas 저장하고, local storage에 저장하기
Code1: 이미지 JSON 파일에 저장 후 사용
implementation ("com.google.code.gson:gson:2.8.8")
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools"> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <application android:allowBackup="true" android:dataExtractionRules="@xml/data_extraction_rules" android:fullBackupContent="@xml/backup_rules" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" android:theme="@style/Theme.Project1" tools:targetApi="31"> <activity android:name=".MainActivity" android:exported="true"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
package com.example.project1 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; @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) 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) 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(200, 200) 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 ?: "" imageView.setOnClickListener { showImageDialog(imageUri, description) } 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) dialogImageView.setImageURI(uri) val alertDialog = AlertDialog.Builder(context) .setView(dialogView) .setPositiveButton("Select") { dialog, _ -> val description = editTextDescription.text.toString() imageDatas.add(ImageData(uri, description)) imageUris.add(uri) notifyDataSetChanged() dialog.dismiss() // 이미지 데이터가 추가될 때마다 JSON 파일에 저장 writeImageDataToJsonFile() } .setNegativeButton("Cancel") { dialog, _ -> dialog.dismiss() } .show() } private fun showImageDialog(imageUri: Uri, description: 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) dialogImageView.setImageURI(imageUri) // 이미지 설정 textViewDescription.text = description // 텍스트 설정 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) 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") myGridAdapter.imageDatas.add(ImageData(imageUri, description)) myGridAdapter.imageUris.add(imageUri) } myGridAdapter.notifyDataSetChanged() } catch (e: Exception) { e.printStackTrace() } } }
- Splash
폰트 변경
[Android] 어플리케이션 글꼴 변경하기 + 글꼴 일괄 적용
안드로이드 스튜디오에서 기본으로 제공하는 글꼴 외에도 자기가 원하는 글꼴을 적용하고 싶을때가 있다. 그럴때는 해당 글꼴의 .ttf or .otf 파일을 안드로이드 스튜디오에 넣어줌으로써 해당 글꼴을 적용할 수 있다. 1. 폰트 디렉토리 생성 폰트를 추가하기 위해서는 먼저, /res/font/ 아래에 추가를 해야하는데 font폴더가 없을경우 res -> New -> Directory 로 폴더를 생성해준다. 2. 폰트 넣어주기 그 다음 font 디렉토리에 적용하고 싶은 글꼴의 .ttf 파일을 넣어준다. (ctrl + c -> ctrl+v) 이때, 폰트 명을 반드시 소문자로 바꿔 주어야 한다 !! (대문자가 포함되어있을 시 파일을 옮긴 후 Refactor 혹은 옮기기 전에 파일명 소문자로 수정 후 옮겨주기) ..
Tab3 → ViewPager 만들기
Code
— kt
callee kt
Callee ---- package com.example.project1 import android.os.Bundle import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment import androidx.viewpager2.widget.ViewPager2 class Frag3_callee : AppCompatActivity() { private var viewPager2: ViewPager2? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_frag3_callee) // XML 파일명 수정 val fragments = ArrayList<Fragment>() fragments.add(Frag3_callee_frag1.newInstance(0)) fragments.add(Frag3_callee_frag2.newInstance(1)) viewPager2 = findViewById<ViewPager2>(R.id.viewPager2_container) val viewPager2Adapter = Frag3_callee_adapter(this, fragments) viewPager2!!.adapter = viewPager2Adapter } }
callee adapter
package com.example.project1 import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentActivity import androidx.viewpager2.adapter.FragmentStateAdapter class Frag3_callee_adapter(fragmentActivity: FragmentActivity, private val mFragments: List<Fragment>) : FragmentStateAdapter(fragmentActivity) { override fun createFragment(position: Int): Fragment { return mFragments[position] } override fun getItemCount(): Int { return mFragments.size } }
callee flag1
package com.example.project1 import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment class Frag3_callee_frag1 : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Arguments에서 데이터를 가져와 사용하는 부분 arguments?.let { val num = it.getInt("number") // 필요한 경우 'num'을 사용하여 추가적인 작업을 수행합니다. } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Fragment1의 XML 레이아웃 파일을 인플레이트하여 반환합니다. return inflater.inflate(R.layout.fragment_frag3_callee_frag1, container, false) // XML 파일명 수정 } companion object { fun newInstance(number: Int): Frag3_callee_frag1 { val fragment1 = Frag3_callee_frag1() val bundle = Bundle() bundle.putInt("number", number) fragment1.arguments = bundle return fragment1 } } }
callee flag2
package com.example.project1 import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment class Frag3_callee_frag2 : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Arguments에서 데이터를 가져와 사용하는 부분 arguments?.let { val num = it.getInt("number") // 필요한 경우 'num'을 사용하여 추가적인 작업을 수행합니다. } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { // Fragment1의 XML 레이아웃 파일을 인플레이트하여 반환합니다. return inflater.inflate(R.layout.fragment_frag3_callee_frag2, container, false) // XML 파일명 수정 } companion object { fun newInstance(number: Int): Frag3_callee_frag2 { val fragment1 = Frag3_callee_frag2() val bundle = Bundle() bundle.putInt("number", number) fragment1.arguments = bundle return fragment1 } } }
— xml
callee
<?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" tools:context=".MainActivity"> <androidx.viewpager2.widget.ViewPager2 android:id="@+id/viewPager2_container" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
callee flag1
<?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" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Fragment1" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
callee flag2
<?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" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="Fragment2" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" /> </androidx.constraintlayout.widget.ConstraintLayout>
Tab3 → Frag2 Raw하게 구현
쓰레기 코드..
Tag Matching
package com.example.project1 import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.BaseAdapter import android.widget.GridView import android.widget.ImageView import androidx.fragment.app.Fragment import org.json.JSONArray import org.json.JSONObject import java.io.BufferedReader import java.io.InputStreamReader class Frag3 : Fragment() { private lateinit var gridView: GridView private lateinit var myGridAdapter: MyGridAdapter private val imageDataList = ArrayList<ImageData>() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { val view = inflater.inflate(R.layout.fragment_frag3, container, false) gridView = view.findViewById(R.id.gridView) myGridAdapter = MyGridAdapter(requireContext()) gridView.adapter = myGridAdapter // Fragment가 생성될 때 이미지 데이터를 로드 loadSavedImageData() return view } // JSON 파일에서 이미지 데이터를 로드 private fun loadSavedImageData() { 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)) } // '제주도' 태그가 있는 이미지들을 필터링하여 adapter에 추가 filterImagesWithTag("제주도") } catch (e: Exception) { e.printStackTrace() } } private fun filterImagesWithTag(tagToFilter: String) { for (imageData in imageDataList) { if (imageData.tag == tagToFilter) { myGridAdapter.addImage(imageData.imageUri) } } } inner class MyGridAdapter(private val context: android.content.Context) : BaseAdapter() { private val imageUris = ArrayList<Uri>() 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(200, 200) scaleType = ImageView.ScaleType.CENTER_CROP setPadding(0, 0, 0, 0) } val imageUri = getItem(position) as Uri imageView.setImageURI(imageUri) return imageView } fun addImage(uri: Uri) { imageUris.add(uri) notifyDataSetChanged() } } data class ImageData(val imageUri: Uri, val description: String, val tag: String) }
오늘의 Tip
intent: frag 이동하는 코드이다.
val intent = Intent(requireContext(), Frag3_Callee::class.java)이 때는 menifest에서
<activity android:name=".Frag3_Callee"(= 이동할 frag) android:exported="false" />등을 작성해 주어야 한다.
