How to parse hex string e.g. #9CCC65 in Color class in jetpack compose.
P.S: option seem to be missing in jetpack compose package
Current Workaround:
Exported parseColor() method from standard Color class.
#ColorInt
fun parseColor(#Size(min = 1) colorString: String): Int {
if (colorString[0] == '#') { // Use a long to avoid rollovers on #ffXXXXXX
var color = colorString.substring(1).toLong(16)
if (colorString.length == 7) { // Set the alpha value
color = color or -0x1000000
} else require(colorString.length == 9) { "Unknown color" }
return color.toInt()
}
throw IllegalArgumentException("Unknown color")
}
Instead of passing as String instead pass as Hexadecimal. For example if you want this #9CCC65 Color just remove the front # and replace it with 0xFF. Example
val PrimaryBlue = Color(0xFF9CCC65)
You can use this object class with a getColor method.
object HexToJetpackColor {
fun getColor(colorString: String): Color {
return Color(android.graphics.Color.parseColor("#" + colorString))
}
}
Or we can use an extension function
fun Color.fromHex(color: String) = Color(android.graphics.Color.parseColor("#" + colorString))
Jetpack Color class i.e androidx.ui.graphics.Color only takes RGB, ARGB, ColorSpace and colorInt in constructor. See: Color.kt
so, here we are directly accessing parseColor() method from android.graphics.Color which returns colorInt.
Hence parseColor() method can be used to get colorInt and then providing it to Jetpack Color class to get androidx.ui.graphics.Color object.
Similar to Int.dp, there can be String.color extenstion property.
val String.color
get() = Color(android.graphics.Color.parseColor(this))
This can be used as member property on color hex String.
"#FF0000".color
Another option is to write an extension function similar to how android.graphics.Color works:
import androidx.compose.ui.graphics.Color
fun Color.Companion.parse(colorString: String): Color =
Color(color = android.graphics.Color.parseColor(colorString))
Then you can write your compose like this:
Modifier.background(Color.parse("#FF0000"))
I also had this problem and I finally found the solution:
val myColorString = "#B00020"
val myComposeColorInt = Color(myColorString.toColorInt())
How about solution not dependant on Android? #KMP
val hashColorString = "#00AB18"
val color = Color(hashColorString.removePrefix("#").toLong(16) or 0x00000000FF000000)
hex string to color [this extension function is available inside android sdk
]
Color("#FFFFFF".toColorInt())
incase if u want to convert back to hex code
fun Color.toHexCode(): String {
val red = this.red * 255
val green = this.green * 255
val blue = this.blue * 255
return String.format("#%02x%02x%02x", red.toInt(), green.toInt(), blue.toInt())
}
incase if u also want alpha value
fun Color.toHexCodeWithAlpha(): String {
val alpha = this.alpha*255
val red = this.red * 255
val green = this.green * 255
val blue = this.blue * 255
return String.format("#%02x%02x%02x%02x", alpha.toInt(),red.toInt(), green.toInt(), blue.toInt())
}
If you want to avoid having to import android.graphics.Color, here's another straightforward alternative:
val hexString = "#f8f8f2"
Color(("ff" + hexString.removePrefix("#").lowercase()).toLong(16))
Color in this case would be immediately the one from androidx.compose.ui.graphics.Color.
Related
This bounty has ended. Answers to this question are eligible for a +100 reputation bounty. Bounty grace period ends in 2 hours.
Steve M wants to draw more attention to this question.
I've set up my app to use Material3 colors roles and theme, and added an option to enable Material You support using DynamicColors.
However, I would also like the user to be able to select a single (preferably arbitrary) input color within the app, and have the app generate all the color roles and apply a theme from that. I feel like this should be possible somehow given that Material You is doing this based on the wallpaper colors, but don't really a way to do this for an arbitrary color. Is it possible?
Have you taken a look at these two links?
https://m3.material.io/styles/color/the-color-system/tokens
https://m3.material.io/styles/color/the-color-system/custom-colors
They describe what you're talking about, I am unsure of what implementation looks like on the code side. But to answer the question "Is it possible?", it looks like it.
This link describes how the dynamic color palletes are generated:
A dynamic tonal palette must be generated from a single source color that should be derived from wallpaper using com.android.systemui.monet.ColorScheme#getSeedColors, which provides multiple valid source colors. If none of the provided colors meet the source color requirement, the single source color should use the value 0xFF1B6EF3.
https://source.android.com/docs/core/display/dynamic-color#how_is_a_dynamic_tonal_pallet_generated
Here is the actual file from the AOSP if you wanted to look at how the actual dynamic color code works:
/*
* Copyright (C) 2021 The Android Open Source Project
* * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* * http://www.apache.org/licenses/LICENSE-2.0
* * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.monet
import android.annotation.ColorInt
import android.app.WallpaperColors
import android.graphics.Color
import com.android.internal.graphics.ColorUtils
import com.android.internal.graphics.cam.Cam
import com.android.internal.graphics.cam.CamUtils.lstarFromInt
import kotlin.math.absoluteValue
import kotlin.math.roundToInt
const val TAG = "ColorScheme"
const val ACCENT1_CHROMA = 48.0f
const val ACCENT2_CHROMA = 16.0f
const val ACCENT3_CHROMA = 32.0f
const val ACCENT3_HUE_SHIFT = 60.0f
const val NEUTRAL1_CHROMA = 4.0f
const val NEUTRAL2_CHROMA = 8.0f
const val GOOGLE_BLUE = 0xFF1b6ef3.toInt()
const val MIN_CHROMA = 5
public class ColorScheme(#ColorInt seed: Int, val darkTheme: Boolean) {
val accent1: List<Int>
val accent2: List<Int>
val accent3: List<Int>
val neutral1: List<Int>
val neutral2: List<Int>
constructor(wallpaperColors: WallpaperColors, darkTheme: Boolean):
this(getSeedColor(wallpaperColors), darkTheme)
val allAccentColors: List<Int>
get() {
val allColors = mutableListOf<Int>()
allColors.addAll(accent1)
allColors.addAll(accent2)
allColors.addAll(accent3)
return allColors
}
val allNeutralColors: List<Int>
get() {
val allColors = mutableListOf<Int>()
allColors.addAll(neutral1)
allColors.addAll(neutral2)
return allColors
}
val backgroundColor
get() = ColorUtils.setAlphaComponent(if (darkTheme) neutral1[8] else neutral1[0], 0xFF)
val accentColor
get() = ColorUtils.setAlphaComponent(if (darkTheme) accent1[2] else accent1[6], 0xFF)
init {
val proposedSeedCam = Cam.fromInt(seed)
val seedArgb = if (seed == Color.TRANSPARENT) {
GOOGLE_BLUE
} else if (proposedSeedCam.chroma < 5) {
GOOGLE_BLUE
} else {
seed
}
val camSeed = Cam.fromInt(seedArgb)
val hue = camSeed.hue
val chroma = camSeed.chroma.coerceAtLeast(ACCENT1_CHROMA)
val tertiaryHue = wrapDegrees((hue + ACCENT3_HUE_SHIFT).toInt())
accent1 = Shades.of(hue, chroma).toList()
accent2 = Shades.of(hue, ACCENT2_CHROMA).toList()
accent3 = Shades.of(tertiaryHue.toFloat(), ACCENT3_CHROMA).toList()
neutral1 = Shades.of(hue, NEUTRAL1_CHROMA).toList()
neutral2 = Shades.of(hue, NEUTRAL2_CHROMA).toList()
}
override fun toString(): String {
return "ColorScheme {\n" +
" neutral1: ${humanReadable(neutral1)}\n" +
" neutral2: ${humanReadable(neutral2)}\n" +
" accent1: ${humanReadable(accent1)}\n" +
" accent2: ${humanReadable(accent2)}\n" +
" accent3: ${humanReadable(accent3)}\n" +
"}"
}
companion object {
/**
* Identifies a color to create a color scheme from.
*
* #param wallpaperColors Colors extracted from an image via quantization.
* #return ARGB int representing the color
*/
#JvmStatic
#ColorInt
fun getSeedColor(wallpaperColors: WallpaperColors): Int {
return getSeedColors(wallpaperColors).first()
}
/**
* Filters and ranks colors from WallpaperColors.
*
* #param wallpaperColors Colors extracted from an image via quantization.
* #return List of ARGB ints, ordered from highest scoring to lowest.
*/
#JvmStatic
fun getSeedColors(wallpaperColors: WallpaperColors): List<Int> {
val totalPopulation = wallpaperColors.allColors.values.reduce { a, b -> a + b }
.toDouble()
val totalPopulationMeaningless = (totalPopulation == 0.0)
if (totalPopulationMeaningless) {
// WallpaperColors with a population of 0 indicate the colors didn't come from
// quantization. Instead of scoring, trust the ordering of the provided primary
// secondary/tertiary colors.
//
// In this case, the colors are usually from a Live Wallpaper.
val distinctColors = wallpaperColors.mainColors.map {
it.toArgb()
}.distinct().filter {
Cam.fromInt(it).chroma >= MIN_CHROMA
}.toList()
if (distinctColors.isEmpty()) {
return listOf(GOOGLE_BLUE)
}
return distinctColors
}
val intToProportion = wallpaperColors.allColors.mapValues {
it.value.toDouble() / totalPopulation
}
val intToCam = wallpaperColors.allColors.mapValues { Cam.fromInt(it.key) }
// Get an array with 360 slots. A slot contains the percentage of colors with that hue.
val hueProportions = huePopulations(intToCam, intToProportion)
// Map each color to the percentage of the image with its hue.
val intToHueProportion = wallpaperColors.allColors.mapValues {
val cam = intToCam[it.key]!!
val hue = cam.hue.roundToInt()
var proportion = 0.0
for (i in hue - 15..hue + 15) {
proportion += hueProportions[wrapDegrees(i)]
}
proportion
}
// Remove any inappropriate seed colors. For example, low chroma colors look grayscale
// raising their chroma will turn them to a much louder color that may not have been
// in the image.
val filteredIntToCam = intToCam.filter {
val cam = it.value
val lstar = lstarFromInt(it.key)
val proportion = intToHueProportion[it.key]!!
cam.chroma >= MIN_CHROMA &&
(totalPopulationMeaningless || proportion > 0.01)
}
// Sort the colors by score, from high to low.
val intToScoreIntermediate = filteredIntToCam.mapValues {
score(it.value, intToHueProportion[it.key]!!)
}
val intToScore = intToScoreIntermediate.entries.toMutableList()
intToScore.sortByDescending { it.value }
// Go through the colors, from high score to low score.
// If the color is distinct in hue from colors picked so far, pick the color.
// Iteratively decrease the amount of hue distinctness required, thus ensuring we
// maximize difference between colors.
val minimumHueDistance = 15
val seeds = mutableListOf<Int>()
maximizeHueDistance# for (i in 90 downTo minimumHueDistance step 1) {
seeds.clear()
for (entry in intToScore) {
val int = entry.key
val existingSeedNearby = seeds.find {
val hueA = intToCam[int]!!.hue
val hueB = intToCam[it]!!.hue
hueDiff(hueA, hueB) < i } != null
if (existingSeedNearby) {
continue
}
seeds.add(int)
if (seeds.size >= 4) {
break#maximizeHueDistance
}
}
}
if (seeds.isEmpty()) {
// Use gBlue 500 if there are 0 colors
seeds.add(GOOGLE_BLUE)
}
return seeds
}
private fun wrapDegrees(degrees: Int): Int {
return when {
degrees < 0 -> {
(degrees % 360) + 360
}
degrees >= 360 -> {
degrees % 360
}
else -> {
degrees
}
}
}
private fun hueDiff(a: Float, b: Float): Float {
return 180f - ((a - b).absoluteValue - 180f).absoluteValue
}
private fun humanReadable(colors: List<Int>): String {
return colors.joinToString { "#" + Integer.toHexString(it) }
}
private fun score(cam: Cam, proportion: Double): Double {
val proportionScore = 0.7 * 100.0 * proportion
val chromaScore = if (cam.chroma < ACCENT1_CHROMA) 0.1 * (cam.chroma - ACCENT1_CHROMA)
else 0.3 * (cam.chroma - ACCENT1_CHROMA)
return chromaScore + proportionScore
}
private fun huePopulations(
camByColor: Map<Int, Cam>,
populationByColor: Map<Int, Double>
): List<Double> {
val huePopulation = List(size = 360, init = { 0.0 }).toMutableList()
for (entry in populationByColor.entries) {
val population = populationByColor[entry.key]!!
val cam = camByColor[entry.key]!!
val hue = cam.hue.roundToInt() % 360
if (cam.chroma <= MIN_CHROMA) {
continue
}
huePopulation[hue] = huePopulation[hue] + population
}
return huePopulation
}
}
}
https://android.googlesource.com/platform/frameworks/base/+/refs/tags/android-12.1.0_r1/packages/SystemUI/monet/src/com/android/systemui/monet/ColorScheme.kt
Going off of this, mind you I haven't tested this, the code would probably look like this:
// Get user color input somehow
val userColor = getUserColor();
// Reference:
// public class ColorScheme(#ColorInt seed: Int, val darkTheme: Boolean)
// Color input needs to be an integer, assuming the color code would probably start in hex
// Using false for darkMode for example purposes
val colorScheme = ColorScheme(userColor.toInt(), false)
val accent1Colors = colorScheme.accent1
val accent2Colors = colorScheme.accent2
val accent3Colors = colorScheme.accent3
val neutral1Colors = colorScheme.neutral1
val neutral2Colors = colorScheme.neutral2
Hopefully you found this helpful đź‘Ť
may be this code snippet helpful .
// import the necessary classes
import androidx.compose.material3.*
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
// get the user-selected input color (in this case, we're using a random color)
val inputColor = Color(0xff0099cc)
// get the context of the app
val context = LocalContext.current
// create a DynamicColorPalette instance using the input color and the context
val dynamicColorPalette = DynamicColorPalette.fromSingleColor(inputColor, context)
// create a MaterialTheme using the generated color palette
MaterialTheme(
colorPalette = dynamicColorPalette,
) {
// your app UI here
}
I'm trying to dynamically swap a text inside a LottieAnimation in jetpack compose.
The lottie file is exported without glyphs
It's working when using the old android view inside a
AndroidView(factory = { context ->
val view = LottieAnimationView(context).apply {
setAnimation(R.raw.testing_no_glyphs)
playAnimation()
repeatCount = LottieConstants.IterateForever
}
val textDel = object : TextDelegate(view) {
override fun getText(layerName: String?, input: String?): String {
return when (layerName) {
"Test234" -> "OtherLettersHere"
else -> super.getText(layerName, input)
}
}
}
val fontDel = object : FontAssetDelegate() {
override fun getFontPath(fontFamily: String?, fontStyle: String?, fontName: String?): String {
return "fonts/[MyFontInside /assets].ttf"
}
}
view.setTextDelegate(textDel)
view.setFontAssetDelegate(fontDel)
return#AndroidView view
})
But I can't find the correct handles in the JetpackCompose version of Lottie to get the same result.
If we export the lottie with glyphs, it's works for the letters in the chars array inside the lottie json. But we want to be able to replace with any letters/symbols, so this isn't a viable solution.
I've noticed in the 5.3.0-SNAPSHOT that a fontMap parameter has been added, but I can't figure out which key to hit with it.
Here is my code:
val dynamicProperties = rememberLottieDynamicProperties(
rememberLottieDynamicProperty(LottieProperty.TEXT, value = "AaBbCcEeFf", keyPath = arrayOf("Test234"))
)
val composition by rememberLottieComposition(
spec = LottieCompositionSpec.RawRes(R.raw.testing)
)
val progress by animateLottieCompositionAsState(composition, iterations = LottieConstants.IterateForever)
LottieAnimation(
composition,
{ progress },
dynamicProperties = dynamicProperties,
fontMap = mapOf("fName" to Typeface.createFromAsset(LocalContext.current.assets, "fonts/[MyFontInside /assets].ttf"))
)
It just shows a blank for all the texts inside the Lottie Animation - so this is kinda where i'm stuck.
After some trial an error I found a way to add the typeface for a specific layer:
val typeface = Typeface.createFromAsset(LocalContext.current.assets, "fonts/[MyFontInside /assets].ttf")
val dynamicProperties = rememberLottieDynamicProperties(
rememberLottieDynamicProperty(LottieProperty.TEXT, value = "AaBbCcEeFf", keyPath = arrayOf("Test234")),
--> rememberLottieDynamicProperty(LottieProperty.TYPEFACE, value = typeface, keyPath = arrayOf("Test234")),
)
Hence there is no need for the fontMap in my case
I am trying to restric the app from affected fro system font scaling. I had gone through many solutions but none helped. Most of them tell use dp instead of sp for text sizes but in compose we can use only sp if i am right as it expects a Text Unit.
Is there any right way to restrict font scaling in our app done with jetpack compose ? Please help .
(Solutions refered) : https://l.workplace.com/l.php?u=https%3A%2F%2Fstackoverflow.com%2Fquestions%2F21546805%2Fhow-to-prevent-system-font-size-changing-effects-to-android-application&h=AT0zIuBPbUONm0T6q8PtqbxCdX6P_ywlp-yFGrqPMqZt7H3wsWYltKO5XwbW3i0lenrxxLi3nn_kMO4aPtFUfig2iG0BcRZpd0wTuZ1_XFpdsjDM6E7RPyZ-G_c2dlmuzGqsSEHYbqBJun0hLLZgOpRUszKbe9-1xQ
You can have an extension for Int or Float like this
#Composable
fun Int.scaledSp(): TextUnit {
val value: Int = this
return with(LocalDensity.current) {
val fontScale = this.fontScale
val textSize = value / fontScale
textSize.sp
}
You can add an extension parameter of Int
val Int.scaledSp:TextUnit
#Composable get() = scaledSp()
Text(text = "Hello World", fontSize = 20.scaledSp)
override fun attachBaseContext(newBase: Context?) {
val newOverride = Configuration(newBase?.resources?.configuration)
if (newOverride.fontScale >= 1.1f)
newOverride.fontScale = 1.1f
applyOverrideConfiguration(newOverride)
super.attachBaseContext(newBase)
}
You can use something like this in your main activity.
Till there is no solution on jetpack compose for Text(), you can use AndroidView:
#Composable
fun CustomText(
// attributes you need to set
){
AndroidView(factory = { context ->
AppCompatTextView(context).apply {
setTextSize(TypedValue.COMPLEX_UNIT_DIP, 25)
setText("")
// other attributes you want to set or other features which is not available in jetpack compose now.
}
},)
}
Given that we have returned separately a list of animals:
val animals = "cat, dog and mouse"
Which we then concat to our animalsMessage so it looks as following:
val animalsMessage = "You have identified cat, dog and mouse"
Given my default font colour is white and I only wanted to change the val animals font colour in my animalsMessage, I could do:
animalsMessage.setSpan(
ForegroundColorSpan(resources.getColor(R.color.yellow, null)),
animalsMessage.length - animals.length,
animalsMessage.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
However, say I wanted to ignore the , and the word and whilst spanning, so they remained the default white colour, how would I go about doing that? I am basing this question on the assumption that there might be an and in the animals string and there might be one , or many.
I believe the answer lies in using a pattern matcher and then ignoring whilst spanning based on finding a match.
Things I have tried:
First, before concat my val animals to my val animalsMessage I tried to format my val animals as described above, to do that, I created the below method:
private fun ignoreSeparators(animals: String): SpannableString {
val spannable = SpannableString(animals)
val matcher: Matcher = Pattern.compile(",\\\\and").matcher(animals)
while (!matcher.matches()) {
val animal = matcher.group(1)
val animalIndex: Int = animals?.indexOf(animal) - 1
spannable.setSpan(ForegroundColorSpan(resources.getColor(R.color.yellow, null)), 0, animalIndex, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
}
return spannable
}
I then planned on returning the spanned text and then concating it to my val animalsMessage, however, I get a crash saying that no match is found.
I would recommend doing the following, first, remove the , and and change to a list as follows...
val animals = listOf("cat", "dog", "mouse")
Then, pass them to the following method that will handle the styling, followed by adding the necessary comma and the and. The rule followed was that the and would always be between the last and second from last animal and all other values no matter how large the list is, would be separated by a comma.
The second param, prefix, is simply our animalsMessage which we concat to, as mentioned in your question.
private fun formatAnimalStrings(animals: List<String>, prefix: String): SpannableStringBuilder {
val lastIndex = animals.size - 1
val secondLastIndex = lastIndex - 1
val result = SpannableStringBuilder(prefix)
animals.forEachIndexed { index, animal ->
val startIndex = result.length
result.append(animals[index])
result.setSpan(
ForegroundColorSpan(resources.getColor(R.color.yellow, null)),
startIndex,
startIndex + animal.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
if (index < lastIndex) {
if (index == secondLastIndex) {
result.append(" and ")
} else {
result.append(", ")
}
}
}
result.append(".")
return result
}
This would result in
You have identified cat, dog and mouse
(I have used bold to express yellow text colour, since I cannot make the text yellow)
I have tried to set a property value as in the following snippet.This SO question is not answering the question.
var person = Person("john", 24)
//sample_text.text = person.getName() + person.getAge()
var kon = person.someProperty
person.someProperty = "crap" //this doesn't allow me to set value
kon = "manulilated" //this allows me to set the value
sample_text.text = kon
class Person(val n: String, val a: Int){
var pname: String = n
var page: Int = a
var someProperty: String = "defaultValue"
get() = field.capitalize()
private set(value){field = value}
fun Init(nm: String, ag: Int){
pname = nm
page = ag
}
fun getAge(): Int{
return page
}
fun getName(): String{
return pname
}
}
Why was I able to set the value of the Person class on the second line but not the first line?
First, the private modifier is your problem.
Change
private set(value){field = value}
To
set(value){field = value}
//public by default
Otherwise you can’t use the setter outside the class. Read here.
For members declared inside a class:
private means visible inside this class only (including all its members);
Second, you’re misunderstanding something:
var kon = person.someProperty
kon = "manulilated"
In these lines you’re not changing the property in the object. After creating the variable kon, being a String pointing to someProperty, you reassign that local variable to something else. This reassignment is unequal to changing the value of person.someProperty! It has totally no effect in the object.
someProperty has a private setter. You can't set it outside the class when the setter is private
This is a good example of trying to write Kotlin in Java style.
Here is how this would like in Kotlin facilitating the language capabilities. One can see how much shorter and clearer the code is. The Person class has only 3 lines.
Using data class spares us from defining hash and equals and toString.
fun test() {
val person = Person("john", 24)
var kon = person.someProperty
person.someProperty = "crap" //this doesn't allow me to set value
kon = "manulilated" //this allows me to set the value (yeah, but not in the class, this is a local string)
val sample_text = SampleText("${person.name}${person.age}")
sample_text.text = kon
println("Person = $person")
println("Kon = $kon")
println("Sample Text = $sample_text")
}
/** class for person with immutable name and age
* The original code only has a getter which allows to use 'val'.
* This can be set in the constructor, so no need for init code. */
data class Person(val name: String, val age: Int) {
/** The field value is converted to uppercase() (capitalize is deprecated)
* when the value comes in, so the get operation is much faster which is
* assumed to happen more often. */
var someProperty: String = "defaultValue"
// setter not private as called from outside class
set(value) { field = value.uppercase() }
}
/** simple class storing a mutable text for whatever reason
* #param text mutable text
*/
data class SampleText(var text: String) {
override fun toString(): String {
return text
}
}
This is the result:
Person = Person(name=john, age=24)
Kon = manulilated
Sample Text = manulilated