28

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.

Ahmad
  • 69,608
  • 17
  • 111
  • 137
Sanjay
  • 281
  • 1
  • 3
  • 3
  • Have you tried moving `setTypeface()` and/or `setPaintFlags()` later in the view's lifecycle, like `onFinishInflate()` or something? My guess is that `setTypeface()` is triggering a `requestLayout()`, as they probably were not expecting it to be called in a view constructor. – CommonsWare Oct 03 '13 at 23:52
  • I tried to move it to onFinishInflate(), that didn't help either. I see those requestLayout() messages in the logs – Sanjay Oct 04 '13 at 18:41
  • Is the MyTextView's only purpose setting a custom font? Creating a custom view to set a cutom font isn't a good solution. – GareginSargsyan Dec 26 '13 at 22:07
  • It also sets the paint flags, like add the Paint.SUBPIXEL_TEXT_FLAG. I didn't want to set the paint flag multiple places as this text view is used in a lot of layouts in the app. – Sanjay Jan 08 '14 at 18:53
  • I'm seeing this as well, and I am only calling `setTypeface()`. – Carl Anderson Jan 28 '14 at 19:42
  • This happens on KitKat too - for no apparent reason (I'm not even subclassing `TextView` or setting the *Typeface*). I could not find anything on how to fix it - but you can ignore the *logcat* output by adding this regex to the search field: `tag:^(?!(View|dalvik))`. – Phil Mar 28 '14 at 17:23

5 Answers5

6

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).

Rick
  • 1,177
  • 1
  • 19
  • 27
Phil
  • 35,852
  • 23
  • 123
  • 164
2

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.

Community
  • 1
  • 1
Paul Lammertsma
  • 37,593
  • 16
  • 136
  • 187
  • 2
    I'm not using Roboguice at all, just setting a Typeface in my onCreate. Any thoughts on why that would be occurring in my case, or any workarounds? – Carl Anderson Feb 03 '14 at 18:00
  • [Inspecting the AOSP source](http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.3_r1/android/widget/TextView.java#TextView.setTypeface%28android.graphics.Typeface%29), `setTypeface()` indeed invokes `requestLayout()`, which makes sense as the dimensions may need changing. I'm not sure why this could cause conflicting layout passes. Perhaps you could try simplifying your layout? I'm stumped on this. – Paul Lammertsma Feb 04 '14 at 00:17
1

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.

kurteous
  • 41
  • 3
0

Please check weather the Id of any view is repeating inside the same activity context. I was also getting the same warning, I was using a TextView repeatedly a loop with same id. I resolved the problem by using different ids each time.

0

I met the same problem. That's because I was trying to set a view's position in the iOS way. You know in iOS set a view's position by set the view's left top value and width, height. But in Android, it should be (left, top, right, bottom). I did pay much attention on this. When I jump into the layout() definition, I read the method's comment, then I find out why the warning happened.

Bruce Lee
  • 4,177
  • 3
  • 28
  • 26