I come from the following question (i asked): Saving the current view as a bitmap
I'll give the maximum details i can in order to ask the question.
My final goal, is to write a view with some information on it (will come later from an API, probably a JSOn object with lots of text). What i've done so far, was: 1) Create a Custom View 2) Draw on this Custom View a Canvas with the information i needed (canvas.drawText() ) 3) Put this CustomView on activity_main.xml (reference it) 4) Instantiate this CustomView on MainActivity.kt (now the problem begins) 5) Convert this CustomView to a Bitmap (Using an extension method. 6) Save the converted CustomView to the SD Card
However, when i try to save it nothing happens. No folder gets created, nothing on the LogCat window also (i'm checking if files \ folders are created using the Device File Explorer on Android Studio).
After reading i understood that i should have an ViewTreeObserver to watch for changes (ex: then the view finishes drawing). I added this to my code as an Extension method (found on SO but can't find the link right now) but changed nothing also.
In order to save the bitmap to internal storage, i got the method from the following link: https://android--code.blogspot.com/2018/04/android-kotlin-save-image-to-internal.html (I just adapted the method since i needed to use a Bitmap no a drawable).
Am i missing something ? As far as i can see i'm doing the correct stuff to save the bitmap to the SD. (Question is big because of the code i posted) Info: Using Android Studio 3.5.1 Kotlin Language
My activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.example.desenhanota.CustomView
android:id="@+id/MyCustomview"
android:layout_width="match_parent"
android:layout_height="442dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</RelativeLayout>
ViewTreeObserver Extension Method:
inline fun View.doOnGlobalLayout(crossinline action: (view: View) -> Unit) {
val vto = viewTreeObserver
vto.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
@SuppressLint("ObsoleteSdkInt")
@Suppress("DEPRECATION")
override fun onGlobalLayout() {
action(this@doOnGlobalLayout)
when {
vto.isAlive -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
vto.removeOnGlobalLayoutListener(this)
} else {
vto.removeGlobalOnLayoutListener(this)
}
}
else -> {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
viewTreeObserver.removeOnGlobalLayoutListener(this)
} else {
viewTreeObserver.removeGlobalOnLayoutListener(this)
}
}
}
}
})
}
CustomView file (CustomView.kt)
class CustomView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
): View(context, attrs, defStyleAttr) {
private val textoGeral = Paint()
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
textoGeral.setColor(Color.BLACK)
canvas?.drawText("DRAW TEST ON CANVAS TEST TEST ", 0f, 120f, textoGeral)
}
}
MainActivity
class MainActivity : AppCompatActivity() {
private val TAG = "MainActivity"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val outraView = CustomView(this)
outraView.doOnGlobalLayout {
try {
val bmp = outraView.fromBitmap()
val uri: Uri = saveImageToInternalStorage(bmp)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
private fun saveImageToInternalStorage(bitmap :Bitmap):Uri{
// Get the context wrapper instance
val wrapper = ContextWrapper(applicationContext)
// The bellow line return a directory in internal storage
var file = wrapper.getDir("images", Context.MODE_PRIVATE)
file = File(file, "${UUID.randomUUID()}.jpg")
try {
val stream: OutputStream = FileOutputStream(file)
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream)
stream.flush()
stream.close()
} catch (e: IOException){ // Catch the exception
e.printStackTrace()
}
// Return the saved image uri
return Uri.parse(file.absolutePath)
}
}
EDIT 1: I changed what the user mhedman suggested on the comments. He mentioned i was acessing a new instance of my Custom View, not the one already drawn from Activity Layout. When i tried outside the ViewTreeObsever Event, i had an exception saying "width and height must be > 0". Inside the ViewTreeObserver, nothing happens (no message is show).
Updated code with the suggestion:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val outraView = MyCustomView
outraView.doOnGlobalLayout {
val finalBmp = outraView.fromBitmap()
val uri: Uri = saveImageToInternalStorage(finalBmp)
}