Samstag, 3. März 2012

Use of Fragments in Android

Android-Apps generally should be able to run on several different devices. Especially resolution and orientation (portrait, landscape) of an application havte to be taken into account. In particular the latter is a challenge for the developer of an application. To build an app that looks acceptable horizontally and vertically is not a trivial task - particularly if you want to avoid to much scrolling.
Introduced by Android 3.0 Honeycomb the Fragments might be helpful. Those are parts of an Activity, getting developed independently. Particularly they have a lifecycle for themselves - you find details in the android documentation. Fragments are only usable as part of an Activity.
To show you how to handle Fragments, I would like to introduce a rudimentary use case, that might serve as a base for more general cases.
What will be implemented is a list of values (first fragment) and the detail view of one selected value (second fragment). In the landscape view list and detail shall be shown beside (meaning one Activity). In the portrait view at first only the list will be seen and the selection of an element will fork into the detail view (two Activities).
The code of the base Activity is the same in both cases:

public class FragmentActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.fragment);
    }
}
The layouts have to be seperated. For landscape in /res/layout-land/fragment.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >

    <fragment class="de.kluck.fragment.FragmentList"
        android:id="@+id/fragment_list"
        android:layout_weight="1"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </fragment>

    <fragment class="de.kluck.fragment.FragmentDetail"
        android:id="@+id/fragment_detail"
        android:layout_weight="1"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </fragment>

</LinearLayout>
For portrait in /res/layout-port/fragment.xml:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:orientation="horizontal" >

    <fragment class="de.kluck.fragment.FragmentList"
        android:id="@+id/fragment_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </fragment>
</LinearLayout>
For the detail view in the portrait mode another layout has to be created (/res/layout-port/fragment_detail_activity.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <fragment class="de.kluck.fragment.FragmentDetail"
        android:id="@+id/fragment_detail"
        android:layout_width="match_parent"
        android:layout_height="match_parent" >
    </fragment>
</LinearLayout>
And the Activity for the detail view has to be coded, too.

public class FragmentDetailActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
		setContentView(R.layout.fragment_detail_activity);
		Bundle extras = getIntent().getExtras();
		if (extras != null) {
			String s = extras.getString("selectedValue");
			TextView view = (TextView) findViewById(R.id.text_detail);
			view.setText(s);
		}
	}
}
The development of the list fragment looks quite simple, since Android already provides a ListFragment class. You just need to subclass this class and set its ListAdapter. A layout is not needed, as far as the standard layout is used for the list entries.
Even in this class the click event for the list entry has to be processed. This is the only place where it is necessary to distinguish the modes the Activity might be in. Depending on this in the landscape mode simply the selected value has to be shown in the detail Fragment and in portrait mode the detail Activity is started via Intent.

public class FragmentList extends ListFragment {
	@Override
	public void onActivityCreated(Bundle savedInstanceState) {
		super.onActivityCreated(savedInstanceState);
		String[] values = new String[] { "One", "Two", "Three", "Four", "Five" };
		ArrayAdapter adapter = new ArrayAdapter(getActivity(),
				android.R.layout.simple_list_item_1, values);
		setListAdapter(adapter);
	}

	@Override
	public void onListItemClick(ListView l, View v, int position, long id) {
		String item = (String) getListAdapter().getItem(position);
		FragmentDetail fragment = (FragmentDetail)getFragmentManager().findFragmentById(R.id.fragment_detail);
		if (fragment != null && fragment.isInLayout()) {
			fragment.setText(item);
		} else {
			Intent intent = new Intent(getActivity().getApplicationContext(), FragmentDetailActivity.class);
			intent.putExtra("selectedValue", item);
			startActivity(intent);

		}
	}    
}
The detail fragment implements a method to set the text.

public class FragmentDetail extends Fragment {
	@Override
	public View onCreateView(LayoutInflater inflater, ViewGroup container,
			Bundle savedInstanceState) {
		View view = inflater.inflate(R.layout.fragment_detail, container, false);
		return view;
	}

	public void setText(String item) {
		TextView view = (TextView) getView().findViewById(R.id.text_detail);
		view.setText(item);
	}
}
The layout for the detail fragment is kept simple in this case (/res/layout/fragment_detail.xml):

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/text_detail"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="center_horizontal|center_vertical"
        android:layout_marginTop="20dp"
        android:text="Detail"
        android:textSize="30dp" />
    
</LinearLayout>
What finally misses is the code for the detail Activity, that just needs to fetch the detail text of the Intent at start and set it on the Fragment.

public class FragmentDetailActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
		setContentView(R.layout.fragment_detail_activity);
		Bundle extras = getIntent().getExtras();
		if (extras != null) {
			String s = extras.getString("selectedValue");
			TextView view = (TextView) findViewById(R.id.text_detail);
			view.setText(s);
		}
	}
}
So far, so simple. As you can see, by the use of the Fragments their code is simply reusable. But the developer still has to maintain the layouts for the different modes.
Remark: just like the ListFragment there are PreferenceFragment and DialogFrament classes for the specific use cases.

Keine Kommentare:

Kommentar veröffentlichen