1. Background
This tutorial is meant to be a very basic beginer's tutorial for resource management in Android by taking a case study of an image list control which is part of a real time published app. In simple term we want to develop an Android form that has a list box which presents scrollable list of items including image. But often a tutorial isn't too much fun unless and untill you know the power of the new thing that you are learning. You also do not know where exactly you can use it and how can you customize it. Therefore instead of just posting a rando tutorial, I have decided to walk you through series of tutorial to guide you through the process of a published app called DreamCurtains .
DreamCurtains is basically an Interior design App available for free that helps you take a snapshot of your window and then select from a list of Window Fabric provided by vendor to visualize how would your window be looking like if you opt for the specific fabric. See the image below. This is exactly the List that we want to create.
So in this specific tutorial we are interested in creating a control that presents information in following form. We shall cover:
- XML layout for creating such list
- Managing the data for the list
- Selecting in the List
- Styling the list
- Creating Menus
- Working with Multiple forms and accesing data of one form from another form.
We will use the data specific to DreamCurtains, but we shall also understand other possible cases and usage corresponding to this tutorial.
At the End of tutorial, you will be able to create your own list, customize it. Also the ultimate objective of this tutorial will be to guide you through the best practices of Android design that actually helps you finally publish an app in Google Play.
So let us start over and learn the step by step process of creating ImageList.
2. Starting with App Development
Before we start with the specifics of the article I want to provide an important tip for your Android Projects. When you create an Android project, default namespace starts with com.example. But remember com.example is a reserved package name. Google play will not permit any apps to be published with com.example package name. So it makes sense that at the begining itself you take a wise decision and provide a consistent namespace. If you have a Google Play developer account, create a namespace similar to that, else you can create an account any time ( which is not free but can be obtained for penut's price) and use that name. Alternatively you can use a namespace that you might want to use later if you do not have a play account right now. Note that package name be changed any time using Eclipse's great refractor tool, but as we say why leave the basic for the end?
Now for this example also, I suggest you that after importing the code into Eclipse you change the package name suitably so that you can keep a track of your own packages. You can do this simply by selecting the Refractor option by righti clicking on the package name inside src folder and opting for rename.
However I would assume that you would be more interested to build the project from scratch and would not need to look at the code at the first hand.
So Click on File->New->Other in Eclipse and opt for Android Application project as shown in Image Below
In the next screen enter the application name as MyImageList with appropriate package name and other options as shown below.
I want to discuss about few issues of selecting the correct minimum sdk and target sdk. Many a times you may pay little attention at the begining in these two only to realize that after publishing your App, you are seeing 'Not compatible to" for many devices and even may be the device you tested your app. Remember 2.2 is really the least that you may want to look for. However if you are developing an app specifically for smartphones than not many good smartphones uses that old 2.2. Android 4.0 has a drastic change from it's previous versions and provides several cool features that were absent in older Androids. Minor revisions in Androids like 4.1 and 4.2 also provides some new features but are nothing drastically or dramatically new in comparision to Android 4.0. Therefore Android 4.0 is really a good option to set your minimum and target SDK both. Keeping it higher will remove many devices, keeping it lower will remove features. So this is really a tradeoff thing.
Now Just Select 'Next' in all subsequent forms. Ensure that you have selected "Blank Activity" in "Create Activity" form.
If Everything goes smoothly as they should be, you would get a screen similar to the screen below.
Now we want to open the image list form through a Menu Click. The Image list will display list of Curtains( Image thumbnail, Curtain's name, color and other information) in the form. When we select any of them, the full image must be displayed in an Image in the home page.
So we should have an image view as well as menu in the main form to start with. For this we will drag and drop an Image View control in the main form as shown in the figure below.
3. Layouts, Strings and Menu
As you can see by default it takes a default icon as image source and it scales the image to suit the size of the icon. However we want the image to be aligned over the entire form. Also we want to rename the resource name of the image view to be able to easily track that in the "Code" while using findViewById method. So we will click fragment_main.xml and update the xml file. Observe that width and height attributes are changed to match_parent instead of default wrap_content. Also note that id is changed to imMainForm from default imageView1.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.grasshoppernetwork.myimagelist.MainActivity$PlaceholderFragment" >
<ImageView
android:id="@+id/imMainForm"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="70dp"
android:layout_marginTop="43dp"
android:src="@drawable/ic_launcher" />
</RelativeLayout>
Once you have carefully made the changes, you can go back to design mode and can see the form change as image below. You can clearly see the change in the image size.
You can play around with the layout_marginLeft and layout_marginTop parameters to see how image location differs.
At this moment you can run the code in emulator as see the result. However as a personal choice I always prefer a real Android device in USB debugger mode rather than AVD. One it provides you all the features that you want to explore and secondly you know how your form looks like in real devices. You also get to run the code in real RAM and display settings which provides a much better testing environment.
Having setup the home form we want to setup our menu option now.
For setting up menu, go inside res->menu and double click on main.xml. It will open up the menu xml in the layout mode. You can create menu options as item from xml or can even create the items through GUI as shown in the image below. First you need to click add and select menu item. Change the ID and title suitable. do not bother about any other changes at this moment. Use ctrl+s to save your changes.
Now when you look inside the xml part you will clearly see following line
<item android:id="@+id/menuOpenList" android:title="Open Curtain List"></item>
You will also observe a yellow warning mark beside this line in Android. It will warn you against using a static text without a resource. Well it's always a good practice in Android to use string resource for static text like menu text or labels.
We will get rid of this warning by creating a string resource. You can create String resource by double clicking on res/values/strings.xml and then adding a new string. You can provide an appropriate name and put "Open Curtain List" as it's value as shown below.
Now all we have to do is go back to main.xml under res/menu and change the static text for the item with newly created resource name.
<item android:id="@+id/menuOpenList" android:title="@string/MenuOpenCurtainTest"/>
Now the warning is gone. It is time to build and test what we have done so far. The image below is a screenshot of the app for the work done so far taken from DDMS for my Asus- Google Nexus 7".
But basic requirement for opening an ImageList is not yet over. You want to open another form when you press the "Open the Curtain Form" Menu option. So we still need some code behind to trigger the menu and need another form to be opened with the code behind.
4. New Layout and Forms
Let us first create a Relativelayout by name list_row.xml
Right click on res/layout, chose New->File, provide a filename called list_row.xml. As shown below.
After this step you will see the designer without any form. Just click on the list_row.xml tab beside Graphics layout and add following two lines.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android">
</RelativeLayout>
Now when you come back to Graphics Layout you will see while form.
You may also do it in more straight forward way. While selecting New after right clicking on layout, choose "Other". There you can select an Android activity. I prefer in using first method because I can nicely set the layout properties as par my suitability without much of hassles.
Alright then. We have a form that should hold an Image list and we have the menu from where it should be called. But one thing is still missing! You you expand src and subsequently your package folder, you will see MainActivity.Java . In similar way I also need a codebehind for my list_row.xml.
Create a new java class by name CurtainListActivity.java by right clicking over the package name inside src folder and opting for New Class. See the image below for understanding the process better.
However remember that our java class is no ordinary class. It is an activity class. Therefore make your new class extend Activity. The moment you modify class declaration as
public class CurtainListActivity extends Activity
You will see a red error in the panel on the left of this line as shown below. Just click on the error marker, Eclipse shoots up all possible solutions, select import Android.Activity option.
So whenever you see error messages in Eclipse in Android development, you can always use this trick to quickly fix the error.
Activity class must override onCreate method where all the component initialization must take place. You can do this by simply copying the onCreate method from MainActivity class and pasting and updating the method defination.
If you have copied the onCreate method from MainActivity class than you would see an if for frame_layout. You definately do not want it. So remove this entire section. Also you will notice an automatic import of com.grasshoppernetwork.myimagelist.MainActivity.PlaceholderFragment . Remove this line too. All you have to do now is inside findViewById you have to change R.layout.activity_main to R.layout.list_row. But when you remove activity_main and use ctrl+space to see the option, in all possibility it is not going to show you an option of list_row. If it does not, then just build the project once(Obviously you would like to comment this like as there is nothing after R.layout. ). Once the project is successfully built, you will find your option of list_row.
So it is always a good option to build the project once after you have created a new layout for the id to be reflected in code.
So finally our CurtainListActivity class is looking something like this:
package com.grasshoppernetwork.myimagelist;
import android.app.Activity;
import android.os.Bundle;
public class CurtainListActivity extends Activity
{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.list_row);
}
}
If you class is also looking exactly same, then you have probably not changed the package name as advised at the begining of the tutorial. It's time to fix that and change the package name.
Whenever you have an activity class you need to provide the information to AndroidManifest file. If you do not do it, then your application will trigger error as and when you try to use this form.
One more thing to be noted here is that for this specific application we want to present every form in landscape. Therefore you need to mention some extra information in the manifest. Go ahead and paste the following lines in AndroidManifest.xml after closing </Activity> for main activity section.
<activity android:name="com.grasshoppernetwork.myimagelist.CurtainListActivity"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden">
</activity>
You have everything with you now that is needed for us to go ahead with ImageList. But wait! Shouldn't we check if our second form is getting called properly from menu selection or not? Surely we should. As the menu is triggered from the MainActivity, let us go to MainActivity and do some coding.
If you scroll down in MainActivity form, you can readily find onOptionsItemSelected(MenuItem item). This method will be called when any menu item is clicked. You will also see a default if for settings menu. But we want a cleaner code. So we want to replace the if with switch and put a case for menuOpenList.
A second form can be called from any form by using Intent class object. An Intent can be considered as an Insatance of a class ( Something like a Dialog class we use in C#). An Intent can take parameters and return some result. Therefore it is always a good option to tell the intent what you want to do with it. As we want to select fabric, we will use a static integer called FABRIC_SELECTION to tell the intent of CurtainActivityClass that we want it to return an Item selected by user in the second form. onOptionItemSelected method takes following form.
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch(id)
{
case R.id.menuOpenList:
try{
Intent intent1 = new Intent();
intent1.setClass(this, CurtainListActivity.class);
Log.i("In Menu Browse","In Menu Browse");
startActivityForResult(intent1, FABRIC_SELECTION);
}catch(Exception ex)
{
}
break;
}
return super.onOptionsItemSelected(item);
}
Let us now built the solution and check if does what it is supposed to do: i.e. opening the new form upon Menu selection.
But when you run the application, and select the menu, you application is going to be crashed. With error message showing something like this:
This is particularly a helpful error to get because it gives you an Idea of how to deal with error. Basically Android runtime shoots a large error message for any crash. Instead of getting afraid of the huge text, you must carefully read the message. Here it clearly shows error in CurtainListActivity and in Binary XML file. As CurtainListActivity is the code behind for list_row.xml, you need to understand that the problem appears there. Now what you see? Absolutely perfect observation. There are no android:layout_width and android:layout_height parameters there. So modify the xml as bellow.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
</RelativeLayout>
Now build your application and you will have the smile you were waiting for.
On a side note I also want to give you a good debugging tip. When you have a complicated workflow, ( for instance user should come through a login form, select some option and open the form and you have similar error in the form, you can temporarily modify AndroidManifest.xml. All you have to do is cut <intent-filter> tag from MainActivity section to section of the form that you want to open after app is launched. This way you can quickly test the problem area. Once the problem is solved, you can change the manifest to it's original setting.
For example in our case when we were getting error in the second form, it makes sense to fix the form first. So we can use following setting to open the list_row form first.
="1.0"="utf-8"
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.grasshoppernetwork.myimagelist"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="14"
android:targetSdkVersion="14" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name="com.grasshoppernetwork.myimagelist.MainActivity"
android:label="@string/app_name" >
</activity>
<activity android:name="com.grasshoppernetwork.myimagelist.CurtainListActivity"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
Observe how intent filter part is now inside CurtainListActivity part.
Let's quickly recap what we have learnt so far:
- How to create Android forms
- How to work with menus
- Why and when you should use string resources
- How to debug your layout related issues and
- What role AndroidManifest file plays.
We have also learnt the best practices to be followed for an app from word go so that app is available to most devices in Google play.
Having created a solid background for our App, let us
5.Managing External Resources
Game developers are more familiar with the term "assets" then say Resources. However in Android we call them resources. Resources are generally static data that the app uses. What exactly is static data? For instance in a single "Pin a Baloon" game there will be several baloons moving across the scene. These are nothing but different baloon images randomly chosen by the application to present in the scene. Their size and position may vary but images will remain same. These images of baloons are static resource. Once you pin a baloon, you need the app to update the score. Now that score is part of a database. It needs to be updated frequently. We will call that resource as dynamic resource. Further a game may also have several configurations which are stored in a xml file, which might be presented to user through some GUI option, user may select any of the item but can't change it. Such resources that holds configuration data will also fall under static resources.
For the simplicity we call all the static resources as simple "Resource". As for our application, we are looking to create a list item that will have several curtain images The list should also present it's name, color and type of the fabric and should also allow the user to easily filter out the information. Therefore it is clear that we need to first have set of images and then we need to have a xml file which should have thumbnail of the images along with other desirable information. Let us now learn how to do it:
5.1 Managing Images
First let us look at a single curtain to understand few facts about this image. This is a PNG image which is transparent in the background. But as obvious such PNG images will be of large size. This particular image is 1.1Mb image. Now imagine if we were looking for minimum 100 curtain database! You would be ending up clubbing 110Mb of only curtain images. How many customers will be actually interested in downloading an app of 100 Mb? Not just that many devices has limited memory. So Apps larger than 30 Mb will not be visible for many devices even though they meet SDk requirement. Therefore if you have immense amount of image resource, it is always advisable to use a Jpg version instead of PNG image. But then what about the transparency? Well we will see that Android provides some real cool coding using which you can make the background transparent programaticaly.
On the other hand if you have only few resources like the case of "Pinch a Baloon", you really do not want to eat out battery of the device by invoking an ImageTransparent code in every refresh. As number of resources is less, PNG images are better suited for such options. But note, if you are using a custom menu for your App in thye store ( Which you won't if you don't want anyone to download your app) then you probably would want that image to be transparent, high quality and invariably a PNG image.
When I was just a beginer in Android development, I struggled a lot with determining these simple issues. There are many tutorials that will teach you "how" but you will rarely come across with solutions that would answer "why" and that would guide you "when". The above two paragraphs will be most helpful for you in long run as Android developer.
Okey, our is quite a resource intensive app. Therefore we will be using JPEG images. We will also scale the images a little to reduce the size a little. For many other Android Image resource utilization you would need to resize, rescale, rename the images. This software Fotosizer will come very handy. Install the software. It's free version is enough for your regular needs. The good thing about it is that it's batch resizing capabilities are really cool.
In order to complete the discussion I would also want to raise another issue here. When you are working with Images, many a times you will have a JPEG image and you want to remove it's background or want to make it transparent. Rather than using using any external software, you can use following snippet of C# code with windows form. Let me tell you, this works better than most of the so called online software.
Bitmap bmp=(Bitmap)Bitmap.FromFile("my.jpg");
bmp.MakeTransparent();
bmp.Save("tmp.png", System.Drawing.Imaging.ImageFormat.Png);
You can build a nice little App for yourself that would ask for an image selection and saving. On the other hand you are always free to use another App. C# community of code project is really strong. We find C# much more comfortable than other languages. So the above bit of code is just a small dose of bribe I wanted to offer the community :)
Coming back to our app quickly. We have learnt the tricks of using types of images, to keep the app size under check and also how to convert and resize between images. The big question is where to place them?
You need to place the images inside res/drawable- directory. But wait! There are 5 versions of them hdpi,ldpi,mdpi,xhdpi and xxhdpi. Where to place our images and what is the significance.
Classic Android theory will tell that you should place the images depending upon their resolution in device pixel ( dp). hdpi=> 640 X 480,ldpi =>240 X 320,mdpi=>320 X 480,xhdpi=> 640 X 960 and xxhdpi for very high resolution images above 640x960. You need to understand that not all the phones are smartphones. There are phones with less power and smaller screen. For those phone you need to present 240x320 images. In case of a good smartphone like Samsung Galaxy S3, if you present image with resolution 240x320, it has to stretch the images which would undoubtedly break the astheticity of the app. So classic Android theory says that it is always a good option to provide different resolution version of the images so that every phone can be presented with image with it's compatibility.
But Android 4 is quite stronger and can automatically scale the images down for low resolution phone. So you really don't need to worry too much about the resolution. An average resolution image placed in xhdpi folder is a good and wise generalized choice for App development.
What if some images are of smaller size that xhdpi specification? Well that does not matter really. You can also place a 50x50 image in xhdpi and it will be able to render your image just as perfectly. So what we learnt?
Any image resource to be placed under res/drawable-xhdpi irrespective of it's size and resolution and phones take care of rest. ( You may come across a situation where this rule fails, if it does, loop back to classical theory. But for me my theory has worked perfectly all the time for most of the phones).
But wait.............. don't rush into placing images in the directory.....
Why? Because there is one more important thing to know at this stage. Every external resource ( I hope you are now pretty clear about what external resource are) is compiled as a binary by Android. Everything is bundled as a single apk file for distribution. So Android does not permit the usage of the resources by their absolute path. Rather ther are compiled as binary resource and just like other resources like menus and layouts.
If you take a look inside R.java in gen folder, you will see that an id is created for ic_launcher.png whose different version is present in different res/drawable- folders.
It is also important to note that Android supports resource names to be starting with small letters.
The moment you rename ic_launcher png as Ic_launcher.png, it will start giving binary xml error. Hence I follow a thumbrule that "All the external resources should be of named with small letters"
Download drawable-xhdpi.zip , extract it and copy and paste the images inside your app's drawable-xhdpi folder. If you use drag and drop inside that folder, it might ask you whether you want to use "copy" or "link" to file, it is unnecessory to say that you must opt for copying the files. Once you do your res/drawable-xhdpi will look something like this.
And after new resources are added ( whether static external, external images, data resource or GUI resource), it makes sense to build your project.
If everything goes as par plan and your app gets build successfully, you will see that R.java is updated and it now should contain ID of all new resources.
This is confirmation that you have been doing awesome. Little more of fun stuff and you would complete the tutorial alsong with some understanding of how resource stuff works in Android.
5.2 Configuration Database
Now we should create our xml file with all the information about the photos that we have placed and the provide this sml to a class for binding the information with our list_row class. XML documents that acts like data for an app which contains information about different static resources and additional information about them are also called assets. Therefore such files must be duely stored inside Asset folder of Android.
Let us look at our data first. Each Curtain is stored within <Curtain> tag inside root tag <Curtains>
<Curtain>
<ItemNo>Ami-Liner-Quill-4</ItemNo>
<ItemName>Ami-Liner-Quill-4</ItemName>
<Color>Grey</Color>
<Genere>Contemporary</Genere>
<Composition>Polyester</Composition>
<Utility>Main Curtain</Utility>
<ImagePath>photo0.jpg</ImagePath>
</Curtain>
ItemNo, ItemName,Color,Composition,Utility are the properties of curtain which we are considering here. Your data may have other configurations. You can change the configuration accordingly. Interesting is Imagepath. You can observe that no absolute path is specified because we would not use it any how. In the binding part of code we will appropriately bind the image with it's specific path.
Download curtaindata.xml and put it inside your Asset folder. As you have added a new resource, build your app once. Now when you look inside R.java you will not found any link to curtaindata.xml . That is because id of assets are not generated, you need to bind it with GUI components from code using the relative path of the assets.
Those who are familier with MVVM styly of coding in .Net will find it quite similar and easy. Others need not to worry, the process of binding is no rocket science here.
It needs no brain of Einstine to represent your xml schema as class right? So we can first write a class with fields sich as ItemNo, Color, ItemName, Genere, ImagePath and Composition. All the xml data can be configured as a list of objects this class.
So create a new class by name CurtainDbClass in your src folder as we have learnt earlier.
package com.grasshoppernetwork.myimagelist;
public class CurtainDbClass
{
public String ItemNo,ItemName,Genere,Composition,Utility,Color;
public CurtainDbClass()
{
}
public CurtainDbClass(String ItemNo,String ItemName,String Genere,String Composition,String Utility,String Color)
{
this.Composition=Composition;
this.Genere=Genere;
this.ItemName=ItemName;
this.ItemNo=ItemNo;
this.Utility=Utility;
this.Color=Color;
}
}
Now we need to pick data from xml and create the desired list. However if you remember, our second form , i.e. list_row form is not yet ready to take this data. Why? Because the form neither contains a list nor contains any controls that can hold image thumbnail, their name, and other properties. So before we shift our focus to java coding and binding xml data with List of the above class, we need to update our layout.
5.3 Custom Style
Custom styles are other important aspect of Android. Again this is very similar to wpf design with some Android specific twek. Using custom styles you can provide beheviors like change of color upon hover, you can define gradient colors and many more. We want to style our list_row to make it more prefessional. style resources are also like assets and can be placed in any directory. I prefer them to be inside drawable as we invariably define rendering styles through them. Download list_style.zip ,unzip the folder and load the files inside res/drawable-shdpi folder. Though it is not mandatory to define custom style for the controls, we use it here wishfully to introduce the concept before the reader. gradient_bg_hover XML file is as follows. You can see that we have defined a gradient gray color here that changes radially with an angle 270'.
="1.0"="utf-8"
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#78DDFF"
android:centerColor="#16cedb"
android:endColor="#09adb9"
android:angle="270" />
</shape>
Similarly we define gradient_bg file as:
="1.0"="utf-8"
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:startColor="#D5DDE0"
android:centerColor="#e7e7e8"
android:endColor="#CFCFCF"
android:angle="270" />
</shape>
You can open Html Color Code website and can get your custom colors. See the following screenshot to understand how it works. The screenshot shows D5DDE0 color used in our style.
We have defined the color styles. Now let us also define a style for the list by using the defined color styles. list_selector.xml does just that. It defines the color when the list is hovered over, when nothing is selected and when an item is selected.
="1.0"="utf-8"
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:state_selected="false"
android:state_pressed="false"
android:drawable="@drawable/gradient_bg" />
<item android:state_pressed="true"
android:drawable="@drawable/gradient_bg_hover" />
<item android:state_selected="true"
android:state_pressed="false"
android:drawable="@drawable/gradient_bg_hover" />
</selector>
Once the styles are defined, let us now update our list_row.xml layout file so that it can not only accomodate the data binding but at the same time has all new style.
Ideally we should use an ImageView control for displaying the curtain image, one text box each for ItemNumber, ItemName, Color, Composition, Genere. But for simplicity, we will use two text boxes, and through code we will concat color, composition and genere into a single string and assign that to one of the text boxes. Other one would have name and number. But you can define different text boxes for each entity.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@drawable/list_selector"
android:orientation="horizontal"
android:padding="5dip" >
<LinearLayout
android:id="@+id/thumbnail"
android:layout_width="80dp"
android:layout_height="80dp"
android:layout_alignParentLeft="true"
android:layout_marginRight="5dip"
android:padding="3dip" >
<ImageView
android:id="@+id/list_image"
style="@dimen/activity_horizontal_margin"
android:layout_width="0dp"
android:layout_height="80dip"
android:layout_gravity="top"
android:layout_weight="9.63"
android:contentDescription="@string/app_name"
android:maxHeight="80dp"
android:maxWidth="120dp" />
</LinearLayout>
<TextView
android:id="@+id/tvCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignTop="@+id/thumbnail"
android:layout_toRightOf="@+id/thumbnail"
android:paddingBottom="9dip"
android:text="@string/strCode"
android:textColor="#040404"
android:textSize="15sp"
android:textStyle="bold" />
<TextView
android:id="@+id/tvName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignLeft="@+id/tvCode"
android:layout_centerVertical="true"
android:paddingTop="5dip"
android:text="@string/strName"
android:textColor="#343434"
android:textSize="15sp" />
</RelativeLayout>
Now when you go back in the Graphical Layout, you should see nice styled one row of the list item.
Along with the row of the list you would also see couple of error messages corresponding to two string resources. Well we know the trick now. Does not take any google search to fix this. Just go inside /res/values/strings.xml and add following lines in the xml part.
<string name="strName">Velvet</string>
<string name="strCode">ABC1</string>
These two variables does not have much of significance barring the fact that they help keeping the warning off. If you are okey with warnings, you can just use static text in list_row.xml for TextView1 and TextView2 instead of string resources.
6. List Control
We created the list_row form and updated it. From name itself it is clear that this form represents a single row of our listvie.w. What about presenting multiple such rows? Well for that you need an Android ListView control and to hold the control you need one more form. Let us create a layout by name main.xml in res/layout. Select the type to be LinearLayout as no other control other than a ListView is expected. Drag and Drop a ListView control from composite section of the toolbox.
Now modify the xml code of the layout with the following code:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<ListView
android:id="@+id/list"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:divider="#b5b5b5"
android:dividerHeight="1dp"
android:listSelector="@drawable/list_selector">
</ListView>
</LinearLayout>
You can see that we have modified the properties of ListView control and used listSelector as the style we created in previous section by name list_selector. But there are two more important properties to be looked into.
First is the layout_width. It is set to fill_parent Because you want the row to have the same width as that of the form. However the layout_height property of the list view is set to wrap_content because you relly want the row to have height same as ImageView height. You can change these properties to see the effect in final result. But for now, our main.xml should look similar to:
As new resource is added, just rebuild the app. We have also learnt that new layout needs to be added in the manifest file. But we are not defining any java class for this. Rather we are going to modify our CurtainListActivity.java onCreate method and make the class populate main xml rather than list_row. We will use list_row just as a template for the ListView row in the main.xml file.
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
PerformList(true);
ad=new Builder(this);
}
7. Data Binding
We have CurtainListActivity class in which we would open the xml file and read the rows. But we need a class which can create a single row from one xml node, put it in list_row and then pass that row to the main layout. So basically we are looking for something like a subsidery view. In Android multiple independent Views can be grouped inside a ViewGroup. Each of the view can be modeled separately to have independent data.
Let us create a class by name BinderData that should perform this task for us. Make the class to extend android.widget.BaseAdapter . Now the class will be able to return a custom view. In order to bind the xml node data with class data, you can define string variables inside the class.
static final String KEY_TAG = "Curtain";
static final String KEY_CODE = "ItemNo";
static final String KEY_NAME = "ItemName";
static final String KEY_COMPOSITION = "Composition";
static final String KEY_Utility = "Utility";
static final String KEY_ICON = "ImagePath";
static final String KEY_GENERE = "Genere";
static final String KEY_COLOR = "Color";
Now we also need a List of pair of two strings ( remember we want to put every detail in two text boxes?). We need an ImageView to pull the image and a ViewHolder to hold the dynamic view.
LayoutInflater inflater;
ImageView thumb_image;
List<HashMap<String,String>> curtainDataCollection;
ViewHolder holder;
Remember BinderData will be used from CurtainListActivity to obtain a View ( one row). Hence BinderData must have a constructor that takes an instance of activity, map the text data with curtainDataCollection and inflate the new view.
public BinderData() {
}
public BinderData(Activity act, List<HashMap<String,String>> map) {
this.curtainDataCollection = map;
inflater = (LayoutInflater) act
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
The other important methods are: getCount which should return number of elements (rows),getItem which must return a row item given row number and getItemId which should return the itemId from the view position.
public int getCount() {
return curtainDataCollection.size();
}
public Object getItem(int arg0) {
return null;
}
public long getItemId(int position) {
return 0;
}
Most important part of the BinderData class is getView method though. This method is responsible for inflating a prepared view.
public View getView(int position, View convertView, ViewGroup parent)
{
View vi=convertView;
if(convertView==null){
Log.i("Tracking view", "-----Scrolling---------");
vi = inflater.inflate(R.layout.list_row, null);
holder = new ViewHolder();
holder.tvName = (TextView)vi.findViewById(R.id.tvName);
holder.tvCode = (TextView)vi.findViewById(R.id.tvCode);
holder.tvcurtainImage =(ImageView)vi.findViewById(R.id.list_image);
vi.setTag(holder);
}
else{
holder = (ViewHolder)vi.getTag();
}
try{
String codeName=curtainDataCollection.get(position).get(KEY_CODE)+""+curtainDataCollection.get(position).get(KEY_NAME);
String genereComUtility=curtainDataCollection.get(position).get(KEY_GENERE)+", "+curtainDataCollection.get(position).get(KEY_COMPOSITION)+", "+curtainDataCollection.get(position).get(KEY_Utility)+", "+curtainDataCollection.get(position).get(KEY_COLOR);
holder.tvName.setText(codeName);
holder.tvCode.setText(genereComUtility);
Log.i("in Binder","before uri");
String identifier=curtainDataCollection.get(position).get(KEY_ICON);
identifier=identifier.split(".jpg")[0];
String uri = "drawable/"+ identifier;
int imageResource = vi.getContext().getApplicationContext().getResources().getIdentifier(uri, null, vi.getContext().getApplicationContext().getPackageName());
Log.i("in Binder resource="," "+imageResource+"----"+identifier+"===="+vi.getContext().getApplicationContext().getPackageName());
Drawable image = vi.getContext().getResources().getDrawable(imageResource);
holder.tvcurtainImage.setImageDrawable(image);
Log.i("in Binder URI",uri);
}catch(Exception ex)
{
}
return vi;
}
static class ViewHolder{
TextView tvName;
TextView tvCode;
ImageView tvcurtainImage;
}
In this line:
vi = inflater.inflate(R.layout.list_row, null);
BinderData defines that the view it is going to present is nothing but an instance of list_row layout. Hence it must define all the elements of the list_row layout programatically. for this it defines a static class ViewHolder and maps it's fields with corresponding layout fields.
holder.tvName = (TextView)vi.findViewById(R.id.tvName);
holder.tvCode = (TextView)vi.findViewById(R.id.tvCode);
holder.tvcurtainImage =(ImageView)vi.findViewById(R.id.list_image);
It creates two strings: One called CodeName where it puts name of the curtain and the other string contains all other information. These informations are fetched from curtainDataCollection through KEY codes.
try{
String codeName=curtainDataCollection.get(position).get(KEY_CODE)+""+curtainDataCollection.get(position).get(KEY_NAME);
String genereComUtility=curtainDataCollection.get(position).get(KEY_GENERE)+", "+curtainDataCollection.get(position).get(KEY_COMPOSITION)+", "+curtainDataCollection.get(position).get(KEY_Utility)+", "+curtainDataCollection.get(position).get(KEY_COLOR);
holder.tvName.setText(codeName);
holder.tvCode.setText(genereComUtility);
After the testData is set, it is time to fetch and create the image. Recall that R.java has a final static class called drawable. resource names are present as variables. so photo0.jpg is just photo0 resource inside drawable. Therefore BinderData must extract the path, split the path , separate .jpg, and extract the first part of the string ( i.e. photo0). Now it should search this resource inside all resources and finally using the ID it must obtain the specific image.
String identifier=curtainDataCollection.get(position).get(KEY_ICON);
identifier=identifier.split(".jpg")[0];
String uri = "drawable/"+ identifier;
int imageResource = vi.getContext().getApplicationContext().getResources().getIdentifier(uri, null, vi.getContext().getApplicationContext().getPackageName());
Once ImageResource number is available, create a Drawable object from the resource and allocate the object to ImageView instance. A drawable is kind of Dirty memory for drawing in Android. Before rendering, you can draw any object like canvas, bitmap into drawable and then render the drawable into main display area.
Drawable image = vi.getContext().getResources().getDrawable(imageResource);
holder.tvcurtainImage.setImageDrawable(image);
Now our BinderData class is completely ready to be used by CurtainListActivity.java. Have a look at complete class:
BinderData.java
package com.grasshoppernetwork.myimagelist;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;
public class BinderData extends BaseAdapter {
static final String KEY_TAG = "Curtain";
static final String KEY_CODE = "ItemNo";
static final String KEY_NAME = "ItemName";
static final String KEY_COMPOSITION = "Composition";
static final String KEY_Utility = "Utility";
static final String KEY_ICON = "ImagePath";
static final String KEY_GENERE = "Genere";
static final String KEY_COLOR = "Color";
LayoutInflater inflater;
ImageView thumb_image;
List<HashMap<String,String>> curtainDataCollection;
ViewHolder holder;
public BinderData() {
}
public BinderData(Activity act, List<HashMap<String,String>> map) {
this.curtainDataCollection = map;
inflater = (LayoutInflater) act
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
public int getCount() {
return curtainDataCollection.size();
}
public Object getItem(int arg0) {
return null;
}
public long getItemId(int position) {
return 0;
}
public View getView(int position, View convertView, ViewGroup parent)
{
View vi=convertView;
if(convertView==null){
Log.i("Tracking view", "-----Scrolling---------");
vi = inflater.inflate(R.layout.list_row, null);
holder = new ViewHolder();
holder.tvName = (TextView)vi.findViewById(R.id.tvName);
holder.tvCode = (TextView)vi.findViewById(R.id.tvCode);
holder.tvcurtainImage =(ImageView)vi.findViewById(R.id.list_image);
vi.setTag(holder);
}
else{
holder = (ViewHolder)vi.getTag();
}
try{
String codeName=curtainDataCollection.get(position).get(KEY_CODE)+""+curtainDataCollection.get(position).get(KEY_NAME);
String genereComUtility=curtainDataCollection.get(position).get(KEY_GENERE)+", "+curtainDataCollection.get(position).get(KEY_COMPOSITION)+", "+curtainDataCollection.get(position).get(KEY_Utility)+", "+curtainDataCollection.get(position).get(KEY_COLOR);
holder.tvName.setText(codeName);
holder.tvCode.setText(genereComUtility);
Log.i("in Binder","before uri");
String identifier=curtainDataCollection.get(position).get(KEY_ICON);
identifier=identifier.split(".jpg")[0];
String uri = "drawable/"+ identifier;
int imageResource = vi.getContext().getApplicationContext().getResources().getIdentifier(uri, null, vi.getContext().getApplicationContext().getPackageName());
Log.i("in Binder resource="," "+imageResource+"----"+identifier+"===="+vi.getContext().getApplicationContext().getPackageName());
Drawable image = vi.getContext().getResources().getDrawable(imageResource);
holder.tvcurtainImage.setImageDrawable(image);
Log.i("in Binder URI",uri);
}catch(Exception ex)
{
}
return vi;
}
static class ViewHolder{
TextView tvName;
TextView tvCode;
ImageView tvcurtainImage;
}
}
Let us now go ahead and Update our CurtainListActivity.java . But before we do that we have to remember that user can select a row in main layout and the information specific to that should be available in our first form where we would be displaying the image. As there are multiple parameters where this form will write and calling MainActivity will read, we will create a class called GlobalPlaceHolderClass. We will create public static fields corresponding to our data and store the specific data.
public class GlobalPlaceholder
{
public static ArrayList<CurtainDbClass> database;
public static String selectedGenere="";
public static String selectedComposition="";
public static String selectedUtility="";
public static String selectedColor="";
}
In section 6, if you had carefully observed the modification of onCreate method from within CurtainListActivity class, you would have surely observed calling of a method by name PerformList.
In order for better code maintainance and usability we put entire logic of reading from xml file, binding each data with BinderData and finally presenting the view within PerformList Method:
XML data will have elements. we will maintain one list for maintaining each of the nodes ( Cutains) and their associated Children ( element data). We will also keep the first set of elements and nodes such that we can put them in selected item initially. Not doing this sometimes forces the app to crash.
Element firstNameElement = null;
NodeList textNameList = null;
NodeList iconList = null;
Element firstIconElement = null;
NodeList textIconList = null;
NodeList genereList = null;
Element firstGenereElement = null;
NodeList textGenereList = null;
NodeList compositionList = null;
Element firstCompositionElement = null;
NodeList textCompositionList = null;
NodeList utilityList = null;
Element firstUtiliElement = null;
NodeList textUtilityList = null;
NodeList colorList = null;
Element firstColorElement = null;
NodeList textColorList = null;
Once the Class properties are declared, it is time to define PerformList and to understand the logic behind it.
void PerformList(boolean updateDatabase)
{
try {
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
Document doc = docBuilder.parse (getAssets().open("curtaindata.xml"));
Log.i("doc opened","Doc opened");
curtainDataCollection = new ArrayList<HashMap<String,String>>();
doc.getDocumentElement ().normalize ();
NodeList curtainList = doc.getElementsByTagName("Curtain");
Log.i("size", ".."+curtainList.getLength());
HashMap<String,String> map = null;
Element firstCurtainElement = null;
if(updateDatabase)
GlobalPlaceholder.database=new ArrayList<CurtainDbClass>();
for (int i = 0; i < curtainList.getLength(); i++) {
map = new HashMap<String,String>();
Node firstCurtainNode = curtainList.item(i);
if(firstCurtainNode.getNodeType() == Node.ELEMENT_NODE)
{
try{
firstCurtainElement = (Element)firstCurtainNode;
codeList = firstCurtainElement.getElementsByTagName(KEY_CODE);
firstCodeElement = (Element)codeList.item(0);
textCodeList = firstCodeElement.getChildNodes();
map.put(KEY_CODE, ((Node)textCodeList.item(0)).getNodeValue().trim());
map.put(KEY_NAME," ");
iconList = firstCurtainElement.getElementsByTagName(KEY_ICON);
firstIconElement = (Element)iconList.item(0);
textIconList = firstIconElement.getChildNodes();
map.put(KEY_ICON, ((Node)textIconList.item(0)).getNodeValue().trim());
genereList = firstCurtainElement.getElementsByTagName(KEY_GENERE);
firstGenereElement = (Element)genereList.item(0);
textGenereList = firstGenereElement.getChildNodes();
map.put(KEY_GENERE, ((Node)textGenereList.item(0)).getNodeValue().trim());
compositionList = firstCurtainElement.getElementsByTagName(KEY_COMPOSITION);
firstCompositionElement = (Element)compositionList.item(0);
textCompositionList = firstCompositionElement.getChildNodes();
map.put(KEY_COMPOSITION, ((Node)textCompositionList.item(0)).getNodeValue().trim());
utilityList = firstCurtainElement.getElementsByTagName(KEY_Utility);
firstUtiliElement = (Element)utilityList.item(0);
textUtilityList = firstUtiliElement.getChildNodes();
map.put(KEY_Utility, ((Node)textUtilityList.item(0)).getNodeValue().trim());
colorList = firstCurtainElement.getElementsByTagName(KEY_COLOR);
firstColorElement = (Element)colorList.item(0);
textColorList = firstColorElement.getChildNodes();
map.put(KEY_COLOR, ((Node)textColorList.item(0)).getNodeValue().trim());
boolean flagInsert=true;
if(GlobalPlaceholder.selectedColor.length()>2 && !GlobalPlaceholder.selectedColor.trim().equals(((Node)textColorList.item(0)).getNodeValue().trim()))
{
flagInsert=false;
}
if(GlobalPlaceholder.selectedGenere.length()>2 && !GlobalPlaceholder.selectedGenere.trim().equals(((Node)textGenereList.item(0)).getNodeValue().trim()))
{
flagInsert=false;
}
if(GlobalPlaceholder.selectedComposition.length()>2 && !GlobalPlaceholder.selectedComposition.trim().equals(((Node)textCompositionList.item(0)).getNodeValue().trim()))
{
flagInsert=false;
}
if(GlobalPlaceholder.selectedUtility.length()>2 && !GlobalPlaceholder.selectedUtility.trim().equals(((Node)textUtilityList.item(0)).getNodeValue().trim()))
{
flagInsert=false;
}
if(flagInsert)
{
Log.i("In Curtain View After Selection",GlobalPlaceholder.selectedColor+"-"+GlobalPlaceholder.selectedGenere+"-"+GlobalPlaceholder.selectedComposition+">>"+map.get(KEY_GENERE));
curtainDataCollection.add(map);
if(updateDatabase)
GlobalPlaceholder.database.add(new CurtainDbClass(textCodeList.item(0).getNodeValue().trim(), " ", textGenereList.item(0).getNodeValue().trim(), textCompositionList.item(0).getNodeValue().trim(), textUtilityList.item(0).getNodeValue().trim(), textColorList.item(0).getNodeValue().trim()));
}
}
catch(Exception ex)
{
Log.e("In list_row", ex.getMessage());
}
}
}
BinderData bindingData = new BinderData(this,curtainDataCollection);
list = (ListView) findViewById(R.id.list);
Log.i("BEFORE", "<<------------- Before SetAdapter-------------->>");
try{
Log.i("Data binding",bindingData.curtainDataCollection.get(0).toString());
list.setAdapter(bindingData);
}catch(Exception ex)
{
}
Log.i("AFTER", "<<------------- After SetAdapter-------------->>");
list.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
Intent i = new Intent();
i.putExtra("name", curtainDataCollection.get(position).get(KEY_NAME));
i.putExtra("code", curtainDataCollection.get(position).get(KEY_CODE));
i.putExtra("icon", curtainDataCollection.get(position).get(KEY_ICON));
if (getParent() == null)
{
setResult(Activity.RESULT_OK, i);
} else
{
getParent().setResult(Activity.RESULT_OK, i);
}
finish();
}
});
}
catch (IOException ex) {
Log.e("Error", ex.getMessage());
}
catch (Exception ex) {
Log.e("Error", ex.getMessage());
}
}
Firstly we will open the curtaindata.xml file present in asset folder using an Instance of DocumentBuilderFactory. A document class object doc is created by parsing the curtaindata.xml through DocumentBuilderFactory.
DocumentBuilderFactory docBuilderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder docBuilder = docBuilderFactory.newDocumentBuilder();
Document doc = docBuilder.parse (getAssets().open("curtaindata.xml"));
Now obtain all the xml nodes with tag <Curtain>
doc.getDocumentElement ().normalize ();
NodeList curtainList = doc.getElementsByTagName("Curtain");
The next part is really simple. Loop through every element of curtainList, read the element, put the children in a hashtable. Each row of the hash table is a key value pair. Keys are: KEY_NAME,KEY_CODE,KEY_GENERE,KEY_COMPOSITION,KEY_UTILITY,KEY_ICON. Their corresponding values are the associated values of the current sml node.
The entry associated with composition is :
compositionList = firstCurtainElement.getElementsByTagName(KEY_COMPOSITION);
firstCompositionElement = (Element)compositionList.item(0);
textCompositionList = firstCompositionElement.getChildNodes();
map.put(KEY_COMPOSITION, ((Node)textCompositionList.item(0)).getNodeValue().trim());
Rest other entries are similar. We now need to update one set of values in GlobalPlaceHolder. So check if it already contains a value. If not, add elements. When it already has elements, dont insert any other row.
if(GlobalPlaceholder.selectedColor.length()>2 && !GlobalPlaceholder.selectedColor.trim().equals(((Node)textColorList.item(0)).getNodeValue().trim()))
{
flagInsert=false;
}
if(updateDatabase)
GlobalPlaceholder.database.add(new CurtainDbClass(textCodeList.item(0).getNodeValue().trim(), " ", textGenereList.item(0).getNodeValue().trim(), textCompositionList.item(0).getNodeValue().trim(), textUtilityList.item(0).getNodeValue().trim(), textColorList.item(0).getNodeValue().trim()));
}
Now that we have our data row ready, create and instance of BinderData which will create one row of list_row for one row of XML data. As we want to create the whole set, simply send curtainDataCollection as argument to BinderData.
BinderData bindingData = new BinderData(this,curtainDataCollection);
list = (ListView) findViewById(R.id.list);
Log.i("BEFORE", "<<------------- Before SetAdapter-------------->>");
try{
Log.i("Data binding",bindingData.curtainDataCollection.get(0).toString());
list.setAdapter(bindingData);
}catch(Exception ex)
{
}
We now have our forms ready. The CurtainListActivity would return the name and code associated with the selected row. However we had
startActivity(intent1);
In MainActivity which starts the CurtainListActivity, we have to change the intent starting method to one that can fetch result from an Instance. Therefore updated code for onOptionsItemSelected method of MainActivity class becomes:
@Override
public boolean onOptionsItemSelected(MenuItem item) {
int id = item.getItemId();
switch(id)
{
case R.id.menuOpenList:
try{
Intent intent1 = new Intent();
intent1.setClass(this, CurtainListActivity.class);
Log.i("In Menu Browse","In Menu Browse");
startActivityForResult(intent1, FABRIC_SELECTION);
}catch(Exception ex)
{
}
break;
}
return super.onOptionsItemSelected(item);
}
If you have followed the steps correctly and have everything ready, you can now test your app. When you select the Open Curtain Form menu from main form, you will see your curtain lsit nicely presented.
You can scroll up and down in the list. When you select any item, you go back to main form. However we havn't yet implemented the logic for first window to show the selected icon.
Let us go ahead and finish our task. For changing the ImageView image in the run time we need to have a member variable mapped to the resource ImageView. Declare a member variable for that
ImageView imgView1=null;
Initialize this in onCreate method after the call of super using findViewById
imgView1=(ImageView)findViewById(R.id.imMainForm);
What you want to do is to write a handler which should be called whenever control comes back from another instance which was started for a result. For doing this, you need to update OnActivityResult.
As in your work you might use several Intents and you need to make a choice from which intent your control has come back, it is always wise to use Switch control statement. Remember we had started CurtainListActivity Intent with ID FABRIC_SELECTION. So the case id inside OnActivityResult will also be same.
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent imageReturnedIntent)
{
super.onActivityResult(requestCode, resultCode, imageReturnedIntent);
switch(requestCode)
{
case FABRIC_SELECTION:
try{
if(resultCode==RESULT_OK)
{
Bundle res = imageReturnedIntent.getExtras();
try{
String identifier=res.getString("icon");
identifier=identifier.split(".jpg")[0];
String uri = "drawable/"+ identifier;
int imageResource = getApplicationContext().getResources().getIdentifier(uri, null, getApplicationContext().getPackageName());
Log.i("While Displaying selected Image resource=", " "+imageResource );
Drawable image = getResources().getDrawable(imageResource);
imgView1.setImageDrawable(image);
imgView1.invalidate();
}catch(Exception ex)
{
}
}
}catch(Exception ex)
{}
break;
}
}
Remember we had put the image path name in "icon" parameter in the CurtainListActivity. So we obtain the selected image path from "icon" resource obtained from Bundle res object which is collected from the getExtra() method. Now we obtain the resource number and update the ImageVie's drawable.
Now when you run your application and select an image from list, you will see the image in your first form as shown in image below.
A note here: If you are using FrameLayout, image might not be updated. You can change the FrameLayout by copying the entire layout code inside fragment_.xml file to main_activity.xml file.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context="com.grasshoppernetwork.myimagelist.MainActivity$PlaceholderFragment" >
<ImageView
android:id="@+id/imMainForm"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
android:layout_alignParentTop="true"
android:layout_marginLeft="70dp"
android:layout_marginTop="43dp"
android:src="@drawable/ic_launcher" />
</RelativeLayout>
8. Conclusion
This tutorial was an effort to teach the fundamentals of Resource handling in Android in specific and menu, layout, string and other resources in general. I have found that simply putting the usage of different controls or concepts rarely teaches the minute issues associated with a workflow. In this tutorial I have covered the concepts in the context of an Already published app. I hope this tutorial helps you in understanding the concepts and also encourage you to develop this into a published app.