Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling TextView.setText() redraws entire screen in spite of view hierarchy

In my app I have a time display which updates every second. Each time the TextView used for the seconds field changes, the Developer Options->Show surface updates tool flashes the entire screen. I've looked around and can really only find this question which pretty well clarifies that there is no way to prevent the TextView from causing a relayout for at least part of the window. So I was sure to verify that my TextView's are wrapped in their own container but I still have the same issue. Every call to setText() causes the entire view to flash.

My hierarchy is as follows:

  • Fragment
    • RelativeLayout (Fragment Root View)
      • LinearLayout
        • RelativeLayout
          • My Time TextViews
      • Various other view components which change rarely

I would like to fix this if possible. I do need to try and reduce my view count if possible and I plan on working on it but this is still a problem I would like to remove from the app.

like image 885
Jared Avatar asked Jan 08 '13 19:01

Jared


2 Answers

Show surface updates flashes the entire screen when hardware acceleration is on but it does not mean the entire window was redrawn. There is another option you can use that shows you exactly what part of the screen was redrawn when hardware acceleration is on ("Show GPU view updates").

like image 122
Romain Guy Avatar answered Oct 24 '22 08:10

Romain Guy


I encountered a similar problem where I was updating a timer which caused a measure pass every time. If you have something that is expensive to measure, like a ListView, it affects performance. In my case, I had a ListView that didn't need to change size when the timer updated, so I made a custom ListView holder to stop the the measure from cascading down to the ListView. Now it runs smoothly!

public class CustomListviewHolderLayout extends FrameLayout {

    public int currentWidth = 0;
    public int currentHeight = 0;

    public CustomListviewHolderLayout(Context context) {
        super(context);
    }

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

    public CustomListviewHolderLayout(Context context, AttributeSet attrs,
            int defStyle) {
        super(context, attrs, defStyle);
    }

    @Override
    protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
        //Debug Prints
        String wMode = "";
        switch(MeasureSpec.getMode(widthMeasureSpec)){
            case MeasureSpec.AT_MOST:
                wMode = "AT_MOST"; break;
            case MeasureSpec.EXACTLY:
                wMode = "EXACTLY"; break;
            case MeasureSpec.UNSPECIFIED:
                wMode = "UNSPECIFIED"; break;
            default:
                wMode = "";
        }
        String hMode = "";
        switch(MeasureSpec.getMode(heightMeasureSpec)){
            case MeasureSpec.AT_MOST:
                hMode = "AT_MOST"; break;
            case MeasureSpec.EXACTLY:
                hMode = "EXACTLY"; break;
            case MeasureSpec.UNSPECIFIED:
                hMode = "UNSPECIFIED"; break;
            default:
                hMode = "";
        }
        Log.i("RandomCustomListViewHolder", "wMode = " + wMode + ", size = " + MeasureSpec.getSize(widthMeasureSpec));
        Log.i("RandomCustomListViewHolder", "hMode = " + hMode + ", size = " + MeasureSpec.getSize(heightMeasureSpec));
        //only remeasure child listview when the size changes (e.g. orientation change)
        if(getChildCount() == 1){
            if(((MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY) ||
                (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.AT_MOST))
              &&((MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) ||
                (MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST))){
                //check if height dimensions are different than before
                int newWidth =  MeasureSpec.getSize(widthMeasureSpec);
                int newHeight = MeasureSpec.getSize(heightMeasureSpec);
                if((newWidth != currentWidth) || (newHeight != currentHeight)){
                    //remeasure if different
                    Log.i("RandomCustomListViewHolder", "measuring listView");
                    View childView = getChildAt(0);
                    childView.measure(widthMeasureSpec, heightMeasureSpec);
                    currentWidth = newWidth;
                    currentHeight = newHeight;
                    this.setMeasuredDimension(newWidth, newHeight);
                } else {
                    //still set this view's measured dimension
                    this.setMeasuredDimension(newWidth, newHeight);     
                }               
            } else {
                //Specify match parent if one of the dimensions is unspecified
                this.setMeasuredDimension(ViewGroup.LayoutParams.MATCH_PARENT,
                        ViewGroup.LayoutParams.MATCH_PARENT);                   
            }
        } else {
            //view does not have the listview child, measure normally 
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);           
            currentWidth = 0;
            currentHeight = 0;
        }
    }
}
like image 35
jtav Avatar answered Oct 24 '22 09:10

jtav