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

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.)

Related

Android - How to save checkbox state (checked/unchecked) after changing activity

I have searched through StackOverflow, but have not found a proper answer yet.
I have created a ListView (iteration of a checkbox + itemview) and populated it through my customAdapter (which extends BaseAdapter).
I have a button which takes the values and print it on the screen via a Toast.
So far, so good.
Next step, I still have the button in the MainActivity, but the ListView is now in a child activity that I reach by clicking an image (ImageView placed in the MainActivity). I can still check the checkboxes, but I face two issues:
I am still not able to pass the values to the MainActivity, where they will be printed on screen (or manipulated)
As soon as I press the back button to go back to the MainActivity and I press again the image, every CheckBox that was checked is not checked anymore (they came back to default state)
I don't think that code is needed, as it comes from a standard implementation (ListView - customAdapter with ViewHolder implementation, ...), but in case just let me know.
Thanks a lot in advance!
You can put which checkboxes are checked into sharedpreferences. Then move the listview initialization code to Activity's onResume method.
Sample class to handle sharedpreferences data:
class DataHandler {
private final SharedPreferences dataStore;
DataHandler(Context mContext) {
dataStore = mContext.getSharedPreferences("appname", Context.MODE_PRIVATE);
}
int which() {
return dataStore.getInt("some_key",0);
}
void setCheckedItem(int itemwhat) {
dataStore.edit().putInt("some_key",itemwhat).apply();
}
}
For multiple values, you can put them into an array then convert them to string using toString() method and save. And, to get the values:
String x = "2,3,4,5"; //assume
String[] y = new String[]{x};
int checkablepositions = Integer.parseInt(y[0]); // y[0]....y[y.length-1]
Now, at MainActivity's onResume(), Assume that you have initialized ListView as 'mainList'.
CheckBox x1y2z3 = (CheckBox)mainList.getChildAt(new DataHandler(getBaseContext).which());
x1y2z3.setChecked(true);
And for Saving item,
I would recommend you to show them in an alert-dialog instead of in a Toast. Then set a Positive button to get the values from below code and save them.
Or, if you directly save the values from listview onClick :
mainList.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
new DataHandler(getBaseContext()).setCheckedItem(position);
}
});
That's it. I'm really new at programming (as you can see my StackOverFlow rep) but hope it will be able to help you.
The main concept is to : store the value → get the value → parse the value → show it on UI.

Changing background in other activities with a button

I have a problem. I have 3 activities (MainActivity, DetailsActivity, SettingsActivity) and in SettingsActivity I have a Togglebutton "Nightmode". What I want is, when the button is changed, change background of all three activities on gray color.
public class SettingsActivity extends AppCompatActivity {
//This is SettingsActivity(not Main one)
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_settings);
TextView SettingsTitle = (TextView) findViewById(R.id.SettingsTitle);
TextView NightText = (TextView) findViewById(R.id.NightmodeText);
ToggleButton toggleNightMode = (ToggleButton) findViewById(R.id.toggleNightmode);
final RelativeLayout NightBG = (RelativeLayout) findViewById(R.id.NightBG);
final LinearLayout DetailsBG = (LinearLayout) findViewById(R.id.mainBG);
final LinearLayout HomeBG = (LinearLayout) findViewById(R.id.HomeBG);
toggleNightMode.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
NightBG.setBackgroundColor(Color.parseColor("#545657"));
HomeBG.setBackgroundColor(Color.parseColor("#545657"));
DetailsBG.setBackgroundColor(Color.parseColor("#545657"));
}
});
NightBG is in the same activity as that java file (SettingsActivity). But HomeBG is in MainActivity and DetailsBG is in the DetailsActivity. Everytime I start the app, and press on that button, app craches. If I delete HomeBG and DetailsBG from this file, it works just fine with changing current layout's color to gray. Please help me.
One easy way to store little settings like this across multiple activities that may not be open/active at the time of the button click would be to use SharedPreferences.
It might be a little overkill for such a simple piece of code but you can always give it a try if you don't find anything else.
Your code could look something like this:
toggleNightMode.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
// Set the color of this activity
int color = Color.parseColor("#545657")
View view = SettingsActivity.this.getWindow().getDecorView();
view.setBackgroundColor(color);
// Save color preference
SharedPreferences sharedPref = SettingsActivity.this.getSharedPreferences("bgColorFile",Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
editor.putInt("color", color);
editor.apply();
}
});
And then when you open your activities you place something like this in the onStart() or onCreate() method of your activity:
// Get the color preference
SharedPreferences sharedPref = getSharedPreferences("bgColorFile",Context.MODE_PRIVATE);
int colorValue = sharedPref.getInt("color", 0);
View view = this.getWindow().getDecorView();
view.setBackgroundColor(colorValue);
So what you're actually doing is storing the background color as persistent data and fetching it once you reopen/open the activity that you want to have the color on. The benefit of this method is that whenever you close your app the preferred background color will be remembered. I hope this helps.
Change background for current activity in the same activity. Since DetailsActivity is not running, you can't do that, it gives you null pointer. Is kind of you are trying to eat 3 apples and you have just one. After current activity is started, change background.
Update:
You can do that in current activity and just in current activity:
findViewById(android.R.id.content).setBackground(getColor(R.color.your_color));
Don't try to call this in other activities that are not running.
setBackground()
or
setBackgroundColor()
If your other activities are open, you should send a message to the other activities by using an Intent.
How to send string from one activity to another?
When you receive the Intent you could then set the background of the activity.
If your other activities are not open yet, you will not be able to send an Intent to them. In this case you could have each Activity reference a static value in your main activity that could contain the current background color. You would want to reference that value on the other activities on create functions.
Here is an example on how to reference a variable from another activity.
How do I get a variable in another activity?
This might not be the most pretty way to handle it but it should work.
as Ay Rue said you have 2 options: use static variable for that button, and then in onResume of each activity, check the value of the static variable (true or false). or you can save a private variable nightMode and then pass this value in the intent when you need to move to the other two activities.
don't set the background color if you already set before and have an updated background color.

Run a method after View is created - Android

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.

Why does LinearLayout child getWidth method return 0?

I have a LinearLayout, and this LinearLayout will hold dynamically placed views. I need to find out what the width of the children of LinearLayout, however this has to be done in onCreate method. From researching I've found out that you can't use getWidth from this method. So instead I'm using onWindowFocusChanged, which works for the parent LinearLayout (returning me the actual size), but it doesn't work with its children.
Another thing I noticed is that when the screen is fading away and the screen is locked, I can see at the logs the actual width of the children being returned (I think the activity is being paused).
I'm really stuck and this is needed because I need to dynamically place those views depending on the children width.
You might be able to get with the below. But as others pointed out, this probably isn't a great idea.
LinearLayout.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
LinearLayout.getMeasuredWidth();
inside the onCreate , views still can't know the state of the nearby views and the children ,etc... so only after all is prepared and the layout process is done , you can get the size of views .
here's a quick code for getting the size of the view just before it's being drawn:
private static void runJustBeforeBeingDrawn(final View view, final Runnable runnable)
{
final ViewTreeObserver vto = view.getViewTreeObserver();
final OnPreDrawListener preDrawListener = new OnPreDrawListener()
{
#Override
public boolean onPreDraw()
{
Log.d(App.APPLICATION_TAG, CLASS_TAG + "onpredraw");
runnable.run();
final ViewTreeObserver vto = view.getViewTreeObserver();
vto.removeOnPreDrawListener(this);
return true;
}
};
vto.addOnPreDrawListener(preDrawListener);
}
alternatively , you can use addOnGlobalLayoutListener instead of addOnPreDrawListener if you wish.
example of usage :
runJustBeforeBeingDrawn(view,new Runnable()
{
#Override
public void run()
{
int width=view.getWidth();
int height=view.getHeight();
}
});
another approach is to use onWindowFocusChanged (and check that hasFocus==true) , but that's not always the best way ( only use for simple views-creation, not for dynamic creations)
EDIT: Alternative to runJustBeforeBeingDrawn: https://stackoverflow.com/a/28136027/878126
https://stackoverflow.com/a/3594216/1397218
So you should somehow change your logic.

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