How to make textView wrap its multiline content exactly? - android

How can I make the textview wrap such text exactly ?
android:width attribute is not a solution, because the text is dynamic.
Desired behaviour
|Adcs |
|adscfd|
Current behavour:
|Adcs |
|adscfd |
Hereis the code (styles of TextViews only define things like textColor, textSize, textStyle).
<TextView
android:id="#+id/text_title_holder"
style="#style/TextBold.Black.Title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:maxWidth="100dp"
android:maxLines="2"
android:text="Adcs adscfd"
android:gravity="left"
android:visibility="visible" />
The topic wrap_content width on mutiline TextView has no good answer.

I have faced this problem and didn't find the solution in internet. I did this trick by creating the new component TightTextView that remeasures the given text in case you have specified the maxWidth of the component and the width of Layout (of text) is less that the measured width of the view.
package com.client.android.app.views;
import android.content.Context;
import android.text.Layout;
import android.util.AttributeSet;
import android.widget.TextView;
/**
* Tightly wraps the text when setting the maxWidth.
* #author sky
*/
public class TightTextView extends TextView {
private boolean hasMaxWidth;
public TightTextView(Context context) {
this(context, null, 0);
}
public TightTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TightTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (hasMaxWidth) {
int specModeW = MeasureSpec.getMode(widthMeasureSpec);
if (specModeW != MeasureSpec.EXACTLY) {
Layout layout = getLayout();
int linesCount = layout.getLineCount();
if (linesCount > 1) {
float textRealMaxWidth = 0;
for (int n = 0; n < linesCount; ++n) {
textRealMaxWidth = Math.max(textRealMaxWidth, layout.getLineWidth(n));
}
int w = Math.round(textRealMaxWidth);
if (w < getMeasuredWidth()) {
super.onMeasure(MeasureSpec.makeMeasureSpec(w, MeasureSpec.AT_MOST),
heightMeasureSpec);
}
}
}
}
}
#Override
public void setMaxWidth(int maxpixels) {
super.setMaxWidth(maxpixels);
hasMaxWidth = true;
}
#Override
public void setMaxEms(int maxems) {
super.setMaxEms(maxems);
hasMaxWidth = true;
}
}
!!! Just did port it to older android APIs, cuz getMaxWidth() is only available since API level 16.

This question is a little old now but I too had this problem where I wanted green text in a black box over a mapView and got around it by putting my textView in a RelativeLayout container. I then used padding to set the border size. The textView now hugs the text nicely.
My outline in eclipse looks like this.
RelativeLayout
mapview
LinearLayout
RelativeLayout
textView1 << this is the box I want to hug the text
imageView1
RelativeLayout
etc....
Hope this helps.

Related

Autosizing TextViews in RecyclerView causes text size to decrease

I am trying to use Autosizing TextViews in a RecyclerView, but when I scroll a few times the text gets so small that it's obviously not working properly.
Example of my TextView:
<android.support.v7.widget.AppCompatTextView
android:id="#+id/textview_unit_title"
android:layout_width="#dimen/tile_image_size"
android:layout_height="wrap_content"
android:maxLines="2"
android:textSize="#dimen/medium_size"
android:textColor="#color/color_text"
android:paddingTop="#dimen/padding_title"
android:layout_marginRight="2dp"
android:layout_marginEnd="2dp"
app:autoSizeMaxTextSize="#dimen/style_medium"
app:autoSizeTextType="uniform"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintLeft_toRightOf="#id/imageview_unit_icon"
app:layout_constraintTop_toTopOf="parent"/>
Should I update this scaling somewhere else programmatically or is there another solution?
The issue I've seen with this is that setting your view height to be wrap_content allows the text size to get smaller, but the text will never get bigger again. This is why the documentation recommends to not use wrap_content for the view size. However, I've found that if you turn off the auto-resizing, set the text size to whatever the max is, then re-enable auto-resizing, the text size resets to the largest size and scales down as necessary.
So my view in XML would look like:
<android.support.v7.widget.AppCompatTextView
android:id="#+id/text_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:textAllCaps="true"
android:textColor="#android:color/white"
android:textSize="42sp"
app:autoSizeMinTextSize="26dp"
app:autoSizeMaxTextSize="42dp"
app:autoSizeTextType="none"/>
Then in my ViewHolder when I bind my text to the view:
TextView title = view.findViewById(R.id.text_title);
String titleValue = "Some Title Value";
// Turn off auto-sizing text.
TextViewCompat.setAutoSizeTextTypeWithDefaults(title,
TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
// Bump text size back up to the max value.
title.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 42);
// Set your text as normal.
title.setText(titleValue);
// Post a runnable to re-enable auto-sizing text so that it occurs
// after the view is laid out and measured at max text size.
title.post(new Runnable() {
#Override
public void run() {
TextViewCompat
.setAutoSizeTextTypeUniformWithConfiguration(title,
26, 42, 1, TypedValue.COMPLEX_UNIT_DIP);
}
});
Autosizing TextViews
Android 8.0 (API level 26) allows you to instruct a TextView to let the text size expand or contract automatically to fill its layout based on the TextView's characteristics and boundaries.
Note: If you set autosizing in an XML file, it is not recommended to
use the value "wrap_content" for the layout_width or layout_height
attributes of a TextView. It may produce unexpected results.
You should bound height
android:layout_height="30dp"
Pavel Haluza's answer's approach was great. However, it didn't work, probably because he missed a line setTextSize(TypedValue.COMPLEX_UNIT_PX, maxTextSize);.
Here is my updated version:
public class MyTextView extends AppCompatTextView {
private int minTextSize;
private int maxTextSize;
private int granularity;
public MyTextView(Context context) {
super(context);
init();
}
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public MyTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
minTextSize = TextViewCompat.getAutoSizeMinTextSize(this);
maxTextSize = TextViewCompat.getAutoSizeMaxTextSize(this);
granularity = Math.max(1, TextViewCompat.getAutoSizeStepGranularity(this));
}
#Override
public void setText(CharSequence text, BufferType type) {
// this method is called on every setText
disableAutoSizing();
setTextSize(TypedValue.COMPLEX_UNIT_PX, maxTextSize);
super.setText(text, type);
post(this::enableAutoSizing); // enable after the view is laid out and measured at max text size
}
private void disableAutoSizing() {
TextViewCompat.setAutoSizeTextTypeWithDefaults(this, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
}
private void enableAutoSizing() {
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(this,
minTextSize, maxTextSize, granularity, TypedValue.COMPLEX_UNIT_PX);
}}
I packaged Michael Celey's answer into a class. The parameters app:autoSizeMinTextSize, app:autoSizeMaxTextSize, app:autoSizeTextType are taken from xml.
public class AutosizingTextView extends AppCompatTextView {
private int minTextSize;
private int maxTextSize;
private int granularity;
public AutosizingTextView(Context context) {
super(context);
init();
}
public AutosizingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public AutosizingTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
minTextSize = TextViewCompat.getAutoSizeMinTextSize(this);
maxTextSize = TextViewCompat.getAutoSizeMaxTextSize(this);
granularity = TextViewCompat.getAutoSizeStepGranularity(this);
}
#Override
public void setText(CharSequence text, BufferType type) {
// this method is called on every setText
disableAutosizing();
super.setText(text, type);
post(this::enableAutosizing); // enable after the view is laid out and measured at max text size
}
private void disableAutosizing() {
TextViewCompat.setAutoSizeTextTypeWithDefaults(this, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
}
private void enableAutosizing() {
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(this,
minTextSize, maxTextSize, granularity, TypedValue.COMPLEX_UNIT_PX);
}
}```
the above solutions didn't work for me so here's mine
public class MyTextView extends AppCompatTextView {
...
#Override
public final void setText(CharSequence text, BufferType type) {
// work around stupid auto size text not *growing* the font size we re binding in a RecyclerView if previous bind caused a small font
int minTextSize = 0, maxTextSize = 0, granularity = 0;
boolean doHack = TextViewCompat.getAutoSizeTextType(this) != TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE;
if (doHack) {
minTextSize = TextViewCompat.getAutoSizeMinTextSize(this);
maxTextSize = TextViewCompat.getAutoSizeMaxTextSize(this);
if (minTextSize <= 0 || maxTextSize <= minTextSize) { // better than validateAndSetAutoSizeTextTypeUniformConfiguration crashing
if (BuildConfig.DEBUG)
throw new AssertionError("fix ya layout");
doHack = false;
} else {
granularity = TextViewCompat.getAutoSizeStepGranularity(this);
if (granularity < 0)
granularity = 1; // need this else setAutoSizeTextTypeUniformWithConfiguration barfs. TextView.UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = 1.
// make the TextView have 0 size so setAutoSizeTextTypeUniformWithConfiguration won't do calculations until after a layout pass using maxSize
TextViewCompat.setAutoSizeTextTypeWithDefaults(this, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
setTextSize(TypedValue.COMPLEX_UNIT_PX, maxTextSize);
measure(MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY));
setRight(getLeft());
setBottom(getTop());
requestLayout();
}
}
super.setText(text, type);
if (doHack)
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(this, minTextSize, maxTextSize, granularity, TypedValue.COMPLEX_UNIT_PX);
}
...
}
Just .setText("") before resetting the text size you want. That ensures that you are not setting the textsize and then immediately autoresizing using the previous text value in the TextView. Like this:
TextView wordWordTextView = getView().findViewById(R.id.wordWordTextView);
wordWordTextView.setAlpha(0.0f);
wordWordTextView.setText("");
wordWordTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 50);
wordWordTextView.setText(wordStr);
wordWordTextView.animate().alpha(1.0f).setDuration(250);
I only just set android:maxLines="1" in xml file, then code in bindViewHolder
TextViewCompat.setAutoSizeTextTypeWithDefaults(binding.tvResultExplain, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE);
binding.tvResultExplain.setText("");
TextViewCompat.setAutoSizeTextTypeUniformWithConfiguration(binding.tvResultExplain, 12,
16, 1, TypedValue.COMPLEX_UNIT_SP);
binding.tvResultExplain.setText(item.getStatusExplain());
It works for me, maybe it can resolve your situation as well.

How to reduce TextView line spacing

I am trying to reduce the line spacing in a TextView by setting a negative 'add' to TextView.setLineSpacing(). It works well except that the bottom line get truncated.
Main layout
<TextView
android:id="#+id/text_view"
android:padding="dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
tools:context=".MainActivity" />
Main activity: (notice the
package com.font_test;
import android.app.Activity;
import android.graphics.Typeface;
import android.os.Bundle;
import android.widget.TextView;
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final Typeface typeface = Typeface.createFromAsset(getAssets(), "fonts/custom_fonts.ttf");
final TextView tv = (TextView) findViewById(R.id.text_view);
tv.setTypeface(typeface);
tv.setTextSize(60);
tv.setLineSpacing(-30f, 1f); // *** -30 to reduce line spacing
tv.setBackgroundColor(0x280000ff);
tv.setText("gggkiiikkk" + "\n" + "gikgikgik" + "\n" + "kigkigkig");
}
}
This results in truncation at the bottom of the view (notice the 'g' at the bottom line):
It seems that the problem is related to incorrect layout measurement. If I set the TextView to
android:layout_height="fill_parent"
It does render properly:
Any idea how to fix it? I don't mind to have ugly workarounds if it helps. I also have access to FontForge and I can modify the font file if needed.
littleFluffyKittys answer is good but it didn't work on some devices if the linespacing was set through xml
I calculate the additional height needed by comparing the original height of the font with the height the textview calculates for a line.
If the line height is smaller than the height of the font the diffrence is added one time.
This works down to at least API 10 propably lower (just not tested any lower)
public class ReducedLineSpacingTextView extends TextView {
public ReducedLineSpacingTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ReducedLineSpacingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReducedLineSpacingTextView(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int truncatedHeight = getPaint().getFontMetricsInt(null) - getLineHeight();
if (truncatedHeight > 0) {
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight() + truncatedHeight);
}
}
}
I ran into this same problem but when I was trying to use a spacing multiplier less than 1.
I created a subclass of TextView that fixes the truncation of the last line automatically and doesn't require you set a known/fixed spacing at the bottom.
Just use this class and you can use it normally, you don't need to apply any additional spacing or fixes.
public class ReducedLineSpacingTextView extends TextView {
private boolean mNegativeLineSpacing = false;
public ReducedLineSpacingTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public ReducedLineSpacingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ReducedLineSpacingTextView(Context context) {
super(context);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (mNegativeLineSpacing) { // If you are only supporting Api Level 16 and up, you could use the getLineSpacingExtra() and getLineSpacingMultiplier() methods here to check for a less than 1 spacing instead.
Layout layout = getLayout();
int truncatedHeight = layout.getLineDescent(layout.getLineCount()-1);
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight() + truncatedHeight);
}
}
#Override
public void setLineSpacing(float add, float mult) {
mNegativeLineSpacing = add < 0 || mult < 1;
super.setLineSpacing(add, mult);
}
}
Nice!
That'll make the job but it's never a good idea to put constants values wherever we have variables. You can use the lineSpacing values to add them to the onMeasure method in a dinamyc way.
Note that this values are always available through "getLineSpacingExtra()" and "getLineSpacingMultiplier()". Or even easier you can get the value of both summed up: "getLineHeight()".
Although it feels for me that this value should be included in the onMeasure method, you can always measure the exact height you need and then make a simple check:
final int measuredHeight = getMeasuredHeight();
if (measuredHeight < neededHeight) {
setMeasuredDimension(getMeasuredWidth, neededHeight);
}
One last thing, you don't need to pass the context along in a separated attribute. If you have a look to your constructors, the context is already there. If you needed along the code of your component you can just use "getContext()".
Hope it helps.
Use this to reduce line spacing in text view
**
android:lineSpacingMultiplier="0.8"
**
If padding doesn't work, margin should do the job. If you still have problem you can always apply the line spacing value to the onMeasure method of the view. You'll have to create a custom component for that and extend onMeasure.
Just add paddingBottom to declaration of your TextView xml, pick a value which produces a good result. And consequently set values for other paddings (top, let and right). This should fix your problem
This is what I did based on Jose's answer here and it seems to work. I am not very familiar with the intricate of the layout mechanism. Is this code safe? Any problem with it?
Layout:
<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.font_test.MyTextView
android:id="#+id/text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
tools:context=".MainActivity" />
</RelativeLayout>
Added custom TextView that extends the vertical height by N pixels:
package com.font_test;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.TextView;
public class MyTextView extends TextView {
public MyTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
// TODO: provide an API to set extra bottom pixels (50 in this example)
setMeasuredDimension(getMeasuredWidth(), getMeasuredHeight() + 50);
}
}
Result text view rendering without truncation at the bottom:

Android - Why is the TextView shown as having a height?

I've got an odd problem here. Basically I have a TextView with no default set text. I would've expected it to have a height of 0 since it hsas no content but there seems to be a gap between the elements above and below it. If I set the height to 0 in the XML and then try and change it through Java code then it does not reset the height.
How do I set the height to be 0 if the content is blank but then allow it to be changed programmatically?
Here is the code that I have:
<TextView
android:gravity="center_horizontal|center_vertical"
android:id="#+id/connectionStatus"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:textSize="18px"
android:textStyle="bold">
</TextView>
and the Java code is this:
private void getConnectionStatus()
{
if (hasConnection() == true)
{
//do something
}
else
{
connectionStatus.setHeight(48);
connectionStatus.setText("No Internet Access");
}
}
Use visibility "gone" inside of the xml layout. Then in the Java code call connectionStatus.setVisibility(View.VISIBLE);
Components may still display themselves even if they don't have content. For example, the may display a border or their viewable area. In order to make it not show up at all you need to use setVisibility(View.GONE).
I've often wondered if this behaviour is intuitive. If you want a TextView that has no height when the text is empty you can make one:
import android.content.Context;
import android.support.annotation.Nullable;
import android.text.TextUtils;
import android.util.AttributeSet;
public class NoHeightWhenEmptyTextView extends android.support.v7.widget.AppCompatTextView {
public NoHeightWhenEmptyTextView(Context context) {
super(context);
}
public NoHeightWhenEmptyTextView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public NoHeightWhenEmptyTextView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int newHeightMeasureSpec = heightMeasureSpec;
if (TextUtils.isEmpty(getText())) {
newHeightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, newHeightMeasureSpec);
}
#Override
public void setText(CharSequence text, BufferType type) {
super.setText(text, type);
// ConstraintLayout totally ignores the new measured height after non-empty text is set.
// A second call to requestLayout appears to work around the problem :(
requestLayout();
}
}

How to remove the top and bottom space on textview of Android

When I include the below XML to layout file, I can see the below image. If you see it, you could realize that the TextView has top and bottom space.
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="E1"
android:background="#ff00ff00"/>
I wish to remove the space. How to remove it? What is it called?
If anyone has clue.. please let me know. Thanks in advance.
Try android:includeFontPadding="false" to see if it helps. In my experience that will help a little bit, but there's no way of reducing the TextView dimensions to the exact pixel-perfect text size.
The only alternative, which may or may not give better results, is to cheat a bit and hard-wire the dimensions to match the text size, e.g. "24sp" instead of "wrap_content" for the height.
I had the same problem. Attribute android:includeFontPadding="false" does not work for me. I've solved this problem in this way:
public class TextViewWithoutPaddings extends TextView {
private final Paint mPaint = new Paint();
private final Rect mBounds = new Rect();
public TextViewWithoutPaddings(Context context) {
super(context);
}
public TextViewWithoutPaddings(Context context, AttributeSet attrs) {
super(context, attrs);
}
public TextViewWithoutPaddings(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onDraw(#NonNull Canvas canvas) {
final String text = calculateTextParams();
final int left = mBounds.left;
final int bottom = mBounds.bottom;
mBounds.offset(-mBounds.left, -mBounds.top);
mPaint.setAntiAlias(true);
mPaint.setColor(getCurrentTextColor());
canvas.drawText(text, -left, mBounds.bottom - bottom, mPaint);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
calculateTextParams();
setMeasuredDimension(mBounds.width() + 1, -mBounds.top + 1);
}
private String calculateTextParams() {
final String text = getText().toString();
final int textLength = text.length();
mPaint.setTextSize(getTextSize());
mPaint.getTextBounds(text, 0, textLength, mBounds);
if (textLength == 0) {
mBounds.right = mBounds.left;
}
return text;
}
}
android:includeFontPadding="false" is pretty good but it does not get it precisely. sometimes you want border line accuracy so you can figure it out yourself by applying negative margins:
try setting your bottom and top margins to a negative value.
something like this:
android:layout_marginTop="-5dp"
android:layout_marginBottom="-5dp"
adjust the values accordingly.
This is the code that saved our day. It was adapted using mono C# code from maksimko:
public class TopAlignedTextView extends TextView {
public TopAlignedTextView(Context context) {
super(context);
}
/*This is where the magic happens*/
#Override
protected void onDraw(Canvas canvas){
float offset = getTextSize() - getLineHeight();
canvas.translate(0, offset);
super.onDraw(canvas);
}
}
Still had to play around with textView.setIncludeFontPadding(false) because we were aligning TextViews with different font sizes.
I faced the same problem.
Here's a good answer: How to align the text to top of TextView?
But code is little unfinished and don't support all font sizes. Change the line
int additionalPadding = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 5, getContext().getResources().getDisplayMetrics());
to
int additionalPadding = getTextSize() - getLineHeight();
Complete C# code (mono) removes top offset:
public class TextControl : TextView {
public TextControl (Context context) : base (context)
{
SetIncludeFontPadding (false);
Gravity = GravityFlags.Top;
}
protected override void OnDraw (Android.Graphics.Canvas canvas)
{
if (base.Layout == null)
return;
Paint.Color = new Android.Graphics.Color (CurrentTextColor);
Paint.DrawableState = GetDrawableState ();
canvas.Save ();
var offset = TextSize - LineHeight;
canvas.Translate (0, offset);
base.Layout.Draw (canvas);
canvas.Restore ();
}
}
Just wanted to add to DynamicMind's answer that the reason why you see spacing around your TextViews is padding in 9-patch backgrounds they use by default.
9-patch technology allows you to specify a content area which is, effectively, padding. That padding is used unless you set the view's padding explicitly. E.g., when you programmatically set a 9-patch background to a view which had paddings set, they are overridden. And vise-versa, if you set paddings they override what was set by 9-patch background.
Unfortunately, in the XML layout it's not possible to determine the order of these operations. I think just removing the background from your TextViews would help:
android:background="#null"
public class TopAlignedTextView extends TextView {
public TopAlignedTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public TopAlignedTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs);
setIncludeFontPadding(false); //remove the font padding
setGravity(getGravity() | Gravity.TOP);
}
#Override
protected void onDraw(Canvas canvas) {
TextPaint textPaint = getPaint();
textPaint.setColor(getCurrentTextColor());
textPaint.drawableState = getDrawableState();
canvas.save();
//remove extra font padding
int yOffset = getHeight() - getBaseline();
canvas.translate(0, - yOffset / 2);
if (getLayout() != null) {
getLayout().draw(canvas);
}
canvas.restore();
}
}
Modified this answer a little bit to use kotlin class and extend AppCompatTextView, trimming vertical padding.
It allows setting android:fontFamily. Method calculateTextParams() moved from onDraw() for performance. Not tested for multiple lines of text:
import android.content.Context
import android.graphics.Canvas
import android.graphics.Rect
import android.util.AttributeSet
import androidx.appcompat.widget.AppCompatTextView
class NoPaddingTextView : AppCompatTextView
{
private val boundsRect = Rect()
private val textParams = calculateTextParams()
constructor(context : Context?)
: super(context)
constructor(context : Context?, attrs : AttributeSet?)
: super(context, attrs)
constructor(context : Context?, attrs : AttributeSet?, defStyleAttr : Int)
: super(context, attrs, defStyleAttr)
override fun onDraw(canvas : Canvas)
{
with(boundsRect) {
paint.isAntiAlias = true
paint.color = currentTextColor
canvas.drawText(textParams,
-left.toFloat(),
(-top - bottom).toFloat(),
paint)
}
}
override fun onMeasure(widthMeasureSpec : Int, heightMeasureSpec : Int)
{
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
calculateTextParams()
setMeasuredDimension(boundsRect.width() + 1, -boundsRect.top + 1)
}
private fun calculateTextParams() : String
{
return text.toString()
.also {text ->
text.length.let {textLength ->
paint.textSize = textSize
paint.getTextBounds(text, 0, textLength, boundsRect)
if(textLength == 0) boundsRect.right = boundsRect.left
}
}
}
}
Have you defined a layout margin?
For example:
android:layout_marginTop="5dp"
Otherwise, if your text view is wrapped inside a LinearLayout or other container, then that cold have either padding or a margin too.
android:background="#android:drawable/editbox_background"
use it according to you change it that you want editbox_background.
because android provide some build in background like above code choose according to your requirement.
May be it is help full to you.
Inside a LinearLayout the default padding might be an issue. Try setting it to 0dp. It worked for me.
The answer of TopAlignedTextView code:TopAlignedTextView#GitHub
use it by layout:
<com.github.captain_miao.view.TopAlignedTextView
android:id="#+id/text_a"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="#dimen/activity_horizontal_margin"
android:text="#string/text_demo_a"
/>
My way for fixing this is pretty hacky, but I managed to get the text to sit where I wanted by setting the height of the text view as static and fiddling with it until it just barely fit the text. In my case, the font style I was using had a height of 64sp so I set the height of my textview to 50sp and it worked okay. I also had to set foreground_gravity to bottom.
android:includeFontPadding="false"

Grid of images inside ScrollView

I'm trying to create a screen with both text and images. I want the images to be laid out like a grid, as shown below, but I want them to have no scroll functionality other that the one provided by the surrounding ScrollView.
An image will best illustrate my question:
<ScrollView>
<LinearLayout>
<ImageView />
<TextView />
<GridView />
<TextView />
</LinearLayout>
</ScrollView>
What is the best way to make show a grid of a varying number of images, where the grid does not have scroll functionality?
Please note that disabling the scroll functionality for the GridView does not work, as this just disables the scrollbars but does not show all items.
Update:
The image below shows what it looks like with scrollbars disabled in the GridView.
Oh boy, yeah, you're gonna have trouble with this one. It drives me nuts that ListViews and GridViews can't be expanded to wrap their children, because we all know that they have more beneficial features in addition to their scrolling and the recycling of their children.
Nonetheless, you can hack around this or create your own layout to suit your needs without too much difficulty. Off the top of my head, I can think of two possibilities:
In my own app I have embedded a ListView within a ScrollView. I have done this by explicitly telling the ListView to be exactly as high as its contents. I do it by changing the layout parameters right inside the ListView.onMeasure() method like so:
public class ExpandableListView extends ListView {
boolean expanded = false;
public ExpandableListView(Context context, AttributeSet attrs, int defaultStyle) {
super(context, attrs, defaultStyle);
}
public boolean isExpanded() {
return expanded;
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// HACK! TAKE THAT ANDROID!
if (isExpanded()) {
// Calculate entire height by providing a very large height hint.
// View.MEASURED_SIZE_MASK represents the largest height possible.
int expandSpec = MeasureSpec.makeMeasureSpec(MEASURED_SIZE_MASK,
MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
}
This works because when you give the ListView a mode of AT_MOST, it creates and measures all of its children for you, right inside the onMeasure method (I discovered this by browsing through the source code). Hopefully GridView behaves the same, but if it doesn't, you can still measure all the contents of the GridView yourself. But it would be easier if you could trick the GridView into doing it for you.
Now, you must keep in mind that this solution would completely disable the view recycling that makes GridView so efficient, and all those ImageViews will be in memory even if they're not visible. Same goes with my next solution.
The other possibility is to ditch the GridView and create your own layout. You could extend either AbsoluteLayout or RelativeLayout. For example, if you extend RelativeLayout, you could place each image LEFT_OF the previous one, keeping track of the width of each image until you run out of room on that row, and then start the next row by placing the first image of the new row BELOW the tallest image of the last row. To get the images horizontally centered or in equally-spaced columns you'll have to go through even more pain. Maybe AbsoluteLayout is better. Either way, kind of a pain.
A GridView with header and footer can be used instead of trying to embed GridView in ScrollView. Header and footer can be anything - texts, images, lists, etc. There is an example of GridView with header and footer: https://github.com/SergeyBurish/HFGridView
You have 2 solutions for this one:
Write your own custom layout. This would be the harder solution (but might be considered the correct one).
Set the real height of your GridView in the code. For example:
RelativeLayout.LayoutParams lp = (RelativeLayout.LayoutParams) myGridView.getLayoutParams();
// lp.setMargins(0, 0, 10, 10); // if you have layout margins, you have to set them too
lp.height = measureRealHeight(...);
myGridView.setLayoutParams(lp);
The measureRealHeight() method should look something like this (hopefully I got it right):
private int measureRealHeight(...)
{
final int screenWidth = getWindowManager().getDefaultDisplay().getWidth();
final double screenDensity = getResources().getDisplayMetrics().density;
final int paddingLeft = (int) (X * screenDensity + 0.5f); // where X is your desired padding
final int paddingRight = ...;
final int horizontalSpacing = (int) (X * screenDensity + 0.5f); // the spacing between your columns
final int verticalSpacing = ...; // the spacing between your rows
final int columnWidth = (int) (X * screenDensity + 0.5f);
final int columnsCount = (screenWidth - paddingLeft - paddingRight + horizontalSpacing - myGridView.getVerticalScrollbarWidth()) / (columnWidth + horizontalSpacing);
final int rowsCount = picsCount / columnsCount + (picsCount % columnsCount == 0 ? 0 : 1);
return columnWidth * rowsCount + verticalSpacing * (rowsCount - 1);
}
The above code should work in Android 1.5+.
Create a non scrollable list view like this:
public class ExpandableListView extends ListView{
public ExpandableListView(Context context) {
super(context);
}
public ExpandableListView(Context context, AttributeSet attrs, int defaultStyle) {
super(context, attrs, defaultStyle);
}
public ExpandableListView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMeasureSpec_custom = MeasureSpec.makeMeasureSpec(
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, heightMeasureSpec_custom);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
}
}
In your layout file create an element like this:
<com.example.ExpandableListView
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
This should work.
I found a way to give the GridView a fixed size inside ScrollView, and enable scrolling it.
To do so, you would have to implement a new class extending GridView and override onTouchEvent() to call requestDisallowInterceptTouchEvent(true).
Thus, the parent view will leave the Grid intercept touch events.
GridViewScrollable.java:
package com.example;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.widget.GridView;
public class GridViewScrollable extends GridView {
public GridViewAdjuntos(Context context) {
super(context);
}
public GridViewAdjuntos(Context context, AttributeSet attrs) {
super(context, attrs);
}
public GridViewAdjuntos(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
#Override
public boolean onTouchEvent(MotionEvent ev){
// Called when a child does not want this parent and its ancestors to intercept touch events.
requestDisallowInterceptTouchEvent(true);
return super.onTouchEvent(ev);
}
}
Add it in your layout with the characteristics you want:
<ScrollView 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"
android:isScrollContainer="true" >
<com.example.GridViewScrollable
android:id="#+id/myGVS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="true"
android:numColumns="auto_fit"
android:columnWidth="100dp"
android:stretchMode="columnWidth" />
</ScrollView>
And just get it in your activity and set the adapter, for example an ArrayAdapter<>:
GridViewScrollable mGridView = (GridViewScrollable) findViewById(R.id.myGVS);
mGridView.setAdapter(new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, new String[]{"one", "two", "three", "four", "five"}));
I hope it helps =)
Try this
public static void setGridViewHeightBasedOnChildren(GridView gridView, int columns) {
ListAdapter listAdapter = gridView.getAdapter();
if (listAdapter == null)
return;
int desiredWidth = View.MeasureSpec.makeMeasureSpec(gridView.getWidth(), View.MeasureSpec.UNSPECIFIED);
int totalHeight = 0;
View view = null;
int rows = listAdapter.getCount() / columns;
if(listAdapter.getCount() % columns> 0){
rows++;
}
for (int i = 0; i < rows; i++) {
view = listAdapter.getView(i, view, gridView);
if (i == 0)
view.setLayoutParams(new ViewGroup.LayoutParams(desiredWidth, LinearLayout.LayoutParams.WRAP_CONTENT));
view.measure(desiredWidth, View.MeasureSpec.UNSPECIFIED);
totalHeight += view.getMeasuredHeight();
}
ViewGroup.LayoutParams params = gridView.getLayoutParams();
params.height = totalHeight + (gridView.getHorizontalSpacing() * rows);
gridView.setLayoutParams(params);
gridView.requestLayout();
}
For GridView with other View inside of the save ScrollView to make it all scroll, go to this link: http://www.londatiga.net/it/programming/android/make-android-listview-gridview-expandable-inside-scrollview/#comment-3967742. It is helpful and have saved my time which I just spend 5 minute with this when I have never know about it.
Update:
From the link I have customize an ExpandedGridView:
public class ExpandedGridView extends GridView {
boolean expanded = false;
public ExpandedGridView(Context context) {
super(context);
}
public ExpandedGridView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public ExpandedGridView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
}
public boolean isExpanded() {
return expanded;
}
#Override
public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// HACK! TAKE THAT ANDROID!
if (isExpanded()) {
// Calculate entire height by providing a very large height hint.
// But do not use the highest 2 bits of this integer; those are
// reserved for the MeasureSpec mode.
int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
super.onMeasure(widthMeasureSpec, expandSpec);
ViewGroup.LayoutParams params = getLayoutParams();
params.height = getMeasuredHeight();
} else {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
}
public void setExpanded(boolean expanded) {
this.expanded = expanded;
}
}
For your xml change from GridView to the ExpandedGridView which have been customized.
<com.your.package.ExpandedGridView
android:id="#+id/home_screen_list_goals"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:numColumns="2" />
Usage:
Call it in your activity. If in fragment use contentView.findViewById(...). Which contentView is your whole layout defined.
ExpandedGridView gridView = (ExpandedGridView) findViewById(R.id.home_screen_list_goals);
//set data into grid view
gridView.setAdapter(YOUR_ADAPTER_OBJECT);
gridView.setExpanded(true);

Categories

Resources