Coder Social home page Coder Social logo

DP utitilies on Int and Float about android-ktx HOT 24 OPEN

android avatar android commented on July 23, 2024 19
DP utitilies on Int and Float

from android-ktx.

Comments (24)

omarmiatello avatar omarmiatello commented on July 23, 2024 9

I wrote this utility:

fun Context.toPixelFromDip(value: Float) = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, resources.displayMetrics)
fun View.toPixelFromDip(value: Float) = context.toPixelFromDip(value)

Pro:

  • toPixelFromDip() is very clear the origin and the destination
  • It's handy when you call this method from a Context or (more common) a View, ex: View().apply { translationX = toPixelFromDip(2) }

from android-ktx.

daberni avatar daberni commented on July 23, 2024 3

I am not sure if it should be treated/interpreted as a conversion. mostly you simply want do pass a value in dp to a method which takes pixels. so 9.toDp() I would assume converting 9px to Ndp but what we want is to be get the value 9 get treated as dp and therefore I would prefer 9.asDp()

from android-ktx.

JakeWharton avatar JakeWharton commented on July 23, 2024

I think we would call these toDp as conversion factory extensions are prefixed with "to".

from android-ktx.

imminent avatar imminent commented on July 23, 2024

Ah, that's fine, updating the description to to. 👍

from android-ktx.

nohitme avatar nohitme commented on July 23, 2024

Just curious, why is it applied on Int & Float instead of on DisplayMetrics ike displayMetrics.toDp(9)?

That way you can also name the param "pixel" so it's less confusing.

from android-ktx.

imminent avatar imminent commented on July 23, 2024

DisplayMetrics is merely a tool needed to execute the conversion, the parameter that matters is the value. In an ideal world it would be 9.toDp().

from android-ktx.

lub0s avatar lub0s commented on July 23, 2024

Wouldn't accessing system Resources just be sufficiet? So we can have something like:

val Int.dp: Int // or just return Float here
    get() = (this * Resources.getSystem().displayMetrics.density).toInt()

and use it in place like val side = 42.dp ?

from android-ktx.

omarmiatello avatar omarmiatello commented on July 23, 2024

I don't think so. Your application resources may have a different density. Maybe not now, but you don't know in future.
Also seems correct take it from application context.

from android-ktx.

Kevinrob avatar Kevinrob commented on July 23, 2024

What about using Number?

fun Number.toDp(context: Context): Float {
    if (this == 0) return 0f

    return TypedValue.applyDimension(
        TypedValue.COMPLEX_UNIT_DIP, 
        this.toFloat(), 
        context.resources.displayMetrics
    )
}

from android-ktx.

romainguy avatar romainguy commented on July 23, 2024

This wouldn't work with multiple displays (virtual or physical).

from android-ktx.

johanneslaubermoovel avatar johanneslaubermoovel commented on July 23, 2024

In my opinion, this is the best implementation.

fun Number.dpToPx(): Int = (toFloat() * Resources.getSystem().displayMetrics.density + .5f).toInt()

from android-ktx.

romainguy avatar romainguy commented on July 23, 2024

@johanneslaubermoovel This doesn't work with negative numbers. Also you are relying on display metrics that might not match the display.

from android-ktx.

hendraanggrian avatar hendraanggrian commented on July 23, 2024

I am against using Number as input since only Int and Float are the common types representing dimensions in Android.

This wouldn't work with multiple displays (virtual or physical).

@romainguy this just blew my mind. Say a secondary display is connected, which DisplayMetrics then would the Resources refers to?

from android-ktx.

romainguy avatar romainguy commented on July 23, 2024

Resources will use the DisplayMetrics of the Display the Resources came from.

from android-ktx.

lukewaggoner-zz avatar lukewaggoner-zz commented on July 23, 2024

I personally would go with the following methods:

fun Number.toDp(displayMetrics: DisplayMetrics? = null): Float = this.toFloat() / ((displayMetrics ?: Resources.getSystem().displayMetrics).densityDpi / 160f)
fun Number.toPx(displayMetrics: DisplayMetrics? = null) : Float = this.toFloat() * ((displayMetrics ?: Resources.getSystem().displayMetrics).densityDpi / 160f)

displayMetrics is an optional parameter in case you have multiple displays. If not, you have the option of a simple 8.px() or 12.px().

from android-ktx.

romainguy avatar romainguy commented on July 23, 2024

This seems error prone. The DisplayMetrics should be mandatory. BTW no need to / 160.0f, just use DisplayMetrics.density instead (and you need to properly round and handle negative values correctly).

from android-ktx.

iglaweb avatar iglaweb commented on July 23, 2024

@JakeWharton What about using these converters?

fun Number.toDp(displayMetrics: DisplayMetrics): Float = this.toFloat() / displayMetrics.density
fun Number.toSp(displayMetrics: DisplayMetrics): Float = this.toFloat() / displayMetrics.scaledDensity
fun Number.toPx(displayMetrics: DisplayMetrics, fromSp: Boolean = false): Float
        = this.toFloat() * (if (fromSp) displayMetrics.scaledDensity else displayMetrics.density)

from android-ktx.

romainguy avatar romainguy commented on July 23, 2024

These are incorrect, toPx in particular does not round properly (which needs to take negative numbers into account). I would also prefer to not have a fromSp boolean, but a separate method.

from android-ktx.

imminent avatar imminent commented on July 23, 2024

If you allow arch components, then you can have an onCreateProperty (which is like an Android-specific lateinit var) and define extensions on Activity and Fragment:

/**
 * Initializes a property with the DP value
 * @receiver An [Activity] that is a [LifecycleOwner]
 * @return [Float] representation of the DP value
 * @see [onCreate]
 */
fun <T> T.dp(value: Float) where T : Activity, T : LifecycleOwner = onCreate {
    value.toDp(resources.displayMetrics)
}

/**
 * Initializes a property with the DP value
 * @receiver A [Fragment] that is a [LifecycleOwner]
 * @return [Int] representation of the DP value
 * @see [onCreate]
 */
fun <T> T.dp(value: Int) where T : Fragment, T : LifecycleOwner = onCreate {
    value.toDp(resources.displayMetrics)
}

// In Activity
val margins by dp(5)

from android-ktx.

iglaweb avatar iglaweb commented on July 23, 2024

@romainguy I refined concrete types for these density functions, could you explain why they are not applicable for negative values? (tested myself)

fun Int.toDp(displayMetrics: DisplayMetrics): Float = this.toFloat() / displayMetrics.density
fun Int.toSp(displayMetrics: DisplayMetrics): Float = this.toFloat() / displayMetrics.scaledDensity
fun Float.spToPx(displayMetrics: DisplayMetrics): Int =
        (this * displayMetrics.scaledDensity).roundToInt()
fun Float.dpToPx(displayMetrics: DisplayMetrics): Int =
        (this * displayMetrics.density).roundToInt()

from android-ktx.

romainguy avatar romainguy commented on July 23, 2024

roundToInt() rounds toward the nearest integer (towards positive infinity when there's a tie), so it won't round in the right direction for negative numbers. For instance 0.5f rounds to 1, but -0.5f rounds to 0.

from android-ktx.

lukewaggoner-zz avatar lukewaggoner-zz commented on July 23, 2024

from android-ktx.

iglaweb avatar iglaweb commented on July 23, 2024

@romainguy possible fix, round should be ok

fun Float.spToPx(displayMetrics: DisplayMetrics): Int =
        (this * displayMetrics.scaledDensity).round()
fun Float.dpToPx(displayMetrics: DisplayMetrics): Int =
        (this * displayMetrics.density).round()
private fun Float.round(): Int = (if(this < 0) ceil(this - 0.5f) else floor(this + 0.5f)).toInt()

from android-ktx.

arekolek avatar arekolek commented on July 23, 2024

How about a small DSL that would make it possible to write stuff like:

view.context.pixelConversions {
    outRect.left = 80.dp
    outRect.right = 20.dp
}

Using the idea from the first post, that would be:

fun Context.pixelConversions(block: ConversionContext.() -> Unit) {
    block(ConversionContext(resources.displayMetrics))
}

class ConversionContext(private val displayMetrics: DisplayMetrics) {
    val Int.dp get() = toFloat().dp.toInt()

    val Float.dp get() = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, this, displayMetrics)
}

After trying to apply it in a different setting, I'm not sure how useful this idea actually is 🤔

(I forgot this repo is unused now, is this ticket tracked on google issue tracker?)

from android-ktx.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.