Replacing the view with fragments - android

I was looking into changing my solution to use Fragments instead of ActivityGroups and found an example which allowed me to change the part of my ui depending on a ListView lying above it. What I want to do is have a ListView which, when an item is clicked, replaces itself so that only the new layout is visible - Is this possible?
Below you'll see the relevant code example I am using :
public class FragmentTestActivity extends FragmentActivity implements OnItemClickListener
{
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
ListView l = (ListView) findViewById(R.id.number_list);
ArrayAdapter<String> magzTitles = new ArrayAdapter<String>(getApplicationContext(),
android.R.layout.simple_list_item_1,
new String[]{"Cupcake",
"TaberTHULE",
"Lite"});
l.setAdapter(magzTitles);
l.setOnItemClickListener(this);
}
public void onItemClick(AdapterView<?> parent, View view, int position, long id)
{
Fragment testFragment = new TestFragment(position++);
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
ft.replace(R.id.the_frag, testFragment);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.addToBackStack(null);
ft.commit();
}
}
main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/frags"
>
<ListView
android:id="#+id/number_list"
android:layout_width="150dp"
android:layout_height="match_parent"
/>
<fragment class="com.android.saket.fragments.TestFragment"
android:id="#+id/the_frag"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</LinearLayout>

This is possible, what I'd do is to use a Framelayout as the container and then have two different fragments which will be switched out (the switching happens in your activity). This is pretty straight forward and you should not have troubles finding examples.

Related

Android: Fragment is added below childview

I have a FrameLayout with a few buttons, and some ImageViews. When I add a fragment, it shows on top of the ImageViews as expected but below the buttons but I don't know why.
I searched through a lot of the SO posts but couldn't find problems similar to mine.
I have a custom onClickListener that adds the fragment
Custom clickListener class:
public void onClick(View v) {
// a context is passed to the listener
// this gets rootview id
int id= ((ViewGroup)((Activity)context).getWindow().getDecorView().findViewById(android.R.id.content)).getChildAt(0).getId();
MyFragment myFragment = new MyFragment();
FragmentTransaction ft = ((Activity) context).getFragmentManager().beginTransaction();
ft.add(id, myFragment, "F1");
ft.commit();
}
The View I am adding the fragment to:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/startscreen"
>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="#drawable/gamelogo"
android:id="#+id/logo"
android:scaleType="fitStart"
android:adjustViewBounds="true"
/>
</FrameLayout>
Activity:
protected void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.startmenu);
final FrameLayout r = (FrameLayout) findViewById(R.id.startscreen);
addViews(r);
}
public void addViews(FrameLayout r){
// add some buttons to r
// add custom OnClickListener to one of the buttons
// add some ImageViews
// add animation to one button
}
FrameLayout is designed to block out an area on the screen to display a single item. Generally, FrameLayout should be used to hold a single child view, because it can be difficult to organize child views in a way that's scalable to different screen sizes without the children overlapping each other.
That's from the Docs, did you considered to change the layout?
I figured that I have to add the fragment to the parent of the base FrameLayout. I don't know why I would have to do that though.

addToBackStack exits app

I call addToBackStack before committing a 'replace' transaction. My understanding is that hitting android's built in 'back' button should cause the transaction to reverse, reverting focus to the original fragment, but instead the app closes. This isn't a crash; my log reveals no errors. My thought is that the backstack is empty, and that this is the default behavior for hitting 'back' on an empty backstack, but the backstack shouldn't be empty. I'm pretty new to this, so I'm wondering if I'm not implementing something I should have.
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String[] names = new String[] {"Stephen Fry", "Bertrand Russell", "Hugh Laurie", "Angela Merkel"};
final String[] descriptions = new String[] {"Comedian, actor, quizmaster, and all around goodie",
"Mathematician, philosopher, maker of sacreligious kettles",
"Comedian, pianist, and fake TV doctor",
"German chancellor, EU ringleader, and bad haircut haver"};
final FragmentManager fm = getFragmentManager();
final TextFragment descriptionsFragment = (TextFragment) fm.findFragmentById(R.id.descriptions_fragment);
final TextView descriptionsView = (TextView) findViewById(R.id.descriptions_view);
ListAdapter namesAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, names);
final ListFragment namesFragment = new ListFragment(){
#Override
public void onListItemClick(ListView l, View v, int position, long id){
fm.beginTransaction().add(R.id.container, descriptionsFragment).addToBackStack(null).commit();
descriptionsView.setText(descriptions[position]);
}
};
namesFragment.setListAdapter(namesAdapter);
fm.beginTransaction().replace(R.id.container, namesFragment).commit();
}
And the activity layout:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.twofragmentsthree.MainActivity"
tools:ignore="MergeRootFrame" >
<fragment android:name="com.example.twofragmentsthree.TextFragment"
android:id="#+id/descriptions_fragment"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<TextView
android:id="#+id/descriptions_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</fragment>

Android App: MainActivity Can't Find ListView/Crashes on Landscape->Portrait Mode

I'm having two issues with my Android application. I'll start with the relevant code, which I'm 99% certain is locked within my MainActivity class:
package com.simplerssreader;
import android.content.res.Configuration;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
public class MainActivity extends FragmentActivity {
private ListView list;
private TextFragment text;
private RssFragment rss;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) {
// Initializing AddNote fragment
if (getFragmentManager().findFragmentByTag("RSS") == null)
{
rss = new RssFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, rss, "RSS").commit();
}
}
if (getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE)
{
if (getFragmentManager().findFragmentByTag("RSS") == null)
{
rss = new RssFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, rss, "RSS").commit();
}
if (getFragmentManager().findFragmentByTag("TEXT") == null)
{
text = new TextFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.text, text, "TEXT").commit();
}
}
//View view = getLayoutInflater().inflate(R.layout.fragment_layout, null);
list = (ListView) this.findViewById(R.id.listView);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id)
{
System.out.println("YES!");
/*RssAdapter adapter = (RssAdapter) parent.getAdapter();
RssItem item = (RssItem) adapter.getItem(position);
TextFragment fragment = new TextFragment();
fragment.setLink(item.getLink());
FragmentManager fragMgr = getSupportFragmentManager();
FragmentTransaction xact = fragMgr.beginTransaction();
xact.replace(R.id.text, fragment, "TEXT");*/
}
});
if (savedInstanceState != null) {
text = (TextFragment) getSupportFragmentManager().findFragmentByTag("TEXT");
rss = (RssFragment) getSupportFragmentManager().findFragmentByTag("RSS");
}
}
#Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean("fragment_added", true);
}
}
(Note that I'm not concerned with the code within the onItemSelected at the moment; I'll deal with that later, when I actually have working code.)
So my first problem is with this line:
list = (ListView) this.findViewById(R.id.listView);
What I'm hoping to attain here is the ListView found in the current display (in portrait or landscape mode, the list view will be displayed). I also attempted another method in the commented out line above that, but it also failed. The error here is a NullPointerException.
My second issue is that whenever I go to portrait mode from landscape mode, the application crashes. I can go from portrait to landscape, and boot in either mode, but going back to portrait kills the application. I've made the proper checks for orientation in onCreate so I'm not sure of where I'm going wrong. The error here states that there's no TextView to find in the current view- but that should only be called when I'm in landscape mode, right?
Anyways, any help would be greatly appreciated!
Here are my layouts:
fragment_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="#+id/listView"
android:layout_width="fill_parent"
android:layout_height="fill_parent" >
</ListView>
<ProgressBar
android:id="#+id/progressBar"
style="?android:attr/progressBarStyleLarge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
</RelativeLayout>
main.xml (portrait)
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:id="#+id/fragment_container"
android:layout_height="fill_parent"
/>
text_view.xml
<?xml version="1.0" encoding="utf-8"?>
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/text"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
main.xml (landscape)
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent" android:layout_height="match_parent"
android:baselineAligned="false">
<FrameLayout android:id="#+id/fragment_container" android:layout_weight="1"
android:layout_width="0px" android:layout_height="match_parent"
android:background="?android:attr/detailsElementBackground" />
<FrameLayout android:id="#+id/text" android:layout_weight="2"
android:layout_width="0px" android:layout_height="match_parent"
android:background="?android:attr/detailsElementBackground" />
</LinearLayout>
First, try moving the code where you get the list view to the fragment.
Also, in
if (savedInstanceState != null) {
text = (TextFragment) getSupportFragmentManager().findFragmentByTag("TEXT");
rss = (RssFragment) getSupportFragmentManager().findFragmentByTag("RSS");
}
you overwrite the text even if there was no TEXT fragment before, in case there's a savedInstance
Finally, you have duplicate ids:
<FrameLayout android:id="#+id/text" android:layout_weight="2"
android:layout_width="0px" android:layout_height="match_parent"
android:background="?android:attr/detailsElementBackground" />
and
<WebView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/text"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
Firstly, I don't see your ListView set to an Adapter in your activity. Here is just a simple example, where customtextview is a simple TextView and items a string array:
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, R.layout.customtextview, items);
listView.setAdapter(adapter);
Then, you have two id with "text" (R.layout.text_view and FrameLayout in R.layout.main landscape). If you use both in one of your Fragment, this will crash. Change one of them..
And, because you have the FrameLayout "text" only in landscape mode, you can do your onCreate method as below (check if this FrameLayout is present or not):
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// in both layout, you add this Fragment in the same FrameLayout
// check the savedInstanceState, if isn't null = orientation has changed
if (savedInstanceState == null)
rss = new RssFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, rss, "RSS").commit();
} else {
rss = (RssFragment) getSupportFragmentManager().findFragmentByTag("RSS");
}
/*
** check if FrameLayout text is null (true = landscape)
*/
if(findViewById(R.id.text) != null) {
// check the savedInstanceState
if (savedInstanceState == null) {
text = new TextFragment();
getSupportFragmentManager().beginTransaction()
.add(R.id.text, text, "TEXT").commit();
} else {
text = (TextFragment) getSupportFragmentManager().findFragmentByTag("TEXT");
}
}
/*
** else the id isn't exist (false = portrait) // do nothing
*/
}
Then, in your RSS Fragment, set your ListView:
private ListView list;
String[] items = new String[] {
"One",
"Two",
"Three",
"And so on.."
};
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_layout, container, false);
list = (ListView) v.findViewById(R.id.listView);
ArrayAdapter<String> adapt = new ArrayAdapter<String>(getActivity(), android.R.layout.simple_list_item_1, items);
list.setAdapter(adapt);
list.setOnItemClickListener(new AdapterView.OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
System.out.println("YES!");
/* some stuff */
}
});
return v;
}
To save a fragment state on a activity, you can use setRetainInstance(true). There is a great post on it, and a tutorial. For more information about what happens with fragment and orientation changed, you can read this answer.
Let me know if this is helpful.
I searched a bit on the web, and I found these tutorials which might help you a lot:
great: Create Android Dynamic view with Fragments
great: User Interface Design: Working With Fragments
not bad: Fragment example tutorial using WebView
All works with WebView and they mostly use findFragmentById and not tag. Maybe it's the clue..
But you can add directly on the xml the appropriate fragments, as:
- Portrait Mode :
o layout 1 : listview (ex fragment RSS)
o layout 2 : fragment WebView
- Landscape Mode :
o layout 1 :
LinearLayout (id container) : listview (ex fragment RSS) + framelayout
And on your FragmentActivity, you host your list and check the orientation, like the following:
ListView list = (ListView) findViewById(...);
list.setAdapter(...);
LinearLayout container = (LinearLayout) findViewById(R.id.container);
if(container != null) {
// your framelayout is present so set your fragment WebView
// inside it with a fragment transaction and also
// use the item clicked to send info to this fragment
} else {
// call a new activity on item clicked to display the WebView
}
This should work..
I hope these added informations will help you to achieve that.

Listview as a detail fragment in master/detail view

Am creating a master/detail fragment from this link. This works fine, now here instead of textview in detail fragment class i would like to implement listviews, (ie) i would like to display the details of the masterview as a child listview, hence i tried
detail.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical" >
<ListView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:id="#+id/listview1"
/>"
<ListView
android:layout_width="120dp"
android:layout_height="wrap_content"
android:id="#+id/listview2"
/>"
</LinearLayout>
and in my detailFragment class
public class DetailFragment1 extends ListFragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] values = new String[] { "1", "2", "3",
"4" };
ArrayAdapter<String> adapter = new ArrayAdapter<String>(getActivity(),
android.R.layout.simple_list_item_1, values);
setListAdapter(adapter);
}
My problem is child listview is displaying at initial stage but i want to display this listview when i click the first row of master page.Am new to fragments, so help me in achieving this. Thanks in advance..
Create a CallBack interface:
public interface Callbacks {
public void onItemSelected(long id);
}
Let the fragment inplement it:
public class DetailFragment1 extends ListFragment implements CallBacks {
In the ListFragment OnListitemClick:
#Override
public void onListItemClick(ListView listView, View view, int position, long id) {
mCallbacks.onItemSelected(id);
}
In the Activity:
Public class MyActivity extends FragmentActivity implements Callbacks {
#Override
public void onItemSelected(long id) {
// Replace the detail fragment with the corresponding item selected in the list
}
Instead of doing like this, you can design a layout which contains ListView as master and frameLayout container. In frameLayout container, you can dynamically inflate layout, which is designed in such a way that you can have child listview and details view.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
tools:context=".NavigationActivity" >
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
android:background="#color/strip_background"
android:layout_weight="1"
android:orientation="vertical" >
<ListView
android:id="#+id/nav_lv_transactions"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none" >
</ListView>
</LinearLayout>
<FrameLayout
android:id="#+id/nav_fl_frag_container"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="14"
android:background="#android:color/darker_gray" >
</FrameLayout>
</LinearLayout>
In onclick listerner put this code,
Bundle arguments = new Bundle();
arguments.putString(ARG_ITEM_ID,
"Some Name");
MyClassFragment newFragment = new MyClassFragment();
newFragment.setArguments(arguments);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.nav_fl_frag_container, newFragment);
transaction.commit();
Try this. It will work

Transitioning from one ListFragment to another

I'm trying to build a basic ListFragment based application which transitions from one ListFragment to another, based upon user input.
I make use of the default ListView that the Android system inflates for a ListFragment, thus I dont over-ride onCreateView().
To set the margins around the ListFragment, I add a GlobalLayoutListener.
Now, when I launch the application, the first screen containing the default fragment shows up properly, with the margins set correctly.
But as soon as I click an image in the main layout, which invokes a transition to a second fragment having the same onActivityCreated() method and the GlobalLayoutListener as the first fragment, I get the dreaded Content View not created yet error in the OnGlobalLayout() method when I try to access the ListView in the second fragment.
public void onActivityCreated(Bundle savedInstanceState){
super.onActivityCreated(savedInstanceState);
setListAdapter(new ArrayAdapter<String>(getActivity(),
R.layout.listcommon, R.id.label, MAIN_TITLES));
ListView lv = getListView();
lv.setDivider(null);
lv.setDividerHeight(0);
lv.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
lv.setItemChecked(0, true);
lv.setSelection(0);
ViewTreeObserver observer = getListView().getViewTreeObserver();
observer.addOnGlobalLayoutListener(fragmentLayoutListener);
}
ViewTreeObserver.OnGlobalLayoutListener fragmentLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener(){
public void onGlobalLayout() {
//Crashes here for second fragment
ListView listView = getListView();
FrameLayout.LayoutParams params = (LayoutParams) listView.getLayoutParams();
params.setMargins(10, 10, 10, 10);
}
};
Here's the main layout: (Default fragment gets added through FragmentTransaction.add() to first FrameLayout)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:baselineAligned="false"
android:background="#00000000">
<FrameLayout
android:id="#+id/TitlesContainer"
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="40"
android:layout_margin="10dip"
android:background="#drawable/titlesbg"
>
</FrameLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="0px"
android:layout_weight="60"
android:background="#00000000"
>
<ImageView
android:id="#+id/imageView_clickWheel"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:src="#drawable/clickImage" />
</FrameLayout>
</LinearLayout>
Any thoughts on what I should be doing to avoid running into this error?
Should I be over-riding onCreateView() after all and inflate a custom list view (but I have no such need)?
EDIT:
Here's how I add the default fragment to the main activity's onCreate():
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
TitlesFragment fragment = (TitlesFragment) fragmentManager.findFragmentByTag(MAIN_SCREEN_TAG);
if (fragment == null){
fragment = TitlesFragment.newInstance(SCREEN_MAIN);
fragmentTransaction.add(R.id.TitlesContainer, fragment, MAIN_SCREEN_TAG);
} else {
fragmentTransaction.add(R.id.TitlesContainer, fragment);
}
fragmentTransaction.commit();
To perform the transition, this is what I do:
fragment = (TitlesFragment) fragmentManager.findFragmentByTag(SECOND_FRAGMENT_TAG);
if (fragment == null){
fragment = TitlesFragment.newInstance(SECOND_FRAGMENT);
fragmentTransaction.replace(R.id.TitlesContainer, fragment, SECOND_FRAGMENT_TAG);
} else {
fragmentTransaction.replace(R.id.TitlesContainer, fragment);
}
fragmentTransaction.commit();
Since you didn't mention it and haven't shown your full code, I'm guessing a bit here, but the following could be the cause of your issue.
I think you need to create your view first before you can access elements of it. See here and here.
An example from one of my projects:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.leftlistfragment, container, false);
return v;
}
EDIT
Maybe the issue is the way you are creating the fragment (I'm not familiar with the method you are using with tags). Maybe this might work for you too:
titleFrag = new TitlesFragment();
getFragmentManager().beginTransaction()
.replace(R.id.TitlesContainer, titleFrag, SECOND_FRAGMENT_TAG).commit();
I got rid of the OnGlobalLayoutListener. Instead I set 'layout_padding' attribute on the first FrameLayout to set the margins on the Fragment that it encloses.

Categories

Resources