안드로이드는 앱 간의 데이터 이동을 위해 OS 차원에서 클립보드를 지원하고 있다.
클립보드를 이용해 공유할 수 있는 데이터 포맷은 다음과 같다.
- 텍스트 : 단순 문자열.
- URI : 데이터의 위치를 가리키는 URI. 실제 데이터를 제공하려면 ContentProvider를 구현해야 한다.
- Intent : 앱 실행 명령과 관련된 데이터.
- HTML 텍스트 : HTML 형태의 서식있는 문자열. 4.1(API 16)부터 추가되었다.
각 데이터 타입에 따라 MIME 타입을 지정함으로써, 클립보드로부터 데이터를 받는 애플리케이션은 이 MIME 타입을 읽어서 데이터가 무슨 타입인지 판별할 수 있다.
클립보드는 저장 공간이 한개 뿐이므로, 한번에 하나의 데이터만 교환할 수 있다. 클립보드에 새로운 데이터가 들어오면, 기존에 있던 데이터는 삭제된다.(즉, 시스템 차원에서는 히스토리를 제공하지 않는다.)
주요 클래스
ClipboardManager
안드로이드에서 클립보드는 시스템이 관리하는 자원이므로, 애플리케이션이 마음대로 생성, 삭제할 수 없다.
대신, getSystemService(CLIPBOARD)SERVICE) 메서드를 이용해 객체를 얻을 수 있다.
이 객체를 이용해 클립보드에 데이터(ClipData)를 저장하거나, 저장된 데이터(ClipData)를 읽을 수 있다. ClipboardManager는 가장 최근의 ClipData 한 개만 가지고 있다.
ClipData
실제로 클립보드에 저장되는 주체로, 한 개 이상의 실제 데이터(ClipData.Item)와, 이 데이터의 메타데이터(ClipDescription)를 포함.
ClipDescription
클립보드에 저장될 데이터의 메타데이터를 나타내는 클래스
메타데이터 중에는 원본 데이터의 타입을 가리키는 MIME 타입의 배열을 지정할 수 있는데, 클립보드를 읽는 애플리케이션에서는 이 MIME타입을 읽어서, 처리할 수 있는 데이터를 식별
ClipData.Item
클립보드에 저장되는 실제 데이터. 텍스트, URI, Intent, HTML 텍스트를 저장가능.
ClipData객체에 한개 이상의 Item 객체를 추가할 수 있는데, 이는 다중 선택을 하나의 클립으로 취급하기 때문.
단, 여러 item을 ClipData에 추가하려면 item의 데이터 타입이 동일해야 함
Copy & Paste
Copy 구현
1.URI 형태로 임의의 데이터를 클립보드에 저장하려면, 해당 URI에 접근했을 때 실제 데이터를 얻을 수 있도록 ContentProvider를 제공
2.ClipboardManager 객체를 얻는다.
3.ClipData 객체를 생성
4. 클립보드에 추가
...
// 임의의 메서드
public void copy() {
// 클립보드 객체 얻기
ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
// 클립데이터 생성
ClipData clipData = ClipData.newPlainText("Test Clipboard", "Test"); //Test 가 실질적으로 복사되는 Text
// 클립보드에 추가
clipboardManager.setPrimaryClip(clipData);
...
}
...
Paste 구현
1.ClipboardManager 객체를 얻는다.
2.ClipDescription 객체를 얻어서 원하는 MIME 타입인지 조사
3.MIME 타입에 맞게 처리
...
// 임의의 메서드
public void paste() {
// 클립보드 객체 얻기
ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
if(!(clipboard.hasPrimaryClip())) {
// 클립보드 데이터가 있을 때 처리
ClipData data = clipboard.getPrimaryClip();
...
if(clipboard.getPrimaryClipDescription().hasMimeType(MIMETYPE_TEXT_PLAIN)) {
// MIME 타입이 텍스트일 때 처리
...
}
}
...
}
...
ClipboardManager의 함수 몇 가지
addPrimaryClipChangedListener() | clipboard의 클립이 변경될 때 호출되는 리스너 콜백을 정의 |
removePrimaryClipChangedListener() | 리스너 제거 |
clearPrimaryClip() | 현재 클립의 내용을 제거 |
getPrimaryClip() | 현재 클립의 내용을 반환 |
hasPrimaryClip() | 클립이 있는지 없는지에 대한 Boolean을 반환 |
setPrimaryClip(ClipData clip) | clipdata를 설정 |
Clipboard 변경 시점 알기
ClipboardManager객체에 ClipboardManager.OnPrimaryClipChangedListener 리스너를 등록하면, onPrimaryClipChanged() 메서드를 구현하여 클립보드가 변경되었을 때 콜백을 받을 수 있다.
Ex) Listener 등록/삭제
public class ClipboardService extends Service implements ClipboardManager.OnPrimaryClipChangedListener {
ClipboardManager mManager;
@Override
public void onCreate() {
super.onCreate();
mManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
// 리스너 등록
mManager.addPrimaryClipChangedListener(this);
}
@Override
public void onDestroy() {
super.onDestroy();
// 리스너 해제
mManager.removePrimaryClipChangedListener(this);
}
@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
throw new UnsupportedOperationException("Not yet implemented");
}
@Override
public void onPrimaryClipChanged() {
if (mManager != null && mManager.getPrimaryClip() != null) {
ClipData data = mManager.getPrimaryClip();
// 한번의 복사로 복수 데이터를 넣었을 수 있으므로, 모든 데이터를 가져온다.
int dataCount = data.getItemCount();
for (int i = 0 ; i < dataCount ; i++) {
Log.e("Test", "clip data - item : "+data.getItemAt(i).coerceToText(this));
}
} else {
Log.e("Test", "No Manager or No Clip data");
}
}
}
직접 사용해본 것은 Text를 이용해 ClipboardManager의 값을 불러와 Set을 해주었다
copyClipBoard()를 호출해 ClipboardManager로부터 복사한 Text를 가져와 TextView에 Set
private fun copyClipBoard() {
//context.getSystemService를 통해 ClipboardManager를 가져온다.
val clipboard = this.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
// clipboard 내부의 값을 가져온다.
val clipboardTest = clipboard.plainTextClip()
// 클립보드 내 값이 유효 && 길이가 20자 이하인지
if (!clipboardTest.isNullOrBlank() && clipboardTest.length <= 20) { //&& clipboardTest.isDigitsOnly()
AlertDialog.Builder(this)
.setTitle("클립 보드에 있는 $clipboardTest 를 붙여넣기")
.setPositiveButton("추가") { _, _ ->
binding.addEdittextTitle.setText(clipboardTest)
}
.setNegativeButton("취소") { _, _ -> }
.create()
.show()
}
}
// 클립보드 내 값의 유효성 검사
private fun ClipboardManager.plainTextClip(): String? =
// 클립보드 내의 값 저장 여부 & 해당 값이 text 형식인지 판단
if (hasPrimaryClip() && (primaryClipDescription?.hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN) == true)) {
// 클립보드의 text 값을 toString()을 통해 String 형태로 변환하여 return 한다.
primaryClip?.getItemAt(0)?.text?.toString()
} else {
// 클립보드 내 값이 없거나, text 형식이 아닐 시 null return
null
}
Activity에서 다른 Activity로 넘어갈 때 새로 띄워지는 Activity의 onCreate에서 바로 호출을 하니
Clipboard의 값이 null로 왔는데 찾아보니 primaryClip에서 가져오려고 할 때 앱에 포커스가 있어야 하기 때문에 발생한다고 하여서 버튼을 넣고 동작확인을 하였다.
Fragment에서는 Activity에 붙어있기 때문에 따로 버튼 안에 호출이 필요 없이 onCreate에서도 동작할 것 같다.
ClipBoard Manager
http://dktfrmaster.blogspot.com/2016/10/blog-post.html
https://hungseong.tistory.com/38?category=518367
'Android > Reference' 카테고리의 다른 글
OutLineTextView 글자 외곽선 (0) | 2022.04.03 |
---|---|
Context (0) | 2022.03.28 |
ViewPager Infinite, Auto Scroll (7) | 2022.03.08 |
권한 체크 Permission Check (0) | 2022.02.23 |
Fragment ViewModel 공유 (0) | 2022.02.22 |