I need insert a space between the stars of my ratingBar, example the ratingbar is well:
but I need it thus:
how i can do this?
I don't know if it will be useful anymore, but I made a custom library which allows you to change space beetwen stars programatically and in XML (among other stuff): SimpleRatingBar.
It features:
Fully working android:layout_width: it can be set to wrap_content, match_parent or abritary dp.
Arbitrary number of stars.
Arbitrary step size.
Size of stars can be controlled exactly or by setting a maximum size.
Customizable colors in normal state (border, fill and background of stars and rating bar).
Customizable colors in pressed state (border, fill and background of stars and rating bar).
Customizable size separation between stars.
Customizable border width of stars.
Customizable stars corner radius.
Allows to set OnRatingBarChangeListener
Stars fill can be set to start from left to right or from right to left (RTL language support).
AnimationBuilder integrated in the view to set rating programatically with animation.
Here is a preview of it.
In your case, you would just have to do:
ratingbar.setStarsSeparation(20, Dimension.DP);
or, for example, in pixels:
ratingbar.setStarsSeparation(100, Dimension.PX);
You can find it either in jcenter or in Maven Central. So in your build.gradle file just add to your dependencies:
compile 'com.iarcuschin:simpleratingbar:0.1.+'
You have a next property.
android:progressDrawable = "#drawable/rating_stars"
android:indeterminateDrawable = "#drawable/rating_stars"
#drawable/rating_stars :
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="#+android:id/background"
android:drawable="#drawable/star_empty" />
<item android:id="#+android:id/secondaryProgress"
android:drawable="#drawable/star_empty" />
<item android:id="#+android:id/progress"
android:drawable="#drawable/star" />
</layer-list>
star_empty and star are the images which are in your drawable directory.
So, you can change star and star_empty in a graphic editor and add a spassing if you need.
Just use the custom icon for it and do use the style , i mean Photoshop it as you want it to look and replace with the system rating style icon.
I think you'd have to grab a copy of the systems star png file and manually add the padding that you want to it with a photoshop / gimp / some other editor.
I agree to Tim
i applied same logic and it worked.
Here in my project i am using my own star images for the star ratin
I made star images having extra space (padding) on the right side that resulted in space between the stars
You can use Custom SVG and Set Your Separation value
By using this class, you can fix Android custom SVG RatingBar and set Drawable End by replacing value(I marked this value as There_You_Can_Set_Your_Value) inside the class.
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.BitmapShader
import android.graphics.Canvas
import android.graphics.Shader
import android.graphics.drawable.*
import android.graphics.drawable.shapes.RoundRectShape
import android.graphics.drawable.shapes.Shape
import android.util.AttributeSet
import android.view.Gravity
import androidx.appcompat.graphics.drawable.DrawableWrapper
import androidx.appcompat.widget.AppCompatRatingBar
class RatingBarSvg #JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = R.attr.ratingBarStyle,
) : AppCompatRatingBar(context, attrs, defStyleAttr) {
private var mSampleTile: Bitmap? = null
private val drawableShape: Shape
get() {
val roundedCorners = floatArrayOf(5f, 5f, 5f, 5f, 5f, 5f, 5f, 5f)
return RoundRectShape(roundedCorners, null, null)
}
init {
val drawable = tileify(progressDrawable, false) as LayerDrawable
//drawable.findDrawableByLayerId(android.R.id.background).setColorFilter(backgroundTintColor, PorterDuff.Mode.SRC_ATOP);
//drawable.findDrawableByLayerId(android.R.id.progress).setColorFilter(progressTintColor, PorterDuff.Mode.SRC_ATOP);
progressDrawable = drawable
}
/**
* Converts a drawable to a tiled version of itself. It will recursively
* traverse layer and state list drawables.
*/
#SuppressLint("RestrictedApi")
private fun tileify(drawable: Drawable, clip: Boolean): Drawable {
if (drawable is DrawableWrapper) {
var inner: Drawable? = drawable.wrappedDrawable
if (inner != null) {
inner = tileify(inner, clip)
drawable.wrappedDrawable = inner
}
} else if (drawable is LayerDrawable) {
val numberOfLayers = drawable.numberOfLayers
val outDrawables = arrayOfNulls<Drawable>(numberOfLayers)
for (i in 0 until numberOfLayers) {
val id = drawable.getId(i)
outDrawables[i] = tileify(
drawable.getDrawable(i),
id == android.R.id.progress || id == android.R.id.secondaryProgress
)
}
val newBg = LayerDrawable(outDrawables)
for (i in 0 until numberOfLayers) {
newBg.setId(i, drawable.getId(i))
}
return newBg
} else if (drawable is BitmapDrawable) {
val tileBitmap = drawable.bitmap
if (mSampleTile == null) {
mSampleTile = tileBitmap
}
val shapeDrawable = ShapeDrawable(drawableShape)
val bitmapShader = BitmapShader(
tileBitmap,
Shader.TileMode.REPEAT, Shader.TileMode.CLAMP
)
shapeDrawable.paint.shader = bitmapShader
shapeDrawable.paint.colorFilter = drawable.paint.colorFilter
return if (clip)
ClipDrawable(
shapeDrawable, Gravity.START,
ClipDrawable.HORIZONTAL
)
else
shapeDrawable
} else {
return tileify(getBitmapDrawableFromVectorDrawable(drawable), clip)
}
return drawable
}
private fun getBitmapDrawableFromVectorDrawable(drawable: Drawable): BitmapDrawable {
val bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth + (**There_You_Can_Set_Your_Value**).toInt(), //dp between svg images //* resources.displayMetrics.density
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val canvas = Canvas(bitmap)
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
drawable.draw(canvas)
return BitmapDrawable(resources, bitmap)
}
#Synchronized
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
if (mSampleTile != null) {
val width = mSampleTile!!.width * numStars
setMeasuredDimension(
resolveSizeAndState(width, widthMeasureSpec, 0),
measuredHeight
)
}
}
}
Related
I am trying to create a circle drawable with a dash. I am able to achieve circle with dash but am not able to apply gradient color. Is there any way to do it? thanks in advance.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:width="#dimen/size60"
android:height="#dimen/size60" />
<stroke
android:width="1dp"
android:color="#color/white"
android:dashWidth="3dp"
android:dashGap="1dp" />
</shape>
View achieved:
View Required:
There is not a way to create a gradient ring using just XML AFAIK. You'll have better luck using a custom drawable. The following combine a sweep gradient shader with a Paint object to create a ring that has a gradient from start to end.
class DashedRingDrawable : Drawable() {
private val mPaint = Paint().apply {
style = Paint.Style.STROKE
strokeWidth = STROKE_WIDTH
}
private val mColorArray = intArrayOf(Color.WHITE, Color.BLACK)
private var mRingOuterDiameter = 0f
private var mRingOuterRadius = 0f
private var mRingInnerRadius = 0f
override fun onBoundsChange(bounds: Rect) {
super.onBoundsChange(bounds)
check(bounds.width() == bounds.height()) {
"Width must be equal to height. (It's a circle.)"
}
mRingOuterDiameter = bounds.width().toFloat()
mRingOuterRadius = mRingOuterDiameter / 2
mRingInnerRadius = (mRingOuterDiameter - STROKE_WIDTH) / 2
val dashLength = getNewDashLength()
mPaint.pathEffect = DashPathEffect(floatArrayOf(dashLength, GAP_LENGTH), 0f)
mPaint.shader = SweepGradient(mRingOuterRadius, mRingOuterRadius, mColorArray, null)
}
override fun draw(canvas: Canvas) {
// The following statement is here to show the boundaries and can be removed/commented out.
canvas.drawColor(Color.RED)
canvas.drawCircle(mRingOuterRadius, mRingOuterRadius, mRingInnerRadius, mPaint)
}
override fun setAlpha(alpha: Int) {
}
override fun setColorFilter(colorFilter: ColorFilter?) {
}
override fun getOpacity(): Int {
return PixelFormat.OPAQUE
}
// Adjust the dash length so that we end on a gap and not in the middle of a dash.
private fun getNewDashLength(): Float {
val circumference = Math.PI.toFloat() * mRingInnerRadius * 2
val dashCount = (circumference / (DASH_LENGTH + GAP_LENGTH)).toInt()
val newDashLength = (circumference - dashCount * GAP_LENGTH) / dashCount
return newDashLength
}
companion object {
const val STROKE_WIDTH = 15f
const val DASH_LENGTH = 50f
const val GAP_LENGTH = 15f
}
}
For API 24 and higher, you can place this drawable into an XML file and use it like any other XML drawable.
<drawable xmlns:android="http://schemas.android.com/apk/res/android"
class="com.example.myapp.DashedRingDrawable"/>
For APIs before API 24, you will need to work with this custom drawable programmatically.
I am here not for looking how add border to imageView I know 10000 methods.
I am here for how add rounded border ImageView specific on HOME WIDGET.
Since currently I found no way to do this. I have tried:
Using xml shape as ImageView background, it works for TextView, but not ImageView;
I tried using Glide programatically add rounded border, but that things need a bounch of thing, and I can not make it work.
Here is current code that doesn't work:
import android.appwidget.AppWidgetManager
import android.content.Context
import android.content.SharedPreferences
import android.net.Uri
import android.widget.RemoteViews
import cn.manaai.daybreak.R
import es.antonborri.home_widget.HomeWidgetBackgroundIntent
import es.antonborri.home_widget.HomeWidgetLaunchIntent
import es.antonborri.home_widget.HomeWidgetProvider
import com.bumptech.glide.Glide
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import com.bumptech.glide.load.resource.bitmap.TransformationUtils
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
class HomeWidgetGlanceProvider : HomeWidgetProvider(), AppCompatActivity {
// this load a todo widget, showing todos here
// so the layout here is different.
override fun onUpdate(context: Context, appWidgetManager: AppWidgetManager, appWidgetIds: IntArray, widgetData: SharedPreferences) {
appWidgetIds.forEach { widgetId ->
val views = RemoteViews(context.packageName, R.layout.glance_app_widget).apply {
// Open App on Widget Click
val pendingIntent = HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java)
setOnClickPendingIntent(R.id.widget_container, pendingIntent)
// Swap Title Text by calling Dart Code in the Background
setTextViewText(R.id.nickname, widgetData.getString("title", null)
?: "No Title Set")
val backgroundIntent = HomeWidgetBackgroundIntent.getBroadcast(
context,
Uri.parse("homeWidgetExample://titleClicked2")
)
setOnClickPendingIntent(R.id.nickname, backgroundIntent)
val message = widgetData.getString("message", null)
setTextViewText(R.id.todonum, message
?: "12")
// Detect App opened via Click inside Flutter
val pendingIntentWithData = HomeWidgetLaunchIntent.getActivity(
context,
MainActivity::class.java,
Uri.parse("homeWidgetExample://message?message=$message"))
setOnClickPendingIntent(R.id.todonum, pendingIntentWithData)
var avatar = findViewById(R.id.avatar) as ImageView;
// avatar
Glide.with(this).load("http://p15.qhimg.com/bdm/720_444_0/t01b12dfd7f42342197.jpg")
.apply(RequestOptions.bitmapTransform(RoundedCorners(20)))
// .circleCrop()
.into(avatar)
}
appWidgetManager.updateAppWidget(widgetId, views)
}
}
}
I simplfy want my avatar to be rounded, any idea??
PS: DO NOT SEND ME ANY JAVA CODE.
I am using kotlin only.
You need to make a bitmap from the URL. And pass that bitmap to the function that I made.
fun createRoundedImage(bitmap: Bitmap): Bitmap {
val imageRounded =
Bitmap.createBitmap(bitmap.width, bitmap.height, bitmap.config)
val canvas = Canvas(imageRounded)
val paint = Paint()
paint.isAntiAlias = true
paint.shader = BitmapShader(bitmap, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
canvas.drawRoundRect(
RectF(0f, 0f, bitmap.width.toFloat(), bitmap.height.toFloat()),
(bitmap.width / 2).toFloat(),
(bitmap.height / 2).toFloat(),
paint
)
return imageRounded
}
Now just set the round bitmap returned by the function to your image view.
For example, this button:
I want to make him glow like this:
How can I do this without using an external library button?
Also, I would really like to know how to do this on an ImageView as well. All I could find is using external buttons and image views from libraries which is not suitable in my case.
Big thanks in advance!
Button XML code:
<com.google.android.material.button.MaterialButton
android:id="#+id/btAddMyCard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/add_another_my_card"
android:layout_centerHorizontal="true"
android:layout_marginTop="24dp"
android:layout_below="#id/topAppBar"
style="#style/Widget.App.Button.OutlinedButton" />
Although the new Material widgets are quite functional, they are locked down so it is not easy to customize them in non-prescribed ways. I can show you one way to accomplish what you need.
I use the foreground to draw a rounded rectangle that is a composite of two rounded rectangles: A non-blurred rectangle and a blurred one using a BlurMaskFilter. I let the button draw a transparent rounded rectangle of a specified with so it shows nothing, but I use the stroke width to draw the rounded rectangle although this value really should be specified in a custom attribute for the custom view.
You may need to do some tweaking for your situation.
If you can't really see the glow, here is a magnification:
This image was produced from a custom view based upon MaterialButton.
class MyMaterialGlowButton #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null
) : MaterialButton(context, attrs) {
private val mPaddedRect = RectF()
private val mBlurRadius = strokeWidth.toFloat() * BLUR_RADIUS_FACTOR
private val mLinePaint = Paint().apply {
isAntiAlias = true
strokeWidth = this#MyMaterialGlowButton.strokeWidth.toFloat()
style = Paint.Style.STROKE
color = ROUNDED_RECT_COLOR
}
private val mBlurPaint = Paint().apply {
isAntiAlias = true
strokeWidth = this#MyMaterialGlowButton.strokeWidth.toFloat()
style = Paint.Style.STROKE
color = ROUNDED_RECT_COLOR
maskFilter = BlurMaskFilter(mBlurRadius, BlurMaskFilter.Blur.NORMAL)
}
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
super.onSizeChanged(w, h, oldw, oldh)
val halfStrokeWidth = strokeWidth.toFloat() / 2f
mPaddedRect.left = halfStrokeWidth + mBlurRadius
mPaddedRect.top = halfStrokeWidth + mBlurRadius
mPaddedRect.right = w.toFloat() - halfStrokeWidth - mBlurRadius
mPaddedRect.bottom = h.toFloat() - halfStrokeWidth - mBlurRadius
}
override fun onDrawForeground(canvas: Canvas?) {
super.onDrawForeground(canvas)
val corners = cornerRadius.toFloat()
canvas?.drawRoundRect(mPaddedRect, corners, corners, mBlurPaint)
canvas?.drawRoundRect(mPaddedRect, corners, corners, mLinePaint)
}
private companion object {
const val ROUNDED_RECT_COLOR = 0xFF2b86e6.toInt()
const val BLUR_RADIUS_FACTOR = 1.0f
}
}
The XML used:
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.example.blurredline.MyMaterialGlowButton
android:id="#+id/btAddMyCard"
style="#style/Widget.App.Button.OutlinedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:textAllCaps="false"
android:insetLeft="4dp"
android:insetRight="4dp"
android:text="#string/add_another_my_card"
app:cornerRadius="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
The insets prevent the ripple from extending outside the bounded area. You may need to adjust these values.
And the style. I wasn't sure what your values were, so I used these:
<style name="Widget.App.Button.OutlinedButton" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="strokeColor">#android:color/transparent</item>
<item name="strokeWidth">4dp</item>
<item name="android:textColor">#FF2b86e6</item>
<item name="cornerRadius">16dp</item>
<item name="textAllCaps">false</item>
<item name="fontFamily">sans-serif-light</item>
</style>
I may prefer a RadialGradient for the glow, but that would be much more difficult to implement.
I am extending Chip class to perform some drawing over it for my lib , my use case is more complex but for simplicity let's say i am just drawing a diagonal line
my code
class MyChip (context: Context,attributeSet: AttributeSet) : Chip(context,attributeSet){
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//just want to draw a diagonal line
canvas.drawLine(0f,0f,width/1f,height/1f,paint)
}
}
xml
<com.abhinav.chouhan.loaderchipdemo.MyChip
android:layout_width="200dp"
android:layout_height="50dp"
android:textAlignment="center"
android:text="SOME TEXT"/>
when i don't have attribute android:textAlignment="center" everything works fine , but with that attribute we can not draw anything on chip.
I tried everything but couldn't figure out why is it happening.
Please Help
The Chip widget adheres strongly to the Material Design guidelines and is not (IMO) amenable to change. I suggest that you look at other widgets - maybe a MaterialButton to see if something else may suit your needs.
The attribute you reference, textAlignment, is available in TextView which is a class that Chip is based on. Chips expects wrap_content and also expect for text to be aligned to the start. Somewhere in the mix, your over-draw is lost. I doubt that something like that has ever been tested as it does not adhere to the Material guidelines.
However, if you must use a Chip, here is a way to do so with a custom view:
class MyChip #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : Chip(context, attrs, defStyleAttr) {
private val mLinePaint = Paint().apply {
color = Color.BLUE
strokeWidth = 10f
}
init {
doOnNextLayout {
// Turn off center alignment if specified. We will handle centering ourselves.
if (isTextAlignmentCenter()) {
textAlignment = View.TEXT_ALIGNMENT_GRAVITY
// Get the length of all the stuff before the text.
val lengthBeforeText =
if (isChipIconVisible) iconStartPadding + chipIconSize + iconEndPadding else 0f
val chipCenter = width / 2
// Chips have only one line, so we can get away with this.
val textWidth = layout.getLineWidth(0)
val newTextStartPadding = chipCenter - (textWidth / 2) - lengthBeforeText
textStartPadding = max(0f, newTextStartPadding)
textEndPadding = 0f
}
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//just want to draw a diagonal line
canvas.drawLine(0f, 0f, width.toFloat(), height.toFloat(), mLinePaint)
}
private fun isTextAlignmentCenter() = textAlignment == View.TEXT_ALIGNMENT_CENTER
}
A sample layout:
activity_main.xml
<com.example.myapplication.MyChip
android:id="#+id/chip"
android:layout_width="300dp"
android:layout_height="50dp"
android:layout_gravity="center"
android:text="Some Chip"
android:textAlignment="center"
app:chipIcon="#drawable/ic_baseline_cloud_download_24"
app:chipIconVisible="true"
app:closeIcon="#drawable/ic_baseline_clear_24"
app:closeIconVisible="true" />
</LinearLayout>
And the result:
I looked into the issue a little more deeply. For some reason, canvas operations such a drawLine(), drawRect(), etc. do not function. However, I can draw text on the canvas and fill it with a paint or a color.
Update: So, I tracked the problem down to the bringTextIntoView() method of TextView. For a reason that is not clear to me, the view is scrolled quite a few places (large, like 10's of thousands) in the positive direction. It is in this scrolled position that the text is written. To draw on the Chip, we must also scroll to this position. This scroll explains why I could fill the canvas with a color but could not draw on it so it would be visible.
The following will capture the "bad" scroll position and scroll to that position before drawing on the Chip. The screen capture looks the same as the above image.
MyChip.kt
class MyChip #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : Chip(context, attrs, defStyleAttr)/*, View.OnScrollChangeListener*/ {
private val mLinePaint = Paint().apply {
color = Color.BLUE
strokeWidth = 10f
}
private var mBadScroll = 0f
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//just want to draw a diagonal line
canvas.withTranslation(x = mBadScroll) {
canvas.drawLine(0f, 0f, this#MyChip.width.toFloat(), height.toFloat(), mLinePaint)
}
}
private fun isTextAlignmentCenter() = textAlignment == View.TEXT_ALIGNMENT_CENTER
override fun scrollTo(x: Int, y: Int) {
super.scrollTo(x, y)
if (isTextAlignmentCenter()) {
mBadScroll = scrollX.toFloat()
}
}
}
Update: Horizontal scrolling is still the issue. In onMeasure() method of TextView, the wanted width is set to VERY_WIDE if horizontal scrolling is enabled for the view. (And it is for Chip.) VERY_WIDE is 1024*1024 or one megabyte. This is the source of the large scroll that we see later on. This problem with Chip can be replicated directly with TextView if we call setHorizontallyScrolling(true) in the TextView.
Chips have only one line and, probably, shouldn't scroll. Calling setHorizontallyScrolling(true) doesn't make the Chip or TextView scrollable anyway. The above code now just becomes:
MyChip.kt
class MyChip #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : Chip(context, attrs, defStyleAttr) {
private val mLinePaint = Paint().apply {
color = Color.BLUE
strokeWidth = 10f
}
init {
setHorizontallyScrolling(false)
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
//just want to draw a diagonal line
canvas.drawLine(0f, 0f, this#MyChip.width.toFloat(), height.toFloat(), mLinePaint)
}
}
Horizontal scrolling is set to "true" in the constructor for Chip.
I'm trying to create a custom ImageView or Drawable in Kotlin which enables dynamic file extensions can be drawn on a base image at runtime. The end result will look like this. Tried creating custom AppCompatImageView class and overriding onDraw() with no luck. Being a novice in this area, can you suggest me a good starting point to achieve this?
EDIT
The file extension is a text that needs to be drawn on the base image with a background as shown in the attachment.
You could create a LayerDrawable at runtime resulting in the superposition of two drawables (one for the background and one for the extension) and position the extension drawable at the bottom right.
It would look like this
val layerDrawable = LayerDrawable(
arrayOf(
AppCompatResources.getDrawable(context, R.drawable.ic_base_sound_file),
AppCompatResources.getDrawable(context, R.drawable.ic_aiff_extension)
)
).apply {
setLayerInset(1, 20, 40, 0, 10)
}
imageView.setImageDrawable(layerDrawable)
The method setLayerInset(index, left, top, right, bottom) will add insets to the drawable at position 'index' (here 1 -> the extension drawable).
You can also use a remote image if needed for the base image.
I prefer to use a custom view than a custom drawable. because of its flexibility in measuring and customizing height and width.
So I've created the FileView:
import android.content.Context
import android.content.res.Resources
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.text.TextPaint
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatImageView
class FileView #JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatImageView(context, attrs, defStyleAttr) {
init {
setImageResource(R.drawable.ic_file)
}
var icon: Drawable? = null
set(value) {
field = value
postInvalidate()
}
var ext: CharSequence? = null
set(value) {
field = value
postInvalidate()
}
private val iconRect = Rect()
private val extRect = Rect()
private val extPaint by lazy {
TextPaint().apply {
style = Paint.Style.FILL
color = Color.WHITE
isAntiAlias = true
textAlign = Paint.Align.CENTER
textSize = 12f * Resources.getSystem().displayMetrics.density + 0.5f
}
}
private val extBackgroundPaint by lazy {
TextPaint().apply {
style = Paint.Style.FILL
color = Color.BLACK
isAntiAlias = true
}
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val centerX = width / 2
val centerY = height / 2
icon?.let { icon ->
iconRect.set(
centerX - icon.intrinsicWidth / 2,
centerY - icon.intrinsicHeight / 2,
centerX + icon.intrinsicWidth / 2,
centerY + icon.intrinsicHeight / 2
)
icon.bounds = iconRect
icon.draw(canvas)
}
ext?.let { ext ->
val truncatedExt =
if (ext.length > 6) ext.subSequence(0, 6).toString().plus('…')
else ext
// extRect is used for measured ext height
extPaint.getTextBounds("X", 0, 1, extRect)
val extHeight = extRect.height() // keep ext height
val extWidth = extPaint.measureText(truncatedExt, 0, truncatedExt.length).toInt() // keep ext width
val extPadding = 4.toPx
val extMargin = 4.toPx
val extRight = width - extMargin
val extBottom = height - extMargin
// extRect is reused for ext background bound
extRect.set(
extRight - extWidth - extPadding * 2,
extBottom - extHeight - extPadding * 2,
extRight,
extBottom
)
canvas.drawRect(extRect, extBackgroundPaint)
canvas.drawText(
truncatedExt,
0,
truncatedExt.length,
extRect.exactCenterX(),
extRect.bottom - ((extRect.height() - extHeight) / 2f),
extPaint
)
}
}
private val Int.toPx get() = (this * Resources.getSystem().displayMetrics.density).toInt()
}
and use it:
with(binding.fileView) {
icon = ContextCompat.getDrawable(context, R.drawable.ic_music)
ext = ".aiff"
}
Output:
I can think of 2 solutions for this. I am simply sharing ideas/approaches here and related code can easily be found.
Simpler approach would be to have this layout designed in your xml. Then create your custom class extending ViewGroup and in its constructor you can inflate the view xml and initialise things. Then you can define any helper method ,say setData() where you can pass the file extension info and/or thumb image. Then you can update your view right there.
Another approach would be to not create any xml but programmatically create them in your custom ViewGroup constructor. Then you can have a similar helper method as above to set values to various view components. After you have set everything, call requestLayout() at the end. You can then, if required, update views in onLayout() and perform any spacing/margin calculations. Then using these values draw them inside onDraw().