Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

"requestLayout() improperly called by..." error on Android 4.3

I have a simple Custom TextView that sets custom font in its constructor like the code below

public class MyTextView extends TextView {

    @Inject CustomTypeface customTypeface;

    public MyTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        RoboGuice.injectMembers(context, this);
        setTypeface(customTypeface.getTypeface(context, attrs));
        setPaintFlags(getPaintFlags() | Paint.SUBPIXEL_TEXT_FLAG);
    }
}

It works fine from Gingerbread through JB 4.2. But the adb logcat is flooded with the following messages when I show my custom textview on Android 4.3 phone.

10-05 16:09:15.225: WARN/View(9864): requestLayout() improperly called by com.cmp.views.MyTextView{42441b00 V.ED.... ......ID 18,218-456,270 #7f060085 app:id/summary} during layout: running second layout pass
10-05 16:09:15.225: WARN/View(9864): requestLayout() improperly called by com.cmp.views.MyTextView{423753d0 V.ED.... ......ID 26,176-742,278 #7f060085 app:id/summary} during layout: running second layout pass

I notice, it does slow down UI a bit. Any ideas why it's happening on 4.3?

Appreciate your help.

like image 724
Sanjay Avatar asked Oct 03 '13 23:10

Sanjay


3 Answers

I found where this bug occurs in my app. Although occurrence of this is not found in the code you provided (it may help if you have done this elsewhere in your code), it will hopefully help others fix this impossible-to-trace problem.

I had a line (not added by me, of course):

myView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
    @Override
    public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
        //this would then make a call to update another view's layout.
    }
});

In my app, I did not need any listener, so removing this entire block fixed this problem. For those that need something like this, remember to remove the listener after the layout has changed (inside of this callback).

like image 178
Phil Avatar answered Oct 25 '22 22:10

Phil


Looking into the Android source, this problem is described in a little more detail:

requestLayout() was called during layout. If no layout-request flags are set on the requesting views, there is no problem. If some requests are still pending, then we need to clear those flags and do a full request/measure/layout pass to handle this situation.

It appears that the problem may be related to Roboguice; see issue #88. The suggested solution there is to use @InjectView:

You can now use @InjectView from inside a view class. Just call Injector.injectMembers() after you've populated your view, ala:

public class InjectedView extends FrameLayout {

    @InjectView(R.id.view1) View v;

    public InjectedView(Context context) {
        super(context);
        final View child = new View(context);
        child.setId(R.id.view1);
        addView(child);

        RoboGuice.getInjector(context).injectMembers(this);
    }
}

Perhaps you should consider migrating RoboGuice.injectMembers(context, this) to the declaration of your View object using the @InjectView annotation.

like image 24
Paul Lammertsma Avatar answered Oct 25 '22 23:10

Paul Lammertsma


I fixed these warnings in my custom ListView item (LinearLayout subclass). This class implements Checkable, and has a setChecked(boolean checked) method that is called to indicate whether the item is checked:

@Override
public void setChecked(boolean checked) {
    mChecked = checked;
    TextView textView = (TextView)this.findViewById(R.id.drawer_list_item_title_text_view);
    if(mChecked) {
        textView.setTypeface(Typeface.createFromAsset(getContext().getAssets(), "font/Andada-Bold.ttf"));
    }
    else {
        textView.setTypeface(Typeface.createFromAsset(getContext().getAssets(), "font/Andada-Regular.ttf"));
    }
}

I visually indicate the checked state by calling setTypeFace() on a textView in my view, toggling between regular and bold typefaces. These setTypeFace() calls were causing the warnings.

To fix the problem, I created instance variables for the Typefaces in the class constructor and used them later, when changing the typeface, rather than calling Typeface.createFromAsset(...) every time:

private Typeface mBoldTypeface;
private Typeface mRegularTypeface;

public DrawerView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initTypefaces();
}

private void initTypefaces() {
        this.mBoldTypeface = Typeface.createFromAsset(getContext().getAssets(), "font/Andada-Bold.ttf");
        this.mRegularTypeface = Typeface.createFromAsset(getContext().getAssets(), "font/Andada-Regular.ttf");
}

@Override
public void setChecked(boolean checked) {
    mChecked = checked;
    TextView textView = (TextView)this.findViewById(R.id.drawer_list_item_title_text_view);
    if(mChecked) {
        textView.setTypeface(mBoldTypeface);
    }
    else {
        textView.setTypeface(mRegularTypeface);
    }
}

This is a pretty specific scenario, but I was pleasantly surprised to find a fix and maybe someone else is in the same situation.

like image 1
kurteous Avatar answered Oct 25 '22 21:10

kurteous