tag:blogger.com,1999:blog-83231212474542500992024-03-13T20:02:45.250-07:00Android & AmirAmirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.comBlogger25125tag:blogger.com,1999:blog-8323121247454250099.post-10111775720128302892011-01-15T23:19:00.000-08:002011-01-16T00:35:56.390-08:00Learn Android through some code...I have been pretty busy lately and didn't really get a chance to post anything new here, which is a big shame by the way...however I have had quite a few requests asking for the source code of <a href="http://android-journey.blogspot.com/2010/03/my-first-android-application.html">EbaySearch application</a> and <a href="http://android-journey.blogspot.com/2010/02/android-2d-simple-example.html">TicTacToe Game</a> that I have used to demonstrate 2D features in Android. so finally I decided to create a couple of projects in GitHub and put all the source codes there for anybody who might be interested. there are 2 separate repositories which can be found at : <br /><br />1- <a href="https://github.com/Amir-Github/Android_EbaySearch">EbaySearch</a><br />2- <a href="https://github.com/Amir-Github/Wacky-TicTacToe-for-Android">TicTacToe </a><br /><br />Please remember these codes have been written a long time ago(relatively) using Android SDK 5 (Android 1.6) and honestly I haven't tested them lately to make sure if they are still OK, consequently there is no guarantee that they work properly on newer versions, Although I doubt if there is any major problem getting them to work on newer versions unless something has been changed dramatically in newer versions and broken the code. <br />nonetheless I thought this would be helpful for anyone who prefers to learn Android through some real code rather than reading books and stuff or is just curious about the full source code of those applications which as it turn out lots of people are ;) <br />the last thing I'd like to add is although there is a lot to learn from these source codes, please don't forget that they have all been written for experimental purposes and they might not be the best possible way to go in some circumstances...Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com1tag:blogger.com,1999:blog-8323121247454250099.post-63392133897769198962010-09-15T16:59:00.000-07:002010-09-15T17:07:29.666-07:00Sprint Challenges Developers to Create Innovative 4G AppsI just received this E-mail, sounds like a great opportunity for anyone who is up for a little bit of challenge, I would definitely give it a go if I wasn't dead busy at the moment....<br /><br />here it goes: <br /><br />Developing mobile applications is very exciting. From developing the concept, building out wireframes, all the way to launch, seeing your creation come to life is very gratifying as a developer. Sprint recognizes this gratification, and wants to continue that feeling by inviting you and your blog audience to a unique app development contest. <br /><br />Sprint is leading the way with 4G speed, and wants to place some Android app developers in the lime light to showcase what they can do when 4G is thrown into the mix. Sprint, along with partners Wired, Reddit and Ars Technica have created the Sprint 4G App Challenge, a contest to develop 4G mobile applications to demonstrate the benefits and features of a 4G application.<br /><br />Below contains information regarding the contest<br /><br />All app submissions must fall within one of the following categories: <br /><br /> Video, Multimedia & Augmented Reality<br /><br /> Gaming<br /><br /> Productivity, Business & Utilities<br /><br /> Social Networking<br /><br /> Entertainment<br /><br /><br />Submission phase: Today through November 5th <br /><br /> <br />Category judging will take place in November by representatives from Wired, Reddit, ArsTechnica and Sprint, and the winners will be announced at the Wired Store on December 16th. <br /><br /> <br /><br />EACH CATEGORY WINNER RECEIVES:<br /><br /> $50,000<br /><br /> 1 year membership to Sprint’s Professional Developers Program<br /><br /> 250 hours for the Virtual Developer Lab<br /><br /> 1 Evo device with 1 year service<br /><br /> Press announcements promoting winning apps<br /><br /> <br /><br />For more information on developing and submitting an application into the Sprint 4G App Challenge, please visit: <a href="http://www.sprint.com/appchallenge">http://www.sprint.com/appchallenge</a>Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com0tag:blogger.com,1999:blog-8323121247454250099.post-40359123146862895142010-05-06T19:42:00.000-07:002010-05-06T20:06:58.140-07:00Ebay-Search UpgradedVersion 0.2 of <a href="http://android-journey.blogspot.com/2010/03/my-first-android-application.html">Ebay-Search application</a> has been published, this version will enable users to do more sophisticated searches using different Ebay search Filters such as Condition, Listing type, Available Country , etc...Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com0tag:blogger.com,1999:blog-8323121247454250099.post-69269150891279506912010-03-15T23:45:00.000-07:002010-03-16T01:52:47.630-07:00Android Layout ManagersSometimes There are some simple rules and facts which can easily improve the quality and performance of your applications but they are widely ignored either because of lack of knowledge or just because of our reluctance to change the way we're already doing stuff.<br />One of these basic principles in android is <a href="http://developer.android.com/resources/articles/layout-tricks-efficiency.html">Layout management</a> which is hugely being misused, I mean if you go through different android samples and blogs (including this Blog!) more than 70% of all samples and examples are using LinearLayout even for some very simple layout that could have been simply done by using a FrameLayout. I am no exception and actually I used to think if I am comfortable with LinearLayout and everybody else is using it, why should I use another layout? but the thing is LinearLayout is a pretty heavyweight LayoutManager compare to FrameLayout and a far less flexible layout compare to RelativeLayout (this inflexibility usually leads to one of those ugly layout files with 4 or 5 nested LinearLayout) and if you are developing an application with a GUI a bit more complicated than a basic TextField and Button (specially when we have a repeating object like what we have in a ListView),Switching from LinearLayout to FrameLayout or RelativeLayout can tangibly improve the responsiveness of your application.<br />Just to see that using FrameLayout or RelativeLayout is pretty much as easy and as straight-forward as LinearLayout is, I'm gonna show you Six simple examples which will hopefully give you the idea of how they work so that you are gonna be able to choose a more efficient layout when you are designing a page. <br />Here are those Six examples I was talking about, I have used Buttons for all subviews just for the sake of simplicity but it can obviously be any other type of View:<table><tr><td><div style="border: thin solid lightgray;width:250px;padding-top=0px;float:left"><div style="background-color:#000;height:25px;color:#FFF;text-align:center">1</div> <br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lqWNM-79b1Y/S58pyriF_9I/AAAAAAAAAKM/pIyfy6zSXcU/s1600-h/Untitled01.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 193px; height: 320px;" src="http://1.bp.blogspot.com/_lqWNM-79b1Y/S58pyriF_9I/AAAAAAAAAKM/pIyfy6zSXcU/s320/Untitled01.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5449120024797511634" /></a></div></td><br /><td><div style="border: thin solid lightgray;width:250px;padding-top=0px;float:left"><div style="background-color:#000;height:25px;color:#FFF;text-align:center">2</div><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/S58p2Z6VvZI/AAAAAAAAAKU/fCasTYYpJJs/s1600-h/Untitled02.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 194px; height: 320px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/S58p2Z6VvZI/AAAAAAAAAKU/fCasTYYpJJs/s320/Untitled02.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5449120088786845074" /></a></div></td><br /><td><div style="border: thin solid lightgray;width:250px;padding-top=0px;float:left"><div style="background-color:#000;height:25px;color:#FFF;text-align:center">3</div><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/S58p5tKAlLI/AAAAAAAAAKc/z7ebgx0-Y0k/s1600-h/Untitled03.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 194px; height: 320px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/S58p5tKAlLI/AAAAAAAAAKc/z7ebgx0-Y0k/s320/Untitled03.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5449120145492448434" /></a></div></td></tr><br /><tr><td><br /><div style="border: thin solid lightgray;width:250px;padding-top=0px;float:left"><div style="background-color:#000;height:25px;color:#FFF;text-align:center">4</div> <br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lqWNM-79b1Y/S58p9GkQQTI/AAAAAAAAAKk/THRn9Mzeysk/s1600-h/Untitled04.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 194px; height: 320px;" src="http://2.bp.blogspot.com/_lqWNM-79b1Y/S58p9GkQQTI/AAAAAAAAAKk/THRn9Mzeysk/s320/Untitled04.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5449120203853021490" /></a><br /></div></td><br /><td><br /><div style="border: thin solid lightgray;width:250px;padding-top=0px;float:left"><div style="background-color:#000;height:25px;color:#FFF;text-align:center">5</div> <br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/S58qAL6KU4I/AAAAAAAAAKs/hX3Hdxbammc/s1600-h/Untitled05.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 193px; height: 320px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/S58qAL6KU4I/AAAAAAAAAKs/hX3Hdxbammc/s320/Untitled05.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5449120256826692482" /></a><br /></div></td><br /><td><br /><div style="border: thin solid lightgray;width:250px;padding-top=0px;float:left"><div style="background-color:#000;height:25px;color:#FFF;text-align:center">6</div> <br /> <a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lqWNM-79b1Y/S58qDYG-S4I/AAAAAAAAAK0/4xOB-SVOimA/s1600-h/Untitled06.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 193px; height: 320px;" src="http://1.bp.blogspot.com/_lqWNM-79b1Y/S58qDYG-S4I/AAAAAAAAAK0/4xOB-SVOimA/s320/Untitled06.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5449120311641262978" /></a><br /></div></td><br /></tr></table><br /><br />and here is the XML source that generates each one of these layouts:<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><div style="background-color:#000;height:25px;color:#FFF;text-align:center">1</div> <br /><pre><code><?xml version="1.0" encoding="utf-8"?><br /><br /><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:layout_width="wrap_content"<br /> android:layout_height="fill_parent"<br /> android:paddingTop="30dip"<br /> android:background="@color/black"><br /><br /> <Button<br /> android:layout_width="fill_parent"<br /> android:layout_height="90dip"<br /> android:layout_alignParentTop="true"<br /> android:text="View 1"<br /> android:id="@+id/view1" /> <br /> <br /> <Button<br /> android:layout_width="fill_parent"<br /> android:layout_height="90dip"<br /> android:layout_below="@id/view1"<br /> android:text="View 2"<br /> android:id="@+id/view2" /> <br /> <br /> <Button<br /> android:layout_width="fill_parent"<br /> android:layout_height="90dip"<br /> android:layout_below="@id/view2"<br /> android:text="View 3"<br /> android:id="@+id/view3" /> <br /> <br /> <Button<br /> android:layout_width="fill_parent"<br /> android:layout_height="90dip"<br /> android:layout_below="@id/view3"<br /> android:text="View 4"<br /> android:id="@+id/view4" /> <br /> <br /> <Button<br /> android:layout_width="fill_parent"<br /> android:layout_height="90dip"<br /> android:layout_below="@id/view4"<br /> android:text="View 5"<br /> android:id="@+id/view5" /> <br /> <br /> <br /></RelativeLayout> </code></pre></div><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><div style="background-color:#000;height:25px;color:#FFF;text-align:center">2</div> <br /><pre><code><?xml version="1.0" encoding="utf-8"?><br /><br /><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:layout_width="fill_parent"<br /> android:layout_height="fill_parent"<br /> android:paddingTop="5dip"<br /> android:background="@color/black"><br /><br /> <Button<br /> android:layout_width="100dip"<br /> android:layout_height="fill_parent"<br /> android:layout_gravity="left"<br /> android:text="View 1"<br /> android:id="@+id/view1" /> <br /> <br /> <Button<br /> android:layout_width="100dip"<br /> android:layout_height="fill_parent"<br /> android:layout_gravity="center_horizontal"<br /> android:text="View 2"<br /> android:id="@+id/view2" /> <br /> <br /> <Button<br /> android:layout_width="100dip"<br /> android:layout_height="fill_parent"<br /> android:layout_gravity="right"<br /> android:text="View 3"<br /> android:id="@+id/view3" /> <br /> <br /> <br /></FrameLayout> </code></pre></div><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><div style="background-color:#000;height:25px;color:#FFF;text-align:center">3</div> <br /><pre><code><?xml version="1.0" encoding="utf-8"?><br /><br /><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:layout_width="fill_parent"<br /> android:layout_height="fill_parent"<br /> android:background="@color/black"><br /><br /> <Button<br /> android:layout_width="120dip"<br /> android:layout_height="120dip"<br /> android:layout_gravity="left|top"<br /> android:layout_margin="15dip"<br /> android:text="View 1"<br /> android:id="@+id/view1" /> <br /> <br /> <Button<br /> android:layout_width="120dip"<br /> android:layout_height="120dip"<br /> android:layout_gravity="right|top"<br /> android:layout_margin="15dip"<br /> android:text="View 2"<br /> android:id="@+id/view2" /> <br /> <br /> <Button<br /> android:layout_width="120dip"<br /> android:layout_height="120dip"<br /> android:layout_gravity="left|center_vertical"<br /> android:layout_marginLeft="15dip"<br /> android:text="View 3"<br /> android:id="@+id/view3" /> <br /> <br /> <Button<br /> android:layout_width="120dip"<br /> android:layout_height="120dip"<br /> android:layout_gravity="right|center_vertical"<br /> android:layout_marginRight="15dip" <br /> android:text="View 4"<br /> android:id="@+id/view4" /> <br /> <br /> <Button<br /> android:layout_width="120dip"<br /> android:layout_height="120dip"<br /> android:layout_gravity="left|bottom"<br /> android:layout_margin="15dip"<br /> android:text="View 5"<br /> android:id="@+id/view5" /> <br /> <br /> <Button<br /> android:layout_width="120dip"<br /> android:layout_height="120dip"<br /> android:layout_gravity="right|bottom"<br /> android:layout_margin="15dip"<br /> android:text="View 6"<br /> android:id="@+id/view6" /> <br /> <br /> <br /></FrameLayout> </code></pre></div><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><div style="background-color:#000;height:25px;color:#FFF;text-align:center">4</div> <br /><pre><code><?xml version="1.0" encoding="utf-8"?><br /><br /><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:layout_width="fill_parent"<br /> android:layout_height="fill_parent"<br /> android:padding="5dip"<br /> android:background="@color/black"><br /><br /> <Button<br /> android:layout_width="150dip"<br /> android:layout_height="120dip"<br /> android:layout_gravity="left|top"<br /> android:text="View 1"<br /> android:id="@+id/view1" /> <br /> <br /> <Button<br /> android:layout_width="150dip"<br /> android:layout_height="120dip"<br /> android:layout_gravity="right|top"<br /> android:text="View 2"<br /> android:id="@+id/view2" /> <br /> <br /> <br /> <Button<br /> android:layout_width="fill_parent"<br /> android:layout_height="200dip"<br /> android:layout_gravity="center"<br /> android:text="View 3"<br /> android:id="@+id/view3" /> <br /> <br /> <br /> <Button<br /> android:layout_width="150dip"<br /> android:layout_height="120dip"<br /> android:layout_gravity="left|bottom"<br /> android:text="View 4"<br /> android:id="@+id/view4" /> <br /> <br /> <Button<br /> android:layout_width="150dip"<br /> android:layout_height="120dip"<br /> android:layout_gravity="right|bottom"<br /> android:text="View 5"<br /> android:id="@+id/view5" /> <br /> <br /> <br /></FrameLayout> </code></pre></div><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><div style="background-color:#000;height:25px;color:#FFF;text-align:center">5</div> <br /><pre><code><?xml version="1.0" encoding="utf-8"?><br /><br /><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:layout_width="fill_parent"<br /> android:layout_height="fill_parent"<br /> android:padding="5dip"<br /> android:background="@color/black"><br /><br /> <Button<br /> android:layout_width="120dip"<br /> android:layout_height="200dip"<br /> android:layout_alignParentTop="true"<br /> android:layout_alignParentLeft="true"<br /> android:text="View 1"<br /> android:id="@+id/view1" /> <br /> <br /> <Button<br /> android:layout_width="180dip"<br /> android:layout_height="120dip"<br /> android:layout_alignParentTop="true"<br /> android:layout_alignParentRight="true"<br /> android:text="View 2"<br /> android:id="@+id/view2" /> <br /> <br /> <br /> <Button<br /> android:layout_width="120dip"<br /> android:layout_height="100dip"<br /> android:layout_alignParentLeft="true"<br /> android:layout_below="@id/view1"<br /> android:text="View 3"<br /> android:id="@+id/view3" /> <br /> <br /> <br /> <Button<br /> android:layout_width="180dip"<br /> android:layout_height="180dip"<br /> android:layout_alignParentRight="true"<br /> android:layout_below="@id/view2"<br /> android:text="View 4"<br /> android:id="@+id/view4" /> <br /> <br /> <Button<br /> android:layout_width="fill_parent"<br /> android:layout_height="120dip"<br /> android:layout_alignParentBottom="true"<br /> android:layout_below="@id/view4"<br /> android:text="View 5"<br /> android:id="@+id/view5" /> <br /> <br /> <br /></RelativeLayout> </pre></code></div><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><div style="background-color:#000;height:25px;color:#FFF;text-align:center">6</div> <br /><pre><code><?xml version="1.0" encoding="utf-8"?><br /><br /><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:layout_width="fill_parent"<br /> android:layout_height="fill_parent"<br /> android:padding="5dip"<br /> android:background="@color/black"><br /><br /> <Button<br /> android:layout_width="180dip"<br /> android:layout_height="180dip"<br /> android:layout_alignParentTop="true"<br /> android:layout_alignParentRight="true"<br /> android:text="View 1"<br /> android:id="@+id/view1" /> <br /> <br /> <Button<br /> android:layout_width="120dip"<br /> android:layout_height="90dip"<br /> android:layout_toLeftOf="@id/view1"<br /> android:layout_alignParentLeft="true"<br /> android:text="View 2"<br /> android:id="@+id/view2" /> <br /> <br /> <br /> <Button<br /> android:layout_width="120dip"<br /> android:layout_height="90dip"<br /> android:layout_toLeftOf="@id/view1"<br /> android:layout_below="@id/view2"<br /> android:layout_alignParentLeft="true"<br /> android:text="View 3"<br /> android:id="@+id/view3" /> <br /> <br /> <br /> <Button<br /> android:layout_width="fill_parent"<br /> android:layout_height="120dip"<br /> android:layout_below="@id/view1"<br /> android:text="View 4"<br /> android:id="@+id/view4" /> <br /> <br /> <Button<br /> android:layout_width="180dip"<br /> android:layout_height="180dip"<br /> android:layout_alignParentLeft="true"<br /> android:layout_below="@id/view4"<br /> android:text="View 5"<br /> android:id="@+id/view5" /> <br /> <br /> <br /> <Button<br /> android:layout_width="120dip"<br /> android:layout_height="90dip"<br /> android:layout_toRightOf="@id/view5"<br /> android:layout_below="@id/view4"<br /> android:layout_alignParentRight="true"<br /> android:text="View 6"<br /> android:id="@+id/view6" /> <br /> <br /> <br /> <Button<br /> android:layout_width="120dip"<br /> android:layout_height="90dip"<br /> android:layout_toRightOf="@id/view5"<br /> android:layout_below="@id/view6"<br /> android:layout_alignParentRight="true"<br /> android:text="View 7"<br /> android:id="@+id/view7" /> <br /> <br /> <br /></RelativeLayout> </code></pre></div>Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com10tag:blogger.com,1999:blog-8323121247454250099.post-66197641992829131832010-03-12T22:13:00.000-08:002010-03-16T02:02:58.489-07:00My First Android Application ;)I was struggling with myself for a long time whether to do this or not, and finally I did it.<br />I uploaded <a href="http://www.androidpit.com/en/android/market/apps/app/com.amir.ebay/Ebay-Search">my first Android application</a> in Market yesterday and actually I feel excited about it...<br />It is not a full-feature application, just a simple Ebay-Search client which will let you search through Items in Ebay...no buying or bidding yet!!<br />since unfortunately I don't have any Android-enabled phone at the moment so I would really appreciate it if you could download this application, give it a go and let me know how it works on a real device.<br />Here's some screen-shots of my application :<br /><table><tbody><tr><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lqWNM-79b1Y/S5sttjjXd8I/AAAAAAAAAJs/3iggjjFUXog/s1600-h/Untitled01.jpg"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 191px; height: 320px;" src="http://3.bp.blogspot.com/_lqWNM-79b1Y/S5sttjjXd8I/AAAAAAAAAJs/3iggjjFUXog/s320/Untitled01.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5447998434895689666" /></a><br /></td><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lqWNM-79b1Y/S5stzAdk7UI/AAAAAAAAAJ0/bg2uJ8y81d4/s1600-h/Untitled02.jpg"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 195px; height: 320px;" src="http://2.bp.blogspot.com/_lqWNM-79b1Y/S5stzAdk7UI/AAAAAAAAAJ0/bg2uJ8y81d4/s320/Untitled02.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5447998528555380034" /></a><br /></td><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lqWNM-79b1Y/S5st3EQlobI/AAAAAAAAAJ8/FiK2vDyTZ9U/s1600-h/Untitled03.jpg"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 194px; height: 320px;" src="http://3.bp.blogspot.com/_lqWNM-79b1Y/S5st3EQlobI/AAAAAAAAAJ8/FiK2vDyTZ9U/s320/Untitled03.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5447998598294118834" /></a><br /></td><br /></tr><tr><td colspan="3"><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/S5st8MjaseI/AAAAAAAAAKE/6zQpsz84YBU/s1600-h/Untitled04.jpg"><img style="float:center; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 320px; height: 194px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/S5st8MjaseI/AAAAAAAAAKE/6zQpsz84YBU/s320/Untitled04.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5447998686419923426" /></a><br /></td><br /></tr></tbody></table>Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com5tag:blogger.com,1999:blog-8323121247454250099.post-14189934503409630822010-02-15T16:08:00.000-08:002010-02-15T17:35:50.538-08:00Android 2D ( A Simple Example )It has been a long time I wanted to go through Android 2D capabilities and see how that works. and after a little bit of reading and researching about the topic, I developed a simple TicTacToe game which I'm gonna share it with you in this article.<br />The primary purpose of this example is to dig up some basic Android 2D features and get familiar with some game programming principles. Although TicTacToe is not really an interactive game, I think its simplicity will help us to be focused on what matters most which is actually how to do things in Android rather than how to design and develop an efficient game algorithm for a particular game. (I'm gonna use the same technique that has been used in <a href="http://developer.android.com/resources/samples/LunarLander/index.html">LunarLanda</a>r application, which by the way is a pretty good example in this area).<br />first of all let's have a look at how our application will be looking like:<br /><center><table><tbody><tr><br /><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/S3n0s2mdXpI/AAAAAAAAAII/V5ailOofYpo/s1600-h/Untitled_01.jpg"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 192px; height: 320px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/S3n0s2mdXpI/AAAAAAAAAII/V5ailOofYpo/s320/Untitled_01.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5438647076434828946" /></a><br /></td><br /><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/S3n00NSQo9I/AAAAAAAAAIQ/6WeaOkMpXlU/s1600-h/Untitled_02.jpg"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 320px; height: 193px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/S3n00NSQo9I/AAAAAAAAAIQ/6WeaOkMpXlU/s320/Untitled_02.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5438647202783208402" /></a><br /></td><br /></tr></tbody></table></center><br />Despite the fact that TicTacToe is a pretty easy game to develop, Graphic wise I mean, and can be simply done by extending View Class, I have actually used 'SurfaceView', since apparently it's a better option for developing interactive games in Android. the most important point about SurfaceView class is that it uses two buffers, a drawing buffer and a displaying buffer, you are not allowed to draw directly on displaying buffer and if you need to draw something you will have to get the drawing buffer , fill it and post it as display buffer. as API documentation has mentioned there is no guarantee that anything gets preserved in drawing buffer and as a result we always need to draw anything we want to be shown without taking any presumption that something is already there from last drawing operation.<br />OK... now that we learned some basic facts about how SurfaceView works, let's have a look at my code and see what we have got there : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><br />public class TicTacToeView extends SurfaceView implements SurfaceHolder.Callback, OnTouchListener {<br /> <br /> .<br /> .<br /> .<br /> <br /> public TicTacToeView(Context context) {<br /> super(context);<br /> getHolder().addCallback(this);<br /> }<br /> <br /> public TicTacToeView(Context context,TicTacToe internalState) {<br /> this(context);<br /> this.tictactoe = internalState;<br /> }<br /> <br /> .<br /> .<br /> .<br /> <br /> class MainThread extends Thread {<br /> <br /> private SurfaceHolder surfaceHolder;<br /> private boolean runFlag = false;<br /> boolean firstTime = true;<br /> <br /> public MainThread(SurfaceHolder surfaceHolder) {<br /> this.surfaceHolder = surfaceHolder;<br /> }<br /> <br /> public void setRunning(boolean run) {<br /> this.runFlag = run;<br /> }<br /> <br /> @Override<br /> public void run() {<br /> Canvas c;<br /> <br /> while (this.runFlag) {<br /> <br /> if(firstTime){<br /> drawLines();<br /> firstTime = false;<br /> continue;<br /> }<br /> <br /> c = null;<br /> try {<br /> <br /> c = this.surfaceHolder.lockCanvas(null);<br /> synchronized (this.surfaceHolder) { <br /> doDraw(c);<br /> updateScores(c);<br /> }<br /> } finally {<br /> <br /> if (c != null) {<br /> this.surfaceHolder.unlockCanvasAndPost(c);<br /> <br /> }<br /> }<br /> }<br /> }<br /> <br /> .<br /> .<br /> .<br /> <br /> }<br /><br /> .<br /> .<br /> .<br />}<br /></pre></div><br />The first thing we're gonna need in any game is a Game Thread, The idea here is to constantly call a series of methods which are responsible to update some states and draw something based on those states. as I said before when you are dealing with SurfaceView you need to draw into the draw buffer and then post it on the surface, It can be done by calling lockCanvas() and unlockCanvasAndPost() methods of SurfaceHolder class, you can get the associated SurfaceHolder instance of your SurfaceView by calling getHolder() method. as you can see in the above code I have implemented the SurfaceHolder.Callback interface; this interface provides 3 callback methods for monitoring the life-cycle of the SurfaceView, here is my implementation of these methods:<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding-left:5px;padding-right:5px;padding-top:0px;padding-bottom:2px;"><br /><pre><br /> .<br /> .<br /> .<br /><br /> @Override<br /> public void surfaceCreated(SurfaceHolder holder) {<br /> <br /> _thread = new MainThread(getHolder()); <br /> <br /> if(tictactoe == null) <br /> this.tictactoe = new TicTacToe(new TicTacToeHandler(),false);<br /> else<br /> _thread.firstTime = false;<br /> <br /> Resources resources = getContext().getResources(); <br /> <br /> this.background = BitmapFactory.decodeResource(resources, R.drawable.background);<br /> this.X_Player = BitmapFactory.decodeResource(resources, R.drawable.x_pic);<br /> this.O_Player = BitmapFactory.decodeResource(resources, R.drawable.o_pic);<br /> <br /> <br /> Rect rect = holder.getSurfaceFrame();<br /> this.gameTable_height = rect.height()-56;<br /> this.gameTable_sY = 0;<br /> this.gameTable_width = rect.width();<br /> this.scoreBox_Height = 50;<br /> this.scoreBox_Width = rect.width();<br /> this.scoreBox_sY = rect.height()-this.scoreBox_Height;<br /> this.squares = new Rect[9];<br /> <br /> final int square_Width = (int)(gameTable_width-(TABLE_BORDER*4))/3;<br /> final int square_Height = (int)(gameTable_height-(TABLE_BORDER*4))/3; <br /> <br /> for(int i=0;i<9;i++){<br /> int row = i%3;<br /> int column = i/3;<br /> int left = ((row+1)*TABLE_BORDER)+(square_Width*row);<br /> int top = ((column+1)*TABLE_BORDER)+(square_Height*column);<br /> this.squares[i] = new Rect(left,top,left+square_Width,top+square_Height); <br /> <br /> }<br /> <br /> this.tablePaint.setStyle(Style.STROKE);<br /><br /> this.tablePaint.setShader(new LinearGradient(0, 0, this.gameTable_height,<br /> this.gameTable_width, <br /> 0xFF000000, <br /> 0xFF343434, <br /> TileMode.MIRROR));<br /> this.tablePaint.setStrokeWidth(TABLE_BORDER);<br /> this.tablePaint.setAlpha(0xCC);<br /> <br /> this.boxPaint.setStyle(Style.STROKE);<br /> this.boxPaint.setStrokeWidth(BOX_BORDER);<br /> this.boxPaint.setColor(0xFFA90000);<br /> <br /> this.textPaint.setColor(0xFF000000);<br /> this.textPaint.setTextAlign(Align.CENTER);<br /> this.textPaint.setTextSize(14);<br /> this.textPaint.setTypeface(Typeface.createFromAsset(getContext().getResources().getAssets(),"HARLOWSI.TTF"));<br /> <br /> this.contentPaint.setAlpha(0xDF);<br /> <br /> _thread.setRunning(true);<br /> _thread.start();<br /> setOnTouchListener(this);<br /> <br /> }<br /> <br /> @Override<br /> public void surfaceDestroyed(SurfaceHolder holder) {<br /> <br /> boolean retry = true;<br /> _thread.setRunning(false);<br /> while (retry) {<br /> try {<br /> _thread.join();<br /> retry = false;<br /> } catch (InterruptedException e) {<br /> <br /> }<br /> }<br /> }<br /> <br /> <br /> @Override<br /> public void surfaceChanged(SurfaceHolder holder, int format, int width,int height) {<br /> }<br /> .<br /> .<br /> .<br /></pre></div><br />in surfaceCreated() method I have initialized some variables and objects such as an instance of TicTacToe class (which will actually take care of game's logic and rules), background Image , cross and circle images and Paint objects that all will be used later to draw the game, here is also a good place to create and start our main thread. then we will kill the main thread in surfaceDestroyed() method to make sure no one's gonna make an attempt to draw anything on the surface after it has been destroyed. <br />now that we have got anything initialized and the main thread running let's see what is happening in each loop of our thread, as you saw in the first chunk of code above we are actually calling 2 methods each time, doDraw() and updateScores() : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><br /> .<br /> .<br /> .<br /><br /> public void doDraw(Canvas canvas) {<br /><br /> canvas.drawBitmap(this.background, 0, 0, null);<br /> drawTable(canvas);<br /> <br /> if(this.draw && Xs[0] == Xs[1] && Ys[0] == Ys[1]){<br /> CheckContent();<br /> }<br /> drawContent(canvas);<br /><br /> }<br /> <br /> /////////<br /> private void updateScores(Canvas canvas){<br /><br /> final int halfBorder = (int)BOX_BORDER/2;<br /> final Paint paint = textPaint;<br /> <br /> Rect rect = new Rect(halfBorder,<br /> this.scoreBox_sY-halfBorder,<br /> this.scoreBox_Width-halfBorder,<br /> (this.scoreBox_sY-halfBorder)+this.scoreBox_Height);<br /> RectF rectf = new RectF(rect);<br /><br /> <br /> canvas.drawRoundRect(rectf, 15, 15, this.boxPaint);<br /> <br /> canvas.drawText("Your Score : "+this.tictactoe.getYourScore(), 65,this.scoreBox_sY+25,paint);<br /> canvas.drawText("Computer's Score : "+this.tictactoe.getOpponentScore(), 15+(this.scoreBox_Width/3)*2,this.scoreBox_sY+25,paint);<br /> <br /> } <br /> <br /> <br /> private void drawTable(Canvas canvas){<br /> <br /> final Paint paint = this.tablePaint;<br /> final float border = TABLE_BORDER;<br /> <br /> final int cellHeight = (int)(this.gameTable_height-(2*border))/3;<br /> final int cellWidth = (int)(this.gameTable_width-(2*border))/3;<br /> <br /> final float table_eX = this.gameTable_width-border;<br /> final float table_eY = this.gameTable_height-border;<br /> <br /> canvas.drawLine(this.gameTable_sY+border, cellHeight+border, table_eX, cellHeight+border, paint);<br /> canvas.drawLine(this.gameTable_sY+border, (cellHeight*2)+border, table_eX, (cellHeight*2)+border, paint);<br /> <br /> canvas.drawLine(cellWidth+border, border, cellWidth+border, table_eY, paint);<br /> canvas.drawLine((cellWidth*2)+border, border, (cellWidth*2)+border, table_eY, paint);<br /> <br /> <br /> paint.setPathEffect(new CornerPathEffect(20));<br /> canvas.drawRect(border/2, border/2, this.gameTable_width-(border/2), <br /> this.gameTable_height-(border/2), <br /> paint);<br /> <br /> paint.setPathEffect(null);<br /> }<br /> <br /> <br /> private void CheckContent(){<br /> <br /> for(int i=0;i<9;i++){<br /> if(this.squares[i].contains(Xs[1], Ys[1])){<br /> this.tictactoe.move_Request(i);<br /> <br /> this.draw = false; <br /> return; <br /> } <br /> }<br /> }<br /> <br /> <br /> private void drawContent(Canvas canvas){<br /> <br /> <br /> for(int i=0;i<9;i++){<br /> int squareContent = this.tictactoe.getContent(i); <br /> if(squareContent == TicTacToe.X_PLAYER)<br /> canvas.drawBitmap(this.X_Player,null,this.squares[i], this.contentPaint); <br /> else if(squareContent == TicTacToe.O_PLAYER)<br /> canvas.drawBitmap(this.O_Player,null,this.squares[i], this.contentPaint); <br /> <br /> } <br /> }<br /><br /> @Override<br /> public boolean onTouch(View v, MotionEvent event) {<br /> <br /> if(event.getAction() == MotionEvent.ACTION_DOWN){ <br /> this.Xs[0] = (int)event.getX();<br /> this.Ys[0] = (int)event.getY();<br /> } <br /> else if(event.getAction() == MotionEvent.ACTION_UP){<br /> this.Xs[1] = (int)event.getX();<br /> this.Ys[1] = (int)event.getY();<br /> <br /> this.draw = true;<br /> }<br /> <br /> return true;<br /> }<br /> .<br /> .<br /> .<br /></pre></div><br />The Last thing I would like to handle is the ability to keep the state of the game when user flips the phone and goes to landscape mode or vice versa, so we are gonna have to save current state of the game just before application get killed and then retrieve that state later when the activity get started again which mean our activity class will be something like this:<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><br />public class TicTacToeActivity extends Activity{<br /> <br /> private TicTacToeView view;<br /> <br /> @Override<br /> public void onCreate(Bundle savedInstanceState) {<br /> super.onCreate(savedInstanceState);<br /> requestWindowFeature(Window.FEATURE_NO_TITLE);<br /> <br /> TicTacToe oldTTT = (TicTacToe)getLastNonConfigurationInstance();<br /> if(oldTTT != null)<br /> this.view = new TicTacToeView(this, oldTTT);<br /> else<br /> this.view = new TicTacToeView(this);<br /> <br /> setContentView(this.view);<br /> }<br /><br /> @Override<br /> public Object onRetainNonConfigurationInstance(){<br /> return this.view.getInternalState();<br /> }<br /> <br />}<br /></pre></div><br />getInternalState() method returns the associated TicTacToe object of the TicTacToeView, as I said before TicTacToe class is just a simple class that holds the game matrix and players' scores and actually that's all we need to save and retrieve each time the configuration gets changed in this example.<br />I also though it would be interesting to start the game with some sort of animation so I wrote a piece of code to have the game table being drawn when you start the application, something like this I mean : <br /><center><table><tbody><tr><br /><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lqWNM-79b1Y/S3n09jjoTjI/AAAAAAAAAIY/_oPUA3HNHyM/s1600-h/Untitled_03.jpg"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 198px; height: 320px;" src="http://2.bp.blogspot.com/_lqWNM-79b1Y/S3n09jjoTjI/AAAAAAAAAIY/_oPUA3HNHyM/s320/Untitled_03.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5438647363380465202" /></a><br /></td><br /><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lqWNM-79b1Y/S3n1C0TU17I/AAAAAAAAAIg/JgbhWvrKOF0/s1600-h/Untitled_04.jpg"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 192px; height: 320px;" src="http://3.bp.blogspot.com/_lqWNM-79b1Y/S3n1C0TU17I/AAAAAAAAAIg/JgbhWvrKOF0/s320/Untitled_04.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5438647453774829490" /></a><br /></td><br /></tr></tbody></table></center><br />and here is the code that generates this animation:<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><br /> .<br /> .<br /> .<br /><br /> public void run(){<br /> final int length = lines.length; <br /> for(int i=0;i<length;i++)<br /> drawLine(i); <br /> }<br /> <br /> private void drawLine(int index){<br /> <br /> final Line line = this.lines[index];<br /> <br /> final float addingPortion_X = ((line.eX - line.sX) / DRAW_PER_LINE);<br /> final float addingPortion_Y = ((line.eY - line.sY) / DRAW_PER_LINE); <br /> <br /> for(int i=0;i<DRAW_PER_LINE;i++){<br /> <br /> Canvas canvas = this.holder.lockCanvas(null);<br /> canvas.drawBitmap(background, 0, 0, null);<br /> for(int j=0;j<index;j++)<br /> canvas.drawLine(this.lines[j].sX,<br /> this.lines[j].sY, <br /> this.lines[j].eX, <br /> this.lines[j].eY,<br /> this.paint); <br /> <br /> canvas.drawLine(line.sX,<br /> line.sY, <br /> line.sX+(addingPortion_X*(i+1)), <br /> line.sY+(addingPortion_Y*(i+1)),<br /> this.paint);<br /> <br /> this.holder.unlockCanvasAndPost(canvas); <br /> <br /> } <br /><br /> .<br /> .<br /> . <br /></pre></div><br />I'm not sure if it's the best way to do this but it works just like i want it to work ;). (since just calling lockCanvas() and unlockCanvasAndPost() alone takes something around 200ms on my emulator - which is of course ridiculous but i have no idea why! - I didn't need to add any delay but on real device it will probably be needed to have some delay).Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com7tag:blogger.com,1999:blog-8323121247454250099.post-44520465808066157862010-01-30T23:16:00.000-08:002010-01-31T04:34:15.558-08:00Android WebViewAndroid WebView enables us to display web pages and interact with web content, there are several reasons that you might want to use WebView in your applications, but i think one the most interesting features of WebView is JavaScript integration capability. in this article I'm gonna show you a simple example of how easy it is to call javascript functions from your java code and vice versa.<br />first of all let's see how our example will be looking like :<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/S2UvbMDo9YI/AAAAAAAAAG4/2CDCj3PFWfY/s1600-h/Untitled01.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 192px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/S2UvbMDo9YI/AAAAAAAAAG4/2CDCj3PFWfY/s320/Untitled01.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5432800669631837570" /></a><br />it's actually just a WebView which is displaying a local Html page, this html page contains a java script slideshow component, I've actually used the very same code that i found <a href="http://www.dynamicdrive.com/dynamicindex17/featuredcontentglider.htm">over here</a>. the original javascript component has two button which can be used to slide into the next or previous page, but what I wanted was to be able to slide through pages using Swiping Gestures, Like...Swipe Left...go to next page, Swipe Right ...previous page.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lqWNM-79b1Y/S2UvjSAAvVI/AAAAAAAAAHA/Ey-zrUyjTAY/s1600-h/Untitled02.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 193px;" src="http://1.bp.blogspot.com/_lqWNM-79b1Y/S2UvjSAAvVI/AAAAAAAAAHA/Ey-zrUyjTAY/s320/Untitled02.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5432800808666185042" /></a><br />There are two javaScript functions , moveBackward() which replaces the current Item with previous one using a left to right sliding effect and moveForward() which does the exact opposite thing. all we need to do is to detect user Gestures, if it is a right to left gesture (swipe left) we will call moveForward()function and if it is a swipe right gesture we will call moveBackward() function, in this example I've used SimpleGestureFilter class (see my <a href="http://android-journey.blogspot.com/2010/01/android-gestures.html">last post</a>) and here is our onSwipe method implementation:<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /> @Override<br /> public void onSwipe(int direction) {<br /> <br /> <br /> switch (direction) {<br /> <br /> case SimpleGestureFilter.SWIPE_RIGHT : webView.loadUrl("javascript:moveBackward()");<br /> break;<br /> case SimpleGestureFilter.SWIPE_LEFT : webView.loadUrl("javascript:moveForward()");<br /> break;<br /> case SimpleGestureFilter.SWIPE_DOWN : <br /> case SimpleGestureFilter.SWIPE_UP : <br /> <br /> } <br /> }<br /></code></pre><br /></div><br />It's much easier than you though it would be, isn't it? we just need to pass the name of our javascript function (with 'javascript:' prefix) to loadUrl() method of the WebView instance.<br />as you can see in the pictures above, we have 3 buttons in our html form 'Add', 'Remove' and 'Report', we also have a Vector of Strings in our Activity; when 'Add' button is pressed the name of the current item will be sent to our activity and got stored in the Vector, when 'Remove' button is pressed the name of current item will be removed from Vector and in both cases a message will be shown afterwords that the operation has just been done :<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/S2UwQHdwYzI/AAAAAAAAAHI/Cl0ywSD6sz0/s1600-h/Untitled03.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 194px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/S2UwQHdwYzI/AAAAAAAAAHI/Cl0ywSD6sz0/s320/Untitled03.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5432801578932265778" /></a><br />if user presses the "Report" button a javascript popup box will be shown which says how many items are currently stored in our Vector: <br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lqWNM-79b1Y/S2UxXKePR1I/AAAAAAAAAHg/e2WEaEPA8i8/s1600-h/Untitled05.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 320px; height: 193px;" src="http://3.bp.blogspot.com/_lqWNM-79b1Y/S2UxXKePR1I/AAAAAAAAAHg/e2WEaEPA8i8/s320/Untitled05.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5432802799510308690" /></a><br />here is the html and javascript code that is being used to render those 3 buttons:<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><pre><br /> .<br /> .<br /> .<br />var items = ["British Columbia", "Ontario", "Yukon","New Brunswick"];<br /><br />function report(){<br /><br /> var contentStr = window.interface.getReportContent();<br /><br /> var contentDiv = document.getElementById("showBoxContent"); <br /> contentDiv.innerHTML = contentStr;<br /> showBox();<br />}<br /> .<br /> .<br /> .<br /><input type="button" onclick="window.interface.add(items[configParams.selected])" value="Add"/><br /><input type="button" onclick="window.interface.remove(items[configParams.selected])" value="Remove"/><br /><input type="button" onclick="report()" value="Report"/><br /> .<br /> .<br /> .<br /></pre></div><br /><br />and here is our Activity source code :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />public class WebViewSample extends Activity implements SimpleGestureListener{<br /><br /> private Handler handler = new Handler();<br /> private WebView webView;<br /> private SimpleGestureFilter filter;<br /> private Vector<String> provinces = new Vector<String>();<br /><br /> @Override<br /> public void onCreate(Bundle icicle) {<br /> super.onCreate(icicle);<br /> setContentView(R.layout.webview_layout);<br /> webView = (WebView) findViewById(R.id.webview);<br /> <br /> WebSettings webSettings = webView.getSettings();<br /> webSettings.setSaveFormData(false);<br /> webSettings.setJavaScriptEnabled(true);<br /><br /> this.filter = new SimpleGestureFilter(this,this);<br /> this.filter.setMode(SimpleGestureFilter.MODE_TRANSPARENT);<br /><br /> webView.addJavascriptInterface(new MyJavaScriptInterface(), "interface");<br /> <br /> webView.loadUrl("file:///android_asset/test.html");<br /> }<br /><br /> @Override <br /> public boolean dispatchTouchEvent(MotionEvent me){ <br /> this.filter.onTouchEvent(me);<br /> return super.dispatchTouchEvent(me); <br /> }<br /><br /> @Override<br /> public void onSwipe(int direction) {<br /> <br /> switch (direction) {<br /> <br /> case SimpleGestureFilter.SWIPE_RIGHT : webView.loadUrl("javascript:moveBackward()");<br /> break;<br /> case SimpleGestureFilter.SWIPE_LEFT : webView.loadUrl("javascript:moveForward()");<br /> break;<br /> case SimpleGestureFilter.SWIPE_DOWN : <br /> case SimpleGestureFilter.SWIPE_UP : <br /> <br /> } <br /> }<br /> <br /> <br /> @Override<br /> public void onDoubleTap() { <br /> }<br /> <br /> <br /> private void addOrRemove(String name,boolean add){<br /> <br /> String msg = null; <br /> boolean alreadyAdded = this.provinces.contains(name);<br /> <br /> if(add){<br /> if(!alreadyAdded){ <br /> this.provinces.add(name);<br /> msg = name+" Has just been added to the List.";<br /> }<br /> else<br /> msg = name+" Has already been added to the list."; <br /> } <br /> else{<br /> if(alreadyAdded){ <br /> this.provinces.remove(name);<br /> msg = name+" Has just been removed from the List.";<br /> }<br /> else<br /> msg = name+" has never been added to the list!"; <br /> }<br /> <br /> Toast.makeText(this, msg, Toast.LENGTH_LONG).show();<br /> }<br /> <br /> <br /> final class MyJavaScriptInterface {<br /><br /> MyJavaScriptInterface() {<br /> }<br /> <br /> public void add(final String name) {<br /> handler.post(new Runnable() {<br /> public void run() {<br /> addOrRemove(name, true);<br /> }<br /> });<br /> }<br /> <br /> public void remove(final String name) {<br /> handler.post(new Runnable() {<br /> public void run() {<br /> addOrRemove(name, false);<br /> }<br /> });<br /> }<br /> <br /> public String getReportContent() {<br /> if(provinces.size() == 0)<br /> return "<i> The list is empty. </i>";<br /> <br /> String content = "<i>You have added these provinces into the list :<i><br/><br/>";<br /> for(int i=0;i<provinces.size();i++)<br /> content += (i+1)+"- "+provinces.get(i)+"<br/>"; <br /> <br /> return content;<br /> }<br /> <br /> } <br /><br />}<br /></code></pre><br /></div><br />as you can see all we have is a simple WebView instance which displays the content of 'test.html' file that is located in assets directory of our project.<br /> <br />we've called addJavascriptInterface() method of our WebView instance in order to be able to call java methods in javascript code this method has two parameters, the first one is an Object methods of which we want to call in our javascript code and the second parameter is an String which will be used as the name of that java object in javascript context. once addJavascriptInterface() method is called the specified java object will be attached to the Html document's window object and can be referred by its name.<br />Remember that when methods of the passed java object is called in javascript context, it's not gonna invoked in the Main(UI) thread, and if you need to get it called in the UI thread you will need to use a Handler and post a new runnable to run your code, just like what I've done for add() and remove() methods in this example (though it is not necessary in this example).<br />another important thing to remember is the fact that you cannot pass or return any complex object to or from javascript context, that is all you are allowed to use are primitive and String data types.Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com13tag:blogger.com,1999:blog-8323121247454250099.post-69871559426879503232010-01-19T01:55:00.000-08:002010-02-17T16:02:14.144-08:00Android GesturesLike it or not touch screens are becoming part of both developers and users life, i mean dont think i would buy one of Those uncool phones without touchscreen unless i really have to, would you? but the important point is that what is the Touchscreen use if applications doesn't support touchscreen interaction, in other words who is really gonna pay for a ,say, Picture management application if it does not support some gestures for switching between pictures or zooming? <font color="white">[ Gesture Detection in Android ].</font><br />In Android there are three levels of touch screen event handling mechanism which can be used by developers. the most low level technique is to receive all touch events and take care of all things, you can attach an 'OnTouchListener' to a view and get notified whenever there is a touch event or you can override onTouchEvent() or dispatchTouchEvent() method of your activity or view, in all these cases you would be dealing with an instance of MotionEvent every single time and you would have to detect what user is doing all on your own which will suit requirments for developing games and stuff like that. But it is just too much hassle if you only need a few simple gestures for your application.<br />Next approach is to use GestureDetector class along with OnGestureListener and/or OnDoubleTapListener, in this technique whenever there is a new MotionEvent you have to pass it to Gesture Detector's onTouchEvent() method, it then will analyse this event and previous events and tell you what is happening on the screen by calling some of the callback methods.<br />here is a simple activity which uses GestureDetector : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />public class SimpleActivity extends Activity implements OnGestureListener,<br /> OnDoubleTapListener{<br /> <br /> private GestureDetector detector; <br /> <br /> /** Called when the activity is first created. */<br /> @Override<br /> public void onCreate(Bundle savedInstanceState) {<br /> super.onCreate(savedInstanceState);<br /> setContentView(R.layout.main);<br /> <br /> detector = new GestureDetector(this,this);<br /> }<br /> <br /> @Override <br /> public boolean onTouchEvent(MotionEvent me){ <br /> this.detector.onTouchEvent(me);<br /> return super.onTouchEvent(me); <br /> }<br /><br /> @Override<br /> public boolean onDown(MotionEvent e) {<br /> Log.d("---onDown----",e.toString()); <br /> return false;<br /> }<br /><br /> @Override<br /> public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,<br /> float velocityY) {<br /> Log.d("---onFling---",e1.toString()+e2.toString());<br /> return false;<br /> }<br /><br /> @Override<br /> public void onLongPress(MotionEvent e) {<br /> Log.d("---onLongPress---",e.toString()); <br /> }<br /><br /> @Override<br /> public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,<br /> float distanceY) {<br /> Log.d("---onScroll---",e1.toString()+e2.toString());<br /> return false;<br /> }<br /><br /> @Override<br /> public void onShowPress(MotionEvent e) {<br /> Log.d("---onShowPress---",e.toString());<br /> }<br /><br /> @Override<br /> public boolean onSingleTapUp(MotionEvent e) {<br /> Log.d("---onSingleTapUp---",e.toString());<br /> return false;<br /> }<br /><br /> @Override<br /> public boolean onDoubleTap(MotionEvent e) {<br /> Log.d("---onDoubleTap---",e.toString());<br /> return false;<br /> }<br /><br /> @Override<br /> public boolean onDoubleTapEvent(MotionEvent e) {<br /> Log.d("---onDoubleTapEvent---",e.toString());<br /> return false;<br /> }<br /><br /> @Override<br /> public boolean onSingleTapConfirmed(MotionEvent e) {<br /> Log.d("---onSingleTapConfirmed---",e.toString());<br /> return false;<br /> }<br /> <br />}<br /></code></pre><br /></div><br />if we want to understand all gesture types and how they work, first of all we need to know three basic MotionEvents which can combine with each other and create some gestures, these three Events are Action_Down , Action_Move and Action_Up , each time you touch the screen an Action_Down occurs and when you start moving it will create Action_Move event and finally when you take your finger off the screen an Action_Up Event will be created.<br />onDown() is called simply when there is an Action_Down event.<br />onShowPress() is called after onDown() and before any other callback method, I found out it sometimes might not get called for example when you tap on the screen so fast, but it's actually what this method all about, to make a distinction between a possible unintentional touch and an intentional one.<br />onSingleTapUp() is called when there is a Tap Gesture. Tap Gesture happens when an Action_Down event followed by an Action_Up event(like a Single-Click).<br />onDoubleTap() is called when there is two consecutive Tap gesture(like a Double-Click).<br />onSingleTapConfirmed() is so similar to onSingleTapUp() but it is only get called when the detected tap gesture is definitely a single Tap and not part of a double tap gesture.<br />Here is the sequence in which these callback methods are called when we tap on the screen:<br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br />onDown() – onShowPress() - onSingleTapUp() – onSingleTapConfirmed() <br /></div><br /><br />and here is when we do a double tap:<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br />onDown() – onShowPress() - onSingleTapUp() – onDoubleTap() – onDoubleTapEvent()<br />onDown() – onShowPress() – onDoubleTapEvent()<br /></div><br /><br />onFling() is called when a Fling Gesture is detected. fling Gesture occurs when there is an Action_Down then some Action_Move events and finally an Action_Up, but they must take place with a specified movement and velocity pattern to be considered as a fling gesture. for example if you put your finger on the screen and start moving it slowly and then remove your finger gently it won’t be count as a fling gesture.<br /><br />onScroll() is usually called when there is a Action_Move event so if you put your finger on the screen and move it for a few seconds there will be a method call chain like this :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br />onDown() – onShowPress() – onScroll() - onScroll() - onScroll() - ....... - onScroll()<br /></div><br /><br />or If the movement was a Fling Gesture, then there would be a call chain like this : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br />onDown() – onShowPress() – onScroll() - onScroll() - onScroll() - ....... – onFling()<br /></div><br />If there is an Action_Move event between first tap and second tap of a doubleTap gesture it will be handled by calling onDoubleTapEvent() instead of onScroll() method. onDoubleTapEvent() receives all Action_Up events of a doubleTap gesture as well.<br />Remember that if we touch the screen and don’t remove our finger for a specified amount of time onLongPress() method is called and in most cases there will be no further event callback regardless of whatever we do after that, moving or removing our finger. we can easily change this behavior of detector by calling setIsLongpressEnabled() method of GestureDetector class. <br />although GestureDetector makes our life much easier, it still could be a real pain in the ass if we would need to handle some complicated gestures, imagine you need an application which should do task1 when there is a circle gesture, task2 for rectangle gesture and task3 for triangle gesture. obviously it would not be so pleasant to deal with such a situation with those mechanism we have seen so far, it's actually when Gesture API and Gesture Builder Application come into play.<br />Gesture Builder Application comes with Android emulator as a pre-installed app, It helps us to simply make new gestures and save them on the device, then we can retrieve and detect those gestures later using Gesture API. I'm not gonna talk about Gesture Builder app here,since there is a pretty good Article about it on <a href="http://android-developers.blogspot.com/2009/10/gestures-on-android-16.html">Android Developers blog</a>.<br />the only thing I'd like to mention here is GestureOverlayView class, It is actually just a transparent FrameLayout which can detect gestures but the thing is it can be used in two completely different ways, you can either put other views inside it and use it as a parent view or put it is the last child view of a FrameLayout (or any other way which causes it to be placed on top of another view).<br />In the first Scenario all child views will receive Touch events, therefore we will be able to press buttons or interact with other widgets as well as doing some gestures, on the other hand if GestureOverlayView has been placed on top, it will swallow all Touch events and no underlay view will be notified for any touch Event.<br />Although Gesture API brings many useful features for us, I personally prefer to use GestureDetector for some simple, basic gestures and honestly I feel like something is missing here, I mean , apart from games, I would say more than 70% of all gestures that might be needed in our applications are just a simple sliding in different directions or a double tap. and that's why I have decided to come up with something easy which can enable us to handle those 70% as simple as possible. how simple? you might be asking...<br />here is how our previous activity will look like if we use SimpleGestureFilter class : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />public class SimpleActivity extends Activity implements SimpleGestureListener{<br /> <br /> private SimpleGestureFilter detector; <br /> <br /> /** Called when the activity is first created. */<br /> @Override<br /> public void onCreate(Bundle savedInstanceState) {<br /> super.onCreate(savedInstanceState);<br /> setContentView(R.layout.main);<br /> <br /> detector = new SimpleGestureFilter(this,this);<br /> }<br /> <br /> @Override <br /> public boolean dispatchTouchEvent(MotionEvent me){ <br /> this.detector.onTouchEvent(me);<br /> return super.dispatchTouchEvent(me); <br /> }<br /><br /> @Override<br /> public void onSwipe(int direction) {<br /> String str = "";<br /> <br /> switch (direction) {<br /> <br /> case SimpleGestureFilter.SWIPE_RIGHT : str = "Swipe Right";<br /> break;<br /> case SimpleGestureFilter.SWIPE_LEFT : str = "Swipe Left";<br /> break;<br /> case SimpleGestureFilter.SWIPE_DOWN : str = "Swipe Down";<br /> break;<br /> case SimpleGestureFilter.SWIPE_UP : str = "Swipe Up";<br /> break;<br /> <br /> } <br /> Toast.makeText(this, str, Toast.LENGTH_SHORT).show();<br /> }<br /><br /> @Override<br /> public void onDoubleTap() {<br /> Toast.makeText(this, "Double Tap", Toast.LENGTH_SHORT).show(); <br /> }<br /><br />}<br /></code></pre><br /></div><br />and here is SimpleGestureFilter source code :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />public class SimpleGestureFilter extends SimpleOnGestureListener{<br /> <br /> public final static int SWIPE_UP = 1;<br /> public final static int SWIPE_DOWN = 2;<br /> public final static int SWIPE_LEFT = 3;<br /> public final static int SWIPE_RIGHT = 4;<br /> <br /> public final static int MODE_TRANSPARENT = 0;<br /> public final static int MODE_SOLID = 1;<br /> public final static int MODE_DYNAMIC = 2;<br /> <br /> private final static int ACTION_FAKE = -13; //just an unlikely number<br /> private int swipe_Min_Distance = 100;<br /> private int swipe_Max_Distance = 350;<br /> private int swipe_Min_Velocity = 100;<br /> <br /> private int mode = MODE_DYNAMIC;<br /> private boolean running = true;<br /> private boolean tapIndicator = false;<br /> <br /> private Activity context;<br /> private GestureDetector detector;<br /> private SimpleGestureListener listener;<br /> <br /> <br /> public SimpleGestureFilter(Activity context,SimpleGestureListener sgl) {<br /> <br /> this.context = context;<br /> this.detector = new GestureDetector(context, this);<br /> this.listener = sgl; <br /> }<br /> <br /> public void onTouchEvent(MotionEvent event){<br /> <br /> if(!this.running)<br /> return; <br /> <br /> boolean result = this.detector.onTouchEvent(event); <br /> <br /> if(this.mode == MODE_SOLID)<br /> event.setAction(MotionEvent.ACTION_CANCEL);<br /> else if (this.mode == MODE_DYNAMIC) {<br /> <br /> if(event.getAction() == ACTION_FAKE) <br /> event.setAction(MotionEvent.ACTION_UP);<br /> else if (result)<br /> event.setAction(MotionEvent.ACTION_CANCEL); <br /> else if(this.tapIndicator){<br /> event.setAction(MotionEvent.ACTION_DOWN);<br /> this.tapIndicator = false;<br /> }<br /> <br /> }<br /> //else just do nothing, it's Transparent<br /> }<br /> <br /> public void setMode(int m){<br /> this.mode = m;<br /> }<br /> <br /> public int getMode(){<br /> return this.mode;<br /> }<br /> <br /> public void setEnabled(boolean status){<br /> this.running = status;<br /> }<br /> <br /> public void setSwipeMaxDistance(int distance){<br /> this.swipe_Max_Distance = distance;<br /> }<br /> <br /> public void setSwipeMinDistance(int distance){<br /> this.swipe_Min_Distance = distance;<br /> }<br /> <br /> public void setSwipeMinVelocity(int distance){<br /> this.swipe_Min_Velocity = distance;<br /> }<br /> <br /> public int getSwipeMaxDistance(){<br /> return this.swipe_Max_Distance;<br /> }<br /> <br /> public int getSwipeMinDistance(){<br /> return this.swipe_Min_Distance;<br /> }<br /> <br /> public int getSwipeMinVelocity(){<br /> return this.swipe_Min_Velocity;<br /> }<br /> <br /> <br /> @Override<br /> public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,<br /> float velocityY) {<br /><br /> final float xDistance = Math.abs(e1.getX() - e2.getX());<br /> final float yDistance = Math.abs(e1.getY() - e2.getY());<br /><br /> if(xDistance > this.swipe_Max_Distance || yDistance > this.swipe_Max_Distance)<br /> return false;<br /><br /> velocityX = Math.abs(velocityX);<br /> velocityY = Math.abs(velocityY);<br /> boolean result = false;<br /><br /> if(velocityX > this.swipe_Min_Velocity && xDistance > this.swipe_Min_Distance){<br /> if(e1.getX() > e2.getX()) // right to left<br /> this.listener.onSwipe(SWIPE_LEFT);<br /> else<br /> this.listener.onSwipe(SWIPE_RIGHT);<br /> <br /> result = true;<br /> }<br /> else if(velocityY > this.swipe_Min_Velocity && yDistance > this.swipe_Min_Distance){<br /> if(e1.getY() > e2.getY()) // bottom to up <br /> this.listener.onSwipe(SWIPE_UP);<br /> else<br /> this.listener.onSwipe(SWIPE_DOWN);<br /> <br /> result = true;<br /> }<br /><br /> return result;<br /> }<br /><br /> @Override<br /> public boolean onSingleTapUp(MotionEvent e) {<br /> this.tapIndicator = true;<br /> return false;<br /> }<br /><br /> @Override<br /> public boolean onDoubleTap(MotionEvent arg0) {<br /> this.listener.onDoubleTap();;<br /> return true;<br /> }<br /><br /> @Override<br /> public boolean onDoubleTapEvent(MotionEvent arg0) {<br /> return true;<br /> }<br /><br /> @Override<br /> public boolean onSingleTapConfirmed(MotionEvent arg0) {<br /> <br /> if(this.mode == MODE_DYNAMIC){ // we owe an ACTION_UP, so we fake an <br /> arg0.setAction(ACTION_FAKE); //action which will be converted to an ACTION_UP later. <br /> this.context.dispatchTouchEvent(arg0); <br /> } <br /> <br /> return false;<br /> }<br /> <br /> <br /> static interface SimpleGestureListener{<br /> void onSwipe(int direction);<br /> void onDoubleTap();<br /> }<br /> <br />}<br /></code></pre><br /></div><br />as you can see clients of these class can determine the minimum and maximum distance and also minimum velocity which is required for a movement on screen to be considered as a Swipe Gesture, I also thought it would be great if our filter can behave differently like what GestureOverlayView can do and even more than that!<br />this Filter can run in three different mode: Transparent, Solid and Dynamic. in Transparent mode it will work just like when we have a GestureOverlayView as parent: all views will receive Touch events; Solid mode works like when we put a GestureOverlayView as a child view: no one will receive TouchEvent, it is not as efficient as GestureOverlayView is, since we actually let all events get passed but what we do is we literally kill them when they are passing through our filter ;).<br />the last mode is Dynamic mode, the primary purpose of this mode is to have a bit smarter gesture detection, i mean there has been sometimes that i wanted to slide from one page to another, but a button get pressed and something else happens. it does not happen so much but it is really annoying. what i tried to do in Dynamic mode is to distinguish between a swipe/double tap gesture and a movement which is neither of them. so if you have a view full of buttons and other interactive stuff and user does a swipe or double tap gesture, it is guaranteed (although i believe there is no such thing as guarantee in life ;) ) that no other event callback will be called but only onSwipe() or onDoubleTap(). <br />Anyway that's what i came up with to take the pain away in those circumstances when we just need to handle some simple Gestures.<br />hope it will be helpful for you and can make your life a bit easier.Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com40tag:blogger.com,1999:blog-8323121247454250099.post-36820901229616103532010-01-16T03:51:00.001-08:002010-01-16T05:08:32.486-08:00Android Braodcast ReceiversAs we all know There are four types of components which can be used in Android, a good android developer should be Familiar with these components and the idea behind them in order to design and implement efficient applications.<br />I think it is safe to say that the most significant thing about android is the sharing philosophy on which it is based and understanding this concept could help us to have a more solid view on when and how to use each one of these component types.<br />Android applications can share their stuff and interact with each other and that is the main idea behind Content Providers,Services and Broadcast Receivers in Android: Sharing Data, Services and Events.<br />In this article I am gonna talk about Broadcast Receivers in Android and I will try to cover almost everything that we might need to know about them in different situations. Broadcast Receiver is actually a mechanism to send and receive events so that all interested applications can be informed when something happens. there are heaps of System events which get broadcast by Android OS such as SMS related events, Connectivity related events, Camera related events and many more.<br />We are able to broadcast our application specific events as well, so for example if we have a RSS news reader application and we want to do something whenever a new item is available, it would be a good idea to use Broadcast Receiver method, not only because it will separate your event handling code but most importantly because it will enable other applications to register and receive a notification whenever that event takes place.<br />The first thing we will be talking about is how to implement a Receiver and register it so that you can receive events that you want to receive. Implementing a Receiver is pretty simple and straight forward, all you need to do is to extend Broadcast Receiver class and override its onReceive() method. once you implement your Receiver you need to register it and determine what kind of events you are interested in to be sent to you.<br />For example if you want to get notified whenever there is an incoming call or SMS you will need to add something like this to you manifest file : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><receiver android:name="amir.android.icebreaking.CallAndSMSListener"><br /> <intent-filter><br /> <action android:name="android.intent.action.PHONE_STATE" /><br /> <action android:name="android.provider.Telephony.SMS_RECEIVED" /><br /> </intent-filter><br /></receiver><br /></code></pre><br /></div><br />and here is CallAndSMSListener class source code :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />public class CallAndSMSListener extends BroadcastReceiver {<br /><br /> @Override<br /> public void onReceive(Context context, Intent intent) {<br /> <br /> String action = intent.getAction();<br /> if(action.equalsIgnoreCase("android.intent.action.PHONE_STATE")){<br /> if (intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(<br /> TelephonyManager.EXTRA_STATE_RINGING)) {<br /> <br /> doSomething(intent.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER));<br /> } <br /> }<br /> else {<br /> <br /> Bundle bundle = intent.getExtras();<br /> Object[] pdus = (Object[]) bundle.get("pdus");<br /> SmsMessage message = SmsMessage.createFromPdu((byte[])pdus[0]);<br /> if(!message.isEmail())<br /> doSomething(message.getOriginatingAddress());<br /> <br /> }<br /> <br /> }<br /> <br /> private void doSomething(String number){<br /> Log.d("<<<<Number>>>>",number);<br /> }<br /><br />}<br /></code></pre><br /></div><br />In this example I have registered my Receiver in Manifest file, but we can also register a Receiver dynamically in our code like this :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /> this.reciever = new ConnectivityListener();<br /> IntentFilter filter = new IntentFilter();<br /> filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);<br /> filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);<br /> registerReceiver(this.reciever, filter);<br /></code></pre><br /></div><br />in this case after calling registerReceiver our ConnectivityListener will be notified when Wi-Fi state or Connectivity state of mobile phone change. Just remember to unregister all Receivers that you have registered in your code using unregisterReceiver() method.<br />ConnectivityListener class looks like this :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />public class ConnectivityListener extends BroadcastReceiver {<br /><br /> @Override<br /> public void onReceive(Context context, Intent intent) {<br /> <br /> String action = intent.getAction();<br /> <br /> if(action.equalsIgnoreCase(WifiManager.NETWORK_STATE_CHANGED_ACTION)){<br /> <br /> int state = intent.getIntExtra(WifiManager.EXTRA_WIFI_STATE, WifiManager.WIFI_STATE_UNKNOWN);<br /> //Do Something.....<br /> }<br /> else {<br /><br /> NetworkInfo info = (NetworkInfo)intent.getParcelableExtra(ConnectivityManager.EXTRA_NETWORK_INFO);<br /> //Do Something.....<br /> <br /> }<br /> <br /><br /> }<br /><br />}<br /></code></pre><br /></div><br />So far we have seen how to register a Receiver both statically and dynamically,but it is just half the story; as i said we can not only receive events but can also broadcast our events either locally for just a specific Receiver or globally for all other applications.<br />In my application I have something like this : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /> Intent intent = new Intent(this,MyReceiver.class);<br /> intent.putExtra("Message", "Something has Changed!!");<br /> <br /> this.sendBroadcast(intent);<br /></code></pre><br /></div><br />As you can see i specified the class type for the Intent, so when i broadcast the Intent, only MyReceiver will be notified. if we wanted to globally broadcast it we should set an Action string for our Intent so that all receivers which have indicated that action in their intent filter would be notified.<br />this is what you need to put in your manifest file for this example:<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /> <receiver android:name=".MyReceiver"/> <br /></code></pre><br /></div><br /><br />and MyReceiver class:<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />public class MyReceiver extends BroadcastReceiver {<br /><br /> @Override<br /> public void onReceive(Context context, Intent intent) {<br /> <br /> String msg = intent.getStringExtra("Message"); <br /> Vibrator vib = (Vibrator)context.getSystemService(Context.VIBRATOR_SERVICE);<br /> <br /> Toast.makeText(context, msg, Toast.LENGTH_LONG).show();<br /> vib.vibrate(2000);<br /> <br /> }<br /><br />}<br /></code></pre><br /></div><br />Sometimes you dont want to broadcast an event directly from your activity but for whatever reason you want another application do that on your behalf, in these sort of cases you should use PendingIntent class instead of Intent. AlarmManager class is a good example to illustrate this kind of scenarios, AlarmManager class allows you to set a time to alarm system goes off, you can also specify a PendingIntent object to be broadcast when alarm goes off.<br />I have used this piece of code in my Activity onStop() method : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /> Calendar calendar = Calendar.getInstance();<br /> calendar.setTimeInMillis(System.currentTimeMillis());<br /> calendar.add(Calendar.HOUR, 4);<br /><br /> Intent intent = new Intent(this, Receiver.class);<br /> intent.setAction("Start");<br /> PendingIntent sender = PendingIntent.getBroadcast(this,<br /> 0, intent, 0);<br /> <br /> AlarmManager am = (AlarmManager)getSystemService(ALARM_SERVICE);<br /> am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), sender); <br /></code></pre><br /></div><br />It means that alarm goes off 4 hours after each time we close the application and when it happens it also broadcast the specified intent it has been provided earlier.<br />and here is the content of Reciever's onRecieve() method : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /> Intent startIntent = new Intent(context, MainActivity.class);<br /> startIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);<br /> <br /> context.startActivity(startIntent);<br /></code></pre><br /></div><br />so simple, it just starts our activity again. 4 hours after that we close the application the alarm goes off and the application comes up again.<br /><br />when we send an ordinary broadcast all Registered Receivers will get notified but the order in which they are notified is undefined, in other words you wouldn't know which Receiver is called first and which one last. another thing to remember is you are not allowed to stop the propagation of an event in a Receiver. but what if you really need to be able to stop the propagation of an event in an Receiver so that other receivers dont get it or what if you need to set an order according to which Receivers should be called, thankfully it's not a big deal, all we would need to do is to call sendOrderedBroadcast() method instead of sendBroadcast() to solve the problem. <br />For example I have used this code in my application :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /> Intent intent = new Intent("com.test.ACTION");<br /> intent.putExtra("Random", (int)(Math.random()*10));<br /> <br /> this.sendOrderedBroadcast(intent, <br /> null, <br /> new LastReceiver(), <br /> null, <br /> Activity.RESULT_OK, <br /> null, <br /> null);<br /></code></pre><br /></div><br />I also have two receivers in my manifest file :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /> <receiver android:name="amir.android.icebreaking.FirstReceiver"><br /> <intent-filter android:priority="100"><br /> <action android:name="com.test.ACTION" /><br /> </intent-filter><br /> </receiver><br /> <br /> <receiver android:name="amir.android.icebreaking.SecondReceiver"><br /> <intent-filter android:priority="50"><br /> <action android:name="com.test.ACTION" /><br /> </intent-filter><br /> </receiver><br /></code></pre><br /></div><br />When we call OrderedBroadcast() method we have two registered Receiver which need to be called, but as you can see we have used android:priority attribute for them, it means that Receiver with higher value will be called first, in this example FirstReciever is notified first and then SecondReciever get notified if FirstReciever have not aborted the intent. <br />LastReciever is always gonna get notified regardless of what has happened in other Receivers.<br />and here is these three classes source codes :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />public class FirstReceiver extends BroadcastReceiver {<br /><br /> @Override<br /> public void onReceive(Context context, Intent intent) {<br /> <br /> int value = intent.getIntExtra("Random", -1);<br /> <br /> Bundle bundle = getResultExtras(true); <br /> bundle.putInt("FirstReciever", value*13);<br /> setResultExtras(bundle);<br /><br /> if(value < 0 || value%2 != 0)<br /> abortBroadcast(); <br /> <br /> }<br /><br />}<br /></code></pre><br /></div><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />public class SecondReceiver extends BroadcastReceiver {<br /><br /> @Override<br /> public void onReceive(Context context, Intent intent) {<br /> <br /> int value = intent.getIntExtra("Random", -1);<br /> if(value > 0){<br /> Bundle bundle = getResultExtras(true); <br /> bundle.putInt("SecondReciever", value*17);<br /> setResultExtras(bundle); <br /> } <br /> <br /><br /> }<br /><br />}<br /><br /></code></pre><br /></div><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />public class LastReceiver extends BroadcastReceiver {<br /><br /> @Override<br /> public void onReceive(Context context, Intent intent) {<br /> <br /> Bundle bundle = getResultExtras(false);<br /> StringBuilder builder = new StringBuilder();<br /> builder.append("---Last Reciever---\n");<br /> builder.append("<<<Random>>>"+intent.getIntExtra("Random", -1)+"\n");<br /> builder.append("<<<First Reciever>>>"+bundle.getInt("FirstReciever")+"\n");<br /> builder.append("<<<Second Reciever>>>"+bundle.getInt("SecondReciever")+"\n");<br /> <br /> Toast.makeText(context, builder.toString(), Toast.LENGTH_LONG).show();<br /> <br /> }<br /><br />}<br /></code></pre><br /></div><br /><br />Do not forget to add these Permission if you want to test these examples:<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /> <uses-permission android:name="android.permission.READ_PHONE_STATE" /><br /> <uses-permission android:name="android.permission.RECEIVE_SMS" /><br /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /><br /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <br /> <uses-permission android:name="android.permission.VIBRATE" /><br /></code></pre><br /></div>Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com10tag:blogger.com,1999:blog-8323121247454250099.post-83349306294615302582010-01-06T23:55:00.000-08:002010-01-07T00:32:48.758-08:00Android PreferencesFor almost any application we need to provide some settings in order to enable users have some level of control over how the application works. Android has provided a standard mechanism to show, save and manipulate user's preferences. PreferenceActivity class is the standard Android Activity to show Preferences page, it contains a bunch of Preference class instances which use SharedPreference<br />class to save and manipulate corresponding data.There are different types of Preferences Available in Preference package, and if you need something more you can extend Preference class and create your own Preference type. in this article we will go through predefined Preference types in Android and I'm also going to implement a custom Preference type to see how that works.<br />Our Preferences page is gonna look like this :<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lqWNM-79b1Y/S0WUB03Xa4I/AAAAAAAAAGQ/Zf_tdmBeaZ4/s1600-h/Untitled01.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 203px; height: 320px;" src="http://2.bp.blogspot.com/_lqWNM-79b1Y/S0WUB03Xa4I/AAAAAAAAAGQ/Zf_tdmBeaZ4/s320/Untitled01.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5423904085329275778" /></a><br /><br />as you can see we have two section in our preferences page , "First Category" which contains two options and "Second Category" which contains three options.<br />here is our Activity which produces this page :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />public class MyPreferenceActivity extends PreferenceActivity {<br /> @Override<br /> protected void onCreate(Bundle savedInstanceState) {<br /> super.onCreate(savedInstanceState);<br /><br /> addPreferencesFromResource(R.xml.preferences);<br /> }<br /> <br />}<br /></code></pre><br /></div><br />pretty simple, isn't it? actually it's supposed to be simple and easy thanks to PreferenceActivity which takes care of pretty much anything. as i said PreferenceActivity renders instances of Preference class which in this example<br />have defined in preferences.xml file under res/xml directory:<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><?xml version="1.0" encoding="utf-8"?><br /><PreferenceScreen<br /> xmlns:android="http://schemas.android.com/apk/res/android"><br /> <br /> <PreferenceCategory android:title="First Category"><br /> <CheckBoxPreference<br /> android:key="Main_Option"<br /> android:title="Main Option"<br /> android:defaultValue="true"<br /> android:summary="SUMMARY_Main_Option" /><br /><br /> <br /> <ListPreference<br /> android:title="List Preference"<br /> android:summary="This preference allows to select an item in an array"<br /> android:dependency="Main_Option"<br /> android:key="listPref"<br /> android:defaultValue="1"<br /> android:entries="@array/colors"<br /> android:entryValues="@array/colors_values" /> <br /> <br /> <br /> </PreferenceCategory><br /> <br /> <PreferenceCategory android:title="Second Category"><br /> <br /> <PreferenceScreen android:title="Advanced Options"><br /> <br /> <CheckBoxPreference<br /> android:key="Advanced_Option"<br /> android:title="Advanced Option"<br /> android:defaultValue="true"<br /> android:summary="SUMMARY_Advanced_Option"/><br /> <br /> </PreferenceScreen> <br /><br /> <br /> <amir.android.icebreaking.SeekBarPreference <br /> android:dependency="Main_Option"<br /> android:key="customPref"<br /> android:defaultValue="32"<br /> android:title="Custom Preference" /> <br /> <br /> <EditTextPreference android:dialogTitle="EditTextTitle"<br /> android:dialogMessage="EditTextMessage"<br /> android:dependency="Main_Option"<br /> android:key="pref_dialog"<br /> android:title="SomeTitle"<br /> android:summary="Summary"<br /> android:defaultValue="test"/> <br /> <br /> <br /> <br /> </PreferenceCategory><br /> <br /></PreferenceScreen><br /><br /></code></pre><br /></div><br />First thing i wanna talk about is dependency, Dependency helps you to disable/enable some Preferences based on the status of another preference. in this example we have three preferences dependent on the first preference so if we disable the first option all dependent options will become uneditable.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/S0WV0zrlx2I/AAAAAAAAAGY/A63V0tutmxQ/s1600-h/Untitled02.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 214px; height: 320px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/S0WV0zrlx2I/AAAAAAAAAGY/A63V0tutmxQ/s320/Untitled02.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5423906060696405858" /></a><br /><br />if we select the second option we will see something like this :<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lqWNM-79b1Y/S0WWJI-1rMI/AAAAAAAAAGg/9sQNdGbC-ns/s1600-h/Untitled03.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 212px; height: 320px;" src="http://1.bp.blogspot.com/_lqWNM-79b1Y/S0WWJI-1rMI/AAAAAAAAAGg/9sQNdGbC-ns/s320/Untitled03.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5423906410011667650" /></a><br /><br />ListPreference tag has two attribute which is used to populate its content; android:entries which refers to an array of labels and android:entryValues which is also an array that represent the actual value of entries, this value will be saved when each entry gets selected. as you can see I've used "@array/" indicator for these two attribute, it means we have our data in arrays.xml file under res/values directory and here is its content :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><?xml version="1.0" encoding="utf-8"?><br /><br /><br /><resources><br /> <br /> <string-array name="colors"><br /> <item>red</item><br /> <item>orange</item><br /> <item>yellow</item><br /> <item>green</item><br /> <item>blue</item><br /> <item>violet</item><br /> </string-array><br /> <br /> <string-array name="colors_values"><br /> <item>1</item><br /> <item>2</item><br /> <item>3</item><br /> <item>4</item><br /> <item>5</item><br /> <item>6</item><br /> </string-array><br /> <br /> <br /></resources><br /><br /></code></pre><br /></div><br />you sometimes want to show a set of Preferences in a different window, to do so you just need to wrap all those Preferences in a PreferenceScreen tag, just like what I've done for "Advanced Options" in this example so when user presses this option we will see something like this :<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lqWNM-79b1Y/S0WWr-UwmMI/AAAAAAAAAGo/g0O_-lYiHe8/s1600-h/Untitled04.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 208px; height: 320px;" src="http://3.bp.blogspot.com/_lqWNM-79b1Y/S0WWr-UwmMI/AAAAAAAAAGo/g0O_-lYiHe8/s320/Untitled04.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5423907008446240962" /></a><br /><br />The last Predefined Preference type I'm gonna talk about is EditTextPreference, when user presses one of these type of Preferences, a dialog with a Text Field pops up and let the user enter a text. I used a EditTextPreference for the last option in this example and you can see here how it's gonna look like after being pressed : <br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lqWNM-79b1Y/S0WW39RQlVI/AAAAAAAAAGw/KbJHLBQ99A8/s1600-h/Untitled05.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 214px; height: 320px;" src="http://3.bp.blogspot.com/_lqWNM-79b1Y/S0WW39RQlVI/AAAAAAAAAGw/KbJHLBQ99A8/s320/Untitled05.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5423907214321554770" /></a><br /><br />So far we've seen what functionality we have already got in our hand which would fulfill our needs in most circumstances but what if we need something that is not already there? Not a big deal... we'll just need to roll up our sleeves and implement our own Preference type.<br />I've developed a simple custom preference type which shows a seekbar and stores an Integer value each time user moves the seekbar. you can see how it looks like in the first picture above.<br />here is our SeekBarPreference class source code : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />public class SeekBarPreference extends Preference <br /> implements OnSeekBarChangeListener{<br /><br /> <br /> public static int maximum = 100;<br /> public static int interval = 5;<br /> <br /> private float oldValue = 50;<br /> private TextView monitorBox;<br /> <br /> <br /> public SeekBarPreference(Context context) {<br /> super(context);<br /> }<br /> <br /> public SeekBarPreference(Context context, AttributeSet attrs) {<br /> super(context, attrs);<br /> }<br /> <br /> public SeekBarPreference(Context context, AttributeSet attrs, int defStyle) {<br /> super(context, attrs, defStyle);<br /> }<br /> <br /> @Override<br /> protected View onCreateView(ViewGroup parent){<br /> <br /> LinearLayout layout = new LinearLayout(getContext());<br /> <br /> LinearLayout.LayoutParams params1 = new LinearLayout.LayoutParams(<br /> LinearLayout.LayoutParams.WRAP_CONTENT,<br /> LinearLayout.LayoutParams.WRAP_CONTENT);<br /> params1.gravity = Gravity.LEFT;<br /> params1.weight = 1.0f;<br /> <br /> <br /> LinearLayout.LayoutParams params2 = new LinearLayout.LayoutParams(<br /> 80,<br /> LinearLayout.LayoutParams.WRAP_CONTENT);<br /> params2.gravity = Gravity.RIGHT;<br /> <br /> <br /> LinearLayout.LayoutParams params3 = new LinearLayout.LayoutParams(<br /> 30,<br /> LinearLayout.LayoutParams.WRAP_CONTENT);<br /> params3.gravity = Gravity.CENTER;<br /> <br /> <br /> layout.setPadding(15, 5, 10, 5);<br /> layout.setOrientation(LinearLayout.HORIZONTAL);<br /> <br /> TextView view = new TextView(getContext());<br /> view.setText(getTitle());<br /> view.setTextSize(18);<br /> view.setTypeface(Typeface.SANS_SERIF, Typeface.BOLD);<br /> view.setGravity(Gravity.LEFT);<br /> view.setLayoutParams(params1);<br /> <br /> <br /> SeekBar bar = new SeekBar(getContext());<br /> bar.setMax(maximum);<br /> bar.setProgress((int)this.oldValue);<br /> bar.setLayoutParams(params2);<br /> bar.setOnSeekBarChangeListener(this);<br /> <br /> this.monitorBox = new TextView(getContext());<br /> this.monitorBox.setTextSize(12);<br /> this.monitorBox.setTypeface(Typeface.MONOSPACE, Typeface.ITALIC);<br /> this.monitorBox.setLayoutParams(params3);<br /> this.monitorBox.setPadding(2, 5, 0, 0);<br /> this.monitorBox.setText(bar.getProgress()+"");<br /> <br /> <br /> layout.addView(view);<br /> layout.addView(bar);<br /> layout.addView(this.monitorBox);<br /> layout.setId(android.R.id.widget_frame);<br /> <br /> <br /> return layout; <br /> }<br /> <br /> @Override<br /> public void onProgressChanged(SeekBar seekBar, int progress,boolean fromUser) {<br /> <br /> progress = Math.round(((float)progress)/interval)*interval;<br /> <br /> if(!callChangeListener(progress)){<br /> seekBar.setProgress((int)this.oldValue); <br /> return; <br /> }<br /> <br /> seekBar.setProgress(progress);<br /> this.oldValue = progress;<br /> this.monitorBox.setText(progress+"");<br /> updatePreference(progress);<br /> <br /> notifyChanged();<br /> }<br /><br /> @Override<br /> public void onStartTrackingTouch(SeekBar seekBar) {<br /> }<br /><br /> @Override<br /> public void onStopTrackingTouch(SeekBar seekBar) {<br /> }<br /> <br /> <br /> @Override <br /> protected Object onGetDefaultValue(TypedArray ta,int index){<br /> <br /> int dValue = (int)ta.getInt(index,50);<br /> <br /> return validateValue(dValue);<br /> }<br /> <br /><br /> @Override<br /> protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {<br /> <br /> int temp = restoreValue ? getPersistedInt(50) : (Integer)defaultValue;<br /> <br /> if(!restoreValue)<br /> persistInt(temp);<br /> <br /> this.oldValue = temp;<br /> }<br /> <br /> <br /> private int validateValue(int value){<br /> <br /> if(value > maximum)<br /> value = maximum;<br /> else if(value < 0)<br /> value = 0;<br /> else if(value % interval != 0)<br /> value = Math.round(((float)value)/interval)*interval; <br /> <br /> <br /> return value; <br /> }<br /> <br /> <br /> private void updatePreference(int newValue){<br /> <br /> SharedPreferences.Editor editor = getEditor();<br /> editor.putInt(getKey(), newValue);<br /> editor.commit();<br /> }<br /> <br />}<br /></code></pre> <br /></div><br />In all examples I've come across about custom preferences, an XML layout file has been used to create the preference view, so I thought it would be a good idea not to used XML and go programatically and actually it was a good opportunity for a person like me who had always used XML for creating GUIs in Android to go through an alternative way. <br /><br />anyway onCreateView() method of Preference class is responsible to return a View to be shown by PereferenceActivity,we override this method to make our own custom view, in Preference class documentation we've been asked to return a ViewGroup with "widget_frame" as its ID, that's what I've done and you should do if you want to do something like this.<br />Another thing to remember is that if you're willing to let clients of this preference type set Listeners and get notified when the status of preference changes you have to call callChangeListener() method before saving new value, this method will invoke the listener callback method and return the result, if client is happy with new value and operation can be carried out the result will be true, otherwise it will be false. Don't forget to call notifyChanged() method once you have saved new value of preference, though I'd forgotten to do so and it was working like a charm ;) <br /><br />I've also used both Preference class's persistInt() method and Editor class's putInt() method to store data in this example to show different ways that it can be done.<br /><br />And here is a simple Activity which retrieves preferences values and show them in a textview :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />public class ShowSettings extends Activity {<br /> @Override<br /> protected void onCreate(Bundle savedInstanceState) {<br /> super.onCreate(savedInstanceState);<br /> setContentView(R.layout.show);<br /><br /> <br /> SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(this); <br /> <br /> StringBuilder builder = new StringBuilder();<br /> <br /> builder.append("\n"+ sp.getBoolean("Main_Option",false));<br /> builder.append("\n"+ sp.getString("listPref","-1"));<br /> builder.append("\n"+ sp.getBoolean("Advanced_Option",false));<br /> builder.append("\n"+ sp.getInt("customPref",-1));<br /> builder.append("\n"+ sp.getString("pref_dialog","NULL"));<br /> <br /> TextView view = (TextView)findViewById(R.id.viewBox);<br /> view.setText(builder.toString());<br /> <br /> }<br /> <br />}<br /></code></pre><br /></div>Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com35tag:blogger.com,1999:blog-8323121247454250099.post-34906246553174453872009-12-31T02:55:00.000-08:002009-12-31T03:41:27.671-08:00Android ViewFlipper and SlidingDrawerIn my last post i talked about android selector mechanism and how to customize default GUI components apperances. another issue with mobile phone applications is limited displaying space, i mean if your application wants to go a bit further than a basic , simple application, it is almost gonna be impossible to fit all GUI options and features in a relatively small displaying space of mobile phones.<br /><br />as we've already talked about it, the first option to solve this sort of problems is Menus and dialogs which are pretty easy to use and simple, but what if you need something more than that with higher level of customizability, that's when <a href="http://developer.android.com/reference/android/widget/ViewFlipper.html">ViewFlipper</a> and <a href="http://developer.android.com/reference/android/widget/SlidingDrawer.html">SlidingDrawer</a> come into play(although they could be used for other purposes as well), like menus and dialogs they enable us to have some views hidden and show them when they are requested or when it's appropriate.<br />I'm gonna add a ViewFlipper and SlidingDrawer to my <a href="http://android-journey.blogspot.com/2009/12/android-selectors.html">last application</a>.<br /><br />first of all let's see what a ViewFlipper is, ViewFlipper is Actually a View container which can hold different Views, but it shows just one of those Views at a time and hide others, you can switch between views manually or automatically, most interesting thing about ViewFlipper is that it uses two different Animations for flipping between views, one is used for outgoing View and the other one for incoming View.<br />OK, here are some snapshots of what we are trying to achieve using a ViewFlipper: <br /><br /><table><tbody><tr><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lqWNM-79b1Y/SzyE1bwWx4I/AAAAAAAAAFg/gZc11i9gcIE/s1600-h/U_02.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 217px; height: 320px;" src="http://2.bp.blogspot.com/_lqWNM-79b1Y/SzyE1bwWx4I/AAAAAAAAAFg/gZc11i9gcIE/s320/U_02.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5421354104965744514" /></a><br /></td><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lqWNM-79b1Y/SzyFG1Joy9I/AAAAAAAAAFw/uDhJkFNCjKE/s1600-h/U_06.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 205px; height: 320px;" src="http://2.bp.blogspot.com/_lqWNM-79b1Y/SzyFG1Joy9I/AAAAAAAAAFw/uDhJkFNCjKE/s320/U_06.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5421354403840445394" /></a><br /></td><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/SzyFBYdsv9I/AAAAAAAAAFo/QXEjreklMls/s1600-h/U_03.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 211px; height: 320px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/SzyFBYdsv9I/AAAAAAAAAFo/QXEjreklMls/s320/U_03.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5421354310240616402" /></a><br /></td></tr></tbody></table><br /><br />There are two views between which we wanna flip, a ListView (Which we talked about it last time) and a simple view with a text and a button on it, When we press "Next" button our ListView will slide out and the other view will slide in and when "Go Back" button is pressed two views will be switched again. <br />our XML will be something like this : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /> <br /><ViewFlipper android:id="@+id/flipper"<br /> android:layout_width="fill_parent" <br /> android:layout_height="fill_parent"><br /><br /><br /> <FrameLayout android:layout_width="fill_parent" <br /> android:layout_height="wrap_content"<br /> android:layout_gravity="top" <br /> android:layout_marginTop="50dip"><br /><br /> <ListView android:id="@+id/list" <br /> android:layout_width="fill_parent"<br /> android:layout_height="wrap_content" <br /> android:dividerHeight="0dip"<br /> android:divider="@null" <br /> android:listSelector="@drawable/list_selector"<br /> android:layout_gravity="center" /><br /> <br /> </FrameLayout><br /><br /><br /> <LinearLayout android:layout_width="fill_parent"<br /> android:layout_height="wrap_content" <br /> android:orientation="vertical"<br /> android:background="@drawable/wood01" <br /> android:padding="20dip"<br /> android:layout_gravity="top" <br /> android:layout_marginTop="50dip"><br /><br /> <TextView android:layout_width="wrap_content" <br /> android:layout_height="wrap_content"<br /> android:text="TEST" <br /> android:layout_gravity="center" <br /> android:padding="15dip"<br /> android:textSize="22dip" <br /> android:textColor="@color/white" /><br /><br /> <Button android:text="Go Back" <br /> android:id="@+id/back_btn"<br /> android:layout_width="fill_parent" <br /> android:layout_height="wrap_content" /><br /><br /><br /> </LinearLayout><br /><br /><br /><br /> </ViewFlipper><br /><br /></code></pre><br /></div><br /><br /><br />As you can see our ViewFlipper has two children, a FrameLayout containing the ListView and a LinearLayout containing a TextView and a Button. by default the first child is shown when application comes up for the first time. <br />setting Animation for our flipper is pretty easy and straigh forward, here's the code I've used :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><br /> this.flipper = (ViewFlipper)findViewById(R.id.flipper);<br /> <br /> Animation s_in = AnimationUtils.loadAnimation(this, R.anim.slidein);<br /> Animation s_out = AnimationUtils.loadAnimation(this, R.anim.slideout);<br /> this.flipper.setInAnimation(s_in);<br /> this.flipper.setOutAnimation(s_out);<br /><br /><br /></code></pre><br /></div><br />and here are the content of slidein.xml and slideout.xml respectively : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><br /> <?xml version="1.0" encoding="utf-8"?><br /><br /><set xmlns:android="http://schemas.android.com/apk/res/android" <br /> android:interpolator="@android:anim/decelerate_interpolator"><br /> <br /> <translate android:fromXDelta="-100%" android:toXDelta="0%" android:duration="1800" /><br /> <br /></set><br /><br /></code></pre><br /></div><br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><br /><?xml version="1.0" encoding="utf-8"?><br /><br /><set xmlns:android="http://schemas.android.com/apk/res/android" <br /> android:interpolator="@android:anim/decelerate_interpolator"><br /> <br /> <translate android:fromXDelta="0%" android:toXDelta="100%" android:duration="1800" /><br /> <br /></set><br /><br /></code></pre><br /></div><br /><br />All you need to do to switch the showing view manually is to use showNext() and showPrevious() methods of ViewFlipper class.<br />Another predefined Widget for hiding stuff is SlidingDrawer and its name pretty much suggests what it does. what does a drawer do!? it has a handle which is used to drag the drawer container out...obviously ;)<br />I added a simple SlidingDrawer to my application and you can see how it looks like below : <br /><br /><table><tbody><tr><td><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/SzyJzGIZ_dI/AAAAAAAAAGI/z3IlgtrbXkk/s1600-h/U_04.jpg"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 202px; height: 320px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/SzyJzGIZ_dI/AAAAAAAAAGI/z3IlgtrbXkk/s320/U_04.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5421359562359438802" /></a><br /><br /></td><td><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/SzyJrTlEyFI/AAAAAAAAAGA/vO1l5H67NEU/s1600-h/U_05.jpg"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 201px; height: 320px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/SzyJrTlEyFI/AAAAAAAAAGA/vO1l5H67NEU/s320/U_05.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5421359428530391122" /></a><br /><br /></td></tr></tbody></table><br /><br /><br />First of all, I should say sorry for this wierd object I used for my drawer's handle ;) I couldn't find anything better!!<br />here is the XML which is being used to create what you saw above:<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><br /><FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:layout_width="wrap_content"<br /> android:layout_height="230dip" <br /> android:id="@+id/frameLayout" <br /> android:layout_gravity="bottom"><br /><br /> <SlidingDrawer android:layout_height="wrap_content"<br /> android:handle="@+id/handle" <br /> android:content="@+id/content"<br /> android:id="@+id/slide" <br /> android:orientation="vertical"<br /> android:layout_width="fill_parent"><br /> <br /><br /> <ImageView android:layout_width="55dip"<br /> android:layout_height="55dip" <br /> android:id="@id/handle" <br /> android:src="@drawable/arrow" /><br /><br /><br /> <LinearLayout android:layout_width="wrap_content"<br /> android:layout_height="wrap_content" <br /> android:id="@id/content"<br /> android:orientation="vertical" <br /> android:background="@drawable/wood01"<br /> android:padding="10dip"><br /><br /> <Button android:text="Test1" <br /> android:id="@+id/Button01"<br /> android:layout_width="fill_parent" <br /> android:layout_height="wrap_content" /><br /><br /> <Button android:text="Test2" <br /> android:id="@+id/Button02"<br /> android:layout_width="fill_parent" <br /> android:layout_height="wrap_content" /><br /><br /> <Button android:text="Test3" <br /> android:id="@+id/Button03"<br /> android:layout_width="fill_parent" <br /> android:layout_height="wrap_content" /><br /><br /><br /> </LinearLayout><br /> </SlidingDrawer><br /></FrameLayout><br /><br /></code></pre><br /></div><br /><br /><br />SlidingDrawer tag has two important attributes, android:handle and android:content; these attributes are actually references to other views which will be rendered as our drawer's handle and content. as you can see here we have two child views with the same id as specified for android:handle and android:content.<br /><br />That's it. we are now familiar with two other useful Android widgets...Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com10tag:blogger.com,1999:blog-8323121247454250099.post-87489004944878199682009-12-30T00:20:00.000-08:002009-12-30T01:08:13.472-08:00Android SelectorsGUI is always an important part of any application, because ordinary users dont know and don't care what's behind the scene; they want something easy to work with and nowadays attractive GUI is a must for most applications. although making an appealing and innovative interface needs something more than just programming skills and knowledge, every programmer should know how to customize different GUI components within whatever framework and environment they are working.<br />Today I'm gonna talk about one of the beautiful features of Android which gives you the ability to change the default behavior of GUI components. <br />when designing GUIs, most of the times you want to change the appearance of buttons, input Fields, menus and.... Android Selectors have been provided to solve all these kind of problems, they enable us to decide what to show and how to show based on different states of each components...for example you can tell a button to have black background color with red text color when it is in pressed state or whatever else.<br />In this post i will show you an example of customizing a ListView which is gonna look like this :<br /><br /><table><tbody><tr><br /><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lqWNM-79b1Y/SzsOGoEqgvI/AAAAAAAAAEo/lIACQpBpBAM/s1600-h/Untitled_01.jpg"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 212px; height: 320px;" src="http://2.bp.blogspot.com/_lqWNM-79b1Y/SzsOGoEqgvI/AAAAAAAAAEo/lIACQpBpBAM/s320/Untitled_01.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5420942083469509362" /></a><br /></td><br /><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lqWNM-79b1Y/SzsOLFXlyFI/AAAAAAAAAEw/wouI9zpKERg/s1600-h/Untitled_02.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 211px; height: 320px;" src="http://3.bp.blogspot.com/_lqWNM-79b1Y/SzsOLFXlyFI/AAAAAAAAAEw/wouI9zpKERg/s320/Untitled_02.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5420942160052996178" /></a><br /></td><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lqWNM-79b1Y/SzsORwy-oNI/AAAAAAAAAE4/E-K0JnqmDq0/s1600-h/Untitled_03.jpg"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 214px; height: 320px;" src="http://3.bp.blogspot.com/_lqWNM-79b1Y/SzsORwy-oNI/AAAAAAAAAE4/E-K0JnqmDq0/s320/Untitled_03.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5420942274789810386" /></a><br /></td></tr><br /></tbody></table><br /><br /><br />It is nothing but a simple ListView... believe me, and here is the XML which is being used to create it :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><br /><ListView android:id="@+id/list" <br /> android:layout_width="fill_parent"<br /> android:layout_height="wrap_content" <br /> android:dividerHeight="0dip"<br /> android:divider="@null" <br /> android:listSelector="@drawable/list_selector"<br /> android:layout_gravity="center" /><br /><br /></code></pre> <br /></div><br /><br /><br />the code which I've used to populate the list :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /> <br /> ListView view = (ListView)findViewById(R.id.list);<br /> view.setAdapter(new ArrayAdapter<String>(this, R.layout.menu_item, ITEMS));<br /> view.setOnItemClickListener(this); <br /><br /></code></pre><br /></div><br /><br />and finally, here is the content of menu_item.xml file :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><?xml version="1.0" encoding="utf-8"?>:<br /><br /><br /> <TextView xmlns:android="http://schemas.android.com/apk/res/android" <br /> android:layout_width="fill_parent"<br /> android:layout_height="wrap_content"<br /> android:textSize="12dip"<br /> android:textStyle="bold"<br /> android:paddingTop="20dip"<br /> android:paddingBottom="20dip"<br /> android:layout_gravity="center" <br /> android:gravity="center"<br /> android:background="@drawable/selector"<br /> android:textColor="@drawable/color_selector"/>:<br /> <br /><br /></code></pre><br /></div><br /><br /><br />see? it's a simple, ordinary list, there is no secret here but a simple trick; I've used selectors for both background and text color for our TextView, what do you think "selector" and "color_selector" are?<br />they actually refer to selector.xml and color_selector.xml files inside drawable directory, you can see the content of them below : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><br /><br /><selector xmlns:android="http://schemas.android.com/apk/res/android">:<br /> <item android:state_selected="true" android:drawable="@drawable/selector_s" />:<br /> <item android:state_pressed="true" android:drawable="@drawable/selector_s" />:<br /> <item android:drawable="@drawable/selector_d" />: <br /></selector>:<br /><br /><br /></code></pre><br /></div><br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><br /><br /><selector xmlns:android="http://schemas.android.com/apk/res/android">:<br /> <item android:state_selected="true" android:color="@color/black" />:<br /> <item android:state_pressed="true" android:color="@color/red" />:<br /> <item android:color="@color/white" />: <br /></selector>:<br /><br /></code></pre><br /></div><br /><br /><br />what does the content of color_selector file mean? it says that the text color will be black in "selected" state, red in "pressed" state and white otherwise, and i reckon now you should be able to figure out what the content of selector file means. <br />here is the content of selector_s and selector_d :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><bitmap xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:src="@drawable/pill"<br /> android:gravity="center" /><br /></code></pre><br /></div><br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><bitmap xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:src="@drawable/pill_s"<br /> android:gravity="center" /><br /></code></pre><br /></div><br /><br />as you might have noticed,I've also used "listSelector" attribute of our ListView to customize the behavior of the list when user is going through options in the list. <br />list_selector.xml file looks like this : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><br /><selector xmlns:android="http://schemas.android.com/apk/res/android"><br /> <item android:state_focused="true" android:drawable="@drawable/wood01" /><br /> <item android:drawable="@drawable/wood02" /> <br /></selector> <br /></code></pre><br /></div><br /><br /><br />and here are all the drawable objects i used in this application if you wanna give it a try and see how easy it works ;)<br /><br /><table><tbody><tr><br /><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lqWNM-79b1Y/SzsTsUHYHWI/AAAAAAAAAFY/8tNvbCo563k/s1600-h/wood02.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 120px; height: 160px;" src="http://3.bp.blogspot.com/_lqWNM-79b1Y/SzsTsUHYHWI/AAAAAAAAAFY/8tNvbCo563k/s320/wood02.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5420948228505345378" /></a><br /><br /></td><td><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lqWNM-79b1Y/SzsToaQkNzI/AAAAAAAAAFQ/aQwAbpbfHss/s1600-h/wood01.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 96px; height: 96px;" src="http://3.bp.blogspot.com/_lqWNM-79b1Y/SzsToaQkNzI/AAAAAAAAAFQ/aQwAbpbfHss/s320/wood01.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5420948161435023154" /></a><br /><br /></td></tr><br /><tr><br /><td><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lqWNM-79b1Y/SzsTfvzuszI/AAAAAAAAAFI/F07zoI-hSZE/s1600-h/pill_s.gif"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 158px; height: 40px;" src="http://2.bp.blogspot.com/_lqWNM-79b1Y/SzsTfvzuszI/AAAAAAAAAFI/F07zoI-hSZE/s320/pill_s.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5420948012600832818" /></a><br /><br /></td><td><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lqWNM-79b1Y/SzsTVlNVy9I/AAAAAAAAAFA/Bdlq4C4Lud4/s1600-h/pill.gif"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 162px; height: 43px;" src="http://1.bp.blogspot.com/_lqWNM-79b1Y/SzsTVlNVy9I/AAAAAAAAAFA/Bdlq4C4Lud4/s320/pill.gif" border="0" alt=""id="BLOGGER_PHOTO_ID_5420947837956770770" /></a><br /><br /></td></tr><br /></tbody></table>Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com14tag:blogger.com,1999:blog-8323121247454250099.post-6546121986768707422009-12-20T20:29:00.000-08:002009-12-22T21:46:52.166-08:00Watch out your background thread!The other day i came across something that i hadn't known before , do you <br />know that when you flip your android phone and go from portrait mode to landscape or vice versa, whatever application is running will be killed by android and recreated again?<br />It wouldn't be a problem as long as lifeCycle methods of your activity are implemented well, but what if you have a seperate thread which is working behind the scene and interact with your activity through a Handler, like what I have used in both <a href="http://android-journey.blogspot.com/2009/12/whitepage-applicationpart-one.html">WhitePage</a> and <a href="http://android-journey.blogspot.com/2009/11/yahoo-news-searchpart1activity-gui.html">YahooSearch</a> applications. what happens? if you switch the screen mode while our Thread is working the activity is killed, but our Thread is abviously not notified about what has happend, so it will send the message through the old handler to the old activity which might be waiting for Garbage Collection or whatever else but it is not the activity which you wanted to send your message to.<br />so I've come up with this solution, I override onSaveInstanceState() method of activity class, it checks whether there is any unfinished task running, if so it locks the thread and save it, we also need to check if there is any saved thread in onCreate() method and unblock it with a new Handler class so that Messages will be delivered through the right Handler.(NOTE : we can also use <a href="http://developer.android.com/reference/android/app/Activity.html#onRetainNonConfigurationInstance()">onRetainNonConfigurationInstance()</a> and <a href="http://developer.android.com/reference/android/app/Activity.html#getLastNonConfigurationInstance()">getLastNonConfigurationInstance()</a> methods which is apparently a better approach to solve this particular problem. <font color="red"> ***Please see comments on this Post*** </font>)<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><br /> @Override<br /> public void onCreate(Bundle savedInstanceState) {<br /> .<br /> .<br /> .<br /> <br /> if(savedInstanceState != null){<br /> this.thread = (FetcherThread)savedInstanceState.getSerializable("thread");<br /> this.thread.unlockIt(handler);<br /> }<br /> else{<br /> this.thread = new FetcherThread(handler);<br /> this.thread.start();<br /> } <br /> <br /> .<br /> .<br /> .<br /> <br /> }<br /><br /> @Override<br /> public void onSaveInstanceState(Bundle bundle){<br /> super.onSaveInstanceState(bundle); <br /> if(!this.thread.lastRequest_finished)<br /> this.thread.lockIt();<br /> <br /> bundle.putSerializable("thread", this.thread);<br /> } <br /><br /><br /></code></pre><br /></div><br /><br />On the other side, in our Thread I added a new flag which is checked just before the thread wants to send a message back, if everything is alright it carries on and send a message, otherwise it will wait until it's notified that it is safe to send a message.<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><br />public void run(){<br /> <br /> .<br /> .<br /> .<br /> <br /> Bundle content = new Bundle(); <br /> content.putSerializable("result",results);<br /> Message msg = new Message(); <br /> msg.setData(content);<br /> <br /> <br /> while(!this.launcherReady){<br /><br /> try{ <br /> synchronized (this) {<br /> this.wait(); <br /> } <br /> }catch(InterruptedException exp){<br /> ////Just Nothing<br /> } <br /> }<br /> <br /> this.callback.sendMessage(msg);<br /><br /> .<br /> .<br /> . <br /> <br /> }<br /> <br /> <br /> <br /> public synchronized void lockIt(){<br /> this.launcherReady = false; <br /> }<br /> <br /> public synchronized void unlockIt(Handler newOne){<br /> this.launcherReady = true;<br /> if(newOne != null)<br /> this.callback = newOne;<br /> this.notifyAll();<br /> }<br /><br /></code></pre><br /></div><br /><br /><br />you can also use Android <a href="http://developer.android.com/reference/android/os/AsyncTask.html">AsyncTask</a> class when you need a background task, which is a neat way to do it (if you will) , but be aware of the fact that it does not support the issue we discussed here by default. so you will still need to somehow handle it by yourself.Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com8tag:blogger.com,1999:blog-8323121247454250099.post-73402315599410486662009-12-19T23:55:00.000-08:002009-12-20T00:02:25.241-08:00When Android takes me beyond the time!!!To be honest I cannot get this question out of my mind that what's gonna happen in future? i mean how do you envision our future? if you had asked someone(even a computer professional) about the future of internet and web just 15 years ago, they would have probably had no idea how powerful it was going to be. It is so funny but sometimes i have a feeling as if internet has always been around, i cannot remember what we used to do without internet back then.<br />Actually I believe the concept of computer is somehow getting mature, The first period was the wave of modern Operating Systems and the concept of GUI, say since early 80s till mid 90s; The second Wave was internet and the concept of remote services, roughly since mid 90s till now. and when you look around yourself you can smell the third wave, it has already begun...Mobile phones are no longer just a simple phone, you hear something like 1Ghz processor mobile phones with 256Mb Ram and you remember just 10 years ago when you were bragging about your new pentium3 PC which was just 600Mhz and you were lucky if you had 256 RAM!!! <br />and more importantly when you are coding using Android API and somehow get involved with this mobile application industry, you sometimes see something that was not more than a dream and fantasy just 5 years ago... but not anymore.<br /><br />I envision a future in which ordinary computer users wouldn't need a big PC (by PC I mean any kind of Personal Computer regardless of its OS) or Laptop to get what they want, all they need would be achievable using their mobile phones, and nobody will even bother themselves to use these boring, old-fashion devices unless you are a professional user or you might say a gamer ;) .<br />But what's gonna happen for pc? Will it just die out? I dont think so....I think we will see a revolution! When smart phones are able to do what a PC can, it is a sign that something should get changed...step up time. <br />But how? I bet you ,like anyone else i know, use your computer mostly to get different types of remote services either web-based or not....it means GUI and Remote services are paying off, but it is so heavy weight, have you ever felt this? actually we're all feeling that and that's why we go and buy ourselves a smart phone; checking your email, chatting with your friends, Twitter, Facebook, Flicker ,Google maps , GPS applications, Bluetooth applications, taking high quality pictures, recording video, browsing through web and you can also give a call to a friend if it is necessary...all of these services and capabilities with a device as big as your palm...and that is what I'd like to call it Technology Transparency...if the first wave was the concept of GUI and Human computer interaction and the second one was Web and Remote services ,I would say the third one is the concept of Mobility and Technology Transparency.<br />Users by no means want to get dependent or limited, they want to get what they want whenever, wherever without any hassle, and that's why we need wireless communication...nobody is willing to figure out which wire should go where or what is the difference between this and that kind of sockets....and that is the reason behind Wireless sensor networks, we want to use some tiny sensors and just chuck them somewhere and without any configuration or any hassle they start serving us...and that is why we love smart phones, we don’t like to have to go to some particular place to get access to some basic services, we just want to pull our phone out of our pocket ,of course a touch screen one because button is not transparent enough!!, and get what we want... boundless and transparent services.<br /><br />when you analyse all these facts you get the impression that Chrome OS concept is a pretty possible candidate for future PCs, current PCs structure and Their operating systems are too difficult to deal with and a real headache for ordinary users who want to get some benefit out of it as fast and transparent as possible, having said that and taking account of this fact that high speed internet connections are becoming available for anyone and thank to Web2.0(which is a good example of Technology Transparency itself) many software giants have started or at least have been considering to provide an online versions of their products which means you would no longer need to have a Microsoft office, Adobe reader or Winamp installed on your local computer, although you might need them on you mobile phone and could have them installed there... <br />Fair enough, so you will not need a HD when you don’t want to install any software unless you need to store something, interestingly enough there are already some web-based services which allow you to store your stuff. all these things means that you would be able to get rid of your old-fashion OS (specially because you are already dealing with one on your mobile phone) and your HD and any hassle related to them such as organizing issues and security issues and let it to be taken care of by someone else(Transparently).<br />all you will need is a high speed connection and a OS which works as a gateway between your computer and infinite online services out there, I also think we will need something more than web and html, we will need some layer on top of web or at least beside it to make the whole thing more smooth and accessible (I mean Transparent ;) ).<br />That’s how I envision our future and what i call it Mobility and Technology Transparency wave. What’s your thought? How do you envision our future? and why?<br /><br /><table><tbody><tr><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/Sy3YqOukC5I/AAAAAAAAAEY/FPWoOZpMDFY/s1600-h/the-future.jpg"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 320px; height: 300px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/Sy3YqOukC5I/AAAAAAAAAEY/FPWoOZpMDFY/s320/the-future.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5417224146816076690" /></a><br /><br /></td><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lqWNM-79b1Y/Sy3YvN_Cn5I/AAAAAAAAAEg/Z3IZWiAAFrg/s1600-h/future_search4-1_petitinvention.jpg"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 320px; height: 300px;" src="http://2.bp.blogspot.com/_lqWNM-79b1Y/Sy3YvN_Cn5I/AAAAAAAAAEg/Z3IZWiAAFrg/s320/future_search4-1_petitinvention.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5417224232516099986" /></a><br /><br /></td></tr></tbody></table>Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com1tag:blogger.com,1999:blog-8323121247454250099.post-35397499250392809192009-12-18T23:36:00.000-08:002009-12-20T22:48:47.653-08:00WhitePage Application_Adding a new contact in Android 2.0Actually i was gonna talk about HTML parsing mechanism which i used for my <a href="http://android-journey.blogspot.com/2009/12/whitepage-applicationpart-2.html">Whitepages application</a>,but i thought it wouldn't be such a good idea to talk about it on this weblog since it has nothing to do with android and I just did it to fulfill my curiosity. the only thing we need to know is how to make our query which in this case was a simple GET query and then identify how data is wrapped inside HTML tags which can be easily done thanks to FireBug plugin, the rest will be some effort to figure out a good algorithm to extract data from HTML document as efficient as possible...<br />What i would like to talk about in this post is Contacts in android and how to add a new Contact to Android's Contact List. Initially i thought it was gonna be a simple thing to do but i gotta admit it, it was the first time i felt<br />a bit confused since I started android development. what happened was i wanted to write a piece of code to add a new contact when user selects one of our application menu options, I went to Android developers website(like i usually do) to find some clues on how to do that... as you might have already noticed there are some examples in Content Providers section about how to work with Contacts using People class, good, i was pretty sure that i got it... but when i started coding,Eclipse warned me that People class was deprecated... Beautiful!!!so what am i supposed to do if i shouldn't use People class, I was thinking with myself... having a look at People class documentation I found out that it's been replaced by a totally different mechanism and we should use another class called ContractsContact to interact with Contacts...<br />one of the things that struck me was the fact that there are more than two dozens classes and interfaces in android.provider package marked as deprecated and a completely different approach has been introduced for interacting with contracts since API level 5. this new API gives you a great level of flexibility and extensibility but as we all know everything has a price, if you want a flexible and extensible framework, no worries, but it comes with a bit more complexity, and I think it could be why they still prefer to stick with People class examples... ;)<br />Fair enough, here is the code that i used in <a href="http://android-journey.blogspot.com/2009/12/whitepage-applicationpart-2.html">our application</a> to add a new Contact according to what user has already selected : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />try{<br /> <br /><br />ArrayList<ContentProviderOperation> op_list = new ArrayList<ContentProviderOperation>();<br /> int backRefIndex = 0;<br /> op_list.add(ContentProviderOperation.newInsert(RawContacts.CONTENT_URI)<br /> .withValue(RawContacts.ACCOUNT_TYPE, null)<br /> .withValue(RawContacts.ACCOUNT_NAME, null)<br /> .build());<br /><br /> op_list.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)<br /> .withValueBackReference(Data.RAW_CONTACT_ID, backRefIndex)<br /> .withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE)<br /> .withValue(StructuredName.DISPLAY_NAME, this.selectedItem.getName())<br /> .build());<br /> <br /> op_list.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)<br /> .withValueBackReference(Data.RAW_CONTACT_ID, backRefIndex)<br /> .withValue(Data.MIMETYPE, StructuredPostal.CONTENT_ITEM_TYPE)<br /> .withValue(StructuredPostal.FORMATTED_ADDRESS, this.selectedItem.getAddress())<br /> .build());<br /> <br /> op_list.add(ContentProviderOperation.newInsert(Data.CONTENT_URI)<br /> .withValueBackReference(Data.RAW_CONTACT_ID, backRefIndex)<br /> .withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE)<br /> .withValue(Phone.NUMBER, this.selectedItem.getPhone())<br /> .withValue(Phone.TYPE, Phone.TYPE_HOME)<br /> .withValue(Phone.LABEL, "<Inserted by White Pages Application>")<br /> .build()); <br /><br /> ContentProviderResult[] result = getContentResolver().applyBatch(ContactsContract.AUTHORITY, op_list); <br /> <br /> <br /> }catch(OperationApplicationException exp){<br /> <br /> exp.printStackTrace();<br /> <br /> }catch(RemoteException exp){<br /> <br /> exp.printStackTrace();<br /> }<br /></code></pre><br /></div><br /><br />dont freak out ;) , although it might seem quite different than what you are familiar with, it's mostly because i used Batch insert technique and ContentProviderOperation.Builder class which have been introduced in API level 5. <br />you can still use getContentResolver().insert() and ContentValue objects but it is recommended to use new batch technique over traditional insert and update method.<br />what is important here is that you first need to create a RawContact and then use its ID to add some data like name, phone number and address, in traditional method you would need to insert a RawContact, get its ID and then use that ID in subsequent operations, I used withValueBackReference() method here which has been provided for handling these sort of cases when you are using batch technique.<br />You can find some good information about this whole thing in <a href="http://developer.android.com/reference/android/provider/ContactsContract.Data.html">Data class documentation.</a><br /><br /><table><tbody><tr><td><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lqWNM-79b1Y/SyyG8uOYlyI/AAAAAAAAAD4/yK2C0YXpaKI/s1600-h/Untitled1.jpg"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 240px; height: 320px;" src="http://1.bp.blogspot.com/_lqWNM-79b1Y/SyyG8uOYlyI/AAAAAAAAAD4/yK2C0YXpaKI/s320/Untitled1.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5416852829578499874" /></a><br /></td><td><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/SyyHDhJrRcI/AAAAAAAAAEA/gESqO9lV_hI/s1600-h/Untitled0.jpg"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 250px; height: 320px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/SyyHDhJrRcI/AAAAAAAAAEA/gESqO9lV_hI/s320/Untitled0.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5416852946328176066" /></a><br /><br /></td></tr></tbody></table>Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com5tag:blogger.com,1999:blog-8323121247454250099.post-40916723833222497822009-12-13T21:27:00.000-08:002009-12-20T22:16:58.867-08:00WhitePage Application_Part 2<a href="http://android-journey.blogspot.com/2009/12/whitepage-applicationpart-one.html">In my last post</a> we saw how the main page of our application works, in this post we will be discussing some other features of our application. if you remember, the result of our search is shown in a page like this:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_lqWNM-79b1Y/SyXNGVaHyxI/AAAAAAAAADU/olP04QlEZD0/s1600-h/Untitled04.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 215px; height: 320px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/SyXNGVaHyxI/AAAAAAAAADU/olP04QlEZD0/s320/Untitled04.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5414959635692505874" /></a><br /><br /><br /><br />I've used ListView for this page and here is our layout file for this page :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><br /><?xml version="1.0" encoding="utf-8"?><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:orientation="vertical"<br /> android:layout_width="fill_parent"<br /> android:layout_height="fill_parent"<br /> android:baselineAligned="true"><br /> <br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:id="@+id/resultLayout"<br /> android:orientation="vertical"<br /> android:layout_width="fill_parent"<br /> android:layout_height="fill_parent"<br /> android:baselineAligned="true" <br /> android:background="@color/black" <br /> android:layout_margin="2dip" <br /> android:scrollbars="horizontal"> <br /> <br /> <br /> <ListView android:text=" text " <br /> android:id="@+id/listView"<br /> android:minWidth="70dip" <br /> android:layout_height="fill_parent" <br /> android:layout_width="fill_parent" <br /> android:padding="5dip"<br /> android:divider="@color/white"<br /> android:dividerHeight="10dip"/> <br /> <br /> <br /><br /><br /><br /><br /></LinearLayout><br /><br /></LinearLayout><br /><br /></div><br /><br />it's pretty simple,isn't it? what we need to know is how to tell to a ListView to show whatever we want to show and and how to format it. in our case after each search we will have an array of Result class, Result objects are simple POJOs which have a name, address and a phone number,we also want to show our result is three different lines(you might need to show an image in each row or have a more complicated structure for each row).<br />ListView uses something called ListAdapter to get all data needed to be shown and know how to show it, there are some predefined subclasses of ListAdapter such as <a href="http://developer.android.com/reference/android/widget/ArrayAdapter.html">ArrayAdapter</a>, <a href="http://developer.android.com/reference/android/widget/CursorAdapter.html">CursorAdapter</a> and <a href="http://developer.android.com/reference/android/widget/SimpleAdapter.html">SimpleAdapter</a> which provide some convenient methods for interacting with data for some frequently used mechanisms such as XML Documents or Database.<br />We can also customize these Adapters by simply inheriting from them and extending their behavior and that's what I've done here. <br />you can see our extended Adapter below : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /> private class MyAdapter extends ArrayAdapter {<br /> <br /> private Activity context;<br /> <br /> public MyAdapter(Result[] items){<br /> super(MainActivity.this, R.layout.item,items);<br /> this.context = MainActivity.this;<br /> }<br /> <br /> @Override<br /> public View getView(int position,View convertView,ViewGroup parent){<br /> <br /> LayoutInflater inflater= this.context.getLayoutInflater();<br /> View row=inflater.inflate(R.layout.item, null);<br /> <br /> TextView name = (TextView)row.findViewById(R.id.name);<br /> TextView loc = (TextView)row.findViewById(R.id.location);<br /> TextView phone = (TextView)row.findViewById(R.id.phone);<br /> <br /> Result temp = (Result)getItem(position);<br /> <br /> name.setText(temp.getName());<br /> loc.setText(temp.getAddress());<br /> phone.setText(temp.getPhone());<br /> <br /> return row;<br /> }<br /> <br /> }<br /></code></pre><br /></div><br /><br /><br />getView() method is our key method here, it is sent the position of a row in ListView and it's responsible to return a View Object representing that row which will be shown by ListView later. it is really cool because you can use<br />a View object and it means that you will be able to literally do whatever you want, i mean it would give you a great opportunity over how much you can customize your rows in ListViews.(we forget some basic concepts of OOP sometimes,<br />or should i say we underestimate how significant they could be, one of these basic concepts is Polymorphism...just look how nice it works here.... anyway just thought it'd be worthwhile to mention it ;) ).<br /><br />Like any other View Objects we've created so far we can either use XML layout file or just hard code it. i used the first approach here, my layout file's name is item.xml and it looks like this :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><br /><?xml version="1.0" encoding="utf-8"?><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:orientation="vertical"<br /> android:layout_width="fill_parent"<br /> android:layout_height="fill_parent"<br /> android:baselineAligned="true"><br /> <br /><br /> <br /> <br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:orientation="vertical"<br /> android:layout_height="wrap_content" <br /> android:layout_marginLeft="5dip" <br /> android:layout_marginRight="5dip" <br /> android:layout_marginTop="5dip" <br /> android:layout_gravity="left" <br /> android:layout_width="wrap_content"><br /> <br /> <TextView android:text="Name" <br /> android:id="@+id/name"<br /> android:layout_width="wrap_content" <br /> android:layout_height="wrap_content" <br /> android:minWidth="70dip"/> <br /> <br /> <TextView android:text="Location" <br /> android:id="@+id/location"<br /> android:layout_width="wrap_content" <br /> android:layout_height="wrap_content" <br /> android:minWidth="70dip"/> <br /> <br /> <TextView android:text="PhoneNumber" <br /> android:id="@+id/phone"<br /> android:layout_width="wrap_content" <br /> android:layout_height="wrap_content" <br /> android:minWidth="70dip"/> <br /> <br /><br /></LinearLayout><br /><br /><br /></LinearLayout><br /><br /></div><br /><br />OK, let's get back to our getView() method that might seem a bit confusing....but it's not, believe me ;) .<br />all we need to do is to convert our layout file into a View Object, actually it's not a new thing...it's done in our all application but behind the scene. All we need to do is to get an instance of LayoutInflater object using getLayoutInfalter() method of Context class (Our Activity) and call inflate() method.once we have our view we can get the Result Object in that position and then fill our Textviews with appropriate data.<br /><br />remember that each time that user does a search and there is any Result for that search we use the following code to renew our data in ListView : <br /><br />listview.setAdapter(new MyAdapter(results)); <br /><br /><br />I'm not sure whether it's a good way to do this though. ;)<br /><br />The last thing I'm gonna talk about is menus and how to use them in our applications. we've got a menu with three options in our application but two options must be disabled unless we are in Result page, if user presses their phone menu button they will see something like this depending on which page they are currently in : <br /><br /><table><tbody><tr><td><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lqWNM-79b1Y/SyXRUxeXA2I/AAAAAAAAADk/Yzv_E2Iwvm0/s1600-h/Untitled07.jpg"><img style="float:right; margin:0 0 10px 10px;cursor:pointer; cursor:hand;width: 210px; height: 320px;" src="http://2.bp.blogspot.com/_lqWNM-79b1Y/SyXRUxeXA2I/AAAAAAAAADk/Yzv_E2Iwvm0/s320/Untitled07.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5414964281791152994" /></a><br /><br /></td><td><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lqWNM-79b1Y/SyXRPjgo3pI/AAAAAAAAADc/aQhLc__cydY/s1600-h/Untitled06.jpg"><img style="float:left; margin:0 10px 10px 0;cursor:pointer; cursor:hand;width: 219px; height: 320px;" src="http://2.bp.blogspot.com/_lqWNM-79b1Y/SyXRPjgo3pI/AAAAAAAAADc/aQhLc__cydY/s320/Untitled06.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5414964192143269522" /></a><br /><br /></td></tr></tbody></table><br /><br /><br />to achieve this we need to override three methods of Activity class which you can see below :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /> <br /> @Override<br /> public boolean onCreateOptionsMenu(Menu menu) {<br /> <br /> menu.add(0, 1, 0, "Show Saved entries"); <br /> menu.add(0, 2, 0, "Add to my contacts");<br /> menu.add(0,3,0,"Save this entry");<br /> return true;<br /> <br /> }<br /> <br /> @Override<br /> public boolean onPrepareOptionsMenu(Menu menu){<br /> //menu items are disable when we are in the main page...<br /> for(int i=1;i<3;i++){<br /> menu.getItem(i).setEnabled(!this.main); <br /> }<br /> <br /> return true; <br /> }<br /> <br /> <br /> @Override<br /> public boolean onOptionsItemSelected(MenuItem item){<br /> <br /> int id = item.getItemId();<br /> this.selectedItem = (Result)view.getSelectedItem();<br /> <br /> switch(id){<br /> <br /> case 1 : showSavedData();<br /> break;<br /> case 2 : addToContact();<br /> break;<br /> case 3 : saveItem();<br /> break;<br /> <br /> default : assert false : "Invalid Options!"; <br /> <br /> }<br /> <br /> return true; <br /> }<br /><br /></code></pre><br /></div><br /><br /><br />I also want to have another menu which shows our options when user clicks on one of our result items in ViewList, we would need to be able to get notified when an item is clicked, ListView class has a setOnItemClickedListener() method which gets an instance of OnItemClickListener class this class has a callback method named onItemClicked(), you can see my implementation of this method here : <br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />public void onItemClick(AdapterView<?> adapter, View arg1, int position, long arg3) {<br /> <br /> CharSequence[] options = {"ADD TO CONTACTS","SAVE THIS ENTRY"};<br /> <br /> this.selectedItem = (Result)adapter.getItemAtPosition(position);<br /> <br /> if(this.resultOptions == null){ <br /> AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);<br /> builder.setTitle("Options");<br /> builder.setItems(options, new DialogInterface.OnClickListener() {<br /><br /> @Override<br /> public void onClick(DialogInterface dialog, int which) {<br /> // TODO Auto-generated method stub<br /> <br /> if(which == 0)<br /> addToContact();<br /> else<br /> saveItem();<br /> <br /> dialog.cancel(); <br /> }<br /> });<br /><br /> this.resultOptions = builder.create();<br /> } <br /> <br /> this.resultOptions.show();<br /> <br /> }<br /></code></pre><br /></div><br /><br />so when user clicks on any item they will be shown something like this : <br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lqWNM-79b1Y/SyXSemSFVNI/AAAAAAAAADs/5rWJ8KOB0-Q/s1600-h/Untitled08.jpg"><img style="display:block; margin:0px auto 10px; text-align:center;cursor:pointer; cursor:hand;width: 217px; height: 320px;" src="http://3.bp.blogspot.com/_lqWNM-79b1Y/SyXSemSFVNI/AAAAAAAAADs/5rWJ8KOB0-Q/s320/Untitled08.jpg" border="0" alt=""id="BLOGGER_PHOTO_ID_5414965550097192146" /></a><br /><br /><br /><br />I will talk about WhitePageExtractor class that has been used to extract our data from www.whitepages.com.au and also I'm gonna figure out how to work with phone contacts and add a new contact in my next post...Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com1tag:blogger.com,1999:blog-8323121247454250099.post-73038711684714301632009-12-10T23:24:00.000-08:002009-12-20T22:19:01.077-08:00WhitePage Application_Part OneBefore starting i gotta say something, Android is amazing...i mean more you work with it more you understand how well designed this Google's creature is. i had a painful experience with J2ME a couple of years ago and i cannot forget how frustrating it was. although i am a Java lover, i think we should admit that there is no light at end of J2ME tunnel.<br />Android on the other hand is really fascinating; beautiful API, comprehensive documentation and snappy simulator; what else would someone expect from a good framework?<br /><br />Ok...let's get down to our business, in my last post I talked about html data extraction and said I would develope an application to do what exactly www.whitepages.com.au is doing...I have done it and I'm gonna be talking about this application in a few next posts.<br />I have examined some of basic android GUI capabilities such as menus,dialogs,List view and simple animations which will be discussed in this post and next post, remember that codes that are provided here should not be considered as the best possible way to do stuff...they are just examples for me and you to get familiar with this whole thing...<br /><br />anyway,our application main page looks like this :<br /><br /><table><tbody><tr><td><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lqWNM-79b1Y/SyH0dpJHaRI/AAAAAAAAACE/2yLYpVylMug/s1600-h/Untitled09.jpg"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 320px; height: 200px;" src="http://1.bp.blogspot.com/_lqWNM-79b1Y/SyH0dpJHaRI/AAAAAAAAACE/2yLYpVylMug/s320/Untitled09.jpg" alt="" id="BLOGGER_PHOTO_ID_5413877017173518610" border="0" /></a><br /><br /></td><td><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lqWNM-79b1Y/SyH0Wt530qI/AAAAAAAAAB8/h8OgvhPyhho/s1600-h/Untitled02.jpg"><img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 219px; height: 320px;" src="http://1.bp.blogspot.com/_lqWNM-79b1Y/SyH0Wt530qI/AAAAAAAAAB8/h8OgvhPyhho/s320/Untitled02.jpg" alt="" id="BLOGGER_PHOTO_ID_5413876898192675490" border="0" /></a><br /><br /><br /></td></tr></tbody></table><br /><br /><br />if you wanna have a good looking GUI in both portrait and landscape modes, there are two possibilities, either design your layout in a flexible fashion to support both modes or use a seperate layout file for each mode(default layout directoryfor portrait mode and "layout-land" directory which will be used for landscape mode, so if you need a different layout for<br />landscape mode just put your landscape specific designed file in "layout-land" instead of "layout" directory).<br />I used a single layout file since our GUI is simple and i could manage to have a dynamic GUI using a single layout file :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><br /><?xml version="1.0" encoding="utf-8"?><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="vertical"<br />android:layout_width="fill_parent"<br />android:layout_height="fill_parent"<br />android:baselineAligned="true"><br /><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:id="@+id/mainLayout"<br />android:orientation="vertical"<br />android:layout_width="fill_parent"<br />android:layout_height="fill_parent"<br />android:baselineAligned="true"<br />android:background="@color/black"><br /><br /><br /><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="horizontal"<br />android:layout_height="wrap_content"<br />android:layout_marginLeft="5dip"<br />android:layout_marginRight="5dip"<br />android:layout_marginTop="25dip"<br />android:layout_width="wrap_content"<br />android:layout_gravity="center"><br /><br /><TextView android:text="Name : "<br />android:id="@+id/NameLable"<br /> android:layout_height="wrap_content"<br /> android:layout_width="wrap_content"<br /> android:minWidth="70dip"/><br /><br /><EditText android:text=" "<br />android:id="@+id/name"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:maxWidth="400dip"<br />android:gravity="top"<br />android:minWidth="230dip"></EditText><br /><br /><br /></LinearLayout><br /><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="horizontal"<br />android:layout_height="wrap_content"<br />android:layout_marginLeft="5dip"<br />android:layout_marginRight="5dip"<br />android:layout_gravity="center"<br />android:layout_width="wrap_content"><br /><br /><TextView android:text="Location : "<br />android:id="@+id/locationLabel"<br /> android:layout_width="wrap_content"<br /> android:layout_height="wrap_content"<br /> android:minWidth="70dip"/><br /><br /><EditText android:text=" "<br />android:id="@+id/location"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:maxWidth="400dip"<br />android:gravity="top"<br />android:minWidth="230dip"></EditText><br /><br /><br /></LinearLayout><br /><br /><br /><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="horizontal"<br />android:layout_height="wrap_content"<br />android:layout_marginLeft="5dip"<br />android:layout_marginRight="5dip"<br />android:layout_width="wrap_content"<br />android:layout_gravity="center"><br /><br /><TextView android:text="Initial : "<br />android:id="@+id/initialLabel"<br /> android:layout_width="wrap_content"<br /> android:layout_height="wrap_content"<br /> android:minWidth="70dip"/><br /><br /><EditText android:text=" "<br />android:id="@+id/initial"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:maxWidth="400dip"<br />android:gravity="top"<br />android:minWidth="230dip"></EditText><br /><br /><br /></LinearLayout><br /><br /><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="horizontal"<br />android:layout_height="wrap_content"<br />android:layout_marginLeft="5dip"<br />android:layout_marginRight="5dip"<br />android:layout_marginTop="25dip"<br />android:layout_width="wrap_content"<br />android:layout_gravity="center"><br /><br /><Button android:text="@string/searchBtn"<br />android:id="@+id/search"<br />android:layout_height="wrap_content"<br />android:gravity="center_horizontal|center"<br />android:layout_width="150dip"></Button><br /><br /></LinearLayout><br /><br /></LinearLayout><br /><br /></LinearLayout><br /><br /></div><br /><br /><br />when user presses search button, if our thread cannot find any result, a dialog will be shown to user telling him no result have been found :<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lqWNM-79b1Y/SyH2Zk6_IdI/AAAAAAAAACM/XA4z2w8elK4/s1600-h/Untitled05.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 223px; height: 320px;" src="http://1.bp.blogspot.com/_lqWNM-79b1Y/SyH2Zk6_IdI/AAAAAAAAACM/XA4z2w8elK4/s320/Untitled05.jpg" alt="" id="BLOGGER_PHOTO_ID_5413879146344292818" border="0" /></a><br /><br />Using dialogs in Android is pretty easy and straight forward, all you need to do is to use AlertDialog.Builder class to create<br />your Dialog instance, once you have your Dialog instance created, you can show it or hide it whenever you want, below is<br />our code for our dialog you just saw :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><br />if(results == null || results.length == 0){<br /> if(alertDialog == null){<br /> AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);<br /><br />builder.setMessage("No Result Could be found").setNeutralButton("OK",<br />new DialogInterface.OnClickListener() {<br /> <br /> <br />@Override<br />public void onClick(DialogInterface dialog, int which) {<br /> dialog.cancel();<br /> }<br /> <br />});<br /> <br />MainActivity.this.alertDialog = builder.create();<br /> <br />}<br /> <br />alertDialog.show();<br /><br /></code></pre><br /></div><br /><br /><br />you can also use setPositiveButton() or set NegativeButton() just like setNeutralButton() if you need to have yes/no or agree/disagree buttons.<br /><br /><br />Obviously there will be a time gap between user presses the search button and when the data extraction process gets finished because we are getting our data using network. it is usually a good practice to show users that something is happening behind the scene specially when delay is likely to be more than 1 or 2 seconds...thankfully Android has taken care of this issue... when search button is pressed we will start our thread to extract data and then we simply<br />use following code :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><br />ProgressDialog.show(this, "","Please wait...", true);<br /><br /></div><br /><br />show() method returns an instance of ProgressDialog class which can be used to interact with the showing dialog, in this case we will call cancel() method later when the process finishes.<br /><br />after calling show() method our application will be looking like this :<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lqWNM-79b1Y/SyH3s71yJXI/AAAAAAAAACU/y_ymAYtQt5M/s1600-h/Untitled03.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 223px; height: 320px;" src="http://1.bp.blogspot.com/_lqWNM-79b1Y/SyH3s71yJXI/AAAAAAAAACU/y_ymAYtQt5M/s320/Untitled03.jpg" alt="" id="BLOGGER_PHOTO_ID_5413880578425628018" border="0" /></a><br /><br /><br />Fair enough, we have already handled the situation where there is no result, but what happens when there is some results?<br />I've used Android ListView to show the result to users :<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lqWNM-79b1Y/SyH4K2dGMyI/AAAAAAAAACc/HSv4OvA3r7Q/s1600-h/Untitled04.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 215px; height: 320px;" src="http://1.bp.blogspot.com/_lqWNM-79b1Y/SyH4K2dGMyI/AAAAAAAAACc/HSv4OvA3r7Q/s320/Untitled04.jpg" alt="" id="BLOGGER_PHOTO_ID_5413881092375982882" border="0" /></a><br /><br /><br />I'm gonna talk about ListView class and how to use it in a customized manner next time. what I'm gonna talk about right now is how to use Animation in android,it's not gonna be comprehensive but it is a simple example which has helped me to have a better understanding of Animation handling mechanism in Android and hopefully it will be helpful for you as well.<br />Actually when search button is pressed current page(containing Text Feilds and search button) does not just simply replace by result page but it will start to rotating and shrinking until it completely disappears and then Result page<br />gradually replaces previous page...<br /><br /><br /><table><tbody><tr><td><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lqWNM-79b1Y/SyH5t2hefWI/AAAAAAAAADE/2sLPrecVfD8/s1600-h/Untitled10.jpg"><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 224px; height: 320px;" src="http://1.bp.blogspot.com/_lqWNM-79b1Y/SyH5t2hefWI/AAAAAAAAADE/2sLPrecVfD8/s320/Untitled10.jpg" alt="" id="BLOGGER_PHOTO_ID_5413882793201401186" border="0" /></a><br /><br /></td><td><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lqWNM-79b1Y/SyH6B-Rw8QI/AAAAAAAAADM/PfrfIMvcqTY/s1600-h/Untitled01.jpg"><img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 222px; height: 320px;" src="http://3.bp.blogspot.com/_lqWNM-79b1Y/SyH6B-Rw8QI/AAAAAAAAADM/PfrfIMvcqTY/s320/Untitled01.jpg" alt="" id="BLOGGER_PHOTO_ID_5413883138880368898" border="0" /></a><br /><br /><br /></td></tr></tbody></table><br /><br /><br />Initially i thought it was gonna be so tricky and chalenging given the fact that this sort of stuff had always been a big headache for me but once again I impressed by Android capabilities and how well this API has been designed.<br />one of the most interesting things which i hadn't realized before but we all gotta know is startAnimation() method of View class. yes...simple as this, all Views have a startAnimation() method which has an Animation Object as input parameter and can be called whenever you want to apply an animation on a view.<br />you can create animation objects problematically or using XML file, I've used the latter.<br />here is the XML file which is used to animate our application's main page after search button get pressed :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><br /><?xml version="1.0" encoding="utf-8"?><br /><br /><set xmlns:android="http://schemas.android.com/apk/res/android" android:shareInterpolator="false"><br /> <scale<br /> android:interpolator="@android:anim/accelerate_decelerate_interpolator"<br /> android:fromXScale="1.0"<br /> android:toXScale="0.8"<br /> android:fromYScale="1.0"<br /> android:toYScale="0.6"<br /> android:pivotX="50%"<br /> android:pivotY="50%"<br /> android:fillAfter="false"<br /> android:duration="500" /><br /> <br /> <br /> <set android:interpolator="@android:anim/decelerate_interpolator"><br /> <scale<br /> android:fromXScale="0.8"<br /> android:toXScale="0.0"<br /> android:fromYScale="0.6"<br /> android:toYScale="0.0"<br /> android:pivotX="50%"<br /> android:pivotY="50%"<br /> android:startOffset="700"<br /> android:duration="700"<br /> android:fillBefore="false" /><br /> <rotate<br /> android:fromDegrees="0"<br /> android:toDegrees="-60"<br /> android:toYScale="0.0"<br /> android:pivotX="50%"<br /> android:pivotY="50%"<br /> android:startOffset="700"<br /> android:duration="700" /><br /> </set><br /></set><br /><br /></div><br /><br />as you can see we used three different animation effects, two Scale animation and one Rotate Animation, there are two more animation types that can be used as well, Alpha animation (<alpha> tag) and Translation animation (<translate> tag). you can use <set> tags to combine two or more animation instances, you can also use nested <set> tags like I have used in this example(actually i sorta pinched this XML file from Android API Demo ;) ).<br />first our view gets shrunk from its original size (1.0X,1.0X) to (0.8X,0.6X), it will take 500 milliseconds to finish, then after 200 milliseconds shrunk view starts to rotating and shrinking simultaneously until it disappears (0.0X.0.0X).<br /><br />for Resultpage i used a simple animation :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><br /><?xml version="1.0" encoding="utf-8"?><br /><br /> <br /> <set xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:interpolator="@android:anim/decelerate_interpolator"><br /> <scale<br /> android:fromXScale="0.1"<br /> android:toXScale="1.2"<br /> android:fromYScale="0.1"<br /> android:toYScale="1.2"<br /> android:pivotX="50%"<br /> android:pivotY="50%"<br /> android:duration="800"<br /> android:fillBefore="false" /><br /><br /> </set><br /><br /></div><br /><br />and here is the code that has been used to run these two animation and replace main view by result view :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br /><br />LinearLayout LL = (LinearLayout) findViewById(R.id.mainLayout);<br />Animation animation = AnimationUtils.loadAnimation(MainActivity.this,R.anim.effect);<br />Animation reverse = AnimationUtils.loadAnimation(MainActivity.this,R.anim.reverse);<br /><br /> LL.startAnimation(animation);<br /> resultPage.startAnimation(reverse);<br /><br /><br /> animation.setAnimationListener(new AnimationListener() {<br /><br /> @Override<br /> public void onAnimationStart(Animation animation) {<br /> // TODO Auto-generated method stub<br /><br /> }<br /><br /> @Override<br /> public void onAnimationRepeat(Animation animation) {<br /> // TODO Auto-generated method stub<br /><br /> }<br /><br /> @Override<br /> public void onAnimationEnd(Animation animation) {<br /><br /> setContentView(resultPage);<br /> main = false;<br /><br /> }<br /> });<br /><br /><br /></code></pre><br /></div>Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com0tag:blogger.com,1999:blog-8323121247454250099.post-50767828890569240282009-12-04T02:48:00.000-08:002009-12-04T02:53:41.312-08:00What do you think?One of the interesting facts about mobile phones is that although they provide Internet browsers but users (I mean all of us) are reluctant to use it, i dont know exactly why, maybe because phone browsers are not efficient enough...but i feel it has something to do with the perception of mobile phones in our minds and also the fact that people tend to expect applications on mobile phones simpler and easier to deal with than PC applications.<br /> that's why there are heaps of application out there providing some services to users which has already been available on the WEB.you probabaly have some applications like Youtube or Flicker client installed on your mobile phone or some widgets like Weather Forecast or Web Search. they are actually just a neaty, easy to use,integrated and graphically more attractive version of some well known Web-Based Services.<br /><br />I was talking with one of my friends the other day, he has an iPhone and he showed me a couple of applications which he had downloaded before, among them were a few applications that all they were doing was to go and parse the HTML code of a Website, extract what they want and show them to users in a bit more appealing fashion, plus they had some new and kind of innovative extra options which cannot be done when you are using a web browser. <br />initially I was thinking it would be ridiculous to spend some time and effort to extract some information out of HTML code of another site and show it to people when they can go straight to the originall Web site.but my friend convinced me that it's actually a pretty good idea, he showed me the Rate of the application and talked about how handy it was for him and why he prefered using this application rathar that going to original web site...YES...he really tempted me to give it try....<br /><br />In the first glance the whole thing might seem a bit dodgy and sneaky, parsing the HTML code of a site and use it to make another application which is actually doing the same thing....but the reallity is that it is a kind of WIN-WIN situation for everyone...if there is a Web site out there Willing to serving users and because of whatever reasonthey haven't got a decent Mobile Phone interface for their services what would be more delightful than having<br />someone else develope it for you for FREE...<br /> <br /><br />Fair enough...but why am i talking about these stuff?....actually i am just trying to adjust what i am about to start to do.. ;)<br />yes..i'm gonna develope an application which will be doing exactly what www.whitepages.com.au is doing. and most challenging part of this application will probabely be how to extract required information out of HTML page since i have had no experience in this area so far...sounds like Fun...doesn't it? ;)<br />I have also decided to design a bit more better GUI for next application, although it's always been a real headache to me.<br />But the truth is even if you have a most reliable,secure and efficient system with a crappy GUI, no one is gonna use that...<br /><br />anyway let me know what you think about the whole HTML data extraction thing, have got any application like the one my friend had?which features were there which made you think it'd better to use it rather than going to originall website?<br />or how many times did you say 'I wish i could do this' when you were visiting a website through your phone browser and felt like you stuck with a isolated tool which cannot be integrated with other facilities of your SMART Phone?Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com0tag:blogger.com,1999:blog-8323121247454250099.post-73644614803867659202009-12-02T00:13:00.001-08:002009-12-20T22:19:50.327-08:00Pull ParserAlthough our last application is working pretty good with SAX Parser, but as i said i'm gonna use Pull Parser instead, to see how it works, as far as i know Pull Parsing is a bit slower that SAX parsing but it gives the ability to stop parsing in the middle of a document and this means that Pull Parsing would be well suited in a situation in which we just need some portions of XML and not the whole of it.<br />In our application we need the whole xml but i think now that we are dealing with XML parsing it would be a good opportunity to dig into PullParser method and draw a comparison between these two XML parsing methods.(you can also use DOM parsing, but i reckon it's safe to say that you shouldn't ever use it unless you really have to).<br />Here is our Pull Parser class:<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><code><br />package com.news.search;<br /><br />import java.io.InputStream;<br />import java.util.ArrayList;<br />import java.util.HashMap;<br />import java.util.List;<br />import java.util.Map;<br /><br />import org.xmlpull.v1.XmlPullParser;<br />import android.util.Xml;<br /><br />import com.yahoo.search.ImageThumbnail;<br />import com.yahoo.search.NewsSearchResult;<br />import com.yahoo.search.xmlparser.XmlParserImageThumbnail;<br />import com.yahoo.xml.XmlParser;<br /><br /><br />public class NewsPullParser {<br /><br /> private InputStream input;<br /> <br /> public final static String TITLE = "Title";<br /> public final static String SUMMARY = "Summary";<br /> public final static String URL = "Url";<br /> public final static String CLICK_URL = "ClickUrl";<br /> public final static String SOURCE = "NewsSource";<br /> public final static String SOURCE_URL = "NewsSourceUrl";<br /> public final static String LANGUAGE = "Language";<br /> public final static String PUBLISH_DATE = "PublishDate";<br /> public final static String MOD_DATE = "ModificationDate";<br /> public static ArrayList<string> newsTags;<br /> static{<br /> newsTags = new ArrayList<string>();<br /> newsTags.add(TITLE);<br /> newsTags.add(SUMMARY);<br /> newsTags.add(URL);<br /> newsTags.add(CLICK_URL);<br /> newsTags.add(SOURCE);<br /> newsTags.add(SOURCE_URL);<br /> newsTags.add(LANGUAGE);<br /> newsTags.add(PUBLISH_DATE);<br /> newsTags.add(MOD_DATE);<br /> }<br /> <br /> <br /><br /> public NewsPullParser(InputStream stream){<br /> this.input = stream;<br /> }<br /> <br /> public NewsSearchResult[] parse(List<string> TagFilter) throws Exception{<br /> <br /> ArrayList<newssearchresult> content = null;<br /> XmlPullParser parser = Xml.newPullParser();<br /> <br /> try{<br /> <br /> parser.setInput(this.input,"UTF-8");<br /> HashMap map = null;<br /> int event = parser.getEventType();<br /> <br /> while (event != XmlPullParser.END_DOCUMENT){<br /> String name = null;<br /> <br /> switch (event){<br /> <br /> case XmlPullParser.START_DOCUMENT: content = new ArrayList<newssearchresult>();<br /> break;<br /> case XmlPullParser.START_TAG:<br /> name = parser.getName();<br /> if (name.equalsIgnoreCase("Result")){<br /> map = new HashMap();<br /> }else if(map != null && newsTags.contains(name) && TagFilter.contains(name)){<br /> <br /> map.put(name,parser.nextText());<br /> <br /> }<br /> break;<br /> <br /> case XmlPullParser.END_TAG:<br /> name = parser.getName();<br /> if (name.equalsIgnoreCase("RESULT") && map != null)<br /> content.add(new PullParserNewsSearchResult(map));<br /> <br /> break;<br /> }<br /> <br /> event = parser.next();<br /> }<br /><br /> <br /> }catch(Exception exp){<br /> throw exp;<br /> }<br /> <br /> return content.toArray(new NewsSearchResult[content.size()]);<br /> }<br /> <br /> <br /> private class PullParserNewsSearchResult implements NewsSearchResult {<br /> private Map result;<br /> <br /><br /> public PullParserNewsSearchResult(Map result) {<br /> this.result = result;<br /> }<br /><br /> public String getTitle() {<br /> return (String)result.get(TITLE);<br /> }<br /><br /> public String getSummary() {<br /> return (String)result.get(SUMMARY);<br /> }<br /><br /> public String getUrl() {<br /> return (String)result.get(URL);<br /> }<br /><br /> public String getClickUrl() {<br /> return (String)result.get(CLICK_URL);<br /> }<br /><br /> public String getNewsSource() {<br /> return (String)result.get(SOURCE);<br /> }<br /><br /> public String getNewsSourceUrl() {<br /> return (String)result.get(SOURCE_URL);<br /> }<br /><br /> public String getLanguage() {<br /> return (String)result.get(LANGUAGE);<br /> }<br /><br /> public String getPublishDate() {<br /> return (String)result.get(PUBLISH_DATE);<br /> }<br /><br /> public String getModificationDate() {<br /> return (String)result.get(MOD_DATE);<br /> }<br /><br /> public ImageThumbnail getThumbnail() {<br /> return null;<br /> }<br /> }<br /> <br /> <br />}<br /><br /></newssearchresult></newssearchresult></string></string></string></code></pre><br /></div><br /><br />I added some kind of filtering to our parsing process because I though it was so silly to extract all those values and store them in memory without even using them.so let's just store those information that is required and<br />simply just skip those ones we are not interested in.<br /><br />Our FecherThread's run() method will also gotta be changed :<br /><br /><div style="background-color:#F7F7F7;border:1px dashed #999999;font-family:'Consolas','Courier New',Courier,Fixed;font-size:11pt;font-size-adjust:none;font-stretch:normal;font-style:normal;font-variant:normal;font-weight:normal;line-height:normal;overflow:auto;padding:5px;"><br /><pre><br />public void run(){<br /> <br /> request.getParameters().put("appid", "javasdktest");<br /> // request.setResults(15);<br /> <br /> <br /> do {<br /> while(!this.anyRequest){<br /> <br /> try{ <br /> synchronized (this) {<br /> this.wait(); <br /> }<br /> }catch(InterruptedException exp){<br /> ////Just Nothing<br /> }<br /> <br /> } <br /> <br /> try{<br /> <br /> NewsPullParser parser = new NewsPullParser(RestClient.call(request.getRequestUrl(), request.getParameters()));<br /> ArrayList<string> filter = new ArrayList<string>();<br /> filter.add(NewsPullParser.TITLE);<br /> filter.add(NewsPullParser.SUMMARY);<br /> <br /> Bundle content = new Bundle(); <br /> content.putSerializable("result",parser.parse(filter));<br /> Message msg = new Message(); <br /> msg.setData(content);<br /> this.callback.sendMessage(msg); <br /><br /> <br /> }catch(Exception exp){<br /> exp.printStackTrace();<br /> <br /> }<br /> <br /> <br /> this.anyRequest = false;<br /> this.lastRequest_finished = true;<br /> <br /> }while(!this.stopFlag);<br /> <br /> }<br /></string></string></pre><br /></div><br /><br /><br />we can also get rid of executeAndParse() method since we no longer need it.<br />Done,that's it.... easy, wasn't it? now we are familiar with both SAX Parsing<br />and Pull Parsing in Android.Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com0tag:blogger.com,1999:blog-8323121247454250099.post-16167466944814633172009-11-30T21:58:00.000-08:002009-12-02T00:12:17.996-08:00Yahoo News Search_Part2(FetcherThread)In the <a href="http://android-journey.blogspot.com/2009/11/yahoo-news-searchpart1activity-gui.html">last post</a> we saw how layout file and activity class of our News search application look like; as you might remember our activity class uses a Handler to communicate with the other thread which is responsible to fetch the result of search using Yahoo WebServices. I used Yahoo Java SDK in this case,despite the fact that it has been designed and implemented for Java SE & EE users and you could argue that it's not suitable for Mobile environment which is a totally sensible critisim. however WE ARE JUST ANDROID STUDENTS, i mean we<br />should take it easy at this stage...<br />Any way you can see our FetcherThread Class below, when you start a FetcherThread it will wait untill startOperation() method is called, this method has a parameter which repesents search criteria, this method is called by our activity whenever user presses the search button.<br /><br /><pre><code><br /><br />package com.news.search;<br /><br />import java.io.IOException;<br />import java.util.ArrayList;<br />import java.util.Iterator;<br />import java.util.Map;<br />import javax.xml.parsers.ParserConfigurationException;<br />import javax.xml.parsers.SAXParser;<br />import javax.xml.parsers.SAXParserFactory;<br /><br />import org.xml.sax.InputSource;<br />import org.xml.sax.SAXException;<br />import org.xml.sax.XMLReader;<br /><br />import android.os.Bundle;<br />import android.os.Handler;<br />import android.os.Message;<br /><br />import com.yahoo.rest.RestClient;<br />import com.yahoo.rest.RestException;<br />import com.yahoo.search.NewsSearchRequest;<br />import com.yahoo.search.NewsSearchResult;<br />import com.yahoo.xml.XmlParser;<br />import com.yahoo.search.xmlparser.XmlParserNewsSearchResults;<br /><br />public class FetcherThread extends Thread {<br /> <br /> public volatile XmlParserNewsSearchResults result = new XmlParserNewsSearchResults();<br /> public volatile boolean lastRequest_finished = true;<br /> <br /> <br /> private boolean anyRequest = false;<br /> private boolean stopFlag = false;<br /> private NewsSearchRequest request = new NewsSearchRequest();<br /> private Handler callback;<br /> <br /> <br /> <br /> public FetcherThread(Handler handler){<br /> this.callback = handler;<br /> }<br /> <br /> public void run(){<br /> <br /> request.getParameters().put("appid", "javasdktest");<br /> // request.setResults(15);<br /> <br /> <br /> do {<br /> while(!this.anyRequest){<br /> <br /> try{ <br /> synchronized (this) {<br /> this.wait(); <br /> }<br /> }catch(InterruptedException exp){<br /> ////Just Nothing<br /> }<br /> <br /> } <br /> <br /> try{<br /> <br /> Map results = executeAndParse(request.getRequestUrl(), request.getParameters());<br /> Thread.sleep(4000); ///Just to simulate any possible real world delay<br /> result.parse(results);<br /> <br /> }catch(Exception exp){<br /> exp.printStackTrace();<br /> <br /> }<br /> <br /> <br /> Bundle content = new Bundle();<br /> content.putSerializable("result",result.listResults());<br />Message msg = new Message(); <br />msg.setData(content);<br /> this.callback.sendMessage(msg);<br /> <br /> this.anyRequest = false;<br /> this.lastRequest_finished = true;<br /> <br /> }while(!this.stopFlag);<br /> <br /> }<br /> <br /> public synchronized void startOperation(String str){<br /> <br /> this.request.setQuery(str);<br /> this.anyRequest = true;<br /> this.lastRequest_finished = false;<br /> this.notifyAll();<br /> <br /> }<br /> <br /> public void cancelThread(){<br /> this.stopFlag = true;<br /> }<br /> <br /> <br /> private Map executeAndParse(String serviceUrl, Map parameters) throws Exception {<br /> XmlParser xmlParser = null;<br /><br /> try {<br /> SAXParser parser = SAXParserFactory.newInstance().newSAXParser();<br /> xmlParser = new XmlParser();<br /> <br /> XMLReader reader = parser.getXMLReader();<br /> reader.setContentHandler(xmlParser);<br /> reader.parse(new InputSource(RestClient.call(serviceUrl, parameters)));<br /> <br /> }<br /> catch (ParserConfigurationException e) {<br /> throw e;<br /> }<br /> catch (SAXException e) {<br /> throw new SAXException("Error parsing XML response", e);<br /> }<br /> catch (RestException ye) {<br /> throw ye;<br /> }<br /><br /> return xmlParser.getRoot();<br /> }<br /> <br /><br />}<br /><br /></code></pre><br /><br />And before i forget it... we know our appilaction will be intracting with internet so we need to announce to the system that we're gonna use network and take the appropriate permission to do that, it's pretty easy, we just gotta add following tag to Android Manifest file :<br /><br /><br /><uses-permission android:name="android.permission.INTERNET" /><br /><br /><br />You can see here how this thread is iteracting with our activity using Handler, we send a request to this thread then it starts calling Web Service and extracts information, store them in an array and send the result array back to our activity using Handler.sendMessage() method, the message will be delivered to handleMessage() method when appropriate.<br />OK...it seems that everything is alright,especially because i tested almost the same code on Desktop Envirnoment and every thing looked pretty good, although i didn't need to do that since Yahoo SDK is tested regularly and there are heaps of people using it out there...<br />but if you run this application you're gonna be surprised since there will be a wierd bug there, the map that is returned by executeAndParse() method is always gonna be empty....YES...I KNOW.. Why? that one nearly killed me to figure it out and you wouldn't believe this...<br />let's see how it works...this is the RestClient.Call() method implementation extracted from Yahoo SDK :<br /><br /><pre><code><br />public static InputStream call(String serviceUrl, Map parameters) throws IOException, RestException {<br />StringBuffer urlString = new StringBuffer(serviceUrl);<br />String query = RestClient.buildQueryString(parameters);<br /><br />HttpURLConnection conn;<br />if((urlString.length() + query.length() + 1) > MAX_URI_LENGTH_FOR_GET) {<br /> // Request is too big, do a POST<br /> URL url = new URL(urlString.toString());<br /> conn = (HttpURLConnection) url.openConnection();<br /> conn.setRequestProperty("User-Agent", USER_AGENT_STRING);<br /><br /> conn.setRequestMethod("POST");<br /> conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");<br /> conn.setDoOutput(true);<br /> conn.getOutputStream().write(query.getBytes());<br />}<br />else {<br /> // Request is small enough it should fit in a GET<br /> if(query.length() > 0) {<br /> urlString.append("?").append(query);<br /> }<br /><br /> URL url = new URL(urlString.toString());<br /> conn = (HttpURLConnection) url.openConnection();<br /> conn.setRequestProperty("User-Agent", USER_AGENT_STRING);<br /><br /> conn.setRequestMethod("GET");<br />}<br /><br /><br />int responseCode = conn.getResponseCode();<br />if (HttpURLConnection.HTTP_OK != responseCode) {<br /> ByteArrayOutputStream errorBuffer = new ByteArrayOutputStream();<br /><br /> int read;<br /> byte[] readBuffer = new byte[ERROR_READ_BUFFER_SIZE];<br /> InputStream errorStream = conn.getErrorStream();<br /> while (-1 != (read = errorStream.read(readBuffer))) {<br /> errorBuffer.write(readBuffer, 0, read);<br /> }<br /><br /> throw new RestException("Request failed, HTTP " + responseCode + ": " + conn.getResponseMessage(), errorBuffer.toByteArray());<br />}<br /><br />return conn.getInputStream();<br />}<br /><br /></code></pre><br /><br />an appropriate query is made in this method and Web Service get called, then an InputStream containing XML will be returned, then what we do with that inputStream is to parse the content using a SAX parser...<br />for SAX Parsing we usually need a Handler, we have used XmlParser class which is the default SAX handler of Yahoo SDK, here is implemtation of three main methods of XmlParser class which extracted from Yahoo SDK :<br /><br /><pre><code><br /> public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {<br /> String parentPath;<br /> if(typeStack.size() < 0) {<br /> parentPath = (String) typeStack.peek();<br /> }<br /> else {<br /> parentPath = "";<br /> }<br /> typeStack.push(parentPath + "/" + qName);<br /><br /> Map newMap = new HashMap();<br /> for (int i = 0; i < attributes.getLength(); i++) {<br /> newMap.put(attributes.getQName(i), attributes.getValue(i));<br /> }<br /><br /> Map top = (Map) stack.peek();<br /> Object obj = top.get(qName);<br /> if (obj == null) {<br /> top.put(qName, newMap);<br /> }<br /> else if (obj instanceof Map) {<br /> List newList = new LinkedList();<br /> newList.add(obj);<br /> newList.add(newMap);<br /> top.put(qName, newList);<br /> }<br /> else if (obj instanceof List) {<br /> ((List) obj).add(newMap);<br /> }<br /><br /> stack.push(newMap);<br /> <br /> }<br /><br /><br /> public void endElement(String uri, String localName, String qName) throws SAXException {<br /> stack.pop();<br /> typeStack.pop();<br /> <br /> }<br /><br /> public void characters(char ch[], int start, int length) throws SAXException {<br /> String current = (String) ((Map) stack.peek()).get("value");<br /> if(current != null) {<br /> current += new String(ch, start, length);<br /> }<br /> else {<br /> current = new String(ch, start, length);<br /> }<br /><br /> ((Map) stack.peek()).put("value", current);<br /> }<br /><br /></code></pre><br /><br />(You might be thinking that those methods have been implemented a bit weird, yes<br />..because they have been implemented to handle all Yahoo Web Services and not<br />only News Web Service).<br />I bet you will be surprised if i tell you that problem is here. actually i had assumed if we have got Java SE APIs in android it would mean that you can expect them to work exactly like it does in Java SE, but apparently it is not correct, take a second look at startElement() method , we normally use qName to get tag names just like it's being used here, but the value of qName is an empty String in android environment!! all we need to do is to replace all accurances of qName with localName and it will be fine then, so our new implementation will be<br />like this:<br /><br /><pre><code><br /> public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {<br /> String parentPath;<br /> if(typeStack.size() < 0) {<br /> parentPath = (String) typeStack.peek();<br /> }<br /> else {<br /> parentPath = "";<br /> }<br /> typeStack.push(parentPath + "/" + localName);<br /><br /> Map newMap = new HashMap();<br /> for (int i = 0; i < attributes.getLength(); i++) {<br /> newMap.put(attributes.getLocalName(i), attributes.getValue(i));<br /> }<br /><br /> Map top = (Map) stack.peek();<br /> Object obj = top.get(localName);<br /> if (obj == null) {<br /> top.put(localName, newMap);<br /> }<br /> else if (obj instanceof Map) {<br /> List newList = new LinkedList();<br /> newList.add(obj);<br /> newList.add(newMap);<br /> top.put(localName, newList);<br /> }<br /> else if (obj instanceof List) {<br /> ((List) obj).add(newMap);<br /> }<br /><br /> stack.push(newMap);<br /> <br /> }<br /><br /></code></pre><br /><br />although it took a long time to me to find out what was wrong, it was worthwhile since i will be more carefull about how i think some java APIs should work from my exprience and how they actually work within android environment.<br />i mean it would not hurt to have a second look at API documentation before using them, even though you have had some exprience working with them before.<br />XML Parsing was not addressed in details in our application, obviously because i used Yahoo SDK to make things easier, but if you got interested in XML Parsing you can find a beautiful document <a href="http://www.ibm.com/developerworks/opensource/library/x-android/index.html">here</a>, besides i'm probabaly gonna change this application to see how we can use Pull Parser instead of SAX Parser.<br /><br /><table><tbody><tr><td><br /><img style="margin: 0pt 0pt 10px 10px; float: right; cursor: pointer; width: 224px; height: 320px;" src="http://1.bp.blogspot.com/_lqWNM-79b1Y/SxTAGUJFeMI/AAAAAAAAAB0/E2nr0N1tUPA/s320/Untitled3.jpg" alt="" id="BLOGGER_PHOTO_ID_5410160267097372866" border="0" /><br /></td><br /><td><br /><img style="margin: 0px 10px 10px; display: block; text-align: center; cursor: pointer; width: 230px; height: 320px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/SxTAA-c6oZI/AAAAAAAAABs/lwzVhLA_LqA/s320/Untitled2.jpg" alt="" id="BLOGGER_PHOTO_ID_5410160175375622546" border="0" /><br /></td><br /><td><br /><img style="margin: 0pt 10px 10px 0pt; float: left; cursor: pointer; width: 225px; height: 320px;" src="http://4.bp.blogspot.com/_lqWNM-79b1Y/SxS_7x9SjPI/AAAAAAAAABk/23HSM-aggrs/s320/Untitled.jpg" alt="" id="BLOGGER_PHOTO_ID_5410160086122401010" border="0" /><br /></td><br /></tr></tbody></table>Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com0tag:blogger.com,1999:blog-8323121247454250099.post-6026911805095790872009-11-30T06:03:00.000-08:002009-11-30T06:29:18.245-08:00Yahoo News Search_Part1(Activity & GUI)I had been thinking to find an idea for my next application, something simple but kinda real and usefull at the same time.<br />and finally came up with this...News Search Application, it's simple...you enter what you are interested to find out more about and then the application will use Yahoo News Search WebService to see what can be found for you. how does it sound? sounds like fun to me....<br />so let's get down to it...<br />Our GUI will be so simple, a text box, a search button and a box to show the result of search , Like this :<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_lqWNM-79b1Y/SxPSyNfRzjI/AAAAAAAAABc/ZXMwSASR4dY/s1600/Untitled.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 226px; height: 320px;" src="http://3.bp.blogspot.com/_lqWNM-79b1Y/SxPSyNfRzjI/AAAAAAAAABc/ZXMwSASR4dY/s320/Untitled.jpg" alt="" id="BLOGGER_PHOTO_ID_5409899337458241074" border="0" /></a><br /><br /><br />for having that our layout file should be something like this :<br /><br /><?xml version="1.0" encoding="utf-8"?><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="vertical"<br />android:layout_width="fill_parent"<br />android:layout_height="fill_parent"<br />><br /><br /><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="horizontal"<br />android:layout_width="fill_parent"<br />android:layout_height="wrap_content"<br />android:layout_margin="10dip"<br />android:gravity="center"><br /><br /><br /> <EditText android:text=" "<br /> android:id="@+id/EditText01"<br /> android:layout_width="wrap_content"<br /> android:layout_height="wrap_content"<br /> android:maxWidth="400dip"<br /> android:gravity="top"<br /> android:minWidth="230dip"></EditText><br /><br /> <Button android:text="@string/searchBtn"<br /> android:id="@+id/Button01"<br /> android:layout_width="wrap_content"<br /> android:layout_height="wrap_content"<br /> android:gravity="center"></Button><br /><br /></LinearLayout><br /><br /><br /><br /> <TextView android:text=" " android:id="@+id/TextView01"<br /> android:layout_height="fill_parent"<br /> android:layout_gravity="center"<br /> android:layout_width="fill_parent"<br /> android:layout_margin="10dip"<br /> android:background="@color/white"<br /> android:textColor="@color/black"<br /> android:padding="5dip"<br /> android:fadeScrollbars="false"<br /> android:scrollbars="vertical" ></TextView><br /><br /><br /><br /></LinearLayout><br /><br /><br /><br />it's pretty simple and only thing that might seem a bit confusing are those gravity attributes of textbox and button, I know...<br />actually I had some struggles to align the textbox and button and finally that's what i came up with...I've always had some problems with designing GUIs though ;).<br /><br /><br />The next thing we would probably need to be worried about is how to handle network communication, i mean it's not a good idea to handle network communication inside our main activity since it will block our main thread, therefore we gotta have another thread<br />dealing with network and fetch the result of search using Yahoo WebService, but how should our main thread interact with this new thread... i reckon the best way would be like having the second thread running , then when user presses the search button we will send<br />the search criteria for that thread, it will then call the WebService and send the result back to our main activity where we can show that in the viewbox with appropriate format , but the whole interaction between our activity and the other thread gotta be Asynchronous, otherwise there is no point using separate thread. Thankfully Android has provided something very handy for Asynchronous thread interactions which is called Handler, the concept is pretty simple : a thread can define a Handler to recieve Asynchronous messages from other threads then each thread that want to send messages can get that Handler and send messages whenever appropriate without the first thread having to wait for the second one...<br />was it simple? ;) i did my best to put it in a simple way,although sometimes we need something more than words to define simplicity....<br />here is what our Activity class will look like...<br /><br /><pre><code><br /><br />package com.news.search;<br /><br />import com.yahoo.search.NewsSearchResult;<br />import com.yahoo.search.xmlparser.XmlParserNewsSearchResults;<br /><br />import android.app.Activity;<br />import android.os.Bundle;<br />import android.os.Handler;<br />import android.os.Message;<br />import android.text.method.ArrowKeyMovementMethod;<br />import android.text.method.ScrollingMovementMethod;<br />import android.view.View;<br />import android.view.View.OnClickListener;<br />import android.widget.EditText;<br />import android.widget.TextView;<br /><br />public class MainActivity extends Activity implements OnClickListener{<br /><br />private MyHandler handler = new MyHandler();<br />private FetcherThread thread;<br />private TextView view;<br />EditText field;<br /><br /><br /> /** Called when the activity is first created. */<br /> @Override<br /> public void onCreate(Bundle savedInstanceState) {<br /> super.onCreate(savedInstanceState);<br /> setContentView(R.layout.main);<br /> <br /> thread = new FetcherThread(this.handler);<br /> thread.start(); <br /> <br /> this.view = (TextView)findViewById(R.id.TextView01);<br /> this.view.setFocusable(true); //Remember to mention this<br /> this.view.setFocusableInTouchMode(true);<br /> this.view.setMovementMethod(ScrollingMovementMethod.getInstance());<br /> this.field = (EditText)findViewById(R.id.EditText01);<br /> findViewById(R.id.Button01).setOnClickListener(this);<br /> <br /> }<br /><br /> @Override<br /> public void onSaveInstanceState(Bundle instanceState){<br /> <br /> }<br /><br /> @Override<br /> public void onRestart(){<br /> super.onRestart();<br /> }<br /><br /> @Override<br /> public void onStart(){<br /> super.onStart();<br /> }<br /><br /> @Override<br /> public void onResume(){<br /> super.onResume();<br /> <br /> }<br /><br /> @Override<br /> public void onPause(){<br /> super.onPause();<br /> <br /> }<br /><br /> @Override<br /> public void onStop(){<br /> super.onStop();<br /> <br /> }<br /><br /> @Override<br /> public void onDestroy(){<br /> this.thread.cancelThread();<br /> super.onDestroy();<br /> <br /> }<br /><br /> @Override<br />public void onClick(View v) {<br /> if(this.thread.lastRequest_finished){<br /> this.thread.startOperation(this.field.getText().toString());<br /> this.view.setText(" \n\nPLEASE WAIT...");<br /> }<br />}<br /><br /><br /> class MyHandler extends Handler {<br /> <br /> public void handleMessage(Message msg){<br /> <br /> Bundle bundle = msg.getData();<br /> NewsSearchResult myResult[] = (NewsSearchResult[])bundle.getSerializable("result");<br /> <br /> if(myResult == null || myResult.length == 0){<br /> view.setText("\n *NO MATCH COULD BE FOUND* ");<br /> return;<br /> }<br /><br /> StringBuffer buffer = new StringBuffer();<br /> for(NewsSearchResult temp : myResult){<br /> buffer.append("TITLE :"+temp.getTitle()+"\n");<br /> buffer.append("SUMMARY : "+temp.getSummary()+"\n\t----------\n");<br /> }<br /> <br /> <br /> view.setText(buffer.toString());<br /> view.requestFocus(); <br /> <br /> }<br /> <br /> }<br /><br />}<br /><br /><br /><br /></code></pre><br /><br />although we dont need activity life cycle methods implemented for this application, i put them there purposely just to remind us the lifecycle of activities<br />sorry if it sounds a bit ridiculous , maybe i'm just a little bit paranoid about the activity lifecycle but i feel like we should not forget it...<br />any way...that's all we needed in our Activity class....it seemed to be more complicated at first, didn't it? ;)<br /><br />you might be thinking about why i used setFocusable() and setMovementMethod(), well initially i did not , but i realized that i couldn't scroll the viewbox content<br />using arrow keys(at least using emulator, i'm not sure about real android devices though) so i added those three line of codes and got the problem solved.<br /><br />i'm gonna talk about FetcherThread in my next post and you will see how smoothly these two threads are working together without any blocking problem.Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com0tag:blogger.com,1999:blog-8323121247454250099.post-61236198573274955782009-11-28T02:45:00.000-08:002009-11-28T04:53:12.429-08:00First Application_Part3 (Activities)We have got our View and Model so far, it means that we've got one more thing left to do, our controller which is called Activity in Android world. we will need 3 activities since we have 3 different functionality in our application, our calculator , Save result page and View Page. you might say , well it's actually one main application with three functions and that's what i though initially, but apparently we need a different activity for each of them , i'm not sure though... but i kinda like this idea...real modularity.<br />Any way you can see our main activity below, it's a bit messy but what i was gonna do was to find out how many ways there are for handling events...you can see that i have used 3 different ways to handling onClick event for our calculator's buttons. if you cannot figure out what oneClicked , twoClicked and... are. you would be better to take a look at our calculator layout file which you can find at <a href="http://android-journey.blogspot.com/2009/11/first-applicationpart1-gui.html">First Application_Part1 .</a><br /><br /><pre><code><br /><br />package com.amir.calculator;<br /><br />import com.amir.calculator.Calculator.Operation;<br /><br />import android.app.Activity;<br />import android.content.Intent;<br />import android.os.Bundle;<br />import android.view.View;<br />import android.view.View.OnClickListener;<br />import android.widget.Button;<br />import android.widget.TextView;<br /><br />public class MainActivity extends Activity implements OnClickListener{<br />/** Called when the activity is first created. */<br /><br />private TextView textView;<br />private double currentValue = 0;<br />private Button addBtn;<br />private Button subBtn;<br />private Button divBtn;<br />private Button mulBtn;<br />private Button SRBtn;<br />private Button clearBtn;<br />private Button showBtn;<br />private Button equal;<br />private Calculator calc = new Calculator();<br /><br />@Override<br />public void onCreate(Bundle savedInstanceState) {<br />super.onCreate(savedInstanceState);<br />setContentView(R.layout.main);<br /><br />this.textView = (TextView)findViewById(R.id.note);<br />this.textView.setText(Double.toString(this.currentValue));<br />this.addBtn = (Button)findViewById(R.id.addition);<br />this.subBtn = (Button)findViewById(R.id.subtract);<br />this.divBtn = (Button)findViewById(R.id.devide);<br />this.mulBtn = (Button)findViewById(R.id.multiply);<br />this.equal = (Button)findViewById(R.id.equalSign);<br />this.SRBtn = (Button)findViewById(R.id.save);<br />this.clearBtn = (Button)findViewById(R.id.clear);<br />this.showBtn = (Button)findViewById(R.id.show);<br /><br />this.addBtn.setOnClickListener(this);<br />this.subBtn.setOnClickListener(this);<br />this.divBtn.setOnClickListener(this);<br />this.mulBtn.setOnClickListener(this);<br />this.equal.setOnClickListener(this);<br /><br />this.SRBtn.setOnClickListener(new OnClickListener() {<br /> <br /> @Override<br /> public void onClick(View v) {<br /> <br /> Intent intent = new Intent();<br /> intent.setAction("com.amir.calculator.SAVE");<br /> intent.putExtra("com.amir.calculator.result", MainActivity.this.currentValue);<br /> startActivity(intent); <br /> <br /> }<br /> });<br /><br />this.clearBtn.setOnClickListener(new OnClickListener() {<br /> <br /> @Override<br /> public void onClick(View v) {<br /><br /> MainActivity.this.textView.setText("");<br /> calc.resetCalculator();<br /> MainActivity.this.currentValue = 0;<br /> <br /> }<br /> });<br /><br /><br />this.showBtn.setOnClickListener(new OnClickListener() {<br /> <br /> @Override<br /> public void onClick(View v) {<br /> <br /> Intent intent = new Intent();<br /> intent.setAction("com.amir.calculator.SHOW");<br /> startActivity(intent); <br /> <br /> }<br /> });<br /><br />}<br /><br />@Override<br />public void onClick(View source) {<br /><br /> if(this.currentValue == 0)<br /> return;<br /><br /> if(!this.calc.isStarted()){<br /> this.calc.initiateProcess(this.currentValue);<br /> }<br /> else if (source.getId() != R.id.equalSign){ //to support cascading operations...<br /> this.currentValue = doCalculation();<br /> this.calc.initiateProcess(this.currentValue);<br /> }<br /><br /> if(source.getId() == R.id.addition){<br /> this.textView.setText("+");<br /> this.calc.nextOperation(Calculator.Operation.ADD);<br /> this.currentValue = 0;<br /> }<br /> else if(source.getId() == R.id.subtract){<br /> this.textView.setText("-");<br /> this.calc.nextOperation(Calculator.Operation.SUBTRACT);<br /> this.currentValue = 0;<br /> }<br /> else if(source.getId() == R.id.devide){<br /> this.textView.setText("/");<br /> this.calc.nextOperation(Calculator.Operation.DIVIDE);<br /> this.currentValue = 0;<br /> }<br /> else if(source.getId() == R.id.multiply){<br /> this.textView.setText("*");<br /> this.calc.nextOperation(Calculator.Operation.MULTIPLY);<br /> this.currentValue = 0;<br /> }<br /> else if(source.getId() == R.id.equalSign){<br /> this.textView.setText(Double.toString(doCalculation()));<br /> this.calc.resetCalculator();<br /> }<br /> else<br /> this.textView.setText("ERROR!!!");<br /><br />}<br /><br />private double doCalculation(){<br /> double result = this.calc.doOperation(this.currentValue); <br /> this.currentValue = result; <br /><br />return result;<br />}<br /><br />public void oneClicked(View btn){<br />this.recalculateValue(1);<br />}<br /><br />public void twoClicked(View btn){<br /> this.recalculateValue(2);<br />}<br /><br />public void threeClicked(View btn){<br /> this.recalculateValue(3);<br />}<br /><br />public void fourClicked(View btn){<br /> this.recalculateValue(4);<br />}<br /><br />public void fiveClicked(View btn){<br /> this.recalculateValue(5); <br />}<br /><br />public void sixClicked(View btn){<br /> this.recalculateValue(6);<br />}<br /> <br />public void sevenClicked(View btn){<br /> this.recalculateValue(7);<br />}<br /><br />public void eightClicked(View btn){<br /> this.recalculateValue(8);<br />}<br /><br />public void nineClicked(View btn){<br /> this.recalculateValue(9);<br />}<br /><br />public void zeroClicked(View btn){<br /> this.recalculateValue(0);<br />}<br /><br />private void recalculateValue(int value){<br /> this.currentValue = (this.currentValue*10)+value;<br /> this.textView.setText(Double.toString(this.currentValue));<br /> <br />}<br /><br />}<br /><br /></code></pre><br /><br /><br />it might not be the best algorithm for implementing a calculator , but i'm not worried about that...at least at this stage ;)<br />you can also see how we interact with other activities in android, when user presses SR button we need to run other activity to Save the result so what we need to do is to send a message for that activity with all required information and ask it to do what it's supposed to do... startActivity() function is responsible to deliver our messages to other activities and Intent class represents our messages(receiver address and required information).<br /><br />and here is our Save Result Activity :<br /><br /><pre><code><br /><br />package com.amir.calculator;<br /><br />import java.util.Calendar;<br /><br />import android.app.Activity;<br />import android.content.ContentResolver;<br />import android.content.ContentValues;<br />import android.os.Bundle;<br />import android.view.View;<br />import android.view.View.OnClickListener;<br />import android.widget.Button;<br />import android.widget.EditText;<br />import android.widget.TextView;<br /><br />public class SaveActivity extends Activity implements OnClickListener{<br /><br />private double value;<br /><br />@Override<br />public void onCreate(Bundle savedInstanceState) {<br /> super.onCreate(savedInstanceState);<br /> setContentView(R.layout.save);<br /><br /> this.value = getIntent().getDoubleExtra("com.amir.calculator.result", 0);<br /> TextView view = (TextView)findViewById(R.id.ResultBox);<br /> view.setText(Double.toString(this.value));<br /><br /> Button btn = (Button)findViewById(R.id.SaveBtn);<br /> btn.setOnClickListener(this);<br /><br />}<br /><br />@Override<br />public void onClick(View v) {<br /><br /> ContentValues values = new ContentValues();<br /> EditText text = (EditText)findViewById(R.id.content);<br /> String content = text.getText().toString();<br /><br /> if( content == null || content.length() == 0){<br /> text.setText("DefaultName - "+Calendar.getInstance().getTime().toString());<br /> return;<br /> }<br /><br /> values.put(CalcContent.Content.NOTE, content);<br /> values.put(CalcContent.Content.VALUE, this.value);<br /> getContentResolver().insert(CalcContent.Content.CONTENT_URI, values);<br /><br /> finish();<br /><br />}<br /><br /><br />}<br /></code></pre><br /><br />the most interesting thing here would probably be how we saved our data, we have already implemented our ContentProvider and also defined it in our Android Manifest file, so all we need to do is to use getContentResolver() function, it will then go and start searching Android manifest file to find a ContentProvider which is able to handle requested data type , in this case<br />CalcContent Data type.<br /><br />Our last activity is 'Show Saved Results' :<br /><br /><br /><pre><code><br /><br />package com.amir.calculator;<br /><br />import android.app.Activity;<br />import android.content.ContentValues;<br />import android.database.Cursor;<br />import android.os.Bundle;<br />import android.view.View;<br />import android.widget.Button;<br />import android.widget.EditText;<br />import android.widget.TextView;<br /><br />public class ShowActivity extends Activity {<br /><br /><br />@Override<br /> public void onCreate(Bundle savedInstanceState) {<br /> super.onCreate(savedInstanceState);<br /> setContentView(R.layout.show);<br /> TextView view = (TextView)findViewById(R.id.viewBox);<br /> <br /> <br /> Cursor cursor = getContentResolver().query(CalcContent.Content.CONTENT_URI, null, null, null, null);<br /> int number = 0;<br /> <br /> if(cursor.moveToFirst()){ <br /> int noteIndex = cursor.getColumnIndex(CalcContent.Content.NOTE);<br /> int valueIndex = cursor.getColumnIndex(CalcContent.Content.VALUE);<br /> <br /> String str = " ";<br /> do{<br /> str += cursor.getString(noteIndex)+" -> "+cursor.getDouble(valueIndex)+"\n";<br /> }while(cursor.moveToNext());<br /> <br /> view.setText(str);<br /> }<br /> else{<br /> view.setText("Data Base is empty mate!!");<br /> } <br /> <br /> <br /> }<br /><br /><br /><br />}<br /><br /><br /><br /></code></pre>There are heaps of thing to learn about Activities and their life cycle which I'm gonna get into them soon in my next application...but before that i will be going over <a href="http://developer.android.com/reference/android/app/Activity.html">Activity class Documentation</a> again and again ;)<br /><br />The last thing that we'll need to complete our application is Android Manifest file :<br /><br /><br /><?xml version="1.0" encoding="utf-8"?><br /><br /><manifest xmlns:android="http://schemas.android.com/apk/res/android"<br /> package="com.amir.calculator"><br /> <br /> <supports-screens<br /> android:largeScreens="true"<br /> android:normalScreens="true"<br /> android:smallScreens="true"<br /> android:anyDensity="false" /><br /> <br /> <br /> <application android:icon="@drawable/icon" android:label="@string/app_name"><br /><br /><br /><br /> <provider android:name="CalcProvider"<br /> android:authorities="com.amir.calculator.CalcContent"<br /> /><br /><br /><br /> <activity android:name="MainActivity"<br /> android:label="@string/app_name"<br /> ><br /> <intent-filter><br /> <action android:name="android.intent.action.MAIN" /><br /> <category android:name="android.intent.category.LAUNCHER" /><br /> </intent-filter><br /> </activity><br /> <br /> <activity android:name="SaveActivity"<br /> android:label="@string/save"<br /> android:theme="@android:style/Theme.Dialog"><br /> <intent-filter><br /> <action android:name="com.amir.calculator.SAVE" /><br /> <category android:name="android.intent.category.DEFAULT" /><br /> </intent-filter><br /> </activity><br /> <br /> <br /> <activity android:name="ShowActivity"<br /> android:label="@string/show"<br /> android:theme="@android:style/Theme.Dialog"><br /> <intent-filter><br /> <action android:name="com.amir.calculator.SHOW" /><br /> <category android:name="android.intent.category.DEFAULT" /><br /> </intent-filter><br /> </activity><br /> <br /><br /> </application><br /> <uses-sdk android:minSdkVersion="5" /><br /><br /></manifest>Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com0tag:blogger.com,1999:blog-8323121247454250099.post-46556535866211334072009-11-27T16:13:00.000-08:002009-11-28T02:41:03.231-08:00First Application_Part 2 (Content Provider)We will need to store and retrieve data in our application and what Android can do in this area is really impressive, at least for me ;). we can use both file or database to store and manipulate information... android has provided a mechanism called 'ContentProvider', it's actually kind of Facade which helps to keep the interface of data communication structure independent from whatever is happening behind the scene , whether you are using database or file or if you are using caching or data validation...the interface will be always the same....<br /><br />I'm gonna use database facilities of Android for this application, speaking of database...the first question that might pop into your mind would be 'what about a mapping tool?' especially if your are an enterprise system developer i bet you cannot even imagine working with database without Hibernate or at least IBatis... but we should remember that we are dealing with mobile phones and not those incredibly powerful servers which are used in enterprise systems... i mean even the ability to work with database in these tiny devices really amazes me specially when you find out there is actually some kind of simple mapping structure to standardize and simplify working with database in android...<br /><br />OK..let's see what i am talking about. we want to store the result of calculation and a title which user assigns to that using our 'Save Result Dialog'. So what will be storing in database are the result, title and some general information like , say , creation date and modification date.<br />We will need a class like this :<br /><br /><pre><code><br /><br />package com.amir.calculator;<br /><br />import android.net.Uri;<br />import android.provider.BaseColumns;<br /><br />public class CalcContent {<br /><br /> public static final String AUTHORITY = "com.amir.calculator.CalcContent";<br /><br /><br /> private CalcContent() {}<br /><br /><br /> public static final class Content implements BaseColumns {<br /><br /> private Content() {}<br /><br /> public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/notes");<br /><br /> <br /> public static final String CONTENT_ITEM_TYPE = "vnd.amir.item/vnd.calc.content";<br /><br /><br /> public static final String DEFAULT_SORT_ORDER = "modified DESC";<br /><br /><br /> public static final String NOTE = "note";<br /> <br /> public static final String VALUE = "value";<br /> <br /> public static final String CREATED_DATE = "created";<br /><br /> public static final String MODIFIED_DATE = "modified";<br /> }<br /><br /><br />}<br /><br /></code></pre><br /><br />CONTENT_URI represents the Identity of our Data Type and will be used<br />wherever we need to work with this type of data as we will see soon.<br /><br />Next step would be Content Provider :<br /><br /><br /><pre><code>package com.amir.calculator;<br /><br /><br />import android.content.ContentProvider;<br />import android.content.ContentUris;<br />import android.content.ContentValues;<br />import android.content.Context;<br />import android.database.Cursor;<br />import android.database.SQLException;<br />import android.database.sqlite.SQLiteDatabase;<br />import android.database.sqlite.SQLiteOpenHelper;<br />import android.database.sqlite.SQLiteQueryBuilder;<br />import android.net.Uri;<br />import android.util.Log;<br /><br />public class CalcProvider extends ContentProvider {<br /><br /><br /> private final static String DB_NAME = "Calc.db";<br /> private final static String TABLE_NAME = "CalcNotes";<br /> private DatabaseHelper helper;<br /><br /><br /> private static class DatabaseHelper extends SQLiteOpenHelper {<br /><br /> DatabaseHelper(Context context) {<br /> super(context, DB_NAME , null,1);<br /> }<br /><br /> @Override<br /> public void onCreate(SQLiteDatabase db) {<br /> db.execSQL("CREATE TABLE " + TABLE_NAME + " ("<br /> + CalcContent.Content._ID + " INTEGER PRIMARY KEY,"<br /> + CalcContent.Content.NOTE + " TEXT,"<br /> + CalcContent.Content.VALUE + " NUMBER,"<br /> + CalcContent.Content.CREATED_DATE + " INTEGER,"<br /> + CalcContent.Content.MODIFIED_DATE + " INTEGER"<br /> + ");");<br /> }<br /><br /> @Override<br /> public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {<br /> Log.w("MyProvider", "Upgrading database from version " + oldVersion + " to "<br /> + newVersion + ", which will destroy all old data");<br /> db.execSQL("DROP TABLE IF EXISTS notes");<br /> onCreate(db);<br /> }<br /> }<br /><br /><br />@Override<br />public int delete(Uri arg0, String arg1, String[] arg2) {<br /> // TODO Auto-generated method stub<br /> return 0;<br />}<br /><br />@Override<br />public String getType(Uri uri) {<br /> // TODO Auto-generated method stub<br /> return null;<br />}<br /><br />@Override<br />public Uri insert(Uri uri, ContentValues initialValues) {<br /><br /> ContentValues values;<br /> if (initialValues != null) {<br /> values = new ContentValues(initialValues);<br /> } else {<br /> values = new ContentValues();<br /> }<br /><br /> Long now = Long.valueOf(System.currentTimeMillis());<br /><br /> // Make sure that the fields are all set<br /> if (values.containsKey(CalcContent.Content.CREATED_DATE) == false) {<br /> values.put(CalcContent.Content.CREATED_DATE, now);<br /> }<br /><br /> if (values.containsKey(CalcContent.Content.MODIFIED_DATE) == false) {<br /> values.put(CalcContent.Content.MODIFIED_DATE, now);<br /> }<br /><br /> if (values.containsKey(CalcContent.Content.NOTE) == false) {<br /> values.put(CalcContent.Content.NOTE, "");<br /> }<br /><br /> if(values.containsKey(CalcContent.Content.VALUE) == false){<br /> values.put(CalcContent.Content.VALUE,0.0);<br /> }<br /><br /> SQLiteDatabase db = helper.getWritableDatabase();<br /> long rowId = db.insert(TABLE_NAME, CalcContent.Content.NOTE, values);<br /> if (rowId > 0) {<br /> Uri noteUri = ContentUris.withAppendedId(CalcContent.Content.CONTENT_URI, rowId);<br /> getContext().getContentResolver().notifyChange(noteUri, null);<br /> return noteUri;<br /> }<br /><br /> throw new SQLException("Failed to insert row into " + uri);<br /><br />}<br /><br />@Override<br />public boolean onCreate() {<br /> this.helper = new DatabaseHelper(getContext());<br /> return true;<br />}<br /><br />@Override<br />public Cursor query(Uri uri, String[] projection, String selection,<br /> String[] selectionArgs, String sortOrder) {<br /><br /> SQLiteQueryBuilder qb = new SQLiteQueryBuilder();<br /> qb.setTables(TABLE_NAME);<br /> SQLiteDatabase db = helper.getReadableDatabase();<br /> Cursor c = qb.query(db, projection, selection, selectionArgs, null, null, sortOrder);<br /><br /> // Tell the cursor what uri to watch, so it knows when its source data changes<br /> c.setNotificationUri(getContext().getContentResolver(), uri);<br /><br /> return c;<br /> <br />}<br /><br />@Override<br />public int update(Uri uri, ContentValues values, String selection,<br /> String[] selectionArgs) {<br /> // TODO Auto-generated method stub<br /> return 0;<br />}<br /><br />}<br /></code></pre><br /><br /><br />From now on any application which want to work with 'CalcContent' Data Type can simply use this Content Provider.the last thing we need to do is to define this Content Provider in Android Manifest file so that activities can use it. it would be pretty easy :<br /><br /><br /> <provider android:name="CalcProvider"<br /> android:authorities="com.amir.calculator.CalcContent"<br /> /><br /><br />You can have most of your questions about 'ContentProvider' answered <a href="http://developer.android.com/guide/topics/providers/content-providers.html">here</a>. although i believe as long as you are not writing some real code you don't really know what's your question ;)Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com0tag:blogger.com,1999:blog-8323121247454250099.post-6806920406188950612009-11-27T00:34:00.000-08:002009-11-27T01:47:58.961-08:00First Application_Part1 (GUI)I though It'd be good to start developing a couple of simple application to see how this whole thing works...so First application will be a simple calculator and it's gonna look like this :<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_lqWNM-79b1Y/Sw-LmP8ULnI/AAAAAAAAABM/7H-Ud17-7Zc/s1600/Untitled.jpg"><br /><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 230px; height: 320px;" src="http://2.bp.blogspot.com/_lqWNM-79b1Y/Sw-LmP8ULnI/AAAAAAAAABM/7H-Ud17-7Zc/s320/Untitled.jpg" alt="" id="BLOGGER_PHOTO_ID_5408695166726057586" border="0" /></a><br />A Simple Calculator which will also be able to Save the results of calculations(SR Button) and users will be able to view what have been stored(Show Saved Data Button).<br />Android has provided a XML_Based Mechanism for designing GUI, you define your XML file and then bind it to an activity, that activity will interpret XML files into android components.<br />in this case our XML will be like this :<br /><br /><?xml version="1.0" encoding="utf-8"?><br /><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="vertical"<br />android:layout_width="fill_parent"<br />android:layout_height="fill_parent"><br /><br /><br /><TextView android:id="@+id/note"<br />android:maxLines="1"<br />android:minLines="1"<br />android:layout_marginTop="2dip"<br />android:layout_width="wrap_content"<br />android:ems="25"<br />android:layout_height="wrap_content"<br />android:autoText="true"<br />android:capitalize="sentences"<br />android:singleLine="true"<br />android:cursorVisible="true"<br />android:layout_gravity="center_vertical|center_horizontal|center"<br />android:layout_margin="10px"<br />android:padding="5px"<br />android:background="@color/white"<br />android:textColor="@color/black"/><br /><br /><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="horizontal"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:layout_gravity="center_vertical|center_horizontal|center"><br /><br /><br /><Button android:id="@+id/one"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/number_One"<br />android:layout_gravity="center_horizontal"<br />android:onClick="oneClicked"<br />android:minWidth="70px"/><br /><br /><br /><Button android:id="@+id/two"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/number_Two"<br />android:layout_gravity="center_horizontal"<br />android:onClick="twoClicked"<br />android:minWidth="70px"/><br /><br /><Button android:id="@+id/three"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/number_Three"<br />android:layout_gravity="center_horizontal"<br />android:onClick="threeClicked"<br />android:minWidth="70px"/><br /><br /><br /><Button android:id="@+id/devide"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/devide_sign"<br />android:layout_gravity="center_horizontal"<br />android:minWidth="70px"/><br /><br /><br /></LinearLayout><br /><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="horizontal"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:layout_gravity="center_vertical|center_horizontal|center"><br /><br /><br /><Button android:id="@+id/four"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/number_Four"<br />android:layout_gravity="center_horizontal"<br />android:onClick="fourClicked"<br />android:minWidth="70px"/><br /><br /><br /><Button android:id="@+id/five"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/number_Five"<br />android:layout_gravity="center_horizontal"<br />android:onClick="fiveClicked"<br />android:minWidth="70px"/><br /><br /><Button android:id="@+id/six"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/number_Six"<br />android:layout_gravity="center_horizontal"<br />android:onClick="sixClicked"<br />android:minWidth="70px"/><br /><br /><br /><Button android:id="@+id/multiply"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/multiply_sign"<br />android:layout_gravity="center_horizontal"<br />android:minWidth="70px"/><br /><br /><br /></LinearLayout><br /><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="horizontal"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:layout_gravity="center_vertical|center_horizontal|center"><br /><br /><br /><Button android:id="@+id/seven"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/number_Seven"<br />android:layout_gravity="center_horizontal"<br />android:onClick="sevenClicked"<br />android:minWidth="70px"/><br /><br /><br /><Button android:id="@+id/eight"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/number_Eight"<br />android:layout_gravity="center_horizontal"<br />android:onClick="eightClicked"<br />android:minWidth="70px"/><br /><br /><Button android:id="@+id/nine"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/number_Nine"<br />android:layout_gravity="center_horizontal"<br />android:onClick="nineClicked"<br />android:minWidth="70px"/><br /><br /><br /><Button android:id="@+id/subtract"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/subtract_sign"<br />android:layout_gravity="center_horizontal"<br />android:minWidth="70px"/><br /><br /></LinearLayout><br /><br /><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="horizontal"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:layout_gravity="center_vertical|center_horizontal|center"><br /><br /><br /><Button android:id="@+id/zero"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/number_Zero"<br />android:layout_gravity="center_horizontal"<br />android:onClick="zeroClicked"<br />android:minWidth="70px"/><br /><br /><br /><Button android:id="@+id/save"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/save_btn"<br />android:layout_gravity="center_horizontal"<br />android:minWidth="70px"/><br /><br /><br /><Button android:id="@+id/equalSign"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/equal_sign"<br />android:layout_gravity="center_horizontal"<br />android:minWidth="70px"/><br /><br /><br /><Button android:id="@+id/addition"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/addition_sign"<br />android:layout_gravity="center_horizontal"<br />android:minWidth="70px"/><br /><br /><br /></LinearLayout><br /><br /><Button android:id="@+id/clear"<br />android:layout_width="fill_parent"<br />android:layout_height="wrap_content"<br />android:text="@string/clear"<br />android:layout_gravity="center_horizontal"<br />android:minWidth="70px"<br />android:layout_marginLeft="18px"<br />android:layout_marginRight="18px" /><br /><br /><Button android:id="@+id/show"<br />android:layout_width="fill_parent"<br />android:layout_height="wrap_content"<br />android:text="@string/show"<br />android:layout_gravity="center_horizontal"<br />android:minWidth="70px"<br />android:layout_marginLeft="18px"<br />android:layout_marginRight="18px" /><br /><br /><br /></LinearLayout><br /><br /><br />When user presses SR button a dialog should get opened asking for a title with which the result of calculation will be saved. This dialog will look like this :<br /><br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_lqWNM-79b1Y/Sw-ZDWhKqSI/AAAAAAAAABU/VQjfu_LuhWY/s1600/Untitled2.jpg"><img style="margin: 0px auto 10px; display: block; text-align: center; cursor: pointer; width: 234px; height: 320px;" src="http://1.bp.blogspot.com/_lqWNM-79b1Y/Sw-ZDWhKqSI/AAAAAAAAABU/VQjfu_LuhWY/s320/Untitled2.jpg" alt="" id="BLOGGER_PHOTO_ID_5408709960358603042" border="0" /></a><br /><br />We would need an Layout file like this for what we see above:<br /><br /><?xml version="1.0" encoding="utf-8"?><br /><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="vertical"<br />android:layout_width="fill_parent"<br />android:layout_height="fill_parent"<br />android:minWidth="300px"><br /><br /><br /><br /><TextView android:text="@string/empty" android:id="@+id/ResultBox"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:gravity="top"<br />android:layout_margin="15px"<br />android:padding="5px"/><br /><br /><br /><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br />android:orientation="horizontal"<br />android:layout_width="fill_parent"<br />android:layout_height="wrap_content"<br />android:layout_margin="15px"><br /><br /><TextView android:text="@string/Title"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:gravity="top"/><br /><br /><br /><EditText android:id="@+id/content"<br />android:maxLines="5"<br />android:minLines="5"<br />android:minWidth="200px"<br />android:layout_width="fill_parent"<br />android:ems="30"<br />android:layout_height="wrap_content"<br />android:autoText="true"<br />android:cursorVisible="true"/><br /><br /><br /></LinearLayout><br /><br /><Button android:id="@+id/SaveBtn"<br />android:layout_width="wrap_content"<br />android:layout_height="wrap_content"<br />android:text="@string/saveBtn"<br />android:layout_gravity="center_horizontal"<br />android:minWidth="120px"/><br /><br /></LinearLayout><br /><br /><br />When user presses 'Show Saved Data' we will need a simple dialog box just to show all values that have been saved, therefore something like this would be good enough :<br /><br /><br /><?xml version="1.0" encoding="utf-8"?><br /><br /><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"<br /> android:orientation="vertical"<br /> android:layout_width="fill_parent"<br /> android:layout_height="fill_parent"<br /> android:minWidth="300px"><br /> <br /> <br /> <br /> <TextView android:id="@+id/viewBox"<br /> android:maxLines="15"<br /> android:minLines="5"<br /> android:layout_marginTop="2dip"<br /> android:layout_width="wrap_content"<br /> android:ems="25"<br /> android:layout_height="wrap_content"<br /> android:autoText="true"<br /> android:capitalize="sentences"<br /> android:scrollHorizontally="true"/><br /> <br /> <br /> <br /> <br /></LinearLayout><br /><br /><br />Remember that all values with '@String' prefix need to be defined in a resource file named string.xml which apparently must be placed in 'values' directory of your project (layouts files must go under 'layout' directory as well).Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com0tag:blogger.com,1999:blog-8323121247454250099.post-73638794929216544282009-11-26T19:13:00.000-08:002009-11-26T19:21:41.033-08:00let's get started....<div style="text-align: center;"><br /><br /><a href="http://developer.android.com/guide/basics/what-is-android.html"><span style="font-style: italic; color: rgb(153, 0, 0);font-size:180%;" ><span style="font-family: trebuchet ms;">What's Android?</span></span></a><a href="http://developer.android.com/guide/basics/what-is-android.html"><span style="font-style: italic; color: rgb(153, 0, 0);font-size:180%;" ><span style="font-family: trebuchet ms;"></span></span></a><br /></div>Amirhttp://www.blogger.com/profile/00559049517596170586noreply@blogger.com0