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?
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
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.
I see few issues in your code, you should notice them:
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.
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)
I see your loop to create a lot of big items, why dont you try RecyclerView? it will boost your layout performance a lot.
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.
Related
I have multiple lines of text that I want to add to a LinearLayout. However, I only want to add as many as will fit within the fixed vertical size of the layout.
I'm using onGlobalLayout and getLocalVisibleRect but I can't figure out a way to get the loop to wait for the layout to finish without the latch waiting forever. (I'm sure I just don't understand how the UI thread works.)
Here's what I'm trying:
final LinearLayout content = ((LinearLayout) findViewById(R.id.innerView));
final Rect myRect = new Rect();
TextView lastView = new TextView(this);
for (int i = 0; i < 3; i++) {
final TextView valueTV = new TextView(content.getContext());
final CountDownLatch waitForLayout = new CountDownLatch(1);
valueTV.setText("hallo hallo this is a really long line that might wrap but might not I'm not really sure what will happen");
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
params.setMargins(15, 5, 50, 20);
valueTV.setLayoutParams(params);
final ViewTreeObserver textViewTreeObserver = valueTV.getViewTreeObserver();
textViewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
//Do your operations here.
valueTV.getLocalVisibleRect(myRect);
Log.i("New Bottom", String.format("Bottom %d", myRect.bottom));
waitForLayout.countDown();
}
});
content.addView(valueTV);
content.requestLayout();
try {
waitForLayout.await();
}
catch(InterruptedException ie)
{
}
}
I'm more than open to moving anything to a separate method.
You're blocking the completion of onCreate(), which means your first view is never laid out, so the countDown() call is never executed.
The OnGlobalLayoutListener already only runs once the first view is laid out, so move your code into the listener and delete the CountDownLatch altogether.
textViewTreeObserver.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
public void onGlobalLayout() {
// put your view adding here
}
});
I want to parse text, and create for each word - button, but i don't know how to arrange them one after the other
String s = "Alice was beginning to get very tired of sitting";
String[] q = s.split(" ");
for (int i = 0; i < q.length; i++) {
Button myButton = new Button(this);
myButton.setText(q[i]);
RelativeLayout layout = (RelativeLayout) findViewById(R.id.layout1);
layout.addView(myButton, params);
}
See this custom library: FlowLayout
While you're adding views inside FlowLayout, it automatically wraps when there is no space for the next item.
There's not much wrong about your approach, it's only that relative layout as name suggests requires child views to have some parameters to align the views relative to them e.g. above, below etc. As a result you are getting views overlapping each other and hence only the last added view is visible being on top.
Use FlowLayout instead and you'll be fine.
You need to define RelativeLayout parameters as in example below
Heres an example to get you started, fill in the rest as applicable:
TextView tv = new TextView(mContext);
RelativeLayout.LayoutParams params = new RelativeLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
params.addRule(RelativeLayout.ALIGN_PARENT_LEFT, RelativeLayout.TRUE);
params.leftMargin = 107
...
mRelativeLayout.addView(tv, params);
The docs for RelativeLayout.LayoutParams and the constructors are
here
From: How to add a view programmattically to RelativeLayout?
Check the link below to get more useful informations.
Hope it will help
In the following code, you should change the upper limits of the for, to a variable.
public class MainActivity
extends Activity
implements View.OnClickListener {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TableLayout layout = new TableLayout (this);
layout.setLayoutParams( new TableLayout.LayoutParams(4,5) );
layout.setPadding(1,1,1,1);
for (int f=0; f<=13; f++) {
TableRow tr = new TableRow(this);
for (int c=0; c<=9; c++) {
Button b = new Button (this);
b.setText(""+f+c);
b.setTextSize(10.0f);
b.setTextColor(Color.rgb( 100, 200, 200));
b.setOnClickListener(this);
tr.addView(b, 30,30);
} // for
layout.addView(tr);
} // for
super.setContentView(layout);
} // ()
public void onClick(View view) {
((Button) view).setText("*");
((Button) view).setEnabled(false);
}
} // class
Sorry for not posting any code.
Searching on stackoverflow and google but could not find proper solution for this, so could you please help me out, if you have any idea or tricks for this.
Requirment:
I have many buttons create programmatically according to web's requirement.and set all button's background image with grayscale. I have done this part but when scrolling horizontal scollview then i want to change of that buttons(visible to that time when scrolling) background image colored( initial load grayscaled) and certain time let us assume 10 second buttons background set intial sate(grayscaled).
Main theme:
Change button background image when scolling on visiable view's
button. for cetain time interval(10 second).
`
Problem:
I could not get buttons current visible in horizontal view and change of that buttons background colored image(first time view load). All i want to this programmatically, because all images getting from server side.
Any idea , how could do this job or any references for this task.
Note:
I create button view and set image background and add images on linearlayout and add that linear layout other mainlinearlayout and add mainlinearlayout on horizontalview layout.
sample snipped of my code:
public class CustomAdWithTitle extends LinearLayout{
private LinearLayout LinearCollectionAds;
private List<CommericalClassifiedAds> listCommericalAds;
HorizontalScrollView commercialH = new HorizontalScrollView(mcontext);
commercialH.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
LinearLayout commercialL = new LinearLayout(mcontext);
commercialL.setOrientation(LinearLayout.HORIZONTAL);
commercialL.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT));
LinearLayout LinearCollectionAds= new LinearLayout(mcontext);
LinearCollectionAds.setOrientation(LinearLayout.VERTICAL);
LinearCollectionAds.setLayoutParams(new LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
for (int i = 0; i < listCommericalAds.size(); i++) {
try {
commercialL.addView(listCommericalAds.get(i));
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
commercialH.addView(commercialL);
LinearCollectionAds.addView(commercialH);
this.addView(LinearCollectionAds);
}
...............
list of CommericalClassifiedAds it could return relativelayout with buttonview. i just add button background only and set grayscaled here first time load view.
public class CommericalClassifiedAds extends RelativeLayout {
}
Let's see if I got you right. For changing the image behind buttons you can use a StateListDrawable
StateListDrawable stateList = new StateListDrawable();
stateList.addState(new int[] { android.R.attr.state_selected }, bitmapFromServer);
stateList.addState(new int[] {}, bitmapGrayScale);
Add it to the each CommericalClassifiedAds background when created instead of the buttons background. Register a Scroll listener to commercialH and update UI in it.
private static boolean wait;
private static Handler handler = new Handler();
private static Runnable delayRunnable = new Runnable() {
#Override
public void run() {
int childcount = commercialL.getChildCount();
for (int i=0; i < childcount; i++){
commercialL.getChildAt(i).setSelected(false);
}
};
#Override
public void onScrollChanged() {
//this method can be called very frequently so only run selective
if (!wait) {
handler.removeCallbacks(delayRunnable);
wait = true;
int childcount = commercialL.getChildCount();
for (int i=0; i < childcount; i++){
commercialL.getChildAt(i).setSelected(true);
}
handler.postDelayed(new Runnable() {
#Override
public void run() {
wait = false;
}
}, 2000);
handler.postDelayed(delayRunnable, 10000);
}
}
If it runs slow I would first rethink the layout structure. Looks like you have a couple more layouts than required and then optimize onScroll.
I'm leaving a reservation for errors, it's not tested but it can give you that idea of how to solve your problem.
Hi i am creating dynamicly linear layouts and textviews inside and seting onclick events on each textview, but because the data from the sqlite query i make is big it takes a lot of time the binding to finish ( about 5-6sec) can anyone refer me to a much more effective way to do what i want to achive? I cannot use ListView because it is continuing text that should not be separeted, i tried to call
i tried to do something like this
onCreate(...){...
Runnable r = new Runnable() {
public void run() {
filldata(BID,CID);// add 30 views
}
};
LinearEn.post(r);// this is the main linearlayout that i add views and layers to it
}
public void filldata(idB, idC){
....
for(i=0;i<VerseArr.length;i++){ // this is the for loop that holds select query sqlite
LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.HORIZONTAL);
LinearLayout Infant1Layout = new LinearLayout(Reads.this);
LinearLayout Infant2Layout = new LinearLayout(Reads.this);
LinearLayout.LayoutParams linearParams2 = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
linearParams2.gravity=Gravity.CENTER;
Infant2Layout.setLayoutParams(linearParams2);
LinearLayout.LayoutParams linearParams1 = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
linearParams1.gravity=Gravity.CENTER;
linearParams1.setMargins(0,0,15,0);
Infant1Layout.setLayoutParams(linearParams1);
Infant1Layout.setOrientation(LinearLayout.HORIZONTAL);
TextView BookTxt=new TextView(Reads.this);
TextView BookVerseNum=new TextView(Reads.this);
BookTxt.setText(Html.fromHtml("<font color='#FFFFFF'>"+VerseArr[i]+"<font>"));
BookTxt.setTypeface(tf);
BookTxt.setFadingEdgeLength(10);
BookTxt.setTextSize(17.2f);
int currentAPIVersion = android.os.Build.VERSION.SDK_INT;
if (currentAPIVersion >= android.os.Build.VERSION_CODES.HONEYCOMB) {
BookTxt.setTextIsSelectable(true);
}
BookTxt.setPadding(0, dpToPx(3), dpToPx(5), 0);
BookVerseNum.setText(Html.fromHtml("<small><font color='#F9F9F9'>"+(i+1)+"<font></small>"));
BookVerseNum.setTypeface(tf);
BookVerseNum.setFadingEdgeLength(10);
BookVerseNum.setTextSize(20.2f);
BookVerseNum.setPadding(dpToPx(valuePad), dpToPx(3), dpToPx(5), dpToPx(3));
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT);
params.setMargins(0,0, 0, 4);
linearLayout.setLayoutParams(params);
Infant1Layout.addView(BookVerseNum);
View v = new View(this);
v.setLayoutParams(new LinearLayout.LayoutParams(
1,LayoutParams.MATCH_PARENT));
v.setBackgroundColor(Color.parseColor("#B3B3B3"));
v.setPadding(dpToPx(15), 0, dpToPx(15), dpToPx(3));
Infant1Layout.addView(v);
Infant2Layout.addView(BookTxt);
linearLayout.addView(Infant1Layout);
linearLayout.addView(Infant2Layout);
LinearEn.addView(linearLayout);
}
}
Yes thats because
for(i=0;i<VerseArr.length;i++)
here based the VerseArr.length say 20 those many views will be created so it takes time, some times if there are more say like 300 or so it might give us OOM as well.
But still, i have tried dynamically creating views for 4 to 5 or so but it will not take so much time, what i can suggest you is keep the Data ready that need to be added and try to reuse the views instead of creating it every time
I have a question regarding Android Activitys:
An Activity has the Method addContentView(View) while a ViewGroup has a (similar?) addView(View) Method.
Unfortunately its undocumented where the View from addContentView is placed. Is it like a LinearLayout just adding the View to the bottom, or is it more like a FrameLayout, which adds its Views "onTop" ? Does it depend on the ViewGroup set by setContentView?
If I dive into the sources I see that addContentView will call Window's abstract Method addContentView. Unfortunately I cannot see which class is implementing this Method. So whats the behaviour of Activitys addContentView exactly?
The base layout of every activity is a FrameLayout. This means the layout you usually set via setContentView() is a child of this layout. addContentView() adds just another child, therefore it behaves like a FrameLayout (which means it adds new UI elements above existing ones).
You can check this by using a tool called hierachyviewer from your ANDROID_SDK\tools folder. Here are two screenshots:
This is the layout before calling addContentView(), my activity consists of the default FrameLayout, holding a LinearLayout with a Button (my layout here). This is reflected in the bottom row here, the other elements above are the title/statusbar.
After adding a TextView via addContentView() it looks like this. You can see that the base FrameLayout got a new child.
public void addContentView(View view,
LayoutParams params) {
mActivity.addContentView(view, params);
}
//
public static void SetActivityRoot(Activity c) {
View v = ((ViewGroup)c.findViewById(android.R.id.content)).getChildAt(0);
ScrollView sv = new ScrollView(c);
LayoutParams lp = new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT);
sv.setLayoutParams(lp);
((ViewGroup) v.getParent()).removeAllViews();
sv.addView((View) v);
c.addContentView(sv, lp);
}
//
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
LinearLayout mainLayout =
(LinearLayout)findViewById(R.id.mainlayout);
//newButton added to the existing layout
Button newButton = new Button(this);
newButton.setText("Hello");
mainLayout.addView(newButton);
//anotherLayout and anotherButton added
//using addContentView()
LinearLayout anotherLayout = new LinearLayout(this);
LinearLayout.LayoutParams linearLayoutParams =
new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
Button anotherButton = new Button(this);
anotherButton.setText("I'm another button");
anotherLayout.addView(anotherButton);
addContentView(anotherLayout, linearLayoutParams);
}
}