I have an android app that let's the user modify some layout parameters. One of my functions let's the user decide if a TextView will be aligned against the top or the bottom of a picture.
This is the function:
private void setAlign(String align) {
/* Get Preferences */
SharedPreferences prefs = getSharedPreferences("prefs", Context.MODE_WORLD_READABLE);
SharedPreferences.Editor editor = prefs.edit();
editor.putString("align", align);
editor.commit();
Log.d("ALIGN", align);
paramAlign = align;
FrameLayout.LayoutParams floLP = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT,
(align == "TOP") ? Gravity.TOP : Gravity.BOTTOM);
txtGoal.setLayoutParams(floLP);
int res = paramAlign == "TOP" ? R.drawable.btn_toolbar_align_top_up : R.drawable.btn_toolbar_align_bottom_up ;
btnAlign.setImageResource(res);
}
Now once the activity is started, this function works fine. However, when I initialize the activity, I call the setAlign() function in the onGlobalLayout method after retrieving the alignment preference.
This is the relevant code:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.personalize);
/* Get Preferences */
SharedPreferences prefs = getSharedPreferences("prefs", Context.MODE_WORLD_READABLE);
paramAlign = prefs.getString("align", "BOTTOM");
Log.d("ALIGN", paramAlign);
// Get screen dimensions and initialize preview
ViewTreeObserver vto = rootView.getViewTreeObserver();
vto.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
setAlign(paramAlign);
ViewTreeObserver obs = rootView.getViewTreeObserver();
obs.removeGlobalOnLayoutListener(this);
}
});
}
Now if you notice the logging functions, they both return "TOP" when I start the activity. And the setAlign() function is obviously getting called. Yet, the TextView is aligned at the bottom. This is the XML for the TextView:
<TextView
android:id="#+id/txtGoal"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="10dip"
android:textColor="#color/color_white"
android:textSize="8sp"
android:shadowColor="#color/color_black"
android:shadowDx="1.2"
android:shadowDy="1.2"
android:shadowRadius="1.2"
/>
Any idea why the setLayoutParams is not happening when the activity is created? The function is getting fired when the layout is done being drawn so it shouldn't be the issue here. And the XML has no gravity specified to start with.
What am I missing here?
First of all, I see that the variable txtGoal is not initialized (nor even declared) so I am assuming you did that somewhere else (that is not posted in the question).
The behavior you are encountering is pretty much normal : the function is working only at start-up, and that's because once you change the layout of your text view, you must indicate that so it will be redrawn, by adding the following :
txtGoal.invalidate ();
after this :
txtGoal.setLayoutParams(floLP);
EDIT:
You can also try changing the gravity in a different way:
txtGoal.setGravity ( Gravity.TOP );
EDIT:
My apologies, what I suggest (the second solution) is wrong, because it changes the gravity of the text inside the text view (and not the gravity of the text view inside the root view).
Please try the following:
Do not try to modify the gravity of your text view using a listener, you can directly apply the gravity you want after setting the content view of your activity (because the text view is thus created). I advise the following:
Apply the new layout to your text view directly (not via the listener) after retrieving the shared preferences.
Your onCreate method should look something similar to this:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.personalize);
/* Get Preferences */
SharedPreferences prefs = getSharedPreferences("prefs", Context.MODE_WORLD_READABLE);
paramAlign = prefs.getString("align", "BOTTOM");
setAlign(paramAlign);
}
You should compare two string using the equals method:
Replace that :
(align == "TOP")
By that
align.equals ( "TOP" )
And this :
paramAlign == "TOP"
By this :
paramAlign.equals ( "TOP" )
So, here is what I found out and what I did to fix my problem.
Apparently the condition
align == "TOP"
Was not testing true when the activity was started altho the Log dump would tell me that it was in fact true at the time. Now why it did that I have no clue. This seems like a weird bug. That condition tested true once the activity was running.
Since this parameter could only have 2 values, I switched it to a Boolean variable where false is now the equivalent of "BOTTOM" and true the equivalent of "TOP" and it is working perfectly.
This is something that might actually need to be looked into as the condition should of tested true at startup.
** EDIT **
You cannot compare 2 strings in java using the "==" operator. You have to use .equals() instead.
Related
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.
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.
I am currently working on a very simple UI for my Android App. My goal is to animate some (I don't know how many yet) buttons on startup and NEVER AGAIN.
So following the official docs, reading java doc and searching on stackoverflow aswell, I finally got it work. Here's what I do with a single test view.
Set the View and the Animation in the OnCreate() method.
private TextView test_text;
private Animation test_anim;
...
protected void onCreate(Bundle savedInstanceState) {
...
test_text = (TextView) findViewById(R.id.text);
test_anim = AnimationUtils.loadAnimation(getApplicationContext(), R.anim.test_animation);
}
Start the Animation in the OnWindowFocusChanged() method.
#Override
public void onWindowFocusChanged(boolean hasFocus) {
test_text.startAnimation(test_anim);
}
This procedure works, the animation is executed when the activity starts, the only problem is that the onWindowFocusChanged() method is called everytime the activity changes status. So the text animates when the app is resumed, when the layout rotates and stuff like that.
So, repeating: My goal is to animate the text only ONCE when the app boots up and then stop forever.
If it helps, I already tried to put the Animation start in other methods like onStart() or onResume(), but the issue remains the same.
You can use SharePreferences, to check a boolean value. If is true or not exists means is first launch or you can animate app in onWindowFOcusChange() method. Set it to false to never aniamte again.
////////////////////////////
/// CONSTANTS
////////////////////////////
private static final String PREF_NAME = "pref_name";
public static final String IS_STARTUP = "is_startup";
////////////////////////////
/// FIELDS
////////////////////////////
private SharedPreferences settings;
#Override
public void onWindowFocusChanged(boolean hasFocus) {
settings = getSharedPreferences(PREF_NAME, MODE_PRIVATE);
if (settings.getBoolean(IS_START_UP, true)) {
test_text.startAnimation(test_anim);
settings.edit().putBoolean(IS_START_UP, false).commit();
}
}
In case you want to aniamte again when app starts next time, you can set the pref IS_START_UP to true when exit the application.
if (!settings.getBoolean(IS_START_UP, false)) {
settings.edit().putBoolean(IS_START_UP, true).commit();
}
Use SharedPreference to store a boolean variable & make it to true immediately after first animation & check this each time before animation starts.
if(!isAnimatedAlready){
animate();
setIsAnimated(true);
}else{}
Simply you can add a boolean variable with initial value true and after first time you can change its value to false and inside onfocus you can add another condition
If(boolean){do the animation;
boolean=false;}
this will do want you want but if you want the animation to be once during the application life cycle you can use shared prefs or simply add a static Boolean variable in application class
On a specific view I am adding a global layout listener:
myView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
// code
LinearLayout linearLayout = (LinearLayout)findViewById(R.id.someView);
if(linearLayout != null && linearLayout.getVisibility() == View.VISIBLE) {
linearLayout.setVisibility(View.GONE);
}
LinearLayout otherLayout = (LinearLayout) findViewById(R.id.someOtherView);
otherLayout.setVisibility(View.GONE);
//other code
}
});
In some cases but I don't know exactly how, during rotation some times it happens that there is NPE for the line otherLayout.setVisibility(GONE)
To be honest I am not sure why the code checks for null in the lines above for the linearLayout and not for the otherLayout but both are defined in the same resource file and are not e.g. removed programmatically anywhere.
The only difference is that the otherLayout is not visible.
So my question is: Are there any things I should look out for on rotation with global layout listeners? Why am I getting NPE in some random cases?
Update:
Both views are part of the same xml file. And actually one defined is after the other. The only difference is that someView is defined as visible and otherView as not visible. Having said that though, there can be such a case where someView is already visible/rendered while otherView has not been yet rendered/made visible when the rotation is happening depending on the current width
I suspect there is a difference between the layouts of findViewById(R.id.someView) and findViewById(R.id.someOtherView). The difference is the timing, for sure, and possibly the layout xml file that it is inflating. With R.id.someOtherView, it is done immediately while R.id.someView, it is executed when the layout is drawn OR at any other time as in screen orientation change since the width/height of the screen changed.
NEW:
final LinearLayout linearLayout = (LinearLayout)findViewById(R.id.someView);
myView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
//LinearLayout linearLayout = (LinearLayout)findViewById(R.id.someView);
if(linearLayout != null && linearLayout.getVisibility() == View.VISIBLE) {
linearLayout.setVisibility(View.GONE);
}
...
}
});
Notes:
I commented out the findViewById() inside onGlobalLayout(), basically removing it.
I think it's not safe to call findViewById() inside the listener since layouts cannot be cached, similar to views and anything related to UI objects. This is what I meant above on my last sentence. I know it's not obvious. I think this explains that the issue is not consistent or strange, as you said essentially.
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.)