Android material aligning font baseline to 4dp grid - android

The Android Material Design Specification says "Type aligns to a 4 dp baseline grid."(http://www.google.com/design/spec/layout/metrics-and-keylines.html#)
It also says in the example graphics that a row in a list should be 72dp high.
It also indicates different font styles and what size the font should be and what its leading should be (http://www.google.com/design/spec/style/typography.html#typography-standard-styles).
I am trying to apply all of these rules to my application to adjust the layout of a row in my listing however I do not know how to space out the text boxes in my row.
I have 3 lines of text to display in each of my row
Heading line one - size 14sp
Details line two - size 12sp
Details line three - size 12sp
How do I ensure that the baseline of the text in each of these text boxes align with the grid? I can not align the bottom of the text box as this is not the base line, the bottom of the text box is the baseline + descent isn't it?
I need to space out the 3 lines of text as evenly as possible but ensure their baselines align to the grid.
I don't believe I can simply use padding/margins as this will not ensure the baselines of each of the lines of text align with the grid.
Futhermore when I do all this calculations I need to ensure that the row height is 72dp (for an mdpi device with normal font size specified).
Finally how would i specify the leading? As from what I understand this is the space from the baseline of the text of the row above to the top of the highest text in the bottom row. Again i cant use padding/margin as this is not from the baseline.
Edit: The Material Specification for lists have a little more information on how the Tiles in the list should appear when you have 3 lines of text. http://www.google.co.uk/design/spec/components/lists.html#lists-keylines
But it still does not indicate how the 3 lines of text are actually placed vertically so that their baselines align on the 4dp grid.

Update
Based on my answer I made a small library. It is better tailored for real world use than this answer, so please feel free to check it out.
Instead of simply ensuring 4dp alignment, it allows to set leading on all lines and the first line separately, and the last line descent:
Previous reply, left for historical purposes (large screenshot removed)
OK, this is pretty late, but I figured out how to do it in the least possible hackish way.
This method will land an existing view on a baseline grid, given that its upper bound is already on the baseline grid. Input parameters are the step of the grid (4dp in pixels, probably read from resources) and desired leading of the line (e.g. 20dp in pixels).
Note that this won't get you the exact heights that you need, but it will make TextView's size deterministic (i.e. height multiple of step with baseline being on the grid), making it much easier to layout as you need with the use of integer margins.
private static void setLeading(TextView view, int step, int leading) {
// This is to make the behavior more deterministic: remove extra top/bottom padding
view.setIncludeFontPadding(false);
// Get font metrics and calculate required inter-line extra
Paint.FontMetricsInt metrics = view.getPaint().getFontMetricsInt();
final int extra = leading - metrics.descent + metrics.ascent;
view.setLineSpacing(extra, 1);
// Determine minimum required top extra so that the view lands on the grid
final int alignTopExtra = (step + metrics.ascent % step) % step;
// Determine minimum required bottom extra so that view bounds are aligned with the grid
final int alignBottomExtra = (step - metrics.descent % step) % step;
view.setPadding(view.getPaddingLeft(), view.getPaddingTop() + alignTopExtra, view.getPaddingRight(), view.getPaddingBottom() + alignBottomExtra);
}

You can use a helper view of a specific height to baseline the textviews to this height.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<com.example.baselinetest.BaseLineHelperView
android:id="#+id/v_baselinehelper"
android:layout_width="match_parent"
android:layout_height="30dp"
android:background="#FF0000" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBaseline="#id/v_baselinehelper"
android:text="#string/hello_world" />
</RelativeLayout>
Use this helper view
public class BaseLineHelperView extends View {
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public BaseLineHelperView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public BaseLineHelperView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
public BaseLineHelperView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public BaseLineHelperView(Context context) {
super(context);
}
#Override
#ExportedProperty(category = "layout")
public int getBaseline() {
return getHeight();
}
}

You should refer to this class in order to align text in the TextView to 4dp baseline grid:
https://github.com/nickbutcher/plaid/blob/master/app/src/main/java/io/plaidapp/ui/widget/BaselineGridTextView.java

You can use android:lineSpacingExtra and android:lineSpacingMultiplier in your XML file for line spacing. To have proper layout of single row of ListView it may help:-
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="88dp"
android:background="#fff"
android:orientation="horizontal"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingLeft="16dp" >
<ImageView
android:id="#+id/imageView1"
android:layout_width="45dp"
android:layout_height="match_parent"
android:background="#000"
android:padding="16dp" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:paddingRight="72dp"
android:paddingLeft="16dp"
android:orientation="vertical" >
<TextView
android:id="#+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
android:textColor="#000"
android:textSize="16sp" />
<TextView
android:id="#+id/textView2"
android:layout_weight="1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="14sp"
android:text="asdjkfah asdfakj sdfjshd sdgf dfg " />
</LinearLayout>
</LinearLayout>

Related

Setting view to expand in available space up to a maximum size

I have an app with a horizontal recyclerview that displays a square image with an overlaid handle and button in the bottom corners of the image (controlled with a constraint layout).
As the images displayed are all svg graphics, I'd like each of these images to expand to fill the available vertical space. However on some large screens, I'm finding there is enough space that the images then get blown up to an overly large size and so only one or two are displayed on the screen.
What I would like is for each image in the recycler view to scale to fill the space up to a maximum size of, say, 120dp. I have tried to achieve this with a custom image view that overrides onMeasure:
class ComponentImageView : AppCompatImageView {
constructor(context: Context?) : super(context) {}
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
var size = 0
val width = measuredWidth
val height = measuredHeight
val heightWithoutPadding = height - paddingTop - paddingBottom
// set the dimensions
size = heightWithoutPadding
var dp = (size / resources.displayMetrics.density + 0.5f)
if(dp > 120) {dp = 120f}
val px : Int = (dp * resources.displayMetrics.density + 0.5f).toInt()
setMeasuredDimension(px + paddingLeft + paddingRight, px + paddingTop + paddingBottom)
}
}
And have used it in the xml like this:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content" android:layout_height="match_parent">
<bw.graph.ComponentImageView
android:id="#+id/component_block_image"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<bw.smith.TouchableImageView
android:id="#+id/component_block_handle"
style="#style/ComponentListIcon"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="#id/component_block_image"
app:srcCompat="#drawable/ic_drag_handle_gray_24dp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="#+id/component_block_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.appcompat.widget.AppCompatImageButton
android:id="#+id/component_block_edit"
style="#style/ComponentListIcon"
app:layout_constraintStart_toStartOf="#id/component_block_image"
app:layout_constraintBottom_toBottomOf="#id/component_block_image"
app:srcCompat="#drawable/ic_edit_gray_24dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
While the image ends up being the correct width, the view itself still fills all the vertical height. So the result is that the image is centered in the available vertical height, and the handle and button that are intended to be overlaid appear to be floating away from the image.
Is there a way to achieve what I want? If I just manually set the android:layout_height attribute to the desired dp (e.g. between 48dp and 120dp) it looks good, I'd just like to find a way to set this in xml or programmatically so that it looks good on different screens without me having to worry.
ConstraintLayout supports this out of the box, via the app:layout_constraintHeight_max attribute. Add these to the view you want to have stretch:
android:layout_height="0dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHeight_max="[your value here]"
Note that when the parent is taller than the max value given, the view will be centered (vertically) within the parent, unless you add bias:
app:layout_constraintVertical_bias="[0 - 1]"
A bias value of 0 will fix the view to the top of the screen, while 1 will fix it to the bottom. You can use any fractional value if you want to tweak the positioning (e.g., 0.33 to have it be one third from the top of the screen).
In cases where the view is not limited by the max, this bias value will have no effect.

How can I add padding to a View?

I am trying to make a View as a divider on my application to separate two elements (two Buttons, two TextViews or whatever) and I would like to have a padding on the left and on the right of that View (to move the background some space on the left and on the right).
It allows you to set a padding but the View still continues occupying the full width screen. Here is the code I have:
<View
android:layout_width="wrap_content"
android:layout_height="0.5dp"
android:background="#FFFFFF"
android:paddingLeft="10dp"
android:paddingRight="10dp"/>
How can I set a space on the left and on the right of that View so the divider will be smaller than screen?
Thanks in advance!
use margins instead of padding
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
Finally I got it. As on Android documentation it says: Even though a view can define a padding, it does not provide any support for margins, I am not able to use marginLeft or marginRight properties.
If you try to set directly android:marginLeft="10dp" you will get the following error:
No resource identifier found for attribute 'marginLeft' in package 'android'
Nevertheless, you can use android:layout_marginLeft="10dp" and android:layout_marginRight="10dp" to get the desired result.
The final View xml will be like this:
<View
android:layout_width="wrap_content"
android:layout_height="0.5dp"
android:background="#FFFFFF"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"/>
Padding takes care of the inner space. Therefore you would use padding to increase the space between the outline of the view and the elements inside it, whether they are buttons, textviews etc.
Margin takes care of the outer space. You would use this to increase the space between the outline of the view and the element which contains it. Hence what you need. To implement this in a view use the following:
private void setMargins (View view, int left, int top, int right, int bottom) {
if (view.getLayoutParams() instanceof ViewGroup.MarginLayoutParams) {
ViewGroup.MarginLayoutParams p = (ViewGroup.MarginLayoutParams) view.getLayoutParams();
p.setMargins(left, top, right, bottom);
view.requestLayout();
}
}
Hope this helps :)

increase the length of layout with increase in size of text(multi-line)

I want to increase the height of green line with increase in size of aa length of aa(suppose aa is of multiline around 7 line, then the green line should automatically gets increased),
i had tried my effort but failed, and now i have no more idea to achieve the above mentioned, following is my xml for the above mentioned layout,
//My root layout, which/who contain all the two child layouts(relative and linear layout)
<RelativeLayout android:layout_width="match_parent"
android:layout_height="wrap_content">
//Layout for image and line, here i want to increase the height of "profile_view_line(green line)" with increase in size of text "profile_tv_descriptionName"
<RelativeLayout android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="#+id/profile_layout_relDescription">
//This is the imageview,whose background is tranparent, so if i remove "layout below " property from "profile_view_line", then "profile_view_line"
//appear behind imageview(profile_img_description),which must/should not happen, but here i fail as well (as i don't want to appear this behind imageview)
<ImageView android:id="#+id/profile_img_description"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/description_icon"
android:layout_alignParentLeft="true"/>
//This is the line to be expanded with increase in size of "profile_tv_descriptionName" but with minimum size of 30dp as height
//and must grow with the "profile_tv_descriptionName" if (profile_tv_descriptionName) is multi-line
//i can't set property depending on "profile_tv_descriptionName"
as it is in another viewgroup(relativeayout), due to which i can't set property on this one
<View android:layout_below="#id/profile_img_description"
android:layout_width="#dimen/1dp"
android:id="#+id/profile_view_line"
android:layout_height="#dimen/30dp"
android:background="#color/colorGreen"
android:layout_centerHorizontal="true" />
</RelativeLayout>
//Layout for description title and description text "aa" which exapnds as expected, here the dependincy is based on above maintained relative layout "profile_layout_relDescription"
<LinearLayout android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_toRightOf="#id/profile_layout_relDescription"
android:layout_alignBottom="#id/profile_layout_relDescription">
//This is the title, useless in this case but need for appearance
<TextView
android:id="#+id/profile_tv_description"
android:text="#string/profile_description"
android:textSize="#dimen/8dp"
android:gravity="left"/>
//this is the dependency factor which can increase and according to which "profile_view_line" green line must expand but i can't do
//that because it is in another viewgroup(linearlayout), due to which i can't set property on this one
<TextView
android:layout_height="wrap_content"
android:id="#+id/profile_tv_descriptionName"
android:text="Vikram"
android:textSize="#dimen/13dp"
android:gravity="left"/>
</LinearLayout>
</RelativeLayout>
Try a layout like this:
<LinearLayout, horizontal>
<LinearLayout, vertical, width wrap_content, height match_parent, gravity center_horizontal>
<ImageView set width, set height>
<View, greeen line, height 0, weight 1>
</LinearLayout>
<LinearLayout, vertical, width 0, height wrap_content, weight 1>
<TextView title, wrap_content>
<TextView description, wrap_content>
</LinearLayout>
</LinearLayout>
Explanation:
You need 4 things in a square:
AB
CD
where A is the image, B is the title, C is the line, and D is the text.
B and D need to be aligned vertically with each other, and next to A and C, so that says to me that each of those pairs should be in a vertical linear layout, and then those two should be aligned in a horizontal one to line them up.
A and C together have the width that A has, but the other two need to take up the remaining space. That means width of 0, and weight 1.
Now we have:
A B----------------------
C D----------------------
Now we need to deal with height. A has a fixed height. B and D have a fixed height, defined by the text in them. That leaves C as having a variable height, taking up the rest of the space. So we say that AC has height matching the parent, which is then defined by the height of BD. Then C has a height 0 and weight 1, making it fill all the remaining height. This gives us:
A B----------------------
C D----------------------
- -----------------------

Android and Layouts

I need to locate text on view:
text 'Some more text' should be located in bottom|center_horizontal
text 'Short text' should be located on with align right, but about 10% from the top of the screen
text 'x.x.x.x' should be aligned to the center of the screen (right/bottom align of the 1st quater)
text 'Some long text ..' should be aligned to the top/left of the 3-rd quater of the screen, but it should cross the center_horizontal of the screen.
Here a couple quick guidelines:
Android Layouts tend to be much more deeply nested than you would normally expect. You often end up with "empty" layouts that just take up space so that other elements lay out correctly.
RelativeLayout is your friend whenever you are aligning text to a particular edge.
Use the padding settings to put text "a little away from" an edge.
Gravity aligns the text within that TextView or button.
Looking again I your diagram, I reproduced it this way:
Start with a relative layout ('fill_content') that takes up the entire screen.
Put in the "short text" and "some more text" by anchoring to the top and bottom.
Put a zero-width item with the property "centerInParent" for a point in the middle
of the screen.
Put the remaining to items above and aligned with that centerpoint.
Unfortunately, nothing in step 4 worked correctly. Nothing like "layout_below" worked when the referenced item was a centerInParent item. with relative layouts to step 3. Turns out it had to do with failing to fill_content on the top level. Yes, the layouts are tricky, and I wish there was a debugger for them.
Here's the correct version:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/r1"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView
android:id="#+id/short_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Short Text"
android:gravity="right"
android:layout_marginTop="30dip"
android:layout_alignParentTop="true" />
<TextView
android:id="#+id/more_text"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:text="Some More Text"
android:gravity="center"
android:layout_alignParentBottom="true" />
<TextView android:id="#+id/centerpoint"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:width="0dip"
android:height="0dip"
/>
<TextView android:id="#+id/run_fox"
android:text="Run, fox, run!"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_above="#id/centerpoint"
android:layout_toLeftOf="#id/centerpoint"
/>
<TextView
android:layout_below="#id/centerpoint"
android:text="The quick brown fox jumped over the lazy dog, who had been a frog, and then got features and ran slowly."
android:layout_alignRight="#id/centerpoint"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
/>
</RelativeLayout>
If I understand correctly you want to simply put a serious of strings on the text view. You can do this in XML. A handy GUI tool for this would be DroidDraw You can run it in a browser or on your Desktop. I find it handy for some GUI things. Other than that you can Draw a view, something like this...
class DrawOnTop extends View {
public DrawOnTop(Context context) {
super(context);
// TODO Auto-generated constructor stub
}
#Override
public void onDraw(Canvas canvas) {
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL);
paint.setColor(Color.RED);
paint.setTextSize(15);
Paint paint2 = new Paint();
canvas.drawText("Test", 30, 30, paint);
}
}
In the above example I've defined a new paint. This is to set the properties of the text. I've given it a red colour and text size 15. You can add more attributes too. I've also drawn a string "Test". It is at coordinates (30,30) these are the x and y coordinates where I want Java to draw it. It's then using paint's attributes.
EDIT: this page may also be of some help http://developer.android.com/reference/android/graphics/Canvas.html

How to align the text to top of TextView?

How to align the text to top of a TextView?
Equivalent Android API for Swings setInsets()?
that is top of text should start be in (0,0) of TextView
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<TextView
android:id="#+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView"
android:textSize="42sp"
android:paddingTop="0px"
android:gravity="top">
</TextView>
</LinearLayout>
I have used above snippet, however still output is not as expected
Any ideas?
So the space at the top of the TextView is padding used for characters outside the English language such as accents. To remove this space you can either set the android:includeFontPadding attribute to false in your XML or you can do it programmatically with the function setIncludeFontPadding(false).
Look at the SDK documentation for TextView if this is still unclear.
EDITED ANSWER
If setting the android:includeFontPadding attribute does not accomplish what you're trying to do, the other solution is to override the onDraw(Canvas canvas) method of the TextView that you're using so that it eliminates the additional top padding that Android adds to every TextView. After writing my original answer, I noticed that for some reason TextView includes extra padding in addition to the font padding. Removing the font padding as well as this additional padding perfectly aligns the text to the top of the TextView. Look at the code snippet below.
public class TopAlignedTextView extends TextView {
// Default constructor when inflating from XML file
public TopAlignedTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
// Default constructor override
public TopAlignedTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
setIncludeFontPadding(false); //remove the font padding
setGravity(getGravity() | Gravity.TOP); //make sure that the gravity is set to the top
}
/*This is where the magic happens*/
#Override
protected void onDraw(Canvas canvas){
TextPaint textPaint = getPaint();
textPaint.setColor(getCurrentTextColor());
textPaint.drawableState = getDrawableState();
canvas.save();
//converts 5dip into pixels
int additionalPadding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getContext().getResources().getDisplayMetrics());
//subtracts the additional padding from the top of the canvas that textview draws to in order to align it with the top.
canvas.translate(0, -additionalPadding);
if(getLayout() != null)
getLayout().draw(canvas);
canvas.restore();
}
}
In your .xml, write that:
android:gravity="center|top"
The first "center" align your line horizontally in the center and the next "top" align vertically up.
The actual font itself has extra whitespace on the top and bottom I believe.
Usually what I do is give the view negative margins. The issue is that this looks bad when devices come with different fonts.
android:layout_marginTop="-5dp"
android:layout_marginBottom="-5dp"
You will have to adjust based on the text size, bigger text sizes require more negative padding.
Possible, you do not need make your view based on TextView. Try to extend View and write your text in onDraw().
Look at here: https://stackoverflow.com/a/32081250/1263771
I think your snippet is right. I guess you are bothered by some pixels between top of the letter "T" and top edge of the TextView? This space is required to render some letters that are not present in English alphabet.
Check that links to get idea:
http://unicode.org/charts/PDF/U0400.pdf
http://en.wikipedia.org/wiki/Typeface
Im not sure if i understand what you are meaning, but the text inside the textview is going to be align to the top if you use gravity=top on the text-view.
If you put a backgrund color on you textview you might see better what is happening...
I guess you problem is that the textview is centered since you use fill_parent on both height and width on the parent LinearLayout. The textview seems to be the only child so try putting gravity=top on the LinearLayout...
Thats the only thing i could think of...

Categories

Resources