Android Tab Layout with Swipeable Views

My previous article explains about Android Tab Layoutand it got very good ranking in search engines. But unfortunately TabHost is deprecated by android in favor of fragments. So it is suggested that use fragment to achieve tab layout.

This article shows you how to create tab layout using fragments and viewpager. Also you can swipe between tab view as it is the functionality of viewpager which is not possible when using TabHost.

VIDEO DEMO

ViewPager and Fragments

Before getting into this tutorial it is suggested to have knowledge on Fragments and ViewPager as these two are main concepts used here. Unfortunately I haven’t covered about fragements and viewpager on androidhive :(

Layout Overview

Checkout the following pic which explains the complete overview of layout architecture. Basically we are using ViewPager as main layout and for individual pager views we use Fragments. The tabs are part of Action Bar.

android tab layout with swipeable views

Creating new Project

Even though you are not familiar with ViewPager or Fragments, don’t worry. You will get an idea about what those are and how to use them once you are done through this article. So let’s start by creating a new project.

1. Create a new project in Eclipse from File ⇒ New ⇒ Android ⇒ Application Project. While creating the project select the app theme which has Action Bar as shown in the below image.

android tabs with swipe gesture

2. As we are going to use Fragments, extend your main activity from FragmentActivity. Alsoimplement this class from ActionBar.TabListener as we are adding Tabs too.

public class MainActivity extends FragmentActivity implements
        ActionBar.TabListener {

3. Open main activity layout file and add ViewPager element. (My layout file for main activity isactivity_main.xml)

activity_main.xml
<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="match_parent">
</android.support.v4.view.ViewPager>

4. I normally prefer to create a separate package for adapter classes just to separate them from activity classes. So create a new package named your_package_name.adapter. I named my new package asinfo.androidhive.tabsswipe.adapter

5. I am creating a FragmentPagerAdapter class to provide views to tab fragments. Create a class called TabsPagerAdapter.java under adapter package. This adapter provides fragment views to tabs which we are going to create them later in this tutorial.

TabsPagerAdapter.java
package info.androidhive.tabsswipe.adapter;
import info.androidhive.tabsswipe.GamesFragment;
import info.androidhive.tabsswipe.MoviesFragment;
import info.androidhive.tabsswipe.TopRatedFragment;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
public class TabsPagerAdapter extends FragmentPagerAdapter {
    public TabsPagerAdapter(FragmentManager fm) {
        super(fm);
    }
    @Override
    public Fragment getItem(int index) {
        switch (index) {
        case 0:
            // Top Rated fragment activity
            return new TopRatedFragment();
        case 1:
            // Games fragment activity
            return new GamesFragment();
        case 2:
            // Movies fragment activity
            return new MoviesFragment();
        }
        return null;
    }
    @Override
    public int getCount() {
        // get item count - equal to number of tabs
        return 3;
    }
}

Adding Tabs to Action Bar

6. In order to display tabs we don’t have to use any other UI element like TabHost. Action bar has the inbuilt capability of adding tabs. All we have to do is enable it usingsetNavigationMode(ActionBar.NAVIGATION_MODE_TABS) method. Open your MainActivity.javado the following.

Here I am adding three tabs Top Rated, Games, Movies to action bar. So I just stored all the tab names in a String array and added them to action bar using a for loop.

MainActivity.java
public class MainActivity extends FragmentActivity implements
        ActionBar.TabListener {
    private ViewPager viewPager;
    private TabsPagerAdapter mAdapter;
    private ActionBar actionBar;
    // Tab titles
    private String[] tabs = { "Top Rated", "Games", "Movies" };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Initilization
        viewPager = (ViewPager) findViewById(R.id.pager);
        actionBar = getActionBar();
        mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(mAdapter);
        actionBar.setHomeButtonEnabled(false);
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);       
        // Adding Tabs
        for (String tab_name : tabs) {
            actionBar.addTab(actionBar.newTab().setText(tab_name)
                    .setTabListener(this));
        }

If you run the project, you can see the tabs displaying under action bar.

android action bar adding tabs

Adding Views for Tabs

We already returned respected fragments for tabs in the adapter class. To make it simple I am creating very simple layout for each tab and leaving it to you to build your own UI depending on your requirement. For now I just displayed a label in the view with some background color.

» First Tab View

7. The first tab I added is Top Rated. Create a new layout file under src ⇒ res folder namedfragment_top_rated.xml and paste the following code.

fragment_top_rated.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"
    android:background="#fa6a6a" >
    
    <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Design Top Rated Screen"
        android:textSize="20dp"
        android:layout_centerInParent="true"/>
    
</RelativeLayout>

8. Also create respected Fragment activity class for this view. Create a new class namedTopRatedFragment.java under your main package.

TopRatedFragment.java
package info.androidhive.tabsswipe;
import info.androidhive.tabsswipe.R;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class TopRatedFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_top_rated, container, false);
        
        return rootView;
    }
}

» Second Tab View

The second tab in the list is Games. Just like above create a layout file and activity file for this tab.

9. Create a new layout file under src ⇒ res folder named fragment_games.xml

fragment_games.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"
    android:background="#ff8400" >
    
    <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Design Games Screen"
        android:textSize="20dp"
        android:layout_centerInParent="true"/>
    
</RelativeLayout>

10. Create a new class named GamesFragment.java with following code.

package info.androidhive.tabsswipe;
import info.androidhive.tabsswipe.R;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class GamesFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_games, container, false);
        
        return rootView;
    }
}

» Third Tab View

This third tab is Movies. This one need a layout file and activity class.

11. Create a layout file called fragment_movies.xml

fragment_movies.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"
    android:background="#17df0d">
    
    <TextView android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="Design Movies Screen"
        android:textSize="20dp"
        android:layout_centerInParent="true"/>
    
</RelativeLayout>

12. Also create activity class for this view named MoviesFragment.java

MoviesFragment.java
package info.androidhive.tabsswipe;
import info.androidhive.tabsswipe.R;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class MoviesFragment extends Fragment {
    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View rootView = inflater.inflate(R.layout.fragment_movies, container, false);
        
        return rootView;
    }
}

Run the project and check whether the views for tabs are added or not.

Android Tab Layout with Swipeable Views

And this is how it looks in landscape mode

Android-Tab-Layout-with-Swipeable-Views-landscape

Tab Change Listener

If you run the project you can see the swiping views working, but if you select a tab, view won’t change automatically. This is because ViewPager didn’t know about the tab change event. We have to manually change the view using Tab change listener.

13. In your MainActivity.java class add following code.

@Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }
    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        // on tab selected
        // show respected fragment view
        viewPager.setCurrentItem(tab.getPosition());
    }
    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
    }

View Change Listener

14. As well if you swipe the view, you can’t see respected tab selected. Here also using ViewPagersetOnPageChangeListener() we have to select the respected tab manually.

/**
 * on swiping the viewpager make respective tab selected
 * */
viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
    @Override
    public void onPageSelected(int position) {
        // on changing the page
        // make respected tab selected
        actionBar.setSelectedNavigationItem(position);
    }
    @Override
    public void onPageScrolled(int arg0, float arg1, int arg2) {
    }
    @Override
    public void onPageScrollStateChanged(int arg0) {
    }
});

After adding these two listeners, if you run the project you can see everything working good.

Complete Code

Below is the complete code for MainActivity.java class

MainActivity.java
package info.androidhive.tabsswipe;
import info.androidhive.tabsswipe.adapter.TabsPagerAdapter;
import info.androidhive.tabsswipe.R;
import android.app.ActionBar;
import android.app.ActionBar.Tab;
import android.app.FragmentTransaction;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.view.ViewPager;
import android.view.Menu;
public class MainActivity extends FragmentActivity implements
        ActionBar.TabListener {
    private ViewPager viewPager;
    private TabsPagerAdapter mAdapter;
    private ActionBar actionBar;
    // Tab titles
    private String[] tabs = { "Top Rated", "Games", "Movies" };
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        // Initilization
        viewPager = (ViewPager) findViewById(R.id.pager);
        actionBar = getActionBar();
        mAdapter = new TabsPagerAdapter(getSupportFragmentManager());
        viewPager.setAdapter(mAdapter);
        actionBar.setHomeButtonEnabled(false);
        actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);       
        // Adding Tabs
        for (String tab_name : tabs) {
            actionBar.addTab(actionBar.newTab().setText(tab_name)
                    .setTabListener(this));
        }
        /**
         * on swiping the viewpager make respective tab selected
         * */
        viewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
            @Override
            public void onPageSelected(int position) {
                // on changing the page
                // make respected tab selected
                actionBar.setSelectedNavigationItem(position);
            }
            @Override
            public void onPageScrolled(int arg0, float arg1, int arg2) {
            }
            @Override
            public void onPageScrollStateChanged(int arg0) {
            }
        });
    }
    @Override
    public void onTabReselected(Tab tab, FragmentTransaction ft) {
    }
    @Override
    public void onTabSelected(Tab tab, FragmentTransaction ft) {
        // on tab selected
        // show respected fragment view
        viewPager.setCurrentItem(tab.getPosition());
    }
    @Override
    public void onTabUnselected(Tab tab, FragmentTransaction ft) {
    }
}

Don’t Store Data in the Application Object

There is always some information that is needed in many places in your app. It can be a session token, the result of an expensive computation, etc. It is often tempting to avoid the overhead of passing objects between activities or keeping those in persistent storage.

A pattern that is sometimes suggested is to dump your data in the Application object with the idea that it will be available across all activities. This solution is simple, elegant and… totally wrong.

If you assume that your data will stay there, your application will eventually crash with a NullPointerException.

A Simple Test Case

The Code

The Application object:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// access modifiers omitted for brevity
class MyApplication extends Application {

    String name;

    String getName() {
        return name;
    }

    void setName(String name) {
        this.name = name;
    }
}

The first activity, where we store the name of the user in the application object:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// access modifiers omitted for brevity
class WhatIsYourNameActivity extends Activity {

    void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.writing);

        // Just assume that in the real app we would really ask it!
        MyApplication app = (MyApplication) getApplication();
        app.setName("Developer Phil");
        startActivity(new Intent(this, GreetLoudlyActivity.class));

    }

}

The second activity, where we shout the name of the user:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
// access modifiers omitted for brevity
class GreetLoudlyActivity extends Activity {

    TextView textview;

    void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.reading);
        textview = (TextView) findViewById(R.id.message);
    }

    void onResume() {
        super.onResume();

        MyApplication app = (MyApplication) getApplication();
        textview.setText("HELLO " + app.getName().toUpperCase());
    }
}

The Scenario

  1. The user starts the app.
  2. In WhatIsYourNameActivity, you ask for the name of the user and you store it in MyApplication.
  3. In GreetLoudlyActivity, you fetch the user’s name from the MyApplication object and display it.
  4. The user leaves the app using the home button.
  5. A few hours later, Android silently kills the app to reclaim some memory.

    So far, so good!

    But here comes the crashy part…

  6. The user reopens the app.
  7. Android creates a new MyApplication instance and restores GreetLoudlyActivity.
  8. GreetLoudlyActivity fetches the user’s name, which is now null, and crashes with a NullPointerException.

Why Does it Crash?

In this sample, we crash because the Application object is brand new, so the name variable is null, leading to a NullPointerException when we call String#toUpperCase() on it.

Which brings us to the core of the problem: The application object will not stay in memory forever, it will get killed. Contrary to popular belief, the app won’t be restarted from scratch. Android will create a new Application object and start the activity where the user was before to give the illusion that the application was never killed in the first place.

Which means that if you expect some data to be in your application object just because your user is not supposed to be able to open activity B before activity A, you are in for a crashy surprise.

What Are The Alternatives

There is no magic solution here, you can do one of the following:

  • Explicitly pass the data to the Activity through the intent.
  • Use one of the many ways to persist the data to disk.
  • Always do a null-check and handle it manually.

How To Simulate The Application Being Killed

EDIT: As pointed out by Daniel Lew, an easier way to kill your app is to simply use the “Stop Process” feature in DDMS. This will work on any phone as long as your application is debuggable.**

To test this, you must use an emulator or a rooted phone.

  1. Exit your app using the home button.
  2. In a terminal:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
# find the process id
adb shell ps
# then find the line with the package name of your app

# Mac/Unix: save some time by using grep:
adb shell ps | grep your.app.package

# The result should look like:
# USER      PID   PPID  VSIZE  RSS     WCHAN    PC         NAME
# u0_a198   21997 160   827940 22064 ffffffff 00000000 S your.app.package

# Kill the app by PID
adb shell kill -9 21997

# the app is now killed
  1. Now return to the app using the task switcher.
    You are now on a new application instance.

The Bottom Line

Storing data in the application object is error prone and can crash your app. Prefer storing your global data on disk if it is really needed later or explicitly pass it to your activity in the intent’s extras.

Also remember that while this is true for the application object, it is also true for any singleton or public static field that you might have in your app.

Creating Global Session Variables for all Activities Android Example

Here is an Android example of how to share variables through out your activities and your project. There is many ways including preferences, extending from a base activity or using  a database to share variables. I like this one because it can be accessed through the whole project and it is easy to implement. The down side is that the variables are only available through a session. For example if the user restarts the application then all the variable will also be cleared. So they can be viewed as session variables.

GlobalState Class:

-Note this extends Application

import android.app.Application;

public class GlobalState extends Application{
	
	 public File sd = null;
	 boolean refresh = true;
	 pictureData [] picturemarkers = null;
	 
	 public File getFilePath(){
		 return sd;
	 }
	 
	 public void setFilePath(File sd){
          this.sd = sd;		 
	 }

	 public pictureData[] getPictureData(){
		 return picturemarkers;
	 }
	  
	 public void setPictureData(pictureData [] picData){
		 picturemarkers = picData;
	 }
	 
	 public void setRefresh(boolean refresh){
		 this.refresh = refresh;
	 }
	 
	 public boolean getRefresh(){
		 return refresh;
	 }
			 
}

ExampleActivity Class:

public class ExampleActivity extends Activity {

        GlobalState gs;

	public void onCreate(Bundle savedInstanceState) {
              gs = (GlobalState) getApplication();
        }

        public void examples(){  
            File temp = gs.getFilePath();
            if(gs.refresh()){
                gs.setRefresh(false);
             }
        }
} 


Manifest

-I do not fully understand why but it is needed for it to work.  Set the ‘name’ of your application to the path where the file is located.

<application
        android:name="your.package.name.GlobalState"

How to use static variables in activities

As previously described in Leaving an Android application, when you exit an app by pressing back button its resources are not completely destroyed immediately.

I would like to explain a concrete mistake I met multiple times, in connection with this behavior, which is easy to commit, if you forget this.

When you use static member variables in an activity you, should think about, how static variables are handled when instantiating, and how android instantiates activities.

If a static variable has an initial value it is only applied when creating the first instance. When the activity is started, when starting from app browser or by an Intent programmatically by you it becomes instantiated. When the app is first started, the first instance will be created, so obviously the static variables will get the initial values.

During the run the static variables may be killed if the activity they belong to are not visible, in this case they will have their default values when returning to the activity. If you want to restore their values in this case from some kind of stored source read this article: Maintaining global Application state

And the problem I recently met, that the opposite can also happen, the static variables may keep their values when you did not expect this. It is very easy to confirm this, just exit the application by pressing back and quickly restart it from the app browser. In such a sort time the android system will not kill the entire activity, and static variables will retain values.

So you should think over why the variable become static, to decide if it is the desired behavior for you or not. For example you can reinitialize the static variables in onCreate or onResume methods if that fits you.

How to get QuickTime Pro – FREE!!! –

STEP 1: Download QuickTime from …

http://www.apple.com/quicktime/d.

STEP 2: Open QuickTime Player.

STEP 3: GOTO Edit – Preferences – Register.

STEP 4: Enter username and password.

Pc Serials:
Registered To: CORE MAFIA
Registration Code: DFXY-5TJN-HU3N-DQXF-WDQ3

Register Name: Dawn M Fredette
Register Code: 4UJ2-5NLF-HFFA-9JW3-X2KV
===========================================
Mac Users:
Registered To: Pablo/nop
Registration code:
SMH2-6F4K-8SK9-ST5W-QKU3

===========================================
Others:
Register Name = In Memory of Abysm
Register Code = YBN7Y-9K77Y-F67B5-PNCCQ

Register Name: http://www.macmofo.com
Register Code: 3DX76-9KJ4F-CBQL9-BJBKH

Registered To: Apple Sales
Registration Code: TTVN-B66S-28V6-X84D-EBWN
===========================================

Disclaimer – This Info is for educational use it is not intentioned nor was it made in order to cause any infringements with the original creator of the software

THAT’S IT!
Hope This Helps.
datroubleshooter.

Quantity Strings (Plurals)

Quantity Strings (Plurals)


Different languages have different rules for grammatical agreement with quantity. In English, for example, the quantity 1 is a special case. We write “1 book”, but for any other quantity we’d write “n books”. This distinction between singular and plural is very common, but other languages make finer distinctions. The full set supported by Android is zero, one, two, few, many, and other.

The rules for deciding which case to use for a given language and quantity can be very complex, so Android provides you with methods such as getQuantityString() to select the appropriate resource for you.

Although historically called “quantity strings” (and still called that in API), quantity strings should only be used for plurals. It would be a mistake to use quantity strings to implement something like Gmail’s “Inbox” versus “Inbox (12)” when there are unread messages, for example. It might seem convenient to use quantity strings instead of an if statement, but it’s important to note that some languages (such as Chinese) don’t make these grammatical distinctions at all, so you’ll always get the other string.

The selection of which string to use is made solely based on grammatical necessity. In English, a string for zero will be ignored even if the quantity is 0, because 0 isn’t grammatically different from 2, or any other number except 1 (“zero books”, “one book”, “two books”, and so on). Conversely, in Korean only the other string will ever be used.

Don’t be misled either by the fact that, say, two sounds like it could only apply to the quantity 2: a language may require that 2, 12, 102 (and so on) are all treated like one another but differently to other quantities. Rely on your translator to know what distinctions their language actually insists upon.

It’s often possible to avoid quantity strings by using quantity-neutral formulations such as “Books: 1”. This will make your life and your translators’ lives easier, if it’s a style that’s in keeping with your application.

Note: A plurals collection is a simple resource that is referenced using the value provided in the name attribute (not the name of the XML file). As such, you can combine plurals resources with other simple resources in the one XML file, under one <resources> element.

file location:
res/values/filename.xml
The filename is arbitrary. The <plurals> element’s name will be used as the resource ID.
resource reference:
In Java: R.plurals.plural_name
syntax:
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <plurals
        name="plural_name">
        <item
            quantity=["zero" | "one" | "two" | "few" | "many" | "other"]
            >text_string</item>
    </plurals>
</resources>
elements:
<resources>
Required. This must be the root node.No attributes.

<plurals>
A collection of strings, of which, one string is provided depending on the amount of something. Contains one or more <item> elements.

attributes:

name
String. A name for the pair of strings. This name will be used as the resource ID.
<item>
A plural or singular string. The value can be a reference to another string resource. Must be a child of a <plurals> element. Beware that you must escape apostrophes and quotation marks. See Formatting and Styling, below, for information about to properly style and format your strings.

attributes:

quantity
Keyword. A value indicating when this string should be used. Valid values, with non-exhaustive examples in parentheses:

Value Description
zero When the language requires special treatment of the number 0 (as in Arabic).
one When the language requires special treatment of numbers like one (as with the number 1 in English and most other languages; in Russian, any number ending in 1 but not ending in 11 is in this class).
two When the language requires special treatment of numbers like two (as with 2 in Welsh, or 102 in Slovenian).
few When the language requires special treatment of “small” numbers (as with 2, 3, and 4 in Czech; or numbers ending 2, 3, or 4 but not 12, 13, or 14 in Polish).
many When the language requires special treatment of “large” numbers (as with numbers ending 11-99 in Maltese).
other When the language does not require special treatment of the given quantity (as with all numbers in Chinese, or 42 in English).
example:
XML file saved at res/values/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <plurals name="numberOfSongsAvailable">
        <!--
             As a developer, you should always supply "one" and "other"
             strings. Your translators will know which strings are actually
             needed for their language. Always include %d in "one" because
             translators will need to use %d for languages where "one"
             doesn't mean 1 (as explained above).
          -->
        <item quantity="one">%d song found.</item>
        <item quantity="other">%d songs found.</item>
    </plurals>
</resources>

XML file saved at res/values-pl/strings.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <plurals name="numberOfSongsAvailable">
        <item quantity="one">Znaleziono %d piosenkę.</item>
        <item quantity="few">Znaleziono %d piosenki.</item>
        <item quantity="other">Znaleziono %d piosenek.</item>
    </plurals>
</resources>

Java code:

int count = getNumberOfsongsAvailable();
Resources res = getResources();
String songsFound = res.getQuantityString(R.plurals.numberOfSongsAvailable, count, count);

When using the getQuantityString() method, you need to pass the count twice if your string includes string formatting with a number. For example, for the string %d songs found, the first count parameter selects the appropriate plural string and the second count parameter is inserted into the %d placeholder. If your plural strings do not include string formatting, you don’t need to pass the third parameter to getQuantityString.

Formatting and Styling


Here are a few important things you should know about how to properly format and style your string resources.

Escaping apostrophes and quotes

If you have an apostrophe or a quote in your string, you must either escape it or enclose the whole string in the other type of enclosing quotes. For example, here are some stings that do and don’t work:

<string name="good_example">"This'll work"</string>
<string name="good_example_2">This\'ll also work</string>
<string name="bad_example">This doesn't work</string>
<string name="bad_example_2">XML encodings don&apos;t work</string>

Formatting strings

If you need to format your strings using String.format(String, Object...), then you can do so by putting your format arguments in the string resource. For example, with the following resource:

<string name="welcome_messages">Hello, %1$s! You have %2$d new messages.</string>

In this example, the format string has two arguments: %1$s is a string and %2$d is a decimal number. You can format the string with arguments from your application like this:

Resources res = getResources();
String text = String.format(res.getString(R.string.welcome_messages), username, mailCount);

Fragment MyFragment not attached to Activity

If you call method getString() or getResources() or getActivity(). You get this error if your fragment hasnt been attach to the activity yet.

E.g:

private horizontalTabTitles = new String[]{getString(R.string.sort_by_time),
getString(R.string.sort_by_table), getString(R.string.sort_by_order)};

when you declare and initialize this, the fragment may not has been attached to the activity yet. hence cause the error.

Git 常用命令详解(二)

Git 是一个很强大的分布式版本管理工具,它不但适用于管理大型开源软件的源代码(如:linux kernel),管理私人的文档和源代码也有很多优势(如:wsi-lgame-pro

Git 的更多介绍,请参考我的上一篇博客:Git 版本管理工具

一、 Git 命令初识

在正式介绍Git命令之前,先介绍一下Git 的基本命令和操作,对Git命令有一个总体的认识

示例:从Git 版本库的初始化,通常有两种方式:

1)git clone:这是一种较为简单的初始化方式,当你已经有一个远程的Git版本库,只需要在本地克隆一份

例如:git  clone  git://github.com/someone/some_project.git   some_project

上面的命令就是将’git://github.com/someone/some_project.git’这个URL地址的远程版本库,完全克隆到本地some_project目录下

2)git init 和 git remote:这种方式稍微复杂一些,当你本地创建了一个工作目录,你可以进入这个目录,使用’git init’命令进行初始化;Git以后就会对该目录下的文件进行版本控制,这时候如果你需要将它放到远程服务器上,可以在远程服务器上创建一个目录,并把 可访问的URL记录下来,此时你就可以利用’git remote add’命令来增加一个远程服务器端,

例如:git  remote  add  origin  git://github.com/someone/another_project.git

上面的命令就会增加URL地址为’git: //github.com/someone/another_project.git’,名称为origin的远程服务器,以后提交代码的时候只需要使用 origin别名即可
二、 Git 常用命令

1) 远程仓库相关命令

检出仓库:        $ git clone git://github.com/jquery/jquery.git

查看远程仓库:$ git remote -v

添加远程仓库:$ git remote add [name] [url]

删除远程仓库:$ git remote rm [name]

修改远程仓库:$ git remote set-url –push [name] [newUrl]

拉取远程仓库:$ git pull [remoteName] [localBranchName]

推送远程仓库:$ git push [remoteName] [localBranchName]
*如果想把本地的某个分支test提交到远程仓库,并作为远程仓库的master分支,或者作为另外一个名叫test的分支,如下:

$git push origin test:master         // 提交本地test分支作为远程的master分支

$git push origin test:test              // 提交本地test分支作为远程的test分支

2)分支(branch)操作相关命令

查看本地分支:$ git branch

查看远程分支:$ git branch -r

创建本地分支:$ git branch [name] —-注意新分支创建后不会自动切换为当前分支

切换分支:$ git checkout [name]

创建新分支并立即切换到新分支:$ git checkout -b [name]

删除分支:$ git branch -d [name] —- -d选项只能删除已经参与了合并的分支,对于未有合并的分支是无法删除的。如果想强制删除一个分支,可以使用-D选项

合并分支:$ git merge [name] —-将名称为[name]的分支与当前分支合并

创建远程分支(本地分支push到远程):$ git push origin [name]

删除远程分支:$ git push origin :heads/[name] 或 $ gitpush origin :[name]
*创建空的分支:(执行命令之前记得先提交你当前分支的修改,否则会被强制删干净没得后悔)

$git symbolic-ref HEAD refs/heads/[name]

$rm .git/index

$git clean -fdx

3)版本(tag)操作相关命令

查看版本:$ git tag

创建版本:$ git tag [name]

删除版本:$ git tag -d [name]

查看远程版本:$ git tag -r

创建远程版本(本地版本push到远程):$ git push origin [name]

删除远程版本:$ git push origin :refs/tags/[name]

合并远程仓库的tag到本地:$ git pull origin –tags

上传本地tag到远程仓库:$ git push origin –tags

创建带注释的tag:$ git tag -a [name] -m ‘yourMessage’

4) 子模块(submodule)相关操作命令

添加子模块:$ git submodule add [url] [path]

如:$git submodule add git://github.com/soberh/ui-libs.git src/main/webapp/ui-libs

初始化子模块:$ git submodule init  —-只在首次检出仓库时运行一次就行

更新子模块:$ git submodule update —-每次更新或切换分支后都需要运行一下

删除子模块:(分4步走哦)

1) $ git rm –cached [path]

2) 编辑“.gitmodules”文件,将子模块的相关配置节点删除掉

3) 编辑“ .git/config”文件,将子模块的相关配置节点删除掉

4) 手动删除子模块残留的目录

5)忽略一些文件、文件夹不提交

在仓库根目录下创建名称为“.gitignore”的文件,写入不需要的文件夹名或文件,每个元素占一行即可,如

target

bin

*.db

三、 Git 命令详解

现在我们有了本地和远程的版本库,让我们来试着用用Git的基本命令:

git pull:从其他的版本库(既可以是远程的也可以是本地的)将代码更新到本地,例如:’git pull origin master’就是将origin这个版本库的代码更新到本地的master主枝,该功能类似于SVN的update

git add:是 将当前更改或者新增的文件加入到Git的索引中,加入到Git的索引中就表示记入了版本历史中,这也是提交之前所需要执行的一步,例如’git add app/model/user.rb’就会增加app/model/user.rb文件到Git的索引中,该功能类似于SVN的add

git rm:从当前的工作空间中和索引中删除文件,例如’git rm app/model/user.rb’,该功能类似于SVN的rm、del

git commit:提交当前工作空间的修改内容,类似于SVN的commit命令,例如’git commit -m story #3, add user model’,提交的时候必须用-m来输入一条提交信息,该功能类似于SVN的commit

git push:将本地commit的代码更新到远程版本库中,例如’git push origin’就会将本地的代码更新到名为orgin的远程版本库中

git log:查看历史日志,该功能类似于SVN的log

git revert:还原一个版本的修改,必须提供一个具体的Git版本号,例如’git revert bbaf6fb5060b4875b18ff9ff637ce118256d6f20’,Git的版本号都是生成的一个哈希值

上面的命令几乎都是每个版本控制工具所公有的,下面就开始尝试一下Git独有的一些命令:

git branch:对分支的增、删、查等操作,例如’git branch new_branch’会从当前的工作版本创建一个叫做new_branch的新分支,’git branch -D new_branch’就会强制删除叫做new_branch的分支,’git branch’就会列出本地所有的分支

git checkout:Git的checkout有两个作用,其一是在不同的branch之间进行切换,例如’git checkout new_branch’就会切换到new_branch的分支上去;另一个功能是还原代码的作用,例如’git checkout app/model/user.rb’就会将user.rb文件从上一个已提交的版本中更新回来,未提交的内容全部会回滚

git rebase:用下面两幅图解释会比较清楚一些,rebase命令执行后,实际上是将分支点从C移到了G,这样分支也就具有了从C到G的功能

git reset:将当前的工作目录完全回滚到指定的版本号,假设如下图,我们有A-G五次提交的版本,其中C的版本号是 bbaf6fb5060b4875b18ff9ff637ce118256d6f20,我们执行了’git reset bbaf6fb5060b4875b18ff9ff637ce118256d6f20’那么结果就只剩下了A-C三个提交的版本

git stash:将当前未提交的工作存入Git工作栈中,时机成熟的时候再应用回来,这里暂时提一下这个命令的用法,后面在技巧篇会重点讲解

git config:利用这个命令可以新增、更改Git的各种设置,例如’git config branch.master.remote origin’就将master的远程版本库设置为别名叫做origin版本库,后面在技巧篇会利用这个命令个性化设置你的Git,为你打造独一无二的 Git

git tag:可以将某个具体的版本打上一个标签,这样你就不需要记忆复杂的版本号哈希值了,例如你可以使用’git tag revert_version bbaf6fb5060b4875b18ff9ff637ce118256d6f20’来标记这个被你还原的版本,那么以后你想查看该版本时,就可以使用 revert_version标签名,而不是哈希值了

Git 之所以能够提供方便的本地分支等特性,是与它的文件存储机制有关的。Git存储版本控制信息时使用它自己定义的一套文件系统存储机制,在代码根目录下有一个.git文件夹,会有如下这样的目录结构:


有 几个比较重要的文件和目录需要解释一下:HEAD文件存放根节点的信息,其实目录结构就表示一个树型结构,Git采用这种树形结构来存储版本信息,那么 HEAD就表示根;refs目录存储了你在当前版本控制目录下的各种不同引用(引用指的是你本地和远程所用到的各个树分支的信息),它有heads、 remotes、stash、tags四个子目录,分别存储对不同的根、远程版本库、Git栈和标签的四种引用,你可以通过命令’git show-ref’更清晰地查看引用信息;logs目录根据不同的引用存储了日志信息。因此,Git只需要代码根目录下的这一个.git目录就可以记录完 整的版本控制信息,而不是像SVN那样根目录和子目录下都有.svn目录。那么下面就来看一下Git与SVN的区别吧

四、 Git 与SVN 比较

SVN(Subversion)是当前使用最多的版本控制工具。与它相比较,Git 最大的优势在于两点:易于本地增加分支和分布式的特性。

下面两幅图可以形象的展示Git与SVN的不同之处:

————

1)本地增加分支

图中Git本地和服务器端结构都很灵活,所有版本都存储在一个目录中,你只需要进行分支的切换即可达到在某个分支工作的效果

而SVN则完全不同,如果你需要在本地试验一些自己的代码,只能本地维护多个不同的拷贝,每个拷贝对应一个SVN服务器地址

举一个实际的例子:

使用SVN作为版本控制工具,当正在试图增强一个模块,工作做到一半,由于会改变原模块的行为导致代码服务器上许多测试的失败,所以并没有提交代码。

这时候假如现在有一个很紧急的Bug需要处理, 必须在两个小时内完成。我只好将本地的所有修改diff,并输出成为一个patch文件,然后回滚有关当前任务的所有代码,再开始修改Bug的任务,等到修改好后,在将patch应用回来。前前后后要完成多个繁琐的步骤,这还不计中间代码发生冲突所要进行的工作量。

可是如果使用Git, 我们只需要开一个分支或者转回到主分支上,就可以随时开始Bug修改的任务,完成之后,只要切换到原来的分支就可以优雅的继续以前的任务。只要你愿意,每一个新的任务都可以开一个分支,完成后,再将它合并到主分支上,轻松而优雅。

2)分布式提交

Git 可以本地提交代码,所以在上面的图中,Git有利于将一个大任务分解,进行本地的多次提交

而SVN只能在本地进行大量的一次性更改,导致将来合并到主干上造成巨大的风险

3)日志查看

Git 的代码日志是在本地的,可以随时查看

SVN的日志在服务器上的,每次查看日志需要先从服务器上下载下来

例如:代码服务器在美国,当每次查看几年前所做的工作时,日志下载可能需要十分钟,这不能不说是一个痛苦。但是如果迁移到Git上,利用Git日志在本地的特性,查看某个具体任务的所有代码历史,每次只需要几秒钟,大大方便了工作,提高了效率。

当然分布式并不是说用了Git就不需要一个代码中心服务器,如果你工作在一个团队里,还是需要一个服务器来保存所有的代码的。

五、 总结

上面简单介绍了Git 的基本概念、一些常用命令和原理,大家也可以尝试动手,在Google Code 或 GitHub 上创建一个自己的开源项目

Android localization at runtime

AndroidLocalize .java

  1. Declare spinner control, locale and button objects
  2. Find the spinner control from main.xml and add OnItemSelectedListener class to it
  3. Based on the list item selected, call setLocale method with the Locale value: English – en, Tamil – ta and Hindi – hi.
  4. Change the configuration of device (add locale) for your application
  5. Make sure that you refresh the current activity to reflect the changes made in configuration
package com.prgguru.android;
import java.util.Locale;
import android.os.Bundle;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.AdapterView;
import android.widget.Button;
import android.widget.Spinner;
import android.widget.Toast;
import android.widget.AdapterView.OnItemSelectedListener;
public class AndroidLocalize extends Activity {
    Spinner spinnerctrl;
    Button btn;
    Locale myLocale;
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        spinnerctrl = (Spinner) findViewById(R.id.spinner1);
        spinnerctrl.setOnItemSelectedListener(new OnItemSelectedListener() {
            public void onItemSelected(AdapterView<?> parent, View view,
                    int pos, long id) {
                if (pos == 1) {
                    Toast.makeText(parent.getContext(),
                            "You have selected Tamil", Toast.LENGTH_SHORT)
                            .show();
                    setLocale("ta");
                } else if (pos == 2) {
                    Toast.makeText(parent.getContext(),
                            "You have selected Hindi", Toast.LENGTH_SHORT)
                            .show();
                    setLocale("hi");
                } else if (pos == 3) {
                    Toast.makeText(parent.getContext(),
                            "You have selected English", Toast.LENGTH_SHORT)
                            .show();
                    setLocale("en");
                }
            }
            public void onNothingSelected(AdapterView<?> arg0) {
                // TODO Auto-generated method stub
            }
        });
    }
    public void setLocale(String lang) {
        myLocale = new Locale(lang);
        Resources res = getResources();
        DisplayMetrics dm = res.getDisplayMetrics();
        Configuration conf = res.getConfiguration();
        conf.locale = myLocale;
        res.updateConfiguration(conf, dm);
        Intent refresh = new Intent(this, AndroidLocalize.class);
        startActivity(refresh);
    }
}