How to create BottomNavigation with one of the item is larger than the parent, but without using floatingActionButton. For example like this:
I tried to do that by wrapping the icon with Box but it get cut like this:
Then i try to separate that one button and use constraintLayout to position it, but the constraintLayout cover the screen like this. Even when i color it using Color.Transparent, it always feels like Color.White (i dont know why Color.Transparent never work for me). In this picture i give it Red color for clarity reason.
So how to do this kind of bottomNavBar without having to create heavy-custom-composable?
Update: so i try to make the code based on MARSK and Dharman comment (thanks btw). This is what i
BoxWithConstraints(
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.background(Color.Transparent)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(56.dp)
.background(Color.White)
.align(Alignment.BottomCenter)
)
Row(
modifier = Modifier
.zIndex(56.dp.value)
.fillMaxWidth()
.selectableGroup(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
items.forEach { item ->
val selected = item == currentSection
BottomNavigationItem(
modifier = Modifier
.align(Alignment.Bottom)
.then(
Modifier.height(
if (item == HomeSection.SCAN) 84.dp else 56.dp
)
),
selected = selected,
icon = {
if (item == HomeSection.SCAN) {
ScanButton(navController = navController, visible = true)
} else {
ImageBottomBar(
icon = if (selected) item.iconOnSelected else item.icon,
description = stringResource(id = item.title)
)
}
},
label = {
Text(
text = stringResource(item.title),
color = if (selected) Color(0xFF361DC0) else LocalContentColor.current.copy(
alpha = LocalContentAlpha.current
),
style = TextStyle(
fontFamily = RavierFont,
fontWeight = if (selected) FontWeight.Bold else FontWeight.Normal,
fontSize = 12.sp,
lineHeight = 18.sp,
),
maxLines = 1,
)
},
onClick = {
if (item.route != currentRoute && item != HomeSection.SCAN) {
navController.navigate(item.route) {
launchSingleTop = true
restoreState = true
popUpTo(findStartDestination(navController.graph).id) {
saveState = true
}
}
}
}
)
}
}
}
It works in preview, but doesn't work when i try in app.
This one in the preview, the transparent working as expected:
And this is when i try to launch it, the transparent doesnt work:
Note: I assign that to bottomBar of Scaffold so i could access the navigation component. Is it the cause that Transparent Color doesnt work?
Update 2: so the inner paddingValues that makes the transparent doesnt work. I fixed it by set the padding bottom manually:
PaddingValues(
start = paddingValues.calculateStartPadding(
layoutDirection = LayoutDirection.Ltr
),
end = paddingValues.calculateEndPadding(
layoutDirection = LayoutDirection.Ltr
),
top = paddingValues.calculateTopPadding(),
bottom = SPACE_X7,
)
Custom Composable are not heavy, really.
Anyway, try this:-
Create a Container of MaxWidth (maybe a BoxWithConstraints or something), keep its background transparent, set the height to wrap content. Create the tabs as usual, but keeping the bigger tab's icon size bigger explicitly using Modifier.size(Bigger Size).
After you have this setup, add another container inside this container with white background, covering a specific height of the original container. Let's say 60%
Now set the z-index of all the icons and tabs to higher than the z-index of this lastly added container. Use Modifier.zIndex for this. And viola, you have your Composable ready.
In order to set a specific percentage height of the inner container, you will need access to the height of the original container. Use BoxWithConstraints for that, or just implement a simple custom Layout Composable
Related
trying to achieve this
I tried using spannable string and ImageSpan but I need functionality to change text also in the view
If you use Kotlin Jetpack Compose you can use row, there will be image and text in the row and you can shape them according to you and add a vertical divider in between.
For example:
Row(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp),
horizontalArrangement =Arrangement.Center,
verticalAlignment = Alignment.Top
){
Image(painter = R.drawable.x, contentDescription ="image description" )
Divider(
color = Color.Gray,
modifier = Modifier
.fillMaxHeight() //fill the max height
.width(1.dp)
)
Text("text example ")
}
In my android app user can change text foreground and background colors by selecting text, everything works fine, but if user created red foreground for text Hello World, and wants to remove red foreground in the World word, code removes the whole sentence background, if there's a way to do it, please, help me, here is the function:
private fun changeTextForegroundColor(
#ColorInt foregroundColor: Int?,
startPosition: Int,
endPosition: Int
) {
val resultText: Spannable = bindingContent.contentEditText.text.toSpannable()
//Get foreground spans and remove them.
val spans: Array<ForegroundColorSpan> = resultText.getSpans(
startPosition, endPosition,
ForegroundColorSpan::class.java)
repeat(spans.count()) {
resultText.removeSpan(spans[it])
}
// If foregroundColor == null, then just remove the span, what we do above
if (foregroundColor != null) {
resultText.setSpan(
ForegroundColorSpan(foregroundColor),
startPosition,
endPosition,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
bindingContent.contentEditText.setText(resultText)
}
I tried not to remove, but to replace the foreground color with white(my text color) but it causes lags in my app, so I need to remove a word World, without removing the Hello word foreground in my span.
Make sure that you are using integer colors and not color ids in your code. You, of course, could use the color ids but you will have to make the conversion to a color integer. The following demonstrates this.
val newColor =
ResourcesCompat.getColor(resources, android.R.color.holo_blue_light, null)
changeTextForegroundColor(newColor, 0, 5)
Then in changeTextForgroundColor()
// If foregroundColor == null, then just remove the span, what we do above
if (foregroundColor != null) {
resultText.setSpan(
ForegroundColorSpan(foregroundColor),
startPosition,
endPosition,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
Finally, I wrote code that as I guess can resolve my problem, here I remove spans if span within selection (It won't remove span which range outside of selection), then check if color is null, then set color to white, else to another one. This code helps to to avoid issue when one span was painted over another one:
private fun changeTextForegroundColor( #ColorInt foregroundColor: Int?, startPosition: Int, endPosition: Int) {
val resultText: Spannable =
noteBinding.contentActivityInclude.contentEditText.text?.toSpannable() ?: return
val spans: Array<ForegroundColorSpan> = resultText.getSpans(
startPosition, endPosition,
ForegroundColorSpan::class.java)
spans.forEach {
val selectedSpanStart = resultText.getSpanStart(it)
val selectedSpanEnd = resultText.getSpanEnd(it)
if (selectedSpanStart >= startPosition && selectedSpanEnd <= endPosition) {
resultText.removeSpan(it)
}
}
if (foregroundColor == null) {
resultText.setSpan(
ForegroundColorSpan(Color.WHITE),
startPosition,
endPosition,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
} else {
resultText.setSpan(
ForegroundColorSpan(foregroundColor),
startPosition,
endPosition,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)
}
noteBinding.contentActivityInclude.contentEditText.setText(resultText)
}
I've spent several frustrating hours trying to implement (what I thought would be) a simple FontActor class.
The idea is to just draw text at a specific position using a provided BitmapFont. That much, I've managed to accomplish. However, I'm struggling to compute my actor's width/height based on the rendered text.
(Using a FitViewport for testing)
open class FontActor<T : BitmapFont>(val font: T, var text: CharSequence = "") : GameActor() {
val layout = Pools.obtain(GlyphLayout::class.java)!!
companion object {
val identity4 = Matrix4().idt()
val distanceFieldShader: ShaderProgram = DistanceFieldFont.createDistanceFieldShader()
}
override fun draw(batch: Batch?, parentAlpha: Float) {
if (batch == null) return
batch.end()
// grab ui camera and backup current projection
val uiCamera = Game.context.inject<OrthographicCamera>()
val prevTransform = batch.transformMatrix
val prevProjection = batch.projectionMatrix
batch.transformMatrix = identity4
batch.projectionMatrix = uiCamera.combined
if (font is DistanceFieldFont) batch.shader = distanceFieldShader
// the actor has pos = x,y in local coords, but we need UI coords
// start by getting group -> stage coords (world)
val coords = Vector3(localToStageCoordinates(Vector2(0f, 0f)), 0f)
// world coordinate destination -> screen coords
stage.viewport.project(coords)
// screen coords -> font camera world coords
uiCamera.unproject(coords,
stage.viewport.screenX.toFloat(),
stage.viewport.screenY.toFloat(),
stage.viewport.screenWidth.toFloat(),
stage.viewport.screenHeight.toFloat())
// adjust position by cap height so that bottom left of text aligns with x, y
coords.y = uiCamera.viewportHeight - coords.y + font.capHeight
/// TODO: use BitmapFontCache to prevent this call on every frame and allow for offline bounds calculation
batch.begin()
layout.setText(font, text)
font.draw(batch, layout, coords.x, coords.y)
batch.end()
// viewport screen coordinates -> world coordinates
setSize((layout.width / stage.viewport.screenWidth) * stage.width,
(layout.height / stage.viewport.screenHeight) * stage.height)
// restore camera
if (font is DistanceFieldFont) batch.shader = null
batch.projectionMatrix = prevProjection
batch.transformMatrix = prevTransform
batch.begin()
}
}
And in my parent Screen class implementation, I rescale my fonts on every window resize so that they don't become "smooshed" or stretched:
override fun resize(width: Int, height: Int) {
stage.viewport.update(width, height)
context.inject<OrthographicCamera>().setToOrtho(false, width.toFloat(), height.toFloat())
// rescale fonts
scaleX = width.toFloat() / Config.screenWidth
scaleY = height.toFloat() / Config.screenHeight
val scale = minOf(scaleX, scaleY)
gdxArrayOf<BitmapFont>().apply {
Game.assets.getAll(BitmapFont::class.java, this)
forEach { it.data.setScale(scale) }
}
gdxArrayOf<DistanceFieldFont>().apply {
Game.assets.getAll(DistanceFieldFont::class.java, this)
forEach { it.data.setScale(scale) }
}
}
This works and looks great until you resize your window.
After a resize, the fonts look fine and automatically adjust with the relative size of the window, but the FontActor has the wrong size, because my call to setSize is wrong.
Initial window:
After making window horizontally larger:
For example, if I then scale my window horizontally (which has no effect on the world size, because I'm using a FitViewport), the font looks correct, just as intended. However, the layout.width value coming back from the draw() changes, even though the text size hasn't changed on-screen. After investigation, I realized this is due to my use of setScale, but simply dividing the width by the x-scaling factor doesn't correct the error. And again, if I remove my setScale calls, the numbers make sense, but the font is now squished!
Another strategy I tried was converting the width/height into screen coordinates, then using the relevant project/unproject methods to get the width and height in world coordinates. This suffers from the same issue shown in the images.
How can I fix my math?
Or, is there a smarter/easier way to implement all of this? (No, I don't want Label, I just want a text actor.)
One problem was my scaling code.
The fix was to change the camera update as follows:
context.inject<OrthographicCamera>().setToOrtho(false, stage.viewport.screenWidth.toFloat(), stage.viewport.screenHeight.toFloat())
Which causes my text camera to match the world viewport camera. I was using the entire screen for my calculations, hence the stretching.
My scaleX/Y calculations were wrong for the same reason. After correcting both of those miscalculations, I have a nicely scaling FontActor with correct bounds in world coordinates.
I have a JDialog with just a few components inside it. I want to make the dialog as small as possible. Currently I am using pack(). This has the unintended effect of reducing the dialog's width so much that the title is no longer completely in view. I want the dialog's width to always be great enough such that the title is always completely in view.
I am using swing. I realize that the title bar appearance/font is determined by the OS. I would prefer to stick with swing so at the moment i am planning on calculating the title string width based on the font of a JLabel. Then I will set the minimum width of one of my components equal to that.
Is there any better way to pack a JDialog while keeping its title visible?
public static void adjustWidthForTitle(JDialog dialog)
{
// make sure that the dialog is not smaller than its title
// this is not an ideal method, but I can't figure out a better one
Font defaultFont = UIManager.getDefaults().getFont("Label.font");
int titleStringWidth = SwingUtilities.computeStringWidth(new JLabel().getFontMetrics(defaultFont),
dialog.getTitle());
// account for titlebar button widths. (estimated)
titleStringWidth += 110;
// set minimum width
Dimension currentPreferred = dialog.getPreferredSize();
// +10 accounts for the three dots that are appended when the title is too long
if(currentPreferred.getWidth() + 10 <= titleStringWidth)
{
dialog.setPreferredSize(new Dimension(titleStringWidth, (int) currentPreferred.getHeight()));
}
}
EDIT:
after reading trashgod's post in the link, I adjusted my solution to override the getPreferredSize method. I think this way is better than my previous static method. Using the static method, I had to adjust it in a pack() sandwich. pack(),adjust(),pack(). This wasy doesn't require special consideration with pack().
JDialog dialog = new JDialog()
{
#Override
public Dimension getPreferredSize()
{
Dimension retVal = super.getPreferredSize();
String title = this.getTitle();
if(title != null)
{
Font defaultFont = UIManager.getDefaults().getFont("Label.font");
int titleStringWidth = SwingUtilities.computeStringWidth(new JLabel().getFontMetrics(defaultFont),
title);
// account for titlebar button widths. (estimated)
titleStringWidth += 110;
// +10 accounts for the three dots that are appended when
// the title is too long
if(retVal.getWidth() + 10 <= titleStringWidth)
{
retVal = new Dimension(titleStringWidth, (int) retVal.getHeight());
}
}
return retVal;
}
};
1) Use FontMetrics to find out the width of your title
2) Add to this value a number representing the window icon and the X (close) button (you should guess that).
3) Set the dialog's width with the above value.
You can't find the exact width size you need but this is a way to make a good guess.
I'm having a problem. I want to put an image inside a form in Java and I don't know if I'm using a proper technique (found it somewhere in a web page).
private void iconSelect() {
String iconString = "";
if (typeCombobox.getSelectedIndex() == 0) {
iconString = "LP_";
} else if (typeCombobox.getSelectedIndex() == 1) {
iconString = "HP_";
} else if (typeCombobox.getSelectedIndex() == 2) {
iconString = "BP_";
} else if (typeCombobox.getSelectedIndex() == 3) {
iconString = "BS_";
}
if (RB_Gain_Clean.isSelected()) {
iconString = iconString + "Clean";
} else if (RB_Gain_dB.isSelected()) {
iconString = iconString + "dB";
}
ImageIcon icon = new ImageIcon("images/" + iconString + ".jpg");
Image img = icon.getImage();
if (iconGraphLabel.getWidth() > 0 && iconGraphLabel.getHeight() > 0) {
img = img.getScaledInstance(iconGraphLabel.getWidth(), iconGraphLabel.getHeight(), java.awt.Image.SCALE_SMOOTH);
}
icon = new ImageIcon(img);
iconGraphLabel.setIcon(icon);
}
So it actually shows the image and it is resizing but when I resize my form and then make it smaller again, the label doesn't seem to follow the resizing so it stays bigger than the window.
Also, since I'm not very familiar with java's graphics, can anyone tell me how can I control a window resizing event so I redraw the picture? Right now the method is triggered by the combobox and the radiobuttons shown in the code.
Thanks in advance!
edit1: Well the form is my jFrame. The iconGraphLabel is the jLabel I'm putting the image in. I'll try to explain the hierarchy of the parent components.
PlotArea [jPanel] (cardLayout) > plotArea_Image [jPanel] ("cardDraw") > iconGraphPanel [jPanel] > iconGraphLabel
but when I resize my form and then
make it smaller again, the label
doesn't seem to follow the resizing so
it stays bigger than the window
Correct, a JLabel, or any Swing component that uses Icons will paint the Icon at its actual size. If you want the Icon to scale depending on the space available then you need to do custom painting.
The Background Panel classes provides different options for displaying an image (you can just use the Icon,getImage() method). You should also read the section from the Swing tutorial on Custom Painting to better understand how the above code works.
Found a solution. This is the final code:
private void iconSelect() {
iconGraphPanel.removeAll();
ImageIcon icon = new ImageIcon("image.jpg");
BackgroundPanel imagePanel = new BackgroundPanel(icon.getImage(), BackgroundPanel.SCALED);
iconGraphPanel.add(imagePanel);
iconGraphPanel.revalidate();
}
iconGraphPanel is a common jPanel that I use as a place holder. It needs to be set to BorderLayout. The BackgroundPanel class can be found here. The removeAll() is needed so the old image disappears. If you don't put this images start to stack up. Don't know if there is a better way to do that but it works just fine for me. The revalidate() method is needed because we create a new panel so it needs to refresh.
This is mostly camickr work and some other guy from sun forum named Maxideon. I'm just posting for future reference.