ConstraintLayout intermittently fails to layout correctly when a view is set from GONE to VISIBLE shortly after an activity is resumed:
<android.support.constraint.ConstraintLayout
android:id="#+id/constraint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="#string/appbar_scrolling_view_behavior">
<TextView
android:id="#+id/text1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="#+id/text2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintLeft_toRightOf="#id/text1"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
override fun onResume() {
super.onResume()
text1.text = ""
text1.visibility = View.GONE
text2.text = ""
text2.visibility = View.GONE
text1.postDelayed({
text1.text = "Hello"
text1.visibility = View.VISIBLE
text2.text = "World"
text2.visibility = View.VISIBLE
}, 100
)
}
Full source code here
Instrumenting the TextView class reveals that the TextView instances are measured correctly but their width is set to 0 when they are laid out.
I wonder if the ConstraintLayout LinearSystem is non-deterministic. Are there maps that are iterated over where the iteration order is undefined? (I've seen this with Cassowary)
I'm looking to your statement in github page:
ConstraintLayout intermittently fails to layout correctly when a view is set from GONE to VISIBLE shortly after an activity is resumed
I've checked out your project and changed 100ms to 1000ms.
Here's the output:
It seems to me, that you expect that the moment you perform textview.setVisibility(View.GONE) you expect the view to be not visible. That's not the way android works. You are merely posting an event to the MessageQueue that would be handled later by Looper, and this 100ms is not enough for human eye to see those changes happening.
This was a bug in the ConstraintLayout fixed in constraint-layout:1.1.0-beta2 https://issuetracker.google.com/issues/65613481
Related
I have quite a complex problem to do with Android views. I am creating a paint application, and I have two views: a transparent background view and the pixel art board.
For both views, I want the height and width to be calculated off of the distance between view A and B:
Instead of calculating the distance between these two views, I simply 'constraint' a view in the middle like so, and then extract its height by using its measuredHeight property (and yes, you could also calculate the distance between view A and B in the code, but my problem still remains when I try that):
Now, here's the XML code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/fragment_background_color_daynight"
tools:context=".activities.canvas.CanvasActivity">
<View
android:id="#+id/activityCanvas_topView"
android:layout_width="match_parent"
android:layout_height="90dp"
android:background="#color/fragment_background_color_daynight"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.therealbluepandabear.pixapencil.customviews.colorswitcherview.ColorSwitcherView
android:id="#+id/activityCanvas_colorSwitcherView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
app:isPrimarySelected="false"
app:layout_constraintBottom_toBottomOf="#+id/activityCanvas_colorPickerRecyclerView"
app:layout_constraintEnd_toEndOf="#+id/activityCanvas_topView"
app:layout_constraintTop_toTopOf="#+id/activityCanvas_colorPickerRecyclerView"
app:primaryColor="#android:color/holo_green_dark"
app:secondaryColor="#color/black" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/activityCanvas_colorPickerRecyclerView"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="#+id/activityCanvas_topView"
app:layout_constraintEnd_toStartOf="#+id/activityCanvas_colorSwitcherView"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/activityCanvas_primaryFragmentHost"
tools:listitem="#layout/color_picker_layout" />
<FrameLayout
android:id="#+id/activityCanvas_distanceContainer"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/activityCanvas_tabLayout"
app:layout_constraintEnd_toEndOf="#+id/activityCanvas_primaryFragmentHost"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/activityCanvas_topView" />
<com.google.android.material.card.MaterialCardView
android:id="#+id/fragmentOuterCanvas_canvasFragmentHostCardViewParent"
style="#style/activityCanvas_canvasFragmentHostCardViewParent_style"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:elevation="1dp"
app:layout_constraintBottom_toTopOf="#+id/activityCanvas_tabLayout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/activityCanvas_topView">
<!-- At runtime, the width and height will be calculated -->
<com.therealbluepandabear.pixapencil.customviews.transparentbackgroundview.TransparentBackgroundView
android:id="#+id/activityCanvas_transparentBackgroundView"
android:layout_width="0dp"
android:layout_height="0dp" />
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.tabs.TabLayout
android:id="#+id/activityCanvas_tabLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:tabStripEnabled="false"
app:layout_constraintBottom_toTopOf="#+id/activityCanvas_viewPager2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_tools_str" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_filters_str" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_color_palettes_str" />
<com.google.android.material.tabs.TabItem
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/activityCanvas_tab_brushes_str" />
</com.google.android.material.tabs.TabLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/activityCanvas_viewPager2"
android:layout_width="0dp"
android:layout_height="110dp"
app:layout_constraintBottom_toBottomOf="#+id/activityCanvas_primaryFragmentHost"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:id="#+id/activityCanvas_coordinatorLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<FrameLayout
android:id="#+id/activityCanvas_primaryFragmentHost"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Of course, when it comes to calculating, I thought it would be best practise to utilize AndroidX's OneShotPreDrawListener, like so:
OneShotPreDrawListener.add(binding.root) {
binding.activityCanvasTransparentBackgroundView!!.setViewWidth(binding.activityCanvasDistanceContainer!!.measuredHeight)
binding.activityCanvasTransparentBackgroundView!!.setViewHeight(binding.activityCanvasDistanceContainer!!.measuredHeight)
}
Now, for some reason, the result looks like so:
Why is this the case!
I did some debugging, and when I log the height of view C, I get the following:
This is wrong. So, as an experiment, I added a GlobalLayoutListener to detect when exactly the view's constraints get resolved:
binding.activityCanvasDistanceContainer?.viewTreeObserver?.addOnGlobalLayoutListener( object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
Log.d("M_LOG", binding.activityCanvasDistanceContainer?.measuredHeight.toString())
}
})
Result:
So, after the first couple of times it's 438, until after the 4th/5th time it shoots up to 1000.
I am really confused why this is happening, as I want to run the event when the constraints have been fully resolved and calculated, but using OneShotPreDrawListener (or any other alternative) is just running the event when the view has been drawn, but not yet when it has been positioned properly.
I am confused what to do. How can I run an event when the view's constraints have been fully calculated?
Edit for Chetichamp:
I have debugged this and I think I can reproduce this error and tell you in what scenario it occurs and in which scenario it does not.
Basically in my app, prior to activity creation, I have a fragment called NewProjectFragment, which looks like so:
Here's the code for when the 'Done' button is pressed:
binding.fragmentNewCanvasDoneButton.setOnClickListener {
checkForTitleError()
checkForWidthError()
checkForHeightError()
if (!invalidTitle && !invalidWidth && !invalidHeight) {
try {
val title =
binding.fragmentNewCanvasProjectTitleTextInputEditText.text.toString()
val widthValue: Int =
binding.fragmentNewCanvasWidthTextInputEditText.text.toString().toInt()
val heightValue: Int =
binding.fragmentNewCanvasHeightTextInputEditText.text.toString().toInt()
if (widthValue + heightValue >= 2000 && (requireActivity() as MainActivity).showLargeCanvasSizeWarning) {
val frameLayout: FrameLayout =
this#setOnClickListeners.activity?.layoutInflater?.inflate(
R.layout.dont_show_large_canvas_warning_again_checkbox,
requireView().findViewById(android.R.id.content),
false
)
as FrameLayout
val checkBox = frameLayout.getChildAt(0) as MaterialCheckBox
requireActivity().showDialog(
getString(R.string.generic_warning_in_code_str),
getString(R.string.dialog_large_canvas_warning_text_in_code_str),
getString(R.string.dialog_large_canvas_warning_positive_button_text_in_code_str),
{ _, _ ->
if (checkBox.isChecked) {
(requireActivity() as MainActivity).showLargeCanvasSizeWarning =
false
with((requireActivity() as MainActivity).sharedPreferenceObject.edit()) {
putBoolean(
StringConstants.Identifiers.SHARED_PREFERENCE_SHOW_LARGE_CANVAS_SIZE_WARNING_IDENTIFIER,
(requireActivity() as MainActivity).showLargeCanvasSizeWarning
)
apply()
}
}
caller.onDoneButtonPressed(
title,
widthValue,
heightValue,
paramSpotLightInProgress
)
},
getString(R.string.dialog_unsaved_changes_negative_button_text_in_code_str),
{ _, _ ->
},
frameLayout
)
} else {
caller.onDoneButtonPressed(
title,
widthValue,
heightValue,
paramSpotLightInProgress
)
}
} catch (exception: Exception) {
HapticFeedbackWrapper.performHapticFeedback(binding.fragmentNewCanvasDoneButton)
}
} else {
HapticFeedbackWrapper.performHapticFeedback(binding.fragmentNewCanvasDoneButton)
}
}
As you can see, it has a listener, so the code for the listener, which is in MainActivity, is like so (maybe this is causing the issue? and I just don't need a listener like this? I don't know if you think this is why I get the issue):
fun MainActivity.extendedOnDoneButtonPressed(projectTitle: String, width: Int, height: Int, spotLightInProgress: Boolean) {
startActivity(
Intent(this, CanvasActivity::class.java)
.putExtra(StringConstants.Extras.PROJECT_TITLE_EXTRA, projectTitle)
.putExtra(StringConstants.Extras.WIDTH_EXTRA, width)
.putExtra(StringConstants.Extras.HEIGHT_EXTRA, height)
.putExtra(StringConstants.Extras.SPOTLIGHT_IN_PROGRESS_EXTRA, spotLightInProgress)
)
}
Now, what I come to the conclusion is that all of this extra work is causing a delay, because when you simply tap on a pre-existing project, we can see that the intent is much simpler:
fun MainActivity.extendedOnCreationTapped(param: PixelArt) {
startActivity(
Intent(this, CanvasActivity::class.java)
.putExtra(StringConstants.Extras.INDEX_EXTRA, pixelArtData.indexOf(param))
.putExtra(StringConstants.Extras.PROJECT_TITLE_EXTRA, param.title))
}
With a simple intent like so, the problem doesn't get reproduced, and it sizes properly.
What I relaized is that the work done in NewProject fragment is causing a delay, and when I simply scrap out the work and perfom a simple intent, the problem is 'fixed'. I don't know how to fix this but hopefully it can help with finding a solution.
Debugging even further
When I debug the issue even further, I notice something strange. The measuredHeight of the root layout jumps up by one thousand:
This is not observed when the creation is tapped with the simple intent. I am lost for words as to how strange this bug is, I've never seen this in my life.
There is something going on with your code that is touching your views that is causing the multiple layouts. Without that code being posted, it is impossible to say what.
To help you with debugging, set the one shot to the following:
OneShotPreDrawListener.add(binding.root) {
Log.d("Applog", "OneShot")
binding.activityCanvasTransparentBackgroundView.layoutParams.width =
binding.activityCanvasDistanceContainer.measuredWidth
binding.activityCanvasTransparentBackgroundView.layoutParams.height =
binding.activityCanvasDistanceContainer.measuredHeight
binding.activityCanvasTransparentBackgroundView.requestLayout()
}
In my test, when placing this in the onCreateView() function of a fragment, "OneShot" is logged just once and the size of the view is adjusted as you say it should be. (btw, requestLayout() was required in this test.)
I would take a look at any other code that touches your views to see if something else is triggering the multiple calls: maybe setWidth() or setHeight().
I will add that I have noticed in the past that ConstraintLayout will call the global layout listener multiple times. Usually, I would say, the listener is removed on the first call, so this behavior would not be noticed.
Instead of the global layout listener or the predraw listener, you could try a layout listener:
binding.root.doOnLayout {
// Your code here
}
or
binding.root.doOnNextLayout {
// Your code here
}
doOnNextLayout is guaranteed to be called only once, while doOnLayout may be called multiple times.
Wow guys.
I finally found a solution, after 12 hours of nonstop debugging, almost going clinically insane, and logging and destroying my codebase.
I realized that the keyboard from the NewProjectFragment was cutting the view, causing the bug. Holy moly... I am ecstatic that I finally found why this is happening but at the same time shocked how I didn't discover this before!
The solution was to add the following to the activity:
<activity
android:name="com.therealbluepandabear.pixapencil.activities.canvas.CanvasActivity"
android:windowSoftInputMode="stateHidden" // this
android:hardwareAccelerated="true"
android:exported="false" />
Prior to this, the windowSoftInputMode was adjustResize -- causing the bug.
Looks like ConstraintSet is finding hard to cope up with Start/End constrains.
This example is taken from Google samples.
Github: android-ConstraintLayoutExamples
When you replace Left & Right constrains with Start & End, ConstraintSet - not working properly, It's working with Left/Right constrains only.
For example replace
layout_constraintStart_toStartOf with layout_constraintLeft_toLeftOf & replace
layout_constraintEnd_toEndOf with layout_constraintRight_toRightOf
in following files:
constraintset_example_main.xml
constraintset_example_big.xml
Behaviour:
onClick of image:
private ConstraintSet mConstraintSetNormal = new ConstraintSet();
private ConstraintSet mConstraintSetBig = new ConstraintSet();
public void toggleMode(View v) {
TransitionManager.beginDelayedTransition(mRootLayout);
mShowBigImage = !mShowBigImage;
applyConfig();
}
private void applyConfig() {
if (mShowBigImage) {
mConstraintSetBig.applyTo(mRootLayout);
} else {
mConstraintSetNormal.applyTo(mRootLayout);
}
}
By default Android studio uses start/ end constrains hence it's I want to know root cause and possible fix.
Or Is this a bug with ConstrainSet itself?
This does look like a problem with ConstraintSet, but let's see. The following analysis is based upon the sample project with the link that you supplied.
In the sample project, I have updated ConstraintLayout to the most recent version:
compile 'com.android.support.constraint:constraint-layout:1.1.0-beta5'
I did this in case we are trying to track down an issue that has already been addressed. I also updated the layout constraintset_example_big and replaced all left/right constraints with start/end constraints as follows:
constraintset_example_big.xml
<android.support.constraint.ConstraintLayout
android:id="#+id/activity_constraintset_example"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ImageView
android:id="#+id/imageView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="24dp"
android:layout_marginStart="24dp"
android:layout_marginTop="24dp"
android:onClick="toggleMode"
android:scaleType="centerCrop"
android:src="#drawable/lake"
app:layout_constraintDimensionRatio="h,16:9"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:contentDescription="#string/lake_tahoe_image" />
<TextView
android:id="#+id/textView9"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/lake_tahoe_title"
android:textSize="30sp"
app:layout_constraintStart_toStartOf="#+id/imageView"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="#+id/imageView" />
<TextView
android:id="#+id/textView11"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="#string/lake_discription"
app:layout_constraintStart_toStartOf="#+id/textView9"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="#+id/textView9"
app:layout_constraintEnd_toEndOf="#+id/imageView"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginBottom="16dp"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintVertical_bias="0.0" />
</android.support.constraint.ConstraintLayout>
With these changes in place, this is what we see.
This is clearly not right. It is supposed to look like this after the transition.
After some debugging, I tracked the issue down to this line in ConstraintSetExampleActivity.java:
mConstraintSetBig.load(this, R.layout.constraintset_example_big);
ConstraintSet#load() seems to be straightforward, but if we replace the code above with an explicit inflation of the layout followed by a clone of the ConstraintSet on the inflated layout as follows:
// mConstraintSetBig.load(this, R.layout.constraintset_example_big);
ConstraintLayout cl = (ConstraintLayout) getLayoutInflater().inflate(R.layout.constraintset_example_big,null);
mConstraintSetBig.clone(cl);
We see this behavior in the app which is much better.
So my takeaway is that ConstraintSet#load() has a problem with start/end constraints. The workaround is to inflate the ConstraintLayout then do a clone.
ConstraintSetExampleActivity#onCreate()
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.constraintset_example_main);
mRootLayout = (ConstraintLayout) findViewById(R.id.activity_constraintset_example);
// Note that this can also be achieved by calling
// `mConstraintSetNormal.load(this, R.layout.constraintset_example_main);`
// Since we already have an inflated ConstraintLayout in `mRootLayout`, clone() is
// faster and considered the best practice.
mConstraintSetNormal.clone(mRootLayout);
// Load the constraints from the layout where ImageView is enlarged.
// Toggle the comment status on the following three lines to fix/break.
// mConstraintSetBig.load(this, R.layout.constraintset_example_big);
ConstraintLayout cl = (ConstraintLayout) getLayoutInflater().inflate(R.layout.constraintset_example_big,null);
mConstraintSetBig.clone(cl);
if (savedInstanceState != null) {
boolean previous = savedInstanceState.getBoolean(SHOW_BIG_IMAGE);
if (previous != mShowBigImage) {
mShowBigImage = previous;
applyConfig();
}
}
}
This issue is known and will be fixed in the 1.1 beta 6 release
https://issuetracker.google.com/issues/74253269
For those who faces issues like constraint set clone not working properly, my layout was not updating to new constraints when i called clone and applyTo methods after an api call, turns out it was due to a loading dialog i showed before the change that caused the error.
I am trying to set the visibility of a Textview depending on the response returned by Retrofit . onFailure, I am setting the visibility to visible tv_no_cat.setVisibility(View.VISIBLE); However, this does not work. I am doing the same with a ContentLoadingProgressBar and it's working appropriately. Here is my code
public void onFailure(Call<List<MoviesCategory>> call, Throwable t) {
tv_no_cat.setVisibility(View.VISIBLE); ////This does not work
Boolean x = tv_no_cat.getVisibility() == View.VISIBLE;
Toast.makeText(getContext(), "Network Error "+x, Toast.LENGTH_LONG).show(); //This shows true
tv_no_cat.setTextColor(Color.BLUE);
videoLoadingPb.setVisibility(View.GONE); //This works
}
My guess is that am trying to set the textview which is on UI thread from another thread. If so, why is it working for the ContentLoadingProgressBar?
Here is my xml for the 2 views
<android.support.v4.widget.ContentLoadingProgressBar
android:id="#+id/loading_categories"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_centerVertical="true"
android:layout_gravity="center"
android:visibility="visible" />
<TextView
android:layout_below="#id/loading_categories"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="No Movie Category Found"
android:id="#+id/tv_no_cat"
android:textAlignment="center"
android:layout_gravity="bottom"
android:visibility="gone"
/>
Any help on how I can set the textview Visibility to VISIBLE is welcome.
From the docs:
On Android, callbacks will be executed on the main thread...
So this is not your issue, and I'm doubtful that it's related to Retrofit, especially when it works for another view. Please make sure that there are no problem in your xml design, and that there are no other methods that affect this view's visibility.
are registering Textview inside Oncreate or some where else ?
And this code you are using inside activity of fragment ?
Please check this ...
tv_no_cat=(TextView)findViewById(R.id.tv_no_cat);(Inside Activity)
tv_no_cat=(TextView)rootView.findViewById(R.id.tv_no_cat);(Inside Fragment )
The following error occurs when I attempt to inflate a layout within a ListView:
requestLayout() improperly called by android.widget.TextView{...} during layout: running second layout pass
I am attempting to inflate a layout within a ListView as follows:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
if(convertView == null){
LayoutInflater inflater = (LayoutInflater) musicActivity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
convertView = inflater.inflate(R.layout.list_item, parent, false);
...
}else{...}
}
The layout being inflated can look as simple as the following, and will still produce the error
<TextView
android:id="#+id/txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="#dimen/txt_size"/>
I have looked into similar questions, and no solutions found seem to work Question 1, Question 2, Question 3.
Does anyone know what causes this type of error? Any troubleshooting advice? For more context, this ListView is displayed within a Fragment within a ViewPager
UPDATE
Here is the full XML Layout (minus a bunch of attributes), that still results in the problem
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/txt1"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/txt2"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/txt3"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="#+id/txt4"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
Based on this, I would think the XML itself is not a problem, unless it has to do with the fact that I am using a ViewPager and Fragments
This issue seems to be a bug in the android implementation, please see: https://code.google.com/p/android/issues/detail?id=75516
Activating the fast scroll feature of a ListView in your code via ListView.setFastScrollEnabled(true) will trigger this bug and you'll start seeing the
requestLayout() improperly called by android.widget.TextView{...}
during layout: running second layout pass
message in your console.
This bug must have been introduced in one of the KitKat (4.4.x) updates, as I've not seen it with the initial KitKat (4.4.0) release. Apart from the ugly console spamming with the debug message from above, there seem to be no other impacts (maybe performance in some cases, which I haven't tested).
Cheers
PS: it's not the first time that the fast scroll feature is bugged, e.g. https://code.google.com/p/android/issues/detail?id=63545, 63545 was fixed in KitKat 4.4.3 but 75516 poped up thereafter --> seems to be a vexed subject for google ;-)
EDIT May 12 2015:
I updated my Nexus 7 to Android 5.1 some minutes ago (was Running 5.0 before) and stopped seeing this issue in this new version. As the appearance of the FastScroll indicator also changed in 5.1, I assume that google fixed this issue or at least commented out those ugly lines that spammed the console...
75516 & 82461 are still 'unresolved', but I guess that those refer to the same issue, that's now resolved in 5.1.
The problem is that while the method getView() of your adapter is displaying your layout some other code is trying to access this view to display it, resulting in a collision.
Note that some methods, that maybe you don't take care of (like setScale(), setTypeFace()) indeed call requestLayout(), so it would be interesting what you are doing after your inflate statement.
For me this issue was occurring upon a setLayoutParams() call. The solution was posting a runnable on the looper:
// java
imageView.post(new Runnable() {
#Override public void run() {
imageView.setLayoutParams(params);
}
});
// kotlin
post(Runnable { imageView.setLayoutParams(params) })
I fixed this issue by disabling fastScroll on the ListView in the XML.
<ListView
android:id="#+id/mListview"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:fastScrollEnabled="false"
/>
In my case (Samsung Galaxy S4, API 21) this happened in ListView with EditTexts. I have a listener for field validation. Something like:
edit.setOnFocusChangeListener(new View.OnFocusChangeListener() {
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
error.setVisibility(View.INVISIBLE);
error.setText("");
} else {
String s = edit.getText().toString();
if (s.isEmpty()) {
error.setText("Error 1");
} else if (s.length() < 2 || s.length() > 100) {
error.setText("Error 2");
}
error.setVisibility(View.VISIBLE);
}
}
});
After settinging focus in one of these EditTexts an above check is called. After that a TextView will change (the TextView contains an error message and lies over the EditText).
Setting focus to the second or the third EditText led to permanent request of the first EditText and return to current. An applications runs in infinite loop of requests (focus edittext 1, unfocus edittext 1, focus 3, unfocus 3, focus 1, etc).
I tried to set listView.setFastScrollEnabled(false). Also I tried a requestLayout() of some elements like in https://github.com/sephiroth74/HorizontalVariableListView/issues/93 with no chances.
Currently I made that TextView of fixed width and height in XML:
<TextView
android:id="#+id/error"
android:layout_width="match_parent" (or "200dp", but not "wrap_content")
android:layout_height="20dp"
.../>
After some experiments I noticed that a height of 20dp can be replaced with "wrap_content". But if a text is too long that divides into 2 lines, the application again catches in the infinite loop. So, android:singleLine="true" will help. It is deprecated, but amazingly android:maxLines="1" with android:lines="1" don't help as they again request layout.
Eventually we have:
<TextView
android:id="#+id/error"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="#f00"
android:textSize="20sp"
tools:text="Error message"/>
That's not a good solution, but at least it breaks the infinite loop.
This might happen if you are using some 3rd party extension of ListView. Replace that with standard ListView and check if it still throws the error.
I had similar problem. Please check Android layout: running second layout pass and my answer.
I had the same issue with Kitkat 4.4.4 on Motorola X with Genymotion. In my case the list item is a simple CheckedTextView and the error occurred in AppCompatCheckedTextView.
As a normal implementation I inflated the item from XML layout file like below:
if (convertView == null) {
convertView = inflater.inflate(R.layout.checkable_list_entry, parent, false);
}
After some trying I found out that this has something to do with XML inflation. I don't know the root cause, but as a solution I decided to inflate the list item by code and set all the properties by code too.
It ended up like this:
CheckedTextView view;
if (convertView == null) {
view = new CheckedTextView(parent.getContext());
view.setMinHeight(getResources().getDimensionPixelSize(R.dimen.default_touch_height));
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
view.setTextAppearance(R.style.SectionEntry);
} else {
view.setTextAppearance(parent.getContext(), R.style.SectionEntry);
}
view.setBackgroundResource(R.drawable.form_element);
view.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
view.setLayoutParams(new AbsListView.LayoutParams(AbsListView.LayoutParams.MATCH_PARENT, AbsListView.LayoutParams.WRAP_CONTENT));
} else {
view = (CheckedTextView) convertView;
}
I had a problem with the same warning log :
requestLayout() improperly called by android.support.v7.widget.AppCompatTextView {...} during layout: running second layout pass
I was working with recylcerview and going to update it with new data.
The only solution that worked for me is as below :
Step(1). Remove current data :
public void removeAll() {
items.clear(); //clear list
notifyDataSetChanged();
}
Step(2). When you want to populate the recyclerview with new data, first set a new LayoutManager to recyclerview again:
private void initRecycleView() {
recyclerView.setHasFixedSize(true);
recyclerView.setLayoutManager(new LinearLayoutManager(activity, LinearLayoutManager.VERTICAL, false));
}
Step(3). Update the recyclerview with new data. for example :
public void refreshData(List newItems) {
this.items = newItems;
notifyItemRangeChanged(0, items.size());
}
Try taking off the textSize from the xml and setting it in Java code. I think that's causing it to be laid out twice.
In my case this warning prevented a button from showing up in API 21 devices. The button visibility was previously set to GONE.
The only workaround I got it was setting to INVISIBLE instead of GONE for API 21. It wasn't a real solution but it was acceptable for me.
I only post this because it can be useful from somebody.
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP) {
theButton.setVisibility(View.INVISIBLE);
}
else {
theButton.setVisibility(View.GONE);
}
Sometimes you maybe already fixed the issue but it still keeps same error, so you need to close visual studio then delete all bin and obj folders from your projects, then uninstall the app from the emulator. then walah!! everything will works fine
I solved the problem like this:
mHolder.txt_fword.setTextSize(22);
mHolder.txt_farth.setTextSize(22);
mHolder.txt_fdef.setTextSize(22);
mHolder.txt_fdef2.setTextSize(22);
mHolder.txt_frem.setTextSize(22);
//if (fdef2.get(pos).equals("")) mHolder.txt_fdef2.setVisibility(View.GONE);
//if (frem.get(pos).equals("")) mHolder.txt_frem.setVisibility(View.GONE);
issue is .setVisibility(View.GONE); , change to .setVisibility(View.VISIBLE);
I have a bug with my activity.
I have three view stubs in my linear layout like so -
<ViewStub
android:id="#+id/index_1"
android:layout="#layout/index_edittext"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<ViewStub
android:id="#+id/index_2"
android:layout="#layout/index_edittext"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
<ViewStub
android:id="#+id/index_3"
android:layout="#layout/index_edittext"
android:layout_width="fill_parent"
android:layout_height="wrap_content" />
my onCreate conditionally checks what to inflate:
for (int i = 0; i < 3; i++) {
int id = convertIndexToId(i); //will turn i into R.id.index_1
ViewStub stub = findViewById(id);
if (bShouldBeSpinner) {
stub.setLayoutResource(R.layout.index_spinner);
View root = stub.inflate();
Spinner spin = (Spinner)root.findViewById(R.id.my_spinner);
spinner.setAdapter(adapter);
spinner.setSelection(0);
}
else {
stub.setLayoutResource(R.layout.index_edittext);
View root = stub.inflate();
EditText et = (EditText)root.findViewById(R.id.my_edittext);
//et.phoneHome(); just kidding
et.setText(String.valueOf(System.currentTimeMillis()));
}
}
I force bShouldBeSpinner to false. The output of the edittext's is as follows:
1300373517172
1300373517192
1300373517221
However, when I rotate the screen and onCreate is called a second time the output is this:
1300373517221
1300373517221
1300373517221
Initially that made me think you should only inflate the view once, and the heirarchy is kept inbetween onCreate's... however when i only run it the first time the second time no views are shown for the stubs.
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<Spinner style="#style/SearchInput" android:id="#+id/my_spinner" />
</LinearLayout>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<EditText style="#style/SearchInput" android:id="#+id/my_edittext" />
</LinearLayout>
I feel the documentation is assuming something that I did not notice or am missing. Does anyone see what I am doing wrong?
EDIT
I added to the view stubs android:inflatedId="index_1_root"... etc
it is the strangest thing, when I add these lines after the for loop:
EditText v = indexRoot1.findViewById(R.id.index_edit_text);
Log.d(TAG, "EditTExt: " + v);
EditText v2 = indexRoot2.findViewById(R.id.index_edit_text);
Log.d(TAG, "EditTExt: " + v2);
the output says (I believe) they are references to different EditTexts.
EditTExt: android.widget.EditText#47210fe8
EditTExt: android.widget.EditText#47212ba8
So they are getting inflated again, but the text is set to what the last edittext was set to on the first pass.
There may be some issues when recreating views of different types with the same id.
ViewStub is replaced by inflated view.
I suggest using
setInflatedId(int inflatedId)
to distinguish inflated views.
Hope that help.
Instead of using ViewStubs, I added an id to the root of those stubs (android:id="index_roots") and used
view.addView( (isSpinner) ?
new Spinner(this) : new EditText(this) );
to fix this problem, I will however not accept this answer right away, I'll allow others to answer using the method I was going for.