Android Question Neumorphism UI - how would you do this

mfstuart

Active Member
Licensed User
Hi all,
I discovered this just yesterday and wanted to create it for the Android tablet platform.
This new UI comes from this website:
1577386286555.png

As you can see, it is a very subtle clean look, where the Elevation top color of the View object (most likely a Panel) is White and the bottom color is a very light gray.
(See the website link above for actual colors)
I've tried playing around with the standard properties of the Panel View and not able to get the effect. (In B4J, you can set CSS properties and most likely get this effect, but haven't tried)
I've also searched the forum and found some threads that use bitmaps and such, but I've never used any of that in my apps, and don't understand it.

Is this effect even possible for any B4A views, and if so, how would it be done?

Thanx,
Mark Stuart
 

JordiCP

Well-Known Member
Licensed User
Well, the approach is based on solid colors, so the routines need to know the parent's color (maincolor)

This iteration automatically calculates the lightColor and darkColor for a given mainColor. I suppose it can still be fine-tuned playing with the values in 'GenerateNewThemeColors', but I liked the results (they change each time you click on the screen) :)

B4X:
Sub Globals
    'These global variables will be redeclared each time the activity is created.
    'These variables can only be accessed from this module.
    Dim mainColor As Int  '0xFFE0E5EC
    Dim lightColor As Int '0xFFFFFFFF
    Dim darkColor As Int  '0xFFA3B1C6
End Sub

Sub Activity_Create(FirstTime As Boolean)
    'Do not forget to load the layout file created with the visual designer. For example:
    'Activity.LoadLayout("Layout1")

    GenerateNewThemeColors
    DrawEverything

End Sub


Sub DrawEverything

    Activity.RemoveAllViews
    Activity.Color = mainColor

    Dim P As Label = createLabel("Warmer")
    Dim WW As Int = Activity.Width/2
    Dim HH As Int = WW/2
    Activity.AddView(P, (Activity.Width-WW)/2, Activity.Height/4-HH/2, WW, HH)
    GenerateViewShadow(P, WW/6, 0.2)

    Dim P2 As Label = createLabel("NEW")
    Dim WW As Int = Activity.Width/2
    Dim HH As Int = WW
    Activity.AddView(P2, (Activity.Width-WW)/2, 2*Activity.Height/4-HH/2, WW, HH)
    GenerateViewShadowWithPath(P2, 0.2)

    Dim P3 As Label = createLabel("Year!!")
    Dim WW As Int = Activity.Width/3
    Dim HH As Int = WW/2
    Activity.AddView(P3, (Activity.Width-WW)/2, 3*Activity.Height/4-HH/2, WW, HH)
    GenerateViewShadow(P3, P.Width/12, 0.2)    
End Sub

    
Sub createLabel(myText As String) As Label
    Dim l As Label
    l.Initialize("")
    l.Gravity = Gravity.CENTER
    l.TextColor = Colors.White
    l.TextSize = 20
    l.Text = myText
    Return l
End Sub
    
    
Sub GenerateViewShadow( P As B4XView, cornerRadius As Int, insetPercentage As Float)

    Dim CVX As B4XCanvas

    Dim BigWW As Int  = P.Width
    Dim BigHH As Int = P.Height
    
    ' inset percentage will be according to the smallest dim, but will be the same for both direction
    Dim absInset As Float = insetPercentage*Min(BigWW, BigHH)
    Dim SmallWW As Int = BigWW - absInset
    Dim SmallHH As Int = BigHH - absInset
        
    CVX.Initialize(P)

    Dim PX As B4XPath
    Dim R0 As B4XRect
    Dim dW As Int = (BigWW-SmallWW)/2
    Dim dH As Int = (BigHH-SmallHH)/2
    Dim d As Int = Max(dW, dH)
    
    ' UPPER (light) shadow    
    For k = 0 To d         
        Dim kW As Float = k*dW/d
        Dim kH As Float = k*dH/d
        R0.Initialize( kW , kH, SmallWW+dW-kW, SmallHH+dH-kH)
        PX.InitializeRoundedRect(R0, cornerRadius)    
        Dim pColor As Int = FindSolidColorBetween(mainColor, lightColor, 1.0*k*k*k/(d*d*d))
        CVX.DrawPath(PX, pColor, True, 0)
    Next

    ' LOWER (dark) shadow
    For k = 0 To d
        Dim kW As Float = k*dW/d
        Dim kH As Float = k*dH/d
        R0.Initialize( 2*dW-kW , 2*dH-kH, 2*dW+SmallWW-kW, 2*dH+SmallHH-kH)
        PX.InitializeRoundedRect(R0, cornerRadius)
        Dim pColor As Int = FindSolidColorBetween(mainColor, darkColor, 1.0*k*k*k/(d*d*d))
        CVX.DrawPath(PX, pColor, True, 0)
    Next
    
    ' Draw the 'plain' area with the main color.
    R0.Initialize( dW , dH, dW+SmallWW, dH+SmallHH)
    PX.InitializeRoundedRect(R0, cornerRadius)
    CVX.DrawPath(PX, mainColor, True, 0)

End Sub


Sub GenerateViewShadowWithPath( P As B4XView,  insetPercentage As Float)

    Dim CVX As B4XCanvas

    Dim BigWW As Int  = P.Width
    Dim BigHH As Int = P.Height
    
    ' inset percentage will be according to the smallest dim, but will be the same for both direction
    Dim absInset As Float = insetPercentage*Min(BigWW, BigHH)
    Dim SmallWW As Int = BigWW - absInset
    Dim SmallHH As Int = BigHH - absInset

    ' Let's build an hexagonal (closed) Path
    Dim PathPoints(7,2) As Float
    For k=0 To 6
        PathPoints(k,0) = SmallWW/2*(1+CosD(60*k))
        PathPoints(k,1) = SmallHH/2*(1+SinD(60*k))
    Next
        
    CVX.Initialize(P)

    Dim PX As B4XPath
    Dim dW As Int = (BigWW-SmallWW)/2
    Dim dH As Int = (BigHH-SmallHH)/2
    Dim d As Int = Max(dW, dH)
    
    ' UPPER (light) shadow    
    For k = 0 To d         
        Dim kW As Float = k*dW/d
        Dim kH As Float = k*dH/d
        PX.Initialize(PathPoints(0,0)+kW, PathPoints(0,1)+kH)
        For c=1 To 6
            PX.LineTo(PathPoints(c,0)+kW, PathPoints(c,1)+kH)            
        Next
        Dim pColor As Int = FindSolidColorBetween(mainColor, lightColor, 1.0*k*k*k/(d*d*d))
        CVX.DrawPath(PX, pColor, True, 0)
    Next

    ' LOWER (dark) shadow
    For k = 0 To d
        Dim kW As Float = k*dW/d
        Dim kH As Float = k*dH/d
        PX.Initialize(PathPoints(0,0)+2*dW-kW, PathPoints(0,1)+2*dH-kH)
        For c=1 To 6
            PX.LineTo(PathPoints(c,0)+2*dW-kW, PathPoints(c,1)+2*dH-kH)
        Next
        Dim pColor As Int = FindSolidColorBetween(mainColor, darkColor, 1.0*k*k*k/(d*d*d))
        CVX.DrawPath(PX, pColor, True, 0)
    Next

    ' Draw the 'plain' area with the main color.
    PX.Initialize(PathPoints(0,0)+dW, PathPoints(0,1)+dH)
    For k=0 To 6
        PX.LineTo(PathPoints(k,0)+dW, PathPoints(k,1)+dH)
    Next
    CVX.DrawPath(PX, mainColor, True, 0)

End Sub



' progress=0 --> we get colorA
' progress=1 -> we get colorB
Sub FindSolidColorBetween(colorA As Int, colorB As Int, progress As Float) As Int

    Dim weight As Int = 256*(1-Max(0, Min(progress, 1)))
    Log("val:"&weight)
    Dim finalRed As Int   = Bit.ShiftRight(weight*Bit.And(Bit.ShiftRight(colorA,16),0xFF) + (256-weight)*Bit.And(Bit.ShiftRight(colorB,16),0xFF),8)
    Dim finalGreen As Int = Bit.ShiftRight(weight*Bit.And(Bit.ShiftRight(colorA, 8),0xFF) + (256-weight)*Bit.And(Bit.ShiftRight(colorB, 8),0xFF),8)
    Dim finalBlue As Int  = Bit.ShiftRight(weight*Bit.And(Bit.ShiftRight(colorA, 0),0xFF) + (256-weight)*Bit.And(Bit.ShiftRight(colorB, 0),0xFF),8)    
    Return Colors.RGB(finalRed, finalGreen, finalBlue)
End Sub

Sub Activity_Click
    GenerateNewThemeColors
    DrawEverything
End Sub

Sub GenerateNewThemeColors
    mainColor = Colors.RGB( 160 + Rnd(0,80), 160+Rnd(0,80), 160+Rnd(0,80))
    darkColor = FindLighterOrDarkerColor(mainColor, 0.85)
    lightColor = FindLighterOrDarkerColor(mainColor, 1.15)
End Sub

' Factor = 0.9 will be slightly darker  Factor=1.1 will be lighter
Sub FindLighterOrDarkerColor(myColor As Int, factor As Float) As Int
    Dim finalRed As Int = Min(0xFF, factor*Bit.And(0xFF, Bit.ShiftRight(myColor, 16)))
    Dim finalGreen As Int = Min(0xFF, factor*Bit.And(0xFF, Bit.ShiftRight(myColor, 8)))
    Dim finalBlue As Int = Min(0xFF, factor*Bit.And(0xFF, Bit.ShiftRight(myColor, 0)))    
    Return Colors.RGB(finalRed, finalGreen, finalBlue)
End Sub
 
Upvote 0

LucaMs

Expert
Licensed User
Well, the approach is based on solid colors, so the routines need to know the parent's color (maincolor)

This iteration automatically calculates the lightColor and darkColor for a given mainColor. I suppose it can still be fine-tuned playing with the values in 'GenerateNewThemeColors', but I liked the results (they change each time you click on the screen)
I have not tested your new version yet but I suppose, given the explanation, that the color of the activity cannot be "completely" different from that of the Views (and the relative shadows).

Using the previous code and changing the color of the Activity at the end of the creation of the rest...

😝
1577573284978.png



I'm sorry, but now you should (must 😝) find a solution to create a beautiful b4x custom view based on your code (unless you want to make all B4X.com members cry! 😝)
 
Upvote 0

JordiCP

Well-Known Member
Licensed User
OMG, I have just tried with shadows on a different background, and the results are horrible 😱😱

As I see it, the original UI is not only something specific to each View, but more like a Theme: I mean, it talks about how to 'gracefully' integrate elements into a background (with the same color, so we need to know it), playing only with slight shadows

I'm sorry, but now you should (must 😝) find a solution to create a beautiful b4x custom view based on your code (unless you want to make all B4X.com members cry! 😝)
Uhmm, if someone (not me, never made one! 🤔) converts it to a CustomView, I think that it will be easier to send the parent's color (mainColor) as a constructor parameter and/or add a function to redraw itself when the parent's color changes (in the later approach lightColor and darkColor are calculated automatically based on mainColor).
 
Upvote 0

LucaMs

Expert
Licensed User
OMG, I have just tried with shadows on a different background, and the results are horrible 😱😱

As I see it, the original UI is not only something specific to each View, but more like a Theme: I mean, it talks about how to 'gracefully' integrate elements into a background (with the same color, so we need to know it), playing only with slight shadows


Uhmm, if someone (not me, never made one! 🤔) converts it to a CustomView, I think that it will be easier to send the parent's color (mainColor) as a constructor parameter and/or add a function to redraw itself when the parent's color changes (in the later approach lightColor and darkColor are calculated automatically based on mainColor).
I could try but... I'm sure that Erel can do it in few minutes with XUI (it would take me at least an hour :D☹)
 
Upvote 0

LucaMs

Expert
Licensed User
Very nice, now how is the click event added?


B4X:
Sub createLabel(myText As String) As Label
   Dim l As Label l.Initialize("")
   l.Gravity = Gravity.CENTER
   l.TextColor = Colors.White
   l.TextSize = 20
   l.Text = myText
   Return l
End Sub

Change that sub, "createLabel", to something like:

B4X:
Sub createLabel(myText As String, EventName As String) As Label
   Dim l As Label l.Initialize(EventName)
   l.Gravity = Gravity.CENTER
   l.TextColor = Colors.White
   l.TextSize = 20
   l.Text = myText
   Return l
End Sub

call it:
B4X:
Dim P As Label = createLabel("Warmer", "EventNameHere")

B4X:
Sub EventNameHere_Click
'...
End Sub

(not tested but it should work).
 
Upvote 0

mfstuart

Active Member
Licensed User
WOW!! This is awesome, Jordi. You nailed this thing right smack dead on the head, with one hit.

You obviously read the whole article I referenced in my first post, because you took into account the different background colors.
And to come up with the "light" and "dark" colors totally completes the answer to the issue.

Let me see how this will apply to my project.
I'm very grateful for your knowledge and expertise on these libraries, that I do not know.
I would be cool to see each line of code take effect/draw on the tablet screen when it is running. I'll put it into Debug mode.

Thanx for your code,
Mark Stuart
 
Upvote 0

AnandGupta

Well-Known Member
Licensed User
And this is the first time nobody is complaining of a huge image attachment.
In fact it is helping in appriciating the beautiful work done :)
Great work. Thanks.

Regards,

Anand
 
Upvote 0
Top