How to handle screen orientation in Canvas of Android? - android

I am working with Canvas in Android and the issue I am facing is during the screen rotation. Let's say I start the app in portrait mode and draw something on the canvas, then on rotation some part of the canvas moves out of the screen. See the screenshots attached.
Code snippets from my file where Canvas is implemented (I will provide other parts if they are required just let me know through comment) :
private lateinit var mBitmap: Bitmap
private lateinit var mCanvas: Canvas
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
canvas.apply {
drawColor(0)
drawBitmap(mBitmap, 0f, 0f, mBitmapPaint)
drawPath(mPath, mPaint)
}
}
private fun createBitmap(w: Int, h: Int) {
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
mBitmap = bitmap
mCanvas = Canvas(bitmap)
clear()
}
private fun createBitmap() {
val p = displayDimensions
val bitmapSize = max(p.x,p.y)
createBitmap(bitmapSize, bitmapSize)
}
init {
mPaint = Paint()
mPaint.isAntiAlias = true
mPaint.isDither = true
mPaint.color = foregroundColor
mPaint.style = Paint.Style.STROKE
mPaint.strokeJoin = Paint.Join.ROUND
mPaint.strokeCap = Paint.Cap.ROUND
mPaint.strokeWidth = currentStrokeWidth.toFloat()
createBitmap()
}

Going through a couple of blogs and SO answers I finally found the answer on Android developers documentation - here
My code snippet looks like :
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
val scaledBitmap: Bitmap = Bitmap.createScaledBitmap(mBitmap, w, h, true)
}
mCanvas = Canvas(scaledBitmap)
}
This way you can handle the screen orientation when the device rotates. The createScaledBitmap() function takes care of taking the previous drawn Bitmap and scaling it according to new width and height.

Related

Kotlin Jetpack Compose: Display ByteArray or FileStream as Image in Android

Heyho,
I'm quiet new to Kotlin, but I came across this problem:
I have a library function, that generates an image(QR-Code). Now I'd like to display that image... But I've no idea how. The documentation only explains how to save the image locally. But I'm not really interested in saving it. So I can either get the image as a FileStream or as a ByteArray. Any possibility to display any of these as an Image in the UI?
An Example:
#Composable
fun QrCode(stand: String) {
Text(text = "QR-Code:", fontSize = 16.sp)
//? Image(QRCode(stand).render().getBytes()) // this obviously won't work
}
Any ideas?
In order to display an Image, you can refer this.
#Composable
fun BitmapImage(bitmap: Bitmap) {
Image(
bitmap = bitmap.asImageBitmap(),
contentDescription = "some useful description",
)
}
So the remaining is to find a way to convert your target input into a Bitmap.
If you have ByteArray of the image file, you can refer to this.
fun convertImageByteArrayToBitmap(imageData: ByteArray): Bitmap {
return BitmapFactory.decodeByteArray(imageData, 0, imageData.size)
}
If you just possess the QR String, you can refer this to convert QR String to a Bitmap.
fun encodeAsBitmap(source: String, width: Int, height: Int): Bitmap? {
val result: BitMatrix = try {
MultiFormatWriter().encode(source, BarcodeFormat.QR_CODE, width, height, null)
} catch (e: Exception) {
return null
}
val w = result.width
val h = result.height
val pixels = IntArray(w * h)
for (y in 0 until h) {
val offset = y * w
for (x in 0 until w) {
pixels[offset + x] = if (result[x, y]) Color.BLACK else Color.WHITE
}
}
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
bitmap.setPixels(pixels, 0, width, 0, 0, w, h)
return bitmap
}
And you need to add this dependency implementation 'com.google.zxing:core:3.3.1' when you decide to use the above code.

Android : Smart cast to 'Bitmap!' is impossible, because 'bitmap' is a mutable property that could have been changed by this time

While migrating application with targetSDK 28 to 29, Android studio(3.6.2) started giving error while compiling code. With target SDK set to 28, code was compiling fine. Does anyone have idea on this?? is there any strict rule applied to this behaviour change with same android studio and Kotlin version?
Below is the code:
private fun generateInternalCanvas(w: Int, h: Int) {
bitmap?.recycle()
if (w > 0 && h > 0) {
bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
bitmap?.eraseColor(Color.TRANSPARENT)
internalCanvas = Canvas(bitmap)
}
}
I am getting above error when i am assigning value to internalCanvass.
Thanks,
Jim.
That is because your bitmap is variable with null safety (the code looks like this var bitmap : Bitmap? = null). But i think your code is safe to make a bitmap to be non null in that code because you assign the bitmap with Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888). So the solution is you can change your code to :
private fun generateInternalCanvas(w: Int, h: Int) {
bitmap?.recycle()
if (w > 0 && h > 0) {
bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
bitmap?.eraseColor(Color.TRANSPARENT)
internalCanvas = Canvas(bitmap!!)
}
}
Should be changed to this code:
private fun generateInternalCanvas(w: Int, h: Int) {
bitmap?.recycle()
if (w > 0 && h > 0) {
val bitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888) // <-
this.bitmap = bitmap // <-
bitmap.eraseColor(Color.TRANSPARENT)
internalCanvas = Canvas(bitmap)
}
}
Now you ensure this bitmap exists across the next 3 lines

How to do wrap content on half circle custom view?

I'm making half circle custom view on Android. However, I'm struggling to remove the un-needed bottom white space on wrap content. I think because it is drawing based on 'a full circle' calculation.
I'm sharing my Custom View implementation, as well as how I call it from my application.
See also this image:
Click here for the screenshot
Note: If I change the onMeasure size, then it will cut the upper circle:
Click here for the screenshot
class CircularProgressView(context: Context, attrs: AttributeSet) : View(context, attrs) {
private var circle = RectF()
private val paint = Paint()
private var size = 0
init {
paint.isAntiAlias = true
paint.style = Paint.Style.STROKE
paint.strokeWidth = strokeWidth.toFloat()
paint.strokeCap = Paint.Cap.BUTT
setupAttributes(attrs)
}
private fun setupAttributes(attrs: AttributeSet?) {
// TypedArray objects are shared and must be recycled.
typedArray.recycle()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
drawBackground(canvas)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
size = Math.min(measuredWidth, measuredHeight)
setMeasuredDimension(size, size)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
val centerX = w / 2
val centerY = h / 2
// Pick the minimum value so that it can fit the container. Radius is half size
val radius = size / 2f
// Create the background and progress circle, adding dialStrokeWidth in equation so that make sure the dial can fit container
circle.top = (centerY - radius)
circle.bottom = (centerY + radius)
circle.left = (centerX - radius)
circle.right = (centerX + radius)
}
private fun drawBackground(canvas: Canvas) {
paint.shader = null
paint.color = backGroundColor
canvas.drawArc(circle, startAngle, sweepAngle, false, paint)
}
}
This is how I use it:
<com.enova.circular_progress.CircularProgressView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:background="#color/white"
app:backgroundColor="#color/colorHint"
app:dialColor="#color/colorPrimary"
app:foregroundColorEnd="#color/colorPrimary"
app:foregroundColorStart="#color/colorPrimaryDark"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:percent="80">
</com.enova.circular_progress.CircularProgressView>
Change your onMeasure. YOu're setting your measured width and height to the same value. If you only want to display a top half circle, then you want to set the height to half the width. Otherwise, it will think the view is the full circle in height.

Native Crash in getting bitmap from resource

I have a native crash:
A/libc: invalid address or address of corrupt block 0x55766f1b00 passed to try_realloc_chunk
A/libc: Fatal signal 11 (SIGSEGV), code 1, fault addr 0xdeadbaad in tid 32219 (onPool-worker-1)
when executing the drawable.draw(canvas) line in the following method:
fun getBitmapFromResource(context: Context, imageRes: Int, iconSize: Float = CATEGORY_ICON_SIZE): Bitmap? {
val drawable = ContextCompat.getDrawable(context, imageRes)
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
val size = GraphicsUtils.toPx(context, iconSize)
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable!!.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas) // crash!!
return bitmap
}
The drawable is VectorDrawable implementation. I am executing this code on a background thread in a coroutine.
I added vectorDrawables.useSupportLibrary = true to build.gradle file, but it did not help.
I need bitmap object because from its width and height I draw a custom chart and I need to perform size calculations there.
I had the suspicion that multi-threading might break the process, so I added this code in the runBlocking section (still on a background thread) - no effect.
Any ideas how to fix this?
After several hours of investigation, I fixed the issue.
The problem seems to be that more than one coroutine was entering the method at the same time. I used Mutex to make sure only one coroutine can be inside the method.
object UIUtilsSingleton {
private val mutex = Mutex()
suspend fun getBitmapFromResource(context: Context, imageRes: Int): Bitmap? {
var bitmap: Bitmap? = null
mutex.withLock {
val iconSize = 42f
val drawable = ContextCompat.getDrawable(context, imageRes)
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
val size = GraphicsUtils.toPx(context, iconSize)
bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
drawable!!.setBounds(0, 0, canvas.width, canvas.height)
drawable.draw(canvas)
}
return bitmap
}
}

Setting MenuItem icon from URL - Android

I want to set a MenuItem in my ActionBar which leads to the user profile page in my app. I would like the icon for that to be his profile picture for which I have the URL and can create a BitMap out of.
The image isn't stored in my project folder or anywhere locally so I can't pick it up from R.drawable.
Can somebody help me with setting a bitmap created with the URL as the MenuItem icon? Thanks for the help!
You could do something like this to set the icon from a bitmap:
myMenuItem.setIcon(new BitmapDrawable(getResources(), myBitmap));
In your code this would looks a bit like this:
public boolean onCreateOptionsMenu( Menu menu ) {
MenuInflater inflater = getMenuInflater();
inflater.inflate( R.menu.actionbar, menu );
userItem = menu.findItem(R.id.userItem);
Bitmap myBitmap = //get your bitmap
userItem.setIcon(new BitmapDrawable(getResources(), myBitmap));
return menu;
}
You'll need to get the file from the URL and turn it into a Bitmap first. Note that this will be slow, since if you are doing this when your app is started, the user will have to wait until the file is downloaded before the app will be shown. If your icon changes infrequently, I'd recommend caching it on the device and reusing the locally stored copy.
Also check the "Changing the menus at runtime" section here.
I was searching for this as well and recently I found it from another stackoverflow answer which I'm giving reference link This one is for JAVA and I'm adding Kotlin code below.
Here is the code for kotlin code:
imageUser is Url of image in string format.
You should add it your own image url.
Glide.with(this).asBitmap().load(imageUser)
.into(object : SimpleTarget<Bitmap?>(150, 100) {
override fun onResourceReady(
resource: Bitmap,
transition: Transition<in Bitmap?>?
) {
yourItemIcon.setIcon(BitmapDrawable(resources, resource))
}
})
Set your item as below inside the. onCreateOptionsMenu
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.menu_main, menu)
val yourItemIcon = menu!!.findItem(R.id.ic_topic_info)
return true
}
For new users:
You should also add these lines of code to dependencies the build.gradle in app level for adding Glide library in your app.
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
Kotlin - Picasso Solution
extension function
fun com.squareup.picasso.Target.picassoLoad(url: String, resources: Resources): com.squareup.picasso.Target {
Picasso.get().load(url)
.resize(resources.getDimension(R.dimen.menuIconSize).toInt(),
resources.getDimension(R.dimen.menuIconSize).toInt())
.into(this)
return this
}
in your activity (note that you need to keep a strong reference on target to work)
private var target : com.squareup.picasso.Target? = null
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.basemenu, menu)
menu.findItem(R.id.menu_you_want)?.let { menuItem ->
target = object : com.squareup.picasso.Target {
override fun onPrepareLoad(placeHolderDrawable: Drawable?) {
menuItem.setIcon(R.drawable.fallback_image)
}
override fun onBitmapFailed(e: java.lang.Exception?, errorDrawable: Drawable?) {
menuItem.setIcon(R.drawable.fallback_image)
}
override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) {
menuItem.icon = BitmapDrawable(resources, CircleTransform.getCroppedBitmap(bitmap!!))
}
}.picassoLoad(url, resources)
}
return super.onCreateOptionsMenu(menu)
}
and circletransform class
class CircleTransform : Transformation {
private var x: Int = 0
private var y: Int = 0
override fun transform(source: Bitmap): Bitmap {
val size = Math.min(source.width, source.height)
x = (source.width - size) / 2
y = (source.height - size) / 2
val squaredBitmap = Bitmap.createBitmap(source, x, y, size, size)
if (squaredBitmap !== source) source.recycle()
val bitmap = Bitmap.createBitmap(size, size, Bitmap.Config.ARGB_8888)
val canvas = Canvas(bitmap)
val paint = Paint()
val shader = BitmapShader(squaredBitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
paint.shader = shader
paint.isAntiAlias = true
val r = size / 2f
canvas.drawCircle(r, r, r, paint)
squaredBitmap.recycle()
return bitmap
}
override fun key() = "circle(x=$x,y=$y)"
companion object {
fun getCroppedBitmap(bitmap: Bitmap): Bitmap {
val output = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
val color = -0xbdbdbe
val paint = Paint()
val rect = Rect(0, 0, bitmap.width, bitmap.height)
paint.isAntiAlias = true
canvas.drawARGB(0, 0, 0, 0)
paint.color = color
canvas.drawCircle(bitmap.width / 2f, bitmap.height / 2f,
bitmap.width / 2f, paint)
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(bitmap, rect, rect, paint)
return output
}
}
}

Categories

Resources