Sunday, May 26, 2013

Swipe with Tabs

Preface
Easy navigation and presentation are 2 main things that can make your software more user friendly.  To achieve this we can combine TabHost widget and ViewPager LayoutManager together.

Scenario
The UI is consist of 3 tabs and 3 pages. Selecting each tab will automatically swipe to the corresponding page in the ViewPager. The other way around,  swiping each page will result in the selection of the matching tab. Here I'm not going to discuss how to create tabs you can find so many tutorials on the net. The focus of this article is on how to use these 2 useful widgets together and how they can interact with each other.

swiped on page 2. Tab 2 is selected


Solution
Now, lets look at some code and see how we can implement the scenario. Here we have a MainActivity class. Apart from creating the necessary views and setting their properties in the overridden onCreate method pay attention to the 2 interfaces that this class has implemented. These are the listeners responsible to listening for the appropriate events that we are interested on. 

 public class MainActivity extends Activity implements OnTabChangeListener, OnPageChangeListener{  
      private TabHost host;  
      private ViewPager pager;  
   @Override  
   public void onCreate(Bundle savedInstanceState) {  
     super.onCreate(savedInstanceState);  
     setContentView(R.layout.activity_main);  
     host = (TabHost)findViewById(android.R.id.tabhost);  
     pager = (ViewPager) findViewById(R.id.pager);  
     
     host.setup();  
     TabSpec spec = host.newTabSpec("tab1");  
     spec.setContent(R.id.tab1);  
     spec.setIndicator("First tab");   
     host.addTab(spec);  
     
     spec = host.newTabSpec("tab2");  
     spec.setContent(R.id.tab2);  
     spec.setIndicator("Second tab");  
     host.addTab(spec);  
     
     spec = host.newTabSpec("tab3");  
     spec.setContent(R.id.tab3);  
     spec.setIndicator("Third tab");  
     host.addTab(spec);  
     
     pager.setAdapter(new MyPagerAdapter(this));  
     pager.setOnPageChangeListener(this);  
     host.setOnTabChangedListener(this);  
   }  
      @Override  
      public void onTabChanged(String tabId){  
           int pageNumber = 0;  
           if(tabId.equals("tab1")){  
                pageNumber = 0;  
           } else if(tabId.equals("tab2")){  
                pageNumber = 1;  
           } else{  
                pageNumber = 2;  
           }  
           pager.setCurrentItem(pageNumber);  
      }  
      @Override  
      public void onPageSelected(int pageNumber) {  
           host.setCurrentTab(pageNumber);  
      }  

If you select a tab or swipe a page you can get notified by these listeners methods.As you can see in the above snippet in each method we are instructing the other one to show the appropriate tab or page. For example, when a tab is selected the tag string we assigned to its TabSpec is passed to the onTabChanged method. By comparing the strings we can find out which tab was selected and accordingly set the corresponding page and instructing the pager to set its current item by setCurrentItem which accepts the page number.

The content of our view is read from the file below. If you take a look you can see that each TabSpec is constructed using a FrameLayout

 <?xml version="1.0" encoding="utf-8"?>  
 <TabHost xmlns:android="http://schemas.android.com/apk/res/android"  
   android:id="@android:id/tabhost"  
   android:layout_width="match_parent"  
   android:layout_height="match_parent" >  
   <LinearLayout  
     android:layout_width="match_parent"  
     android:layout_height="wrap_content"  
     android:orientation="vertical" >  
     <TabWidget  
       android:id="@android:id/tabs"  
       android:layout_width="match_parent"  
       android:layout_height="wrap_content" >  
     </TabWidget>  
     <FrameLayout  
       android:id="@android:id/tabcontent"  
       android:layout_width="match_parent"  
       android:layout_height="match_parent" >  
           <FrameLayout  
             android:id="@+id/tab1"  
             android:layout_width="match_parent"  
             android:layout_height="wrap_content"  
             android:visibility="gone" />  
           <FrameLayout  
             android:id="@+id/tab2"  
             android:layout_width="match_parent"  
             android:layout_height="wrap_content"  
             android:visibility="gone" />  
           <FrameLayout  
             android:id="@+id/tab3"  
             android:layout_width="match_parent"  
             android:layout_height="wrap_content"  
             android:visibility="gone" />  
     </FrameLayout>  
     <android.support.v4.view.ViewPager  
       xmlns:android="http://schemas.android.com/apk/res/android"  
       android:id="@+id/pager"  
       android:layout_width="match_parent"  
       android:layout_height="wrap_content"/>  
   </LinearLayout>  
 </TabHost>  

Finally, here is our PageAdapter feeding our ViewPager. for the sake of simplicity each page is consisted of only one TextView.

 public class MyPagerAdapter extends PagerAdapter {  
      private Context ctx;  
      public MyPagerAdapter(Context ctx){  
           this.ctx = ctx;  
      }  
      @Override  
      public Object instantiateItem(ViewGroup container, int position) {  
           TextView tView = new TextView(ctx);  
           position++;  
           tView.setText("Page number: " + position);  
           tView.setTextColor(Color.RED);  
           tView.setTextSize(20);  
           container.addView(tView);  
           return tView;  
      }  
      @Override  
      public int getCount() {  
           return 3;  
      }  
      @Override  
      public boolean isViewFromObject(View view, Object object) {  
           return (view == object);  
      }  
 }  

18 comments :

  1. Sir i am getting an error as
    create a constructor for MyPagerAdapter(MainActivity ) as parameter can you plz help me sir

    ReplyDelete
    Replies
    1. Well, MainActivity extends from android.app.Activity which itself is extended from android.content.Context. In MyPagerAdapter we need to have an access to a Context. So, simply passing the MainActivity solves our problem. There shouldn't be any problems. Make sure you have used the classes mentioned correctly.

      Delete
  2. Thank you very much, worked perfectly.

    ReplyDelete
  3. I'm getting this error " java.lang.unsupportedoperationexception required method destroy item was not overridden "

    ReplyDelete
  4. First, check your class hierarchy and please be more specific as how you are coding. Are exactly using the code in the current post?

    ReplyDelete
  5. thanks so much! very helpful.

    one note: with the ViewPager afterward (and outside) the tabcontent FrameLayout, as in the XML given here, the pages inside the ViewPager never showed up for me. they appeared when i moved the ViewPager either inside or before that FrameLayout.

    thanks again!

    ReplyDelete
  6. The example I've provided is working even if the viewPager is outside and after FrameLayout. I'm running the code against Android 4.0.3 with android-support-v4 library. Check the the version of Android's API documentation you are using for any changes and see if its related to that.

    ReplyDelete
  7. Hi Sir, Great tutorial you have!
    my emulator force shut down when i click on the 3rd tab, can you help me ?

    ReplyDelete
    Replies
    1. Produce the error again. Go to the LogCat and check the stack trace of the error. Then post more specific information without it I can not help you.

      Delete
  8. I got error when changing tab3..
    logcathere

    12-05 01:59:17.582: E/AndroidRuntime(978): FATAL EXCEPTION: main
    12-05 01:59:17.582: E/AndroidRuntime(978): java.lang.UnsupportedOperationException: Required method destroyItem was not overridden
    12-05 01:59:17.582: E/AndroidRuntime(978): at android.support.v4.view.PagerAdapter.destroyItem(PagerAdapter.java:192)
    12-05 01:59:17.582: E/AndroidRuntime(978): at com.example.testtabandviewpager.MyPageAdapter.destroyItem(MyPageAdapter.java:37)
    12-05 01:59:17.582: E/AndroidRuntime(978): at android.support.v4.view.PagerAdapter.destroyItem(PagerAdapter.java:124)
    12-05 01:59:17.582: E/AndroidRuntime(978): at android.support.v4.view.ViewPager.populate(ViewPager.java:948)
    12-05 01:59:17.582: E/AndroidRuntime(978): at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:523)
    12-05 01:59:17.582: E/AndroidRuntime(978): at android.support.v4.view.ViewPager.setCurrentItemInternal(ViewPager.java:495)

    ReplyDelete
  9. I found error..in MyPageAdapter.java..add this code
    @Override
    public void destroyItem(View container, int position, Object object) {
    // TODO Auto-generated method stub
    ((ViewPager) container).removeView((View) object);
    }


    It finally work with no error

    ReplyDelete
  10. This comment has been removed by the author.

    ReplyDelete
  11. HI, its working perfect. I have a device with 2.3 android version of GingerBread.
    I Have 3 activities and i want to swap them but i dont know how to do that.
    Please help me that how i can swap activities :(

    Thanks

    ReplyDelete
  12. Take a look at the Using ViewPager for Screen Slides. For your 3 activities you can create 3 Fragments each representing one activity. Your Adapter then can feed your ViewPager. In the example provided please notice how "ScreenSlidePagerAdapter" creates a new Fragment in "getItem".

    ReplyDelete
    Replies
    1. yes you are right but the problem is that device with 2.3 android version is not supporting fragments.
      Please guide me some thing like that works with 2.3 android version .

      Thanks for the help :)

      Delete
    2. You can use Fragments on devices running Android 1.6 upward. The trick is, if you are running on any version prior to 3.0 you should use Support Library. You can Google it and have more information.

      Delete
  13. eykash file haye projaro ham mizashti.

    ReplyDelete