yuwono95
yuwono95

Reputation: 257

How to increase layouting performance

I have dynamic and quite large contents which needs to be populated on user interaction (click next / prev page). The dynamic contents displayed programmatically on myTableLayout (see xml below). Each time user clicked next/prev page then I call myTableLayout.removeAllViews(), do some stuff and repopulate with myTableLayout.addView(). On small contents it draws quite fast, but I notice two to three seconds lag if populating large contents.

I already saw an application that do same functionality as mine (also with quite large contents) but it really paging so fast even on low-end device.

Need some advice on how to increase the layout performance.

Thanks.

Note: Upper TableLayout used as title (header), LinearLayout below it is where the contents placed.

<TableLayout
    android:layout_width="fill_parent"
    android:layout_height="45dip"
    android:stretchColumns="0,2"
    android:shrinkColumns="0,2"
    >
        <TableRow>
            <LinearLayout
                android:id="@+id/llLeft"
                android:layout_width="wrap_content"
                android:layout_height="fill_parent"
            >
                <TextView
                    android:id="@+id/tvLeft1"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                />
            </LinearLayout>

            <LinearLayout
                android:id="@+id/llCenter"
                android:layout_width="wrap_content"
                android:layout_height="fill_parent"
            >
                <TextView
                    android:id="@+id/tvCenter1"
                    android:layout_width="wrap_content"
                    android:layout_height="fill_parent"
                />

                <TextView
                    android:id="@+id/tvCenter2"
                    android:layout_width="wrap_content"
                    android:layout_height="fill_parent"
                />
            </LinearLayout>

            <LinearLayout
                android:id="@+id/llRight"
                android:layout_width="wrap_content"
                android:layout_height="fill_parent"
            >
                <TextView
                    android:id="@+id/tvRight1"
                    android:layout_width="fill_parent"
                    android:layout_height="fill_parent"
                />
            </LinearLayout>
    </TableRow>
</TableLayout>

<LinearLayout
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
>
    <com.MyScrollView
        android:id="@+id/myScrollView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        >

        <TableLayout
            android:id="@+id/**myTableLayout**"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:shrinkColumns="1"
            android:stretchColumns="1"
            />

    </com.MyScrollView>
</LinearLayout>

Here is the code that create the dynamic content. The content fetched from memory (ArrayList) so it should not be the problem.

private void gotoPage() {
    myTableLayout.removeAllViews();
    GlobalVar g = GlobalVar.getInstance();
    ArrayList<String> arrScripts = g.getDatasMap().get(SCRIPT_INDEX);
    ArrayList<String> arrTrans = g.getTranslationMap().get(SCRIPT_INDEX);
    Log.i(APP_NAME, "finished preparing data");

    try {

        // Data Title
        tvCenter1.setText(arrScripts.get(0));
        tvCenter2.setText(" (" + SCRIPT_INDEX + ")");

        int countScripts = arrScripts.size();
        for(int i=1; i<countScripts; i++) {

            // Row Script
            TableRow trScript = new TableRow(this);
            trScript.setPadding(0, 5, 0, 0);
            TextView tvIndex = new TextView(this);
            tvIndex.setGravity(Gravity.CENTER);
            tvIndex.setWidth(40);
            tvIndex.setText("" + i);

            TextView tvScript = new TextView(this);
            tvScript.setGravity(Gravity.RIGHT);
            tvScript.setTypeface(g.getFontTypeFace());
            tvScript.setTextSize(FONT_SIZE);
            tvScript.setPadding(0, 0, 5, 0);
            tvScript.setText(arrScripts.get(i));

            trScript.addView(tvIndex);
            trScript.addView(tvScript);

            TableRow trTrans = null;

            if(IS_USE_TRANSLATION) {
                // Row Translation
                trTrans = new TableRow(this);

                TextView tvBlank = new TextView(this);
                tvBlank.setPadding(0, 0, 0, 5);
                TextView tvTrans = new TextView(this);
                tvTrans.setPadding(0, 0, 0, 5);
                tvTrans.setText(arrTrans.get(i));

                trTrans.addView(tvBlank);
                trTrans.addView(tvTrans);
            }

            // Place for line separator
            View vSep = new View(this);
            ViewGroup.LayoutParams p = new ViewGroup.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT);
            p.height = 3;
            vSep.setLayoutParams(p);

            // Combine rows to table
            if(IS_USE_SCRIPT) myTableLayout.addView(trScript);
            if(IS_USE_TRANSLATION) myTableLayout.addView(trTrans);
            myTableLayout.addView(vSep);
        }
    } catch (Exception e) {
        Log.e(APP_NAME, e.getMessage());
    }
}

Here is the traceview result.

Upvotes: 2

Views: 5996

Answers (5)

Nanne
Nanne

Reputation: 64399

I agree with @amplify91 that it might be from generating the content, but one of the other things you might want to look at is the number of layouts you are using. The basic thought is "the fewer the better". You can check out some of googles layout tricks

Example: I see your header is a table with linearlayouts with textviews. Couldn't that be fixed by a Relative layout with the textviews as children?

You'll save some Views, and also some 'depth' in the layout tree.

Upvotes: 4

Johnson Wong
Johnson Wong

Reputation: 4483

If all you want to do is add text in a nicely formatted table format (all columns auto-sized etc), don't use a TableLayout at all and just use something like java.util.Formatter to format text into a table shape. This requires some pre-work like computing the max string length of each column, but in the end resulted in large performance gains as you basically reduce your view usage to a single TextView (~2 seconds to load activity down to almost instant).

For example:

// Compute max string length of each column
for (Entry entry : entries) {
    if (entry.column1.length() > column1MaxLength) {
        column1MaxLength = entry.column1.length();
    }
    ...
    if (entry.columnN.length() > columnNMaxLength) {
        columnNMaxLength = entry.columnN.length();
    }
}

// Formatter internally stores output string in StringBuilder
Formatter formatter = new Formatter();

// Construct table header
formatter.format("%1$" + column1MaxLength + "s", "Column 1 Title");
...
formatter.format("%1$" + columnNMaxLength + "s", "Column N Title");

// Add each entry into table
for (Entry entry : entries) {
    formatter.format("%1$" + column1MaxLength + "s", entry.column1.value);
    ...
    formatter.format("%1$" + columnNMaxLength + "s", entry.columnN.value);
}

// Get string representing formatted table from Formatter and put into TextView
textView.setText(formatter.toString());

Upvotes: 0

3c71
3c71

Reputation: 4511

To put it simply, get rid of TableLayout and TableRow and TextView altogether!

If you collect profiling data from Eclipse, you'll see that most of the CPU/real time is spent in the addView() method from the TabletLayout and also the TextView. calls!

Each time I've replaced a TableLayout and TextView with custom implementations it resulted in a HUGE performance gain!

TextView should be replaced by custom View, TableLayout by custom ListView.

EDIT: I have created a few custom views depending on my needs. To put it simply I've overwritten the View class with onDraw() and onMeasure() methods:

In below code, the painter is created once in the static constructor, so are textBaseline and textHeight as I'm using a single font size for all those custom "text" views.

@Override
protected void onDraw(Canvas canvas) 
{
    final int width = getWidth();
    final float size = painter.measureText(innerText);

    canvas.drawText(innerText, width - size - 5, textBaseline, painter);

    if (underline)
    {
        canvas.drawLine(0, canvas.getHeight() - 1, canvas.getWidth(), canvas.getHeight() - 1, painter);
    }
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) 
{
    int w = MeasureSpec.getSize(widthMeasureSpec);
    int h = MeasureSpec.getSize(heightMeasureSpec);

    if (innerText != null && painter != null)
    {
        switch (MeasureSpec.getMode(widthMeasureSpec))
        {
            case MeasureSpec.EXACTLY:
                break;
            case MeasureSpec.AT_MOST:
            case MeasureSpec.UNSPECIFIED:
                w = (int)( (painter.measureText(innerText) + 5) );
                break;
        }
    }
    switch (MeasureSpec.getMode(heightMeasureSpec))
    {
        case MeasureSpec.EXACTLY:
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.UNSPECIFIED:
            h = (int)( textHeight );
            break;
    }

    setMeasuredDimension(w, h);
}

Upvotes: 2

Heiko Rupp
Heiko Rupp

Reputation: 30934

I think you should surround your code by Debug.startMethodTracing("xx") and Debug.stopMethodTracing() calls and then use traceview to find out what is going on. Everything else is just guesswork.

Upvotes: 1

Amplify91
Amplify91

Reputation: 2660

I would say that the delay isn't in building the layout from XML, but from generating your dynamic content. What kind of content is it? Could you post some of your code? If you're accessing it from a website you may want to pre-fetch it. If you're creating when you change pages, it could be in how you're creating it.

Upvotes: 0

Related Questions