So I have a dumb question, how can you make a new line within the string, instead of using Java, I would want to use Kotlin instead, the purpose of this is I want to make the TextView to display a certain string, but I also want to make a new line within the same string, but don't know the command as I search in the net, it doesn't show. Such as:
Java:
System.out.println("Hello")
System.out.println("World!")
System.out.print("Hello\n World!")
C#:
Console.WriteLine("Hello")
Console.WriteLine("World!")
Console.Write("Hello\n World!")
Here is the code of what I am planning to do:
The method
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
mImageView = findViewById(R.id.image_view)
val longitudeText = mLocation.longitude
val latitudeText = mLocation.latitude
val altitudeText = mLocation.altitude
val originalBitmap = BitmapFactory.decodeResource(resources, R.drawable.gefvqkbbslamdpkxf1zv_bigstock_aerial_view_of_blue_lakes_and__227291596)?.let { it ->
firstAddWatermark(
it,
"Longitude: ${resources.getString(R.string.value_of_7, longitudeText)}м, Latitude: ${resources.getString(R.string.value_of_7, latitudeText)}м" + "\n" +
"Altitude: ${{resources.getString(R.string.value_of_2, altitudeText)}}",
WatermarkOptions(
Corner.TOP_LEFT,
textSizeToWidthRatio = 0.015f,
paddingToWidthRatio = 0.03f,
Color.RED,
Color.BLACK,
typeface = null
)
)
}
mImageView.setImageBitmap(originalBitmap)
}
The functions:
private fun firstAddWatermark(bitmap: Bitmap, firstWatermarkText: String, options: WatermarkOptions = WatermarkOptions()): Bitmap {
val result = bitmap.copy(bitmap.config, true)
val canvas = Canvas(result)
val paint = Paint(ANTI_ALIAS_FLAG or DITHER_FLAG)
paint.textAlign = when (options.corner) {
Corner.TOP_LEFT,
Corner.BOTTOM_LEFT -> Paint.Align.LEFT
Corner.TOP_RIGHT,
Corner.BOTTOM_RIGHT -> Paint.Align.RIGHT
}
val textSize = result.width * options.textSizeToWidthRatio
paint.textSize = textSize
paint.color = options.textColor
if (options.shadowColor != null) {
paint.setShadowLayer(textSize / 2, 0f, 0f, options.shadowColor)
}
if (options.typeface != null) {
paint.typeface = options.typeface
}
val padding = result.width * options.paddingToWidthRatio
val coordinates = calculateCoordinates(firstWatermarkText, paint, options, canvas.width, canvas.height, padding)
canvas.drawText(firstWatermarkText, coordinates.x, coordinates.y, paint)
return result
}
private fun calculateCoordinates(watermarkText: String, paint: Paint, options: WatermarkOptions, width: Int, height: Int, padding: Float): PointF {
val x = when (options.corner) {
Corner.TOP_LEFT,
Corner.BOTTOM_LEFT -> {
padding
}
Corner.TOP_RIGHT,
Corner.BOTTOM_RIGHT -> {
width - padding
}
}
val y = when (options.corner) {
Corner.BOTTOM_LEFT,
Corner.BOTTOM_RIGHT -> {
height - padding
}
Corner.TOP_LEFT,
Corner.TOP_RIGHT -> {
val bounds = Rect()
paint.getTextBounds(watermarkText, 0, watermarkText.length, bounds)
val textHeight = bounds.height()
textHeight + padding
}
}
return PointF(x, y)
}
enum class Corner {
TOP_LEFT,
TOP_RIGHT,
BOTTOM_LEFT,
BOTTOM_RIGHT
}
data class WatermarkOptions(
val corner: Corner = Corner.BOTTOM_RIGHT,
val textSizeToWidthRatio: Float = 0.04f,
val paddingToWidthRatio: Float = 0.03f,
#ColorInt val textColor: Int = Color.WHITE,
#ColorInt val shadowColor: Int? = Color.BLACK,
val typeface: Typeface? = null
)
The result of that:
postimg.cc/75SF4v3F
You can use \n in the kotlin as well.
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 would like to create a watermark effect in my app using text as shown in the picture below.
I achieved this by using canvas and bitmap, is there any other reliable way to do this?
Here is my composable function
#Composable
fun WaterMark(
modifier: Modifier = Modifier,
content: (#Composable BoxScope.() -> Unit)? = null,
) {
val watermarkText: String = "some mutable text"
val paint = Paint(Paint.ANTI_ALIAS_FLAG)
paint.textSize = LocalContext.current.dpToPx(24).toFloat()
paint.color = PSCoreColours.psCoreColours.onSurface.hashCode()
paint.textAlign = Paint.Align.LEFT
paint.alpha = (255 * 0.25).toInt()
val baseline: Float = -paint.ascent()
val image: Bitmap = Bitmap.createBitmap(paint.measureText(watermarkText).toInt(),
(baseline + paint.descent()).toInt(),
Bitmap.Config.ARGB_8888)
val canvas = android.graphics.Canvas(image)
canvas.drawText(watermarkText, 0f, baseline, paint)
val rotationMatrix: Matrix = Matrix().apply { postRotate(-45f) }
val rotatedImage: Bitmap = Bitmap.createBitmap(image, 0, 0, image.width, image.height, rotationMatrix, true)
val pattern: ImageBitmap = rotatedImage.asImageBitmap()
Box {
content?.let { it() }
Canvas(
modifier = modifier
) {
val totalWidth = size.width / pattern.width
val totalHeight = size.height / pattern.height
var x = 0f
var y = 0f
for (i in 0..totalHeight.toInt()) {
y = (i * pattern.height).toFloat()
for (j in 0..totalWidth.toInt()) {
x = (j * pattern.width).toFloat()
drawImage(
pattern,
colorFilter = null,
topLeft = Offset(x, y),
)
}
}
}
}
}
You can do custom layouts in compose for this
private const val SPACING = 100
#Composable
fun Watermark(
content: #Composable BoxScope.() -> Unit,
) {
Box {
content()
Layout(
content = {
// Repeating the placeables, 6 should do for now but we should be able to calculate this too
repeat(6) {
Text(
text = watermarkText,
..
)
}
}
) { measurables, constraints ->
// Measuring all the placables
val placeables: List<Placeable> = measurables
.map { measurable -> measurable.measure(constraints) }
layout(constraints.maxWidth, constraints.maxHeight) {
// Calculating the max width of a placable
val maxWidth: Double = placeables.maxOf { it.width }.toDouble()
// Calculating the max width of a tile given the text is rotated
val tileSize: Int = (constraints.maxWidth / atan(maxWidth)).toInt()
placeables
.chunked(2) // Placing 2 columns
.forEachIndexed { index, (first, second) ->
val indexedTileSize: Int = index * tileSize
first.placeRelativeWithLayer(-SPACING, indexedTileSize + SPACING) { rotationZ = -45f }
second.placeRelativeWithLayer(tileSize, indexedTileSize) { rotationZ = -45f }
}
}
}
}
}
Watermark function creates instance of Paint and Bitmap on each recomposition. You should wrap them with remember as in this answer.
However you might, i think, do what you do fully Compose way without Paint and Bitmap either using Modifier.drawWithContent{} and drawText function of DrawScope and using translate or rotate inside DrawScope.
This is a drawText sample to understand how you can create and store TextLayoutResult remember.
And another sample using Modifier.drawWithContent
You can also try using Modifier.drawWithCache to cache TextLayoutResult in layout phase instead of composition phase which is suggested by Google Compose developer works on Text here
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.
What is the Android Compose approach to tile an image to fill my background with a small pattern?
A naive approach for Bitmaps without rotation could be like this:
#Composable
fun TileImage() {
val pattern = ImageBitmap.imageResource(R.drawable.pattern_bitmap)
Canvas(modifier = Modifier.fillMaxSize()) {
// rotate(degrees = -15f) { // The rotation does not produce the desired effect
val totalWidth = size.width / pattern.width
val totalHeight = size.height / pattern.height
var x = 0f
var y = 0f
for (i in 0..totalHeight.toInt()) {
y = (i * pattern.height).toFloat()
for (j in 0..totalWidth.toInt()) {
x = (j * pattern.width).toFloat()
drawImage(
pattern,
colorFilter = giftColorFilter,
topLeft = Offset(x, y)
)
}
}
// }
}
}
In Android XML you can easily create XML to repeat a bitmap
<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
android:src="#drawable/pattern_bitmap"
android:tileMode="repeat" />
Or if you need to tile a vector you can use a custom Drawable class to achieve your goal
TileDrawable(AppCompatResources.getDrawable(context, R.drawable.pattern_vector), Shader.TileMode.REPEAT)
class TileDrawable(drawable: Drawable, tileMode: Shader.TileMode, private val angle: Float? = null) : Drawable() {
private val paint: Paint = Paint().apply {
shader = BitmapShader(getBitmap(drawable), tileMode, tileMode)
}
override fun draw(canvas: Canvas) {
angle?.let {
canvas.rotate(it)
}
canvas.drawPaint(paint)
}
override fun setAlpha(alpha: Int) {
paint.alpha = alpha
}
override fun getOpacity() = PixelFormat.TRANSLUCENT
override fun setColorFilter(colorFilter: ColorFilter?) {
paint.colorFilter = colorFilter
}
private fun getBitmap(drawable: Drawable): Bitmap {
if (drawable is BitmapDrawable) {
return drawable.bitmap
}
val bmp = Bitmap.createBitmap(
drawable.intrinsicWidth, drawable.intrinsicHeight,
Bitmap.Config.ARGB_8888
)
val c = Canvas(bmp)
drawable.setBounds(0, 0, drawable.intrinsicWidth, drawable.intrinsicHeight)
drawable.draw(c)
return bmp
}
}
If you want to use native canvas you can do something like this in jetpack compose.
Canvas(
modifier = Modifier
.fillMaxSize()
) {
val paint = Paint().asFrameworkPaint().apply {
isAntiAlias = true
shader = ImageShader(pattern, TileMode.Repeated, TileMode.Repeated)
}
drawIntoCanvas {
it.nativeCanvas.drawPaint(paint)
}
paint.reset()
}
And If you want to limit your repetition to a certain height and width you can use the clip modifier in canvas like below otherwise it will fill the entire screen.
Canvas(
modifier = Modifier
.width(300.dp)
.height(200.dp)
.clip(RectangleShape)
) {
----
}
Based on Rafiul's answer, I was able to come up with something a bit more succinct. Here's hoping Compose comes up with something built-in to make this simpler in the future.
val image = ImageBitmap.imageResource(R.drawable.my_image)
val brush = remember(image) { ShaderBrush(ImageShader(image, TileMode.Repeated, TileMode.Repeated)) }
Box(Modifier
.fillMaxSize()
.background(brush)) {
}
I want to draw a simple XY chart using my data parsed from JSON, but every answer here is redirecting to using some sort of library. I want to draw it without any library usage, is there is a possible way to do this in Kotlin ?
PS No, it's NOT a homework or smth.
There is one simple way to integrate a graph by writing custom view
( original:https://github.com/SupahSoftware/AndroidExampleGraphView )
import android.content.Context
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
class GraphView(context: Context, attributeSet: AttributeSet) : View(context, attributeSet) {
private val dataSet = mutableListOf<DataPoint>()
private var xMin = 0
private var xMax = 0
private var yMin = 0
private var yMax = 0
private val dataPointPaint = Paint().apply {
color = Color.BLUE
strokeWidth = 7f
style = Paint.Style.STROKE
}
private val dataPointFillPaint = Paint().apply {
color = Color.WHITE
}
private val dataPointLinePaint = Paint().apply {
color = Color.BLUE
strokeWidth = 7f
isAntiAlias = true
}
private val axisLinePaint = Paint().apply {
color = Color.RED
strokeWidth = 10f
}
fun setData(newDataSet: List<DataPoint>) {
xMin = newDataSet.minBy { it.xVal }?.xVal ?: 0
xMax = newDataSet.maxBy { it.xVal }?.xVal ?: 0
yMin = newDataSet.minBy { it.yVal }?.yVal ?: 0
yMax = newDataSet.maxBy { it.yVal }?.yVal ?: 0
dataSet.clear()
dataSet.addAll(newDataSet)
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
dataSet.forEachIndexed { index, currentDataPoint ->
val realX = currentDataPoint.xVal.toRealX()
val realY = currentDataPoint.yVal.toRealY()
if (index < dataSet.size - 1) {
val nextDataPoint = dataSet[index + 1]
val startX = currentDataPoint.xVal.toRealX()
val startY = currentDataPoint.yVal.toRealY()
val endX = nextDataPoint.xVal.toRealX()
val endY = nextDataPoint.yVal.toRealY()
canvas.drawLine(startX, startY, endX, endY, dataPointLinePaint)
}
canvas.drawCircle(realX, realY, 7f, dataPointFillPaint)
canvas.drawCircle(realX, realY, 7f, dataPointPaint)
}
canvas.drawLine(0f, 0f, 0f, height.toFloat(), axisLinePaint)
canvas.drawLine(0f, height.toFloat(), width.toFloat(), height.toFloat(), axisLinePaint)
}
private fun Int.toRealX() = toFloat() / xMax * width
private fun Int.toRealY() = toFloat() / yMax * height
}
data class DataPoint(
val xVal: Int,
val yVal: Int
)