0

I have to create a large layout in Android programmatically so I want to do it in background. This layout is a vertical LinearLayout defined in my XML and it will contain a big number of rows.

This is the layout container (defined in my XML):

private LinearLayout gridL;
        
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    ...
    gridL = (LinearLayout)_fragment.findViewById(R.id.grid);
    ...
}

This is the Thread class to populate this layout:

private class CreateGridThread extends Thread {

        public void run() {
            Looper.prepare();
            createGrid();
            handler.sendEmptyMessage(101);
        }
    }

And I call this class this way:

CreateGridThread gridThread = new CreateGridThread();
gridThread.start();

Inside createGrid() I added my components directly to gridL so I get a "CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views." exception.

So, to avoid this I created an auxiliary layout:

private LinearLayout gridLAux;

And I changed my createGrid() so all components were added to this Layout not to gridL. This is my createGrid() method (with some minor editions):

public void createGrid()
{
    gridLAux = new LinearLayout(myActivity);
    gridLAux.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
    gridLAux.setOrientation(LinearLayout.VERTICAL);

    LinearLayout currentLayout = null;
    int lastIndex = 0;
    for(int i = 0; i < myData.size(); i++)
    {
        Bundle b = myData.get(i);

    // Here I read my data

        // 3 columns
        lastIndex = i % 3;
        if(lastIndex == 0)
        {
            // Container for the whole row
            currentLayout = new LinearLayout(myActivity);
            LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
                                                                                          LayoutParams.WRAP_CONTENT);
            currentLayout.setLayoutParams(params);
            currentLayout.setOrientation(LinearLayout.HORIZONTAL);
            currentLayout.setWeightSum(3);
            gridLAux.addView(currentLayout);
        }

        // Container for a cell
        RelativeLayout rowL = new RelativeLayout(myActivity);
        LinearLayout.LayoutParams params1 = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT,
                                                                          LinearLayout.LayoutParams.WRAP_CONTENT);
        params1.weight = 1;
        rowL.setLayoutParams(params1);
        rowL.setTag(i);
        currentLayout.addView(rowL);

        // Container for 2 images
        LinearLayout imagesL = new LinearLayout(myActivity);
        RelativeLayout.LayoutParams params2 = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT,
                RelativeLayout.LayoutParams.WRAP_CONTENT);
        params2.addRule(RelativeLayout.ALIGN_PARENT_TOP);
        params2.setMargins(0, 0, 0, 0);
        imagesL.setLayoutParams(params2);
        imagesL.setOrientation(LinearLayout.HORIZONTAL);
        imagesL.setWeightSum(2);
        imagesL.setId(R.id.text);
        rowL.addView(imagesL);

        // Left image
        ImageView leftIV = new ImageView(myActivity);
        LinearLayout.LayoutParams params3 = new LinearLayout.LayoutParams(
                myActivity.getResources().getDimensionPixelSize(R.dimen.img_width),
                myActivity.getResources().getDimensionPixelSize(R.dimen.img_height));
        params3.weight = 1;
        leftIV.setLayoutParams(params3);
        leftIV.setAdjustViewBounds(true);
        leftIV.setScaleType(ScaleType.FIT_XY);
        leftIV.setImageResource(R.drawable.ico_left);
        imagesL.addView(leftIV);

        // Right image
        ImageView rightIV = new ImageView(myActivity);
        LinearLayout.LayoutParams params4 = new LinearLayout.LayoutParams(
                myActivity.getResources().getDimensionPixelSize(R.dimen.img_width),
                myActivity.getResources().getDimensionPixelSize(R.dimen.img_height));
        params4.weight = 1;
        rightIV.setLayoutParams(params4);
        rightIV.setAdjustViewBounds(true);
        rightIV.setScaleType(ScaleType.FIT_XY);
        rightIV.setImageResource(R.drawable.ico_right);
        imagesL.addView(rightIV);
    }
    if(currentLayout != null)
    {
        for(int i = 0; i < 2 - lastIndex; i++)
        {
            LinearLayout imgWrapper = new LinearLayout(myActivity);
            LinearLayout.LayoutParams paramsLayoutWrapper = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
            paramsLayoutWrapper.weight = 1;
            imgWrapper.setLayoutParams(paramsLayoutWrapper);
            currentLayout.addView(imgWrapper);
        }
    }
}

Finally, after the task has ended I call the Handler:

   private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case 101:
                    gridL.addView(gridLAux);
                     break;
             }
        }
    };

So, in background I add all my components to an auxiliar layout and in the main thread I add this auxiliar layout to the good one.

But, I'm still getting

"CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views." exceptions in addView calls inside createGrid()

. What am I doing wrong?

Basi
  • 3,009
  • 23
  • 28
Wonton
  • 1,033
  • 16
  • 33
  • _is a vertical LinearLayout defined in my XML and it will contain a big number of rows._ <- **Please** use a listview or recyclerview – David Medenjak Sep 21 '17 at 22:13
  • Yes, I thought that, but due to design requirements this layout must be shown completely, inside a ScrollView with other components above it and below it, so if I use a listview or recycleview I wouldn't have their advantages because they would show all the rows at the same time. – Wonton Sep 21 '17 at 22:17
  • There is no but. You're describing a list, and you even say you want a lot of items. Lists can have headers / footers and I'm sure you'll find a solution for whatever design requirements you have. – David Medenjak Sep 21 '17 at 22:20
  • I'll take a look at this approach. – Wonton Sep 21 '17 at 22:21

4 Answers4

1

You always must add views on UI thread, so use Handler on MainLooper

new Handler(Looper.getMainLooper()).post(new Runnable() {
     @Override
     public void run() {
          gridL.addView(gridLAux);
     }
});

Or if you have access to activity you may use Activity's runOnUiThread method

Romadro
  • 626
  • 4
  • 10
0

I faced a similar problem creating large dynamic forms and I found that certain UI widgets can only be created from the main thread while other can be created from outside without problem. In my case, there where nested linear layouts including every kind of widgets you could name. My solution was, at the main thread execute the creation loop (like Romadro says) but not every element at once to avoid blocking the UI and allow me to display a processing message to the user in the meantime allowing a responsive UI. Hope it helps.

Walter Palladino
  • 479
  • 1
  • 3
  • 8
0

I see few issues in your code, you should notice them:

  1. You should always create your View in UI thread (because of the sake of synchronization). While I do not see any bigs in your view, you can try to define it in the xml then inflate it.
  2. Your Handler will be in the thread which you call new Handler. Therefore, if you call new Handler in a worker thread -> the handler will be in the worker thread as well. (you can use new Handler(Looper.getMainLooper())) instead)
  3. I see your loop to create a lot of big items, why dont you try RecyclerView? it will boost your layout performance a lot.
Kingfisher Phuoc
  • 8,052
  • 9
  • 46
  • 86
0

What Romadro said is correct, also you can inflate or add views in layout like LinearLayout without blocking UI thread with AsyncTask or Concurrent. Take a look at my answer here : https://stackoverflow.com/a/65741410/12204620 you may get some idea.

Bitwise DEVS
  • 2,858
  • 4
  • 24
  • 67