I have problem to check, if text view been ellipsized. I defined layout for item in recycle view and I have to check, if text was ellipsized and hide button if yes. I found solution, where can I can get layout from text view and check if it was ellipsized, but in bind method in recycle view it always return false. Do you have someone some idea, how I can do it?
Layout l = textview.getLayout();
if (l != null) {
int lines = l.getLineCount();
if (lines > 0)
if (l.getEllipsisCount(lines-1) > 0)
Log.d(TAG, "Text is ellipsized");
}
This code is not working for me.
try this in your adapter
holder.textView.post(new Runnable() {
#Override
public void run() {
if (holder.textView.getLayout() != null) {
if (widthText == 0) {
widthText = holder.textView.getWidth();
}
boolean isEllipsize = !holder.textView.getText().toString().equalsIgnoreCase(holder.textView.getLayout().getText().toString());
} else {
Paint paint = new Paint();
paint.setTextSize(holder.textView.getTextSize());
final float size = paint.measureText(holder.textView.getText().toString());
boolean isEllipsize = (int) (size / maxLine) > widthText;
}
}
});
if you want to read more if text too long you can use this libs:
https://github.com/bravoborja/ReadMoreTextView
As stated in another SO post:
This only works after the layout phase, otherwise the returned layout will be null, so call this at an appropriate place in your code.
Make sure this is called after the text had been laid out (After onCreate)!
Try this,
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:lines="1" />
Related
I have a Chip view.
I want to check if the text is ellipsized and if so - to replace the text with a shorter text (on run time).
I have seen this code to check if the text in the Chip is ellipsized.
Layout l = textview.getLayout();
if (l != null) {
int lines = l.getLineCount();
if (lines > 0)
if (l.getEllipsisCount(lines-1) > 0)
Log.d(TAG, "Text is ellipsized");
}
But I don't know at what lifecycle event should i call this method, as for this line
Layout l = myAccountView.getLayout();
I get l = null
I have view lifecycle (frame layout that holds my Chip)
I have tried to check on onDraw() and in onLayout()
I have also tried to call from the Dialog that hold the frame
but I know inflation is top to buttom, so it returns l= null on setContentView() as well.
Approach 1: Do call myAccountView.onPreDraw(); just before myAccountView.getLayout()
Approach 2 : Using ViewTreeObserver
ViewTreeObserver vtObserver= myAccountView.getViewTreeObserver();
vtObserver.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
Layout layout = myAccountView.getLayout();
}
});
I am showing a ListView in my app with a custom item. The custom item is already doing quite a bit of work since I am using the android-swipelistview from 47deg found here.
So the "front" part of the list item is a LinearLayout with various bits nested inside it - icons on left and right and in the middle 3 different TextViews - name, address and notes. Each TextView has a different text size but the height of the whole list item is fixed so that the list looks fairly uniform,
The middle item - the address - has been causing me a bit of trouble. Basically I want to make sure it fits in and looks good. I have set space enough for it to be able to take up 2 lines, then ellipsize it after that. However if the address is very short then it all fits in one line and I have a rather large space before notes line which looks bad.
So I thought I would do some analysis on the text - if it is shorter than one line I will break it after the last comma (my addresses always have commas) so I fill both lines.
I have some code like this in my Adapter
private class MyViewHolder {
ImageView defaultLogo;
TextView name;
TextView address;
TextView notes;
ImageView otherLogo;
}
.
.
.
#Override
public View getView(final int position, View convertView, ViewGroup parent) {
MyViewHolder holder = null;
if (convertView == null) {
.
.
holder = new MyViewHolder();
.
.
.
holder.address = (TextView) convertView.findViewById(R.id.street_address);
.
.
.
else {
holder = (MyViewHolder) convertView.getTag();
}
.
.
.
MyItem item = (MyItem) getItem(position);
// Set address:
doAfterLayout(holder.address, fixLines);
holder.address.setText(item.getAddress());
.
.
.
}
.
.
.
/**
* Runs a piece of code after the layout run
*/
public static void doAfterLayout(final View view, final FixLinesRunnable runnable) {
final OnGlobalLayoutListener listener = new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
runnable.view = view;
runnable.run();
}
};
view.getViewTreeObserver().addOnGlobalLayoutListener(listener);
}
private class FixLinesRunnable implements Runnable {
public View view;
#Override
public void run() {
// try to get the width of this control and the text
TextView tv = (TextView) view;
int lineCount = tv.getLineCount();
if (0 == lineCount) {
return;FixLinesRunnable
} else if (1 < lineCount) {
//lineCount over 2 means we leave text as it is
return;
}
//if we got here we have only one line text
//want to try to force it to be 2 lines by breaking at last comma
String text = tv.getText().toString();
int lastCommaPos = text.lastIndexOf(", ");
if (lastCommaPos > text.length() - 1) {
//comma is right at the end
lastCommaPos = text.lastIndexOf(", ",lastCommaPos);
}
if (0 < lastCommaPos) {
String secondLine = text.substring(lastCommaPos + 2);
text = text.substring(0, lastCommaPos + 1) + "\n" + secondLine;
}
tv.setText(text);
}
This Really, Nearly works. It works fine the first time the list is shown - Addresses which are too short are pushed into 2 lines, broken on the last comma. But if I scroll the item out of view and back into view it doesn't work...what could be going wrong?
The original doAfterLayout function would remove the OnGlobalLayoutListener after the first adjustment, but even with the OnGlobalLayoutListener still there, it doesn't get called a second time when the item reappears, so the text shows in one line?
Anyone got any ideas?
EDIT: Annoyingly, if something covers up the list (I have another window which pulls open from the side and covers some items) the visible items redraw... I can even see it before they are covered...
Thanks #Daniel-Benedykt for pointing me in the right direction.
In the end I subclassed TextView. The only place I found I could override which would work each time the view was shown was onDraw...so:
public class AddressTextView extends TextView {
public AddressTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onDraw (Canvas canvas) {
int lineCount = getLineCount();
if (0 == lineCount) {
super.onDraw(canvas);
return;
} else if (1 < lineCount) {
//lineCount over 2 means we leave text as it is
super.onDraw(canvas);
return;
}
//if we got here we have only one line text
//want to try to force it to be 2 lines by breaking at last comma
String text = getText().toString();
int lastCommaPos = text.lastIndexOf(", ");
if (lastCommaPos > text.length() - 1) {
//comma is right at the end
lastCommaPos = text.lastIndexOf(", ",lastCommaPos);
}
if (0 < lastCommaPos) {
String secondLine = text.substring(lastCommaPos + 2);
text = text.substring(0, lastCommaPos + 1) + "\n" + secondLine;
}
setText(text);
super.onDraw(canvas);
}
}
Hope this helps someone.
So I've been creating a custom view similar to GridView. It loads and scrolls through images just fine when they are resources inside the app but now that I'm using images coming in through an HTTP request, the images aren't loading correctly.
When the app starts: all images are set to the default (bad)
After scrolling past that cell and immediately scrolling back to it: image loads correctly (good)
After scrolling back to that same cell sometime later: image was set back to the default (bad)
Does anyone have any ideas of what could be causing this error? I assume it's some kind of recycling issue but I haven't been able to fix it.
Here is my xml file:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:paddingBottom="1dip"
android:background="#color/white"
android:id="#+id/highlight_counter_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:gravity="center" >
<ImageView
android:id="#+id/catalog_image"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<View
android:id="#+id/solid"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:background="#android:color/black" />
<View
android:id="#+id/text_gradient"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_above="#id/solid"
android:background="#drawable/highlight_text_gradient" />
<TextView
android:id="#+id/title"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:padding="#dimen/highlight_text_padding"
android:ellipsize="end"
android:gravity="bottom"
android:maxLines="2"
android:textColor="#color/white"
android:textSize="#dimen/text_large" />
</RelativeLayout>
Here is an excerpt from my adapter (where I think the problem probably lies):
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final LayoutParams lp;
int viewType = getItemViewType(position);
ImageView img;
PulseTextView title;
Resources res = getContext().getResources();
int height = (int) res.getDimension(R.dimen.icon_main_size);
if (convertView == null) {
LayoutInflater inflater = (LayoutInflater) mContext.get().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.element_item, parent, false)
LayoutParams layp = new LayoutParams(height);
convertView.setLayoutParams(layp);
}
img = ViewHolder.get(convertView,R.id.catalog_image);
title = ViewHolder.get(convertView,R.id.title);
final CatalogItem channel = getCatalogItem(position);
// find the url of the associated image then set image
String url = null;
try {
url = mCatalogHandler.getImageUrl(CatalogHandler.VALUE_ICON, channel.mPrimaryKey, 100, 100);
} catch (CatalogException e) {
e.printStackTrace();
}
if (url == null || TextUtils.isEmpty(url) || url.equals("null")) {
img.setImageBitmap(mDefaultPic);
} else {
// downloads the image to img
mImageDownloader.download(url, img, mDefaultPic, false);
}
title.setText(channel.mDomain);
img.setScaleType(ScaleType.FIT_XY);
img.setTag(RAMImageCache.KEY_URL, url);
// set the gradient behind the text
View grad = convertView.findViewById(R.id.text_gradient);
ViewUtils.setHeight(grad, height * 3 / 5);
grad.getBackground().setDither(true);
View solid = convertView.findViewById(R.id.solid);
ViewUtils.setHeight(solid, height / 5);
// set the padding based on the position on the screen
DisplayMetrics displaymetrics = new DisplayMetrics();
((Activity)getContext()).getWindowManager().getDefaultDisplay().getMetrics(displaymetrics);
int width = displaymetrics.widthPixels;
if (convertView.getRight() == width && convertView.getLeft() == 0) {
convertView.setPadding(0, 0, 0, 1);
} else if (convertView.getRight() == width) {
//convertView.setPadding(1, 0, 0, 1);
convertView.setPadding(0, 0, 1, 1);
} else if (convertView.getLeft() == 0) {
//convertView.setPadding(0, 0, 1, 1);
convertView.setPadding(1, 0, 0, 1);
}
// set the onclicklistener to jump to the next fragment
convertView.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View view) {
Bundle bundle = new Bundle();
bundle.putString("channelitem", channel.getMetadata().toString());
ChannelFragment fragment = new ChannelFragment();
fragment.setArguments(bundle);
((PulseFragmentActivity)mContext.get()).openFragment(fragment);
}
});
return convertView;
}
static class ViewHolder {
ImageView img;
TextView title;
public ViewHolder(ImageView i, PulseTextView t) {
img = i;
title = t;
}
public static <T extends View> T get(View view, int id) {
SparseArray<View> viewHolder = (SparseArray<View>) view.getTag();
if (viewHolder == null) {
viewHolder = new SparseArray<View>();
view.setTag(viewHolder);
}
View childView = viewHolder.get(id);
if (childView == null) {
childView = view.findViewById(id);
viewHolder.put(id, childView);
}
return (T) childView;
}
}
Any point in the right direction would help greatly! Let me know if there are any other code snippets you would need to see.
I suggest one way to debug is to try another image download library to find out if the error is in your code. I used https://github.com/koush/UrlImageViewHelper and it works well when cell is reused, and its API is similar to what you used now
The behaviour very much depends on what and how does your mImageDownloader handles the downloaded image.
In most cases LRU Cache implementation might be used to store your downloaded image and this cache has a maximum value of bytes assigned. Once your cached image exceeded this value, the old bitmaps will be discarded, hence why you are seeing default image and you need to re-download it.
My suggestion is after you have downloaded the image, scale it as small as you can, so that you can cache as much bitmaps as you could.
There is no way Android could cache up all your downloaded images due to memory restriction.
I have a listView, one component of the row is a TextView. By default, I wish to show only 2 rows of text in the TextView. But if the user taps on the TextView I wanted to the textView to expand in context.
I actually have this portion working, but I want to have a more content indicator :
My current implementation (Below) has it's own issues w/ not collapsing if the list view is scrolled, but I will handle that by storing the values for each cursor record in some collection..
I tried using chatBubble.getLineCount() but that returns zero initially b/c it has not gone through onLayout (from my understanding).
I only wish to show it if there is more than 2 lines of content.
I figure my solution will be creating my own implementation of TextView which can handle some of my requirements, but not sure if anyone has any examples I can look at.. (Or other suggestions).
<RelativeLayout
android:id="#+id/linear_layout_row_three"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="#+id/linear_layout_row_two"
android:layout_toRightOf="#+id/munzeeQuickContact"
android:orientation="horizontal" >
<TextView
android:id="#+id/chat_bubble"
android:clickable="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:maxLines="2"
android:background="#drawable/chat_bubble"
android:text="I went looking for this particular token on a rainy day, and I was unable to locate it due to the bad weather, next time please leave an um I went looking for this particular munzee on a rainy day, and I was unable to locate it due to the bad weather, next time please leave an um" />
<ImageView
android:id="#+id/minimize_maximize"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignBottom="#id/chat_bubble"
android:layout_alignRight="#id/chat_bubble"
android:visibility="gone"
android:src="#android:drawable/ic_menu_more"/>
</RelativeLayout>
Here is some of the source I currently have :
final TextView chatBubble = (TextView) view.getTag(R.id.chat_bubble);
final ViewGroup expandableContainer = (ViewGroup) view.getTag(R.id.linear_layout_row_three);
final ImageView minimizeMaximize = (ImageView) view.getTag(R.id.minimize_maximize);
chatBubble.setOnClickListener(
new View.OnClickListener() {
boolean isExpanded = false;
int lastHeight = 0;
// This is for the auto expanding text view
#Override
public void onClick(View v) {
if (isExpanded) {
ViewGroup.LayoutParams params = (ViewGroup.LayoutParams) expandableContainer
.getLayoutParams();
params.height = lastHeight;
chatBubble.setMaxLines(2);
expandableContainer.setLayoutParams(params);
expandableContainer.invalidate();
minimizeMaximize.setVisibility(View.VISIBLE);
} else {
lastHeight = expandableContainer.getHeight();
ViewGroup.LayoutParams params = (ViewGroup.LayoutParams) expandableContainer
.getLayoutParams();
params.height = ViewGroup.LayoutParams.WRAP_CONTENT;
chatBubble.setMaxLines(99);
expandableContainer.setLayoutParams(params);
expandableContainer.invalidate();
minimizeMaximize.setVisibility(View.VISIBLE);
}
isExpanded = !isExpanded;
}
});
I figure my solution will be creating my own implementation of
TextView which can handle some of my requirements, but not sure if
anyone has any examples I can look at..
Have a look at the class below:
public class LimitedTextView extends TextView {
private boolean mStatus;
public LimitedTextView(Context context, AttributeSet attrs) {
super(context, attrs);
}
#Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
Paint p = getPaint();
String s = getText().toString();
if (s != null && !s.equals("")) {
int m = (int) p.measureText(s);
if (m < getMeasuredWidth() * 2) {
modifyParent(true);
} else {
modifyParent(false);
}
}
}
private void modifyParent(boolean how) {
RelativeLayout rl = (RelativeLayout) getParent();
rl.findViewById(R.id.minimize_maximize).setVisibility(
how ? View.GONE : View.VISIBLE);
if (mStatus) {
setMaxLines(40); // arbitrary number, set it as high as possible
} else {
setMaxLines(2);
}
}
public void storeCurrentStatus(boolean status) {
mStatus = status;
}
}
The LimitedTextView will measure its text using its own Paint object and test it against the measured width. If it fits on the two allowed rows it will hide the ImageView, otherwise it will show it. It also stores the current status of row(expanded/not-expanded) and increases or decreases the maximum number of lines to obtain the proper appearance.
In the getView method of the adapter you would:
set the text
set the status from a boolean array according to a position(this is also required to keep the rows in order as you scroll the list):
textView.storeCurrentStatus(mStatus[position])
set the OnClickListener on the LimitedTextView itself and from there update the status:
mStatus[(Integer) v.getTag()] = !mStatus[(Integer) v.getTag()];
notifyDataSetChanged();
based on the same mStatus boolean array you'll probably change the drawable of the ImageView, to show a different one depending on if the TextView is expanded or not
I manually wrote it, so there could be some mistakes I'm missing right now, take it as an idea. The LimitedTextView could be improved as in performance, I also don't know how well it would behave if you want to animate expanding the text.
I have a TableLayout where I add dynamically TableRows. In each TableRow, I add a Button.
I just would like to add some space between my columns (which are my buttons) but I can't figure out how...
I've tried to change all the possible margins but it doesn't work :(
So maybe I made a mistake in my code where I inflate them from XML files:
private void createButtons(final CategoryBean parentCategory) {
final List<CategoryBean> categoryList = parentCategory.getCategoryList();
title.setText(parentCategory.getTitle());
// TODO à revoir
int i = 0;
TableRow tr = null;
Set<TableRow> trList = new LinkedHashSet<TableRow>();
for (final CategoryBean category : categoryList) {
TextView button = (TextView) inflater.inflate(R.layout.button_table_row_category, null);
button.setText(category.getTitle());
if (i % 2 == 0) {
tr = (TableRow) inflater.inflate(R.layout.table_row_category, null);
tr.addView(button);
} else {
tr.addView(button);
}
trList.add(tr);
button.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
CategoryBean firstChild = category.getCategoryList() != null && !category.getCategoryList().isEmpty() ? category
.getCategoryList().get(0) : null;
if (firstChild != null && firstChild instanceof QuestionsBean) {
Intent intent = new Intent(CategoryActivity.this, QuestionsActivity.class);
intent.putExtra(MainActivity.CATEGORY, category);
startActivityForResult(intent, VisiteActivity.QUESTION_LIST_RETURN_CODE);
} else {
Intent intent = new Intent(CategoryActivity.this, CategoryActivity.class);
intent.putExtra(MainActivity.CATEGORY, category);
startActivityForResult(intent, VisiteActivity.CATEGORY_RETURN_CODE);
}
}
});
i++;
}
for (TableRow tableRow : trList) {
categoryLaout.addView(tableRow);
}
}
My button_table_row_category.xml:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/buttonTableRowCategory"
style="#style/ButtonsTableRowCategory"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="#string/validate" />
My table_row_category.xml:
<?xml version="1.0" encoding="utf-8"?>
<TableRow xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/tableRowCategory"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="100dp"
android:gravity="center"
android:padding="5dp" >
</TableRow>
Thank you for your help.
In the case of a TableLayout, Buttons themselves are the columns. That means you have to advise the Buttons to keep some space inbetween. You can do this by using layout parameters. They are much easier to set in XML, but it also works programmatically. It's important that you always use the LayoutParam class of the parent layout of the element where you apply it - in this case the parent is a TableRow:
// Within createButtons():
android.widget.TableRow.LayoutParams p = new android.widget.TableRow.LayoutParams();
p.rightMargin = DisplayHelper.dpToPixel(10, getContext()); // right-margin = 10dp
button.setLayoutParams(p);
// DisplayHelper:
private static Float scale;
public static int dpToPixel(int dp, Context context) {
if (scale == null)
scale = context.getResources().getDisplayMetrics().density;
return (int) ((float) dp * scale);
}
Most dimension attributes in Android take pixels if you set them programmatically - therefore you should use something like my dpToPixel() method. Please, don't EVER use pixel values in Android! You will regret it later on.
If you don't want the rightmost button to have this margin, just check with an IF and don't add the LayoutParam on it.
Solution in XML:
To avoid the LayoutInflater erasing your XML-defined attributes, do this while inflating (taken from Layout params of loaded view are ignored):
View view = inflater.inflate( R.layout.item /* resource id */,
MyView.this /* parent */,
false /*attachToRoot*/);
Alternative: Use a GridView like so: Android: Simple GridView that displays text in the grids
Add Padding Right for a component in the table row component
<TableRow
android:id="#+id/tableRow1">
<TextView
android:id="#+id/textView1"
android:paddingRight="20dp" />
</TableRow>
Try android:layout_marginRight="6dp" this worked for me.
Try Using the setColumnStretchable function of the TableLayout. Give it a columnn index and set its stretchable property to true.
Eg. If you have 3 columns.
TableLayout tblLayout;
tblLayout.setColumnStretchable(0, true);
tblLayout.setColumnStretchable(1, true);
tblLayout.setColumnStretchable(2, true);
The above will give you equal spacing between all 3 columns of the tableLayout.