I this specific example I want set hint text color progrommatically but I couldn't change it.
public class FloatingHintEditText extends EditText {
private static enum Animation { NONE, SHRINK, GROW }
private final Paint mFloatingHintPaint = new Paint();
private final ColorStateList mHintColors;
private final float mHintScale;
private final int mAnimationSteps;
private boolean mWasEmpty;
private int mAnimationFrame;
private Animation mAnimation = Animation.NONE;
public FloatingHintEditText(Context context) {
this(context, null);
}
public FloatingHintEditText(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.floatingHintEditTextStyle);
}
public FloatingHintEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedValue typedValue = new TypedValue();
getResources().getValue(R.dimen.floatinghintedittext_hint_scale, typedValue, true);
mHintScale = typedValue.getFloat();
mAnimationSteps = getResources().getInteger(R.dimen.floatinghintedittext_animation_steps);
mHintColors = getHintTextColors();
mWasEmpty = TextUtils.isEmpty(getText());
}
#Override
public int getCompoundPaddingTop() {
final FontMetricsInt metrics = getPaint().getFontMetricsInt();
final int floatingHintHeight = (int) ((metrics.bottom - metrics.top) * mHintScale);
return super.getCompoundPaddingTop() + floatingHintHeight;
}
#Override
protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
super.onTextChanged(text, start, lengthBefore, lengthAfter);
final boolean isEmpty = TextUtils.isEmpty(getText());
// The empty state hasn't changed, so the hint stays the same.
if (mWasEmpty == isEmpty) {
return;
}
mWasEmpty = isEmpty;
// Don't animate if we aren't visible.
if (!isShown()) {
return;
}
if (isEmpty) {
mAnimation = Animation.GROW;
setHintTextColor(Color.TRANSPARENT);
} else {
mAnimation = Animation.SHRINK;
}
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
if (TextUtils.isEmpty(getHint())) {
return;
}
final boolean isAnimating = mAnimation != Animation.NONE;
// The large hint is drawn by Android, so do nothing.
if (!isAnimating && TextUtils.isEmpty(getText())) {
return;
}
mFloatingHintPaint.set(getPaint());
mFloatingHintPaint.setColor(
mHintColors.getColorForState(getDrawableState(), mHintColors.getDefaultColor()));
final float hintPosX = getCompoundPaddingLeft() + getScrollX();
final float normalHintPosY = getBaseline();
final float floatingHintPosY = normalHintPosY + getPaint().getFontMetricsInt().top + getScrollY();
final float normalHintSize = getTextSize();
final float floatingHintSize = normalHintSize * mHintScale;
// If we're not animating, we're showing the floating hint, so draw it and bail.
if (!isAnimating) {
mFloatingHintPaint.setTextSize(floatingHintSize);
canvas.drawText(getHint().toString(), hintPosX, floatingHintPosY, mFloatingHintPaint);
return;
}
if (mAnimation == Animation.SHRINK) {
drawAnimationFrame(canvas, normalHintSize, floatingHintSize,
hintPosX, normalHintPosY, floatingHintPosY);
} else {
drawAnimationFrame(canvas, floatingHintSize, normalHintSize,
hintPosX, floatingHintPosY, normalHintPosY);
}
mAnimationFrame++;
if (mAnimationFrame == mAnimationSteps) {
if (mAnimation == Animation.GROW) {
setHintTextColor(mHintColors);
}
mAnimation = Animation.NONE;
mAnimationFrame = 0;
}
invalidate();
}
private void drawAnimationFrame(Canvas canvas, float fromSize, float toSize,
float hintPosX, float fromY, float toY) {
final float textSize = lerp(fromSize, toSize);
final float hintPosY = lerp(fromY, toY);
mFloatingHintPaint.setTextSize(textSize);
canvas.drawText(getHint().toString(), hintPosX, hintPosY, mFloatingHintPaint);
}
private float lerp(float from, float to) {
final float alpha = (float) mAnimationFrame / (mAnimationSteps - 1);
return from * (1 - alpha) + to * alpha;
}
}
So when I call from my activity mEditText.setHintTextColor(getResources().getColor(R.color.red)) the color doesn't change
The color changes when I set only from xml.
I tried to do mEditText.invalidate() but it doesn't help too.
What should I do here to make my hintTextColor red.
Make your fields not final and try this:
public FloatingHintEditText(Context context) {
super(context);
init();
}
public FloatingHintEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public FloatingHintEditText(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
private void init() {
TypedValue typedValue = new TypedValue();
getResources().getValue(R.dimen.floatinghintedittext_hint_scale, typedValue, true);
mHintScale = typedValue.getFloat();
mAnimationSteps = getResources().getInteger(R.dimen.floatinghintedittext_animation_steps);
mHintColors = getHintTextColors();
mWasEmpty = TextUtils.isEmpty(getText());
}
Related
I have a custom EditText which I got from here.
PROBLEM: This by default, opens the normal keyboard. I want to open the numeric keyboard. I tried adding inputType="number" in the XML, but then it stops showing the placeholder lines.
How can I make this open the numeric keyboard while still showing the placeholder lines ?
Also, how is it possible to set maxLength from inside the class ?
Below is the code:
public class PinEntryEditText extends android.support.v7.widget.AppCompatEditText {
public static final String XML_NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android";
private float mSpace = 10; //24 dp by default, space between the lines
private float mCharSize;
private float mNumChars = 6;
private float mLineSpacing = 8; //8dp by default, height of the text from our lines
private int mMaxLength = 6;
private int pinLength;
private OnClickListener mClickListener;
private float mLineStroke = 1; //1dp by default
private float mLineStrokeSelected = 2; //2dp by default
private Paint mLinesPaint;
int[][] mStates = new int[][]{
new int[]{android.R.attr.state_selected}, // selected
new int[]{android.R.attr.state_focused}, // focused
new int[]{-android.R.attr.state_focused}, // unfocused
};
int[] mColors = new int[]{
Color.GREEN,
Color.BLACK,
Color.GRAY
};
ColorStateList mColorStates = new ColorStateList(mStates, mColors);
public PinEntryEditText(Context context) {
super(context);
}
public PinEntryEditText(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.PinEntryEditText , 0, 0);
try {
pinLength = ta.getInteger(R.styleable.PinEntryEditText_pinLength , mMaxLength);
} finally {
ta.recycle();
}
mNumChars = pinLength;
mMaxLength = pinLength;
init(context, attrs);
}
public PinEntryEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
float multi = context.getResources().getDisplayMetrics().density;
mLineStroke = multi * mLineStroke;
mLineStrokeSelected = multi * mLineStrokeSelected;
mLinesPaint = new Paint(getPaint());
mLinesPaint.setStrokeWidth(mLineStroke);
if (!isInEditMode()) {
TypedValue outValue = new TypedValue();
context.getTheme().resolveAttribute(R.attr.colorControlActivated,
outValue, true);
final int colorActivated = outValue.data;
mColors[0] = colorActivated;
context.getTheme().resolveAttribute(R.attr.colorPrimaryDark,
outValue, true);
final int colorDark = outValue.data;
mColors[1] = colorDark;
context.getTheme().resolveAttribute(R.attr.colorControlHighlight,
outValue, true);
final int colorHighlight = outValue.data;
mColors[2] = colorHighlight;
}
setBackgroundResource(0);
mSpace = multi * mSpace; //convert to pixels for our density
mLineSpacing = multi * mLineSpacing; //convert to pixels for our density
mMaxLength = attrs.getAttributeIntValue(XML_NAMESPACE_ANDROID, "maxLength", mMaxLength);
mNumChars = mMaxLength;
//Disable copy paste
super.setCustomSelectionActionModeCallback(new ActionMode.Callback() {
public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
return false;
}
public void onDestroyActionMode(ActionMode mode) {
}
public boolean onCreateActionMode(ActionMode mode, Menu menu) {
return false;
}
public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
return false;
}
});
// When tapped, move cursor to end of text.
super.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
setSelection(getText().length());
if (mClickListener != null) {
mClickListener.onClick(v);
}
}
});
}
#Override
public void setOnClickListener(OnClickListener l) {
mClickListener = l;
}
#Override
public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) {
throw new RuntimeException("setCustomSelectionActionModeCallback() not supported.");
}
#Override
protected void onDraw(Canvas canvas) {
//super.onDraw(canvas);
int availableWidth = getWidth() - getPaddingRight() - getPaddingLeft();
if (mSpace < 0) {
mCharSize = (availableWidth / (mNumChars * 2 - 1));
} else {
mCharSize = (availableWidth - (mSpace * (mNumChars - 1))) / mNumChars;
}
int startX = getPaddingLeft();
int bottom = getHeight() - getPaddingBottom();
//Text Width
Editable text = getText();
int textLength = text.length();
float[] textWidths = new float[textLength];
getPaint().getTextWidths(getText(), 0, textLength, textWidths);
for (int i = 0; i < mNumChars; i++) {
updateColorForLines(i == textLength);
canvas.drawLine(startX, bottom, startX + mCharSize, bottom, mLinesPaint);
if (getText().length() > i) {
float middle = startX + mCharSize / 2;
canvas.drawText(text, i, i + 1, middle - textWidths[0] / 2, bottom - mLineSpacing, getPaint());
}
if (mSpace < 0) {
startX += mCharSize * 2;
} else {
startX += mCharSize + mSpace;
}
}
}
private int getColorForState(int... states) {
return mColorStates.getColorForState(states, Color.GRAY);
}
/**
* #param next Is the current char the next character to be input?
*/
private void updateColorForLines(boolean next) {
if (isFocused()) {
mLinesPaint.setStrokeWidth(mLineStrokeSelected);
mLinesPaint.setColor(getColorForState(android.R.attr.state_focused));
if (next) {
mLinesPaint.setColor(getColorForState(android.R.attr.state_selected));
}
} else {
mLinesPaint.setStrokeWidth(mLineStroke);
mLinesPaint.setColor(getColorForState(-android.R.attr.state_focused));
}
}
}
XML:
<com.mridulahuja.kudamm.tools.PinEntryEditText
android:id="#+id/txtToken"
android:layout_width="0dp"
android:layout_height="55dp"
android:ems="10"
pin:pinLength="6"
android:gravity="center"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintRight_toRightOf="parent"
android:layout_marginRight="15dp"
android:layout_marginEnd="15dp"
android:layout_marginLeft="15dp"
android:layout_marginStart="15dp"
app:layout_constraintLeft_toLeftOf="parent"/>
Default length is set from code:
private int mMaxLength = 6;
But this library reads value from xml too:
mMaxLength = attrs.getAttributeIntValue(XML_NAMESPACE_ANDROID, "maxLength", mMaxLength);
so you can use both approaches.
In order to make placeholders visible you should add the _ symbol like this:
android:digits="0123456789_"
May be you'll need to add some other symbols, based on your needs.
I'm coding a ring menu that is going to put together several different types of view, which will have all the same size. The thing is that it works perfectly with native views, like ImageView, but when I try to put a custom labeled image view, it simply doesn't appear int the custom ViewGroup. It's also worth mentioning that when this view is declared in the XML file, outside de custom ViewGroup it is shown just fine, but as soon as I put it inside the ViewGroup, or declare it programatically, it vanishes. My guess is that I'm doing something weong inside the onLayout method, but I can't put my finger on it, since all coordinates and view sizes are correct according to the Log.
The XML file for the CompoundView:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true">
<ImageView
android:id="#+id/header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_centerVertical="true"
android:src="#drawable/ropeiconselector"/>
<TextView
android:id="#+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="WWWWWWWWWWWW"
android:background="#color/button"
android:layout_marginLeft="-10dp"
android:layout_centerVertical="true"
android:layout_toRightOf="#id/header"
android:padding="8dp"
/>
The code for the CompoundView (I omitted some unimportant methods)
public class CircularLabeledImageView extends RelativeLayout implements View.OnClickListener {
ImageView headerView;
TextView labelView;
boolean isOpen = false;
String[] itemDesc;
int position;
int size;
int maxLabelWidth = 100;
int minLabelWidth = 20;
final Handler timeHandler = new Handler();
Runnable toggleTimer;
public CircularLabeledImageView(Context context) {
super(context);
//EDIT Methhod called
initView(context);
}
public CircularLabeledImageView(Context context, AttributeSet attrs) {
super(context, attrs);
initView(context);
}
public CircularLabeledImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initView(context);
}
private void initView(Context context){
LayoutInflater inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
inflater.inflate(R.layout.expandabletextimageview, this);
headerView = (ImageView) this.findViewById(R.id.header);
labelView = (TextView) this.findViewById(R.id.label);
labelView.setBackgroundResource(R.color.backgroundMenuContents);
headerView.setOnClickListener(this);
itemDesc = new String[]{"Item A","Item B", "Item C","Quantos itens"};
size = itemDesc.length;
toggleTimer = new Runnable() {
#Override
public void run() {
toggle();
}
};
this.setOnFocusChangeListener(new OnFocusChangeListener() {
#Override
public void onFocusChange(View v, boolean hasFocus) {
if (isOpen) {
toggle();
}
}
});
}
}
The code for the custom ViewGroup
public class RingListMenu extends ViewGroup implements View.OnClickListener {
boolean isOpen = true;
int headerSize= 90;
int childSize= 80;
int radiusInit = 150;
int childInitSize = 80;
int radius = 150;
int padding;
DPoint center = new DPoint();
float startingAngle= 2;
DPoint click0;
DPoint clickIni;
DPoint clickFinal;
final static float SENSIBILITY = 10f;
final static float FRICTION = 0.00001f;
final static float MAXVELOCITY = 0.06f;
final static long TOGGLE_DURATION = 300;
private VelocityTracker vTracker = null;
boolean isScrolling;
ImageView circleView;
OnItemClickListener listener = null;
public RingListMenu(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
if (android.os.Build.VERSION.SDK_INT >= 11)
{
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
TypedArray at = context.obtainStyledAttributes(attrs,R.styleable.RingListMenu);
childInitSize = childSize = at.getDimensionPixelSize(R.styleable.RingListMenu_childSize, 0);
radiusInit = radius = at.getDimensionPixelSize(R.styleable.RingListMenu_circleRadius, 0);
headerSize = at.getDimensionPixelSize(R.styleable.RingListMenu_headerSize, 0);
padding = at.getDimensionPixelSize(R.styleable.RingListMenu_padding, 0);
}
public RingListMenu(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
if (android.os.Build.VERSION.SDK_INT >= 11)
{
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
TypedArray at = context.obtainStyledAttributes(attrs,R.styleable.RingListMenu);
childInitSize = childSize = at.getDimensionPixelSize(R.styleable.RingListMenu_childSize, 0);
radiusInit = radius = at.getDimensionPixelSize(R.styleable.RingListMenu_circleRadius, 0);
headerSize = at.getDimensionPixelSize(R.styleable.RingListMenu_headerSize, 0);
padding = at.getDimensionPixelSize(R.styleable.RingListMenu_padding, 0);
}
public RingListMenu(Context context, AttributeSet attrs) {
super(context, attrs);
if (android.os.Build.VERSION.SDK_INT >= 11)
{
setLayerType(View.LAYER_TYPE_SOFTWARE, null);
}
TypedArray at = context.obtainStyledAttributes(attrs, R.styleable.RingListMenu);
childInitSize = childSize = at.getDimensionPixelSize(R.styleable.RingListMenu_childSize, 0);
radiusInit = radius = at.getDimensionPixelSize(R.styleable.RingListMenu_circleRadius, 0);
headerSize = at.getDimensionPixelSize(R.styleable.RingListMenu_headerSize, 0);
padding = at.getDimensionPixelSize(R.styleable.RingListMenu_padding, 0);
}
#Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
Log.d("RingList", "Calling Ring list onLayout" + childSize + " " + radius + " " + headerSize);
float angle = (float) (2*Math.PI)/(getChildCount()-1);
center.x = padding+headerSize/2;
center.y = padding+headerSize/2;
float childX;
float childY;
//radius = (float) (getChildCount()*(minSpacing+2*childSize)/(2*Math.PI));
for (int i = 1; i < getChildCount(); i++) {
View child = getChildAt(i);
childX = (float) (center.x + radius * Math.cos(startingAngle + i * angle));
childY = (float) (center.y + radius * Math.sin(startingAngle + i * angle));
child.layout((int) (childX - childSize / 2), (int) (childY - childSize / 2),
(int) (childX + childSize / 2), (int) (childY + childSize / 2));
}
View header = getChildAt(0);
header.setX(padding);
header.setY(padding);
header.layout(padding, padding, padding + headerSize, padding + headerSize);
}
#Override
public void addView(View child) {
child.setTag(getChildCount());
super.addView(child);
child.setOnClickListener(this);
}
}
And finally, the declaration part:
RingListMenu ring = (RingListMenu) findViewById(R.id.ring);
CircularLabeledImageView ViewA = new CircularLabeledImageView(this);
ring.addView(ViewA);
I have a TextView in an app, the text of which is set by a hard-coded string resource in the layout. In order to get a bulleted list in the TextView, I've used the (unofficial?) support for the <li> element. This creates properly-indented bullets, as desired, but the leftmost edge of the bullets themselves are slightly cut off, as you can see:
I have tried adding left padding to these, but it did nothing to the clipped edge - just moved the whole thing inwards.
Is there any simple solution to resolve this?
Where does the resource for that bulleted list live?
Old question but for anyone else finding this late:
I've found the built in BulletSpan class has had bugs from early android versions all the way through to marshmallow:
Bullet radius and gap width don't scale depending on dp
Bullets are sometimes cut off (should be + BULLET_RADIUS, not * BULLET_RADIUS)
Warning: I've seen a few custom BulletSpan classes out there which implement ParcelableSpan like the internal class. This WILL cause crashes and is not intended to be used externally.
Here's my BulletSpanCompat:
public class BulletSpanCompat implements LeadingMarginSpan {
private final int mGapWidth;
private final boolean mWantColor;
private final int mColor;
private static final int BULLET_RADIUS = MaterialDesignUtils.dpToPx(1.5f);
private static Path sBulletPath = null;
public static final int STANDARD_GAP_WIDTH = MaterialDesignUtils.dpToPx(8);
public BulletSpanCompat() {
mGapWidth = STANDARD_GAP_WIDTH;
mWantColor = false;
mColor = 0;
}
public BulletSpanCompat(int gapWidth) {
mGapWidth = gapWidth;
mWantColor = false;
mColor = 0;
}
public BulletSpanCompat(int gapWidth, int color) {
mGapWidth = gapWidth;
mWantColor = true;
mColor = color;
}
public BulletSpanCompat(Parcel src) {
mGapWidth = src.readInt();
mWantColor = src.readInt() != 0;
mColor = src.readInt();
}
public int getLeadingMargin(boolean first) {
return 2 * BULLET_RADIUS + mGapWidth;
}
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir,
int top, int baseline, int bottom,
CharSequence text, int start, int end,
boolean first, Layout l) {
if (((Spanned) text).getSpanStart(this) == start) {
Paint.Style style = p.getStyle();
int oldcolor = 0;
if (mWantColor) {
oldcolor = p.getColor();
p.setColor(mColor);
}
p.setStyle(Paint.Style.FILL);
if (c.isHardwareAccelerated()) {
if (sBulletPath == null) {
sBulletPath = new Path();
// Bullet is slightly better to avoid aliasing artifacts on mdpi devices.
sBulletPath.addCircle(0.0f, 0.0f, 1.2f + BULLET_RADIUS, Path.Direction.CW);
}
c.save();
c.translate(x + dir + BULLET_RADIUS, (top + bottom) / 2.0f);
c.drawPath(sBulletPath, p);
c.restore();
} else {
c.drawCircle(x + dir + BULLET_RADIUS, (top + bottom) / 2.0f, BULLET_RADIUS, p);
}
if (mWantColor) {
p.setColor(oldcolor);
}
p.setStyle(style);
}
}
}
Try using the unicode character • (Unicode 2022)? Looks like you can just paste it into the XML.
http://www.fileformat.info/info/unicode/char/2022/index.htm
Please use below code:-
<CustomBulletTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Point1" />
<CustomBulletTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Point2" />
/* CustomBulletTextView.java */
public class CustomBulletTextView extends TextView {
public CustomBulletTextView(Context context) {
super(context);
addBullet();
}
public CustomBulletTextView(Context context, AttributeSet attrs) {
super(context, attrs);
addBullet();
}
public CustomBulletTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
addBullet();
}
public CustomBulletTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
addBullet();
}
private void addBullet() {
CharSequence text = getText();
if (TextUtils.isEmpty(text)) {
return;
}
SpannableString spannable = new SpannableString(text);
spannable.setSpan(new CustomBulletSpan(16), 0, text.length(), 0);
setText(spannable);
}
}
/* CustomBulletSpan.java */
public class CustomBulletSpan implements LeadingMarginSpan, ParcelableSpan {
private final int mGapWidth;
private final boolean mWantColor;
private final int mColor;
private static final int BULLET_RADIUS = 3;
private static Path sBulletPath = null;
public static final int STANDARD_GAP_WIDTH = 2;
public CustomBulletSpan(int gapWidth) {
mGapWidth = gapWidth;
mWantColor = false;
mColor = 0;
}
public int describeContents() {
return 0;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mGapWidth);
dest.writeInt(mWantColor ? 1 : 0);
dest.writeInt(mColor);
}
public int getLeadingMargin(boolean first) {
return 2 * BULLET_RADIUS + mGapWidth;
}
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom,
CharSequence text, int start, int end, boolean first, Layout l) {
// Here I shifted the bullets to right by the given half bullet
x += mGapWidth / 2;
if (((Spanned) text).getSpanStart(this) == start) {
Paint.Style style = p.getStyle();
int oldcolor = 0;
if (mWantColor) {
oldcolor = p.getColor();
p.setColor(mColor);
}
p.setStyle(Paint.Style.FILL);
if (c.isHardwareAccelerated()) {
if (sBulletPath == null) {
sBulletPath = new Path();
// Bullet is slightly better to avoid aliasing artifacts on
// mdpi devices.
sBulletPath.addCircle(0.0f, 0.0f, 1.2f * BULLET_RADIUS, Direction.CW);
}
c.save();
c.translate(x + dir * BULLET_RADIUS, (top + bottom) / 2.0f);
c.drawPath(sBulletPath, p);
c.restore();
} else {
c.drawCircle(x + dir * BULLET_RADIUS, (top + bottom) / 2.0f, BULLET_RADIUS, p);
}
if (mWantColor) {
p.setColor(oldcolor);
}
p.setStyle(style);
}
}
#Override
public int getSpanTypeId() {
return 0;
}
}
I have a weird problem, for some reason the android:ellipsize="end" works, but added the point in the middle of the text == centered vertically instead of being aligned to baseline:
I checked for any "center" properties, but there is none of those:
Update:
This is the XML part:
<com.citylifeapps.cups.customviews.CarmelaTextView
android:id="#+id/venue_address"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_toLeftOf="#+id/venue_distance"
android:layout_toRightOf="#+id/venue_name"
android:gravity="left"
android:text="#string/placeholder_venue_address"
android:textColor="#color/cups_white"
android:textSize="20sp"
android:textStyle="bold"
android:ellipsize="end"
android:singleLine="true"
android:scrollHorizontally="true"
android:layout_alignBaseline="#+id/venue_name" />
And the custom TextView class:
public class CarmelaTextView extends TextView {
public CarmelaTextView(Context context, AttributeSet attrs) {
super(context, attrs);
setCarmelaTypeface(context);
}
public CarmelaTextView(Context context) {
super(context);
setCarmelaTypeface(context);
}
private void setCarmelaTypeface(Context context) {
if (this.isInEditMode()) return;
Typeface typeface = Typeface.createFromAsset(context.getAssets(), "carmela.ttf");
this.setTypeface(typeface);
}
}
further check shows that if I use a simple TextView the problem disappears,
but there is nothing in the custom TextView that will cause such a behavior.
Does anyone know why this might happen?
Thanks.
It looks like the problem lies within my custom font I'm using for this custom TextView, from the accepted answer here:
Why does TextView in single line elipsized with "end" show boxes?
I'm guessing that I'm facing the same problem but with a different result because the 3 dots (...) U+FEFF glyph for my font is different.
But still if some one found a solution that works for this issue I would be glad if he could share it.
I used this class to resolve this issue
public class EllipsizingTextView extends TextView {
private static final String ELLIPSIS = "...";
public interface EllipsizeListener {
void ellipsizeStateChanged(boolean ellipsized);
}
private final List<EllipsizeListener> ellipsizeListeners = new ArrayList<EllipsizeListener>();
private boolean isEllipsized;
private boolean isStale;
private boolean programmaticChange;
private String fullText;
private int maxLines = -1;
private float lineSpacingMultiplier = 1.0f;
private float lineAdditionalVerticalPadding = 0.0f;
public EllipsizingTextView(Context context) {
super(context);
}
public EllipsizingTextView(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.maxLines });
setMaxLines(a.getInt(0, 1));
}
public EllipsizingTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs, new int[] { android.R.attr.maxLines });
setMaxLines(a.getInt(0, 1));
}
public void addEllipsizeListener(EllipsizeListener listener) {
if (listener == null) {
throw new NullPointerException();
}
ellipsizeListeners.add(listener);
}
public void removeEllipsizeListener(EllipsizeListener listener) {
ellipsizeListeners.remove(listener);
}
public boolean isEllipsized() {
return isEllipsized;
}
#Override
public void setMaxLines(int maxLines) {
super.setMaxLines(maxLines);
this.maxLines = maxLines;
isStale = true;
}
public int getMaxLines() {
return maxLines;
}
#Override
public void setLineSpacing(float add, float mult) {
this.lineAdditionalVerticalPadding = add;
this.lineSpacingMultiplier = mult;
super.setLineSpacing(add, mult);
}
#Override
protected void onTextChanged(CharSequence text, int start, int before,
int after) {
super.onTextChanged(text, start, before, after);
if (!programmaticChange) {
fullText = text.toString();
isStale = true;
}
}
#Override
protected void onDraw(Canvas canvas) {
if (isStale) {
super.setEllipsize(null);
resetText();
}
super.onDraw(canvas);
}
private void resetText() {
int maxLines = getMaxLines();
String workingText = fullText;
boolean ellipsized = false;
if (maxLines != -1) {
Layout layout = createWorkingLayout(workingText);
if (layout.getLineCount() > maxLines) {
workingText = fullText.substring(0,
layout.getLineEnd(maxLines - 1)).trim();
while (createWorkingLayout(workingText + ELLIPSIS)
.getLineCount() > maxLines) {
workingText = workingText.substring(0,
workingText.length() - 1 - 1);
}
workingText = workingText + ELLIPSIS;
ellipsized = true;
}
}
if (!workingText.equals(getText())) {
programmaticChange = true;
try {
setText(workingText);
} finally {
programmaticChange = false;
}
}
isStale = false;
if (ellipsized != isEllipsized) {
isEllipsized = ellipsized;
for (EllipsizeListener listener : ellipsizeListeners) {
listener.ellipsizeStateChanged(ellipsized);
}
}
}
private Layout createWorkingLayout(String workingText) {
return new StaticLayout(workingText, getPaint(), getWidth()
- getPaddingLeft() - getPaddingRight(), Alignment.ALIGN_NORMAL,
lineSpacingMultiplier, lineAdditionalVerticalPadding, false);
}
#Override
public void setEllipsize(TruncateAt where) {
// Ellipsize settings are not respected
}
}
public class CheckedTextView extends TextView implements Checkable {
private boolean mChecked;
private int mCheckMarkResource;
private Drawable mCheckMarkDrawable;
private int mBasePaddingRight;
private int mCheckMarkWidth;
private static final int[] CHECKED_STATE_SET = {
R.attr.state_checked
};
public CheckedTextView(Context context) {
this(context, null);
}
public CheckedTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CheckedTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.CheckedTextView, defStyle, 0);
Drawable d = a.getDrawable(R.styleable.CheckedTextView_checkMark);
if (d != null) {
setCheckMarkDrawable(d);
}
boolean checked = a.getBoolean(R.styleable.CheckedTextView_checked, false);
setChecked(checked);
a.recycle();
}
public void toggle() {
setChecked(!mChecked);
}
#ViewDebug.ExportedProperty
public boolean isChecked() {
return mChecked;
}
/**
* <p>Changes the checked state of this text view.</p>
*
* #param checked true to check the text, false to uncheck it
*/
public void setChecked(boolean checked) {
if (mChecked != checked) {
mChecked = checked;
refreshDrawableState();
}
}
/**
* Set the checkmark to a given Drawable, identified by its resourece id. This will be drawn
* when {#link #isChecked()} is true.
*
* #param resid The Drawable to use for the checkmark.
*/
public void setCheckMarkDrawable(int resid) {
if (resid != 0 && resid == mCheckMarkResource) {
return;
}
mCheckMarkResource = resid;
Drawable d = null;
if (mCheckMarkResource != 0) {
d = getResources().getDrawable(mCheckMarkResource);
}
setCheckMarkDrawable(d);
}
/**
* Set the checkmark to a given Drawable. This will be drawn when {#link #isChecked()} is true.
*
* #param d The Drawable to use for the checkmark.
*/
public void setCheckMarkDrawable(Drawable d) {
if (mCheckMarkDrawable != null) {
mCheckMarkDrawable.setCallback(null);
unscheduleDrawable(mCheckMarkDrawable);
}
if (d != null) {
d.setCallback(this);
d.setVisible(getVisibility() == VISIBLE, false);
d.setState(CHECKED_STATE_SET);
setMinHeight(d.getIntrinsicHeight());
mCheckMarkWidth = d.getIntrinsicWidth();
mPaddingRight = mCheckMarkWidth + mBasePaddingRight;
d.setState(getDrawableState());
} else {
mPaddingRight = mBasePaddingRight;
}
mCheckMarkDrawable = d;
requestLayout();
}
#Override
public void setPadding(int left, int top, int right, int bottom) {
super.setPadding(left, top, right, bottom);
mBasePaddingRight = mPaddingRight;
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
final Drawable checkMarkDrawable = mCheckMarkDrawable;
if (checkMarkDrawable != null) {
final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK;
final int height = checkMarkDrawable.getIntrinsicHeight();
int y = 0;
switch (verticalGravity) {
case Gravity.BOTTOM:
y = getHeight() - height;
break;
case Gravity.CENTER_VERTICAL:
y = (getHeight() - height) / 2;
break;
}
int right = getWidth();
checkMarkDrawable.setBounds(
right - mCheckMarkWidth - mBasePaddingRight,
y,
right - mBasePaddingRight,
y + height);
checkMarkDrawable.draw(canvas);
}
}
#Override
protected int[] onCreateDrawableState(int extraSpace) {
final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
if (isChecked()) {
mergeDrawableStates(drawableState, CHECKED_STATE_SET);
}
return drawableState;
}
#Override
protected void drawableStateChanged() {
super.drawableStateChanged();
if (mCheckMarkDrawable != null) {
int[] myDrawableState = getDrawableState();
// Set the state of the Drawable
mCheckMarkDrawable.setState(myDrawableState);
invalidate();
}
}
#Override
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
boolean populated = super.dispatchPopulateAccessibilityEvent(event);
if (!populated) {
event.setChecked(mChecked);
}
return populated;
}
}
As indicated above, checkMarkDrawable is Gravity position in the Canvas to determine the location.
My question is: How can change checkMarkDrawable the location, for example to set checkMarkDrawable in a TextView on the left.
If you want the checkbox to be on the left, simply just use a CheckBox. Maybe it is not matter of course, but a CheckBox can contain text. You can define that text by adding an XML attribute android:text, or by calling the setText() method. Actually CheckBox is inherited from Button, which inherits from TextView, that's why it has all the text-related properties.
Use drawableRight instead of checkMarkDrawable in xml.