Confusion on resolve gesture conflict - android

I am now trying to resolve the conflict of gesture and DrawLayout.
According to the document, to resolve the conflict, I should call DrawerLayout.setSystemGestureExclusionRects to specify the exclusion area Rect.
So I write the code like this:
override fun onDraw(c: Canvas?) {
super.onDraw(c)
updateGestureExclusion()
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, l, t, r, b)
if (!changed) {
return
}
updateGestureExclusion()
}
private fun updateGestureExclusion() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
return
}
systemGestureExclusionRects = listOf(createSystemGestureExclusionRect())
}
private fun createSystemGestureExclusionRect(): Rect {
val screenSize = ScreenUtils.getScreenSize()
val height = screenSize.y
val heightInDp = SizeUtils.px2dp(height.toFloat())
return if (heightInDp <= 200) {
Rect(0, 0, SizeUtils.dp2px(56f), height)
} else {
val topInDp = (heightInDp - 200) / 2f
val top = SizeUtils.dp2px(topInDp)
val bottom = top + SizeUtils.dp2px(199f)
Rect(0, top, SizeUtils.dp2px(56f), bottom)
}
}
I clip an 56dp * 200dp at the center-left of screen as systemGestureExclusionRect. Unforetunately, For Realme RMX2020 device, Android System recognize the area of left-top screen as systemGestureExclusionRect. Even if I change the exclusionRect, Android System still recognize the area of left-top screen as systemGestureExclusionRect.
I also checked the size of the rect area and it did not exceed the limit in the document.
What's wrong with my code and how to resolve the conflict correctly? Thank you! :)

Related

How to draw curve to bottom line of a Triangle in Android using Canvas by Path

Here, a triangle is drawn using a path, I'm not sure how it is drawn by just two lineTo calls, if I manipulate any of the lineTo methods the triangle's right & left side is updated, what I want to do is add a curve to the bottom line such that it looks something like this .
class TriangleIndicator(context: Context) : Indicator<TriangleIndicator>(context) {
private var indicatorPath = Path()
private var indicatorTop = 0f
init {
width = dpTOpx(20F)
}
override fun getTop(): Float {
return indicatorTop
}
override fun getBottom(): Float {
return indicatorTop + width
}
override fun draw(canvas: Canvas) {
canvas.drawPath(indicatorPath, indicatorPaint)
}
override fun updateIndicator() {
indicatorPath = Path()
indicatorTop = speedometer!!.padding.toFloat() + speedometer!!.speedometerWidth + dpTOpx(5f)
indicatorPath.moveTo(getCenterX(), indicatorTop)
indicatorPath.lineTo(getCenterX() - width, indicatorTop + width)
indicatorPath.lineTo(getCenterX() + width, indicatorTop + width)
indicatorPaint.color = color
}
}

Custom ImageSpan not displaying properly

I'm trying to create a custom class that extends ImageSpan because I need some kind of margin/padding on the spans.
What I figured I need to do is to override the getSize function to return a bigger width so the spans get graphically spaced.
The problem is that as soon as I override the getSize function my view gets completely screwed up. My educated guess is then that I'm doing something stupid inside that funcion, but I can't get what.
Custom class code:
class PaddingImageSpan(drawable: Drawable, private val offset: Float = 0f) : ImageSpan(drawable) {
override fun getSize(
paint: Paint,
text: CharSequence?,
start: Int,
end: Int,
fm: Paint.FontMetricsInt?
): Int {
val width = paint.measureText(text, start, end)
val fontMetricsInt = paint.fontMetricsInt
if (fm != null){
fm.ascent = fontMetricsInt.ascent
fm.bottom = fontMetricsInt.bottom
fm.descent = fontMetricsInt.descent
fm.leading = fontMetricsInt.leading
fm.top = fontMetricsInt.top
}
println(width)
return width.roundToInt()
}
}
I figured it out. I'm posting the solution so if someone looks for it he can find it!
My problem was I was using the text metrics instead of the drawable metrics.
This is the correct code:
override fun getSize(
paint: Paint,
text: CharSequence?,
start: Int,
end: Int,
fm: Paint.FontMetricsInt?
): Int {
val rect = drawable.bounds
if (fm != null) {
fm.ascent = -rect.bottom
fm.descent = 0
fm.top = fm.ascent
fm.bottom = 0
}
return rect.right// + offset
}
That said, the cleaner way that I could come up with to space spannable is not by working on the spannable class but changing the setBounds() values.

Crash with "trying to use a recycled bitmap" using Glide 4.11, can transformations cause this?

I know there are already a bunch of questions related to the "trying to use a recycled bitmap" crash, but none helped me.
Details:
There are no calls to Bitmap.recycle() anywhere in this project
All images are loaded using Glide (4.11.0)
Glide calls are all simple, and not using placeholders
The crash seems to happen randomly when switching fragments
Only thing I could think of were the transformations.
There are only 2 transformations in this project.
CircleTransformation (clip image as a circle with a custom radius):
class CircleTransformation(private val radius: Float) : BitmapTransformation() {
companion object {
private const val ID = "com.project.transformation.circle"
private val ID_BYTES: ByteArray = ID.toByteArray()
}
public override fun transform(pool: BitmapPool, source: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
val paint = Paint()
paint.isAntiAlias = true
paint.shader = BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
val halfWidth = source.width / 2f
val output = Bitmap.createBitmap(source.width, source.height, Bitmap.Config.ARGB_8888)
val canvas = Canvas(output)
canvas.drawCircle(
halfWidth,
(source.height / 2).toFloat(),
halfWidth * radius,
paint
)
return output
}
// Caching helpers
override fun equals(other: Any?): Boolean {
return other is CircleTransformation && other.hashCode() == hashCode()
}
override fun hashCode(): Int {
return ID.hashCode()
}
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(ID_BYTES)
}
}
And ClipWhiteTransformation (remove white border from image):
class ClipWhiteTransformation() : BitmapTransformation() {
companion object {
private const val ID = "com.project.transformation.clipWhite"
private val ID_BYTES: ByteArray = ID.toByteArray()
// Config
const val white = 253 // White pixel, if all channels are equal or greater than this
const val transparent = 50 // Transparent pixel, if Less than this
}
public override fun transform(pool: BitmapPool, source: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
val width = source.width - 1
val height = source.height - 1
val halfX = width / 2
val halfY = height / 2
var startY = 0
// Left Margin
var left = 0
for (x in 0 until halfX) {
val pixel = source.getPixel(x, halfY)
// Transparent?
if (Color.alpha(pixel) < transparent) continue
// Not white?
if (Color.red(pixel) < white || Color.green(pixel) < white || Color.blue(pixel) < white) {
left = x
if (x > 2) {
startY = 2
}
break
}
}
// Right Margin
var right = 0
for (x in 0 until halfX) {
val pixel = source.getPixel(width - x, halfY)
// Transparent?
if (Color.alpha(pixel) < transparent) continue
// Not white?
if (Color.red(pixel) < white || Color.green(pixel) < white || Color.blue(pixel) < white) {
right = x
if (x > 2) {
startY = 2
}
break
}
}
// Top Margin
var top = 0
for (y in startY until halfY) {
val pixel = source.getPixel(halfX, y)
// Transparent?
if (Color.alpha(pixel) < transparent) continue
// Not white?
if (Color.red(pixel) < white || Color.green(pixel) < white || Color.blue(pixel) < white) {
top = y
break
}
}
// Bottom Margin
var bottom = 0
for (y in startY until halfY) {
val pixel = source.getPixel(halfX, height - y)
// Transparent?
if (Color.alpha(pixel) < transparent) continue
// Not white?
if (Color.red(pixel) < white || Color.green(pixel) < white || Color.blue(pixel) < white) {
bottom = y
break
}
}
// Clip, scale and return
val newWidth = width - (left + right)
val newHeight = height - (top + bottom)
val scale = if (abs(newWidth - outWidth) > abs(newHeight - outHeight)) outWidth / newWidth.toFloat() else outHeight / newHeight.toFloat()
val matrix = Matrix().apply { setScale(scale, scale) }
return Bitmap.createBitmap(source, left, top, newWidth, newHeight, matrix, false)
}
// Caching helpers
override fun equals(other: Any?): Boolean {
return other is ClipWhiteTransformation && other.hashCode() == hashCode()
}
override fun hashCode(): Int {
return ID.hashCode()
}
override fun updateDiskCacheKey(messageDigest: MessageDigest) {
messageDigest.update(ID_BYTES)
}
}
Was using the BitmapPool initially, removing it didn't stop the crash.
By the way, this is the extension used to load images:
fun ImageView.setURL(url: String,
#DrawableRes error: Int? = null,
#DrawableRes placeholder: Int? = null,
size: Int? = null,
options: ((RequestBuilder<Drawable>) -> Unit)? = null,
completion: ((resource: Drawable?) -> Unit)? = null) {
// No URL, use Placeholder if exists, if not, use the error image
if (url.isEmpty()) {
placeholder?.also{ setImageResource(it) } ?: run { error?.also{ setImageResource(it) } }
return
}
Glide.with(applicationInstance) // (I'm using an application instance directly here)
.load(url).apply {
completion?.also { completion ->
this.listener(object : RequestListener<Drawable> {
override fun onLoadFailed(e: GlideException?, model: Any?, target: Target<Drawable>?, isFirstResource: Boolean): Boolean {
completion(null)
return false
}
override fun onResourceReady(resource: Drawable?, model: Any?, target: Target<Drawable>?, dataSource: DataSource?, isFirstResource: Boolean): Boolean {
completion(resource)
return false
}
})
}
}
.apply { size?.also { this.override(it)} }
.apply { options?.invoke(this) }
.placeholder(placeholder ?: 0)
.error(error ?: 0)
.transition(DrawableTransitionOptions.withCrossFade(350))
.into(this)
}
Sorry for pasting so much code here (hope it's useful to someone).
Can these transformations or loader cause the crash?
To shape(circle/square/oval) your image You do not need to Transform your image .
MaterialDesign has introduce ShapeableImageView which let you shape your image at runtime, also you can add border with color .
add matrial dependecies :
implementation 'com.google.android.material:material:1.3.0-alpha01'
Add shapeableImageView in your xyz.xml:
<com.google.android.material.imageview.ShapeableImageView
android:id="#+id/imgStudent"
android:layout_width="100dp"
android:layout_height="100dp"
app:shapeAppearanceOverlay="#style/circleImageView"
android:padding="2dp"
app:strokeColor="#color/white"
app:strokeWidth="5dp"
android:scaleType="centerCrop"
android:adjustViewBounds="true"
tools:srcCompat="#drawable/ic_kid_placeholder"
/>
Add style inside res/values/style.xml file
<style name="circleImageView" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">50%</item>
<item name="android:shadowRadius">100</item>
<item name="android:shadowColor">#color/gray</item>
<item name="backgroundOverlayColorAlpha">12</item>
</style>
And at last load your image with glide .

How to set bullet radius in BulletSpan below api level 28?

How to use BulletSpan(gapWidth, color, bulletRadius), below api level 28? I am unable to set bulletRadius below api level 28. Any help would be appreciated.
So the method with radius parameters do not appear until API level 28. For previous APIs, you can refer to this article.
Basically what the author did was porting the API 28+ BulletSpan to your app project so you can use the ported version to achieve setting the radius.
Writing a Custom Bullet Span works like a charm, because you will get the canvas in your hand. With this you can paint a bullet of any kind/size to your view.
open class CharBulletSpan(var charCode: String, gapWidth: Int, internal val bulletColor: Int, val bulletSize: Int, val alignment: Layout.Alignment,val typeface: Typeface) : BulletSpan(gapWidth, bulletColor) {
private var isSpanStart = true
private val space = gapWidth
var alpha = 0f
override fun getLeadingMargin(first: Boolean): Int { // Returns the amount of indentation as set by the LeadingMarginSpan.class
if (!first) {
return 0
}
return super.getLeadingMargin(first)
}
override fun drawLeadingMargin(canvas: Canvas, paint: Paint, x: Int, dir: Int, top: Int, baseline: Int, bottom: Int, text: CharSequence, start: Int, end: Int, first: Boolean, layout: Layout?) {
//This is where the magic happens. Let us get the x - translation for the bullet since we will be painting on the canvas for different text alignments
isSpanStart = (text as Spanned).getSpanStart(this) == start
val xPos = getBulletXPos(layout!!, start, x)
val selectionStart = Selection.getSelectionStart(text)
val selectionEnd = Selection.getSelectionEnd(text)
//The following block is just for a fancy purpose. When we type text and press enter and the cursor is in a new line, we can apply an alpha value to the bullet/ set a transparency. If text is typed in that line , the bullet's alpha value can be changed.
if (!text.isNullOrEmpty()) {
if (start != end && (text.subSequence(start, end).isNotEmpty() && text.subSequence(start, end) != "\n")) {
this.alpha = 255f
}
if (start == end) {
if ((start == 1 && selectionStart == 0) || (start == selectionStart && end == selectionEnd)) { // first line
this.alpha = 150f
if (!isCursorVisible) {
this.alpha = 0f
}
} else if (selectionStart != start) {
this.alpha = 0f
}
}
} else if (text != null && text.isEmpty() && start == 0 && start == end) {
this.alpha = 255f
}
if (isSpanStart) {
// Now we shall fire the bullet
renderCharBullet(canvas, paint, xPos, dir, top, baseline, bottom, text,charCode)
}
}
private fun getBulletXPos(layout: Layout, start: Int, x: Int): Int {
val width = layout.width
val lineNo = layout.getLineForOffset(start)
val lineLeft = layout.getLineLeft(lineNo)
val lineWidth = layout.getLineWidth(lineNo)
return when (alignment) {
Layout.Alignment.NORMAL -> x
Layout.Alignment.OPPOSITE -> x + (width - lineWidth).toInt()
Layout.Alignment.ALIGN_CENTER -> lineLeft.toInt() - space
else -> x
}
}
private fun renderCharBullet(canvas: Canvas?, paint: Paint?, x: Int, dir: Int, top: Int, baseline: Int, bottom: Int, text: CharSequence?, charCode: String) {
val rectF = Rect()
val newPaint = Paint()
newPaint.typeface = typeface
newPaint.textSize = bulletSize
//Constructing a new paint to compute the y - translation of the bullet for the current line
if (!text.isNullOrEmpty()) {
newPaint.getTextBounds(text.subSequence(0, 1).toString(), 0, text.subSequence(0, 1).length, rectF)
}
val oldStyle = paint?.style
paint?.textSize = bulletSize
paint?.typeface = typeface
paint?.style = Paint.Style.FILL
paint?.color = bulletColor
paint?.alpha = alpha.toInt()
if (canvas!!.isHardwareAccelerated) {
canvas.save()
canvas.translate((x + dir).toFloat(), baseline - rectF.height().div(2.0f))
canvas.drawText(charCode, 0f, rectF.height().div(2.0f), paint!!)
canvas.restore()
}
paint?.style = oldStyle
}
}

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.

Categories

Resources