I have an activity with a Button and a GridLayout with many children. If I add all these children in onCreate() my activity appears on a screen with a lag:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LinearLayout main = new LinearLayout(this);
main.setOrientation(LinearLayout.VERTICAL);
Button button = new Button(this);
button.setText("test");
main.addView(button);
GridLayout testGrid = new GridLayout(this);
testGrid.setColumnCount(5);
for (int i = 0; i < 100; i++)
testGrid.addView(new Button(this));
main.addView(testGrid);
setContentView(main);
}
But I want at least my Buttton to appear immediately, so I try to add the children to the grid in a thread. After experiments I came to this solution:
final GridLayout testGrid = new GridLayout(this);
testGrid.setColumnCount(5);
main.addView(testGrid);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
public void run() {
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++)
MyActivity.this.runOnUiThread(new Runnable() {
#Override
public void run() {
testGrid.addView(new Button(testGrid.getContext()));
}
});
}
}).start();
}
}, 1);
But I'm not sure it's a good idea, because it looks kinda complicated and may be it won't work well on some devices. Any better suggestions?
When you have to do something like this, it is a clear indication that you do something wrong. If you really need 100 buttons in a grid, maybe you should consider using GridView instead of GridLayout and loading buttons into view via a simple adapter.
Related
xml:
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/main_table"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</TableLayout>
Activity sample code:
public class SecondActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.second);
TableLayout tl = (TableLayout) findViewById(R.id.main_table);
TableRow trHead = new TableRow(this);
trHead.setId(10);
trHead.setBackgroundColor(Color.GRAY);
trHead.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
TextView col1Header = new TextView(this);
col1Header.setText("Time");
col1Header.setTextColor(Color.CYAN);
trHead.addView(col1Header);
TextView col2Header = new TextView(this);
col2Header.setText("Company");
col2Header.setTextColor(Color.CYAN);
col2Header.setGravity(Gravity.END);
trHead.addView(col2Header);
tl.addView(trHead, new TableLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
for (int i = 0; i < 3; i++) {
TableRow row = new TableRow(this);
row.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT,
LayoutParams.MATCH_PARENT));
TextClock clock = new TextClock(this);
row.addView(clock);
TextView data = new TextView(this);
data.setText("dev" + 1);
data.setGravity(Gravity.END);
row.addView(data);
tl.addView(row, new TableLayout.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
SystemClock.sleep(1000);
}
}
}
}
SystemClock.sleep(1000); does not fulfil my need.
Whole table is appering on UI after 3 seconds. But I want rows to be added after 1 minute on the UI.
Whole table apper after 3 seconds because SystemClock.sleep receive miliseconds as parameters. (1 minute = 60000 miliseconds).
Another way to do it is using a Runnable
Runnable r = new Runnable() {
public void run() {
//Add row
}
};
Call runnable:
Handler handler = new Handler();
handler.postDelayed(r, 60000);
The problem is that you are sleeping inside of onCreate(). This blocks the UI thread so nothing will be rendered until after sleep() returns. This means that the database will be updated and rendered almost immediately. To perform an actual delay, you need to use concurrency of some type. At the lowest level, you can create a new Thread or Runnable. At a higher level, you can use AsyncTask. There are also even higher level abstractions available, but these two options should get you started in the right direction.
for(int i=1;i<=3;i++){
new Handler().postDelayed(new Runnable() {
#Override
public void run() {
runOnUiThread(new Runnable() {
#Override
public void run() {
//add row
}
});
}
},i*60000);
}
I have to build a custom dialog that update the view when the content view is changed.
In the example below I have two TextView, one in the main activity and one in the dialog layout and counter from 1 to 10 should showed on both (main activity layout and dialog layout), but only the TextView in the main activity is update.
The code:
TextView tvCounter_Dialog, tvCounter_OutsideDialog;
int counter = 0;
Handler handler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_live_custom_dialog);
handler = new Handler();
View dialogView = getLayoutInflater().inflate(R.layout.dialog_live_layout, null);
tvCounter_Dialog = (TextView) dialogView.findViewById(R.id.tvCounter);
tvCounter_OutsideDialog = (TextView) findViewById(R.id.tvCounter_outsideDialog);
Dialog counterDialog = new Dialog(this);
counterDialog.setContentView(R.layout.dialog_live_layout);
counterDialog.show();
final Runnable updatedr = new Runnable() {
#Override
public void run() {
counter = (counter + 1) % 10;
tvCounter_Dialog.setText(String.valueOf(counter));
tvCounter_OutsideDialog.setText(String.valueOf(counter));
handler.postDelayed(this, 500);
}
};
updatedr.run();
}
So the question is - how can I update the textview in the dialog?
EDIT: here is an capture (to be more clear)
the mistake is on this line
counterDialog.setContentView(R.layout.dialog_live_layout);
it should be
counterDialog.setContentView(dialogView);
in your version the custom view of your Dialog and the one you inflated are different
I have a tricky issue.
There's a RecyclerView
Each child of RecyclerView have several programmatically generated Buttons:
List<Button> buttons = new ArrayList<>();
for (int k = 0; k < values.length; k++) {
String value = values[k];
Button btnAnswer = (Button)LayoutInflater.from(mContext).inflate( R.layout.button_answer_unpressed, mainLayout, false);
btnAnswer.setId(View.generateViewId());
mainLayout.addView(btnAnswer);
buttons.add(btnAnswer);
}
...
for (Button button:buttons) {
applyBlur(button, mainLayout);
}
The interesting part starts inside applyBlur method.
3.1. Realization without threads (I provide only a part of method which distinguishes in two realizations) :
private void applyBlur(final View view, final View parent) {
...
parent.buildDrawingCache();
final Bitmap bmp = parent.getDrawingCache();
...
Bitmap blurredBitmap = blur(bmp, view);
view.setBackground(new BitmapDrawable(mContext.getResources(), blurredBitmap));
...
}
view variable is a Button passed as an argument. Function blur makes blur effect :) (taken from here) and it works fine. It looks as it have to (see blur buttons):
3.2. Realization with threads:
private void applyBlur(final View view, final View parent) {
...
parent.buildDrawingCache();
final Bitmap bmp = parent.getDrawingCache();
...
new Thread(new Runnable() {
public void run() {
final Bitmap blurredBitmap = blur(bmp, view);
view.post(new Runnable() {
public void run() {
view.setBackground(new BitmapDrawable(mContext.getResources(), blurredBitmap));
}
});
}
}).start();
...
}
You may notice that the code remains the same but just putted into Thread. However result is different:
I also tried AsyncTask and AsyncTaskLoader but result remains the same: only last Button gets blurred. If I keep everything in main thread - it's all right but not good for productivity.
I checked that blurredBitmap is always generated properly.
Why threads crash Button's background initialization?
Try this:
List<Button> buttons = new ArrayList<>(values.length);
for(int k = 0; k < values.length; k++) {
buttons.add((Button) LayoutInflater.from(mContext).inflate(R.layout.button_answer_unpressed, mainLayout, false););
}
for (int k = 0; k < buttons.size(); k++) {
String value = values[k];
buttons.get(k).setId(View.generateViewId());
mainLayout.addView(buttons.get(k));
applyBlur(buttons.get(k), mainLayout);
}
and have your applyBlur method use threads
Thanks to #pskink, problem solved
mainLayout.post(new Runnable() {
#Override
public void run() {
for(Button btn:buttons)
applyBlur(btn, mainLayout);
}
});
The doc says I should add something like this to my code:
// create a new layout
LinearLayout layout = new LinearLayout(this);
/*
Add any elements to your view
*/
// make the view visible
this.setContentView(layout);
final Activity act = this;
// now add the banner or overlay to the app
layout.post(new Runnable() { //what about this line?
public void run() {
myController = new AdController(act, MY_LB_SECTION_ID);
myController.loadAd();
}
});
My layout is in xml and I have already defined setContentView(R.layout.config); I mean the layout.post(new Runnable() { line. How should I modify the code?
For anyone who is interested I have found the obvious solution:
RelativeLayout rellaymain = (RelativeLayout)findViewById(R.id.rellaymain);
final Activity act = this;
rellaymain.post(new Runnable() {
public void run() {
myController = new AdController(act, MY_LB_SECTION_ID2);
myController.setAsynchTask(true);
myController.loadAd();
}
//});
});
rellaymain is the layout of the .xml file.
I am trying to add a textview inside a tab dynamically. using this code
Oncreate()
{
OA.loaderShow(this); //Loader display
new Thread(new Runnable(){
public void run()
{
Looper.prepare();
fetchDocs();
OA.loaderHide(); //Loader Hide
Looper.loop();
}
}).start();
}
fetchDocs()
{
LinearLayout layout = (LinearLayout) findViewById(R.id.layout);
TextView text = new TextView(this);
text.setText(mytext);
layout.addView(text);
}
I am getting this error "Only the original thread that created a view hierarchy can touch its view".
Please Help.
Put above inside the following block
runOnUiThread(new Runnable() {#Overridepublic void run() {//your code here}}
Try using a handler like this:
EDIT:
protected static final int SET_TEXTVIEW = 0;
Oncreate()
{
OA.loaderShow(this); //Loader display
new Thread(new Runnable(){
public void run()
{
Looper.prepare();
handler.sendMessage(handler.obtainMessage(SET_TEXTVIEW));
OA.loaderHide(); //Loader Hide
Looper.loop();
}
}).start();
}
public Handler handler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
case SET_TEXTVIEW :
LinearLayout layout = (LinearLayout) findViewById(R.id.layout);
TextView text = new TextView(this);
text.setText(mytext);
layout.addView(text);
}
}
};
Add this in the onCreate method rather than adding from else where.