How to change letter spacing in a Textview? - android

How can i change letter spacing in a textview?
Will it help if I have HTML text in it (I cannot use webview in my code).
P.S. I'm using my own typeface in the textview with HTML text.

Since API 21 there is an option set letter spacing. You can call method setLetterSpacing or set it in XML with attribute letterSpacing.

More space:
android:letterSpacing="0.1"
Less space:
android:letterSpacing="-0.07"

check out android:textScaleX
Depending on how much spacing you need, this might help. That's the only thing remotely related to letter-spacing in the TextView.
Edit: please see #JerabekJakub's response below for an updated, better method to do this starting with api 21 (Lollipop)

This answer is based on Pedro's answer but adjusted so it also works if text attribute is already set:
package nl.raakict.android.spc.widget;
import android.content.Context;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ScaleXSpan;
import android.util.AttributeSet;
import android.widget.TextView;
public class LetterSpacingTextView extends TextView {
private float letterSpacing = LetterSpacing.BIGGEST;
private CharSequence originalText = "";
public LetterSpacingTextView(Context context) {
super(context);
}
public LetterSpacingTextView(Context context, AttributeSet attrs){
super(context, attrs);
originalText = super.getText();
applyLetterSpacing();
this.invalidate();
}
public LetterSpacingTextView(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
}
public float getLetterSpacing() {
return letterSpacing;
}
public void setLetterSpacing(float letterSpacing) {
this.letterSpacing = letterSpacing;
applyLetterSpacing();
}
#Override
public void setText(CharSequence text, BufferType type) {
originalText = text;
applyLetterSpacing();
}
#Override
public CharSequence getText() {
return originalText;
}
private void applyLetterSpacing() {
if (this == null || this.originalText == null) return;
StringBuilder builder = new StringBuilder();
for(int i = 0; i < originalText.length(); i++) {
String c = ""+ originalText.charAt(i);
builder.append(c.toLowerCase());
if(i+1 < originalText.length()) {
builder.append("\u00A0");
}
}
SpannableString finalText = new SpannableString(builder.toString());
if(builder.toString().length() > 1) {
for(int i = 1; i < builder.toString().length(); i+=2) {
finalText.setSpan(new ScaleXSpan((letterSpacing+1)/10), i, i+1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
super.setText(finalText, BufferType.SPANNABLE);
}
public class LetterSpacing {
public final static float NORMAL = 0;
public final static float NORMALBIG = (float)0.025;
public final static float BIG = (float)0.05;
public final static float BIGGEST = (float)0.2;
}
}
If you want to use it programatically:
LetterSpacingTextView textView = new LetterSpacingTextView(context);
textView.setSpacing(10); //Or any float. To reset to normal, use 0 or LetterSpacingTextView.Spacing.NORMAL
textView.setText("My text");
//Add the textView in a layout, for instance:
((LinearLayout) findViewById(R.id.myLinearLayout)).addView(textView);

after API >=21 there is inbuild method provided by TextView called setLetterSpacing
check this for more

I built a custom class that extends TextView and solves this problem... Check out my answer here =)

As android doesn't support such a thing, you can do it manually with FontCreator. It has good options for font modifying.
I used this tool to build a custom font, even if it takes some times but you can always use it in your projects.

For embedding HTML text in your textview you can use Html.fromHTML() syntax.
More information you will get from http://developer.android.com/reference/android/text/Html.html#fromHtml%28java.lang.String%29

Related

How make spacing between letters in Android TextView?

I need make design from photoshop layout. There are some fonts on the layouts.
Designer gave me this fonts. But on layout in photoshop, he use spacing between letters. How i can realize this in android textView? I found one solution:
answer on stackoverflow
answer two
but if i make myTextView extends TextView it work wrong. If i adapt for one devise, on the device with biger display, spacing between letters increase is not proportional.
EDIT
public class MyTextView extends TextView {
private float letterSpacing = 0.0f;
private CharSequence originalText = "";
private Typeface typeface;
public MyTextView(Context context) {
this(context, null);
isInEditMode();
}
public MyTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
isInEditMode();
}
public MyTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray attributesArray = getResources().obtainAttributes(attrs, R.styleable.MyTextView);
letterSpacing = attributesArray.getDimension(R.styleable.MyTextView_letterSpacing, 0.0f);
String fontName = attributesArray.getString(R.styleable.MyTextView_fontName);
if(!this.isInEditMode()) {
if (null == fontName) {
typeface = Fonts.getBlockBertholdRegular(context);
} else {
typeface = Fonts.get(context, fontName);
}
super.setTypeface(typeface);
}
originalText = super.getText();
applyLetterSpacing();
this.invalidate();
}
public float getLetterSpacing() {
return letterSpacing;
}
public void setLetterSpacing(float letterSpacing) {
this.letterSpacing = letterSpacing;
applyLetterSpacing();
}
#Override
public void setText(CharSequence text, BufferType type) {
originalText = text;
applyLetterSpacing();
}
#Override
public CharSequence getText() {
return originalText;
}
private void applyLetterSpacing() {
if (this == null || this.originalText == null) return;
StringBuilder builder = new StringBuilder();
for (int i = 0; i < originalText.length(); i++) {
String c = "" + originalText.charAt(i);
builder.append(c.toUpperCase());
if (i + 1 < originalText.length()) {
builder.append("\u00A0");
}
}
SpannableString finalText = new SpannableString(builder.toString());
if (builder.toString().length() > 1) {
for (int i = 1; i < builder.toString().length(); i += 2) {
finalText.setSpan(new ScaleXSpan((letterSpacing + 1) / 10), i, i + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
super.setText(finalText, BufferType.SPANNABLE);
if(!this.isInEditMode()) {
super.setTypeface(typeface);
}
}
}
Try using the new TextView API method setLetterSpacing.
See here
EDIT
You can also create your own font with spaces inside the font itself and apply it to your TextView.
Since API 21 You can use
setLetterSpacing
Documentation can be found here

How to use TextViewEx, (Textjustify) clases in android

I am trying to use TextViewEx, (Textjustify) from here
[link] (https://github.com/bluejamesbond/TextJustify-Android )
to get text justification effect in my project but if I copy and paste the files directly to my project then the files are giving error like some thing (other files) are missing. Also I have searched for how to use TextViewEx but I got is this result below
[link] (TextViewEx, (Textjustify))
In this some one told to import the files to the root folder. What does it mean (the root folder). Also if any one has sample code that uses TextViewEx or any other easy way to justify text in Android or sample code that shows justification of text then plz help me Thanks.
Well I have struggled a lot but could not found any help to solve this problem but I have found another alternative to justify text. Use this class if one having problem with justification of text.
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import android.util.Log;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
public class JustifiedTextView extends WebView {
private String core = "<html><body style='text-align:justify;color:rgba(%s);font-size:%dpx;margin: 0px 0px 0px 0px;'>%s</body></html>";
private String text;
private int textColor;
private int backgroundColor;
private int textSize;
public JustifiedTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public JustifiedTextView(Context context, AttributeSet attrs, int i) {
super(context, attrs, i);
init(attrs);
}
#SuppressWarnings("deprecation")
#SuppressLint("NewApi")
public JustifiedTextView(Context context, AttributeSet attrs, int i,
boolean b) {
super(context, attrs, i, b);
init(attrs);
}
private void init(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.JustifiedTextView);
text = a.getString(R.styleable.JustifiedTextView_text);
if (text == null)
text = "";
textColor = a.getColor(R.styleable.JustifiedTextView_textColor,
Color.BLACK);
backgroundColor = a.getColor(
R.styleable.JustifiedTextView_backgroundColor,
Color.TRANSPARENT);
textSize = a.getInt(R.styleable.JustifiedTextView_textSize, 12);
a.recycle();
this.setWebChromeClient(new WebChromeClient() {
});
reloadData();
}
public void setText(String s) {
if (s == null)
this.text = "";
else
this.text = s;
reloadData();
}
#SuppressLint("NewApi")
private void reloadData() {
if (text != null) {
String data = String
.format(core, toRgba(textColor), textSize, text);
Log.d("test", data);
this.loadDataWithBaseURL(null, data, "text/html", "utf-8", null);
}
// set WebView's background color *after* data was loaded.
super.setBackgroundColor(backgroundColor);
// Hardware rendering breaks background color to work as expected.
// Need to use software renderer in that case.
if (android.os.Build.VERSION.SDK_INT >= 11)
this.setLayerType(WebView.LAYER_TYPE_SOFTWARE, null);
}
public void setTextColor(int hex) {
textColor = hex;
reloadData();
}
public void setBackgroundColor(int hex) {
backgroundColor = hex;
reloadData();
}
public void setTextSize(int textSize) {
this.textSize = textSize;
reloadData();
}
#SuppressLint("DefaultLocale")
private String toRgba(int hex) {
String h = Integer.toHexString(hex);
int a = Integer.parseInt(h.substring(0, 2), 16);
int r = Integer.parseInt(h.substring(2, 4), 16);
int g = Integer.parseInt(h.substring(4, 6), 16);
int b = Integer.parseInt(h.substring(6, 8), 16);
return String.format("%d,%d,%d,%d", r, g, b, a);
}
}
this is the attrib.xml class
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="JustifiedTextView">
<attr name="text" format="string" localization="suggested" />
<attr name="textColor" format="color|reference" />
<attr name="backgroundColor" format="color|reference" />
<attr name="textSize" format="integer" min="1" />
</declare-styleable>
</resources>
In the layout class use this as
<com.example.animationtest.JustifiedTextView
android:id="#+id/tjTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
app:text="Your Text Here" >
</com.example.animationtest.JustifiedTextView>
com.example.animationtest is the package name in your project where you put the file JustifiedTextView.class
In the code behind class access to the control as
JustifiedTextView tjTextView;
tvTextView2.setTextSize(convertToDp(24));
tjTextView.setTextColor(Color.RED);
tjTextView.setTextSize((int) convertFromDp(18));
where convertFromDp is used to get text size according to screen.
public float convertFromDp(int input) {
final float scale = getResources().getDisplayMetrics().density;
return ((input - 0.5f) / scale);
}

Custom TextView - setText() called before constructor

I've got a problem with my CustomTextView. I'm trying to get a custom value from my layout-xml file and use this in my setText() method. Unfortunately the setText() method gets called before the constructor and because of this I can't use the custom value in this method.
Here's my code (broken down to the relevant parts):
CustomTextView.class
public class CustomTextView extends TextView {
private float mHeight;
private final String TAG = "CustomTextView";
private static final Spannable.Factory spannableFactory = Spannable.Factory.getInstance();
public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG, "in CustomTextView constructor");
TypedArray values = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView);
this.mHeight = values.getDimension(R.styleable.CustomTextView_cHeight, 20);
}
#Override
public void setText(CharSequence text, BufferType type) {
Log.d(TAG, "in setText function");
Spannable s = getCustomSpannableString(getContext(), text);
super.setText(s, BufferType.SPANNABLE);
}
private static Spannable getCustomSpannableString(Context context, CharSequence text) {
Spannable spannable = spannableFactory.newSpannable(text);
doSomeFancyStuff(context, spannable);
return spannable;
}
private static void doSomeFancyStuff(Context context, Spannable spannable) {
/*Here I'm trying to access the mHeight attribute.
Unfortunately it's 0 though I set it to 24 in my layout
and it's correctly set in the constructor*/
}
}
styles.xml
<declare-styleable name="CustomTextView">
<attr name="cHeight" format="dimension"/>
</declare-styleable>
layout.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:ctvi="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.mypackage.views.CustomTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/my_fancy_string"
android:textSize="16sp"
ctvi:cHeight="24dp" />
</LinearLayout>
And just as a proof - here's the LogCat output:
30912-30912/com.mypackage.views D/CustomTextView﹕ in setText function
30912-30912/com.mypackage.views D/CustomTextView﹕ in CustomTextView constructor
So as you can see the setText() method is called before the constructor. That's kinda weird and I don't know what I need to change in order to use my custom attribute (cHeight) in the setText-method.
Thanks in advance for any help!
It's the TextView super() constructor that calls your setText() based on the attribute values.
If you really need to access your custom attribute when setting a text value, use a custom attribute for the text as well.
I don't think any of this solutions are good, IMHO. What if you just use a custom method, like setCustomText() instead of overriding the custom TextView.setText(). I think it could be much better in terms of scalability, and hacking / overriding the implementation of the TextView could lead you into future problems.
Cheers!
First of all, remember to always recycle the TypedArray after using it.
TextView calls #setText(CharSequence text, BufferType type) during its construction therefore define a delayed call to setText as so:
private Runnable mDelayedSetter;
private boolean mConstructorCallDone;
public CustomTextView(Context context, AttributeSet attrs) {
super(context, attrs);
Log.d(TAG, "in CustomTextView constructor");
TypedArray values = context.obtainStyledAttributes(attrs, R.styleable.CustomTextView);
this.mHeight = values.getDimension(R.styleable.CustomTextView_cHeight, 20);
mConstructorCallDone = true;
}
Then inside your setText-override:
public void setText(final CharSequence text, final TextView.BufferType type) {
if (!mConstructorCallDone) {
// The original call needs to be made at this point otherwise an exception will be thrown in BoringLayout if text contains \n or some other characters.
super.setText(text, type);
// Postponing setting text via XML until the constructor has finished calling
mDelayedSetter = new Runnable() {
#Override
public void run() {
CustomTextView.this.setText(text, type);
}
};
post(mDelayedSetter);
} else {
removeCallbacks(mDelayedSetter);
Spannable s = getCustomSpannableString(getContext(), text);
super.setText(s, BufferType.SPANNABLE);
}
}
Unfortunately it is a limitation on Java, that requires to call super(..) in constructor before anything else. So, your only workaround is to call setText(..) again after you initialize the custom attributes.
Just remember, as setText called also before you initialize your custom attributes, they may have null value and you can get NullPointerException
Check my example of customTextView which capitalize first letter and adds double dots at the and (I use it in all my activities)
package com.example.myapp_android_box_detector;
import android.content.Context;
import android.content.res.TypedArray;
import android.support.v7.widget.AppCompatTextView;
import android.util.AttributeSet;
public class CapsTextView extends AppCompatTextView {
public Boolean doubleDot;
private Boolean inCustomText = false;
public CapsTextView(Context context){
super(context);
doubleDot = false;
setText(getText());
}
public CapsTextView(Context context, AttributeSet attrs){
super(context, attrs);
initAttrs(context, attrs);
setText(getText());
}
public CapsTextView(Context context, AttributeSet attrs, int defStyle){
super(context, attrs, defStyle);
initAttrs(context, attrs);
setText(getText());
}
public void initAttrs(Context context, AttributeSet attrs){
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CapsTextView, 0, 0);
doubleDot = a.getBoolean(R.styleable.CapsTextView_doubleDot, false);
a.recycle();
}
#Override
public void setText(CharSequence text, BufferType type) {
if (text.length() > 0){
text = String.valueOf(text.charAt(0)).toUpperCase() + text.subSequence(1, text.length());
// Adds double dot (:) to the end of the string
if (doubleDot != null && doubleDot){
text = text + ":";
}
}
super.setText(text, type);
}
}

TextView using Spannable - ellipsize doesn't work

The issue I'm trying to fix is the following: I'm having a TextView and I'm using a Spannable to set some characters bold.
The text needs to have a maxim of 2 lines ( android:maxLines="2") and I want the text to be ellipsized, but for some reason I cannot make the text ellipsized.
Here is the simple code:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TextView android:id="#+id/name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="center"
android:maxLines="2"
android:ellipsize="end"
android:bufferType="spannable"
android:text="#string/app_name"
android:textSize="15dp"/>
</LinearLayout>
and the activity:
public class MyActivity extends Activity {
private TextView name;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
name= (TextView) findViewById(R.id.name);
name.setText("Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy ");
Spannable spannable = (Spannable)name.getText();
StyleSpan boldSpan = new StyleSpan( Typeface.BOLD );
spannable.setSpan( boldSpan, 10, 15, Spannable.SPAN_INCLUSIVE_INCLUSIVE );
}
}
The text is truncated, no "..." are displayed.
I realise this is a very old post, but seeing as it's still unanswered and I also ran into this issue today I thought I would post a solution to this. Hopefully it helps someone in the future.
ViewTreeObserver viewTreeObserver = textView.getViewTreeObserver();
viewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener()
{
#Override
public void onGlobalLayout()
{
ViewTreeObserver viewTreeObserver = textView.getViewTreeObserver();
viewTreeObserver.removeOnGlobalLayoutListener(this);
if (textView.getLineCount() > 5)
{
int endOfLastLine = textView.getLayout().getLineEnd(4);
String newVal = textView.getText().subSequence(0, endOfLastLine - 3) + "...";
textView.setText(newVal);
}
}
});
Having same problem and seems the following works for me:
Spannable wordtoSpan = new SpannableString(lorem);
wordtoSpan.setSpan(new ForegroundColorSpan(0xffff0000), 0, 10, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
wordtoSpan.setSpan(new ForegroundColorSpan(0xff00ffff), 20, 35, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
textView.setText(wordtoSpan);
in xml the textView has android:multiLine set, and android:ellipsize="end", and android:singleLine="false;
You're right in that ellipsize, declared either in xml or in code won't work on spannable text.
However, with a little bit of investigation you can actually do the ellipsizing yourself:
private TextView name;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
name= (TextView) findViewById(R.id.name);
String lorem = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy "
name.setText(lorem);
Spannable spannable = (Spannable)name.getText();
StyleSpan boldSpan = new StyleSpan(Typeface.BOLD);
spannable.setSpan( boldSpan, 10, 15, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
int maxLines = 2;
// in my experience, this needs to be called in code, your mileage may vary.
name.setMaxLines(maxLines);
// check line count.. this will actually be > than the # of visible lines
// if it is long enough to be truncated
if (name.getLineCount() > maxLines){
// this returns _1 past_ the index of the last character shown
// on the indicated line. the lines are zero indexed, so the last
// valid line is maxLines -1;
int lastCharShown = name.getLayout().getLineVisibleEnd(maxLines - 1);
// chop off some characters. this value is arbitrary, i chose 3 just
// to be conservative.
int numCharsToChop = 3;
String truncatedText = lorem.substring(0, lastCharShown - numCharsToChop);
// ellipsize! note ellipsis character.
name.setText(truncatedText+"…");
// reapply the span, since the text has been changed.
spannable.setSpan(boldSpan, 10, 15, Spannable.SPAN_INCLUSIVE_INCLUSIVE);
}
}
This may a little trick by using reflection to solve this problem. After reading the source code of AOSP, in TextView.java, DynamicLayout only contains a static field member named sStaticLayout and it's constructed by new StaticLayout(null) without any params including maxLines.
Therefore, doEllipsis will alway be false as mMaximumVisibleLineCount is set Integer.MAX_VALUE by default.
boolean firstLine = (j == 0);
boolean currentLineIsTheLastVisibleOne = (j + 1 == mMaximumVisibleLineCount);
boolean lastLine = currentLineIsTheLastVisibleOne || (end == bufEnd);
......
if (ellipsize != null) {
// If there is only one line, then do any type of ellipsis except when it is MARQUEE
// if there are multiple lines, just allow END ellipsis on the last line
boolean forceEllipsis = moreChars && (mLineCount + 1 == mMaximumVisibleLineCount);
boolean doEllipsis =
(((mMaximumVisibleLineCount == 1 && moreChars) || (firstLine && !moreChars)) &&
ellipsize != TextUtils.TruncateAt.MARQUEE) ||
(!firstLine && (currentLineIsTheLastVisibleOne || !moreChars) &&
ellipsize == TextUtils.TruncateAt.END);
if (doEllipsis) {
calculateEllipsis(start, end, widths, widthStart,
ellipsisWidth, ellipsize, j,
textWidth, paint, forceEllipsis);
}
}
So I extends the TextView and make a View named EllipsizeTextView
public class EllipsizeTextView extends TextView {
public EllipsizeTextView(Context context) {
super(context);
}
public EllipsizeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
public EllipsizeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
}
public EllipsizeTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
StaticLayout layout = null;
Field field = null;
try {
Field staticField = DynamicLayout.class.getDeclaredField("sStaticLayout");
staticField.setAccessible(true);
layout = (StaticLayout) staticField.get(DynamicLayout.class);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
if (layout != null) {
try {
field = StaticLayout.class.getDeclaredField("mMaximumVisibleLineCount");
field.setAccessible(true);
field.setInt(layout, getMaxLines());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (layout != null && field != null) {
try {
field.setInt(layout, Integer.MAX_VALUE);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
}
problem solved!
Another solution is to overwrite onDraw of TextView. The following suggested solution, doesn't make use any reflection technique. Hence, shouldn't break in the future, in case any member variable naming changes.
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
import android.text.Layout;
import android.text.SpannableStringBuilder;
import android.util.AttributeSet;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
public class EllipsizeTextView extends TextView {
private static final String THREE_DOTS = "...";
private static final int THREE_DOTS_LENGTH = THREE_DOTS.length();
private volatile boolean enableEllipsizeWorkaround = false;
private SpannableStringBuilder spannableStringBuilder;
public EllipsizeTextView(Context context) {
super(context);
}
public EllipsizeTextView(Context context, #Nullable AttributeSet attrs) {
super(context, attrs);
}
public EllipsizeTextView(Context context, #Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
#RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public EllipsizeTextView(Context context, #Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public void setEnableEllipsizeWorkaround(boolean enableEllipsizeWorkaround) {
this.enableEllipsizeWorkaround = enableEllipsizeWorkaround;
}
// https://stackoverflow.com/questions/14691511/textview-using-spannable-ellipsize-doesnt-work
// https://blog.csdn.net/htyxz8802/article/details/50387950
#Override
protected void onDraw(Canvas canvas) {
if (enableEllipsizeWorkaround && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
final Layout layout = getLayout();
if (layout.getLineCount() >= getMaxLines()) {
CharSequence charSequence = getText();
int lastCharDown = layout.getLineVisibleEnd(getMaxLines()-1);
if (lastCharDown >= THREE_DOTS_LENGTH && charSequence.length() > lastCharDown) {
if (spannableStringBuilder == null) {
spannableStringBuilder = new SpannableStringBuilder();
} else {
spannableStringBuilder.clear();
}
spannableStringBuilder.append(charSequence.subSequence(0, lastCharDown - THREE_DOTS_LENGTH)).append(THREE_DOTS);
setText(spannableStringBuilder);
}
}
}
super.onDraw(canvas);
}
}
This is a known issue in the Android framework: https://code.google.com/p/android/issues/detail?id=67186
A simple and working solution
This is my code ->
<TextView
android:id="#+id/textViewProfileContent"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="false"
android:ellipsize="end"
android:maxLines="3"
android:textSize="14sp"
android:textColor="#000000" />
SpannableStringBuilder sb = new SpannableStringBuilder();
SpannableString attrAdditional = new SpannableString(additionalText);
attrAdditional.SetSpan(new StyleSpan(TypefaceStyle.Bold), 0, additionalText.Length, 0);...
sb.Append(attrAdditional);...
ProfileContent.SetText(sb, **TextView.BufferType.Normal**);
Result
As for single line case, android:maxLines is not working,but android:singleLine is ok.
Similar to #Dallas187 but doesn't break link formatting
fun TextView.addEllipsizeToSpannedOnLayout() {
doOnNextLayout {
if (maxLines != -1 && lineCount > maxLines) {
val endOfLastLine = layout.getLineEnd(maxLines - 1)
val spannedDropLast3Chars = text.subSequence(0, endOfLastLine - 3) as? Spanned
if (spannedDropLast3Chars != null) {
val spannableBuilder = SpannableStringBuilder()
.append(spannedDropLast3Chars)
.append("…")
text = spannableBuilder
}
}
}
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
if (maxLength < 0) {
Layout layout = getLayout();
if (layout.getLineCount() > getMaxLines()) {
maxLength = layout.getLineVisibleEnd(getMaxLines() - 1) - 1;
setSpannableString();
}
}
}
The "setSpannableString()" shows below:
private void setSpannableString() {
// ShowString is real visible string. FullString is original string which
// is useful when caching and calculating.
String showString = fullString;
if (maxLength > 0) {
showString = fullString.substring(0, maxLength) + THREE_DOTS;
}
SpannableStringBuilder builder = new SpannableStringBuilder(showString);
for (int i = 0; i < mHighLightColor.size(); i++) {
String highLightString = mHighLightString.get(i);
int color = mHighLightColor.get(i);
int start = fullString.indexOf(highLightString);
int end = Math.min(start + highLightString.length(), showString.length());
if (mClickableSpanList.get(i) != null) {
builder.setSpan(mClickableSpanList.get(i), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
setMovementMethod(LinkMovementMethod.getInstance());
}
builder.setSpan(new ColorBoldSpan(color), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
setText(builder, BufferType.SPANNABLE);
}
Use Kotlin's CharSequence instead of using SpannableString directly.
for eg:
finalString = "your very long string"
var spannable :CharSequence? = ""
spannable = SpannableStringBuilder(finalString).apply{
setSpan(.....)
setSpan(.....)
setSpan(.....)
}
textView.text = spannable
Kotlin's CharSequence works perfectly with ellipsize for more than one line.
Try This Its Work for me.
This is my text view
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:ellipsize="end"
android:text="Type here maximum characters."
android:textSize="16sp" />

how do you color percentage of a textview in android?

I know Spannable can help me color any specific letters in a textview. However, is it possible to color 1/2 or 1/3 of a letter. I wanted to color text within a textview by percentage instead of by letter. Thanks for reading, and please let me know if you had some idea or solution to this.
thanks
It may be easier to use Spanned thought android.text.HTML
So something like this:
Spanned text = HTML.fromHtml("Sp<span style=\"color:red\">ann</span>able");
textView.setText(text);
It can also be used to add images, but it a bit more complicated.
UPDATE
I re-read your question and thought of a solution. You could create a custom view that has two textViews in a FrameLayout (on top of each other) and then resize the one on the top relative to the percentage. Something like this:
import android.content.Context;
import android.content.res.ColorStateList;
import android.util.AttributeSet;
import android.widget.FrameLayout;
import android.widget.TextView;
public class ProgressTextView extends FrameLayout {
private TextView backgroundTextView;
private TextView foregroundTextView;
private CharSequence text;
private ProgressTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init(context, attrs);
}
private ProgressTextView(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
}
private ProgressTextView(Context context) {
super(context);
init(context, null);
}
private void init(Context context, AttributeSet attrs) {
backgroundTextView = new TextView(context);
foregroundTextView = new TextView(context);
addView(backgroundTextView);
addView(foregroundTextView);
// process custom attributeSet from xml to set the colours
}
public void setBackgroundTextColor(int color) {
backgroundTextView.setTextColor(color);
}
public void setBackgroundTextColor(ColorStateList colors) {
backgroundTextView.setTextColor(colors);
}
public void setForegroundTextColor(int color) {
backgroundTextView.setTextColor(color);
}
public void setForegroundTextColor(ColorStateList colors) {
backgroundTextView.setTextColor(colors);
}
public void setPercentage(float per) {
foregroundTextView.setWidth((((float) backgroundTextView.getWidth()) / 100f) * per);
}
public void setText(CharSequence text) {
this.text = text;
backgroundTextView.setText(text);
foregroundTextView.setText(text);
}
public CharSequence getText() {
return text;
}
}
PS: not tested ;) just an idea
MORE UPDATE
Apparently, with this method the text gets shortened instead of just cropped as I expected. The maybe on creation of the foregroundTextView you could do this:
foregroundTextView = new TextView(context) {
#Override
protected void onDraw (Canvas canvas) {
super.onDraw(canvas);
Rect bounds = canvas.getClipBounds();
bounds.right (((float) bounds.right) / 100.0f) * per;
canvas.clipRect(bounds);
}
};
And also add the per variable and modify setPercentage(float per) to be just a setter:
private float per = 0.0f;
public void setPercentage(float per) {
this.per = per;
}
Hope this one works ;)
You could draw the letters yourself on a canvas. Each letter consisting of a bunch of interconnected arcs, that way you could style each individually.
Having to draw each letter would be considerably painstaking though, e.g., paint.setColor(), canvas.drawArc(), paint.setColor(), canvas.drawArc(), and so on...
create a drawable and override it's onDraw method and paint canvas how you wish and set it as background to your textview

Categories

Resources