Run a method after View is created - Android - java

I've currently got an activity that creates a view. this view uses other classes (such as one to create a random sequence of integers). I need to run a method (which will display the sequence using bitmaps) once the view is created. So once the user clicks "Start Game" this sequence will be displayed.
I've tried calling the method after setting the content view inside the onCreate method by the sequence is not generated (all 0's) correctly. I've tries this also with onStart and onFinishInflate inside the myView class.
Is there a way i can run this method after everything is inflated and initialized? So after the user clicks "Start Game" and the view is changed, the method needs to run.
Thanks for looking.
Edit: A failed attempt.
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.gameView = new GameView(getApplicationContext(), getCellSource(getApplicationContext()));
setContentView(this.gameView);
// this.gameView.displaySequence(this.gameView.gameEngine.getGenSequence()); Need this to run once view is displayed.
}

Try using ViewTreeObserver as follow:
final View yourView = View.inflate(....);
ViewTreeObserver observer = yourView .getViewTreeObserver();
observer.addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
yourView.getViewTreeObserver().removeGlobalOnLayoutListener(this);
// Do what you need with yourView here...
}
});
Notice that the function removeGlobalOnLayoutListener(this) is different in some sdk versions.

Related

Android Studio cannot resolve method SetOnItemClickListener (Java)

I am trying to make carousel with clickable images, made as a list. I want to make so using SetOnItemCLickListener, but when I try to rebuild the project an error pops up and tells me:
error: cannot find symbol method setOnItemClickListener(<anonymous OnItemClickListener>)
I searched the internet for a solution but I am still stuck.
I have tried so far:
Clean the project (obvious);
Searched for name illegal elements, there are none;
When I tried only with SetOnCLickListener it did not work; it is in red-colored letters and there is no method like this.
public class FinalSadMovies extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_final_sad_movies);
List<CarouselPicker.PickerItem> imageItems = new ArrayList<>();
imageItems.add(new CarouselPicker.DrawableItem(R.drawable.joker));
imageItems.add(new CarouselPicker.DrawableItem(R.drawable.starwars_resized));
imageItems.add(new CarouselPicker.DrawableItem(R.drawable.test));
CarouselPicker.CarouselViewAdapter imageAdapter = new CarouselPicker.CarouselViewAdapter(this, imageItems, 0);
carouselPicker.setAdapter(imageAdapter);
carouselPicker.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Log.d("MainActivity", "ListView item clicked.");
}
});
If you're using carouselPicker from GoodieBag then setOnItemClickListener is not supported.
The only thing you can rely on is the addOnPageChangeListener but this requires you to change your app UI and flow (because you need to use a button to confirm the selection).
Another option is to create a custom CarouselViewAdapter class where you can handle the click event on the instantiateItem method.

Android textview contents not refreshing upon callback

I am trying to refresh the content in text view upon call back. In my onCreateView() when I first load the fragment:
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.activity_expense,container,false);
total = 0;
// some logic to get the total
txtTotal.setText(String.valueOf(total));
return v;
}
From first fragment I set the call back when button on click go to second fragment:
// set call back
addFragment.setTransactionListCallBack(ExpenseActivity.this);
Then when finish updating and retrieve in second fragment using async task, I pass back the updated results:
if(mcallback != null){
mcallback.callback(GetMonthlyTransactionAsyncTask.allTransaction);
}
Then I perform some insert into database, but before I return I am pulling the data again so I will pass back the updated one. In my callback in first fragment:
#Override
public void callback(ArrayList<Transaction> list)
{
// logic to sum up the total
Log.d("UPDATED TOTAL", String.valueOf(total));
txtTotal.setText(String.valueOf(total));
}
The call back is working perfectly as I did printed out the log to check the before update (inside onCreateView) and after update (inside callback), the value printed out is correct. So I guess there is not a need to show the code.
The problem now is inside my callback in first fragment, the updated value I printed out in Log is correct which is the updated one, but not the content displayed in the text view. The text view is still stuck with the old value.
Any ideas how to refresh the textview content upon call back?
invalidate() must be called from a UI thread. To call from a non-UI thread, call postInvalidate()
#Override
public void callback(ArrayList<Transaction> list)
{
// logic to sum up the total
Log.d("UPDATED TOTAL", String.valueOf(total));
txtTotal.setText(String.valueOf(total));
txtTotal.postInvalidate();
}
Also, check if there is a textview with the same id.
You may be trying to set the textview on a different thread if asynctask is involved, try setting the text view on the UI thread:
runOnUiThread(new Runnable() {
#Override
public void run() {
txtTotal.setText(String.valueOf(total));
}
});
Edit: also try txtTotal.invalidate() after you set text.

Custom view isn't being restored after adding it to a layout from AsyncTask's onPostExecute()

I've written a pretty large custom view which overrides onSaveInstanceState() and onRestoreInstanceState(Parcelable state).
I wanted to populate a LinearLayout with my custom view, so I wrote the following code:
public class MainActivity extends Activity {
private LinearLayout mRootLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRootLayout = (LinearLayout) findViewById(R.id.root_layout);
int i;
// Test: adding 10 instances of MyCustomView.
for (i = 0; i < 10; ++i) {
MyCustomView cv = new MyCustomView(this);
// I set an ID for this view so that onSaveInstanceState() and
// onRestoreInstanceState(Parcelable state) will be called
// automatically.
cv.setId(++i);
mRootLayout.addView(cv);
}
}
// ...
}
It works fine - mRootLayout is indeed being populated with 10 instances of MyCustomView, and each instance of MyCustomView is being properly restored after, for example, screen rotation.
I've noticed that due to the fact that MyCustomView is pretty large, my code is being heavy on the UI thread.
To solve the issue and take some effort off of the UI thread, I decided to use a custom AsyncTask, which will create an instance of MyCustomView in doInBackground() and add it to the the main layout ( mRootLayout ) in onPostExecute().
The following code is my custom AsyncTask:
private class LoadMyCustomViewTask extends AsyncTask<Void, Void, MyCustomView> {
private Context mContext;
private LinearLayout mLayoutToPopulate;
private int mId;
public LoadMyCustomViewTask(Context context, LinearLayout layout, int id) {
mContext = context;
mLayoutToPopulate = layout;
mId = id;
}
#Override
protected MyCustomView doInBackground(Void... params) {
MyCustomView cv = new MyCustomView(mContext);
return cv;
}
#Override
protected void onPostExecute(MyCustomView result) {
result.setId(mId);
mLayoutToPopulate.addView(result);
}
}
In MainActivity I use it as follows:
public class MainActivity extends Activity {
private LinearLayout mRootLayout;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRootLayout = (LinearLayout) findViewById(R.id.root_layout);
int i;
for (i = 0; i < 10; ++i) {
new LoadMyCustomViewTask(this, mRootLayout, ++i).execute();
}
}
// ...
}
This code works too, but there is only one problem - MyCustomView is not being restored at all.
For debug purposes I put a Log.d(...) in MyCustomView's onSaveInstanceState() and in onRestoreInstanceState(Parcelable state), and I've noticed that onRestoreInstanceState(Parcelable state) isn't being called.
Do you have any idea why onRestoreInstanceState(Parcelable state) isn't being called when I use an AsyncTask to populate mRootLayout, but it is indeed being called when I create MyCustomView completely on the UI thread?
Thank you.
Edit: I'm posting the methods onSaveInstanceState() and onRestoreInstanceState() of MyCustomView
#Override
protected Parcelable onSaveInstanceState() {
debug("onSaveInstanceState()");
Bundle state = new Bundle();
state.putParcelable(_BUNDLE_KEY_PARENT_STATE, super.onSaveInstanceState());
state.putBooleanArray(_BUNDLE_KEY_CLICKED_VIEWS, mClickedViews);
return state;
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
debug("onRestoreInstanceState(Parcelable state)");
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
mClickedViews = bundle.getBooleanArray(_BUNDLE_KEY_CLICKED_VIEWS);
state = bundle.getParcelable(_BUNDLE_KEY_PARENT_STATE);
}
super.onRestoreInstanceState(state);
}
View state restoration begins at the root view and moves down to all of the child views attached at that time. This can be seen in the ViewGroup.dispatchRestoreInstanceState method. This means that Android can only restore your views if they are part of the view hierarchy at the time Activity.onRestoreInstanceState is called.
Using the AsyncTask, you are creating your views asynchronously and then scheduling them to be added some time later when the main looper is idle. Considering the lifecycle, Android only lets your AsyncTask.onPostExecute run after Activity.onStart, Activity.onRestoreInstanceState, Activity.onResume, etc. are called. Your views are being added to the layout too late for automatic restoration to take place.
If you add log statements to those methods mentioned above, as well as to your AsyncTask.onPostExecute, you will be able to see how the ordering/timing plays out in reality. The following code runs after Activity.onRestoreInstanceState even though it all happens on the main thread, simply because of the scheduling:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
new Handler().post(new Runnable() {
#Override
public void run() {
Log.i("TAG", "when does this run?");
}
});
...
}
Smells like a false observation... creating a view on a background thread should not affect your activity lifecycle.
That said, doing anything at all with View objects on background threads is a no-no and I'm surprised you got this far with such an approach. All View code should be quick and avoid blocking. If you have long-running work to do then separate that work into the background thread, post the results of that complex computation to the main thread, and keep all the actual View/presentation stuff on the main thread where it belongs.
I remember having read about how onSaveInstanceState()/onRestoreInstanceState() work a time ago and it was not a "fixed-situation-rules" thing. As I can't find the references to it at this moment, I'll try to explain it with my own words:
Basically, a factor on which depends the calling of those methods is the resources left that the device has. Both methods will get in action when a second Activity gets focused and the first one gets killed due to lack of resources. Concretely, onRestoreInstanceState should be triggered when that Activity was killed and restarted, so it gets the previous state.
Although you've not posted your MyCustomView implementation, my guess is that when you do that entirely on the main UI Thread, you're involving some action that makes the MainActivity lose its focus and once the MyCustomView is created, it needs to restore its state. Doing this in a separate thread (as AsyncTask does) makes creating those Views in paralell, so your main Activity doesn't lose its focus and thus it doesn't get killed, so those methods are not called.
Concluding this, don't worry if those methods are not always called as they don't have to be called everytime, just when needed, and that doesn't mean there's something going wrong.
I recommend you to connect SDK sources to your IDE and walk there with debugger through the whole process related with the View class onRestoreInstanceState(). Since I don't have your code, looking at sources I can only guess what might have gone wrong, but from what I see, that might be related to problem:
1) try to set an Id to every view you generate
2) try to use Fragment as a host to your views (instead of MainActivity).

when am I safe to query a View's dimensions?

I'm trying to grab the dimensions of a view in my activity. The view is a simple custom view which extends an ImageView:
<com.example.dragdropshapes.CustomStrechView
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="#drawable/border"
android:src="#drawable/missingpuz"
android:clickable="true"
android:onClick="pickShapes"
/>
I need to know what the specific "fill_parent" ends up being. I attempted to get this information during the onCreate method of the Activity using the layout containing my custom views:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_puzzle_picker);
// Show the Up button in the action bar.
setupActionBar();
int a = findViewById(R.id.pickshapes).getMeasuredHeight();
int b = findViewById(R.id.pickshapes).getHeight();
In this case, both a and b return a value of 0. Later, the custom view will be used as a button (it has an onClick handler) so I thought to try again to get the size in the handler:
public void pickShapes(View view){
Intent intent = new Intent(this, ShapesActivity.class);
int a = findViewById(R.id.pickshapes).getMeasuredHeight();
int b = findViewById(R.id.pickshapes).getHeight();
startActivity(intent);
}
Here a and b both give valid dimensions... I don't want to wait for a "onClick" event however, I want to get the dimensions as soon as possible. I've tried Overriding both onStart() and onResume() to check the dimensions as well, but in both cases I still get 0.
So my question is, where in the Android Activity start up flow, is the first place I can get the actual size of a View? I want to be able to get the height/width as soon as I can, and I want to do it before the user has a chance to interact with the environment.
There's a fairly useful thing in Android called the ViewTreeObserver. I've done precisely what you need to do many times this way. As you've discovered, you need to wait until at least the measure cycle completes. Try something like the following:
...
setContextView(R.layout.activity_puzzle_picker);
final View view = findViewById(R.id.pickshapes);
view.getViewTreeObserver().addOnGlobalLayoutListener(new OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
int height = view.getMeasuredHeight();
if(height > 0) {
// do whatever you want with the measured height.
setMyViewHeight(height);
// ... and ALWAYS remove the listener when you're done.
view.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
}
});
...
(Note that you haven't set the id of your view in your XML... I'm using R.id.pickshapes because that's what you chose.)

Custom AlertDialog fails to initialize in onCreate()

I'm trying to build a custom AlertDialog by extending the AlertDialog class.
As usual, I'm setting up the dialog inside its onCreate() method. Or, I'm trying to do so:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setTitle("Some title");
this.setButton(BUTTON_POSITIVE, "Click me", (DialogInterface.OnClickListener)null);
final FrameLayout custom = (FrameLayout) this
.findViewById(android.R.id.custom);
custom.addView(this.getLayoutInflater().inflate(R.layout.mydlg, null),
LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}
Now, when it comes to displaying an instance of this dialog, nothing is shown. The current Activity fades out and loses focus but not a single pixel of my dialog is displayed. Pressing Back brings the Activity back to the foreground, indicating to me that a dialog actually is shown, but just a completely empty one.
However, when I create an AlertDialog and use, for instance, dlg.setButton(BUTTON_POSITIVE, "Click me", (DialogInterface.OnClickListener)null);
the dialog is shown with the respective button.
Even when I set up my custom dialog in its constructor using the very same code as above everything seems to work ok.
Now, how can this be? Why can't I seem to initialize my dialog in its onCreate() method? Isn't this the way you're supposed to initialize any GUI element? What am I missing?
EDIT
Please note, that something is 'shown', fading out the Activity and taking focus from it. It's just that it seems to be completely empty/invisible.
Here another attempt:
this.setTitle("Some title");
this.setButton(BUTTON_POSITIVE, "Click me", (DialogInterface.OnClickListener)null);
final View v = this.getLayoutInflater().inflate(R.layout.mydlg, null);
this.setView(v);
These exact lines do work when put into my dialog's constructor.
These exact lines do not work when put into my dialog's onCreate().
What is going on here?!
Generally, am I not supposed to do it in onCreate()? - Am I facing trouble if I resort to doing the above initialization in the constructor instead? (This does not seem too clean to me, anyway.)
You need to call the show() method in order to see something.
You should consider using AlertDialog.Builder instead of subclassing AlertDialog itself. It allows you to do all the things you need in your example (in order: setTitle(),setPositiveButton() and setView() ). Don't forget to call create() at the end to actually get your dialog.
Also, check if your onCreateDialog() and onPrepareDialog() activity methods are implemented correctly. If you don't have them implemented at all (an unmanaged dialog), consider doing that anyway, especially if your app allows for orientation changes. You probably know about this, but here is a tutorial:
http://developer.android.com/guide/topics/ui/dialogs.html
also, DialogFragments are a bit easier way to implement this, but you need a newer API version or the Compatibility package:
http://developer.android.com/reference/android/app/DialogFragment.
One final issue - where are you calling show() in your activity? onResume() should be OK, onCreate() not as much.
Sorry I'm late to the party :)
You have to thing differently for the alert dialog.
The way I did it is to customize the view before creating the alert dialog:
// This is the activity that is the background of the AlertDialog
public class Main extends Activity {
public static final int DIALOG_CONFIG = 1;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
setContentView(R.layout.emptybackground);
}
#Override
protected void onStart() {
super.onStart();
// Open the alert dialog on openning the Activity
showDialog(Main.DIALOG_CONFIG );
}
protected Dialog onCreateDialog(int id) {
LayoutInflater factory = LayoutInflater.from(this);
switch (id) {
case DIALOG_CONFIG:
// Here, we load the existing view R.layout.config
configView = factory.inflate(R.layout.config, null);
configDialog = new AlertDialog.Builder(this)
.setTitle("Configuration")
.setView(configView)
.create();
// Using configView, you can do whatever you want with the view. Here, we add value to a spinner.
Spinner spinner = (Spinner)configView.findViewById(R.id.config_select_conn);
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_spinner_item);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
adapter.add("TCP");
adapter.add("Bluetooth");
spinner.setAdapter(adapter);
return configPrinter;
}
return null;
}
}
you should call custom_alertDialog.create(); before custom_alertDialog.show();

Categories