진행 순서 : Layout 🔗 → jpg 🖼️ → pdf 💾 → Print 🖨️
val root = binding.root
root.setBackgroundColor(Color.WHITE)
Bitmap.createBitmap(root.width, root.height, Bitmap.Config.ARGB_8888)
변환할 레이아웃을 가져온다. 나는 A4 사이즈로 미리 작업한 xml 파일 전체 레이아웃을 변환하고 싶었기 때문에 root 레이아웃을 가져왔다.
이 블로그를 참고하면서 비트맵에 가로세로 값을 주는데,
Bitmap.createBitmap(root.width, root.height, Bitmap.Config.ARGB_8888)
이상하게 나는 root.width
값과 root.height
값만 주면 값이 0으로 나왔다...
아직 View가 그려지지 않았는데 값을 달라고하니 0을 반환하는듯 했다.
프래그먼트나 액티비티 상속을 받고 생명주기 안에 넣어봐도 해결되지 않았다.
결국 직접 크기를 주다가 레이아웃이 자꾸 비뚤어져 .measure()
과 .layout()
을 이용하여 크기를 주게 되었다.
val bitmap = root.drawToBitmap(Bitmap.Config.ARGB_8888)
val bytes = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, bytes)
val dirPath = "/sdcard/DCIM/LayoutImage"
File(dirPath).mkdir()
val file = File(dirPath + "image.jpg")
.drawToBitmap()
을 이용해 View의 내용을 비트맵으로 그리고 데이터를 바이트 배열로 저장한다.
이 바이트 배열을 PNG 형식의 이미지로 압축하고 경로를 설정한다. 나는 이미지 파일을 저장할 새로운 폴더를 만들어 경로를 주었다.
file.createNewFile()
val fileOS = FileOutputStream(file)
fileOS.write(bytes.toByteArray())
fileOS.close()
.FileOutputStream()
으로 위에서 만든 이미지 파일을 저장한다. 파일 저장후 스트림은 꼭 닫아주자!
// itextpdf (change images to pdf)
implementation 'com.itextpdf:itextpdf:5.0.6'
pdf 저장을 위해 itext 라이브러리 이용
val dirPath = "/sdcard/DCIM/LayoutPdf"
File(dirPath).mkdir()
val currentTime: Long = System.currentTimeMillis()
val dateFormat = SimpleDateFormat("yyyy.MM.dd_HHmmss").format(currentTime)
val pdfFilePath = dirPath + "/${dateFormat}.pdf"
pdf를 저장할 폴더를 만들어주고 파일명도 정해준다. 이미지 파일은 pdf 변환후 삭제할거라 image라고 이름을 주었지만 pdf는 보관하고 있을거라, 저장되는 순서와 파일 구분을 위해 .dateFormat
으로 현재 날짜와 시간을 파일명으로 주었다.
val document = Document()
PdfWriter.getInstance(document, FileOutputStream(pdfFilePath))
document.open()
val image = Image.getInstance(file.toString())
val sclar = ((document.getPageSize().getWidth()
- document.leftMargin()
- document.rightMargin() - 0)
/ image.getWidth()) * 100
image.scalePercent(sclar)
image.alignment = Image.ALIGN_CENTER or Image.ALIGN_TOP
document.add(image)
document.close()
pdf를 저장하기 위한 빈 Document 인스턴스 생성
Document로 문서를 쓰기위해 위에서 만든 폴더와 경로를 FileOutputStream으로 연다.
이미지 파일을 로드하여 이미지를 문서 페이지 너비에 맞게 조정하고 정렬을 설정한 뒤 이 조정된 이미지를 문서에 추가한다.
작성이 끝난뒤 document를 닫아준다.
file.delete()
doPdfPrint(requireContext(), pdfFilePath = pdfFilePath)
pdf 저장이 끝났으니 기존에 저장했던 이미지 파일은 지워주고 pdf 인쇄를 시작한다. 이 메소드는 아래에 이어서...
처음에는 pdf로 만들지 않고 이미지를 그대로 출력했다.
PrintHelper를 이용하면
private fun doPhotoPrint() {
activity?.also { context ->
PrintHelper(context).apply {
scaleMode = PrintHelper.SCALE_MODE_FIT
}.also { printHelper ->
val bitmap = BitmapFactory.decodeResource(resources, R.drawable.droids)
printHelper.printBitmap("droids.jpg - test print", bitmap)
}
}
}
이걸로 코드가 끝나는데 더 자세한 설정을 조정하고 싶어 pdf로 만들어 PrintDocumentAdapter를 이용하기로 헀다. (이미지도 Adapter로 프린트가 된다고 하는데 뭔가 잘못했는지 인쇄가 안돼서... 안드로이드 공식문서에 써있는대로 pdf로 바꿔줬다.)
이 메소드는 Context와 pdf 파일경로(String)을 파라미터로 받는다.
val printManager = context.getSystemService(Context.PRINT_SERVICE) as PrintManager
PrintManager는 JobName과 Adapter, Attributes를 이용해 인쇄를 해주는 역할을 한다.
지금부터 Adapter, Attributes 를 만들어주고 이 값을 주면 된다.
val printAdapter = object : PrintDocumentAdapter() {}
PrintDocumentAdapter를 만들어주는데 인터페이스를 상속하는 객체이기때문에 object 키워드를 사용해서 생성한다. PrintDocumentAdapter 추상 클래스는 인쇄 수명 주기를 처리하도록 설계되었으며, 수명 주기에는 네 가지 기본 콜백 메서드가 있다.
onStart()
: 인쇄 프로세스가 시작될 때 한 번 호출, 일회성 작업 처리onLayout()
: 인쇄 설정을 변경할 때마다 호출, 예상되는 인쇄 페이지 수를 반환해야함onWrite()
: 인쇄 페이지를 인쇄할 파일로 렌더링하기 위해 호출, onLayout() 호출 후 한 번 이상 호출onFinish()
: 인쇄 프로세스가 끝날 때 한 번 호출, 일회성 작업 처리여기서 onStart()와 onFinish()는 필수로 작성해야하는 메소드는 아니므로 필요에 따라 작성해주면 된다.
override fun onLayout(
p0: PrintAttributes?,
p1: PrintAttributes?,
p2: CancellationSignal?,
p3: LayoutResultCallback?,
p4: Bundle?
) {
if (p2?.isCanceled == true) {
p3?.onLayoutCancelled()
return
}
val printInfo = PrintDocumentInfo.Builder("pdf - print")
.setContentType(PrintDocumentInfo.CONTENT_TYPE_DOCUMENT)
.build()
p3?.onLayoutFinished(printInfo, true)
}
onLayout에서 도큐먼트타입을 PrintDocumentInfo.CONTENT_TYPE_DOCUMENT로 셋해준다.
(이미지의 경우 여기서 PrintDocumentInfo.CONTENT_TYPE_PHOTO로 설정하면 된다고 한다...나는 실패했지만...)
override fun onWrite(
p0: Array<out PageRange>?,
p1: ParcelFileDescriptor?,
p2: CancellationSignal?,
p3: WriteResultCallback?
) {
if (p2?.isCanceled == true) {
p3?.onWriteCancelled()
return
}
val file = File(pdfFilePath)
val input = FileInputStream(file)
val output = FileOutputStream(p1?.fileDescriptor)
val buffer = ByteArray(16384)
var size: Int
while (input.read(buffer).also { size = it } >= 0) {
output.write(buffer, 0, size)
}
input.close()
output.close()
p3?.onWriteFinished(arrayOf(PageRange.ALL_PAGES))
}
pdf 경로를 이용하여 File 객체를 만들고 파일에서 데이터를 읽어서 버퍼에 저장하고 출력 파일에 쓴다. 파일 사이즈까지 반복.
쓰기 작업이 완료되면 onWriteFinished 함수를 호출하고 모든 페이지를 출력했음을 나타내는 PageRange 배열을 전달한다.
나의 경우 다이얼로그에서 작업했기때문에 onFinish()까지 작성해주었고, dismiss() 하는 작업을 해주었다.
val printAttributes = PrintAttributes.Builder()
.setColorMode(PrintAttributes.COLOR_MODE_COLOR)
.setMediaSize(PrintAttributes.MediaSize.ISO_A4)
.setResolution(PrintAttributes.Resolution("print","pdf",300,300))
.build()
Attributes는 본인이 인쇄하기위해 필요한 속성들을 설정해놓을 수 있다. 컬러, 인쇄 사이즈, 해상도 등을 설정하여 빌드해주면 된다.
printManager.print("pdf - print", printAdapter, printAttributes)
마지막으로 PrintManager에게 print()를 하고, 인자로 위에서 작업해준 Adapter와 Attributes를 주면된다.
도움 받은 곳
사진 인쇄 | Android 개발자 | Android Developers