Android SlidingPaneLayout: Tutorial
In this post, we want to show how to use SlidingPaneLayout. This is an interesting component that can be used when we want to have a multi-pane horizontal layout. This component is divided in two different parts:
- left side: The master part. It usually contains a list of values (i.e. Contacts and so on)
- right side: The detail part. It contains the details of the values in the left side.
This component helps us to divide the available screen space in two different sides that doesn’t overlap and can be sledded horizontally. It is smart enough to know when it is the time to enable the sliding feature or when the screen width is enough to hold both parts.
In this post, we will show how to use this component implementing a bookmark list in one side and the corresponding web content view in the other side. SlidingPaneLayout can be used with fragment or with other standard components, we will show how to use it with fragments. One thing we have to remember is that in our main layout it has to be the root. At the end, we want to have something like the picture shown below:
Let’s start.
SlidingPaneLayout set up
The first thing we need is creating our main layout:
<android.support.v4.widget.SlidingPaneLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/sp" > <!-- Left side pane. (Master) --> <fragment android:id="@+id/leftpane" android:name="com.survivingwithandroid.slidingpanelayout.ListBookmarkFragment" android:layout_width="190dp" android:layout_height="match_parent" android:layout_gravity="left" /> <!-- Right side page. (Slave) --> <fragment android:id="@+id/rightpane" android:name="com.survivingwithandroid.slidingpanelayout.ViewBookmarkFragment" android:layout_width="350dp" android:layout_height="match_parent" android:layout_gravity="right" android:layout_weight="1" /> </android.support.v4.widget.SlidingPaneLayout>
Notice we have two fragment: one on the left side called ListBookmarkFragment and the other one on the right side called ViewBookmarkFragment. At line 1, we have our SlidingPaneLayout as the root element. One thing we have to set up is the width of this two fragment, at line 9 and 16. If the screen width is more than the sum of the two fragments width then the two fragments are visible at the same time, otherwise we have to slide left and right to enable one of them.
I won’t spend much time talking about fragment implementation details because is out of the topic, if you are interested you can give a look at the source code.
The main activity that holds the two fragments and handle the sliding pane is shown below:
public class MainActivity extends Activity { SlidingPaneLayout pane; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); } ... }
Everything is very simple, but we need to implement more thing is we want to “use” the SlidingPaneLayout.
SlidingPaneLayout Listener
The api gives us the capability to listen when the user slide our panes left and right so that we can react in the right way and implement here our business logic. We have to implements an interface called SlidingPaneLayout.PanelSlideListener.
private class PaneListener implements SlidingPaneLayout.PanelSlideListener { @Override public void onPanelClosed(View view) { System.out.println("Panel closed"); } @Override public void onPanelOpened(View view) { System.out.println("Panel opened"); } @Override public void onPanelSlide(View view, float arg1) { System.out.println("Panel sliding"); } }
So in our Activity let’s add this piece of code. As you can notice at line 1 we implement that interface and we get notified when the left panel is closed, is opened and when the user is sliding. We are more interested on the first two events (closed and opened). Then we have to register our listener:
... @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); pane = (SlidingPaneLayout) findViewById(R.id.sp); pane.setPanelSlideListener(new PaneListener()); } ....
Running the code we can notice that when the left pane is closed or opened in the log we get the message. Now we can a way to get notified about the main two events. We can use it to set up correctly the action bar, for example.
SlidingPaneLayout and Actionbar
Using the listener, we can setup correctly the action bar. This aspect is very important because we can change the action bar icons according to the opened panel. In other words, when the left panel is open we can show some icons and when it is closed we can show other icons. To do it, we have simply implement our logic inside the listener methods:
private class PaneListener implements SlidingPaneLayout.PanelSlideListener { @Override public void onPanelClosed(View view) { System.out.println("Panel closed"); getFragmentManager().findFragmentById(R.id.leftpane).setHasOptionsMenu(false); getFragmentManager().findFragmentById(R.id.rightpane).setHasOptionsMenu(true); } @Override public void onPanelOpened(View view) { System.out.println("Panel opened"); getFragmentManager().findFragmentById(R.id.leftpane).setHasOptionsMenu(true); getFragmentManager().findFragmentById(R.id.rightpane).setHasOptionsMenu(false); } @Override public void onPanelSlide(View view, float arg1) { System.out.println("Panel sliding"); } }
We use setHasOptionMenu to turn on and off the fragment menu (line 7,8,14,15). Running the code we have:
As you can notice when you open the left panel the action bar icons change.
Inter fragment communication
One more thing we need to explain: how to exchange information between fragments. In other word, we want when user clicks on a bookmark list on the left side the corresponding web page is opened on the right side.
To get it, we can simply create our interface that acts as a listener and let our main activity implement it. So that when user select an item in the list on the left panel we notify the event to the main activity, that in turns call the right method in fragment that handles the right panel. So we have:
public interface BookmarkListener { public void onChangeBookmark(String bookmark); }
The interface. In the fragment that handles the left panel we have:
.... @Override public void onAttach(Activity activity) { // It's time we check if our activity implements the right inteface if (! (activity instanceof BookmarkListener) ) throw new ClassCastException(); super.onAttach(activity); } ... @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.leftside_layout, container, true); ListView lView = (ListView) v.findViewById(R.id.bookList); LinkAdapter la = new LinkAdapter(bookmarkList, getActivity()); lView.setAdapter(la); lView.setOnItemClickListener(new AdapterView.OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { ( (BookmarkListener) getActivity()).onChangeBookmark( bookmarkList.get(position).link ); } }); setHasOptionsMenu(true); return v; } ...
In the main activity, we have:
public class MainActivity extends Activity implements BookmarkListener{ .... @Override public void onChangeBookmark(String bookmark) { // We get notified when user clicks on a bookmark in the ListBookmarkFragment System.out.println("Bookmark ["+bookmark+"]"); ViewBookmarkFragment f = (ViewBookmarkFragment) getFragmentManager().findFragmentById(R.id.rightpane); f.setBookmark(bookmark); } .... }
At the end we have:
The last thing we need to take care is to set up correctly the icons the first time the app is launched. In this case we need to add this piece of code to the onCreate method:
public class MainActivity extends Activity implements BookmarkListener{ SlidingPaneLayout pane; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); pane = (SlidingPaneLayout) findViewById(R.id.sp); pane.setPanelSlideListener(new PaneListener()); if (!pane.isSlideable()) { getFragmentManager().findFragmentById(R.id.leftpane).setHasOptionsMenu(false); getFragmentManager().findFragmentById(R.id.rightpane).setHasOptionsMenu(true); } }
- Source code available @ github
Nice tutorial but, is the SlidingPaneLayout still experimental??
Great tuto!! Just if someone is wondering, you can modify MainActivity to start with the pane opened, this way:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pane = (SlidingPaneLayout) findViewById(R.id.sp);
pane.setPanelSlideListener(new PaneListener());
if (!pane.isSlideable()) {
getFragmentManager().findFragmentById(R.id.leftpane).setHasOptionsMenu(false);
getFragmentManager().findFragmentById(R.id.rightpane).setHasOptionsMenu(true);
}
pane.openPane();
}
Great! it works fine only when invoke the openPane() method. thanks a lot