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()
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.
}
},)
}
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.
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
}
I am implementing a custom KeyboardView in my app and it's all working at the moment, however, when I attempt to press a key on the keyboard using Espresso ViewAction, I am getting an exception saying:
android.support.test.espresso.PerformException:
Error performing 'single click - At Coordinates: 1070, 2809 and
precision: 16, 16' on view 'with id:
com.example.app.mvpdemo:id/keyboardLayout'.
The code throwing the exception is:
#Test
fun enter100AsPriceShouldDisplay120ForA20PercentTip(){
onView(withId(R.id.editTextCheckAmount))
.perform(typeText("100"), closeSoftKeyboard())
val appContext = InstrumentationRegistry.getTargetContext()
val displayMetrics = appContext.resources.displayMetrics
onView(withId(R.id.keyboardLayout)).perform(clickXY(displayMetrics.widthPixels - 10, displayMetrics.heightPixels - 10))
onView(withText("$120.00")).check(matches(isDisplayed()))
}
and the click XY function which came from this post
private fun clickXY(x: Int, y: Int): ViewAction {
return GeneralClickAction(
Tap.SINGLE,
CoordinatesProvider { view ->
val screenPos = IntArray(2)
view.getLocationOnScreen(screenPos)
val screenX = (screenPos[0] + x).toFloat()
val screenY = (screenPos[1] + y).toFloat()
floatArrayOf(screenX, screenY)
},
Press.FINGER, 0, 0)
}
Here is my keyboard layout (pinned to the bottom of the screen inside a ConstraintLayout):
Does anyone know why? Any help is appreciated.
Answering my own question after determining a flexible solution:
First attempt - get DisplayMetrics of the root View and subtract an arbitrary number to attempt to hit the Keyboard.Key
this didn't work because clickXY function uses the position of the view
this ended up being the reason for the exception since the view is smaller than the DisplayMetrics values and adding to the Views on screen position would give a very high number for the x and y.
So I tried again,
Second attempt - use check method on the ViewMatcher to check the KeyBoardView.
by doing so I was able to get access to the KeyboardView's position x
then I was able to get the KeyboardView's width and height
by performing some math, I was able to figure out target index for x & y
the math:
take the widthPercent for the Keyboard.Key (in my case 33.3%)
take the rowCount of the keyboard.xml (in my case 3)
use (viewWidth * widthPercent) / 4 to get relativeButtonX
use (viewHeight / rowCount) / 2 to get relativeButtonY
then for targetY, I took viewHeight - relativeButtonY
finally, for targetX, I took (viewPosX + viewWidth) - relativeButtonX
So enough explanation, here is the code:
#Test
fun enter100AsPriceShouldDisplay120ForA20PercentTip() {
onView(withId(R.id.editTextCheckAmount))
.perform(typeText("100"), closeSoftKeyboard())
// call the function to get the targets
val (viewTargetY, viewTargetX) = getTargetXAndY()
// perform the action
onView(withId(R.id.keyboardLayout)).perform(clickXY(viewTargetX.toInt(), viewTargetY))
onView(withText("Tip: $20.00")).check(matches(isDisplayed()))
onView(withText("Total: $120.00")).check(matches(isDisplayed()))
}
and the helper method with all the math:
private fun getTargetXAndY(): Pair<Int, Double> {
var viewHeight = 0
var viewWidth = 0
var viewPosX = 0F
val viewMatcher = onView(withId(R.id.keyboardLayout))
viewMatcher.check { view, _ ->
viewWidth = view.width
viewHeight = view.height
viewPosX = view.x
}
val keyboardKeyWidthPercent = 0.333
val keyboardRowsCount = 3
val keyboardButtonWidthQuarter = (viewWidth * keyboardKeyWidthPercent) / 4
val keyboardButtonHeightHalf = (viewHeight / keyboardRowsCount) / 2
val viewTargetY = viewHeight - keyboardButtonHeightHalf
val viewTargetX = (viewPosX + viewWidth) - keyboardButtonWidthQuarter
return Pair(viewTargetY, viewTargetX)
}
Now, the click is not perfectly centered but it clicks the button pretty close to the center.
So I have my TextView named 'container'
container.setText(Html.fromHtml(text, new URLImageParser(container, this),null));
with URLImageParser from Android HTML.fromHTML() with images?
this works fine and displayes the image. The problem is that I want to define the size of the image in the html tag. <img src=".." width="30px"> doesn´t work in all kind of variations!
Any idea how to set the image size in the html?
Since Html.fromHtml() only supports a few tags and attributes as noted by #Selvin in comments:
Any idea how to set the image size in the html? You can't .... but i got an idea ... you could try to append size to the and of url ... fx example.com/images/image_1.png#120x100 ... and then make of use it in whithin ImageGetter.getDrawable ... –
Selvin
Jan 29, 2016 at 15:26
Based on this I amended the text in my resource file where a specified size was desired with a '#' followed by a width value:
Old markup: \<img src="imgBall" width=150>
New markup: <img src="imgBall#150">
Then in the replacement ImageGetter:
class ResourceImageGetter(private val context: Context) : ImageGetter {
override fun getDrawable(source: String): Drawable {
val resources = context.resources
val height = ScreenMetricsCompat.getScreenSize(context).height
val width = ScreenMetricsCompat.getScreenSize(context).width
val imgId = resources.getIdentifier(source, "drawable", context.getPackageName())
val res = resources.getDrawable(imgId, context.theme)
if (res.intrinsicWidth <= width && res.intrinsicHeight <= height) {
res.setBounds(0, 0, res.intrinsicWidth, res.intrinsicHeight)
} else if ...
....
}
return res
}
}
Insert the code to extract the size from the source name:
class ResourceImageGetter(private val context: Context) : ImageGetter {
override fun getDrawable(source: String): Drawable {
val resources = context.resources
var height = ScreenMetricsCompat.getScreenSize(context).height
var width = ScreenMetricsCompat.getScreenSize(context).width
// extract the resource name and size from the input
var sourceImage = source
val imgHasWidth = source.indexOf('#')
if (imgHasWidth > 0) {
sourceImage = source.take(imgHasWidth)
width = source.takeLast(source.length - imgHasWidth - 1).toInt()
}
// change 'source' to 'sourceImage' otherwise results in 'resource not found'
val imgId = resources.getIdentifier(sourceImage, "drawable", context.getPackageName())
val res = resources.getDrawable(imgId, context.theme)
if (res.intrinsicWidth <= width && res.intrinsicHeight <= height) {
res.setBounds(0, 0, res.intrinsicWidth, res.intrinsicHeight)
} else if ...
....
}
return res
}
}
In the ImageGetter, the screen height and width are obtained to set bounds on the image, that it fits on the screen. This modification replaces the screen width with the specified image width as the new width bound. The aspect ratio remains the same.