[Edited]:
I have already developed one application which works well on different screen size phones and tablets, I followed as per android guidelines to create the different sets of UI layouts to cater with different sizes of screens(phones and tablets) and all layouts will be in portrait mode. Everything works fine till then but now I got one new requirement where the layouts of tablets will be changed i.e. it will be having some extra information than phone layouts and all new layouts will be in landscape mode.
Now my doubts are:
In old implementation, all layouts name are same in different layout folders i.e. layout,layout-small,layout-large etc... so , as per new requirement, all layouts of large and extra-large should be in landscape mode but if i keep the same name of layout then how do I handle the orientation? At present, orientation attribute mentioned in manifest file for each activity.
If I keep the different name of these layouts then I have to handle at run time i.e. while loading the activity,based on checking on phone screen size and then load the respective layout, so same activity can load different layout based on checking screen size. Will it be a optimal approach to do so?
OR,Do I have to altogether introduce new activities for all new layouts to handle this scenario?
Please suggest me best approach to handle this scenario.
A good point to start on this would be official Android documents (below links).
http://developer.android.com/guide/practices/screens_support.html
http://developer.android.com/training/basics/supporting-devices/screens.html
In a nutshell I can tell you would have to create multiple layouts to address your requirement for supporting multiple screen sizes/resolution. Within your application source directory you will have to create a different layout xml (with same name) under different resource-layout-directories as shown below:
res/
layout/ # default (portrait)
main.xml
layout-land/ # landscape
main.xml
layout-large/ # large screen devices (portrait)
main.xml
layout-large-land/ # large screen devices (landscape)
main.xml
I would suggest you to name all similar files with the same name.
Take full advantage of the qualifiers provided by Android like the size, or the set width qualifier. You may have a single pane layout for small screen phones and multi pane layout for tablets. It's a good advice to provide multi pane layout ( you may combine two existing layouts into one ) for landscape orientation.
For example :
res/layout/onepane.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:id="#+id/frag1"
android:layout_height="fill_parent"
android:name="com.example.android.dummyApp"
android:layout_width="match_parent"
android:background="#drawable/bg"/>
</LinearLayout>
res/layout/twopanes.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<fragment android:id="#+id/fragone"
android:layout_height="fill_parent"
android:name="com.example.android.dummyApp"
android:layout_width="400dp"
android:layout_marginRight="10dp"
android:background="#drawable/bg"/>
<fragment android:id="#+id/fragtwo"
android:layout_height="fill_parent"
android:name="com.example.android.dummyApp"
android:layout_width="fill_parent" />
</LinearLayout>
Now that all possible layouts are defined, it's just a matter of mapping the correct layout to each configuration using the configuration qualifiers. You can now do it using the layout alias technique:
res/values/layouts.xml:
<resources>
<item name="main_layout" type="layout">#layout/onepane</item>
<bool name="has_two_panes">false</bool>
</resources>
res/values-sw600dp-land/layouts.xml:
<resources>
<item name="main_layout" type="layout">#layout/twopanes</item>
<bool name="has_two_panes">true</bool>
</resources>
Note : The bool values in the two xml files in values folder.
The Smallest-width qualifier allows you to target screens that have a certain minimum width given in dp. Instead of the large size qualifier, use sw600dp to indicate the two-pane layout is for screens on which the smallest-width is 600 dp.
Also, another solution:
If you are planning to make different activities for different screen sizes, you can build multiple apk for the same project. There should be a separate Android project for each APK you’re going to release.
Please look at this link for more information on Multiple APKs.
http://developer.android.com/training/multiple-apks/screensize.html
Personally, I'd make two different Fragments, with two different Fragment Layouts, and I'd load the Fragment for the right screen size based on the layout folder identifier, like so: http://developer.android.com/training/basics/supporting-devices/screens.html
Typical Fragment:
public class MyFragment extends Fragment implements View.OnClickListener
{
private Button btnSave;
private Button btnCancel;
public MyFragment()
{
super();
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View rootView = inflater.inflate(R.layout.fragment_myfragment_small, container, false);
return rootView;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
super.onViewCreated(view, savedInstanceState);
btnSave = (Button) getActivity().findViewById(R.id.myfragment_small_save);
btnCancel = (Button) getActivity().findViewById(R.id.myfragment_small_cancel);
btnSave.setOnClickListener(this);
btnCancel.setOnClickListener(this);
}
#Override
public void onClick(View v)
{
if (btnSave == v)
{
save();
}
else if (btnCancel == v)
{
cancel();
}
}
}
And do the same thing for the larger one. Bonus points if you reuse the save() and cancel() function by extending from MyFragment, or by ripping out the logic of them into another class.
Static fragment linking:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<fragment
android:id="#+id/fragment_myfragment_small"
android:name="com.example.stuff.longer.MyFragment"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
And the layout for the fragment in fragment_myfragment_small.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" >
<Button android:id="#+id/myfragment_small_cancel"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="#string/cancel"
/>
<Button android:id="#+id/myfragment_small_save"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_above="#+id/myfragment_small_cancel"
android:text="#string/save"
/>
</RelativeLayout>
And make a second one for the second, larger layout. Place the layout in a different \res\layout folder, such as \res\layout-large.
Related
My app for phones has a pager with the main view and the premium features view when swiping right, in landscape mode it has a slightly modified version to fit the landscape orientation.
Now i have to make the tablet UI, and I'd like to show both views on one screen, I've already managed to show them, but the tablet in landscape mode is picking up the fragments from layout-land instead of the portrait layout, and I need it to pick the portrait ones to show side by side.
How can i make the fragment pick the portrait version even if the tablet is in landscape without creating duplicate layouts?
My fragments look like this
public class PremiumFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
ViewGroup premiumView = (ViewGroup) inflater.inflate(
R.layout.premium_features, container, false);
return premiumView;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Log.d(TAG, "onView created ran");
Intent intent = new Intent("setup");
LocalBroadcastManager.getInstance(getBaseContext()).sendBroadcast(intent);
}
}
It's been a while but for anyone interested in this matter:
If I understand correctly, you need to have a portrait layout, a landscape layout (for mobile for example) and a different landscape layout for tablets which matches the portrait one. There's not that much duplicity involved. You can include an independent non-qualified layout in your qualified layouts.
Lets say you have these layouts:
layout-land/my_fragment.xml
layout-port/my_fragment.xml
layout-large-land/my_fragment.xml
layout/my_fragment_content.xml
layout-port/my_fragment.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="#layout/my_fragment_content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout >
layout-land/my_fragment.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
//something completely different
</FrameLayout >
and layout-large-land/my_fragment.xml (contents are the same as portrait one):
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="#layout/my_fragment_content"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</FrameLayout >
There is some minimum duplicity involved but the only important thing is that you have no reason to ever touch layout-port/my_fragment.xml or layout-large-land/my_fragment.xml because all vital information is contained in a single file layout/my_fragment_content.xml.
This topic is more thoroughly analysed in this article that the author probably read aleady:
Android Developers: Building a Flexible UI
I'm just starting to use fragments in my app and the idea is one column for screens of a certain width (or less) and two for wider screens. So I have two separate layout files and in the onCreate method of MainActivity I choose which one to show based on the screen width. Since I am interested in the screen width rather than the orientation I can't use the simpler option of 'layout-land'. Both layouts use the same fragments but they can't be hard coded into the layout files because some of them need to be add
added and removed at runtime - thus I use a fragment transaction in the onCreate method to (at the moment) just add the fragments.
The problem comes when the activity is destroyed and recreated. If I don't check for whether savedinstancestate is null, it adds the fragments again (which is to be expected) and everything is doubled up. But if I only do the create code when it's null - as you would if there was only one layout - then when I test screen width again and just use setContentView(one or the other layout) it recreates the one that was shown with no problem but the other is blank. Again that's to be expected because the second one hasn't been instantiated yet. So is it possible to determine from the savedinstancestate which layout was in use when the activity was destroyed? And if it is, is it possible (or safe) to use the information in that to create the other layout - or should I just run the create code again? In other words does the standard savedinstancestate persist all the data I need when more than one layout is in use or will I have to do it all myself?
You can still use resource buckets to contain your layouts. i.e:
use /layout-sw600dp/ that is layout smallest width of 600 dip
From the official documentation
http://developer.android.com/training/multiscreen/screensizes.html#TaskUseSWQuali
res/layout/main.xml, single-pane (default) layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment android:id="#+id/headlines"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="match_parent" />
</LinearLayout>
res/layout-sw600dp/main.xml, two-pane layout:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<fragment android:id="#+id/headlines"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.HeadlinesFragment"
android:layout_width="400dp"
android:layout_marginRight="10dp"/>
<fragment android:id="#+id/article"
android:layout_height="fill_parent"
android:name="com.example.android.newsreader.ArticleFragment"
android:layout_width="fill_parent" />
</LinearLayout>
I actually have a ListActivity fed by a custom ArrayAdapter. It builds rows made by single ImageViews. In portrait mode it looks nice. But in landscape mode it stretches the image to the borders (I set scaleType to fitXY).
I would like to have 2 ImageViews per row in landscape mode. Is GridView the right layout?
How would you do this?
Since I have a 480x800 screen, in portrait, the ImageView would be 480px wide, while in landscape each of the two would be 400px wide. This is not a problem, but it is important to me to respect the width/height ratio.
Why don't you use Gridview to do your task. If you want to use listview you have to check for the orientation, based on the orientations
1) In landscape mode the layout from the layout-land/ will be loaded which contains the 2 imageviews.
2) In portrait mode the layout from the layout-port/ will be loaded which contains the 1 imageview.
Yes, GridView is the best and easiest approach. You can use the same custom ArrayAdapter and OnClickListener you have now for the ListView, and only need to change
ListView myList = (ListView) findViewById(R.id.list);
to
GridView myGrid = (GridView) findViewById(R.id.gridview);
and the xml to something like:
<GridView
android:id="#+id/gridview"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:columnWidth="390px"
android:gravity="center"
android:numColumns="auto_fit"
android:stretchMode="columnWidth" />
Now, you should avoid using "px" as units and use "dp" instead, and that only if you are really sure your app is designed only for a 480x800 resolution.
gridView is not much of a layout . it's an adapterView , so its purpose is to be able to "show" tons of views and also be able to scroll between them .
maybe you would like to use gridLayout instead ? it also has a comparability library ...
anyway , if you wish to stick with gridView , simply set the android:numColumns to be of a variable that you set via the values folder for portrait mode , and and values-land for the value you need for landscape
EDIT: here's the solution of using a gridView :
the layout xml file should contain something like:
<GridView android:layout_width="match_parent"
android:numColumns="#integer/cols" android:layout_height="match_parent"
/>
create an xml file (for example "consts.xml") in the res/values folder that has something like:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="integer" name="cols">2</item>
</resources>
also , create the same file in res/values-land folder , and set the 2 to be 3 .
You can supply different resources for different orientations. So, when the device is in landscape, you populate the ListView with a layout that has two ImageViews in every item and in portrait, the layout has 1 ImageView per item.
Take a look at Providing Resources to know about how you can provide different layout XMLs for different orientations.
Sample code:
// Create an anonymous implementation of OnClickListener
private OnClickListener mImageListener = new OnClickListener() {
public void onClick(View v) {
// do something when the ImageView is clicked
}
};
//Check the orientation and handle accordingly in the getView
public View getView (int position, View convertView, ViewGroup parent){
if(convertView == null){
convertView = (LinearLayout)inflater.inflate(R.layout.list_item, parent);
}
ImageView leftImage = (ImageView)convertView.findViewById(R.id.leftImage);
ImageView rightImage = (ImageView)convertView.findViewById(R.id.rightImage);
boolean isTwoColumn = (rightImage != null);
//If it is two column layout, set both ImageViews
if(isTwoColumn){
leftImage.setImageResource(...);
leftImage.setOnClickListener(mImageListener);
rightImage.setImageResource(...);
rightImage.setOnClickListener(mImageListener);
}else{
leftImage.setImageResource(...);
leftImage.setOnClickListener(mImageListener);
}
}
/res/layout-land/list-item.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="+#id/leftImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<ImageView
android:id="+#id/rightImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</LinearLayout>
/res/layout-port/list-item.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="+#id/leftImage"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</LinearLayout>
I tried Gridview and personally found it was incredibly fiddly to get the display I was after (which was ratio correct display for smaller cells inside)... after weeks of restling with it on different resolutions and screen formats I opted for creating my own gridview from scratch and haven't looked back.
I'm not saying Gridview isn't without it's merit but what I can say is to implement your own system takes a matter of hours if you have a very clear idea in your mind how it should operate you'll probably find it easier to code your own.
In essence it was just a class derived from ViewGroup, override the onMeasure and onLayout items to measure the child views it contains and next lay them out into the locations on screen that you want them.
If you're looking for long lists however I'd still look at the listview or perhaps gridview might be worth a shot but this is certainly good peace of mind to know you can take control of it all if you need to.
I have two android devices for testing. One is with resolution 480x320 and other is with 800x480. I define different layouts in layout-normal and layout directories. I also tried it with layout-hdpi, layout-mdpi etc different combinations.
Is there a way to know from the log of somewhere that in which layout category a device falls just for debugging purposes. I would like to know layout file from which directory is used at runtime. If not then could someone tell me the right combination of layout directories for two devices with the pre-mentioned resolution.
Thanks in advance.
To find which layout (from layout-ldpi, layout-mdpi folder etc...) is used during runtime. You can use the tag attribute on your layout. For example let's say you have defined two layouts for different screens, the one in layout-mdpi folder and the other in layout-hdpi folder. Something like this:
<?xml version="1.0" encoding="utf-8"?>
<!--Layout defined in layout-mdi folder-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/MainLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:tag="mdpi"
android:orientation="horizontal" >
<!-- View and layouts definition-->
<!LinearLayout>
And:
<?xml version="1.0" encoding="utf-8"?>
<!--Corresponding Layout defined in layout-hdi folder-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/MainLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:tag="hdpi"
android:orientation="horizontal" >
<!-- View and layouts definition-->
<!LinearLayout>
To check which layout is used during runtime, you can use something like this:
LinearLayout linearLayout = (LinearLayout) findViewById(R.id.MainLayout);
if(linearLayout.getTag() != null) {
String screen_density = (String) linearLayout.getTag();
}
if(screen_density.equalsIgnoreCase("mdpi") {
//layout in layout-mdpi folder is used
} else if(screen_density.equalsIgnoreCase("hdpi") {
//layout in layout-hdpi folder is used
}
Here is an extension of #Angelo's answer that may work depending how you are using your elements: in each file, if you have the same element that you don't need to manipulate, you can give it a different ID for each layout you define (as opposed to tagging it).
For example, say I don't need to manipulate the base linear layout, I only need to manipulate the views inside it.
Here is my hdpi layout:
<?xml version="1.0" encoding="utf-8"?>
<!--Corresponding Layout defined in layout-hdpi folder-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/layout-hdpi"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<!-- View and layouts definition-->
</LinearLayout>
Heres an mdpi layout:
<?xml version="1.0" encoding="utf-8"?>
<!--Corresponding Layout defined in layout-mdpi folder-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/layout-mdpi"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal" >
<!-- View and layouts definition-->
</LinearLayout>
And here is my code that decides which layout it is:
if ( findViewById(R.id.layout-hdpi) != null ) {
//we are in hdpi layout
} else if ( findViewById(R.id.layout-mdpi) != null ) {
//we are in mdpi layout
}
The idea is that only one of the ids that you define for that item accross your different files will actually exist, and whichever one does is in the layout that actually loaded. The caveat is that if you actually need to manipulate that item later on, this method makes for a lot of extra work and is probably not ideal. You wouldn't want to use this technique on an item such as an EditText because then you have to check which layout you are in to decide which id to use to get that edit text.
I'm new to Android development. I'm trying to use the orientation change to switch between two activities. My idea is to use three Activities one TabActivity and a normal Activity. Here is some pseudo code:
public class Main extends Activity{
// this is the entry point into my app
#Override
public void onCreate(...){
if(this.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE){
// Start Activity1
} else { //Start Activity2}
}
The Activities Activity1 and Activity2 will have their onPause() functions overridden with an intent to start the Main Activity again.
onPause(){
Intent intent = new Intent(this.ActivityX, Main.class);
startActivity(intent);
}
\/\/\/\/ EDIT \/\/\/\/
Ok, I'm taking a different approach. As i need a TabActivity, because i want to be able to start different activities in my tabs (I think i have to use a TabActivity in this case).
I've edited the Manifest.xml and added android.configChanges="orientation" for the TabActivity and the second Activity. Then I've overridden the onConfigurationChanged() functions. With this approach I'm able to switch from landscape (this is the "normal" activity") to portrait (the TabActivity).
The other way round does not work and i don't know why yet. I've exactly the same onConfigurationChanged functions (Copy & Pasted) and only changed the essential parts.
Overriding the onConfigurationChanged of the Activities started in the tabs has no effect, too.
You don't have to write any code - Android already handles this automatically. Just create two different layout resource folders:
/res/layout-land // layout resources for landscape
/res/layout-port // portrait layout
Put in this folders xyz.xml resource description files with the same name and different content. One using activities for portrait, the other for landscape.
Note that you can use the same technique (-port & -land qualifiers) for drawables (bitmaps) or any other resources (text).
Note: this is not supported on Android 1.5. If you want to support this version you must additionally add the /res/layout folder.
If you still want to use the advantages of TabActivity when using tabs in portrait mode, while not having tabs in landscape mode, you could use the following (ugly but still working) workaround. Create /res/layout-land folder and put there a corresponding layout file (it should have the same name that the file in layout folder, you use for portrait orientation). This file, however, should contain the blocks required by the TabActivity to work. That is fine, add those blocks and set their visibility to "gone", like in the following snippet:
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#android:id/tabhost"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp">
<TabWidget
android:id="#android:id/tabs"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:visibility="gone" />
<FrameLayout
android:id="#android:id/tabcontent"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:padding="5dp"
android:visibility="gone" />
<!-- Feel free to add your REAL layout for the landscape -->
</LinearLayout>
</TabHost>
Of course, make sure you handle the content management correctly in your activity class, depending on the orientation.