First off, I've already looked at this question. In my scenario, I have a fragment that extends support.v4.app.ListFragment. The fragment calls a custom adapter. I need to handle orientation changes on a device that will switch to a 2 pane layout. I believe I'm doing most things right as the view itself changes correctly and I am able to retrieve the data from savedInstanceState. However, my list is always empty. I tried the recommended answer in the linked question (calling setListShown(true)), but I get an exception, "Can't be used with a custom content view". The relevant code is below:
#Override
public void onConfigurationChanged(Configuration newConfig){
super.onConfigurationChanged(newConfig);
LayoutInflater inflater = LayoutInflater.from(getActivity());
ViewGroup group = ((ViewGroup) getView());
group.removeAllViews();
inflater.inflate(R.layout.activity_message_list, group);
if(!messages.isEmpty()){
mAdapter = new MessageListAdapter(getActivity().getApplicationContext(), messages);
}
setListAdapter(mAdapter);
}
The adapter's getView method is never invoked after the configuration change. What else do I need to do to re-hydrate the view? Let me know if you need to see any other code.
Since you are creating a new view then you have to redo all View's initialization that you do in the original piece of code (in onViewCreated or somewhere else). So, in order to initialize the ListView - you should do something like this:
View rootView = inflater.inflate(R.layout.activity_message_list, group);
if(!messages.isEmpty()){
mAdapter = new MessageListAdapter(getActivity().getApplicationContext(), messages);
}
ListView listView = (ListView) root.findViewById(R.id.<your_list_view_id>;
listView.setAdapter(mAdapter);
Just keep in mind that you also have to do all other initialization (creating references to Views and creating onClickListeners or whatever else you're doing)
There is a way to avoid app reload after device orientation changed, you just have to tell system that you'll handle screen orientation changed events on your own.
Have you tried to add parameter android:configChanges="orientation" inside activity tag in Manifest.xml?
With a ton of direction from Chaosit, I was able to achieve what I wanted. My app has a nav drawer activity and I swap the fragment (which is a master-detail type fragment) based on the selection. I added android:configChanges="orientation" so the fragment wouldn't revert back to the default selection I made in the onCreate method of the activity. So, I removed that line and made the adjustments to store the selection in the savedInstanceState bundle. Everything is working as I would have expected now.
Related
I'm a beginner in Android, so I apologize for the mistakes and I'd appreciate any constructive criticism.
I'm writing a basic application with a ListView of images, and when the user clicks on an item in the list, I want to display that image in a ViewPager, where the user can swipe back and forth to browse the whole list of images. Afterwards when the user presses the back button, I want to switch back to the ListView.
I manage the business logic in the MainActivity, which uses MainActivityFragment for the ListView and ImageHolderFragment for ViewPager.
The simplified code so far is as follows:
#Override
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mListItems = new ArrayList<>();
mListItemAdapter = new ListItemAdapter(this, R.layout.list_item, R.id.list_item_name, mListItems);
mListView = (ListView) findViewById(R.id.list_view_content);
mListView.setAdapter(mListItemAdapter);
mDeletedListItems = new ArrayList<>();
mViewPager = (ViewPager) getLayoutInflater().inflate(R.layout.image_display, null, true);
mImageAdapter = new ImageAdapter(getSupportFragmentManager(), mListItems);
mViewPager.setAdapter(mImageAdapter);
mViewPager.setOffscreenPageLimit(3);
mListView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
mViewPager.setCurrentItem(position);
setContentView(mViewPager); // TODO: this is very wrong!
}
});
loadImages();
noContentText = (TextView) findViewById(R.id.no_content_text);
if (mListItems.isEmpty()) {
noContentText.setText(R.string.no_images);
} else {
mImageAdapter.notifyDataSetChanged();
}
}
Although this does work to some extent, meaning that it manages to display the ViewPager when an item in the list is clicked, there are two things about it ringing the alarm bells:
I've read that calling setContentView() for the second time in the same class is pretty much a sin. Nobody explained me why.
The back button doesn't work in this case. When it's pressed, the application is terminated instead of going back to the list view. I believe this is connected to the first point.
I would appreciate any help, explanations if my idea is completely wrong, and if my case is hopeless, I'd like to see a successful combination of ListView and ViewPager with transitions between each other.
Your activity already has R.layout.activity_main set as content view, which rightly displays the list view - that's what the responsibility of this activity is as you defined it. If we want to change what's shown on the screen, we should use a different instance of a building block (activity or fragment) to display the view pager images.
To say the least, imagine if you wanted to change the view to a third piece of functionality or UI, or a fourth... it would be a nightmare to maintain, extend and test as you're not separating functionality into manageable units. Fields that are needed in one view are mixed with those needed in another, your class file would grow larger and larger as each view brings its click listeners, callbacks, etc., you'd also have to override the back button so it does what you want - it's just not how the Android framework was designed to help you. And what if you wanted to re-use UI components in different contexts whilst tapping in to the framework's activity lifecycle callbacks? That's why fragments were introduced.
In your case, the list view could continue to run in your MainActivity and in your click listener, onItemClick you could start a new activity that will hold a viewPager:
Intent i = new Intent(MainActivity.this, MyLargePhotoActivityPager.class);
i.putExtra(KEY_POSITION, position);
// pass the data too
startActivityForResult(i, REQUEST_CODE);
Notice how you could pass the position to this activity as an int extra, in order for that second activity to nicely set the viewPager to the position that the user clicked on. I'll let you discover how to build the second activity and put the ViewPager there. You also get back button functionality assuming your launch modes are set accordingly, if needed. One thing to note is that when you do come back to the list View, you'd probably want to scroll to the position from the view pager, which is why you could supply that back as a result via a request code. The returned position can be supplied back to the list view.
Alternatively, you could use the same activity but have two fragments (see the link further above) and have an equivalent outcome. In fact, one of your fragments could store the list view, and the second fragment could be a fullscreen DialogFragment that stores a viewPager, like a photo gallery (some details here).
Hope this helps.
I've read that calling setContentView() for the second time in the
same class is pretty much a sin. Nobody explained me why.
Well, you kind of get an idea as to why.
When you use setContentView() to display another 'screen' you do no have a proper back stack.
You also keep references to Views (like mListView) that are not visible anymore and are therefore kind of 'useless' after you setContentView() for the second time.
Also keep in mind orientation changes or your app going to the background - you'll have to keep track of the state that your Activity was in which is way more complicated than it has to be if you have one Activity that does two different things.
You won't be arrested for doing things like you do right now, but it's just harder to debug and keep bug free.
I'd suggest using two different Activities for the two different things that you want to do, or use one Activity and two Fragments, swapping them back and forth.
If you insist on having it all in one Activity you need to override onBackPressed() (called when the user presses the back button) and restore the first state of your Activity (setContentView() again, pretty much starting all over).
I really can't make this up so I'd be thankful for any hint. I must make some mistake here (4.1.2).
I have an Activity which, in onCreate(), sets up a subclassed ArrayAdapter for ListView items which render as a Checkable ViewGroup.
The Activity already utilizes a NonConfiguration mechanism to re-build the Adapter upon orientation change. However, it's currently not storing the ListView's getCheckedItemPosition() because I feel it shouldn't be necessary (details below).
Interestingly, what I'm observing is the following.
The Activity is rendered.
The user checks a ListView item.
The ListView item is displayed in a checked state.
The onItemClickListener calls getCheckedItemPosition() for the ListView and gets a correct result.
The user changes the screen orientation.
onCreate() re-builds the 'ListView' and it displays just like before (after onCreate(); see below).
onCreate() calls getCheckedItemPosition() and gets -1 despite the ListView showing the correcly checked item
Upon further examination, the following details emerge.
onCreate():
get ListView resource
build MyAdapter
set MyAdapter as adapter for ListView
getCheckedItemPosition() returns -1
after onCreate():
MyAdapter.getView() is being called
CheckableViewGroup.setChecked() is called with the correct checked value
the last two steps will be repeated for all items
As said before, I'm relying on the Android feature that View objects save their state if they have an ID assigned. I'd say this is the case since there must be some object out there which sets the correct checked status for all the list entries. (By the way, CheckableViewGroup also overrides on{Save,Restore}InstanceState() but that won't be called regardless whether or not it has an ID assigned (presumably because it never gets attached to the layout root?).
So it looks as if the ListView at the same time knows and does not know its getCheckedItemPosition()? Or am I on the wrong track?
public final class MyActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.my_activity);
itemList = (ListView)findViewById(R.id.ma_list);
listAdapter = new MyAdapter();
itemList.setAdapter(listAdapter);
itemList.setOnItemClickListener(
new OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> av, View v, int pos, long id) { checkForm(); }
}
);
listAdapterSetup();
// this is where itemList.getCheckedItemPosition() returns -1
// when the listView has been re-built from NonConfiguration data:
checkForm();
}
}
I was making another test after I posted my question, because I was getting an idea while describing my observations.
And indeed, my suspicion was confirmed.
Timing is key here.
The ListView will report the correct getCheckedItemPosition() value in onResume() (but not before).
So the solution is easy: Perform any evaluation logic in onResume().
Normally I would use a separate activity for each "screen" I wish to display, using different XML files.
However I'm working with Dynamically loading jar files into an android application, so therefore at runtime, I am not aware of how many activities there will be, or how many screens there will be.
At the moment, using java reflection, I am able to return a list of strings from the dynamically loaded java file, and draw each list item, as a separate button onto the screen. If one of these buttons is clicked, i want to be able to load a different "screen" on the stack. So when I press back from this new screen, it goes to the previous screen that called it.
Is it possible to do this without creating a new activity and passing a new intent to it and of course making relevant changes to the android manifest file?
To use blackberry functionality as an example - Is there an equivalent in android to blackberry's push and pop screens? Where the screen ur pushing/popping, would simply extent MainScreen?
If anyone has questions, or If I've been vague, please comment and I will try my best to explain myself, any help is very much appreciated.
The Android equivalent to BB's push/pop screen is startActivity()/finish(). However, you can manage your own views in a single activity by either using a container view (such as ViewSwitcher, as #hasanghaforian suggests) or by simply calling setContentView() with a new view hierarchy whenever you want to change the screen. Be aware that when you call setContentView, any view references that you obtained by calling findViewById will be stale.
In my opinion you should use Fragment. I assume that you have some piece of code where you iterate over the strings:
for(String def : definitions) {
Fragment f = new CustomFragment();
Bundle b = new Bundle();
b.putString("STRING_DEF",def);
f.setArguments(b);
fragments.add(f);
}
in above piece of code a collection of Framents is just created. Let's look at the CustomFragment implementation:
CustomFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
String def = getArguments.getString("STRING_DEF");
//write code to create view
return view;
}
}
Now in your ListActivity you have to implement on click listener more or less like this like this
public void onListItemClick(ListView l, View v, int position, long id) {
FragmentManager fragMgr = getFragmentManager();
FragmentTransaction t = fragMgr.beginTransaction();
t.replace(R.id.id_of_view_place_holder_for_fragment,
fragments.get(position),"FRAGMENT_TAG");
t.commit();
}
you can use ViewSwitcher. ViewSwitcher is a ViewAnimator that switches between two views, and has a factory from which these views are created. You can either use the factory to create the views, or add them yourself. A ViewSwitcher can only have two child views, of which only one is shown at a time.Or you can use fragments.
If you refer to if it is possible to have different layouts in the same activity, the answer is yes.
Activities are independent of layouts, you don't assign the layout for an activity in the manifest, you define what layout to use in the activity calling setContentView() method from Activity class to set a layout.
So if you want to have some layouts (screens) the only thing you have to do is define various layouts and use them when you want calling setContentView(R.layout.the layout), after this call, the layout chosen will be displayed.
If you can't create the layout statically by xml, you can create it dinamically by code as needed by demand each time you want.
In addition you can have a stack of layouts, each time you need a new screen, build it, push it to the stack and call setContentView() method, when you don't need it more, pop off the stack and call setContentView() with the new layout in the top of the stack.
Hope it help you
I've an android app that has multiple views. I've disabled the automatic orientation switching by declaring android:configChanges="keyboardHidden|orientation|screenSize" for my activity.
Q1) How I should re-activate views in onConfigurationChanged()? It seems I need to reset the content view using setContentView method. Is there an alternative way to signal view that now you should inflate the landscape/portrait version of the layout? Some of my views contain quite complicated states/modeless dialogs etc that can appear any time so deleting the view object and re-instantiating it just doesn't sound right.
Q2) What's the best way in onConfigurationChanged() to know which view we should actually now activate i.e. what was focused when orientation change was initiated? I'm reluctant to rely on getCurrentFocus() as it can be null sometimes.
Q1) How I should re-activate views in onConfigurationChanged()?
As you mentioned, setContentView() is a good way to do this. Just remember to not always pass in a layout XML to this method. Instead, pre-inflate your layout into a View object and pass in that View object to setContentView. That way, you don't incur the cost of re-creating your view object on every orientation change.
The following is a sample code - may not work as-is; but meant to illustrate my point.
View mLandscapeView;
View myPortraitView
protected void onCreate(Bundle bundle){
View myLandscapeView = getLayoutInflater().inflate(R.layout.my_landscape, null);
View myPortraitView = getLayoutInflater().inflate(R.layout.my_portrait, null);
//...
}
#Override
protected void onConfigurationChanged(Configuration config){
if(config.orientation = Configuration.ORIENTATION_LANDSCAPE){
//adjust mLandscapeView as needed
setContentView(mLandscapeView);
}
// And so on ...
}
I am using LocalActivityManager activityManager = getLocalActivityManager();, I put all my activities to this by View view = activityManager.startActivity("123", myIntent).
There are many activities in this activityManger which are identical by their ID e.g(123).
1 activity hold favorite Items name FavoriteActivty. Another activity hold items. All I want to do is to notify FavoriteActivty that data has been changed so activity has to refresh itself...
How to do this? I dont want to create that activity again.
I'm not 100% sure if I got what you need.
For my understanding you need to set an adapter to this activity...
then you can just handle a list and give the adapter a "notification"
Example:
adapter = new ArrayAdapter<Module>(this,
android.R.layout.simple_list_item_1, moduleList);
setListAdapter(adapter);
and if you change something (I add here an entry to the list):
moduleList.add(m);
adapter.notifyDataSetChanged();
OK. I finally got my answer. I used the following method:
onAttachedToWindow();
It worked for me. As the window comes to front I check look for the changes.
onResume() ;
onWindowFocusChanged(boolean hasFocus)