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);  
      }  
 }  

Friday, January 25, 2013

ViewPager, Fragment and one oversight

Scenario
There is a  fragment which inflates a view consisting of some UI widgets (few TextViews, Spinners and etc). A ViewPager will load multiple instances of this fragment with different data using FragmentPagerAdapter. As you can see in the first snippet below the data is received through an intent from another activity (first part in red). Then iterating over the data we are creating new fragments, passing as an argument the data we want to populate each fragment with (second part in red). For example, consider the data as a list of all tasks assigned to you by your manager. It consists of subject shown in a TextView, status of the task shown in a Spinner and whether its finalized or not shown with a CheckBox.

Problem
ViewPager loads first fragment with the help of  the assigned adapter. The data is loaded into the UI views, so far so good. Now, if we try to swipe to the next fragment we notice that the data either is not populated into the second fragment's UI or it is partially populated. 
public class MyActivity extends FragmentActivity {  
           @Override  
           public void onCreate(Bundle savedInstanceState) {  
                super.onCreate(savedInstanceState);  
                setContentView(R.layout.activity_my_layout);  
                ViewPager pager = (ViewPager) findViewById(R.id.dataPager);  
                List<MyData> dataList = (List<MyData>) getIntent()  
                          .getSerializableExtra("DATA");  
                List<Fragment> fragments = new ArrayList<Fragment>();  
                int i = 0;  
                if (dataList != null) {  
                     for (MyData data: dataList) {  
                          MyFragment myFragment = new MyFragment();  
                          Bundle args = new Bundle();  
                          args.putSerializable("MYDATA", data);  
                          myFragment.setArguments(args);  
                          fragments.add(myFragment);  
                     }  
                MyPagerAdapter adapter = new MyPagerAdapter(getSupportFragmentManager(), fragments);  
                pager.setAdapter(adapter);  
           }  
           private class MyPagerAdapter extends FragmentPagerAdapter {  
                private List<Fragment> myFrags;  
                public MyPagerAdapter(FragmentManager manager,  
                          List<Fragment> frags) {  
                     super(manager);  
                     this.myFrags = frags;  
                }  
                @Override  
                public Fragment getItem(int paramInt) {  
                     return myFrags.get(paramInt);  
                }  
                @Override  
                public int getCount() {  
                     return myFrags.size();  
                }  
           }  
      }  
 }  

Solution
In the onCreateView of the fragment we are inflating a view for our fragment's UI. The problem arises when we try to find our UI widgets from findViewById of the activity instead of the inflated view of the current fragment. You can clearly see this in the snippet below. In our example the code in green is the correct way of finding a view.

Public class MyFragment extends Fragment{
      . . .  
      private View rootView;  
      . . .  
      @Override  
      public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {  
           rootView = inflater.inflate(R.layout.myLayout, container, false);   
           return rootView;  
      }  
      @Override  
      public void onActivityCreated(Bundle savedInstanceState) {  
           super.onActivityCreated(savedInstanceState);  
           . . .  
           ((TextView) getActivity().findViewById(R.id.my_Text_view))  
                               .setText("MY_TEXT");  
           ((TextView) rootView.findViewById(R.id.my_Text_view))  
                               .setText("MY_TEXT");  
           . . .  
      }   
           . . .  




Saturday, January 5, 2013

Event Bubbling and Event Capturing

Preface
Sometimes order of events becomes important. Different browsers behave differently. Precedence of events differs in browsers. Some might handle them in capturing phase and some in bubbling phase or both (W3C model). Precedence makes more sense when you have elements inside one another.

Scenario
We have a table with one cell. There is a combo-box in the cell. Suppose the requirement is to  reverse the order of handlers for onClick and see if we can stop event propagation.

For doing so you can make use of addEventListener method described in W3C Event Capture. This function accepts 3 arguments. Event, handler function and a boolean for capturing/bubbling. First of all we are registering handler when the page load is complete by using onload event.
By passing true to addEventListener we indicate that we are interested in capturing phase and if false
it means we are interested in bubbling phase.
Lets have an example. If you click on the combo, first any ancester is checked for an event handler. Since the capturing phase is set to true, tblBblClicked will be called then since there is no other handlers in capturing phase it will start the bubbling phase and calls comboBblClicked.
You can also turn off all the handlers in the capturing phase by setting both listeners to false.
...
 function comboBblClicked(){  
      alert("combo bbl is clicked");  
 }  
 function tblBblClicked(e){  
      alert("tbl bbl is clicked!");  
 }  
 function registerEventListeners(){  
      var tblBbl = document.getElementById('tblBbl');  
      var comboBbl = document.getElementById('cmbBbl');  
      tblBbl.addEventListener('click',tblBblClicked,true);  
      comboBbl.addEventListener('click',comboBblClicked,false);  
 }  
 </script>  
 </head>  
 <body onload="registerEventListeners();">    
 <table id="tblBbl" border="1" >  
      <tr>  
           <td>  
                <select id="cmbBbl" >  
                     <option value="opt1" >opt1</option>  
                </select>  
           </td>  
      </tr>  
 </table>  
...

Now if you want to stop the propagation of events in one of your event handlers you can make use of
e.stopPropagation() in the W3C model. "e" is the event passed to your handler function.