I want to create cardView with anko and set cornerRadius param to it. But When I try to do - no such differents come.
In main class I do this:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup): View {
with(applicationContext!!) {
listView = listView {
layoutParams = ViewGroup.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)
dividerHeight = 20
}
}
listView?.adapter = CustomAdapter(forms)
return listView!!
}
In CustomAdapter I return cardView like this:
override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View {
val currentForm = getItem(position)
return convertView ?: createCardView(parent!!.context, currentForm)
}
private fun createCardView(context: Context, form: FormField): View =
with(context) {
frameLayout {
cardView {
layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT).apply {
leftMargin = dip(10)
rightMargin = dip(10)
topMargin = dip(5)
bottomMargin = dip(5)
}
backgroundColor = Color.WHITE
radius = dip(8).toFloat()
verticalLayout {
// title
textView {
text = form.title
textColor = ContextCompat.getColor(context, R.color.colorPrimary)
textSize = 20f
}.lparams(width = matchParent) {
leftMargin = dip(15)
topMargin = dip(10)
bottomMargin = dip(10)
}
// subtitle
textView {
if (form.subTitle != null) {
text = form.subTitle
textColor = ContextCompat.getColor(context, R.color.colorPrimary)
textSize = 12f
visibility = View.VISIBLE
} else {
visibility = View.GONE
}
}.lparams(width = matchParent) {
leftMargin = dip(15)
topMargin = dip(10)
bottomMargin = dip(10)
}
}.lparams(width = matchParent, height = matchParent)
}
}
}
I try to call 'radius' setter in different ways and values, but result is always like this
As you can see - the corners are always rectangle. What I want - to round the corners with Anko
Small p.s. - when I return from getView inflated xml layout with same cardview - it has rounded corners.
So, the problem was in
backgroundColor = Color.WHITE
It was set the default background DRAWABLE param to ColorDrawable instead inner RoundRectDrawable.
So, when I change this row to :
background.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_ATOP)
All start to work and corners become to be rounded
The previous answer didn't help me. That is worked for me:
cardView {
background = GradientDrawable().apply {
shape = GradientDrawable.RECTANGLE
cornerRadius = 8f
setStroke(2, grey)
....
}
}
Related
I am trying to create tiles view where a group of tiles are placed in an order, either side by side or one over the other. Each tile has some data like an image and text, these data should support dragging onto other tiles to allow swapping the content between tiles. I was able to achieve drawing tiles and swapping the data but clipping around the tiles is a little tricky.
If you notice the tiles group on the left, there is a separation between each tile, and the green background is clearly visible. But in the tiles group on the right where tiles are placed on top of each other, the background is not visible. I have done some research and everything points towards clipOutRect or clipOutPath but I am unable to achieve what I am trying. Below is the code I am using
In my activity:
private fun createView() {
contentView?.removeAllViews()
val relativeRoot = RelativeLayout(this)
val param = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
relativeRoot.layoutParams = param
currentLayout?.forEach { tile ->
tile.setServiceDragListener()
tile.outlineProvider = TileOutlineProvider()
tile.clipToOutline = true
relativeRoot.addView(tile)
tile.bringToFront()
}
contentView?.addView(relativeRoot)
}
and this is the custom view
class Tile2#JvmOverloads constructor(tileId: Int, title: String, left: Float, right: Float, top: Float, bottom: Float, parentHeight: Int, parentWidth: Int,
context: Context?, attrs: AttributeSet? = null, style: Int = 0):
View(context, attrs, style) {
var tileId: Int = -1
private var titleTitle: String = ""
private var left: Float = 0f
private var right: Float = 0f
private var top: Float = 0f
private var bottom: Float = 0f
private val paint = Paint().apply {
color = Color.BLACK
strokeWidth = 10f
this.style = Paint.Style.STROKE
}
val tileRectF = RectF()
val tilePath = Path()
private var bitmap: Bitmap? = null
private var parentWidth: Int = 0
private var parentHeight: Int = 0
private var dragListener: OnDragListener? = null
private var text: String = "test"
set(value) {
field = value
}
private var textPaint = Paint().apply {
color = Color.BLACK
this.style = Paint.Style.FILL
textSize = 30f
textAlign = Paint.Align.CENTER
}
private var icon: Int = -1
set(value) {
field = value
bitmap = BitmapFactory.decodeResource(context?.resources, value)
}
init {
this.tileId = tileId
this.titleTitle = title
this.left = left * parentWidth
this.right = right * parentWidth
this.top = top * parentHeight
this.bottom = bottom * parentHeight
this.parentHeight = parentHeight
this.parentWidth = parentWidth
background = resources.getDrawable(R.drawable.border)
}
override fun dispatchDraw(canvas: Canvas?) {
super.dispatchDraw(canvas)
clipViews(canvas)
}
private fun clipViews(canvas: Canvas?) {
tileRectF.set(left + 15, top - 15, right - 15, bottom - 15)
tilePath.lineTo(left, top)
tilePath.lineTo(right, top)
tilePath.lineTo(right, bottom)
tilePath.lineTo(left, bottom)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
canvas?.clipOutRect(tileRectF)
} else {
#Suppress("DEPRECATION")
canvas?.clipRect(tileRectF, Region.Op.DIFFERENCE)
}
canvas?.clipPath(tilePath)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
canvas?.clipOutPath(tilePath)
} else {
#Suppress("DEPRECATION")
canvas?.clipPath(tilePath, Region.Op.DIFFERENCE)
}
bitmap?.also { canvas?.drawBitmap(it, (left + right)/2f, (top + bottom)/2f + 50, paint) }
canvas?.drawRect(left, top, right, bottom, paint)
canvas?.save()
canvas?.translate((left + right)/2f, (top + bottom)/2f)
canvas?.drawText(text, 0f, 0f, textPaint)
canvas?.restore()
canvas?.save()
}
fun setServiceDragListener() {
this.setOnDragListener { _, event ->
when (event?.action) {
DragEvent.ACTION_DROP -> {
val serviceItem =
((event as DragEvent).clipData as ClipData).getItemAt(0).intent.getSerializableExtra(
"serviceData"
) as? ServiceItem
serviceItem?.let {
updateViews(it.name, it.imageIcon)
}
}
}
true
}
}
fun updateViews(serviceName: String, serviceIcon: Int) {
text = serviceName
icon = serviceIcon
invalidate()
}
}
I use Compose as a item in RecyclerView, I want to calculate the visible percentage of item. But the boundsInParent and boundsInRoot doesn't change when scroll up and down, the visible area is same as item size.
The following is code, this code works well when use in LazyColumn
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.onGloballyPositioned {
val bounds = it.boundsInParent()
val visibleArea = bounds.height * bounds.width
val totalArea = it.size.width * it.size.height
val percentage = visibleArea / totalArea
L.d("TAG", "In recyclerview: $percentage, ${it.boundsInWindow()}, ${it.boundsInParent()}, ${it.boundsInRoot()}")
}
)```
If you use compose in RecyclerView, you should also use View to calculate the visibility percentage, the following code works!
fun Modifier.visibilityPercentage(callback: (Float) -> Unit): Modifier {
return composed {
val view = LocalView.current.parent as View
onGloballyPositioned {
callback(visibilityPercentage(view))
}
}
}
private fun visibilityPercentage(view: View): Float {
if (!view.isAttachedToWindow) return 0f
val rect = Rect()
if (!view.getLocalVisibleRect(rect)) return 0f
val totalArea = (view.height * view.width).toFloat()
if (totalArea == 0f) return 0f
val visibleArea(rect.bottom - rect.top) * (rect.right - rect.left)
return visibleArea / totalArea
}
I'm trying to implement a rating bar. I refer to https://gist.github.com/vitorprado/0ae4ad60c296aefafba4a157bb165e60 but I don't understand anything from this code. It works but when I use this code the stars don't have rounded corners. I want to implement something like the following :
I made very basic sample for this, it would give the basic idea for creating rating bar with sample border and filled png files.
#Composable
private fun RatingBar(
modifier: Modifier = Modifier,
rating: Float,
spaceBetween: Dp = 0.dp
) {
val image = ImageBitmap.imageResource(id = R.drawable.star)
val imageFull = ImageBitmap.imageResource(id = R.drawable.star_full)
val totalCount = 5
val height = LocalDensity.current.run { image.height.toDp() }
val width = LocalDensity.current.run { image.width.toDp() }
val space = LocalDensity.current.run { spaceBetween.toPx() }
val totalWidth = width * totalCount + spaceBetween * (totalCount - 1)
Box(
modifier
.width(totalWidth)
.height(height)
.drawBehind {
drawRating(rating, image, imageFull, space)
})
}
private fun DrawScope.drawRating(
rating: Float,
image: ImageBitmap,
imageFull: ImageBitmap,
space: Float
) {
val totalCount = 5
val imageWidth = image.width.toFloat()
val imageHeight = size.height
val reminder = rating - rating.toInt()
val ratingInt = (rating - reminder).toInt()
for (i in 0 until totalCount) {
val start = imageWidth * i + space * i
drawImage(
image = image,
topLeft = Offset(start, 0f)
)
}
drawWithLayer {
for (i in 0 until totalCount) {
val start = imageWidth * i + space * i
// Destination
drawImage(
image = imageFull,
topLeft = Offset(start, 0f)
)
}
val end = imageWidth * totalCount + space * (totalCount - 1)
val start = rating * imageWidth + ratingInt * space
val size = end - start
// Source
drawRect(
Color.Transparent,
topLeft = Offset(start, 0f),
size = Size(size, height = imageHeight),
blendMode = BlendMode.SrcIn
)
}
}
private fun DrawScope.drawWithLayer(block: DrawScope.() -> Unit) {
with(drawContext.canvas.nativeCanvas) {
val checkPoint = saveLayer(null, null)
block()
restoreToCount(checkPoint)
}
}
Usage
Column {
RatingBar(rating = 3.7f, spaceBetween = 3.dp)
RatingBar(rating = 2.5f, spaceBetween = 2.dp)
RatingBar(rating = 4.5f, spaceBetween = 2.dp)
RatingBar(rating = 1.3f, spaceBetween = 4.dp)
}
Result
Also created a library that uses gestures, other png files and vectors as rating items is available here.
RatingBar(
rating = rating,
space = 2.dp,
imageBackground = imageBackground,
imageForeground = imageForeground,
animationEnabled = false,
gestureEnabled = true,
itemSize = 60.dp
) {
rating = it
}
You can pass the custom drawable as icon. check this code.
Replace your RatingStar() function as it is using canvas to draw star, instead pass the custom drawable.
#Composable
private fun starShow(){
val icon = if (isSelected)
//your selected drawable
else
//your unselected drawable
Icon(
painter = painterResource(id = icon),
contentDescription = null,
tint = MyColor.starColor)
}
I've been having this problem for a couple of days, and I don't see what I'm doing wrong.
I am trying to add a cardView with some Views inside, to a LinearLayout, one for each note that I have in the DB. So far no problem, the issue is that I also want to add an OnClickListener to each of those cardViews that I am creating programmatically. So that each time the CardView is clicked it expands or collapses.
The problem is that when doing so, all the CardViews share the OnClickListener, and when clicking on one, all the CardViews expand, I want only the one I'm clicking to expand.
I hope I have explained myself, I share part of the code.
for (nota in notas) {
val card = CardView(view.context)
val note = TextView(view.context)
val date = TextView(view.context)
val lLHor = LinearLayout(view.context)
val lLVer = LinearLayout(view.context)
val im = ImageView(view.context)
lLVer.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT, 1f
)
with(im) {
setImageDrawable(
ContextCompat.getDrawable(
view.context,
R.drawable.ic_arrow_down
)
)
foregroundGravity = Gravity.END
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT, 0f
)
}
lLHor.orientation = (LinearLayout.HORIZONTAL)
lLVer.orientation = (LinearLayout.VERTICAL)
with(note) {
setTextColor(Color.parseColor("#252323"))
text = nota.getString("Nota")
textSize = 20F
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT, 1f
)
}
with(date) {
setTextColor(Color.parseColor("#252323"))
text = nota.getString("Fecha")
textSize = 20F
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT, 0f
)
}
card.layoutParams = lP
card.setBackgroundColor(Color.parseColor("#FFFFFF"))
card.setOnClickListener {
cardEC(card, im)
}
lLVer.addView(date)
lLVer.addView(note)
lLHor.addView(lLVer)
lLHor.addView(im)
card.addView(lLHor)
linearNotas.addView(card)
}
private fun cardEC(cardView: CardView, imageView: ImageView) {
val height: Int =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 28F, resources.displayMetrics)
.toInt()
val layPar = cardView.layoutParams
if (cardView.layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
layPar.height = height
cardView.layoutParams = layPar
imageView.setImageDrawable(
view?.let {
ContextCompat.getDrawable(
it.context,
R.drawable.ic_arrow_down
)
})
} else {
layPar.height = ViewGroup.LayoutParams.WRAP_CONTENT
cardView.layoutParams = layPar
imageView.setImageDrawable(
view?.let {
ContextCompat.getDrawable(
it.context,
R.drawable.ic_arrow_up
)
})
}
}
Edit: I add images to see the problem well
Card before press
Then when i press any cardView
Cards after press
you can set note visibility to GONE when card is not expanded
and set height of card view to 63 when the card is expanded
MainActivity
package com.example.testapp
import android.graphics.Color
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.TypedValue
import android.view.Gravity
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.cardview.widget.CardView
import androidx.core.content.ContextCompat
import com.example.testapp.entity.Note
import kotlinx.android.synthetic.main.activity_main.*
import org.w3c.dom.Text
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val notas = arrayListOf<Note>()
for (i in 0..20) {
notas.add(Note(i, "note$i"))
}
val linearNotas = main_lp
for (nota in notas) {
val card = CardView(this)
val note = TextView(this)
val date = TextView(this)
val lLHor = LinearLayout(this)
val lLVer = LinearLayout(this)
val im = ImageView(this)
lLVer.layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT, 1f
)
with(im) {
setImageDrawable(
ContextCompat.getDrawable(
context,
R.drawable.ic_baseline_5g_24
)
)
foregroundGravity = Gravity.END
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT, 0f
)
}
lLHor.orientation = (LinearLayout.HORIZONTAL)
lLVer.orientation = (LinearLayout.VERTICAL)
with(note) {
setTextColor(Color.parseColor("#252323"))
text = nota.title
textSize = 20F
visibility = View.GONE
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT, 1f
)
}
with(date) {
setTextColor(Color.parseColor("#252323"))
text = "date ${nota.title}"
textSize = 20F
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT, 0f
)
}
card.setBackgroundColor(Color.parseColor("#FFFFFF"))
card.setOnClickListener {
cardEC(card, im,note)
}
lLVer.addView(date)
lLVer.addView(note)
lLHor.addView(lLVer)
lLHor.addView(im)
card.addView(lLHor)
linearNotas.addView(card)
}
}
private fun cardEC(cardView: CardView, imageView: ImageView,note:TextView) {
val height: Int =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 63f, resources.displayMetrics)
.toInt()
val layPar = cardView.layoutParams
if (cardView.layoutParams.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
note.visibility= View.VISIBLE
layPar.height = height
cardView.layoutParams = layPar
imageView.setImageDrawable(
ContextCompat.getDrawable(
this,
R.drawable.ic_baseline_arrow_drop_down_24
)
)
} else {
note.visibility= View.GONE
layPar.height = ViewGroup.LayoutParams.WRAP_CONTENT
cardView.layoutParams = layPar
imageView.setImageDrawable(
ContextCompat.getDrawable(
this,
R.drawable.ic_baseline_arrow_drop_up_24
)
)
}
}
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:id="#+id/main_lp"
/>
</ScrollView>
I "solved"
Although I still don't understand why the problem occurred, I found a way to bypass it. I leave the solution I found in case it helps someone.
Based on the help #DavudDavudov gave me.
I keep the height of the CardView as WrapContent. When I create the TextView that contains the note, I set the View.Gone visibility to it. Then in the cardEC function I change the visibility of the TextView to View.Visible or View.Gone depending on how it was. And since the CardView is in WrapContent, plus the animateLayoutChanges It works perfectly, it gives the sensation of opening a drop-down menu. I copy part of the code in case it helps.
with(note) {
setTextColor(Color.parseColor("#252323"))
text = nota.getString("Nota")
textSize = 20F
layoutParams = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT, 1f
)
visibility = View.GONE
}
val noteContent = mutableListOf(note)
cardEC(card, im, noteContent)
private fun cardEC(cardView: CardView, imageView: ImageView, noteContent: MutableList<TextView>) {
cardView.setOnClickListener {
for (textView in noteContent){
if (textView.visibility == View.VISIBLE){
textView.visibility = View.GONE
imageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_arrow_down, null))
}else{
textView.visibility = View.VISIBLE
imageView.setImageDrawable(ResourcesCompat.getDrawable(resources, R.drawable.ic_arrow_up, null))
}
}
}
}
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.