Im trying to add a suffix to my TextInputEditText by creating a TextDrawable then setting that using compoundDrawable. Everything is going fairly well except the drawable gets clipped outside to right of the component. What could be causing this? So far ive tried changing the font size but that is not doing any difference... Is the drawable too wide or what?
The String is "kr/månad" and as you can see it is clipped..
XML
<android.support.design.widget.TextInputLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/text_input_layout"
style="#style/TextInputLayoutStyle"
android:theme="#style/TextInputLayoutTheme">
<android.support.design.widget.TextInputEditText
android:id="#+id/text_input_edit_text"
style="#style/TextInputEditTextStyle" />
</android.support.design.widget.TextInputLayout>
COMPONENT CODE
textInputEditText.setCompoundDrawables(null, null, TextDrawable(unitText), null)
TEXTDRAWABLE
class TextDrawable(private val text: String?) : Drawable() {
private val paint: Paint
init {
paint = Paint()
paint.color = Color.BLACK
paint.textSize = 44f
paint.isAntiAlias = true
paint.isFakeBoldText = true
paint.typeface = Typeface.create("sans-serif-light", Typeface.NORMAL)
paint.style = Paint.Style.FILL
paint.textAlign = Paint.Align.CENTER
}
override fun draw(canvas: Canvas) {
text?.let { text ->
canvas.drawText(text, 0f, 0f, paint)
}
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
}
override fun setColorFilter(cf: ColorFilter?) {
paint.colorFilter = cf
}
override fun getOpacity(): Int {
return PixelFormat.TRANSLUCENT
}
}
Try this code:
class TextDrawable(private val text: String?) : Drawable() {
private val paint = Paint().apply {
color = Color.BLACK
textSize = 44f
isAntiAlias = true
isFakeBoldText = true
typeface = Typeface.create("sans-serif-light", Typeface.NORMAL)
style = Paint.Style.FILL
setBounds(0, 0, measureText(text).toInt(), 0)
}
override fun draw(canvas: Canvas) {
text?.let { text ->
canvas.drawText(text, 0f, 0f, paint)
}
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
}
override fun setColorFilter(cf: ColorFilter?) {
paint.colorFilter = cf
}
override fun getOpacity(): Int {
return PixelFormat.TRANSLUCENT
}
}
The difference is in two lines. I removed this
paint.textAlign = Paint.Align.CENTER
and added this:
setBounds(0, 0, measureText(text).toInt(), 0)
Related
I am developing an android app using:
Jetpack Lifecycle (ViewModel)
Jetpack Navigation
Coil (Image Loader)
I am trying to customize the BottomNavigationMenu.
But one thing is very hard...
The last tab is the User's profile Image with Border.
If the user's profile image background color is white, then the ui is weird.
So I should show the border.
class MainActivity {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
initBottomNav(binding.bottomNav)
vm.initProfileBottomIcon()
}
private fun initBottomNav(bottomNav: BottomNavigationView) {
val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
bottomNav.setupWithNavController(navHostFragment.navController)
bottomNav.itemIconTintList = null
vm.profileImgUrl.observe(this) { url ->
bottomNav.menu.findItem(R.id.profileFragment).load(this, url) {
transformations(CircleCropTransformation())
}
}
}
}
This code draws the profile image on the BottomNavigationMenu.
But not draw the border.
When I googling, there is no support for CircleCrop with Border on Coil (even Glide).
So I tried the below code, But it doesn't work well..
vm.profileImg.observe(this) { imgBitmap ->
val layerBorder = ResourcesCompat.getDrawable(resources, R.drawable.oval_trans_border1_red, null)
val layerIcon = BitmapDrawable(Resources.getSystem(), imgBitmap)
val layerDrawable = LayerDrawable(arrayOf(layerIcon, layerBorder))
val bottomNavProfile = bottomNav.menu.findItem(R.id.profileFragment)
val request = ImageRequest.Builder(this)
.data(layerDrawable)
.target {
bottomNavProfile.icon = it
}
.apply {
transformations(CircleCropTransformation())
}
.build()
imageLoader.enqueue(request)
}
Somebody help me please?
You can write your own Transformation with border like this:
class BorderedCircleCropTransformation(
private val borderSize: Float = 0f,
#ColorInt private val borderColor: Int = Color.BLUE
) : Transformation {
override fun key(): String = BorderedCircleCropTransformation::class.java.name
override suspend fun transform(pool: BitmapPool, input: Bitmap, size: Size): Bitmap {
val borderOffset = (borderSize * 2).toInt()
val halfWidth = input.width / 2
val halfHeight = input.height / 2
val circleRadius = Math.min(halfWidth, halfHeight).toFloat()
val newBitmap = Bitmap.createBitmap(
input.width + borderOffset,
input.height + borderOffset,
Bitmap.Config.ARGB_8888
)
// Center coordinates of the image
val centerX = halfWidth + borderSize
val centerY = halfHeight + borderSize
val paint = Paint()
val canvas = Canvas(newBitmap).apply {
// Set transparent initial area
drawARGB(0, 0, 0, 0)
}
// Draw the transparent initial area
paint.isAntiAlias = true
paint.style = Paint.Style.FILL
canvas.drawCircle(centerX, centerY, circleRadius, paint)
// Draw the image
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
canvas.drawBitmap(input, borderSize, borderSize, paint)
// Draw the createBitmapWithBorder
paint.xfermode = null
paint.style = Paint.Style.STROKE
paint.color = borderColor
paint.strokeWidth = borderSize
canvas.drawCircle(centerX, centerY, circleRadius, paint)
return newBitmap
}
override fun equals(other: Any?) = other is BorderedCircleCropTransformation
override fun hashCode() = javaClass.hashCode()
override fun toString() = "BorderedCircleCropTransformation()"
private companion object {
val XFERMODE = PorterDuffXfermode(PorterDuff.Mode.SRC_IN)
}
}
internal val Bitmap.safeConfig: Bitmap.Config
get() = config ?: Bitmap.Config.ARGB_8888
then pass it as a transformation to coil and it will draw what you want.
I use this code for drawing border.
Is it possible to problematically "draw" text to a Shape or Drawable in Android (using Kotlin) to use the result as image for an ImageView?
In iOS it is no problem to draw custom text to a UIImageContext which can then be used to create a UIImage (e.g as described here). Is something similar possible in Android as well?
You can make your own Drawable implementation. Android drawing is done via Paint and a Canvas. Here's an example:
class TextIconDrawable: Drawable() {
private var alpha = 255
private var textPaint = TextPaint().apply {
textAlign = Paint.Align.CENTER
}
var text by Delegates.observable("") { _, _, _ -> invalidateSelf() }
var textColor by Delegates.observable(Color.BLACK) { _, _, _ -> invalidateSelf() }
private fun fitText(width: Int) {
textPaint.textSize = 48f
val widthAt48 = textPaint.measureText(text)
textPaint.textSize = 48f / widthAt48 * width.toFloat()
}
override fun draw(canvas: Canvas) {
val width = bounds.width()
val height = bounds.height()
fitText(width)
textPaint.color = ColorUtils.setAlphaComponent(textColor, alpha)
canvas.drawText(text, width / 2f, height / 2f, textPaint)
}
override fun setAlpha(alpha: Int) {
this.alpha = alpha
}
override fun setColorFilter(colorFilter: ColorFilter?) {
textPaint.colorFilter = colorFilter
}
override fun getOpacity(): Int = PixelFormat.TRANSLUCENT
}
Usage:
val drawable = TextIconDrawable().apply {
text = "Hello, world!"
textColor = Color.BLACK
}
requireView().findViewById<ImageView>(R.id.imageView).setImageDrawable(drawable)
You can of course customize what properties are exposed. Or if this is a one-time use thing, just set the properties as needed on the paint instance.
I have a custom view that extends ImageView. After setting an image I start by highlighting something on the image. When I press a button I want to make the image black except for that part of the path. Is there a way how I can achieve that?
What I have tried initially was to create a second bitmap with a second canvas but now displaying it, while drawing on the main canvas also draw with transparent paint on the black canvas and on the final step merge them together. I could not achieve drawing transparent on the black canvas.
#SuppressLint("AppCompatCustomView")
class BlackImageView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ImageView(context, attrs, defStyleAttr) {
var path: Path = Path()
var transparentPath: Path = Path()
var paint: Paint = Paint().apply {
isAntiAlias = true
isDither = true
strokeWidth = 25f
color = ResourcesCompat.getColor(resources, R.color.highlight, null)
style = Paint.Style.STROKE
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
}
var transparentPaint: Paint = Paint().apply {
isAntiAlias = true
isDither = true
strokeWidth = 25f
color = Color.TRANSPARENT
xfermode = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
style = Paint.Style.STROKE
strokeJoin = Paint.Join.ROUND
strokeCap = Paint.Cap.ROUND
}
private lateinit var blackBitmap: Bitmap
private lateinit var extraCanvas: Canvas
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
blackBitmap = Bitmap.createBitmap(w, h, Bitmap.Config.ARGB_8888)
extraCanvas = Canvas(blackBitmap)
extraCanvas.drawColor(Color.BLACK)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
event?.let {
when (it.action) {
MotionEvent.ACTION_DOWN -> {
path.moveTo(event.x, event.y)
transparentPath.moveTo(event.x, event.y)
}
MotionEvent.ACTION_MOVE -> {
path.lineTo(event.x, event.y)
transparentPath.lineTo(event.x, event.y)
invalidate()
}
MotionEvent.ACTION_UP -> {
}
else -> {
invalidate()
return true
}
}
}
invalidate()
return true
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
// canvas?.drawBitmap(blackBitmap, 0f, 0f, null)
// canvas?.drawPath(transparentPath, transparentPaint)
canvas?.drawPath(path, paint)
}
}
I created a custom view to draw a line, but progressively. I tried to use PathMeasure and getSegment, but the effect doesn't work. It just keeps drawing the line already with the final size.
private val paint = Paint().apply {
isAntiAlias = true
color = Color.WHITE
style = Paint.Style.STROKE
strokeWidth = 10f
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
val path = Path().apply {
moveTo(width/2.toFloat(), height/2.toFloat())
lineTo(width/2.toFloat(), height/4.toFloat())
}
val measure = PathMeasure(path, false)
val length = measure.length
val partialPath = Path()
measure.getSegment(0.0f, length, partialPath, true)
partialPath.rLineTo(0.0f, 0.0f)
canvas!!.drawPath(partialPath, paint)
}
you can do this with DashPathEffect
DashPathEffect dashPathEffect = new DashPathEffect(new float[]{1000.0f,9999999},0);
Paint.setPathEffect(dashPathEffect);
change 1000 to your length ("on" parts in Dash)
and set 99999999 to your max ("off" parts in Dash)
play with this parameters and read this article please
Here's how I made it, as #mohandes explained:
private var path = Path()
private var paint = Paint()
private val dashes = floatArrayOf(125f, 125f)
init {
paint = Paint().apply {
isAntiAlias = true
color = Color.WHITE
style = Paint.Style.STROKE
strokeWidth = 10.0f
pathEffect = CornerPathEffect(8f)
}
path = Path().apply {
moveTo(312f, 475f)
lineTo(312f, 375f)
}
val lineAnim = ValueAnimator.ofFloat(100f, 0f)
lineAnim.interpolator = LinearInterpolator()
lineAnim.addUpdateListener {
paint.pathEffect = ComposePathEffect(DashPathEffect(dashes, lineAnim.animatedValue as Float), CornerPathEffect(8f))
invalidate()
}
lineAnim.duration = 1000
lineAnim.start()
}
override fun onDraw(canvas: Canvas?) {
super.onDraw(canvas)
canvas!!.drawPath(path, paint)
}
I am drawing a custom shape in onDraw with Paint and Canvas. Within the onDraw class, I have a rectangle clipPath.
I Would like to be able to animate the position (animate from left-to-right) of the clipPath from MainActivity. This will hide the shape that's drawn (BlackGraph) from left to right.
class BlackGraph(context: Context) : View(context) {
var clipAmount:Float = 0.0f
override fun onDraw(canvas: Canvas) {
val paint = Paint()
paint.style = Paint.Style.FILL
paint.color = Color.parseColor("#000000")
val path = Path()
val clipPath = Path()
clipPath.addRect(clipAmount, 0f, width.toFloat(), height.toFloat(), Path.Direction.CW)
canvas.clipPath(clipPath)
path.moveTo(0f, height-30.toFloat())
path.lineTo(width.toFloat(), 0f)
path.lineTo(width.toFloat(), height.toFloat())
path.lineTo(0f, height.toFloat())
path.lineTo(0f, 0f)
canvas.drawPath(path, paint)
}
}
In onCreate:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val layout1 = findViewById<android.support.constraint.ConstraintLayout>(R.id.layout1)
val blackGraph = BlackGraph(this)
layout1.addView(blackGraph)
val valueAnimator = ValueAnimator.ofFloat(0f, 450f)
valueAnimator.addUpdateListener {
val value = it.animatedValue as Float
println("Value -> $value")
blackGraph.clipAmount = value
}
valueAnimator.duration = 2000
valueAnimator.start()
}
I am trying to simply animate the position of clipAmount to achieve the desired animation.
The problem is in onCreate, the clipAmount value never animated. The print statement works just fine, however. Logcat is full of float values ranging from 0.0 to 450.0
How can I animate the movement of the clipPath?
Ok, you got some problems here. The first, you shouldn't init Paint, Path in onDraw(), it should be initialized first and you can modify it later and you will get better performance. When you update clipAmount value, you need to call postInvalidateOnAnimation() to make your BlackGraph view to draw again, this will trigger the method onDraw(). The last thing is clipPath need to call reset() to clear before add new path with the method addRect().
class BlackGraph(context: Context) : View(context) {
var clipAmount:Float = 0.0f
val paint = Paint().apply {
style = Paint.Style.FILL
color = Color.parseColor("#000000")
}
val path = Path()
val clipPath = Path()
override fun onDraw(canvas: Canvas) {
clipPath.apply {
reset()
addRect(clipAmount, 0f, width.toFloat(), height.toFloat(), Path.Direction.CW)
}
canvas.clipPath(clipPath)
path.moveTo(0f, height-30.toFloat())
path.lineTo(width.toFloat(), 0f)
path.lineTo(width.toFloat(), height.toFloat())
path.lineTo(0f, height.toFloat())
path.lineTo(0f, 0f)
canvas.drawPath(path, paint)
}
fun animateClipAmount() {
val valueAnimator = ValueAnimator.ofFloat(0f, 450f)
valueAnimator.addUpdateListener {
val value = it.animatedValue as Float
clipAmount = value
println("Value -> $clipAmount")
postInvalidateOnAnimation()
}
valueAnimator.duration = 2000
valueAnimator.start()
}
}
In onCreate:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val layout1 = findViewById<android.support.constraint.ConstraintLayout>(R.id.layout1)
val blackGraph = BlackGraph(this)
layout1 .addView(blackGraph)
blackGraph.animateClipAmount()
}
}