I want to create Custom Image View for avatars which can work in two modes:
If android:src is empty display circle with first letter of name
Else display circle avatar
At now if I use android:src my drawable is not in center, how can i make central crop of it?
Example
Here is code of custom view:
class AvatarImageView : AppCompatImageView{
private var letter = ""
private var avatarBackgroundColor = 0
private var avatarLetterColor = 0
private val paintBackground = Paint()
private val paintLetter = Paint(Paint.LINEAR_TEXT_FLAG)
private val paintAvatarImage = Paint()
private var circleX = 0f
private var circleY = 0f
private var circleRadius = 0f
private var letterX = 0f
private var letterY = 0f
private val avatarImageRect = RectF(0f, 0f, 0f, 0f)
constructor(context : Context)
: this(context, null)
constructor(context: Context, attrs : AttributeSet?)
: this(context, attrs, 0)
constructor(context: Context, attrs : AttributeSet?, defStyleAttr : Int)
: super(context, attrs, defStyleAttr){
attrs?.let{
val array = context.obtainStyledAttributes(it, R.styleable.AvatarImageView)
letter = array.getString(R.styleable.AvatarImageView_signature)?.trim()?.substring(0..0)?.toUpperCase(Locale.ROOT) ?: ""
avatarBackgroundColor = array.getColor(R.styleable.AvatarImageView_background_color, Color.BLUE)
avatarLetterColor = array.getColor(R.styleable.AvatarImageView_letter_color, Color.WHITE)
array.recycle()
}
init()
}
private fun init(){
paintBackground.style = Paint.Style.FILL
paintBackground.color = avatarBackgroundColor
paintLetter.color = avatarLetterColor
paintLetter.textAlign = Paint.Align.CENTER
paintLetter.isAntiAlias = true
paintLetter.style = Paint.Style.FILL
drawable?.let{
paintAvatarImage.isAntiAlias = true
val shader = BitmapShader(getBitmapFromDrawable(it), Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
paintAvatarImage.shader = shader
}
}
private fun getBitmapFromDrawable(drawable: Drawable): Bitmap{
if (drawable is BitmapDrawable) {
drawable.bitmap?.let {
return drawable.bitmap
}
}
val bitmap: Bitmap = Bitmap.createBitmap(
drawable.intrinsicWidth,
drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
return bitmap
}
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
super.onLayout(changed, left, top, right, bottom)
val availableWidth = width
val availableHeight = height
circleX = availableWidth / 2f
circleY = availableHeight / 2f
circleRadius = min(availableWidth, availableHeight) / 2f
paintLetter.textSize = availableHeight / 2f
letterX = availableWidth / 2f
letterY = availableHeight / 2f - ((paintLetter.descent() + paintLetter.ascent()) / 2f)
avatarImageRect.right = width.toFloat()
avatarImageRect.bottom = height.toFloat()
}
override fun onDraw(canvas: Canvas?) {
drawable?.let{
canvas?.drawRoundRect(avatarImageRect, width.toFloat(), height.toFloat(), paintAvatarImage)
} ?:run {
canvas?.drawCircle(circleX, circleY, circleRadius, paintBackground)
canvas?.drawText(letter, letterX, letterY, paintLetter)
}
}
}
My xml file:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:padding="16dp"
tools:context=".ui.AvatarsFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<com.test.test.views.AvatarImageView
android:layout_width="32dp"
android:layout_height="32dp"
android:src="#drawable/avatar"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
style="#style/TextAppearance.AppCompat.Medium"
android:text="Ivan Ivanov"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginTop="10dp">
<com.test.test.views.AvatarImageView
android:layout_width="32dp"
android:layout_height="32dp"
app:signature="Ivan Ivanov"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
style="#style/TextAppearance.AppCompat.Medium"
android:text="Ivan Ivanov"/>
</LinearLayout>
</LinearLayout>
UPDATED: I know about other libraries, but I want to do it without them.
I used a CircleImageView to display images and picasso to load images into the CircleImageView
Picasso centers and crops the image
It worked for me...
SOLVED:
I`m now using this code for crop my image from https://stackoverflow.com/a/12089127/12209565:
private fun getCircleBitmap(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
}
Related
In Android app , I want to draw a button in canvas , like the image exactly the below image
I tried some stack overflow answers but can't get the exact output , particularly the light shadow below . My worst code is below
val corners = floatArrayOf(
80f, 80f, // Top left radius in px
80f, 80f, // Top right radius in px
0f, 0f, // Bottom right radius in px
0f, 0f // Bottom left radius in px
)
val path = Path()
val rect = RectF(550f, 500f, 100f, 300f)
paint.style = Paint.Style.FILL;
paint.color = Color.WHITE;
path.addRoundRect(rect, corners, Path.Direction.CW)
canvas?.drawPath(path, paint)
paint.style = Paint.Style.STROKE;
paint.color = Color.BLACK;
path.addRoundRect(rect, corners, Path.Direction.CW)
canvas?.drawPath(path, paint)
paint.setColor(Color.RED)
paint.setStyle(Paint.Style.FILL)
val paint2 = Paint()
paint2.setColor(Color.GREEN)
paint2.setTextSize(50f) //set text size
val w: Float = paint2.measureText("VIEW CAMPSITE MAP") / 2
val textSize: Float = paint2.textSize
canvas?.drawText("VIEW CAMPSITE MAP", 300f, 300f ,paint2);
Also I can't set text in correct position
I also need a gradient to show bulging state like a real material button
And also need an click listener in the canvas / paint
I need it in pure canvas and paint not in any views
please help me
Here is one of the ways. Dont forget to move all variables like text, textSize, etc in custom attributes. Also I didnt get what do you mean by "click listener in the canvas"
class CustomView #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
//constants. move them all to custom attributes
private val boxRadius = convertDpToPixel(10f)
private val boxColor = Color.parseColor("#52618e")
private val boxBackgroundColor = Color.WHITE
private val boxShadowSize = convertDpToPixel(2f)
private val boxStrokeWidth = convertDpToPixel(1f)
private val textColor = Color.parseColor("#21a207")
private val fontSize = convertDpToPixel(30f)
private val text = "View Campsite Plan"
private lateinit var boxShadow: RectF
private lateinit var boxBackground: RectF
private lateinit var boxShadowPaint: Paint
private lateinit var boxBackgroundPaint: Paint
private var textWidth = 0f
private var textSmallGlyphHeight = 0f
private lateinit var textPaint: Paint
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
boxShadow = RectF(0f, 0f, w.toFloat(), h.toFloat())
boxBackground = RectF(boxStrokeWidth, boxStrokeWidth,
w.toFloat()-boxStrokeWidth, h.toFloat()-boxStrokeWidth-boxShadowSize)
boxShadowPaint = Paint().apply { color = boxColor }
boxBackgroundPaint = Paint().apply { color = boxBackgroundColor }
textPaint = Paint().apply {
color = textColor
textSize = fontSize
typeface = Typeface.create(Typeface.DEFAULT, Typeface.BOLD)
textWidth = measureText(text)
textSmallGlyphHeight = fontMetrics.run { ascent + descent }
}
}
override fun onDraw(canvas: Canvas?) {
canvas?.drawRoundRect(boxShadow, boxRadius, boxRadius, boxShadowPaint)
canvas?.drawRoundRect(boxBackground, boxRadius, boxRadius, boxBackgroundPaint)
val textStartPadding = (width - textWidth)/2f
val textTopPadding = (height - textSmallGlyphHeight)/2f
canvas?.drawText(text, textStartPadding, textTopPadding, textPaint)
}
private fun convertDpToPixel(dp: Float) =
dp*(resources.displayMetrics.densityDpi/DisplayMetrics.DENSITY_DEFAULT)
}
I want to draw a progress animation in a button from left to right somewhat like this:
Here the button nothing but a custom view. So the canvas should draw this and I have to extend view class only. I am not sure what should I use here I tried using canvas.drawPath using a value animator but no success.
I am not sure which canvas method should be used here and how I can animate it from left to right.
Can anyone help me here, please?
To start with the custom view, keep in mind of the following points
As it dictates it's a progress indicator view with text over it. So
choose TextView to extend the functionality.
The background animates here, that means our progress draw part has
to be done before original textview draw cycle.
It's wise to keep the progress update outside the custom view.
With this in mind the custom view will look as below
class DownloadButton : androidx.appcompat.widget.AppCompatTextView {
/// constructor
private val bgPaint: Paint = Paint().apply {
color = 0xff216353.toInt()
}
private val progressPaint: Paint = Paint().apply {
color = 0xff75daad.toInt()
}
var progress: Float = 0f
override fun onDraw(canvas: Canvas?) {
// Draw before original content drawn
// Compute the dx based on the progress
// and draw 2 rects
canvas?.let {
val dx = it.width * progress
it.drawRect(RectF(0f, 0f, dx, it.height * 1f), bgPaint)
it.drawRect(RectF(dx, 0f, it.width * 1f, it.height * 1f), progressPaint)
}
super.onDraw(canvas)
}
fun updateProgress(progress: Float) {
this.progress = progress
val percent = (progress * 100).toInt()
text = "Progress $percent%"
invalidate()
}
}
Use it in xml as any TextView used
<com.example.custom.DownloadButton
android:id="#+id/downloadButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center"
android:padding="16dp"
android:text="Download"
android:textColor="#fff"
android:textSize="20sp"
android:textStyle="bold"
android:layout_margin="16dp"
android:typeface="monospace" />
Anywhere in the code, call downloadButton.updateProgress() to redraw the progress.
Note this is a bare minimum implementation where we haven't done computation optimization for edge cases (0 - 100) progress where drawing one rect would be suffice
This worked for me , hope it also help you out !
LoadingButton.kt
import android.animation.AnimatorInflater
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.View
import androidx.core.content.ContextCompat
import kotlin.properties.Delegates
class LoadingButton #JvmOverloads constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : View(context, attrs, defStyleAttr) {
private var bgColor: Int = Color.BLACK
private var textColor: Int = Color.BLACK // default color
private var widthSize = 0
private var heightSize = 0
#Volatile
private var progress: Double = 0.0
private var valueAnimator: ValueAnimator
private var buttonState: ButtonState by Delegates.observable(ButtonState.Completed) {
p, old, new ->
}
private val updateListener = ValueAnimator.AnimatorUpdateListener {
progress = (it.animatedValue as Float).toDouble()
invalidate()
requestLayout()
}
fun hasCompletedDownload() {
// cancel the animation when file is downloaded
valueAnimator.cancel()
buttonState = ButtonState.Completed
invalidate()
requestLayout()
}
init {
isClickable = true
valueAnimator = AnimatorInflater.loadAnimator(
context, R.animator.loading_animation
) as ValueAnimator
valueAnimator.addUpdateListener(updateListener)
val attr = context.theme.obtainStyledAttributes(
attrs,
R.styleable.LoadingButton,
0,
0
)
try {
bgColor = attr.getColor(
R.styleable.LoadingButton_bgColor,
ContextCompat.getColor(context, R.color.colorPrimary)
)
textColor = attr.getColor(
R.styleable.LoadingButton_textColor,
ContextCompat.getColor(context, R.color.colorPrimary)
)
} finally {
attr.recycle()
}
}
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.FILL
textAlign = Paint.Align.CENTER
textSize = 55.0f
typeface = Typeface.create("", Typeface.BOLD)
}
override fun performClick(): Boolean {
super.performClick()
if (buttonState == ButtonState.Completed) buttonState = ButtonState.Loading
animation()
return true
}
private fun animation() {
valueAnimator.start()
}
private val rect = RectF(
740f,
50f,
810f,
110f
)
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
paint.strokeWidth = 0f
paint.color = bgColor
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
if (buttonState == ButtonState.Loading) {
paint.color = Color.parseColor("#004349")
canvas.drawRect(
0f, 0f,
(width * (progress / 100)).toFloat(), height.toFloat(), paint
)
paint.color = Color.parseColor("#F9A825")
canvas.drawArc(rect, 0f, (360 * (progress / 100)).toFloat(), true, paint)
}
val buttonText =
if (buttonState == ButtonState.Loading)
resources.getString(R.string.loading)
else resources.getString(R.string.download)
paint.color = textColor
canvas.drawText(buttonText, (width / 2).toFloat(), ((height + 30) / 2).toFloat(),
paint)
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val minw: Int = paddingLeft + paddingRight + suggestedMinimumWidth
val w: Int = resolveSizeAndState(minw, widthMeasureSpec, 1)
val h: Int = resolveSizeAndState(
MeasureSpec.getSize(w),
heightMeasureSpec,
0
)
widthSize = w
heightSize = h
setMeasuredDimension(w, h)
}
}
call hasCompletedDownload() function when file got downloaded in order to stop the animation.
Add a new file (say attrs.xml) in "values" folder under "res" folder to add attributes for custom button.
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="LoadingButton">
<attr name="bgColor" format="integer" />
<attr name="textColor" format="integer" />
</declare-styleable>
</resources>
Add this in your layout
<com.android.example.LoadingButton
android:id="#+id/custom_button"
android:layout_width="0dp"
android:layout_height="60dp"
android:layout_margin="20dp"
app:textColor="#color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
Add another folder (say animation) under "res" folder then add a file "loading_animation.xml" in order to add animation attributes for the custom button.
<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="500"
android:interpolator="#android:anim/accelerate_decelerate_interpolator"
android:valueFrom="0f"
android:valueTo="100f"
android:valueType="floatType" />
Thank You :)
I wrote the below code to draw a red rectangle, but apparently it is wrong :( I did not get any error, in same time did not get anything drawn, how can I fix it
private fun updateTransform() {
val matrix = Matrix()
// Compute the center of the view finder
val centerX = viewFinder.width / 2f
val centerY = viewFinder.height / 2f
// Correct preview output to account for display rotation
val rotationDegrees = when(viewFinder.display.rotation) {
Surface.ROTATION_0 -> 0
Surface.ROTATION_90 -> 90
Surface.ROTATION_180 -> 180
Surface.ROTATION_270 -> 270
else -> return
}
matrix.postRotate(-rotationDegrees.toFloat(), centerX, centerY)
val bmp = Bitmap.createBitmap(1425, 1425, Bitmap.Config.ARGB_8888)
val paint = Paint().apply {
isAntiAlias = true
style = Paint.Style.STROKE
color = Color.RED
strokeWidth = 10f
}
// x: {559, y: 1901}, height: 1425, width: 1425
var canvas = Canvas(bmp)
canvas.apply {
drawRect(
25.toFloat(), // faceRectangle.left,
25.toFloat(), //faceRectangle.top,
250.toFloat(),
250.toFloat(),
paint
)
}
viewFinder.unlockCanvasAndPost(canvas)
viewFinder.setTransform(matrix)
}
I found a work around to do this, not sure if this is the only way, anyhow, I'll post my way here in case it is helpful for someone in the future:
The solution is to create an image view, with the same dimension, and location (constrains) of the cameeraX texture view, toact as overlay, and do all the drawings on it, you just need to be sure that this over lay is above not under the texture view, i.e. following it in the layout xml file.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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">
<TextureView
android:id="#+id/view_finder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:visibility="visible" />
<ImageView
android:id="#+id/imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="#string/pic_desc"
android:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>
And the drawing code is:
lateinit var overlay: Bitmap
private fun startCamera() {
val analyzerConfig = ImageAnalysisConfig.Builder().apply {
setImageReaderMode(
ImageAnalysis.ImageReaderMode.ACQUIRE_LATEST_IMAGE)
}.build()
val analyzerUseCase = ImageAnalysis(analyzerConfig).apply {
analyzer = ImageAnalysis.Analyzer { image, rotationDegrees ->
val bitmap = view_finder.bitmap ?: return#Analyzer
overlay = Bitmap.createBitmap(bitmap.width, bitmap.height, Bitmap.Config.ARGB_8888)
scope.launch(Dispatchers.Unconfined) {
val mat = Mat()
Utils.bitmapToMat(bitmap!!, mat)
val detectedFaces = FaceDetection.detectFaces(bitmap!!)
if (detectedFaces.toArray().isNotEmpty()) {
val paint = Paint().apply {
isAntiAlias = true
style = Paint.Style.STROKE
color = Color.RED
strokeWidth = 10f
}
for (rect in detectedFaces.toArray()) {
var canvas = Canvas(overlay)
canvas.drawRect(
rect.x.toFloat(),
rect.y.toFloat(),
rect.x.toFloat() + rect.width,
rect.y.toFloat() + rect.height,
paint
)
overlay?.let { Canvas(it) }?.apply {
canvas
}
}
}
}
runOnUiThread {
imageView.setImageBitmap(overlay)
}
}
}
CameraX.bindToLifecycle(this, preview, imageCapture, analyzerUseCase)
}
Below is an output of my app as of now showing this drawing:
I want to have an ImageView containing an Image with rounded curves, but not circular (which is way easier of course)..
The X marked areas should be black, the rest should stay blue. It should look (kind of) like this
What I tried:
I spent hours fighting with Path.quadTo and Path.cubicTo with the help of some tools but I didn't have any success yet. I just don't really get the usage to be honest.
What my code currently looks like:
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val paint = Paint()
paint.color = Color.BLACK
paint.strokeWidth = 1f
paint.strokeCap = Paint.Cap.ROUND
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)
paint.style = Paint.Style.FILL
val fHeight = canvas.height.toFloat()
val startEndHeight = canvas.height / 1.18f
val fWidth = canvas.width.toFloat()
val halfWidth = (fWidth / 2)
val path = Path()
//X = Left side, Y = close to bottom
val ptStart = PointF(0f, startEndHeight)
//X = Middle, Y = Bottom
val ptMiddle = PointF(halfWidth, fHeight + 95)
// X = Right Side, Y = close to bottom
val ptEnd = PointF(fWidth, startEndHeight)
path.moveTo(ptStart.x, ptStart.y)
path.quadTo(ptMiddle.x, ptMiddle.y, ptEnd.x, ptEnd.y)
path.close()
canvas.drawPath(path, paint)
}
It can't be that hard, right?
Is it possible to colorize the red marked areas and leave everything else untouched?
Fixed! This is my final solution which applies a quad bezier to the ImageView. I had to add 2 lines to the path to get the result I wanted.
class HeaderImageView : AppCompatImageView {
constructor(context: Context?) : super(context) {
init()
}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {
init()
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
init()
}
lateinit var paint: Paint
private fun init() {
paint = Paint()
paint.color = Color.WHITE
paint.xfermode = PorterDuffXfermode(PorterDuff.Mode.SRC_ATOP)
paint.style = Paint.Style.FILL
}
#SuppressLint("CanvasSize")
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
val fHeight = canvas.height.toFloat()
val startEndHeight = canvas.height / 1.18f
val fWidth = canvas.width.toFloat()
val halfWidth = (fWidth / 2)
val path = Path()
//X = Left side, Y = close to bottom
val ptStart = PointF(0f, startEndHeight)
//X = Middle, Y = Bottom
val ptMiddle = PointF(halfWidth, fHeight + 95)
// X = Right Side, Y = close to bottom
val ptEnd = PointF(fWidth, startEndHeight)
path.moveTo(ptStart.x, ptStart.y)
path.quadTo(ptMiddle.x, ptMiddle.y, ptEnd.x, ptEnd.y)
path.lineTo(fWidth, fHeight)
path.lineTo(0f, fHeight)
path.close()
canvas.drawPath(path, paint)
}
}
I am implementing custom ViewGroup which has overlapping ImageViews.
I load images with GlideApp. Problem is that some images are cropped a little (visible on the left side) and I cannot prevent this. The drawables which get cropped are Vector drawables and their mDrawableWidth & mDrawableHeight are larger than the height of the ViewGroup. I thought that the images would be scaled to fit the view dimensions (mScaleType = FIT_CENTER).
class OverlappingImageViewGroup : ViewGroup {
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val widthSize: Int = getDefaultSize(0, widthMeasureSpec)
val heightSize: Int = getDefaultSize(0, heightMeasureSpec)
val majorDimension = Math.min(widthSize, heightSize)
//Measure all child views
val childSpec = MeasureSpec.makeMeasureSpec(majorDimension, MeasureSpec.EXACTLY)
measureChildren(childSpec, childSpec)
//MUST call this to save our own dimensions
setMeasuredDimension(majorDimension, majorDimension)
}
override fun onLayout(changed: Boolean,
leftRelativeToParent: Int,
topRelativeToParent: Int,
rightRelativeToParent: Int,
bottomRelativeToParent: Int) {
val percentOverlap = 0.8f
if (childCount > 0) {
for (i in 0 until childCount) {
val child = getChildAt(i)
val childWidth = child.measuredWidth
val topBound = 0 /* there is just one row */
if (i == 0) {
child.layout(0, topBound, childWidth, childWidth)
} else {
val leftBound = i * percentOverlap * childWidth
child.layout(
leftBound.toInt(),
topBound,
leftBound.toInt() + childWidth,
topBound + childWidth)
}
}
}
}
}
ImageViews are created programmatically:
val context = holder.rootView.context
val imageView: ImageView = ImageView(context)
GlideApp.with(context)
.load(imageUrl)
.placeholder(userPlaceholder)
.fallback(userPlaceholder)
.error(userPlaceholder)
.transform(CircleWithBorderTransform())
.into(imageView)
holder.membersIcons.addView(imageView)
Glide transformation:
override fun transform(pool: BitmapPool, source: Bitmap, outWidth: Int, outHeight: Int): Bitmap {
val width = source.width
val height = source.height
val canvasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val shader = BitmapShader(source, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)
val paint = Paint()
paint.isAntiAlias = true
paint.shader = shader
val canvas = Canvas(canvasBitmap)
val radius = if (width > height) height.toFloat() / 2f else width.toFloat() / 2f
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
//border around the image
paint.shader = null
paint.style = Paint.Style.STROKE
paint.strokeWidth = 2f
paint.color = Color.WHITE
canvas.drawCircle((width / 2).toFloat(), (height / 2).toFloat(), radius, paint)
return canvasBitmap
}