Divider() doesn't work with horizontalScroll() in Jetpack Compose [duplicate] - android-studio

This question already has an answer here:
Divider composable becomes invisible when placed inside composable with horizontalScroll modifier set. Is this a bug?
(1 answer)
Closed 9 months ago.
I'm trying to present a soccer table in Jetpack Compose UI. The user should be able to scroll the table both horizontally (through columns) and vertically (through rows). However, Divider() doesn't work with horizontalScroll() in Jetpack Compose and hence, the dividing line is missing in between items. If I remove horizontalScroll() from Card, it's working perfectly. How can I improve this?
Box(
modifier = Modifier
.fillMaxSize()
.background(Color(0, 0, 0, 255))
) {
Card(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp)
.horizontalScroll(rememberScrollState()),
backgroundColor = Color.White,
shape = RoundedCornerShape(topStart = 12.dp, bottomStart = 12.dp)
) {
LazyColumn(
modifier = Modifier
.fillMaxWidth()
.padding(16.dp),
) {
itemsIndexed(teams) { i, team ->
Row(
modifier = Modifier
.fillMaxWidth()
.padding(6.dp),
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = team.name,
fontSize = 18.sp,
color = Color.Black,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.width(150.dp)
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = team.gamesWon.toString(),
fontSize = 18.sp,
color = Color.Black,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.width(30.dp),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = team.gamesLost.toString(),
fontSize = 18.sp,
color = Color.Black,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.width(30.dp),
textAlign = TextAlign.Center
)
Spacer(modifier = Modifier.width(12.dp))
Text(
text = team.stadiumName,
fontSize = 18.sp,
color = Color.Black,
fontWeight = FontWeight.SemiBold,
modifier = Modifier.width(300.dp),
maxLines = 1,
textAlign = TextAlign.Center
)
}
Divider()
}
}
}
}

Since you are setting exact widths of all the things, you could just add up all the sizes and set the Divider width to match.

Related

Android compose: how to position icons in the margins of a text in a row?

I would like to have a card with the following layout:
an icon on the left;
text in the center;
an icon to the right;
The icons must always be present regardless of the length of the text:
In this regard I wrote the following code:
fun test() {
Card(
modifier = Modifier.fillMaxWidth(),
shape = RoundedCornerShape(16.dp)
) {
Row(
Modifier.fillMaxWidth().padding(all = 16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
Text("Title", textAlign = TextAlign.Center)
Icon(imageVector = Icons.Default.Delete, contentDescription = "Delete")
}
}
}
The problem is that if the text is too long, then the last icon "disappears":
A solution could be to use Modifier.width (x.dp) on the text, but in this case how do I set the value of x to cover the maximum possible width within the icons?
You can apply Modifier.weight(1f) to the Text composable.
Something like:
Row(
Modifier
.fillMaxWidth()
.height(30.dp)
){
Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
Text("Title", textAlign = TextAlign.Center,
modifier = Modifier.weight(1f)) // Fill this with remaining space available
Icon(imageVector = Icons.Default.Delete, contentDescription = "Delete")
}
You can make this spacing arrangement with distributed weights directly.
I've added each icon weight 1 and rest of the space to title.
Or you may also use ConstraintLayout for this, but row with weights works fine too.
Row(
Modifier.fillMaxWidth().padding(16.dp),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Icon(
modifier = Modifier.weight(1f),
imageVector = Icons.Default.ArrowBack, contentDescription = "Back"
)
Text(
modifier = Modifier.weight(8f),
text = "Title",
textAlign = TextAlign.Center
)
Icon(
modifier = Modifier.weight(1f),
imageVector = Icons.Default.Delete,
contentDescription = "Delete"
)
}

Jetpack Compose layout challenge

I'm trying to layout the following in compose but I cannot seem to get the alignment / arrangements to work correctly. I have tried starting with Row() and also trying starting with Column but the results are the same - misaligned.
So I have the following properties:
1. constantA = "774"
2. operator = "+"
3. constantB = "62"
4. line = "-------"
5. answer = "826"
6. icon = "checkmark"
The alignment needs to be as follows:
774
+ 52
-----
826 ✓
Any help or pointers will be appreciated!
In case you wanna use a regular Icon:
#Preview
#Composable
fun OperationPreview() {
val constantA = "774"
val operator = "+"
val constantB = "62"
val line = "-------"
val answer = "826"
Row(verticalAlignment = Alignment.Bottom) {
Column(horizontalAlignment = Alignment.End) {
Text(text = constantA)
Text(text = "$operator $constantB")
Text(text = line)
Row() {
Text(text = answer)
}
}
Icon(
modifier = Modifier
.size(10.dp)
.padding(bottom = 5.dp),
imageVector = Icons.Default.Check,
contentDescription = null
)
}
}
#Composable
fun TextAlignLayout() {
val constantA = "774" + " "
val operator = "+" + " "
val constantB = "62" + " "
val line = "-------" + " "
val answer = "826"
val icon = "✓"
Column(
modifier = Modifier
.padding(24.dp)
.fillMaxSize(),
horizontalAlignment = Alignment.End
) {
Text(constantA)
Text("$operator$constantB")
Text(line)
Text("$answer $icon")
}
}

What is the difference between alphabetic and ideographic in Flutter's TextBaseline enum

The TextBaseline enum in Flutter has two options:
alphabetic
ideographic
How do these values actually change the baseline?
TextBaseline.alphabetic
The alphabetic baseline is the line that the letters in alphabets like English sit on. Here is an example:
You can see that the English letters sit nicely on the line, but it cuts through the Chinese characters.
TextBaseline.ideographic
When you use the ideographic option, though, the baseline is at the bottom of the text area. Note that the Chinese characters don't actually sit right on the line. Rather, the line is at the very bottom of the text line.
Supplemental code
You can plug this into a CustomPaint widget (as described here) to reproduce the above examples.
#override
void paint(Canvas canvas, Size size) {
final textStyle = TextStyle(
color: Colors.black,
fontSize: 30,
);
final textSpan = TextSpan(
text: 'My text 文字',
style: textStyle,
);
final textPainter = TextPainter(
text: textSpan,
textDirection: TextDirection.ltr,
);
textPainter.layout(
minWidth: 0,
maxWidth: size.width,
);
print('width: ${textPainter.width}');
print('height: ${textPainter.height}');
// draw a rectangle around the text
final left = 0.0;
final top = 0.0;
final right = textPainter.width;
final bottom = textPainter.height;
final rect = Rect.fromLTRB(left, top, right, bottom);
final paint = Paint()
..color = Colors.red
..style = PaintingStyle.stroke
..strokeWidth = 1;
canvas.drawRect(rect, paint);
// draw the baseline
final distanceToBaseline =
textPainter.computeDistanceToActualBaseline(TextBaseline.ideographic);
print('distanceToBaseline: ${distanceToBaseline}');
canvas.drawLine(
Offset(0, distanceToBaseline),
Offset(textPainter.width, distanceToBaseline),
paint,
);
// draw the text
final offset = Offset(0, 0);
textPainter.paint(canvas, offset);
}

Remove tint color but keep font color UISegmentedControl

I would like to remove the selected color from my UISegmetedControl. I know tintColor can do this but that also removes the font color with it. Also using kCTForegroundColorAttributeName will remove both.
Side note I made a UIView and placed it above the selected segment to show selected state. I thought this would look better. Trying to branch out and make my own custom controls.
public let topLine = UIView()
override func awakeFromNib() {
super.awakeFromNib()
self.removeBorders()
setFont()
addTopLine()
}
func setFont() {
let font = UIFont(name: FontTypes.avenirNextUltraLight, size: 22.0)!
let textColor = UIColor.MyColors.flatWhite
let attribute = [kCTFontAttributeName:font]
self.setTitleTextAttributes(attribute, for: .normal)
}
func addTopLine() {
topLine.backgroundColor = UIColor.MyColors.flatWhite
let frame = CGRect(x: 7,
y: -5,
width: Int(self.frame.size.width)/2,
height: 2)
topLine.frame = frame
self.addSubview(topLine)
}
struct FontTypes {
static let avenirNextRegular = "AvenirNext-Regular"
static let avenirLight = "Avenir-Light"
static let avenirNextUltraLight = "AvenirNext-UltraLight"
}
TintColor is attach with
Background colour of Selected segment,
Text color of Unselected segment and
Border colour of UISegmentedControl.
So, if you going to change tintColor to white, then background colour and tint color Both are gone.
You need to set Selected/Unselected text attribute like below:
mySegment.tintColor = .white
let selectedAtrribute = [NSAttributedStringKey.foregroundColor: UIColor.red, NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16)]
mySegment.setTitleTextAttributes(selectedAtrribute as [NSObject : AnyObject], for: UIControlState.selected)
let unselected = [NSAttributedStringKey.foregroundColor: UIColor.black, NSAttributedStringKey.font: UIFont.systemFont(ofSize: 16)]
mySegment.setTitleTextAttributes(unselected as [NSObject : AnyObject], for: UIControlState.normal)

how to convert a swift String to an array of CGGlyph

This snippet can be used for drawing CGGlyphs with a CGContext.
//drawing
let coreGraphicsFont = CTFontCopyGraphicsFont(coreTextFont, nil)
CGContextSetFont(context, coreGraphicsFont);
CGContextSetFontSize(context, CTFontGetSize(coreTextFont))
CGContextSetFillColorWithColor(context, Color.blueColor().CGColor)
CGContextShowGlyphsAtPositions(context, glyphs, positions, length)
But how do I obtain the CGGlyphs from a swift string which contains emoji symbols like flags or accented characters?
let string = "swift: \u{1F496} \u{65}\u{301} \u{E9}\u{20DD} \u{1F1FA}\u{1F1F8}"
Neither of these approaches shows the special characters, even though they are correctly printed to the console. Note that this first approach returns NSGlyph but CGGlyph's are required for drawing.
var progress = CGPointZero
for character in string.characters
{
let glyph = font.glyphWithName(String(character))
glyphs.append(CGGlyph(glyph))
let advancement = font.advancementForGlyph(glyph)
positions.append(progress)
progress.x += advancement.width
}
or this second approach which requires casting to NSString:
var buffer = Array<unichar>(count: length, repeatedValue: 0)
let range = NSRange(location: 0, length: length)
(string as NSString).getCharacters(&buffer, range: range)
glyphs = Array<CGGlyph>(count: length, repeatedValue: 0)
CTFontGetGlyphsForCharacters(coreTextFont, &buffer, &glyphs, length)
//glyph positions
advances = Array<CGSize>(count: length, repeatedValue: CGSize.zero)
CTFontGetAdvancesForGlyphs(ctFont, CTFontOrientation.Default, glyphs, &advances, length)
positions = []
var progress = CGPointZero
for advance in advances
{
positions.append(progress)
progress.x += advance.width
}
Some of the characters are drawn as empty boxes with either approach. Kinda stuck here, hoping you can help.
edit:
Using CTFontDrawGlyphs renders the glyphs correctly, but setting the font, size and text matrix directly before calling CGContextShowGlyphsAtPositions draws nothing. I find that rather odd.
If you generate glyphs yourself, you also need to perform font substitution yourself. When you use Core Text or TextKit to lay out and draw the text, they perform font substitution for you. For example:
let richText = NSAttributedString(string: "Hello😀→")
let line = CTLineCreateWithAttributedString(richText)
print(line)
Output:
<CTLine: 0x7fa349505f00>{run count = 3, string range = (0, 8), width = 55.3457, A/D/L = 15/4.6875/0, glyph count = 7, runs = (
<CTRun: 0x7fa34969f600>{string range = (0, 5), string = "Hello", attributes = <CFBasicHash 0x7fa3496902d0 [0x10e85a7b0]>{type = mutable dict, count = 1,
entries =>
2 : <CFString 0x1153bb720 [0x10e85a7b0]>{contents = "NSFont"} = <CTFont: 0x7fa3496182f0>{name = Helvetica, size = 12.000000, matrix = 0x0, descriptor = <CTFontDescriptor: 0x7fa34968f860>{attributes = <CFBasicHash 0x7fa34968f8b0 [0x10e85a7b0]>{type = mutable dict, count = 1,
entries =>
2 : <CFString 0x1153c16c0 [0x10e85a7b0]>{contents = "NSFontNameAttribute"} = <CFString 0x1153b4700 [0x10e85a7b0]>{contents = "Helvetica"}
}
>}}
}
}
<CTRun: 0x7fa3496cde40>{string range = (5, 2), string = "\U0001F600", attributes = <CFBasicHash 0x7fa34b11a150 [0x10e85a7b0]>{type = mutable dict, count = 1,
entries =>
2 : <CFString 0x1153bb720 [0x10e85a7b0]>{contents = "NSFont"} = <CTFont: 0x7fa3496c3eb0>{name = AppleColorEmoji, size = 12.000000, matrix = 0x0, descriptor = <CTFontDescriptor: 0x7fa3496a3c30>{attributes = <CFBasicHash 0x7fa3496a3420 [0x10e85a7b0]>{type = mutable dict, count = 1,
entries =>
2 : <CFString 0x1153c16c0 [0x10e85a7b0]>{contents = "NSFontNameAttribute"} = <CFString 0x11cf63bb0 [0x10e85a7b0]>{contents = "AppleColorEmoji"}
}
>}}
}
}
<CTRun: 0x7fa3496cf3e0>{string range = (7, 1), string = "\u2192", attributes = <CFBasicHash 0x7fa34b10ed00 [0x10e85a7b0]>{type = mutable dict, count = 1,
entries =>
2 : <CFString 0x1153bb720 [0x10e85a7b0]>{contents = "NSFont"} = <CTFont: 0x7fa3496cf2c0>{name = PingFangSC-Regular, size = 12.000000, matrix = 0x0, descriptor = <CTFontDescriptor: 0x7fa3496a45a0>{attributes = <CFBasicHash 0x7fa3496a5660 [0x10e85a7b0]>{type = mutable dict, count = 1,
entries =>
2 : <CFString 0x1153c16c0 [0x10e85a7b0]>{contents = "NSFontNameAttribute"} = <CFString 0x11cf63230 [0x10e85a7b0]>{contents = "PingFangSC-Regular"}
}
>}}
}
}
)
}
We can see here that Core Text recognized that the default font (Helvetica) doesn't have glyphs for the emoji or the arrow, so it split the line into three runs, each with the needed font.
The Core Text Programming Guide says this:
Most of the time you should just use a CTLine object to get this information because one font may not encode the entire string. In addition, simple character-to-glyph mapping will not get the correct appearance for complex scripts. This simple glyph mapping may be appropriate if you are trying to display specific Unicode characters for a font.
Your best bet is to use CTLineCreateWithAttributedString to generate glyphs and choose fonts. Then, if you want to adjust the position of the glyphs, use CTLineGetGlyphRuns to get the runs out of the line, and then ask the run for the glyphs, the font, and whatever else you need.
If you want to handle font substitution yourself, I think you're going to want to look into “font cascading”.

Resources