레이블이 android인 게시물을 표시합니다. 모든 게시물 표시

     위와 같이 EditText를 사용하다보면 키보드가 올라가면서 화면이 밀리게 되는데요, windowSoftInputMode 속성으로 소프트 키보드가 출력될 때 화면 변화를 설정할 수도 있지만 또 다른 방법으로 내용을 편집할 수 있는 입력 창을...

위와 같이 EditText를 사용하다보면 키보드가 올라가면서 화면이 밀리게 되는데요,

windowSoftInputMode 속성으로 소프트 키보드가 출력될 때 화면 변화를 설정할 수도 있지만

또 다른 방법으로 내용을 편집할 수 있는 입력 창을 따로 띄워주는 Custom EditText을 

활용하는 것도 방법이 될 수 있을 것 같습니다.

 

 

 

잠깐 시작하기 전에

그래서 어떤 Custom EditText인지 사진으로 보는게 확실하기 때문에 결과 화면부터 보고 갑시다.


 




 

 

 

1. Custom EditText : InputTextView

Custom EditText 네이밍은 InputTextView로 했습니다.

InputTextView라는 클래스를 생성하고 아래와 같이 작성합니다.

 

class InputTextView(context: Context, attrs: AttributeSet) : LinearLayout(context, attrs) {
private lateinit var inputTextViewBinding: ViewInputTextBinding

init {
initView()
}

private fun initView() {
val layoutInflater = LayoutInflater.from(context)
inputTextViewBinding = ViewInputTextBinding.inflate(layoutInflater, this, true)

setOnClickListener { v: View? -> showEditDialog() }
}

private fun showEditDialog() {
val builder = AlertDialog.Builder(context)
val alertDialog = builder.create()
alertDialog.window?.setBackgroundDrawableResource(R.color.transparent)

val layoutInflater = LayoutInflater.from(context)
val inputTextDialogBinding = DialogInputTextBinding.inflate(layoutInflater, this, false)

inputTextDialogBinding.etText.setText(inputTextViewBinding.tvText.text)
inputTextDialogBinding.btnOk.setOnClickListener { v: View? ->
setText(inputTextDialogBinding.etText.text.toString())
alertDialog.dismiss()
}
inputTextDialogBinding.btnCancel.setOnClickListener { v: View? ->
alertDialog.dismiss()
}
alertDialog.setView(inputTextDialogBinding.root)
alertDialog.show()
}

fun setText(text: String) {
inputTextViewBinding.tvText.text = text
}

fun getText(): String {
return inputTextViewBinding.tvText.text.toString()
}
}

 

 

(참고)

R.color.transparent는 values 경로에 있는 colors.xml에 아래와 같이 추가해주면 됩니다.

colors.xml

<resources>
<color name="transparent">#00000000</color>
...
</resources>

 

 

 

 

 

 

 

2. InputTextView : layout

InputTextView에 대한 layout을 아래와 같이 작성합니다.


view_input_text.xml

<layout xmlns:android="http://schemas.android.com/apk/res/android">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">

<TextView
android:id="@+id/tvText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="3dp"
android:maxLines="1"
android:paddingStart="2dp"
android:paddingEnd="2dp"
android:singleLine="true"
android:textColor="@color/black"
android:textSize="17dp" />

<TextView
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_margin="3dp"
android:background="#222222" />
</LinearLayout>
</layout>

 

dialog_input_text.xml

<layout>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center">

<LinearLayout
android:layout_width="350dp"
android:layout_height="wrap_content"
android:background="#888888"
android:gravity="center"
android:orientation="vertical">

<TextView
android:id="@+id/tvTitle"
android:layout_width="match_parent"
android:layout_height="40dp"
android:background="#000000"
android:gravity="center"
android:text="편집"
android:textColor="@color/white"
android:textSize="20sp"
android:textStyle="bold" />

<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:orientation="vertical">

<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingStart="10dp"
android:paddingEnd="10dp">

<EditText
android:id="@+id/etText"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_margin="3dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:gravity="center"
android:selectAllOnFocus="true"
android:textSize="17sp" />
</LinearLayout>

<LinearLayout
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="25dp"
android:orientation="horizontal">

<Space
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="2" />

<Button
android:id="@+id/btnCancel"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="3dp"
android:layout_weight="1"
android:text="취소"
android:textAllCaps="false"
android:textSize="17sp" />

<Button
android:id="@+id/btnOk"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="3dp"
android:layout_weight="1"
android:text="확인"
android:textAllCaps="false"
android:textSize="17sp" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</LinearLayout>
</layout>

 

 

 

 

 

 

 

3. InputTextView 사용

Custom EditText가 준비되었으니 원하시는 layout에 아래와 같이 정의해서 사용해주시면 됩니다.

<com.example.customedittext.view.InputTextView
android:id="@+id/inputText"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

 

 


 

 

 

 

 

FTP(File Transfer Protocol)를 활용해서 파일 업로드 및 다운로드 예제를 정리하려고 합니다.         1. FTP 사전 준비 AndroidManifest 권한 추가  (파일 읽고/쓰기 권한은 MainActivity 에서도 따로...

FTP(File Transfer Protocol)를 활용해서 파일 업로드 및 다운로드 예제를 정리하려고 합니다.



 

 

 

 

1. FTP 사전 준비

AndroidManifest 권한 추가 

(파일 읽고/쓰기 권한은 MainActivity 에서도 따로 권한 요청을 해주시기 바랍니다.)

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<application
android:requestLegacyExternalStorage="true"

...
</application>

 

Gradle 라이브러리 추가

implementation("commons-net:commons-net:3.6")

 

 

 

 

 

 

 

2. FTPUtil 클래스

FTPUtil 이름을 가진 object class를 생성하였습니다.

코틀린 코루틴을 활용하여 파일 업로드 및 다운로드 동작을 구현하였습니다.

 

사용하실 때 FTP 설정을 빠트리면 안됩니다! (경험담..)

 

object FTPUtil {
private val coroutineScope = CoroutineScope(Dispatchers.Default)

private var clientFTP = FTPClient()

// Input your default setting
private var host = ""
private var user = ""
private var password = ""
private var port = 21

fun changeFTPSetting(host: String, user: String, password: String, port: Int) {
this.host = host
this.user = user
this.password = password
this.port = port
}

suspend fun downloadFile(srcFilePath: String, desFilePath: String): Boolean {
return coroutineScope.async {
var result = false
try {
if (connect()) {
val fileOutputStream = FileOutputStream(desFilePath)
result = clientFTP.retrieveFile(srcFilePath, fileOutputStream)
fileOutputStream.close()
}
} catch (e: Exception) {
println("[FTPUtil] e $e")
e.printStackTrace()
}

disconnect()

println("[FTPUtil] downloadFile result : $result")

result
}.await()
}

suspend fun uploadFile(srcFilePath: String, desFileName: String, desDirectory: String): Boolean {
return coroutineScope.async {
var result = false
try {
if (connect()) {
val fileInputStream = FileInputStream(srcFilePath)
if (changeDirectory(desDirectory)) {
result = clientFTP.storeFile(desFileName, fileInputStream)
}
fileInputStream.close()
}
} catch (e: Exception) {
println("[FTPUtil] e $e")
e.printStackTrace()
}

disconnect()

println("[FTPUtil] uploadFile $result")

result
}.await()
}

suspend fun getFiles(directory: String): ArrayList<FTPFile> {
return coroutineScope.async {
val fileList = ArrayList<FTPFile>()
try {
if (connect()) {
val ftpFiles = clientFTP.listFiles(directory)
for (file in ftpFiles) fileList.add(file)
}
} catch (e: Exception) {
println("[FTPUtil] e $e")
e.printStackTrace()
}

disconnect()

println("[FTPUtil] getFiles size ${fileList.size}")

fileList
}.await()
}

private fun connect(): Boolean {
clientFTP.controlEncoding = "euc-kr"
clientFTP.connect(host, port)
if (!FTPReply.isPositiveCompletion(clientFTP.replyCode)) {
println("[FTPUtil] $host, $port connect fail")
return false
}

if (!clientFTP.login(user, password)) {
println("[FTPUtil] $user, $password login fail")
return false
}

clientFTP.enterLocalPassiveMode()

println("[FTPUtil] FTP connected : host($host), user($user), password($password), port($port)")

return true
}

private fun disconnect(): Boolean {
try {
clientFTP.logout()
clientFTP.disconnect()
} catch (e: Exception) {
e.printStackTrace()
println("[FTPUtil] e $e")
}

return true
}

private fun changeDirectory(directory: String): Boolean {
try {
clientFTP.changeWorkingDirectory(directory)
return true
} catch (e: Exception) {
e.printStackTrace()
println("[FTPUtil] e $e")
}
return false
}

private fun getCurrentDirectory(): String {
return try {
clientFTP.printWorkingDirectory()
} catch (e: Exception) {
""
}
}
}

 

 

 

 

 

 

 

3. FTP 업로드 / 다운로드 사용 예시

Upload, Download, GetFiles 버튼을 생성하여 아래와 같이 동작시켜 확인할 수 있었습니다. 

 

class MainActivity : AppCompatActivity() {

private lateinit var binding: ActivityMainBinding

private val coroutineScope = CoroutineScope(Dispatchers.Default)

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

permissionCheck()

binding.btnUpload.setOnClickListener {
coroutineScope.launch {
val srcFilePath = "sdcard/Download/test.txt"
val desFileName = "test.txt"
val desDirectory = "/user/tempDir"
FTPUtil.uploadFile(srcFilePath, desFileName, desDirectory)
}
}

binding.btnDownload.setOnClickListener {
coroutineScope.launch {
val srcFilePath = "/user/tempDir/test.txt"
val desFilePath = "sdcard/Download/test.txt"
FTPUtil.downloadFile(srcFilePath, desFilePath)
}
}

binding.btnGetFiles.setOnClickListener {
coroutineScope.launch {
val directory = "/user/tempDir"
val files = FTPUtil.getFiles(directory)
for (file in files) {
println("getFiles : ${file.name}")
}
}
}
}

...