Существует множество вопросов о сохранении закадрового представления в растровом изображении, и я следил за всем безрезультатно, поэтому не думаю, что это дубликат.
Фон
Мое приложение предлагает два разных представления одних и тех же данных.Оба эти представления представлены с использованием пользовательских представлений, которые работают как задумано (один использует SurfaceView, что является проблемой для такого рода вещей).Существует требование об обмене этими данными в виде изображения, которое включает в себя оба вида (пользователю одновременно виден только один вид, они могут переключаться между видами).Для улучшения взаимодействия с пользователем и того, что одно и то же изображение генерируется для одних и тех же данных независимо от используемого устройства, эти данные должны каждый раз содержаться в растровом изображении (ну, в формате PNG) фиксированного размера.
Метод
Чтобы выполнить вышеизложенное, я создал
- Создан
fragment_screenshot_generator.xml
макет ниже: (Горизонтальный LinearLayout
с установленными шириной и высотой)на match_parent
, который содержит 2 FrameLayouts
, каждый с высотой match_parent
и layout_weight
, равный 1. Каждый занимает половину экрана. Для целей тестирования они имеют полупрозрачный фон (соответственно красный и зеленый) - Создано
DialogFragment
(диалоговое окно прогресса), которое должно выполнять эту работу в фоновом режиме и автоматически закрывать после сохранения снимка экрана в файл (возвращая файл в родительский фрагмент) - Во время работы диалогового окнаСоздав, я надуваю
fragment_screenshot_generator.xml
и настраиваю два типа представлений в каждом из FrameLayout
с. Здесь используется тот же код, что и для создания исходных видимых пользователю представлений. - Представлениекурица, отображаемая на холсте, подкрепленная растровым изображением
- Растровое изображение сжимается в файл
Проблема
Ничего, кроме фона видаотображается на растровом изображении.Я подозреваю, что где-то пропущен вызов, чтобы сообщить корню fragment_screenshot_generator.xml
, что "все хорошо" , чтобы представления внутри него отображались.Я попытался опубликовать в корне макета, но это очень непредсказуемо, иногда занимает очень много времени, и все же рисует только прозрачность.Нужно ли прикрепить этот макет к окну?Могу ли я прикрепить его к текущему окну, не показывая его?В моем коде что-то отсутствует?
Примечания
Я пробовал
- Включение кэша чертежа перед вызовом отрисовки.Не исправить.Это все равно не рекомендуется.
- Генерация растрового изображения непосредственно из кэша отрисовки представления.Не исправить.Выдает
NullPointerException
, потому что метод getDrawingCache
возвращает null
.Все равно не рекомендуется. - Рендеринг точно такого же вида на экране (как часть макета диалога) и сохранение его в растровом изображении.Это работает, но часть
SurfaceView
пуста.Это ожидается, потому что SurfaceView
- это, по сути, 'дыра' .Я подозреваю, что API PixelCopy
не пострадает от этого. - Я удалил фоновое выполнение из кода ниже для простоты.Так что не кричи на меня за то, что я запустил все это в главном потоке!:)
Код
fragment_screenshot_generator.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:orientation="horizontal">
<FrameLayout
android:id="@+id/view_1"
android:background="#4f00"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<FrameLayout
android:id="@+id/view_2"
android:background="#40f0"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
ScreenshotGeneratorDialogFragment#onCreateDialog
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
viewModel = ViewModelProviders.of(activity!!).get(DetailViewModel::class.java)
val parameter1 = viewModel.dataTypeOne.value!!.parameter1
val parameter2 = viewModel.dataTypeOne.value!!.parameter2
screenshotView = View.inflate(context, R.layout.fragment_screenshot_generator, null).apply {
measure(View.MeasureSpec.makeMeasureSpec(SCREENSHOT_WIDTH, View.MeasureSpec.EXACTLY),
View.MeasureSpec.makeMeasureSpec(SCREENSHOT_HEIGHT, View.MeasureSpec.EXACTLY))
layout(0, 0, measuredWidth, measuredHeight)
setupViewTypeOne(findViewById(R.id.view_1)!!, parameter1, parameter2)
setupViewTypeTwo(findViewById(R.id.view_2)!!, parameter1, parameter2, viewModel.plotData.value)
layout(0, 0, measuredWidth, measuredHeight)
}
dialogView = View.inflate(context, R.layout.dialog_screenshot_generator, null).apply {
findViewById<TextView>(R.id.progress_label)!!.setText(R.string.preparing_screenshots)
}
val dialog = AlertDialog.Builder(context!!)
.setView(dialogView)
.create()
dialog.setOnShowListener { capture() }
return dialog
}
ScreenshotGeneratorDialogFragment#capture
private fun capture():File {
var bitmap = Bitmap.createBitmap(screenshotView.measuredWidth, screenshotView.measuredHeight, Bitmap.Config.ARGB_8888)
var canvas = Canvas(bitmap)
canvas.drawColor(0xffffffff.toInt())
screenshotView.draw(canvas)
// Saving to SDcard for testing. This will move to cache dir.
val file = File.createTempFile("share-", ".png", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES))
file.outputStream().use {
bitmap.compress(Bitmap.CompressFormat.PNG, 0, it)
}
return file
}