When I write this code:
val widthScreenDp = LocalConfiguration.current.screenWidthDp
val heightScreenDp = LocalConfiguration.current.screenHeightDp
val widthScreenPx = with(LocalDensity.current) { widthScreenDp.dp.toPx() }
val heightScreenPx = with(LocalDensity.current) { heightScreenDp.dp.toPx() }
For my Xiaomi mi10, the result is 1078 by 2117.5 instead of 1080 by 2340
For the Pixel5 emulator 1078 by 2062...
What is the problem ? How to have complete dimension of the device on Jetpack Compose ?
it looks like it lacks the status/nav bars heights.
Thank you !
This is the official explain for screenHeightDp:
The current height of the available screen space, in dp units,
corresponding to screen height resource qualifier.
Maybe status/nav bars is not available spaces.
You can get screen height like that:
BoxWithConstraints {
val screenHeight = maxHeight
}
Related
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.
}
},)
}
I'd like to build a row in Jetpack Compose, with 3 elements, where the first and last elements are "stuck" to either sides, and the middle one stays in the center. The elements are not all the same width. It's possible for the first element to be really long, in which case I would like the middle item to move to the right, as much as possible. The images below hopefully illustrate what I mean:
All elements fit nicely
The first element is long and pushes the middle item to the right
The first element is super long, pushes the middle item all the way to the right and uses an ellipsis if necessary.
Wrapping each element in a Box and setting each weight(1f) helps with the first layout, but it doesn't let the first element to grow if it's long. Maybe I need a custom implementation of a Row Arrangement?
Ok, I managed to get the desired behaviour with a combination of custom implementation of an Arrangement and Modifier.weight.
I recommend you investigate the implementation of Arrangement.SpaceBetween or Arrangement.SpaceEvenly to get the idea.
For simplicity, I'm also assuming we'll always have 3 elements to place within the Row.
First, we create our own implementation of the HorizontalOrVertical interface:
val SpaceBetween3Responsively = object : Arrangement.HorizontalOrVertical {
override val spacing = 0.dp
override fun Density.arrange(
totalSize: Int,
sizes: IntArray,
layoutDirection: LayoutDirection,
outPositions: IntArray,
) = if (layoutDirection == LayoutDirection.Ltr) {
placeResponsivelyBetween(totalSize, sizes, outPositions, reverseInput = false)
} else {
placeResponsivelyBetween(totalSize, sizes, outPositions, reverseInput = true)
}
override fun Density.arrange(
totalSize: Int,
sizes: IntArray,
outPositions: IntArray,
) = placeResponsivelyBetween(totalSize, sizes, outPositions, reverseInput = false)
override fun toString() = "Arrangement#SpaceBetween3Responsively"
}
The placeResponsivelyBetween method needs to calculate the correct gap sizes between the elements, given their measured widths, and then place the elements with the gaps in-between.
fun placeResponsiveBetween(
totalSize: Int,
size: IntArray,
outPosition: IntArray,
reverseInput: Boolean,
) {
val gapSizes = calculateGapSize(totalSize, size)
var current = 0f
size.forEachIndexed(reverseInput) { index, it ->
outPosition[index] = current.roundToInt()
// here the element and gap placement happens
current += it.toFloat() + gapSizes[index]
}
}
calculateGapSize has to try and "place" the second/middle item in the centre of the row, if the first element is short enough. Otherwise, set the first gap to 0, and check if there's space for another gap.
private fun calculateGapSize(totalSize: Int, itemSizes: IntArray): List<Int> {
return if (itemSizes.sum() == totalSize) { // the items take up the whole space and there's no space for any gaps
listOf(0, 0, 0)
} else {
val startOf2ndIfInMiddle = totalSize / 2 - itemSizes[1] / 2
val firstGap = Integer.max(startOf2ndIfInMiddle - itemSizes.first(), 0)
val secondGap = totalSize - itemSizes.sum() - firstGap
listOf(firstGap, secondGap, 0)
}
}
Then we can use SpaceBetween3Responsively in our Row! Some code edited out for simplicity
Row(
horizontalArrangement = SpaceBetween3Responsively,
) {
Box(modifier = Modifier.weight(1f, fill = false)) {
Text(text = "Supercalifragilisticexplialidocious",
maxLines = 1,
overflow = TextOverflow.Ellipsis)
}
Box {
// Button
}
Box {
// Icon
}
}
Modifier.weight(1f, fill = false) is important here for the first element - because it's the only one with assigned weight, it forces the other elements to be measured first. This makes sure that if the first element is long, it's truncated/cut to allow enough space for the other two elements (button and icon). This means the correct sizes are passed into placeResponsivelyBetween to be placed with or without gaps. fill = false means that if the element is short, it doesn't have to take up the whole space it's assigned - meaning there's space for the other elements to move closer, letting the Button in the middle.
Et voila!
My goal is to test colors of my composables. For some reason assertion below fails on specific device/composable (e.g. Composable1 passes on Device1 and on Emulator1, Composable2 fails on Device1, but passes on Emulator1). The color difference is marginal (e.g. #3F3C34 / #403C34). What might be the cause of this problem? Is there any way to get consistent results, beside allowing slight difference in color's assertion?
internal fun SemanticsNodeInteraction.assertColorAtRelativePositionStrict(
expected: Color,
#FloatRange(from = 0.0, to = 1.0) xPercentage: Float,
#FloatRange(from = 0.0, to = 1.0) yPercentage: Float,
): SemanticsNodeInteraction {
val bitmap = captureToImage().asAndroidBitmap()
val x = ((bitmap.width - 1) * xPercentage).roundToInt()
val y = ((bitmap.height - 1) * yPercentage).roundToInt()
val capturedColor = bitmap.getColor(x, y)
assert(capturedColor == expected) {
"Captured color was ${capturedColor.toArgb().toHexString()} " +
"but expected ${expected.toArgb().toHexString()}."
}
return this
}
private fun Int.toHexString() = String.format("#%06X", (0xFFFFFF and this))
Expected color and color, used in compoasable, are acquired from colors.xml.
I think this issues happened because different dp / pixel size for each device. I would recommend to create more isolated compose widget to improve test stability.
I'm currently working on support for multiple screen sizes for my app, and stumbled upon this advice from google;
https://developer.android.com/guide/topics/large-screens/support-different-screen-sizes#kotlin
It has this code;
private fun computeWindowSizeClasses() {
val metrics = WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(this)
val widthDp = metrics.bounds.width() /
resources.displayMetrics.density
val widthWindowSizeClass = when {
widthDp < 600f -> WindowSizeClass.COMPACT
widthDp < 840f -> WindowSizeClass.MEDIUM
else -> WindowSizeClass.EXPANDED
}
val heightDp = metrics.bounds.height() /
resources.displayMetrics.density
val heightWindowSizeClass = when {
heightDp < 480f -> WindowSizeClass.COMPACT
heightDp < 900f -> WindowSizeClass.MEDIUM
else -> WindowSizeClass.EXPANDED
}
// Use widthWindowSizeClass and heightWindowSizeClass
}
Then there are the in-project resource qualifiers, eg
/layout
/layout-w600dp
/layout-w840dp
When Android chooses a layout based on width, does it use this exact same calculation?
widthDp = metrics.bounds.width() / resources.displayMetrics.density
Different layouts will have different elements. How do I ensure my class code is expecting the same layout that Android has chosen?
If you really need to know, you can have a values resource for each resource qualifier you are supporting. For example,
values/whatever.xml:
<string name="resource_qualifier">default</string>
values-w600dp/whatever.xml:
<string name="resource_qualifier">w600dp</string>
And then you can getString() this resource value.
I have found a better answer;
getResources().getConfiguration()
I'm following the CameraX codelab and I'm getting a wrong aspect ratio on the preview even using setTargetAspectRatio and setTargetResolution methods.
private fun startCamera() {
// Create configuration object for the viewfinder use case
val previewConfig = PreviewConfig.Builder().apply {
setTargetAspectRatio(Rational(1, 1))
setTargetResolution(Size(640, 640))
}.build()
...
And the layout is using a hardcoded size as presented in the codelab.
<TextureView
android:id="#+id/view_finder"
android:layout_width="640px"
android:layout_height="640px"
...
It would be nice if the library had CameraTextureView and a property android:scaleType (similar to the existing for the ImageView) to adjust the preview to the preview size.
did you try it?
val metrics = DisplayMetrics().also { viewFinder.display.getRealMetrics(it) }
val screenSize = Size(metrics.widthPixels, metrics.heightPixels)
val screenAspectRatio = Rational(metrics.widthPixels, metrics.heightPixels)
val viewFinderConfig = PreviewConfig.Builder().apply {
//...
setTargetResolution(screenSize)
setTargetAspectRatio(screenAspectRatio)
setTargetRotation(viewFinder.display.rotation)
}.build()
val preview = AutoFitPreviewBuilder.build(viewFinderConfig, viewFinder)
And AutoFitPreviewBuilder you can find here:
https://gist.github.com/yevhenRoman/90681822adef43350844464be95d23f1
I would recommend you to set width and height for your TextureView using dp or constaraints.
Let me know if it works for you, thanks!
https://stackoverflow.com/a/49449986/9397052 there is a solution for a similar problem. After you decide on a resolution, don't use setTargetAspectRatio function; instead you should only use setTargetResolution. Then it should work the same.
According to the official Android documentation:
"It is not allowed to set both target aspect ratio and target resolution on the same use case."
Although the compiler does not throw an error if you attempt to do this, unexpected results can occur.
You can set the aspect ratio of a TextureView with Matrix like this:
Matrix txform = new Matrix();
textureView.getTransform(txform);
txform.setScale((float) = videoWidth / viewWidth, (float) videoHeight / viewHeight);// aspect ratio
textureView.setTransform(txform); // apply matrix