I've inherited an app which displays a range of values as buttons (0 - 1000) - layout ViewA.
The buttons get indexed, and stuff happens. When the user clicks on some subset of those values, they change color, and get associated with some data which gets exported to a csv file eventually.
This works.
Now the user wants to be able to change between the original set, and a new set (0 - 1500) - layout ViewB.
I created these layouts, and put in a checkbox, and when it's selected it sets the visibility of ViewA to GONE and ViewB to Visible.
These buttons should be indexed just like the original ones, but the screen doesn't get updated before it crashes at observerPosBtn[i] = 42. 1 past the max index of the original array.
What (I think) I'm running into is that the layout is not getting updated until I leave the onChecked method, so the buttons which exceed the original range don't exist and can't get indexed. Then when stuff happens to an out-of-range button the app crashes. (Each range worked properly before adding the checkbox switch.)
Is there a way to "force" an update before I leave onChecked, or where does the code jump back to after the onChecked handler that I could call the 'updateObsPosButtons' function?
The code is structured (broadly) like:
Main Activity{
//definitions and stuff like
onCreate(){}
#Override
protected void onResume() {
super.onResume();
if(refreshSelectionEntries) {
startupConfig();
refreshSelectionEntries = false;
}
}
....
public void startupConfig(){
//onClick etcs
show1500mRangeCheckBox = findViewById(R.id.show1500mRangeCheckBox);
boolean[] observerPosChecked = new boolean[observerPosCount];
show1500mRangeCheckBox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
#Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
if (isChecked) {
currentRange = "1500";
observerPosCount = 61;
} else {
currentRange = "1000";
observerPosCount = 41;
}
updateScreenRange(currentRange);
updateObsPosButtons(currentRange);
}
});
updateObsPosButtons(currentRange); //Called once in initial startup?
....
private void updateScreenRange(String range){
...
final View obs1500Incr50LL = findViewById(R.id.obs1500Incr50LL);
final View obs1500Incr25LL = findViewById(R.id.obs1500Incr25LL);
final View obsIncr50LL = findViewById(R.id.obsIncr50LL);
final View obsIncr25LL = findViewById(R.id.obsIncr25LL);
if (range == "1000") {
obs1500Incr50LL.setVisibility(View.GONE);
obs1500Incr25LL.setVisibility(View.GONE);
obsIncr50LL.setVisibility(View.VISIBLE);
obsIncr25LL.setVisibility(View.VISIBLE);
} else {
obsIncr50LL.setVisibility(View.GONE);
obsIncr25LL.setVisibility(View.GONE);
obs1500Incr50LL.setVisibility(View.VISIBLE);
obs1500Incr25LL.setVisibility(View.VISIBLE);
}
}
private void updateObsPosButtons(String range){
if (range == "1000"){
observerPosChecked = new boolean[observerPosCount];
....
populateObserverPos1000Btn();
//iterate through button range then "do stuff"
for (int observerPosIndex = 0; observerPosIndex < observerPosCount; observerPosIndex++) {
final int i = observerPosIndex;
....
}else{
observerPosChecked = new boolean[observerPosCount];
...
populateObserverPos1500Btn();
//iterate through button range - CRASHES AT i = 42
for (int observerPosIndex = 0; observerPosIndex < observerPosCount; observerPosIndex++) {
final int i = observerPosIndex;
// CHEAT HERE
....
}
} //End StartConfig
private void populateObserverPos1000Btn() {
int oPS_index = 0;
View obsIncr50LL = findViewById(R.id.obsIncr50LL);
View obsIncr25LL = findViewById(R.id.obsIncr25LL);
observerPosBtn[oPS_index++] = obsIncr50LL.findViewById(R.id.observerPosBtn_0);
observerPosBtn[oPS_index++] = obsIncr25LL.findViewById(R.id.observerPosBtn_25);
...
observerPosBtn[oPS_index++] = obsIncr50LL.findViewById(R.id.observerPosBtn_1000);
}
private void populateObserverPos1500Btn() {
int oPS_index = 0;
View obs1500Incr50LL = findViewById(R.id.obsIncr50LL);
View obs1500Incr25LL = findViewById(R.id.obs1500Incr25LL);
observerPosBtn[oPS_index++] = obs1500Incr50LL.findViewById(R.id.observerPosBtn_0);
observerPosBtn[oPS_index++] = obs1500Incr25LL.findViewById(R.id.observerPosBtn_25);
...
observerPosBtn[oPS_index++] = obs1500Incr50LL.findViewById(R.id.observerPosBtn_1500);
}
....
} //End MainActivity
If I "cheat" and add (at CHEAT HERE):
if (observerPosBtn[i] == null){
continue;
}
The updObsPosButtons function does finish and the screen updates, but it seems like if I click on anything above index 41 it crashes.
I think I've included the relevant code.
I've tried a few suggestions I've seen here like notifydatasetchanged, and a run handler... not sure I really can implement them in the current configuration though.
Is there anything I can do without a major refactor?
(I apologize in advance for my poor terminology, code architecture, and general ignorance - my software experience is a little firmware (C) - not Java let alone Android!
I'm the only option to even look it over for this "simple" (haha) update. Either it's something I can take a crack at, or I need to explain the roadblock well enough to outsource it! Thanks so much!)
I have this code:
final ViewTreeObserver[] viewTreeObserver = {myAcco
viewTreeObserver[0].addOnPreDrawListener(
new OnPreDrawListener() {
#Override
public boolean onPreDraw() {
int chipWidth =
myAccountView.getMeasuredWidth()
- myAccountView.getPaddingLeft()
- myAccountView.getPaddingRight();
if (chipWidth > 0) {
myAccountView.setText(
setChipTextWithCorrectLength(
getContext().getString(R.string.og_my_account_desc_long_length),
getContext().getString(R.string.og_my_account_desc_meduim_length),
getContext().getString(R.string.og_my_account_desc_short_length),
chipWidth));
viewTreeObserver[0] = myAccountView.getViewTreeObserver();
if (viewTreeObserver[0].isAlive()) {
viewTreeObserver[0].removeOnPreDrawListener(this);
}
}
return true;
}
});
}
#VisibleForTesting
String setChipTextWithCorrectLength(
String longDesc, String mediumDesc, String shortDesc, int clipWidth) {
if (!isTextEllipsized(longDesc, clipWidth)) {
return longDesc;
}
if (!isTextEllipsized(mediumDesc, clipWidth)) {
return mediumDesc;
}
return shortDesc;
}
private boolean isTextEllipsized(String text, int clipWidth) {
TextPaint textPaint = myAccountView.getPaint();
Toast.makeText(this.getContext(), "textPaint.measureText(text) = "+textPaint.measureText(text)+" clipWidth ="+ clipWidth,
Toast.LENGTH_LONG).show();
return textPaint.measureText(text) > clipWidth;
}
The code should change the text in a textView dynamically when ellipsize is needed.
However, when i run the application I see sometimes the view (clip) width == the long text width and there is still ellipsize in the UI.
I see that happens when the textView is not visible and toggeled to be visible.
Should I calculate the vlip (view) width differently?
Any special way to measure textView before becomes visible?
You should check with
getEllipsisCount()
to see the source of the problem.
Definition : Returns the number of characters to be ellipsized away, or 0 if no ellipsis is to take place.
Yes you can use textView.getLayout().getEllipsisStart(0) ,make sure your textView is singleline , you can do this by adding android:maxLines="1" to your texView. It return the index from which the text gets truncated else returns 0.
Depending on your observation , it might be that the view is not recalculating after changing the visibility to visible again, try to use textView.setVisibility(View.GONE) instead of textView.setVisibility(View.INVISIBLE); .This might cause the preDraw() to be called again.
You might also consider using addOnGlobalLayoutListener() to keep the track of visibilty changes of your view, for reference.
I have an android project where i need to use a switch.
I am using a Button for this.
I have two images one for on state and another for off state. Initially i am giving "off_image" as background of Button. When the user clicks the button the background should change to "on_image" and when again the user clicks it should change back to "off_image".
I am using the below code but it is not working....
Inside onClick method --->
if(button.getBackground().equals(R.drawable.off_image)
button.setImageResource(R.drawable.on_image);
if(button.getBackground().equals(R.drawable.on_image)
button.setImageResource(R.drawable.off_image);
Please treat me as a novice and give detailed solution.
Thank you.
OK, save a global var lastImageResource.
int lastImageResource= R.id.off_image;
now, each time you switch background update it like this:
if(lastImageResource == (R.id.off_image)){
button.setImageResource(R.id.on_image);
lastImageResource = R.id.on_image;
}
if(lastImageResource == (R.id.on_image)){
button.setImageResource(R.id.off_image);
lastImageResource = R.id.off_image;
}
Now it should work.
Use getConstantState()
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) {
drawableDownloaded=holder.iv_catch_status.getDrawable().getConstantState() == context.getDrawable(R.drawable.ic_cloud_download).getConstantState();
}else{
drawableDownloaded=holder.iv_catch_status.getDrawable().getConstantState() == context.getResources().getDrawable(R.drawable.ic_cloud_download).getConstantState();
}
and to set image
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
this.statusImage.setImageDrawable(context.getDrawable(R.drawable.ic_play));
}else{
this.statusImage.setImageDrawable(context.getResources().getDrawable(R.drawable.ic_play));
}
Compare the constant states and use setBackgroundResource() as follows:
final Button button = (Button) findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (button.getBackground().getConstantState().equals(getDrawable(R.drawable.ic_menu_camera).getConstantState())){
button.setBackgroundResource(R.drawable.ic_menu_gallery);
}
else if(button.getBackground().getConstantState().equals(getDrawable(R.drawable.ic_menu_gallery).getConstantState())){
button.setBackgroundResource(R.drawable.ic_menu_camera);
}
}
});
If minSdkVersion is less than 21 use getResources().getDrawable() instead of the getDrawable() method.
How come you are using R.id.image.
These are drawables, use R.drawable.image.
Also,
You are setting, ImageResource, but check background.
Both are different things.
Why don't you just use boolean?
boolean isOn = false
And, then toggle it.
I want my toggle button's ON text to be large and OFF text to be small. But can't do it.. any suggestions? This is what i was trying
mf=(ToggleButton)findViewById(R.id.mf);
if(mf.isEnabled()==true)
{
mf.setTextSize(13);
}
else
mf.setTextSize(8);
Your code has to be called each time you click on your button. so use :
toggleButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if (toggleButton.isChecked()) {
toggleButton.setTextSize(13.0f);
} else {
toggleButton.setTextSize(8.0f);
}
}
});
You can set OnClickListner with a easy method. In your .xml put the option
android:onClick="nameOfMethod"
And then in your code:
public void nameOfMethod(View v){
}
Where v is the togglebutton in this case ( ToggleButton mf = (ToggleButton)v; )
I put the solution here:
mf=(ToggleButton)findViewById(R.id.mf);
if(mf.isChecked())
{
mf.setTextSize(13);
}
else
mf.setTextSize(8);
Use isChecked() instead of isEnabled()
You need to do some debugging.
Firstly:
if(mf.isEnabled()==true)
can be
if (mf.isEnabled())
Does mf.setTextSize(13) on it's own work as expected?
Add some logging:
if (mf.isEnabled())
{
// Add some logging, is this line reached correctly?
mf.setTextSize(13);
}
else
// Add some logging, is this line reached correctly?
mf.setTextSize(8);
Chances are you need to change isEnabled() to isChecked(). isEnabled() means exactly that, whether it's enabled or not. You want to know whether the button has been checked.
I have a multi-line TextView that has android:ellipsize="end" set. I would like to know, however, if the string I place in there is actually too long (so that I may make sure the full string is shown elsewhere on the page).
I could use TextView.length() and find about what the approximate length of string will fit, but since it's multiple lines, the TextView handles when to wrap, so this won't always work.
Any ideas?
You can get the layout of the TextView and check the ellipsis count per line. For an end ellipsis, it is sufficient to check the last line, like this:
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 only works after the layout phase, otherwise the returned layout will be null, so call this at an appropriate place in your code.
textView.getLayout is the way to go but the problem with that is that it returns null if layout is not prepared. Use the below solution.
ViewTreeObserver vto = textview.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
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");
}
}
});
Code snippet for removing the listener (source):
mLayout.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
public void onGlobalLayout() {
scrollToGridPos(getCenterPoint(), false);
mLayout.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
I think the easiest solution to this question is the following code:
String text = "some looooong text";
textView.setText(text);
boolean isEllipsize = !((textView.getLayout().getText().toString()).equalsIgnoreCase(text));
This code assumes that in your XML the TextView set a maxLineCount :)
This worked to me:
textView.post(new Runnable() {
#Override
public void run() {
if (textView.getLineCount() > 1) {
//do something
}
}
});
The most eloquent solution I have found (in Kotlin) is to create an extension function on TextView
fun TextView.isEllipsized() = layout.text.toString() != text.toString()
This is great because it doesn't require knowing what the full string is or worrying about how many lines the TextView is using.
TextView.text is the full text that it's trying to show, whereas TextView.layout.text is what's actually shown on the screen so if they are different it must be getting ellipsized
To use it:
if (my_text_view.isEllipsized()) {
...
}
public int getEllipsisCount (int line):
Returns the number of characters to be ellipsized away, or 0 if no ellipsis is to take place.
So, simply call :
int lineCount = textview1.getLineCount();
if(textview1.getLayout().getEllipsisCount(lineCount) > 0) {
// Do anything here..
}
Since the getLayout() cant be called before the layout is set, use this:
ViewTreeObserver vto = textview.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
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");
}
}
});
And finally do not forget to remove removeOnGlobalLayoutListener when you need it nomore.
lateinit var toggleMoreButton: Runnable
toggleMoreButton = Runnable {
if(reviewTextView.layout == null) { // wait while layout become available
reviewTextView.post(toggleMoreButton)
return#Runnable
}
readMoreButton.visibility = if(reviewTextView.layout.text.toString() != comment) View.VISIBLE else View.GONE
}
reviewTextView.post(toggleMoreButton)
It is some typical case:
comment in 'reviewTextView'
comment can collapsed by some criteria
if comment collapsed you show button 'readMoreButton'
The Kotlin way:
textView.post {
if (textView.lineCount > MAX_LINES_COLLAPSED) {
// text is not fully displayed
}
}
Actually View.post() is executed after the view has been rendered and will run the function provided
Simple Kotlin method. Allows android:ellipsize and android:maxLines to be used
fun isEllipsized(textView: TextView, text: String?) = textView.layout.text.toString() != text
Solution with kotlin extensions:
infoText.afterLayoutConfiguration {
val hasEllipsize = infoText.hasEllipsize()
...
}
Extensions:
/**
* Function for detect when layout completely configure.
*/
fun View.afterLayoutConfiguration(func: () -> Unit) {
viewTreeObserver?.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
viewTreeObserver?.removeOnGlobalLayoutListener(this)
func()
}
})
}
fun TextView.hasEllipsize(): Boolean = layout.getEllipsisCount(lineCount - 1) > 0
it is working for me
if (l != null) {
int lines = l.getLineCount();
if (lines > 0) {
for (int i = 0; i < lines; i++) {
if (l.getEllipsisCount(i) > 0) {
ellipsize = true;
break;
}
}
}
}
If your textview contains multiple paragraphs, using getEllipsisCount will not work for empty lines within it. getEllipsisCount for the last line of any paragraph will return 0.
Really work so, for example, to pass full data to dialog from item of RecyclerView:
holder.subInfo.post(new Runnable() {
#Override
public void run() {
Layout l = holder.subInfo.getLayout();
if (l != null) {
final int count = l.getLineCount();
if (count >= 3) {
holder.subInfo.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
final int c = holder.subInfo.getLineCount();
if (c >= 3) {
onClickToShowInfoDialog.showDialog(holder.title.getText().toString(), holder.subInfo.getText().toString());
}
}
});
}
}
}
});
Combining #Thorstenvv awnser with #Tiano fix, here is the Kotlin version :
val layout = textView.layout ?: return#doOnLayout
val lines = layout.lineCount
val hasLine = lines > 0
val hasEllipsis = ((lines - 1) downTo 0).any { layout.getEllipsisCount(it) > 0 }
if (hasLine && hasEllipsis) {
// Text is ellipsized
}
In Kotlin, you can use the below code.
var str= "Kotlin is one of the best languages."
textView.text=str
textView.post {
val isEllipsize: Boolean = !textView.layout.text.toString().equals(str)
if (isEllipsize) {
holder.itemView.tv_viewMore.visibility = View.VISIBLE
} else {
holder.itemView.tv_viewMore.visibility = View.GONE
}
}
This is simple library for creating textview expandable. Like Continue or Less. This library extended version TextView. Easy to use.
implementation 'com.github.mahimrocky:ShowMoreText:1.0.2'
Like this,
1 https://raw.githubusercontent.com/mahimrocky/ShowMoreText/master/screenshot1.png
2 https://raw.githubusercontent.com/mahimrocky/ShowMoreText/master/screenshot2.png
<com.skyhope.showmoretextview.ShowMoreTextView
android:id="#+id/text_view_show_more"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="#string/text"
/>
In Activity you can use like:
ShowMoreTextView textView = findViewById(R.id.text_view_show_more);
//You have to use following one of method
// For using character length
textView.setShowingChar(numberOfCharacter);
//number of line you want to short
textView.setShowingLine(numberOfLine);
After researching I found the best way for me in Kotlin
To get the ellipsize status the textView must be rendered first, so we have to set the text first, then check the ellipsize logic inside textView.post scope
textView.text = "your text"
textView.post {
var ellipsized: Boolean = textView.layout.text.toString()).equalsIgnoreCase("your text"))
if(ellipsized){
//your logic goes below
}
}
Using getEllipsisCount won't work with text that has empty lines within it. I used the following code to make it work :
message.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
#Override
public boolean onPreDraw() {
if(m.isEllipsized == -1) {
Layout l = message.getLayout();
if (message.getLineCount() > 5) {
m.isEllipsized = 1;
message.setMaxLines(5);
return false;
} else {
m.isEllipsized = 0;
}
}
return true;
}
});
Make sure not to set a maxLineCount in your XML. Then you can check for the lineCount in your code and if it is greater than a certain number, you can return false to cancel the drawing of the TextView and set the line count as well as a flag to save whether the text view is too long or not. The text view will draw again with the correct line count and you will know whether its ellipsized or not with the flag.
You can then use the isEllipsized flag to do whatever you require.
create a method inside your TextViewUtils class
public static boolean isEllipsized(String newValue, String oldValue) {
return !((newValue).equals(oldValue));
}
call this method when it's required eg:
if (TextViewUtils.isEllipsized(textviewDescription.getLayout().getText().toString(), yourModelObject.getDescription()))
holder.viewMore.setVisibility(View.VISIBLE);//show view more option
else
holder.viewMore.setVisibility(View.GONE);//hide
but textView.getLayout() can't call before the view(layout) set.