복사 및 붙여넣기를 위한 Android 클립보드 기반 프레임워크는 다음을 비롯한 기본 및 복잡한 데이터 유형을 지원합니다.
- 텍스트 문자열
- 복잡한 데이터 구조
- 텍스트 및 바이너리 스트림 데이터
- 애플리케이션 확장 소재
간단한 텍스트 데이터는 클립보드에 직접 저장되는 반면 복잡한 데이터는 붙여넣기 애플리케이션이 콘텐츠 제공자로 확인하는 참조로 저장됩니다.
복사 및 붙여넣기는 프레임워크를 구현하는 애플리케이션 내에서와 애플리케이션 간에 모두 작동합니다.
프레임워크의 일부는 콘텐츠 제공자를 사용하므로 이 문서에서는 Android 콘텐츠 제공자 API에 익숙하다고 가정합니다.
텍스트 작업
일부 구성요소는 다음 표와 같이 기본적으로 텍스트 복사 및 붙여넣기를 지원합니다.
| 구성요소 | 텍스트 복사 | 텍스트 붙여넣기 |
|---|---|---|
| BasicTextField | ✅ | ✅ |
| TextField | ✅ | ✅ |
| SelectionContainer | ✅ |
예를 들어 다음 스니펫에서 카드에 있는 텍스트를 클립보드에 복사하고 복사된 텍스트를 TextField에 붙여넣을 수 있습니다.
TextField를 길게 터치하거나 커서 핸들을 탭하여 텍스트를 붙여넣을 메뉴를 표시합니다.
val textFieldState = rememberTextFieldState() Column { Card { SelectionContainer { Text("You can copy this text") } } BasicTextField(state = textFieldState) }
다음 키보드 단축키( Ctrl+V)를 사용하여 텍스트를 붙여넣을 수 있습니다 . 키보드 단축키는 기본적으로도 사용할 수 있습니다. 자세한 내용은 키보드 작업 처리를 참고하세요.
ClipboardManager로 복사
ClipboardManager를 사용하여 텍스트를 클립보드에 복사할 수 있습니다.
setText() 메서드는 전달된 AnnotatedString 객체를 클립보드에 복사합니다.
다음 스니펫은 사용자가 버튼을 클릭할 때 'Hello, clipboard'를 클립보드에 복사합니다.
// Retrieve a ClipboardManager object val clipboardManager = LocalClipboardManager.current Button( onClick = { // Copy "Hello, clipboard" to the clipboard clipboardManager.setText(AnnotatedString("Hello, clipboard")) } ) { Text("Click to copy a text") }
다음 스니펫은 동일한 작업을 실행하지만 더 세부적으로 제어할 수 있습니다.
일반적인 사용 사례는 비밀번호와 같은 민감한 콘텐츠를 복사하는 것입니다. ClipEntry 는 클립보드의 항목을 설명합니다.
클립보드의 데이터를 설명하는 ClipData 객체를 포함합니다.
ClipData.newPlainText() 메서드는 String 객체에서 ClipData 객체를 만드는 편의 메서드입니다.
생성된 ClipEntry 객체를 ClipboardManager 객체에서 setClip() 메서드를 호출하여 클립보드에 설정할 수 있습니다.
// Retrieve a ClipboardManager object val clipboardManager = LocalClipboardManager.current Button( onClick = { val clipData = ClipData.newPlainText("plain text", "Hello, clipboard") val clipEntry = ClipEntry(clipData) clipboardManager.setClip(clipEntry) } ) { Text("Click to copy a text") }
ClipboardManager로 붙여넣기
`ClipboardManager`에서 getText() 메서드를 호출하여 클립보드에 복사된 텍스트에 액세스할 수 있습니다.
텍스트가 클립보드에 복사되면 getText() 메서드는 AnnotatedString 객체를 반환합니다.
다음 스니펫은 클립보드의 텍스트를 TextField의 텍스트에 추가합니다.
val textFieldState = rememberTextFieldState() Column { TextField(state = textFieldState) Button( onClick = { // The getText method returns an AnnotatedString object or null val annotatedString = clipboardManager.getText() if (annotatedString != null) { // The pasted text is placed on the tail of the TextField textFieldState.edit { append(annotatedString.text) } } } ) { Text("Click to paste the text in the clipboard") } }
리치 콘텐츠 작업
사용자는 이미지, 동영상, 기타 표현력이 풍부한 콘텐츠를 좋아합니다.
앱을 사용하면 사용자가 ClipboardManager 및 ClipEntry를 사용하여 리치 콘텐츠를 복사할 수 있습니다.
contentReceiver 수정자를 사용하면 리치 콘텐츠 붙여넣기를 구현할 수 있습니다.
리치 콘텐츠 복사
앱은 리치 콘텐츠를 클립보드에 직접 복사할 수 없습니다.
대신 앱은 URI 객체를 클립보드
에 전달하고 ContentProvider를 사용하여 콘텐츠에 액세스할 수 있도록 합니다.
다음 코드 스니펫은 JPEG 이미지를 클립보드에 복사하는 방법을 보여줍니다.
자세한 내용은 데이터 스트림 복사를 참고하세요.
// Get a reference to the context val context = LocalContext.current Button( onClick = { // URI of the copied JPEG data val uri = Uri.parse("content://your.app.authority/0.jpg") // Create a ClipData object from the URI value // A ContentResolver finds a proper ContentProvider so that ClipData.newUri can set appropriate MIME type to the given URI val clipData = ClipData.newUri(context.contentResolver, "Copied", uri) // Create a ClipEntry object from the clipData value val clipEntry = ClipEntry(clipData) // Copy the JPEG data to the clipboard clipboardManager.setClip(clipEntry) } ) { Text("Copy a JPEG data") }
리치 콘텐츠 붙여넣기
contentReceiver 수정자를 사용하면 수정된 구성요소의 BasicTextField에 리치 콘텐츠
를 붙여넣는 작업을 처리할 수 있습니다.
다음 코드 스니펫은 이미지 데이터의 붙여넣은 URI를
Uri 객체 목록에 추가합니다.
// A URI list of images val imageList = remember { mutableStateListOf<Uri>() } // Remember the ReceiveContentListener object as it is created inside a Composable scope val receiveContentListener = remember { ReceiveContentListener { transferableContent -> // Handle the pasted data if it is image data when { // Check if the pasted data is an image or not transferableContent.hasMediaType(MediaType.Image) -> { // Handle for each ClipData.Item object // The consume() method returns a new TransferableContent object containging ignored ClipData.Item objects transferableContent.consume { item -> val uri = item.uri if (uri != null) { imageList.add(uri) } // Mark the ClipData.Item object consumed when the retrieved URI is not null uri != null } } // Return the given transferableContent when the pasted data is not an image else -> transferableContent } } } val textFieldState = rememberTextFieldState() BasicTextField( state = textFieldState, modifier = Modifier .contentReceiver(receiveContentListener) .fillMaxWidth() .height(48.dp) )
contentReceiver 수정자는 ReceiveContentListener 객체
를 인수로 사용하고 사용자가 수정된 구성요소 내의 BasicTextField에 데이터를 붙여넣을 때 전달된 객체의 onReceive
메서드를 호출합니다.
A TransferableContent 객체는 onReceive 메서드에 전달되며,
이 경우 붙여넣기를 통해 앱 간에 전송할 수 있는 데이터를 설명합니다.
clipEntry 속성을 참조하여 ClipEntry 객체에 액세스할 수 있습니다.
ClipEntry 객체에는 사용자가 여러 이미지를 선택하고 클립보드에 복사하는 경우와 같이 여러 ClipData.Item 객체가 있을 수 있습니다.
각 ClipData.Item 객체에 사용됨 또는 무시됨을 표시하고 가장 가까운 상위 contentReceiver 수정자가 수신할 수 있도록 무시된 ClipData.Item 객체를 포함하는 TransferableContent를 반환해야 합니다.
TransferableContent.hasMediaType() 메서드를 사용하면
TransferableContent 객체가 미디어 유형이 있는 항목을 제공할 수 있는지 확인할 수 있습니다.
예를 들어 다음 메서드 호출은 TransferableContent 객체가 이미지를 제공할 수 있는 경우 true를 반환합니다.
transferableContent.hasMediaType(MediaType.Image)
복잡한 데이터 작업
리치 콘텐츠와 동일한 방식으로 복잡한 데이터를 클립보드에 복사할 수 있습니다. 자세한 내용은 콘텐츠 제공자를 사용하여 복잡한 데이터 복사를 참고하세요.
리치 콘텐츠와 동일한 방식으로 복잡한 데이터의 붙여넣기를 처리할 수도 있습니다.
붙여넣은 데이터의 URI를 수신할 수 있습니다.
실제 데이터는 ContentProvider에서 가져올 수 있습니다.
자세한 내용은 제공자에서 데이터 가져오기를 참고하세요.
콘텐츠 복사에 관한 의견
사용자는 콘텐츠를 클립보드에 복사할 때 의견을 기대하므로 복사 및 붙여넣기를 지원하는 프레임워크 외에도 Android는 사용자가 Android 13 (API 수준 33) 이상에서 복사할 때 사용자에게 기본 UI를 표시합니다. 이 기능으로 인해 알림이 중복될 위험이 있습니다. 이 엣지 케이스에 관한 자세한 내용은 중복 알림 방지를 참고하세요.
Android 12L (API 수준 32) 이하에서 복사할 때 사용자에게 의견을 수동으로 제공합니다. 권장사항을 참고하세요.
민감한 콘텐츠
앱에서 사용자가 비밀번호와 같은 민감한 콘텐츠를 클립보드에 복사하도록 선택하는 경우 시스템에서 복사된 민감한 콘텐츠가 UI에 표시되지 않도록 앱에서 시스템에 알려야 합니다 (그림 2).
ClipboardManager 객체에서 setClip() 메서드를 호출하기 전에 ClipData
의 ClipDescription에 플래그를 추가해야 합니다.
// If your app is compiled with the API level 33 SDK or higher. clipData.apply { description.extras = PersistableBundle().apply { putBoolean(ClipDescription.EXTRA_IS_SENSITIVE, true) } } // If your app is compiled with a lower SDK. clipData.apply { description.extras = PersistableBundle().apply { putBoolean("android.content.extra.IS_SENSITIVE", true) } }