Post on 05-Apr-2018
AndroidApplicationDevelopment
ABeginner’sTutorial
BudiKurniawan
AndroidApplicationDevelopment:ABeginner’sTutorial
FirstEdition:February2015
All rights reserved.No part of this bookmay be reproduced or transmitted in anyformorbyanymeans,electronicormechanical,includingphotocopying,recording,orbyanyinformationstorageandretrievalsystem,withoutwrittenpermissionfromthepublisher,exceptfortheinclusionofbriefquotationsinareview.
ISBN:9780992133016
Indexer:ChrisMayle
Trademarks
OracleandJavaareregisteredtrademarksofOracleand/orit’saffiliatesUNIXisaregisteredtrademarkoftheOpenGroupApacheisatrademarkofTheApacheSoftwareFoundation.FirefoxisaregisteredtrademarkoftheMozillaFoundation.GoogleisatrademarkofGoogle,Inc.
Throughout this book the printing of trademarked names without the trademarksymbol is for editorial purpose only.We have no intention of infringement of thetrademark.
WarningandDisclaimerEveryefforthasbeenmadetomakethisbookasaccurateaspossible.Theauthorandthe publisher shall have neither liability nor responsibility to any person or entitywithrespecttoanylossordamagesarisingfromtheinformationinthisbook.
AbouttheAuthorBudi Kurniawan is known for his clear writing style. A consultant at Brainy SoftwareCorp., he haswritten software licensed by Fortune 100 companies and architected anddevelopedlargescaleapplicationsforvariousorganizationsaroundtheworld.Hehasalsopublishedmorethan100articlesinprestigiouspublications.Hisotherbooksincludethepopular“HowTomcatWorks”and“ServletandJSP:ATutorial.”
TableofContentsIntroduction
OverviewApplicationDevelopmentinBriefAndroidVersionsOnlineReferenceWhichJavaVersionsCanIUse?AboutThisBookCodeDownload
Chapter1:GettingStarted
DownloadingandInstallingAndroidStudioCreatingAnApplicationRunningtheApplicationontheEmulatorTheApplicationStructureDebugingYourApplicationTheAndroidSDKManagerCreatingAnAndroidVirtualDeviceRunningAnApplicationonAPhysicalDeviceOpeningAProjectinAndroidStudioUsingJava8GettingRidoftheSupportLibrarySummary
Chapter2:Activities
TheActivityLifecycleActivityDemoExampleChangingtheApplicationIconUsingAndroidResourcesStartingAnotherActivityActivity-RelatedIntentsSummary
Chapter3:UIComponents
OverviewUsingtheAndroidStudioUIToolUsingBasicComponentsToastAlertDialogNotificationsSummary
Chapter4:Layouts
Overview
LinearLayoutRelativeLayoutFrameLayoutTableLayoutGridLayoutCreatingALayoutProgrammaticallySummary
Chapter5:Listeners
OverviewUsingtheonClickAttributeImplementingAListenerSummary
Chapter6:TheActionBar
OverviewAddingActionItemsAddingDropdownNavigationGoingBackUpSummary
Chapter7:Menus
OverviewTheMenuFileTheOptionsMenuTheContextMenuThePopupMenuSummary
Chapter8:ListView
OverviewCreatingAListAdapterUsingAListViewExtendingListActivityandWritingACustomAdapterStylingtheSelectedItemSummary
Chapter9:GridView
OverviewUsingtheGridViewSummary
Chapter10:StylesandThemes
OverviewUsingStylesUsingThemesSummary
Chapter11:BitmapProcessing
OverviewBitmapProcessingSummary
Chapter12:GraphicsandCustomViews
OverviewHardwareAccelerationCreatingACustomViewDrawingBasicShapesDrawingTextTransparencyShadersClippingUsingPathsTheCanvasDemoApplicationSummary
Chapter13:Fragments
TheFragmentLifecycleFragmentManagementUsingAFragmentExtendingListFragmentandUsingFragmentManagerSummary
Chapter14:Multi-PaneLayouts
OverviewAMulti-PaneExampleSummary
Chapter15:Animation
OverviewPropertyAnimationAnAnimationProjectSummary
Chapter16:Preferences
SharedPreferencesThePreferenceAPIUsingPreferencesSummary
Chapter17:WorkingwithFiles
OverviewCreatingaNotesApplicationAccessingthePublicStorage
Summary
Chapter18:WorkingwiththeDatabase
OverviewTheDatabaseAPIExampleSummary
Chapter19:TakingPictures
OverviewUsingCameraTheCameraAPIUsingtheCameraAPISummary
Chapter20:MakingVideos
UsingtheBuilt-inIntentMediaRecorderUsingMediaRecorderSummary
Chapter21:TheSoundRecorder
TheMediaRecorderClassExampleSummary
Chapter22:HandlingtheHandler
OverviewExampleSummary
Chapter23:AsynchronousTasks
OverviewExampleSummary
Chapter24:Services
OverviewTheServiceAPIDeclaringAServiceAServiceExampleSummary
Chapter25:BroadcastReceivers
OverviewBroadcastReceiver-basedClockCancelingANotification
Summary
Chapter26:TheAlarmService
OverviewExampleSummary
Chapter27:ContentProviders
OverviewTheContentProviderClassCreatingAContentProviderConsumingAContentProviderSummary
AppendixA:InstallingtheJDK
DownloadingandInstallingtheJDK
AppendixB:UsingtheADTBundle
InstallingtheADTCreatingAnApplicationRunningAnApplicationonAnEmulatorLoggingDebuggingAnApplication
IntroductionThis book is for you if you want to learn Android application development for smartphonesandtablets.AndroidisthemostpopularmobileplatformtodayanditcomeswithacomprehensivesetofAPIsthatmakeiteasyfordeveloperstowrite,testanddeployapps.With theseAPIs you can easily show user interface (UI) components, play and recordaudioandvideo,creategamesandanimation,storeandretrievedata,searchtheInternet,andsoon.
Thesoftwaredevelopmentkit(SDK)forAndroidapplicationdevelopmentisfreeandincludes an emulator, a computer program that can be configured tomimic a hardwaredevice.Thismeans,youcandevelop,debugand testyourapplicationswithoutphysicaldevices.
ThisintroductionprovidesanoverviewoftheAndroidplatformandthecontentsofthebook.
OverviewTheAndroidoperating system is amulti-userLinux system.Eachapplication runs as adifferentuserinaseparateLinuxprocess.Assuch,anapplicationrunsinisolationfromotherapps.
OneofthereasonsforAndroid’srapidascenttothetopisthefactthatitusesJavaasitsprogramminglanguage.But,isAndroidreallyJava?Theanswerisyesandno.Yes,Javais thedefaultprogramminglanguageforAndroidapplicationdevelopment.No,AndroidapplicationsdonotrunonaJavaVirtualMachineasallJavaapplicationsdo.Instead,uptoAndroidversion4.4allAndroidapplicationsrunonavirtualmachinecalledDalvik.Inversion 5.0 and later, Android sources are ultimately compiled to machine code andapplicationsrunwithanewruntimecalledART(AndroidRuntime).Android4.4wastheturningpointandshippedwithbothDalvikandART.
As for the development process, initially code written in Java is compiled to Javabytecode. The bytecode is then cross-compiled to a dex (Dalvik executable) file thatcontainsoneormultipleJavaclasses.Thedexfile,resourcefilesandotherfilesarethenpackagedusingtheapkbuildertoolintoanapkfile,whichisbasicallyazipfilethatcanbeextractedusingunziporWinzip.APK,bytheway,standsforapplicationpackage.
Theapkfileishowyoudeployyourapp.AnyonewhogetsacopyofitcaninstallandrunitonhisorherAndroiddevice.
Inpre-5.0versionsofAndroid,theapkfilerunonDalvik.Inversion5.0andlater,thedexfileintheapkisconvertedintomachinecodewhentheapplicationisinstalled.Themachinecodeisexecutedwhentheuserrunstheapplication.Allofthisistransparenttothedeveloperandyoudonothavetounderstandintimatelythedexformatortheinternalworkingoftheruntime.
An apk file can run on a physical device or the emulator. Deploying an Androidapplication is easy.You canmake the apk file available for download and download itwithanAndroiddevicetoinstallit.Youcanalsoemailtheapkfiletoyourselfandopen
theemailonanAndroiddeviceandinstallit.TopublishyourapplicationonGooglePlay,however,youneedtosigntheapkfileusingthejarsignertool.Fortunately,signinganapkiseasywithanintegrateddevelopmentenvironment(IDE),eitheritisAndroidStudioorADTEclipse.
If you’re interested in learningmore about theAndroid build process, thisweb pageexplainstheAndroidbuildprocessindetail.
https://developer.android.com/tools/building/index.html
ApplicationDevelopmentinBriefBefore you embark on a long journey to becoming a professional Android applicationdeveloper,youshouldknowwhatliesahead.
Beforestartingaproject,youshouldalreadyhaveanideawhatAndroiddeviceswillbeyourtarget.Mostapplicationswilltargetsmartphonesandtablets.However,thecurrentAndroidreleasealsoallowsyoutodevelopappsforsmartTVsandwearables.Thisbook,however,isfocusedonapplicationdevelopmentforsmartphonesandtablets.
Then,youneedtodecidewhatversionsofAndroidyouwanttosupport.Androidwasreleased in 2008, but at the time of writing this book there are already 21 API levelsavailable, level 1 to level 21. Of course, the higher the level, the more features areavailable. However, many older phones and tablets do not run the latest Android andcannotrunapplicationsthattargethigherAPIlevelsthanwhatareinstalled.Forexample,ifyou’reusingfeaturesinAPIlevel21,yourapplicationwillnotruninAndroiddevicesthat support API level 20, let alone API level 2. Fortunately, Android is backward-compatible.Applicationswrittenforanearlierversionwillalwaysrunonnewerversions.Inotherwords,ifyouwriteapplicationsusingAPIlevel10,yourapplicationswillworkindevicesthatsupportAPIlevel10andlater.Therefore,youwouldwanttoaimthelowestAPIlevelpossible.Thistopicwillbediscussedfurtherinthesectiontocome.
Once you decidewhatAndroid devices to target and theAPI level you shouldwriteyour program in, you can start looking at the API. There are four types of Androidapplicationcomponents:
Activity:Awindowthatcontainsuserinterfacecomponents.Service:Alongrunningoperationthatrunsinthebackground.Broadcastreceiver:Alistenerthatrespondstoasystemorapplicationannouncement.Contentprovider:Acomponent thatmanages a set ofdata tobe sharedwithotherapplications.
An application can contain multiple component types, even though a beginner wouldnormally start with an application that has one or two activities. You can think of anactivity as a window. You can use Android user interface components or controls todecorateanactivityandasawaytointeractwiththeuser.IfyouareusinganIDE,youcandesign an activityby simplydragginganddroppingcontrols aroundyour computerscreen.
Toencouragecodereuse,anapplicationcomponentcanbeofferedtootherapplications.In fact,youshould takeadvantageof thissharingmechanismtospeedupdevelopment.For instance, insteadofwritingyourownphoto capture component, youcanutilize thecomponent of the default Camera application. Instead of writing an email sendingcomponentandreinventingthewheel,youcanusethesystem’semailapplicationtosendemailsfromyourapp.
Another important concept in Android programming is the intent. An intent is amessagesenttothesystemoranotherapplicationtorequestthatanactionbeperformed.Youcandoalotofdifferentthingswithintents,butgenerallyyouuseanintenttostartanactivity,startaserviceorsendabroadcast.
Everyapplicationmusthaveamanifest,whichdescribestheapplication.ThemanifesttakestheformofanXMLfileandcontainsoneorseveralofthefollowing:
TheminimumAPIlevelrequiredtoruntheapplication.Thenameoftheapplication.Thisnamewillbedisplayedonthedevice.Thefirstactivity(window)thatwillbeopenedwhentheusertouchestheapplicationiconontheHomescreenofhisorherphoneortablet.? Whether or not you allow your application components be invoked from otherapplications.Topromotecodereuse, functionality inanapplicationcanbe invokedfromotherapplicationsaslongastheauthoroftheapplicationagreetoshareit.Forinstance,thedefaultCameraapplicationcanbeinvokedfromotherapplicationsthatneedphotoorvideocapturefunctionality.Whatsetofpermissionstheusermustgrantfortheapplicationtobeinstalledonthetargetdevice.Iftheuserdoesnotgrantalltherequiredpermissions,theapplicationwillnotinstall.
Yes,manythingsrequireuserpermissions.Forexample,ifyourapplicationneedstostoredata in external storage or access the Internet, the application must request the user’spermissionbefore itcanbeinstalled.If theapplicationneedstobeautomaticallystartedwhenthedevicebootsup,thereisapermissionforthattoo.Infact,therearemorethan150permissionsthatanapplicationmayrequirebeforeitcanbeinstalledonanAndroiddevice.
Most applications are probably simple enough to only need activities and not othertypes of application components. Even with only activities, there is a lot to learn: UIcontrols,eventsandlisteners, fragments,animation,multi-threading,graphicandbitmapprocessingandsoon.Onceyoumasterthese,youmaywanttolookatservices,broadcastreceiversandcontentproviders.Allareexplainedinthisbook.
AndroidVersionsFirst released in September 2008, Android is now a stable and mature platform. Thecurrentversion,version5.0,isthe21stAndroidAPIleveleverreleased.TableI.1showsthecodename,APIlevelandreleasedateofallAndroidmajorreleases.
Version
CodeName
APILevel
ReleaseDate
1.0
1
September23,2008
1.1
2
February9,2009
1.5
Cupcake
3
April30,2009
1.6
Donut
4
September15,2009
2.0
Eclair
5
October26,2009
2.0.1
Eclair
6
December3,2009
2.1
Eclair
7
January12,2010
2.2
Froyo
8
May20,2010
2.3
Gingerbread
9
December6,2010
2.3.3
Gingerbread
10
February9,2011
3.0
Honeycomb
11
February22,2011
3.1
Honeycomb
12
May10,2011
3.2
Honeycomb
13
July15,2011
4.0
IceCreamSandwich
14
October19,2011
4.0.3
IceCreamSandwich
15
December16,2011
4.1
JellyBean
16
July9,2012
4.2
JellyBean
17
November13,2012
4.3
JellyBean
18
July24,2013
4.4
Kitkat
19
October31,2013
4.4w
Kitkat
20
July22,2014
5.0
Lollipop
21
November3,2014
TableI.1:Androidversions
NoteVersion4.4wisthesameas4.4butwithwearableextensions.
Witheachnewversion,newfeaturesareadded.Assuch,youcanusethemostfeaturesifyou target the latest Android release. However, not every Android phone and tablet isrunningthelatestreleasebecauseAndroiddevicesmadeforolderAPIsmaynotsupportlater releases and software upgrade is not always automatic. Table I.2 shows Androidversionsstillinusetoday.
Version
Codename
API
Distribution
2.2
Froyo
8
0.5%
2.3.3-2.3.7
Gingerbread
10
9.1%
4.0.3-4.0.4
IceCreamSandwich
15
7.8%
4.1.x
JellyBean
16 21.3%
4.2.x
17
20.4%
4.3
18
7.0%
4.4
KitKat
19
33.9%
TableI.2:Androidversionsstillinuse(December2014)
ThedatainTableI.2wastakenfromthiswebpage:
https://developer.android.com/about/dashboards/index.html
IfyoudistributeyourapplicationthroughGooglePlay,themostpopularmarketplaceforAndroidapplications,thelowestversionofAndroidthatcandownloadyourapplicationis2.2, because versions older than 2.2 cannot access Google Play. In general you wouldwanttoreachaswidecustomerbaseaspossible,whichmeanssupportingversion2.2andup.Ifyouonlysupportversion4.0andup,forexample,youleaveout9.6%ofAndroiddevices,whichmayormaynotbeokay.
However, the lower the version, the fewer features are supported. Some people riskalienatingsomecustomersinordertobeabletousethemorerecentfeatures.Toalleviatethisproblem,Googleprovidesasupportlibrarythatallowsyoutousenewerfeaturesinolddeviceswhichotherwisewouldnotbeabletoenjoythosefeatures.Youwilllearnhowtousethissupportlibraryinthisbook.
OnlineReferenceThe first challenge facing newAndroid programmers is understanding the componentsavailable inAndroid.Luckily,documentation is inabundanceand it iseasy tofindhelpovertheInternet.ThedocumentationofallAndroidclassesandinterfacescanbefoundonAndroid’sofficialwebsite:
http://developer.android.com/reference/packages.html
Undoubtedly,youwillfrequentthiswebsiteaslongasyouworkwithAndroid.Ifyouhadachancetobrowsethewebsite,you’dhavelearnedthatthefirstbatchoftypesbelongtotheandroidpackageanditssubpackages.AfterthemcomethejavaandjavaxpackagesthatyoucanuseinAndroidapplications.JavapackagesthatcannotbeusedinAndroid,suchasjavax.swingandjava.nio.file,arenotlistedthere.
WhichJavaVersionsCanIUse?TodevelopAndroidapplicationsusingtoolssuchasAndroidStudioorADTEclipse,youneed JDK 6 or later. Support for Java 7 language features was added to Android 4.4
(Kitkat).
At the timeofwriting there isnoofficialsupport forJava8yet.However, ifyouareusingAndroidStudioandhaveJDK8installed,youcanalreadyuselambdaexpressions,anewmajorfeatureinJava8.
AboutThisBookThissectionoutlinesthecontentsofthisbook.
Chapter1,“GettingStarted”showshowtoinstalltheAndroidSDKandAndroidStudioandcreateasimpleAndroidapplication.
Chapter2,“Activities”explainstheactivityanditslifecycle.TheactivityisoneofthemostimportantconceptsinAndroidprogramming.
Chapter 3, “UI Components” covers the more important UI components, includingwidgets,Toast,AlertDialogandnotifications.
Chapter4,“Layouts”showshowtolayoutUIcomponentsinAndroidapplicationsandusethebuilt-inlayoutsavailableinAndroid.
Chapter5,“Listeners”talksaboutcreatingalistenertohandleevents.
Chapter6,“TheActionBar”showshowyoucanadditemstotheactionbaranduseittodriveapplicationnavigation.
Menus are a common feature inmany graphical user interface (GUI) systemswhoseprimary role is to provide shortcuts to certain actions. Chapter 7, “Menus” looks atAndroidmenusclosely.
Chapter8,“ListView”explainsaboutListView,aviewthatshowsascrollable listofitemsandgetsitsdatasourcefromalistadapter.
Chapter9, “GridView”covers theGridViewwidget, a view similar to theListView.UnliketheListView,however,theGridViewdisplaysitsitemsinagrid.
Chapter 10, “Styles and Themes” discusses the two important topics directlyresponsibleforthelookandfeelofyourapps.
Chapter11, “BitmapProcessing” teachesyouhow tomanipulatebitmap images.Thetechniquesdiscussedinthischapterareusefulevenifyouarenotwritinganimageeditorapplication.
The Android SDK comes with a wide range of views that you can use in yourapplications.Ifnoneofthesesuitsyourneed,youcancreateacustomviewanddrawonit.Chapter 12, “Graphics andCustomViews” shows how to create a custom view anddrawshapesonacanvas.
Chapter13,“Fragments”discussesfragments,whicharecomponentsthatcanbeaddedto an activity. A fragment has its own lifecycle and hasmethods that get called whencertainphasesofitslifeoccur.
Chapter 14, “Multi-Pane Layouts” shows how you can use different layouts fordifferentscreensizes,likethatofhandsetsandthatoftablets.
Chapter15,“Animation”discussesthelatestAnimationAPIinAndroidcalledpropertyanimation.Italsoprovidesanexample.
Chapter 16, “Preferences” teaches you how to use the Preference API to storeapplicationsettingsandreadthemback.
Chapter 17, “WorkingwithFiles” showhow to use the JavaFileAPI in anAndroidapplication.
Chapter18,“WorkingwiththeDatabase”discussestheAndroidDatabaseAPI,whichyoucanuse to connect to anSQLitedatabase.SQLite is thedefault relationaldatabasethatcomeswitheveryAndroiddevice.
Chapter19, “TakingPictures” teachesyouhow to take still imagesusing thebuilt-inCameraapplicationandtheCameraAPI.
Chapter 20, “Making Videos” shows the two methods for providing video-makingcapabilityinyourapplication,byusingabuilt-inintentorbyusingtheMediaRecorderclass.
Chapter21,“TheSoundRecorder”showshowyoucanrecordaudio.
Chapter22,“HandlingtheHandler”talksabouttheHandlerclass,whichcanbeused,amongothers,toscheduleaRunnableatafuturetime.
Chapter 23, “Asynchronous Tasks” explains how to handle asynchronous tasks inAndroid.
Chapter24,“Services”explainshowtocreatebackgroundservices thatwill runevenaftertheapplicationthatstartedthemwasterminated.
Chapter 25, “Broadcast Receivers” discusses another kind of android component forreceivingintentbroadcasts.
Chapter 26, “The AlarmManager” shows how you can use theAlarmManager toschedulejobs.
Chapter27, “ContentProviders” explainsyet another application component type forencapsulatingdatathatcanbesharedacrossapplications.
AppendixA,“InstallingtheJDK”providesinstructionsonhowtoinstalltheJDK.
Appendix B, “Using the ADT Bundle” explains how to use the ADT Bundle as analternativeIDEtodevelopAndroidapps.
CodeDownloadTheexamplesaccompanyingthisbookcanbedownloadedfromthepublisher’swebsite:
http://books.brainysoftware.com
Chapter1GettingStarted
YouneedtheAndroidSoftwareDevelopmentKit(SDK)todevelop,debugandtestyourapplications.TheSDKcontainsvarioustoolsincludinganemulatortohelpyoutestyourapplicationswithoutaphysicaldevice.CurrentlytheSDKisavailableforWindows,MacOSXandLinuxoperatingsystems.
Youalsoneedanintegrateddevelopmentenvironment(IDE)tospeedupdevelopment.YoucouldbuildapplicationswithoutanIDE,butthatwouldbemoredifficultandunwise.TherearetwoIDEscurrentlyavailable,bothfree:
AndroidStudio,whichisbasedonIntelliJIDEA,apopularJavaIDE.ThissoftwaresuiteincludestheAndroidSDK.The Android Developer Tools (ADT) Bundle, a bundle that includes the AndroidSDKandEclipse.EclipseisanotherpopularJavaIDE.
Released inDecember 2014,AndroidStudio is the preferred IDE and theADTbundlewill not be supported in the future. Therefore, you should start using Android StudiounlessyouhaveverygoodreasonstochoosetheADTBundle.ThisbookassumesyouareusingAndroidStudio.
In thischapteryouwill learnhowtodownloadand installAndroidStudio.Afteryouhave successfully installed the IDE, you will write and build your first Androidapplicationandrunitontheemulator.
AndroidapplicationdevelopmentrequiresaJavaDevelopmentKit(JDK).ForAndroid5orlater,orifyouaredevelopingusingAndroidStudio,youneedJDK7orlater.Forpre-5Android,youneedJDK6or later.IfyoudonothaveaJDKinstalled,makesureyoudownload and install one by following the instructions in Appendix A, “Installing theJDK.”
DownloadingandInstallingAndroidStudioYoucandownloadAndroidStudiofromthiswebpage:
http://developer.android.com/sdk/index.html
AndroidStudioisavailableforWindows,MacOSXandLinux.InstallingAndroidStudioalsodownloadsandinstallstheAndroidSDK.
InstallingonWindowsFollowthesestepstoinstallAndroidStudioonWindows.
1. Double-click the exe file you downloaded to launch the Setup wizard. The
welcomepageofthewizardisshowninFigure1.1.
Figure1.1:TheAndroidStudioSetupprogram
2.ClickNexttoproceed.
Figure1.2:Choosingcomponents
3.YouwillseethenextdialogoftheSetupwizardasshowninFigure1.2.Hereyoucanchoosethecomponentsdoinstall.LeaveallcomponentsselectedandclickNextagain.
Figure1.3:Thelicenseagreement
4. The next dialog, shown in Figure 1.3, shows the license agreement. You reallyhave no choice but to agree on the license agreement if youwish to useAndroidStudio,inwhichcaseyouhavetoclickIAgree.
Figure1.4:Choosingtheinstalllocations
5.Inthenextdialogthatappears,whichisshowninFigure1.4,browsetotheinstalllocations for both Android Studio and the Android SDK. Android Studio shouldcomewith suggestions. It’s not a bad idea to accept the locations suggested.Onceyoufindlocationsforthesoftware,clickNext.
Figure1.5:Emulatorsetup
6. The next dialog, presented in Figure 1.5, shows the configuration page for theemulator.ClickNext.
Figure1.6:ChoosingtheStartmenufolder
7.Thenextdialog, shown inFigure1.6, is the lastdialogbefore installation.Hereyou have to select a Start menu folder. Simply accept the default value and clickInstall.AndroidStudiowillstarttoinstall.
Figure1.7:Installationcomplete
8.Onceinstallationiscomplete,youwillseeanotherdialogsimilartothatinFigure1.7.ClickNext.
Figure1.8:Setupisfinished
9.Onthenextdialog,showninFigure1.8,clickFinish,leavingthe“StartAndroidStudio” checkbox checked. If you have installed a previous version of AndroidStudio, the Setup wizard will ask you if you want to import settings from thepreviousversionofAndroidStudio.ThisisshowninFigure1.9.
Figure1.9:DecidingwhethertoimportsettingsfromanotherversionofAndroidStudio
10. Leave the second radio button checked and clickOK. The Setup wizard willquietlycreateanAndroidvirtualdeviceandreportit toyouonceit’sfinished.(SeeFigure1.10).
Figure1.10:TheSetupwizardhasjustcreatedanAVD
11. ClickFinish. Finally, Android Studio is ready to use. The welcome dialog isshowninFigure1.11.
Figure1.11:AndroidStudio’swelcomedialog
InstallingonMacOSXToinstallAndroidStudioonaMacOSXmachine,followthesesteps:
1.Launchthedmgfileyoudownloaded.2.DraganddropAndroidStudiototheApplicationsfolder.3.OpenAndroidStudioandfollowthesetupwizardtoinstalltheSDK.
InstallingonLinuxOnLinux,extractthedownloadedzipfile,openaterminalandchangedirectorytothebindirectoryoftheinstallationdirectoryandtype:
./studio.sh
Then,followthesetupwizardtoinstalltheSDK.
CreatingAnApplicationCreating anAndroid applicationwithAndroidStudio is as easy as a fewmouse clicks.ThissectionshowshowtocreateaHelloWorldapplication,packageit,andrunitontheemulator. Make sure you have installed the Android SDK and Android Studio byfollowingtheinstructionsintheprevioussection.
Next,followthesesteps.
1.ClicktheFilemenuinAndroidStudioandselectNewProject.ThefirstdialogoftheCreateNewProjectwizard,showninFigure1.12,appears.
Figure1.12:Enteringapplicationdetails
2.Enter thedetailsof thenewapplication.IntheApplicationname field, type thename to appear on theAndroid device. In theCompanyDomain field, type yourcompany’s domain. If you do not have one, just use example.com. The companydomain in reverseorderwillbeusedas thebasepackagenamefor theapplication.Thepackagenameuniquelyidentifiesyourapplication.YoucanchangethepackagenamebyclickingtheEditbuttontotherightofthefield.Bydefault,theprojectwillbe created under theAndroidStudioProjects directory createdwhen you installedAndroidStudio.Youcanchangethelocationtooifyouwish.3.ClickNext.TheseconddialogopensasshowninFigure1.13.Hereyouneedtoselect a target (phone andTablet,TV, etc) and theminimumAPI level.This bookonlydiscussesAndroidapplicationdevelopmentforphonesandtablets,soleavetheselectedoptionchecked.AsfortheminimumAPIlevel,thelowerthelevel,themoredevicesyourapplicationcanrunon,butthefewerfeaturesareavailabletoyou.Fornow,keeptheAPIlevelAndroidStudiohasselectedforyou.
Figure1.13:Selectingatarget
4.ClickNextagain.AdialogsimilartothatinFigure1.14appears.AndroidStudioisasking you if youwant to add an activity to your project and, if so,what kind ofactivity.Atthisstage,youprobablydonotknowwhatanactivityis.Fornow,thinkof it asawindow,andaddablankactivity toyourproject.So,accept the selectedactivitytype.
Figure1.14:Addinganactivity
5.ClickNextagain.ThenextdialogthatappearslookslikethedialoginFigure1.15.InthisdialogyoucanenteraJavaclassnameforyouractivityclassaswellasatitleforyouractivitywindowandalayoutname.Fornowjustacceptthedefault.
Figure1.15:Enteringtheactivityclassnameandotherdetails
5.ClickFinish.AndroidStudiowill prepareyourproject and itmay takeawhile.Finally,whenit’sfinished,youwillseeyourprojectinAndroidStudio,liketheoneshowninFigure1.16.
Figure1.16:ThenewAndroidproject
Thenextsectionshowsyouhowyoucanrunyourapplicationontheemulator.
RunningtheApplicationontheEmulatorNowthatyouhaveanapplicationready,youcanrunitbyclickingtheRunbutton.Youwillbeaskedtochooseadevice.
Figure1.17:Selectingadevicetoruntheapplication
If you have not created an emulator, do so now. If you have, youwill see all runningemulators.Or,youcanlaunchone.Click“Usesamedeviceforfuturelaunches”tousethesameemulatorinthefuture.
Next,clickOK.
ItwilltakesecondstolaunchtheAVD.Asyouknow,theemulatoremulatesanAndroiddevice.Justlikeaphysicaldevice,youneedtounlocktheemulator’sscreenwhenrunningyourappforthefirsttime.
Ifyourapplicationdoesnotopenautomatically,locatetheapplicationiconanddouble-clickonit.Figure1.18showstheapplicationyoujustcreated.
Figure1.18:Yourapplicationrunningontheemulator
Duringdevelopment,leavetheemulatorrunningwhileyouedityourcode.Thisway,theemulatordoesnotneedtobeloadedagaineverytimeyoutestyourapplication.
TheApplicationStructureNow,afterthelittleexcitementofhavingjustrunyourfirstAndroidapplication,let’sgobacktoAndroidStudioandtakealookatthestructureofanAndroidapplication.Figure1.19showsthelefttreeviewthatcontainstheprojectcomponents.
Figure1.19:Theapplicationstructure
There are twomain nodes in the Project window in Android Studio, app andGradleScripts.Theappnodecontainsallthecomponentsintheapplication.TheGradleScriptsnodecontainstheGradlebuildscriptsusedbyAndroidStudiotobuildyourproject.Iwillnotdiscussthesescripts,butitwouldbeagoodideaforyoutogetfamiliarwithGradle.
Therearethreenodesundertheappnode:
manifests.ContainsanAndroidManifest.xmlfilethatdescribesyourapplication.Itwillbeexplainedinmoredetailinthenextsection“TheAndroidManifest.”java.ContainsallJavaapplicationandtestclasses.res. Contains resource files. Underneath this directory are these directories:drawable (containing images for various screen resolutions), layout (containinglayout files),menu (containingmenufiles)andvalues (containingstringandothervalues).
TheRClass
NotvisiblefrominsideAndroidStudioisageneratedJavaclassnamedR,whichcanbe found in the app/build/generated/source directory of the project. R containsnestedclassesthatinturncontainall theresourceIDsforallyourresources.Everytimeyouadd,changeordeletearesource,Risre-generated.Forinstance,ifyouaddan image file named logo.png to the res/drawable directory, Android Studio willgenerateafieldnamedlogounderthedrawableclass,anestedclassinR.The purpose of havingR is so that you can refer to a resource in your code. Forinstance,youcanrefertothelogo.pngimagefilewithR.drawable.logo.
TheAndroidManifestEvery Android applicationmust have a manifest file calledAndroidManifest.xml filethatdescribestheapplication.Listing1.1showsasamplemanifestfile.
Listing1.1:Asamplemanifest
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.firstapp”>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.firstapp.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
Amanifest file is anXML documentwithmanifest as the root element. Thepackageattributeofthemanifestelementspecifiesauniqueidentifierfortheapplication.AndroidtoolswillalsousethisinformationtogenerateappropriateJavaclassesthatareusedfromtheJavasourceyouwrite.
Under<manifest> is an application element that describes the application. Amongothers,itcontainsoneormoreactivityelements thatdescribeactivities inyourapp.Anapplicationtypicallyhasamainactivitythatservesastheentrypoint totheapplication.Thename attribute of anactivity element specifies an activity class. It can be a fullyqualifiednameorjust theclassname.If it is thelatter, theclassisassumedtobeinthepackagespecifiedbythepackageattributeofthemanifestelement. Inotherwords, thenameattributeoftheaboveactivityelementcanbewrittenasoneofthefollowing:
android:name=“MainActivity”
android:name=”.MainActivity”
Youcanreferencearesourcefromyourmanifestfile(andotherXMLfilesintheproject)usingthisformat:
@resourceType/name
Forexample,thesearesomeoftheattributesoftheapplicationelementinListing1.1:
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”
Thefirstattribute,android:icon,referstoadrawablenamedic_launcher.IfyoubrowsetheprojectinAndroidStudio,youcanfindanic_launcher.pngfileunderres/drawable.
Thesecondattribute,android:label, refers toa string resourcecalledapp_name.Allstringresourcesarelocatedinthestrings.xmlfileunderres/values.
Finally, the thirdattribute,android:theme, references a stylenamedAppTheme.Allstylesaredefinedinthestyles.xmlfileunderres/values.StylesandthemesarediscussedinChapter10,“StylesandThemes.”
ThereareotherelementsthatmayappearintheAndroidmanifestandyouwilllearntousemanyoftheminthisbook.Youcanfindthecompletelistofelementshere:
http://developer.android.com/guide/topics/manifest/manifest-
element.html
TheAPKFileAnAndroidapplicationispackagedintoanapkfile,whichisbasicallyazipfileandcanbeopenedusingWinZiporasimilarprogram.Allapplicationsaresignedwithaprivatekey. This process sounds hard enough, but thankfully Android Studio takes care ofeverything.WhenyourunanAndroidapplicationfrominsideAndroidStudio,anapkfilewillbebuiltandsignedautomatically.Thefilewillbenamedapp-debug.apkandstoredin theapp/build/outputs/apkdirectoryunder theprojectdirectory.AndroidStudioalsonotifiestheemulatororthetargetdeviceofthelocationsothattheapkfilecanbefoundandexecuted.
Theautomaticallygeneratedapkfilealsocontainsdebuginformationtoenablerunningitindebugmode.
Figure 1.20 shows the structure of the apk file that is created when you run yourapplication.
Figure1.20:Androidapplicationstructure
Themanifestfileisthereandsoaretheresourcefiles.TheAndroidManifest.xmlfileiscompiled soyoucannotusea text editor to read it.There is alsoaclasses.dex file thatcontainsthebinarytranslationofyourJavaclassesintoDalvikexecutable.Notethatevenifyouhavemultiplejavafilesinyourapplication,thereisonlyoneclasses.dexfile.
DebugingYourApplicationAndroid Studio is full of useful features for rapidly developing and testing yourapplication.Oneofthefeaturesissupportfordebugging.
Thefollowingaresomeofthewaysyoucandebugyourapplication.
LoggingTheeasiestwaytodebuganapplicationisbyloggingmessages.Javaprogrammersliketouseloggingutilities,suchasCommonsLoggingandLog4J,tologmessages.TheAndroidframework provides the android.util.Log class for the same purpose. The Log classcomeswithmethodstologmessagesatdifferentloglevels.Themethodnamesareshort:d(debug),i(info),v(verbose),w(warning),e(error),andwtf(whataterriblefailure).
Thismethodsallowyoutowriteatagandthetext.Forexample,
Log.e(“activity”,“Somethingwentwrong”);
Duringdevelopment,youcanwatch theAndroidDDMSviewat thebottomofAndroid
Studio’smainscreen.
Thegood thingaboutLogCat is thatmessagesatdifferent log levelsaredisplayed indifferent colors. In addition, each message has a tag and this makes it easy to find amessage.Inaddition,LogCatallowsyoutosavemessagestoafileandfilterthemessagessoonlymessagesofinteresttoyouarevisible.
TheLogCatviewisshowninFigure1.21.
Figure1.21:LogCatinAndroidDDMS
Anyruntimeexception thrown, including thestack trace,willalsoappear inLogCat,soyoucaneasilyidentifywhichlineofcodeiscausingtheproblem.
SettingBreakpointsTheeasiestwaytodebuganapplicationisbyloggingmessages.However,ifthisdoesnothelpandyouneedtotraceyourapplication,youcanuseotherdebuggingtoolsinAndroidStudio.
Try addinga linebreakpoint inyour codeby clickingon a line and selectingRun>ToggleLineBreakpoint.Figure1.22showsalinebreakpointinthecodeeditor.
Figure1.22:Settingalinebreakpoint
Now,debugyourapplicationbyselectingRun>Debugapp.
TheDebugviewisshowninFigure1.23.
Figure1.23:TheDebugview
Here,youcanstepintoyourcode,viewvariables,andsoon.
TheAndroidSDKManagerWhenyouinstallAndroidStudio,thesetupprogramalsodownloadsthelatestversionoftheAndroidSDK.YoucanmanagethepackagesintheAndroidSDKusingtheAndroidSDKManager.
TolaunchtheAndroidSDKManager,inAndroidStudioclickTools>Android>SDKManager.Alternatively,clicktheSDKManagerbuttononthetoolbar.
TheSDKManagerbuttonisshowninFigure1.24.
Figure1.24:TheSDKManagerbuttononthetoolbar
TheSDKManagerwindowisshowninFigure1.25.
Figure1.25:TheAndroidSDKManagerwindow
In theAndroid SDKManager, you can download other versions of the SDK or deletecomponentsyoudonotneed.
CreatingAnAndroidVirtualDeviceTheSDKshipswithanemulatorsothatyoucantestyourapplicationswithoutaphysicaldevice. The emulator can be configured tomimic various Android phones and tablets,fromNexusStoNexus9.EachinstanceoftheconfiguredemulatoriscalledanAndroidvirtualdevice(AVD).Youcancreatemultiplevirtualdevicesandrunthemsimultaneouslytotestyourapplicationinmultipledevices.
When you installAndroid Studio, it also creates anAndroid virtual device.You cancreatemorevirtualdevicesusingtheAndroidVirtualDevice(AVD)Manager.
TocreateanAVD,opentheAndroidVirtualDevice(AVD)Manager.Youcanopenitby clickingTools >Android > AVDManager. Alternatively, simply click the AVDManagerbuttononthetoolbar.Figure1.26showstheAVDManagerbutton
Figure1.26:TheAVDManagerbuttononthetoolbar
If you have not created a single AVD in your machine, the first window of the AVDManagerwilllooklikethatinFigure1.27.Ifyouhavecreatedvirtualdevicesbefore,thefirstwindowwilllistallthedevices.
Figure1.27:TheAVDManager’swelcomescreen
TocreateanAVD,followthesesteps.
1.ClicktheCreateavirtualdevicebutton.YouwillseeawindowsimilartothatinFigure1.28.
Figure1.28:Selectingaphoneprofile
2. SelectPhone from Category and then select a device from the center window.Next,clicktheNextbutton.Thenextwindowwillshow.SeeFigure1.29.
Figure1.29:SelectingtheAPIlevelandABI
3.SelectanAPIlevelandapplicationbinaryinterface(ABI).Ifyouareusinga32-bitIntelCPU,thenitmustbex86.Ifitisa64-bitIntelCPU,chancesareyouneedthex86_64.4. Click the Next button. In the next step you will be asked to verify theconfigurationdetailsoftheAVDyouarecreating.(SeeFigure1.30.)
Figure1.30:VerifyingthedetailsoftheAVD
5.ClicktheFinishbutton.ItwilltakemorethanafewsecondsfortheAVDManagertocreateanewemulator.Onceit’sfinished,youwillseealistliketheoneinFigure1.31.
Figure1.31:AlistofavailableAVDs
For eachAVD, there are three actionbuttons in the rightmost column.The first icon, agreenarrow,isforlaunchingtheemulator.Thesecond,apencil,isforeditingtheemulatordetails.Thelastone,adownarrow,showsmoreactionssuchasDeleteandViewDetails.
RunningAnApplicationonAPhysicalDeviceThereareacoupleof reasons forwanting to testyourapplicationona realdevice.Themost compelling one is that you should test your applications on real devices beforepublishing them.Otherreasons includespeed.AnemulatormaynotbeasfastasanewAndroiddevice.Also,itisnotalwayseasytosimulatecertainuserinputsinanemulator.For example, you can change the screen orientation easily with a real device. On theemulator,youhavetopressCtrl+F12.
Torunyourapplicationonarealdevice,followthesesteps.
1.Declareyourapplicationasdebuggablebyaddingandroid:debuggable=“true”intheapplicationelementinthemanifestfile.2.EnableUSBdebuggingonthedevice.OnAndroid3.2orolder,theoptionisunderSettings >Applications >Development. OnAndroid 4.0 and later, the option isunderSettings>DeveloperOptions.OnAndroid4.2andlater,Developeroptionsishiddenbydefault.Tomakeitvisible,gotoSettings>AboutphoneandtapBuildnumberseventimes.
Next,setupyoursystemtodetectthedevice.Thestepdependsonwhatoperatingsystem
you’reusing.ForMacusers,youcanskipthisstep.Itwilljustwork.
For Windows users, you need to install the USB driver for Android Debug Bridge(adb), a tool that lets you communicatewith an emulator or connectedAndroiddevice.Youcanfindthelocationofthedriverfromthissite.
http://developer.android.com/tools/extras/oem-usb.html
ForLinuxusers,pleaseseetheinstructionshere.
http://developer.android.com/tools/device.html
OpeningAProjectinAndroidStudioYou can download the Android Studio projects accompanying this book from thepublisher’swebsite.Toopenaproject,selectFile>Openandbrowsetotheapplicationdirectory.Figure1.32showshowtheOpenFileorProjectwindowlookslike.
Figure1.32:Openingaproject
UsingJava8Bydefault,AndroidStudiocancompilesourceswithJava7 languagesyntax.However,youcanuseJava8languagefeatures,eventhoughJava8isnotyetofficiallysupported.ItgoeswithoutsayingthatyouneedJDK8orlatertousethehigherlanguagelevel.Also,even thoughyoucanuse the Java8 language features,you still cannotuse the librariesthatcomewithJava8,suchasthenewDateTimeAPIortheStreamAPI.
If you reallywant to use Java 8 towriteAndroid applications, here is how you canchangetheJavalanguagelevelfrom7to8inAndroidStudio.
1.ExpandtheGradleScriptsnodeontheProjectview.Youwillseetwobuild.gradlenodes on the list. Double-click the second build file to open it. You will seesomethinglikethis:
android{
compileSdkVersion21
buildToolsVersion“19.1.0”
defaultConfig{
…
}
buildTypes{
…
}
}
2.Addthelineinboldtothebuildfiletochangethelanguagelevelto7.
android{
compileSdkVersion21
buildToolsVersion“19.1.0”
defaultConfig{
…
}
buildTypes{
…
}
compileOptions{
sourceCompatibilityJavaVersion.VERSION_1_8
targetCompatibilityJavaVersion.VERSION_1_8
}
}
Aschangingthelanguageleveladdscomplexitytoaproject,thisbookwillstickwithJava7.
GettingRidoftheSupportLibraryWhenyoucreateanewprojectwithAndroidStudio,itstructurestheapplicationtousetheAndroidsupportlibrary,sothatyourapplicationcanberunwithalowerAPIlevel.Whilethismighthelp, inmanypractical circumstances,youmaynotwant the support library.Fortunately,youcanremovethesupportlibraryquiteeasilybyfollowingthesesteps.
1. In the app’s build.gradle file, remove the dependency on appcompat-v7 byremovingorcommentingoutthecorrespondingline:
dependencies{
compilefileTree(dir:‘libs’,include:[‘*.jar’])
//compile‘com.android.support:appcompat-v7:21.0.2’
}
2.Savethebuild.gradlefile.Amessageinlightyellowbackgroundwillappearonthetoppartoftheeditor,askingyoutosynchronizetheproject.ClickSyncnow.3. In the res/values/styles.xml file, assign android:Theme.Holo orandroid:Theme.Holo.Lighttotheparentattribute,likeso
<stylename=“AppTheme”parent=“android:Theme.Holo”>
<!—Customizeyourthemehere.—>
</style>
4. ChangeActionBarActivity in every activity class toActivity and remove theimportstatementthat importsActionBarActivity.Theshortcut for this inAndroidStudioisCtrl+Alt+O.5. In all the menu.xml files, replace app:showAsAction withandroid:showAsAction.Forexample,replace
app:showAsAction=“never”
with
android:showAsAction=“never”
6.RebuildtheprojectbyselectingProject>RebuildProject.
SummaryThis chapter discusses how to install the required software and create your firstapplication.Youalso learnedhow tocreateavirtualdevicesoyoucan testyourapp inmultipledeviceswithoutphysicaldevices.
Chapter2Activities
InChapter1, “GettingStarted”you learned towrite a simpleAndroid application. It isnow time to delve deeper into the art and science ofAndroid application development.This chapter discusses one of the most important component types in Androidprogramming,theactivity.
TheActivityLifecycleThe first application component that you need to get familiar with is the activity. Anactivityisawindowcontaininguserinterface(UI)componentsthattheusercaninteractwith.Startinganactivityoftenmeansdisplayingawindow.
An activity is an instance of the android.app.Activity class. A typical Androidapplication starts by starting an activity, which, as I said, loosely means showing awindow. The first window that the application creates is called the main activity andservesastheentrypointtotheapplication.Needlesstosay,anAndroidapplicationmaycontain multiple activities and you specify the main activity by declaring it in theapplicationmanifestfile.
For example, the followingapplication element in anAndroidmanifest defines twoactivities,oneofwhichisdeclaredasthemainactivityusingtheintent-filterelement.Tomakeanactivitythemainactivityofanapplication,itsintent-filterelementmustcontaintheMAINactionandLAUNCHERcategorylikeso.
<application…>
<activity
android:name=“com.example.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<activity
android:name=“com.example.SecondActivity”
android:label=”@string/title_activity_second”>
</activity>
</application>
Inthesnippetabove,itisnothardtoseethatthefirstactivityisthemainactivity.
WhentheuserselectsanapplicationiconfromtheHomescreen,thesystemwilllookforthemainactivityoftheapplicationandstartit.Startinganactivityentailsinstantiatingtheactivityclass(whichisspecifiedintheandroid:nameattributeoftheactivityelementinthemanifest)andcallingitslifecyclemethods.Itisimportantthatyouunderstandthesemethodssoyoucanwritecodecorrectly.
Thefollowingare the lifecyclemethodsofActivity.Somearecalledonceduring theapplicationlifetime,somecanbecalledmorethanonce.
onCreateonStartonResumeonPauseonStoponRestartonDestroy
Totrulyunderstandhowtheselifecyclemethodscomeintoplay,considerthediagraminFigure2.1.
Figure2.1:Theactivitylifecycle
ThesystembeginsbycallingtheonCreatemethodtocreatetheactivity.YoushouldplacethecodethatconstructstheUIhere.OnceonCreateiscompleted,youractivityissaidtobeintheCreatedstate.Thismethodwillonlybecalledonceduringtheactivitylifetime.
Next, thesystemcalls theactivity’sonStartmethod.When thismethod iscalled, theactivity becomes visible. Once thismethod is completed, the activity is in theStartedstate.Thismethodmaybecalledmorethanonceduringtheactivitylifetime.
onStartisfollowedbyonResumeandonceonResumeiscompleted,theactivityisintheResumedstate.HowIwishtheyhadcalleditRunninginsteadofResumed,becausethefactisthisisthestatewhereyouractivityisfullyrunning.onResumemaybecalledmultipletimesduringtheactivitylifetime.
Therefore, onCreated, onStart, and onResume will be called successively unlesssomething goes awry during the process. Once in the Resumed state, the activity isbasicallyrunningandwillstayinthisstateuntilsomethingoccurstochangethat,suchasifthealarmclocksetsofforthescreenturnsoffbecausethedeviceisgoingtosleep,orperhapsbecauseanotheractivityisstarted.
The activity that is leaving theResumed statewill have itsonPausemethod called.OnceonPause iscompleted,theactivityentersthePausedstate.onPausecanbecalledmultipletimesduringtheactivitylifetime.
What happens after onPause depends on whether or not your activity becomescompletely invisible. If it does, theonStopmethod is called and the activity enters theStoppedstate.Ontheotherhand,iftheactivitybecomesactiveagainafteronPause,thesystemcallstheonResumemethodandtheactivityre-enterstheResumedstate.
AnactivityintheStoppedstatemaybere-activatediftheuserchoosestogobacktothe activity or for some other reason it goes back to the foreground. In this case, theonRestartmethodwillbecalled,followedbyonStart.
Finally, when the activity is decommissioned, its onDestroy method is called. Thismethod,likeonCreate,canonlybecalledonceduringtheactivitylifetime.
ActivityDemoExampleTheActivityDemo application accompanying this book demonstrates when the activitylifecyclemethodsarecalled.Listing2.1showsthemanifestforthisapplication.
Listing2.1:ThemanifestforActivityDemo
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.activitydemo”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“8”
android:targetSdkVersion=“21”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.activitydemo.MainActivity”
android:screenOrientation=“landscape”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
ThismanifestisliketheoneinChapter1,“GettingStarted.”Ithasoneactivity,themainactivity. However, notice that I specify the orientation of the activity using theandroid:screenOrientationattributeoftheactivityelement.
ThemainclassforthisapplicationisprintedinListing2.2.TheclassoverridesallthelifecyclemethodsofActivityandprintsadebugmessageineachlifecyclemethod.
Listing2.2:TheMainActivityclassforActivityDemo
packagecom.example.activitydemo;
importandroid.os.Bundle;
importandroid.app.Activity;
importandroid.util.Log;
importandroid.view.Menu;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
Log.d(“lifecycle”,“onCreate”);
setContentView(R.layout.activity_main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
//Inflatethemenu;thisaddsitemstotheactionbar
//ifitispresent.
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
@Override
publicvoidonStart(){
super.onStart();
Log.d(“lifecycle”,“onStart”);
}
@Override
publicvoidonRestart(){
super.onRestart();
Log.d(“lifecycle”,“onRestart”);
}
@Override
publicvoidonResume(){
super.onResume();
Log.d(“lifecycle”,“onResume”);
}
@Override
publicvoidonPause(){
super.onPause();
Log.d(“lifecycle”,“onPause”);
}
@Override
publicvoidonStop(){
super.onStop();
Log.d(“lifecycle”,“onStop”);
}
@Override
publicvoidonDestroy(){
super.onDestroy();
Log.d(“lifecycle”,“onDestroy”);
}
}
Note that if you override an activity’s lifecycle method, you must call the overriddenmethodintheparentclass.
Beforeyourunthisapplication,createaLogcatmessagefiltertoshowonlymessagesfromtheapplication,filteringoutsystemmessages,byfollowingthesesteps.
SelectDebugfromtheLogleveldrop-downlist.Typeinthefiltertext,inthiscase“lifecycle,”inthesearchbox.Figure2.2showstheLogcatwindow.
Figure2.2:CreatingaLogcatmessagefilter
Runtheapplicationandnoticetheorientationoftheapplication.Itshouldbelandscape.Now, try running another application and then switch back to the ActivityDemoapplication.CheckthemessagesprintedinLogcat.
Note thatwhenyoucreate anewapplicationusingAndroidStudio, theactivityclassmay not extendActivity butActionBarActivity.ActionBarActivity is a class in theSupportLibrarythatsupportsusingtheactionbarinpre-3.0Androiddevices.(Theaction
barisdiscussedinChapter6,“TheActionBar.”)Ifyouarenotusingtheactionbarordonot plan on deploying to pre-3.0Android devices, you can replaceActionBarActivitywithActivity.
ChangingtheApplicationIconIf you do not like the application icon you have chosen, you can easily change it byfollowingthesesteps.
Saveajpegorpngfileinres/drawable.Pngispreferredbecausetheformatsupportstransparency.Edit theandroid:iconattributeof themanifest topoint to thenew image.Youcanrefertotheimagefilewiththisformat:@drawable/fileName,wherefileNameisthenameoftheimagefilewithouttheextension.
UsingAndroidResourcesAndroidisrich,itcomeswithabunchofassets(resources)youcanuseinyourapps.Tobrowsetheavailableresources,opentheapplicationmanifestinAndroidStudioandfillapropertyvaluebytyping“@android:”followedbyCtrl+space.AndroidStudiowillshowthelistofassets.(SeeFigure2.3).
Figure2.3:UsingAndroidassets
Forexample,toseewhatimages/iconsareavailable,select@android:drawable/.Touseadifferenticonforanapplication,changethevalueoftheandroid:iconattribute.
android:icon=”@android:drawable/ic_menu_day”
StartingAnotherActivity
ThemainactivityofanAndroidapplicationisstartedbythesystemitself,whentheuserselectstheappiconfromtheHomescreen.Inanapplicationwithmultipleactivities,itispossible (and easy) to start another activity. In fact, starting an activity from anotheractivitycanbedonesimplybycallingthestartActivitymethodlikethis.
startActivity(intent);
whereintentisaninstanceoftheandroid.content.Intentclass.
Asanexample,considertheSecondActivityDemoprojectthataccompaniesthisbook.Ithastwoactivities,MainActivityandSecondActivity.MainActivitycontainsabuttonthatwhenclicked startsSecondActivity.Thisprojectalso showshowyoucanwriteaneventlistenerprogrammatically.
ThemanifestforSecondActivityDemoisgiveninListing2.3.
Listing2.3:ThemanifestforSecondActivityDemo
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.secondactivitydemo”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“8”
android:targetSdkVersion=“19”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.secondactivitydemo.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<activity
android:name=“com.example.secondactivitydemo.SecondActivity”
android:label=”@string/title_activity_second”>
</activity>
</application>
</manifest>
Unlikethepreviousapplication,thisprojecthastwoactivities,oneofwhichisdeclaredasthemainactivity.
The layout files for themain and second activities are listed inListings 2.4 and 2.5,respectively.
Listing2.4:Theactivity_main.xmlfile
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingBottom=”@dimen/activity_vertical_margin”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
tools:context=”.MainActivity”>
<TextView
android:id=”@+id/textView1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/first_screen”/>
</RelativeLayout>
Listing2.5:Theactivity_second.xmlfile
<RelativeLayoutxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingBottom=”@dimen/activity_vertical_margin”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
tools:context=”.SecondActivity”>
<TextView
android:id=”@+id/textView1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”/>
</RelativeLayout>
BothactivitiescontainaTextView.TouchingtheTextViewinthemainactivitystartsthesecondactivityandpassamessagetothelatter.ThesecondactivitydisplaysthemessageinitsTextView.
TheactivityclassforthemainactivityisgiveninListing2.6.
Listing2.6:TheMainActivityclass
packagecom.example.secondactivitydemo;
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.View.OnTouchListener;
importandroid.widget.TextView;
publicclassMainActivityextendsActivityimplements
OnTouchListener{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextViewtv=(TextView)findViewById(R.id.textView1);
tv.setOnTouchListener(this);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
//Inflatethemenu;thisaddsitemstotheactionbarifit
//ispresent.
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
@Override
publicbooleanonTouch(Viewarg0,MotionEventevent){
Intentintent=newIntent(this,SecondActivity.class);
intent.putExtra(“message”,“MessagefromFirstScreen”);
startActivity(intent);
returntrue;
}
}
To handle the touch event, the MainActivity class has implemented theOnTouchListener interface and overridden its onTouch method. In this method, youcreateanIntentandputamessageinit.Youthencall thestartActivitymethodtostartthesecondactivity.
TheSecondActivityclassisgiveninListing2.7.
Listing2.7:TheSecondActivityclass
packagecom.example.secondactivitydemo;
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.widget.TextView;
publicclassSecondActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
Intentintent=getIntent();
Stringmessage=intent.getStringExtra(“message”);
((TextView)findViewById(R.id.textView1)).setText(message);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_second,menu);
returntrue;
}
}
IntheonCreatemethodofSecondActivity,yousettheviewcontentasusual.YouthencallthegetIntentmethodandretrieveamessagefromitsgetStringExtramethod,whichyou then pass to the setText method of theTextView. You retrieve theTextView bycallingthefindViewByIdmethod.
ThemainactivityandthesecondactivityareshowninFigures2.4and2.5,respectively.
Figure2.4:ThemainactivityinSecondActivityDemo
Figure2.5:ThesecondactivityinSecondActivityDemo
Activity-RelatedIntentsIn the SecondActivityDemo project, you learned that you can start a new activity bypassinganintenttothestartActivitymethod.YoucanalsocallstartActivityForResultifyouwantaresultfromtheinvokedactivity.
Hereisthecodethatactivatesanactivityintheproject:
Intentintent=newIntent(this,SecondActivity.class);
startActivity(intent);
Andoftenyouwanttopassadditionalinformationtotheinvokedactivity,whichyoucando by attaching the information to the intent. In the previous example, you did so bycallingtheputExtramethodontheIntent:
Intentintent=newIntent(this,SecondActivity.class);
intent.putExtra(“message”,“MessagefromFirstScreen”);
startActivity(intent);
Anintentthatisconstructedbypassingtoitanactivityclassiscalledanexplicitintent.TheIntentinSecondActivityDemoissuchanexample.
Youcanalsocreateanimplicitintent,inwhichcaseyoudonotspecifyanactivityclass.Rather,youpasstotheIntentclass’sconstructoranaction,suchasACTION_SEND,andlet thesystemdecidewhichactivity tostart. If there ismore thanoneactivities thatcanhandletheintent,thesystemwillnormallyasktheusertochoose.
ACTION_SENDisaconstantintheIntentclass.Table2.1showsalistofactionsthatcanstartanactivityasdefinedintheIntentclass.
Action
Description
ACTION_MAIN
Starttheactivityasamainentrypoint.
ACTION_VIEW
Viewthedataattachedtotheintent.
ACTION_ATTACH_DATA
Attachthedatathathasbeenaddedtotheintenttosomeotherplace.
ACTION_EDIT
Editthedataattachedtotheintent.
ACTION_PICK
Pickanitemfromthedata.
ACTION_CHOOSER
Displaysallapplicationsthatcanhandletheintent.
ACTION_GET_CONTENT
Allowtheusertoselectaparticularkindofdataandreturnit.
ACTION_DIAL
Dialthenumberattachedtotheintent.
ACTION_CALL
Callthepersonspecifiedintheintent.
ACTION_SEND
Sendthedataattachedtotheintent.
ACTION_SENDTO
Sendamessagetothepersonspecifiedintheintentdata.
ACTION_ANSWER
Answertheincomingcall.
ACTION_INSERT
Insertanemptyitemintothespecifiedcontainer.
ACTION_DELETE
Deletethespecifieddatafromitscontainer.
ACTION_RUN
Runtheattacheddata.
ACTION_SYNC
Performadatasynchronization.
ACTION_PICK_ACTIVITY
Selectanactivityfromasetofactivities.
ACTION_SEARCH
Performasearchusingthespecifiedstringasthesearchkey.
ACTION_WEB_SEARCH
Performawebsearchusingthespecifiedstringasthesearchkey.
ACTION_FACTORY_TEST
Indicatethisisthemainentrypointforfactorytests.
Table2.1:Intentactionsforstartinganactivity
Notallintentscanbeusedtostartanactivity.TomakesureanIntentcanrevolvetoanactivity,callitsresolveActivitymethodbeforepassingittostartActivity:
if(intent.resolveActivity(getPackageManager())!=null){
startActivity(intent);
}
An intent that cannot resolve to an action will throw an exception if passed tostartActivity.
Forexample,hereisanIntenttosendanemail.
Intentintent=newIntent(Intent.ACTION_SEND);
intent.setType(“message/rfc822”);//required
intent.putExtra(Intent.EXTRA_EMAIL,
newString[]{“walter@example.com”});//optional
intent.putExtra(Intent.EXTRA_SUBJECT,“subject”);//optional
intent.putExtra(Intent.EXTRA_TEXT,“body”);//optional
//Verifythattheintentwillresolvetoanactivity
if(intent.resolveActivity(getPackageManager())!=null){
startActivity(intent);
}else{
Toast.makeText(this,“Noemailclientfound.”,
Toast.LENGTH_LONG).show();
}
IfmultipleapplicationscanhandleanIntent, theuserwillbeable todecidewhether toalwaysusetheselectedapplicationinthefutureortouseitjustforthisoccasion.Youcanforceachoosertoappeareachtime(regardlessofwhetherornottheuserhasdecidedtousethesameapp),byusingthiscode:
startActivity(Intent.createChooser(intent,dialogTitle));
wheredialogTitleisthetitleoftheChooserdialog.
As another example, the following code sends an ACTION_WEB_SEARCH intent.Upon receiving themessage, the systemwill open thedefaultwebbrowser and tell thebrowsertogooglethesearchkey.
StringsearchKey=“Buffalo”;
Intentintent=newIntent(Intent.ACTION_WEB_SEARCH);
intent.putExtra(SearchManager.QUERY,searchKey);
startActivity(intent);
SummaryInthischapteryoulearnedabouttheactivitylifecycleandcreatedtwoapplications.Thefirstapplicationallowedyou toobservewheneachof the lifecyclemethodswascalled.Thesecondapplicationshowedhowtostartanactivityfromanotheractivity.
Chapter3UIComponents
One of the first things you do when creating an Android application is build the userinterface(UI)forthemainactivity.Thisisarelativelyeasytaskthankstotheready-to-useUIcomponentsthatAndroidprovides.
ThischapterdiscussessomeofthemoreimportantUIcomponents.
OverviewThe Android SDK provides various UI components called widgets that include manysimple and complex components. Examples of widgets include buttons, text fields,progressbars, etc. In addition, you alsoneed to choose a layout for layingout yourUIcomponents. Both widgets and layouts are implementations of the android.view.Viewclass. A view is a rectangular area that occupies the screen.View is one of the mostimportantAndroidtypes.However,unlessyouarecreatingacustomview,youdon’toftenworkwiththisclassdirectly.Instead,youoftenspendtimechoosingandusinglayoutsandUIcomponentsforyouractivities.
Figure3.1showssomeAndroidUIcomponents.
Figure3.1:AndroidUIcomponents
UsingtheAndroidStudioUITool
Creating aUI is easywithAndroid Studio.All you need is open the layout file for anactivityanddraganddropUIcomponentstothelayout.Theactivityfilesarelocatedintheres/layoutdirectoryofyourapplication.
Figure3.2showstheUItoolforcreatingAndroidUI.Thisiswhatyouseewhenyouopenanactivityfile.Thetoolwindowisdividedintothreemainareas.Ontheleftarethewidgets, which are grouped into different categories such as Layouts, Widgets, TextFields, Containers, etc. Click on the tab header of a category to see what widgets areavailableforthatcategory.
Figure3.2:UsingtheUItool
Tochooseawidget,clickonthewidgetanddragittotheactivityscreenatthecenter.Thescreen in Figure 3.2 shows two text fields and a button. You can also view how yourscreenwill look like in different devices by choosing a device from theDevices drop-down.
EachwidgetandlayouthasasetofpropertiesderivedfromtheViewclassoraddedtothe implementationclass.Tochangeanyof theseproperties, clickon thewidgeton thedrawingareaorselectitfromtheOutlinepaneintheStructurewindowontheright.ThepropertiesarelistedinthesmallpaneundertheLayoutpane.
What you do with the UI tool is reflected in the layout file, in the form of XML
elements.Toseewhathasbeengeneratedforyou,clicktheXMLviewatthebottomoftheUItool.
UsingBasicComponentsThe BasicComponents project is a simple Android application with one activity. Theactivityscreencontainstwotextfieldsandabutton.
Youcaneitheropentheaccompanyingapplicationorcreateoneyourselfbyfollowingthe instructions inChapter1,“GettingStarted.” Iwillexplain thisprojectbypresentingthe manifest for the application, which is an XML file named AndroidManifest.xmllocateddirectlyundertherootdirectory.
Listing3.1showstheAndroidManifest.xmlfortheBasicComponentsproject.
Listing3.1:ThemanifestforBasicComponents
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.basiccomponents”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“8”
android:targetSdkVersion=“17”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.basiccomponents.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
The first thing to note is the package attribute of themanifest tag, which specifiescom.example.basiccomponentsastheJavapackageforthegeneratedclasses.Alsonotethat the application element defines one activity, the main activity. The applicationelementalsospecifiestheicon,label,andthemeforthisapplication.
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
Itisgoodpracticetoreferencearesource(suchasaniconoralabel)indirectly,likewhatI
amdoinghere.@drawable/ic_launcher,thevalueforandroid:icon,referstoadrawable(normallyan imagefile) that residesunder theres/drawabledirectory. ic_launchercanmeananic_launcher.pngoric_launcher.jpgfile.
All string references start with@string. In the example above,@string/app_namerefers to theapp_name key in the res/values/strings.xml file. For this application, thestrings.xmlfileisgiveninListing3.2.
Listing3.2:Thestrings.xmlfileunderres/values
<?xmlversion=“1.0”encoding=“utf-8”?>
<resources>
<stringname=“app_name”>BasicComponents</string>
<stringname=“action_settings”>Settings</string>
<stringname=“prompt_email”>Email</string>
<stringname=“prompt_password”>Password</string>
<stringname=“action_sign_in”><b>Signin</b></string>
</resources>
Let’snowlookat themainactivity.Thereare tworesourcesconcernedwithanactivity,thelayoutfilefortheactivityandtheJavaclassthatderivesfromandroid.app.Activity.Forthisproject,thelayoutfileisgiveninListing3.3andtheactivityclass(MainActivity)inListing3.4.
Listing3.3:Thelayoutfile
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_gravity=“center”
android:gravity=“center_horizontal”
android:orientation=“vertical”
android:padding=“120dp”
tools:context=”.MainActivity”>
<EditText
android:id=”@+id/email”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:hint=”@string/prompt_email”
android:inputType=“textEmailAddress”
android:maxLines=“1”
android:singleLine=“true”/>
<EditText
android:id=”@+id/password”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:hint=”@string/prompt_password”
android:imeActionId=”@+id/login”
android:imeOptions=“actionUnspecified”
android:inputType=“textPassword”
android:maxLines=“1”
android:singleLine=“true”/>
<Button
android:id=”@+id/sign_in_button”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“right”
android:layout_marginTop=“16dp”
android:paddingLeft=“32dp”
android:paddingRight=“32dp”
android:text=”@string/action_sign_in”/>
</LinearLayout>
The layout file contains a LinearLayout element with three children, namely twoEditTextcomponentsandabutton.
Listing3.4:TheMainActivityclassofBasicComponents
packagecom.example.basiccomponents;
importandroid.os.Bundle;
importandroid.app.Activity;
importandroid.view.Menu;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
//Inflatethemenu;thisaddsitemstotheactionbarifit
//ispresent.
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
}
TheMainActivityclassinListing3.4isaboilerplateclasscreatedbyAndroidStudio.Itoverrides the onCreate and onCreateOptionsMenu methods. onCreate is a lifecyclemethodthatgetscalledwhentheapplicationiscreated.InListing3.4, itsimplysetsthecontentviewfortheactivityusingthelayoutfile.onCreateOptionsMenuinitializesthecontentoftheactivity’soptionsmenu.Itmustreturntrueforthemenutobedisplayed.
Runtheapplicationandyou’llseetheactivityasshowninFigure3.3.
Figure3.3:TheBasicComponentsproject
ToastAToastisasmallpopupfordisplayingamessageasfeedbackfortheuser.AToastdoesnotreplacethecurrentactivityandonlyoccupiesthespacetakenbythemessage.
Figure3.4showsaToastthatsays“Downloadingfile…”TheToastdisappearsafteraprescribedperiodoftime.
Figure3.4:AToast
Theandroid.widget.Toast class is the template forcreatingaToast.TocreateaToast,callitsonlyconstructorthattakesaContextasanargument:
publicToast(android.content.Contextcontext)
ToastalsoprovidestwostaticmakeTextmethodsforcreatinganinstanceofToast.Thesignaturesofbothmethodoverloadsare
publicstaticToastmakeText(android.content.Contextcontext,
intresourceId,intduration)
publicstaticToastmakeText(android.content.Contextcontext,
java.lang.CharSequencetext,intduration)
Bothoverloads require thatyoupassaContext, possibly theactiveactivity, as the firstargument.Inaddition,bothoverloadstakeastring,whichmaycomefromastrings.xmlfileoraStringobject,andthedurationofthedisplayfortheToast.TwovalidvaluesforthedurationaretheLENGTH_LONGandLENGTH_SHORTstaticfinalsinToast.
TodisplayaToast,callitsshowmethod.Thismethodtakesnoargument.
ThefollowingcodesnippetshowshowtocreateanddisplayaToastinanactivityclass.
Toast.makeText(this,“Downloading…”,Toast.LENGTH_LONG).show();
Bydefault,aToastisdisplayednearthebottomoftheactiveactivity.However,youcanchangeitspositionbycallingitssetGravitymethodbeforecallingitsshowmethod.HereisthesignatureofsetGravity.
publicvoidsetGravity(intgravity,intxOffset,intyOffset)
Thevalidvalueforgravity isoneof thestatic finals in theandroid.view.Gravityclass,includingCENTER_HORIZONTALandCENTER_VERTICAL.
YoucanalsoenhancethelookofaToastbycreatingyourownlayoutfileandpassingit
totheToast’ssetViewmethod.HereisanexampleofhowtocreateacustomToast.
LayoutInflaterinflater=getLayoutInflater();
Viewlayout=inflater.inflate(R.layout.toast_layout,
(ViewGroup)findViewById(R.id.toast_layout_root));
Toasttoast=newToast(getApplicationContext());
toast.setView(layout);
toast.show();
In this case, R.layout.toast_layout is the layout identifier for the Toast andR.id.toast_layout_rootistheidoftherootelementinthelayoutfile.
AlertDialogLike aToast, anAlertDialog is awindow that provides feedback to the user.Unlike aToastthatfadesbyitself,however,anAlertDialogshowsindefinitelyuntilitlosesfocus.Inaddition,anAlertDialogcancontainuptothreebuttonsandalistofselectableitems.AbuttonaddedtoanAlertDialogcanbeconnectedtoalistenerthatgetstriggeredwhenthebuttonisclicked.
Figure3.5showsasampleAlertDialog.
Figure3.5:AnAlertDialog
The android.app.AlertDialog class is the template for creating an AlertDialog. Allconstructorsinthisclassareprotected,soyoucannotusethemunlessyouaresubclassingtheclass.Instead,youshouldusetheAlertDialog.BuilderclasstocreateanAlertDialog.YoucanuseoneofthetwoconstructorsofAlertDialog.Builder.
publicAlertDialog.Builder(android.content.Contextcontext)
publicAlertDialog.Builder(android.content.Contextcontext,
inttheme)
Once you have an instance ofAlertDialog.Builder, you can call its createmethod toreturn anAlertDialog.However, before callingcreate you can call variousmethodsofAlertDialog.Builder todecoratetheresultingAlertDialog. Interestingly, themethods inAlertDialog.BuilderreturnthesameinstanceofAlertDialog.Builder,soyoucancascadethem.HerearesomeofthemethodsinAlertDialog.Builder.
publicAlertDialog.BuildersetIcon(intresourceId)
SetstheiconoftheresultingAlertDialogwiththeDrawablepointedbyresourceId.
publicAlertDialog.BuildersetMessage(java.lang.CharSequencemessage)
SetsthemessageoftheresultingAlertDialog.
publicAlertDialog.BuildersetTitle(java.lang.CharSequencetitle)
SetsthetitleoftheresultingAlertDialog.
publicAlertDialog.BuildersetNegativeButton(
java.lang.CharSequencetext,
android.content.DialogInterface.OnClickListenerlistener)
Assignsabuttonthattheusershouldclicktoprovideanegativeresponse.
publicAlertDialog.BuildersetPositiveButton(
java.lang.CharSequencetext,
android.content.DialogInterface.OnClickListenerlistener)
Assignsabuttonthattheusershouldclicktoprovideapositiveresponse.
publicAlertDialog.BuildersetNeutralButton(
java.lang.CharSequencetext,
android.content.DialogInterface.OnClickListenerlistener)
Assignsabuttonthattheusershouldclicktoprovideaneutralresponse.
Forinstance,thefollowingcodeproducesanAlertDialogthatlooksliketheoneinFigure3.5.
newAlertDialog.Builder(this)
.setTitle(“Pleaseconfirm”)
.setMessage(
“Areyousureyouwanttodelete”+
“thiscontact?”)
.setPositiveButton(“Yes”,
newDialogInterface.OnClickListener(){
publicvoidonClick(
DialogInterfacedialog,
intwhichButton){
//deletepicturehere
dialog.dismiss();
}
})
.setNegativeButton(“No”,
newDialogInterface.OnClickListener(){
publicvoidonClick(
DialogInterfacedialog,
intwhich){
dialog.dismiss();
}
})
.create()
.show();
PressingtheYesbuttonwillexecutethelistenerpassedtothesetPositiveButtonmethodandpressingtheNobuttonwillrunthelistenerpassedtothesetNegativeButtonmethod.
NotificationsAnotificationisamessageonthestatusbar.Unlikeatoast,anotificationispersistentandwillkeepshowinguntilitisclosedorthedeviceisshutdown.
Anotificationisaninstanceofandroid.app.Notification.ThemostconvenientwaytocreateanotificationisbyusinganestedclasscalledBuilder,whichcanbeinstantiatedbypassing a Context. You can then call the build method on the builder to create aNotification.
Notificationn=newNotification.Builder(context).build();
TheNotification.Builderclasshasmethods todecorate theresultingnotification.Thesemethods include addAction, setAutoCancel, setColor, setContent, setContentTitle,setContentIntent,setLargeIcon,setSmallIconandsetSound.
Manyofthesemethodsareself-explanatory,butaddActionandsetContentIntentareofparticularimportancebecauseyoucanusethemtoaddanactionthatwillbeperformedwhentheusertouchesthenotification.Inthiscase,anotificationactionisrepresentedbyaPendingIntent.Here are the signatures ofaddAction and setContentIntent, both ofwhichtakeaPendingIntent.
publicNotification.BuilderaddAction(inticon,
java.lang.CharSequencetitle,
android.app.PendingIntentintent)
publicNotification.BuildersetContentIntent(
android.app.PendingIntentintent)
When the user touches the notification, the sendmethod of thePendingIntent will beinvoked.SeethesidebarforadescriptionofPendingIntent.
setAutoCancel is also important and passing true to it allows the notification to bedismissedwhentheusertouchesitonthenotificationdrawer.Thenotificationdrawerisanareathatopenswhenyouslidedownthestatusbar.Thenotificationdrawershowsallnotificationsthatthesystemhavereceivedandhavenotbeendismissed.
Themethods inNotification.Builder return the sameBuilder object, so they can becascaded:
Notificationnotification=newNotification.Builder(context)
.setContentTitle(“Newnotification”)
.setContentText(“You’vegotone!”)
.setSmallIcon(android.R.drawable.star_on)
.setContentIntent(pendingIntent)
.setAutoCancel(false)
.addAction(android.R.drawable.star_big_on,
“Open”,pendingIntent)
.build();
To sound a ringtone, flash lights andmake thedevicevibrate, you canOR thedefaultsflagslikeso:
notification.defaults|=Notification.DEFAULT_SOUND;
notification.defaults|=Notification.DEFAULT_LIGHTS;
notification.defaults|=Notification.DEFAULT_VIBRATE;
Inaddition,tomakerepeatingsound,youcansettheFLAG_INSISTENTflag.
notification.flags|=Notification.FLAG_INSISTENT;
ThePendingIntentClass
APendingIntentencapsulatesanIntentandanactionthatwillbecarriedoutwhenitssendmethodisinvoked.SinceaPendingIntentisapendingintent,theactionisnormallyanoperationthatwillbeinvokedsometimeinthefuture,mostprobablybythesystem.Forexample,aPendingIntentcanbeusedtoconstructaNotificationsothatsomethingcanbemadehappenwhentheusertouchesthenotification.TheactioninaPendingIntentisoneofseveralmethodsintheContextclass,suchasstartActivity,startServiceorsendBroadcast.YouhavelearnedthattostartanactivityyoucanpassanIntenttothestartActivitymethodonaContext.
Intentintent=…
context.startActivity(intent);
TheequivalentcodeforstartinganactivityusingaPendingIntentlookslikethis:
Intentintent=…
PendingIntentpendingIntent=PendingIntent.getActivity(context,0,intent,0);
pendingIntent.send();
ThestaticmethodgetActivity isoneofseveralmethods that returnsan instanceofPendingIntent. Other methods are getActivities, getService and getBroadcast.Thesemethodsdetermine the action that the resultingPendingIntent canperform.Constructing aPendingIntent by calling getActivity returns an instance that canstart an activity.Creating aPendingIntent usinggetService gives you an instancethat can be used to start a service. You call getBroadcast if you want aPendingIntentforsendingabroadcast.
To publish a notification, use the NotificationManager, which is one of the built-in
services in theAndroidsystem.Asit isanexistingsystemservice,youcanobtain itbycallingthegetSystemServicemethodonanactivity,likeso:
NotificationManagernotificationManager=(NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
Then, you can publish a notification by calling the notify method on theNotificationManager,passingauniqueIDandthenotification.
notificationManager.notify(notificationId,notification);
ThenotificationIDisanintegerthatyoucanchoose.ThisIDisneededjustincaseyouwanttocancelthenotification,inwhichcaseyoupasstheIDtothecancelmethodoftheNotificationManager:
notificationManager.cancel(notificationId);
TheNotificationDemoproject isanapplicationthatshowshowtousenotifications.Themainactivityoftheappcontainstwobuttons,oneforpublishinganotificationandoneforcancellingit.Afterthenotificationispublished,openingitwillinvokeasecondactivity.
Listing3.5showsthelayoutfileandListing3.6theactivityclass.
Listing3.5:ThelayoutfileofthemainactivityofNotificationDemo
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:orientation=“horizontal”>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:onClick=“setNotification”
android:text=“SetNotification”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:onClick=“clearNotification”
android:text=“ClearNotification”/>
</LinearLayout>
Listing3.6:Themainactivityclass
packagecom.example.notificationdemo;
importandroid.app.Activity;
importandroid.app.Notification;
importandroid.app.NotificationManager;
importandroid.app.PendingIntent;
importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.View;
publicclassMainActivityextendsActivity{
intnotificationId=1001;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
publicvoidsetNotification(Viewview){
Intentintent=newIntent(this,SecondActivity.class);
PendingIntentpendingIntent=
PendingIntent.getActivity(this,0,intent,0);
Notificationnotification=newNotification.Builder(this)
.setContentTitle(“Newnotification”)
.setContentText(“You’vegotanotification!”)
.setSmallIcon(android.R.drawable.star_on)
.setContentIntent(pendingIntent)
.setAutoCancel(true)
.addAction(android.R.drawable.ic_menu_gallery,
“Open”,pendingIntent)
.build();
NotificationManagernotificationManager=
(NotificationManager)getSystemService(
NOTIFICATION_SERVICE);
notificationManager.notify(notificationId,notification);
}
publicvoidclearNotification(Viewview){
NotificationManagernotificationManager=
(NotificationManager)getSystemService(
NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId);
}
}
Whenyouruntheapplication,youwillseethemainactivitywithtwobuttons,liketheoneshowninFigure3.6.
Figure3.6:TheNotificationDemoproject
If you click the Set Notification button, the notification icon (an orange star) will beshownonthestatusbar.
Figure3.6:Thenotificationicononthestatusbar
Nowdragdown thestatusbar right to thebottomof the screen toopen thenotificationdrawer(SeeFigure3.7).
Figure3.7:Thenotificationinthenotificationdrawer
The notification drawer in Figure 3.7 has one notification. The upper part of thenotification UI has a title that screams “New notification.” The text “You’ve got anotification”isthenotificationcontent.ThelowerpartisaUIcomponentthatrepresentsan action. With its autoClose set to true, the notification will be canceled if the usertouches on the content.However, itwill not close if the user touches on the actionUI.Unfortunately,itisnotstraightforwardtomakebothareascancelthenotificationdrawerwhentouched.Toremedythissituationyoucanuseabroadcastreceiver,asexplainedinChapter25,“BroadcastReceivers.”
In this example, touching on the notification starts the SecondActivity, as shown inFigure3.8.
Figure3.8:Thesecondactivityactivatedbythenotification
SummaryIn this chapter you learned about the UI components available in Android. You alsolearnedhowtousethetoast,dialogsandnotifications.
Chapter4Layouts
Layoutsare importantbecause theydirectlyaffect the lookandfeelofyourapplication.Technically,alayoutisaviewthatarrangeschildviewsaddedtoit.Androidcomeswithanumber of built-in layouts, ranging fromLinearLayout,which is the easiest to use, toRelativeLayout,whichisthemostpowerful.
ThischapterdiscussesthevariouslayoutsinAndroid.
OverviewAn important Android component, a layout defines the visual structure of your UIcomponents.A layout is a subclassofandroid.view.ViewGroup,which in turn derivesfromandroid.view.View.AViewGroupisaspecialviewthatcancontainotherviews.Alayoutcanbedeclaredinalayoutfileoraddedprogrammaticallyatruntime.
ThefollowingaresomeofthelayoutsinAndroid.
LinearLayout. A layout that aligns its children in the same direction, eitherhorizontallyorvertically.RelativeLayout.Alayoutthatarrangeseachofitschildrenbasedonthepositionsofoneormoreofitssiblings.FrameLayout. A layout that arranges each of its children based on top of oneanother.TableLayout.Alayoutthatorganizesitschildrenintorowsandcolumns.GridLayout.Alayoutthatarrangesitschildreninagrid.
Inamajorityofcases,aviewinalayoutmusthavethelayout_widthandlayout_heightattributes so that the layout knows how to size the view. Both layout_width andlayout_heightattributesmaybeassignedthevaluematch_parent(tomatchtheparent’swidth/height),wrap_content(tomatchthewidth/heightofitscontent)orameasurementunit.
TheAbsoluteLayout,whichoffersexactlocationsforitschildviews,isdeprecatedandshouldnotbeused.UseRelativeLayoutinstead.
LinearLayoutALinearLayout is a layout that arranges its children either horizontally or vertically,depending on the value of its orientation property. The LinearLayout is the easiestlayouttouse.
ThelayoutinListing4.1isanexampleofLinearLayoutwithhorizontalorientation.Itcontainsthreechildren,anImageButton,aTextViewandaButton.
Listing4.1:AhorizontalLinearLayout
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:orientation=“horizontal”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<ImageButton
android:src=”@android:drawable/btn_star_big_on”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”/>
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/hello_world”/>
<Buttonandroid:text=“Button1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”/>
</LinearLayout>
Figure4.1showstheviewsintheLinearLayoutinListing4.1.
Figure4.1:HorizontalLinearLayoutexample
The layout in Listing 4.2 is a vertical LinearLayout with three child views, anImageButton,aTextViewandaButton.
Listing4.2:Verticallinearlayout
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<ImageButton
android:src=”@android:drawable/btn_star_big_on”
android:layout_gravity=“center”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”/>
<TextView
android:layout_gravity=“center”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_marginLeft=“15dp”
android:text=”@string/hello_world”/>
<Buttonandroid:text=“Button1”
android:layout_gravity=“center”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”/>
</LinearLayout>
Figure4.2showstheverticalLinearLayout.
Figure4.2:Verticallinearlayoutexample
Note that each view in a layout can have a layout_gravity attribute to determine itspositionwithin itsaxis.Forexample, setting the layout_gravity attribute tocenterwillcenterit.
ALinearLayoutcanalsohaveagravityattributethataffectsitsgravity.Forexample,the layout in Listing 4.3 is a verticalLinearLayout whose gravity attribute is set tobottom.
Listing4.3:Verticallinearlayoutwithbottomgravity
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:orientation=“vertical”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:gravity=“bottom”>
<ImageButton
android:src=”@android:drawable/btn_star_big_on”
android:layout_gravity=“center”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”/>
<TextView
android:layout_gravity=“center”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_marginLeft=“15dp”
android:text=”@string/hello_world”/>
<Buttonandroid:text=“Button1”
android:layout_gravity=“center”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”/>
</LinearLayout>
Figure4.3showstheverticalLinearLayoutinListing4.3.
Figure4.3:Verticallinearlayoutwithgravity
RelativeLayoutThe RelativeLayout is the most powerful layout available. All children in aRelativeLayoutcanbepositionedrelativetoeachotheror totheirparent.Forexample,youcantellaviewtobepositionedtotheleftorrightofanotherview.Or,youcanspecifythataviewisalignedtothebottomortopedgeofitsparent.
PositioningachildinaRelativeLayoutisachievedusingtheattributessummarizedinTable4.1.
Attribute
Description
layout_above
PlacesthebottomedgeofthisviewabovethespecifiedviewID.
layout_alignBaseline
PlacesthebaselineofthisviewonthebaselineofthespecifiedviewID.
layout_alignBottom
Alignsthebottomofthisviewwiththespecifiedview.
layout_alignEnd
Alignstheendedgeofthisviewwiththeendedgeofthespecifiedview.
layout_alignLeft
Alignstheleftedgeofthisviewwiththeleftedgeofthespecifiedview.
layout_alignParentBottom
Avalueoftruealignsthebottomofthisviewwiththebottomedgeofitsparent.
layout_alignParentEnd
Avalueoftruealignstheendedgeofthisviewwiththeendedgeofitsparent.
layout_alignParentLeft
Avalueoftruealignstheleftedgeofthisviewwiththeleftedgeofitsparent.
layout_alignParentRight
Avalueoftruealignstherightedgeofthisviewwiththerightedgeofitsparent.
layout_alignParentStart
Avalueoftruealignsthestartedgeofthisviewwiththestartedgeofitsparent.
layout_alignParentTop
Avalueoftruealignsthetopedgeofthisviewwiththetopedgeofitsparent.
layout_alignRight
Alignstherightedgeofthisviewwiththerightedgeofthegivenview.
layout_alignStart
Alignsthestartedgeofthisviewwiththestartedgeofthegivenview.
layout_alignTop
Alignsthetopedgeofthisviewwiththetopedgeofthegivenview.
layout_alignWithParentIfMissing
Avalueoftruesetstheparentastheanchorwhentheanchorcannotbefoundforlayout_toLeftOf,layout_toRightOf,etc.
layout_below
Placesthetopedgeofthisviewbelowthegivenview.
layout_centerHorizontal
Avalueoftruecentersthisviewhorizontallywithinitsparent.
layout_centerInParent
Avalueoftruecentersthisviewhorizontallyandverticallywithinitsparent.
layout_centerVertical
Avalueoftruecenterthisviewverticallywithinitsparent.
layout_toEndOf Placesthestartedgeofthisviewtotheendofthegivenview.
layout_toLeftOf
Placestherightedgeofthisviewtotheleftofthegivenview.
layout_toRightOf
Placestheleftedgeofthisviewtotherightofthegivenview.
layout_toStartOf
Placestheendedgeofthisviewtothestartofthegivenview.
Table4.1:AttributesforchildrenofaRelativeLayout
As an example, the layout in Listing 4.4 specifies the positions of three views and aRelativeLayout.
Listing4.4:Relativelayout
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=“2dp”
android:paddingRight=“2dp”>
<Button
android:id=”@+id/cancelButton”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“Cancel”/>
<Button
android:id=”@+id/saveButton”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_toRightOf=”@id/cancelButton”
android:text=“Save”/>
<ImageView
android:layout_width=“150dp”
android:layout_height=“150dp”
android:layout_marginTop=“230dp”
android:padding=“4dp”
android:layout_below=”@id/cancelButton”
android:layout_centerHorizontal=“true”
android:src=”@android:drawable/ic_btn_speak_now”/>
<LinearLayout
android:id=”@+id/filter_button_container”
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:layout_alignParentBottom=“true”
android:gravity=“center|bottom”
android:background=”@android:color/white”
android:orientation=“horizontal”>
<Button
android:id=”@+id/filterButton”
android:layout_width=“wrap_content”
android:layout_height=“fill_parent”
android:text=“Filter”/>
<Button
android:id=”@+id/shareButton”
android:layout_width=“wrap_content”
android:layout_height=“fill_parent”
android:text=“Share”/>
<Button
android:id=”@+id/deleteButton”
android:layout_width=“wrap_content”
android:layout_height=“fill_parent”
android:text=“Delete”/>
</LinearLayout>
</RelativeLayout>
AddingAnIdentifier
The first button inListing 4.4 includes the following id attribute so that it can bereferencedfromthecode.
android:id=”@+id/cancelButton”
Theplussign(+)after@indicatesthattheidentifier(inthiscase,cancelButton)isbeingaddedwiththisdeclarationandisnotdeclaredinaresourcefile.
Figure4.4showstheRelativeLayoutinListing4.4.
Figure4.4:RelativeLayout
FrameLayoutAFrameLayoutpositionsitschildrenontopofeachother.Byadjustingthemarginandpaddingofaview,itispossibletolayouttheviewbelowanotherview,asshownbythelayoutinListing4.5.
Listing4.5:UsingaFrameLayout
<FrameLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:orientation=“horizontal”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<Buttonandroid:text=“Button1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_marginTop=“100dp”
android:layout_marginLeft=“100dp”/>
<ImageButton
android:src=”@android:drawable/btn_star_big_on”
android:alpha=“0.35”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_marginTop=“90dp”
android:layout_marginLeft=“90dp”/>
</FrameLayout>
ThelayoutinListing4.5usesaFrameLayoutwithaButtonandanImageButton.TheImageButtonisplacedabovetheButton,asshowninFigure4.5.
Figure4.5:UsingFrameLayout
TableLayoutATableLayout is used to arrange child views in rows and columns.TheTableLayoutclass isa subclassofLinearLayout.Toadda row inaTableLayout, use aTableRowelement.AviewdirectlyaddedtoaTableLayout(withoutaTableRow)willalsooccupyarowthatspansallcolumns.
The layout in Listing 4.6 shows a TableLayout with four rows, two of which arecreatedusingTableRowelements.
Listing4.6:UsingtheTableLayout
<TableLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“center”>
<TableRow
android:id=”@+id/tableRow1”
android:layout_width=“500dp”
android:layout_height=“wrap_content”
android:padding=“5dip”>
<ImageViewandroid:src=”@drawable/ic_launcher”/>
<ImageViewandroid:src=”@android:drawable/btn_star_big_on”/>
<ImageViewandroid:src=”@drawable/ic_launcher”/>
</TableRow>
<TableRow
android:id=”@+id/tableRow2”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”>
<ImageViewandroid:src=”@android:drawable/btn_star_big_off”/>
<TextClock/>
<ImageViewandroid:src=”@android:drawable/btn_star_big_on”/>
</TableRow>
<EditTextandroid:hint=“Yourname”/>
<Button
android:layout_height=“wrap_content”
android:text=“Go”/>
</TableLayout>
Figure4.6showshowtheTableLayoutinListing4.6isrendered.
Figure4.6:UsingTableLayout
GridLayoutAGridLayoutissimilartoaTableLayout,butthenumberofcolumnsmustbespecifiedusingacolumnCountattribute.Listing4.7showsanexampleofGridLayout.
Listing4.7:GridLayoutexample
<GridLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“center”
android:columnCount=“3”>
<!—1strow,spanning3columns—>
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“Enteryourname”
android:layout_columnSpan=“3”
android:textSize=“26sp”
/>
<!—2ndrow—>
<TextViewandroid:text=“FirstName”/>
<EditText
android:id=”@+id/firstName”
android:layout_width=“200dp”
android:layout_columnSpan=“2”/>
<!—3rdrow—>
<TextViewandroid:text=“LastName”/>
<EditText
android:id=”@+id/lastName”
android:layout_width=“200dp”
android:layout_columnSpan=“2”/>
<!—4throw,spanning3columns—>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_column=“2”
android:layout_gravity=“right”
android:text=“Submit”/>
</GridLayout>
Figure4.7visualizestheGridLayoutinListing4.7.
Figure4.7:UsingGridLayout
CreatingALayoutProgrammaticallyThemostcommonwaytocreatealayoutisbyusinganXMLfile,asyouhaveseenintheexamplesinthischapter.However,itisalsopossibletocreatealayoutprogrammatically,by instantiating the layout class and passing it to the addContentView method in anactivity class. For instance, the following code is part of the onCreate method of anactivity that programmatically creates a LinearLayout, sets a couple properties, andpassesittoaddContentView.
LinearLayoutroot=newLinearLayout(this);
LinearLayout.LayoutParamsmatchParent=new
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
root.setOrientation(LinearLayout.VERTICAL);
root.setGravity(Gravity.CENTER_VERTICAL);addContentView(root,matchParent);
SummaryAlayoutisresponsibleforarrangingitschildviews.Itdirectlyaffectthelookandfeelof
an application. In this chapter you learned some of the layouts available in Android,LinearLayout,RelativeLayout,FrameLayout,TableLayout,andGridLayout.
Chapter5Listeners
Like many GUI systems, Android is event based. User interaction with a view in anactivitymay triggeraneventandyoucanwritecode thatgetsexecutedwhen theeventoccurs. The class that contains code to respond to a certain event is called an eventlistener.
Inthischapteryouwilllearnhowtohandleeventsandwriteeventlisteners.
OverviewMostAndroidprogramsare interactive.Theusercaninteractwiththeapplicationeasilythanks to the event-driven programming paradigm the Android framework offers.Examplesofeventsthatmayoccurwhentheuserisinteractingwithanactivityareclick,long-click,touch,key,andsoon.
Tomake a program respond to a certain event, you need towrite a listener for thatevent. The way to do it is by implementing an interface that is nested in theandroid.view.Viewclass.Table5.1showssomeofthelistenerinterfacesinViewandthecorrespondingmethodineachinterfacethatwillgetcalledwhenthecorrespondingeventoccurs.
Interface
Method
OnClickListener
onClick()
OnLongClickListner
OnLongClick()
OnFocusChangeListener
OnFocusChange()
OnKeyListener
OnKey()
OnTouchListener
OnTouch()
Table5.1:ListenerinterfacesinView
Once you create an implementation of a listener interface, you can pass it to theappropriatesetOnXXXListenermethodof theviewyouwant to listento,whereXXX is
theeventname.Forexample,tocreateaclicklistenerforabutton,youwouldwritethisinyouractivityclass.
privateOnClickListenerclickListener=newOnClickListener(){
publicvoidonClick(Viewview){
//codetoexecuteinresponsetotheclickevent
}
};
protectedvoidonCreate(BundlesavedValues){
…
Buttonbutton=(Button)findViewById(…);
button.setOnClickListener(clickListener);
…
}
Alternatively, you can make your activity class implement the listener interface andprovideanimplementationoftheneededmethodaspartoftheactivityclass.
publicclassMyActivityextendsActivity
implementsView.OnClickListener{
protectedvoidonCreate(BundlesavedValues){
…
Buttonbutton=(Button)findViewById(…);
button.setOnClickListener(this);
…
}
//implementationofView.OnClickListener
@Override
publicvoidonClick(Viewview){
//codetoexecuteinresponsetotheclickevent
}
…
}
In addition, there is a shortcut for handling the click event. You can use the onClickattributeinthedeclarationofthetargetviewinthelayoutfileandwriteapublicmethodintheactivityclass.ThepublicmethodmusthavenoreturnvalueandtakeaViewargument.Forexample,ifyouhavethismethodinyouractivityclass
publicvoidshowNote(Viewview){
//dosomething
}
youcanusethisonClickattributeinaviewtoattachthemethodtotheclickeventofthatview.
<Buttonandroid:onClick=“showNote”…/>
In the background, Android will create an implementation of the OnClickListener
interfaceandattachittotheview.
Inthesampleapplicationsthatfollowyouwilllearnhowtowriteeventlisteners.
NoteAlistenerrunson themain thread.Thismeansyoushoulduseadifferent thread ifyour listener takes a long time (say, more than 200ms) to run. Or else, yourapplication will look unresponsive during the execution of the listener code. Youhavetwochoicesforsolvingthis.YoucaneitheruseahandleroranAsyncTask.ThehandleriscoveredinChapter2,“HandlingtheHandler”andAsyncTaskinChapter23, “AsynchronousTasks.”For long-running tasks, you should also considerusingtheJavaConcurrencyUtilities.
UsingtheonClickAttributeAsanexampleofusingtheonClickattributetohandletheclickeventofaview,considertheMulticolorClockprojectthataccompaniesthisbook.Itisasimpleapplicationwithasingle activity that shows an analog clock that can be clicked to change its color.AnalogClock is one of the widgets available on Android, so writing the view for theapplicationisabreeze.Themainobjectiveofthisprojectistodemonstratehowtowritealistenerbyusingacallbackmethodinthelayoutfile.
The manifest for MulticolorClock is given in Listing 5.1. There is nothing out ofordinaryhereandyoushouldnotfinditdifficulttounderstand.
Listing5.1:ThemanifestforMulticolorClock
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.multicolorclock”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“8”
android:targetSdkVersion=“17”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.multicolorclock.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
Now comes the crucial part, the layout file. It is calledactivity_main.xml and locatedundertheres/layoutdirectory.ThelayoutfileispresentedinListing5.2.
Listing5.2:ThelayoutfileinMulticolorClock
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingBottom=”@dimen/activity_vertical_margin”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
tools:context=”.MainActivity”>
<AnalogClock
android:id=”@+id/analogClock1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentTop=“true”
android:layout_centerHorizontal=“true”
android:layout_marginTop=“90dp”
android:onClick=“changeColor”
/>
</RelativeLayout>
ThelayoutfiledefinesaRelativeLayoutcontaininganAnalogClock.TheimportantpartistheonClickattributeintheAnalogClockdeclaration.
android:onClick=“changeColor”
ThismeansthatwhentheuserpressestheAnalogClockwidget,thechangeColormethodin theactivity classwillbecalled.For a callbackmethod likechangeColor towork, itmusthavenoreturnvalueandacceptanargumentoftypeView.Thesystemwillcallthismethodandpassthewidgetthatwaspressed.
ThechangeColormethodispartoftheMainActivityclassshowninListing5.3.
Listing5.3:TheMainActivityclassinMulticolorClock
packagecom.example.multicolorclock;
importandroid.app.Activity;
importandroid.graphics.Color;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.View;
importandroid.widget.AnalogClock;
publicclassMainActivityextendsActivity{
intcounter=0;
int[]colors={Color.BLACK,Color.BLUE,Color.CYAN,
Color.DKGRAY,Color.GRAY,Color.GREEN,Color.LTGRAY,
Color.MAGENTA,Color.RED,Color.WHITE,Color.YELLOW};
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
//Inflatethemenu;thisaddsitemstotheactionbarifit
//ispresent.
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
publicvoidchangeColor(Viewview){
if(counter==colors.length){
counter=0;
}
view.setBackgroundColor(colors[counter++]);
}
}
Pay special attention to thechangeColormethod in theMainActivity class.When theuserpresses(ortouches)theanalogclock,thismethodwillbecalledandreceivetheclockobject.Tochangetheclock’scolor,callitssetBackgroundColormethod,passingacolorobject.InAndroid,colorsarerepresentedbytheandroid.graphics.Colorclass.Theclasshas pre-defined colors that make creating color objects easy. These pre-defined colorsincludeColor.BLACK,Color.Magenta,Color.GREEN, and others. TheMainActivityclass defines an array of ints that contains some of the pre-defined colors inandroid.graphics.Color.
int[]colors={Color.BLACK,Color.BLUE,Color.CYAN,
Color.DKGRAY,Color.GRAY,Color.GREEN,Color.LTGRAY,
Color.MAGENTA,Color.RED,Color.WHITE,Color.YELLOW};
There is also a counter that points to the current index position of colors. ThechangeColormethodinquiriesthevalueofcounterandchangesittozeroifthevalueisequal to the array length. It then passes the pointed color to the setBackgroundColormethodoftheAnalogClock.
view.setBackgroundColor(colors[counter++]);
TheapplicationisshowninFigure5.1.
Figure5.1:TheMulticolorClockapplication
Touchtheclocktochangeitscolor!
ImplementingAListenerAs a second example, the GestureDemo application shows you how to implement theView.OnTouchListener interface to handle the touch event. To make it simple, theapplicationonly has one activity that contains a grid of cells that canbe swapped.TheapplicationisshowninFigure5.2.
Figure5.2:TheGestureDemoapplication
Eachof the images is an instance of theCellView class given inListing 5.4. It simplyextendsImageViewandaddsxandyfieldstostorethepositioninthegrid.
Listing5.4:TheCellViewclass
packagecom.example.gesturedemo;
importandroid.content.Context;
importandroid.widget.ImageView;
publicclassCellViewextendsImageView{
intx;
inty;
publicCellView(Contextcontext,intx,inty){
super(context);
this.x=x;
this.y=y;
}
}
There is no layout class for the activity as the layout is built programmatically.This isshownintheonCreatemethodoftheMainActivityclassinListing5.5.
Listing5.5:TheMainActivityclass
packagecom.example.gesturedemo;
importandroid.app.Activity;
importandroid.graphics.drawable.Drawable;
importandroid.os.Bundle;
importandroid.view.Gravity;
importandroid.view.Menu;
importandroid.view.MotionEvent;
importandroid.view.View;
importandroid.view.View.OnTouchListener;
importandroid.view.ViewGroup;
importandroid.widget.ImageView;
importandroid.widget.LinearLayout;
publicclassMainActivityextendsActivity{
introwCount=7;
intcellCount=7;
ImageViewimageView1;
ImageViewimageView2;
CellView[][]cellViews;
intdownX;
intdownY;
booleanswapping=false;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
LinearLayoutroot=newLinearLayout(this);
LinearLayout.LayoutParamsmatchParent=
newLinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT);
root.setOrientation(LinearLayout.VERTICAL);
root.setGravity(Gravity.CENTER_VERTICAL);
addContentView(root,matchParent);
//createrow
cellViews=newCellView[rowCount][cellCount];
LinearLayout.LayoutParamsrowLayoutParams=
newLinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
ViewGroup.LayoutParamscellLayoutParams=
newViewGroup.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT);
intcount=0;
for(inti=0;i<rowCount;i++){
CellView[]cellRow=newCellView[cellCount];
cellViews[i]=cellRow;
LinearLayoutrow=newLinearLayout(this);
row.setLayoutParams(rowLayoutParams);
row.setOrientation(LinearLayout.HORIZONTAL);
row.setGravity(Gravity.CENTER_HORIZONTAL);
root.addView(row);
//createcells
for(intj=0;j<cellCount;j++){
CellViewcellView=newCellView(this,j,i);
cellRow[j]=cellView;
if(count==0){
cellView.setImageDrawable(
getResources().getDrawable(
R.drawable.image1));
}elseif(count==1){
cellView.setImageDrawable(
getResources().getDrawable(
R.drawable.image2));
}else{
cellView.setImageDrawable(
getResources().getDrawable(
R.drawable.image3));
}
count++;
if(count==3){
count=0;
}
cellView.setLayoutParams(cellLayoutParams);
cellView.setOnTouchListener(touchListener);
row.addView(cellView);
}
}
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
privatevoidswapImages(CellViewv1,CellViewv2){
Drawabledrawable1=v1.getDrawable();
Drawabledrawable2=v2.getDrawable();
v1.setImageDrawable(drawable2);
v2.setImageDrawable(drawable1);
}
OnTouchListenertouchListener=newOnTouchListener(){
@Override
publicbooleanonTouch(Viewv,MotionEventevent){
CellViewcellView=(CellView)v;
intaction=event.getAction();
switch(action){
case(MotionEvent.ACTION_DOWN):
downX=cellView.x;
downY=cellView.y;
returntrue;
case(MotionEvent.ACTION_MOVE):
if(swapping){
returntrue;
}
floatx=event.getX();
floaty=event.getY();
intw=cellView.getWidth();
inth=cellView.getHeight();
if(downX<cellCount-1
&&x>w&&y>=0&&y<=h){
//swapwithrightcell
swapping=true;
swapImages(cellView,
cellViews[downY][downX+1]);
}elseif(downX>0&&x<0
&&y>=0&&y<=h){
//swapwithleftcell
swapping=true;
swapImages(cellView,
cellViews[downY][downX-1]);
}elseif(downY<rowCount-1
&&y>h&&x>=0&&x<=w){
//swapwithcellbelow
swapping=true;
swapImages(cellView,
cellViews[downY+1][downX]);
}elseif(downY>0&&y<0
&&x>=0&&x<=w){
//swapwithcellabove
swapping=true;
swapImages(cellView,
cellViews[downY-1][downX]);
}
returntrue;
case(MotionEvent.ACTION_UP):
swapping=false;
returntrue;
default:
returntrue;
}
}
};
}
TheMainActivity class contains aView.OnTouchListener called touchListener thatwillbeattachedtoeverysingleCellViewinthegrid.TheOnTouchListenerinterfacehasanonTouchmethodthatmustbeimplemented.HereisthesignatureofonTouch.
publicbooleanonTouch(Viewview,MotionEventevent)
Themethodshouldreturntrueifithasconsumedtheevent,whichmeansthattheeventshouldnotpropagatetootherviews.Otherwise,itshouldreturnfalse.
AsingletouchactionbytheusercausestheonTouchmethodtobecalledseveraltimes.Whentheusertouchestheview,themethodiscalled.Whentheusermoveshis/herfinger,onTouch is called. Likewise, onTouch is calledwhen the user lifts his/her finger. ThesecondargumenttoonTouch,aMotionEvent,containstheinformationabouttheevent.You can inquire what type of action is triggering the event by calling the getActionmethodontheMotionEvent.
intaction=event.getAction();
ThereturnvaluewillbeoneofthestaticfinalintsdefinedintheMotionEventclass.Forthis application we are interested in MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE, and MotionEvent.ACTION_UP. When the usertouchestheview,thegetActionmethodreturnsaMotionEvent.ACTION_DOWN.Thecodesimplystoresthelocationoftheeventtothexandyfieldsandreturnstrue.
case(MotionEvent.ACTION_DOWN):
downX=cellView.x;
downY=cellView.y;
returntrue;
If the user moves his/her finger to a neighboring cell, the touch action will return aMotionEvent.ACTION_MOVEandyouneedtoswaptheimagesoftheoriginalcellandthe destination cell and set the swapping field to true. This would prevent anotherswappingbeforethefingerislifted.
Finally,when the user lifts his/her finger, the swapping field is set to false to enableanotherswapping.
ThelayoutfortheactivityisbuiltdynamicallyintheonCreatemethodoftheactivityclass.EachCellViewispassedtheOnTouchListenersothatthelistenerwillhandletheCellView’stouchevent.
cellView.setOnTouchListener(touchListener);
SummaryInthischapteryoulearnedthebasicsofAndroideventhandlingandhowtowritelistenersby implementing anested interface in theView class.Youhavealso learned touse theshortcutforhandlingtheclickevent.
Chapter6TheActionBar
Theactionbarisarectangularwindowareathatcontainstheapplicationicon,applicationname,menusandothernavigationbuttons.Theactionbarnormallyappearsatthetopofthewindow.
This chapter explains how to decorate the action bar on Android with API level 11(Android3.0)orhigher.
OverviewTheactionbarisrepresentedbytheandroid.app.ActionBarclass.Itshouldlookfamiliarto anyAndroid user. Figure 6.1 shows the action bar of theMessaging application andFigure6.2showsthatofCalendar.
Figure6.1:TheactionbarofMessaging
Figure6.2:TheactionbarofCalendar
Theapplicationiconandnameontheleftoftheactionbararetherebydefault.Theyareboth optional and no programming is needed to display them. The systemwill use thevalues of the application element’s android:icon and android:label attributes in themanifest.Otheritemtypes,suchasnavigationtabsoranoptionsmenu,havetobeaddedusingcode.
Therightmosticonontheactionbar(theonewiththreelittledots)iscalledthe(action)overflowbutton.Whenpressed,theoverflowbuttondisplaysactionitemsthatmaydoanaction if selected. Important action items can be configured to display directly on theactionbar insteadofhidden in theoverflow.Anaction itemshownon theactionbar iscalledanactionbutton.Anactionbuttoncanhaveanicon,alabel,orboth.Forexample,theactionbar inFigure6.1contains twoactionbuttons,NewMessageandSearch.TheNewMessageactionbuttonhasbothaniconandalabel.TheSearchactionbuttononlyhasanicon.TheactionbarinFigure6.2alsocontainstwoactionbuttons.
InAndroid3.0orhigher,theactionbarisshownautomatically.YoucanhidetheactionbarifyouwishbyaddingthiscodeintheonCreatemethodofyouractivity.
getActionBar().hide();
Toshowahiddenactionbar,calltheshowmethod:
getActionBar().hide();
Thenextsectionsshowhowtoaddactionitemsanddrop-downnavigation.
NoteYoucandownloadAndroid’siconpackthatcontainsiconsforyouractionbarfromthissite.
http://developer.android.com/downloads/design/
Android_Design_Icons_20131106.zip
AddingActionItemsToaddactionitemstotheactionoverflow,followthesetwosteps.
1.Createamenu inanxml fileandsave itunder theres/menudirectory.AndroidStudiowilladdafieldtoyourR.menuclasssothatyoucanloadthemenuinyourapplication.ThefieldnameisthesameastheXMLfileminustheextension.IftheXML file is calledmain_activity_menu.xml, for example, the fieldwill be calledmain_activity_menu.2. In your activity class, override the onCreateOptionsMenu method and callgetMenuInflater().inflate(),passingthemenutobeloadedandthemenupassedtothemethod,likethis.
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.main,menu);
returntrue;
}
Anactionitemthatdoesnothingisuseless.Foranactionitemtobeabletorespondtoaactionitembeingselected,youmustoverridetheonOptionsItemSelectedmethodinyouractivityclass.ThismethodiscalledeverytimeanitemmenuisselectedandthesystemwillpasstheMenuItemselected.Thesignatureofthemethodisasfollows.
publicbooleanonOptionsItemSelected(MenuItemitem);
You can find out which menu item was selected by calling the getItemId on theMenuItemargument.Normallyyouwoulduseaswitchstatementlikethis:
switch(item.getItemId()){
caseR.id.action_1:
//dosomething
returntrue;
caseR.id.action_2:
//dosomethingelse
returntrue;
…
Now that you know the theory, let’s add some item actions. The ActionBarDemoapplicationshowshowtodoit.Itaddsthreeactionitemstotheactionbar.
Asusual,let’sstartwiththemanifest,whichforthisexampleisshowninListing6.1.
Listing6.1:ThemanifestforActionBarDemo
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.actionbardemo”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“11”
android:targetSdkVersion=“18”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.actionbardemo.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
Itisgoodpracticetolistactionnamesinaresourcefile.Listing6.2showsthestrings.xmlfile that contains three strings for the action items,action_capture, action_profile andaction_about.
Listing6.2:Theres/values/strings.xml
<?xmlversion=“1.0”encoding=“utf-8”?>
<resources>
<stringname=“app_name”>ActionBarDemo</string>
<stringname=“action_capture”>Capture</string>
<stringname=“action_profile”>Profile</string>
<stringname=“action_about”>About</string>
<stringname=“hello_world”>Helloworld!</string>
</resources>
Next, create an XML file under res/menu. If you used Android Studio to create theAndroidapplication,onehasbeencreatedforyou.Youjustneedtoadditemelementstoit.Listing6.3showsthemenufortheactionitems.
Listing6.3:Theres/menu/menu_main.xml
<menuxmlns:android=“http://schemas.android.com/apk/res/android”>
<item
android:id=”@+id/action_capture”
android:orderInCategory=“100”
android:showAsAction=“ifRoom|withText”
android:icon=”@drawable/icon1”
android:title=”@string/action_capture”/>
<item
android:id=”@+id/action_profile”
android:orderInCategory=“200”
android:showAsAction=“ifRoom|withText”
android:icon=”@drawable/icon2”
android:title=”@string/action_profile”/>
<item
android:id=”@+id/action_about”
android:orderInCategory=“50”
android:showAsAction=“never”
android:title=”@string/action_about”/>
</menu>
Theitemelementmayhaveanyoftheseattributes.
android:id.Auniqueidentifiertorefertotheactionitemintheprogram.android:orderInCategory.Theordernumberforthisitem.Anitemwithasmallernumberwillbeshownbeforeitemswithlargernumbers.android:icon.Theiconforthisactionitemifitisshownasanactionbutton(directlyontheactionbar).android:title.Theactionlabel.android:showAsAction. The value can be one or a combination of these values:ifRoom,never,withText,always,andcollapseActionView.Populatingthisattributewithneverindicatesthatthisitemwillneverbeshownontheactionbardirectly.Onthe other hand, always forces the system to always display this item as an actionbutton.However,becautiouswhenusingthisvalueasifthereisnotenoughroomontheactionbar,whatwillbedisplayedwillbeunpredictable.Instead,useifRoom todisplayanitemasanactionbuttonifthereisroom.ThewithTextvaluewilldisplaythisitemwithalabelifthisitemisbeingdisplayedasanactionbutton.
Thecompletelistofattributesfortheitemelementcanbefoundhere.
http://developer.android.com/guide/topics/resources/menu-
resource.html
Finally,Listing6.4presentstheMainActivityclassfortheapplication.
Listing6.4:TheMainActivityclass
packagecom.example.actionbardemo;
importandroid.app.Activity;
importandroid.app.AlertDialog;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.MenuItem;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
//Handlepressesontheactionbaritems
switch(item.getItemId()){
caseR.id.action_profile:
showAlertDialog(“Profile”,“YouselectedProfile”);
returntrue;
caseR.id.action_capture:
showAlertDialog(“Settings”,
“YouselectedSettings”);
returntrue;
caseR.id.action_about:
showAlertDialog(“About”,“YouselectedAbout”);
returntrue;
default:
returnsuper.onOptionsItemSelected(item);
}
}
privatevoidshowAlertDialog(Stringtitle,Stringmessage){
AlertDialogalertDialog=new
AlertDialog.Builder(this).create();
alertDialog.setTitle(title);
alertDialog.setMessage(message);
alertDialog.show();
}
}
NoticedthattheactivityclassoverridestheonOptionsItemSelectedmethod?SelectinganitemwillinvoketheshowAlertDialogmethodthatshowsanAlertDialog.
Figure6.3showsthreeactionitemsinActionBarDemo.Twooftheitemsaredisplayedasactionbuttons.
Figure6.3:TheActionBarDemoapplication
AddingDropdownNavigationA dropdown list can be used as a navigation mode. The visual difference between adropdown list andanoptionsmenu is that adropdown list alwaysdisplays the selecteditemontheactionbarandhidetheotheroptions.Ontheotherhand,anoptionsmenumayhide all of the items or show all or some of them as action buttons. Figure 6.4 showsdropdownnavigationinCalendar.
Figure6.4:DropdownnavigationinCalendar
Toadddrop-downnavigationtotheactionbar,followthesethreesteps.
1.Declareastringarrayinyourstrings.xmlfileunderres/values.2. In your activity class, add an implementation ofActionBar.OnNavigationListenertorespondtoitemselection.3. Create a SpinnerAdapter in the onCreate method of your activity, passActionBar.NAVIGATION_MODE_LIST to theActionBar’s setNavigationModemethod, and pass the SpinnerAdapter and OnNavigationListener to theActionBar’ssetListNavigationCallbacksmethod.
SpinnerAdapterspinnerAdapter=
ArrayAdapter.createFromResource(this,
R.array.colors,
android.R.layout.simple_spinner_dropdown_item);
ActionBaractionBar=getActionBar();
actionBar.setNavigationMode(
ActionBar.NAVIGATION_MODE_LIST);
actionBar.setListNavigationCallbacks(spinnerAdapter,
onNavigationListener);
Asanexample,theDropDownNavigationDemoapplicationshowshowtoadddropdownnavigation to the actionbar.The application adds a list of five colors to the actionbar.Selectingacolorchangesthewindowbackgroundcolorwiththeselectedcolor.
Listing6.5showsthemanifestfortheapplication.
Listing6.5:TheDropDownNavigationDemomanifest
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.dropdownnavigationdemo”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“14”
android:targetSdkVersion=“18”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.dropdownnavigationdemo.MainActivity”
android:label=”@string/app_name”
android:theme=”@style/MyTheme”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
Listing6.6 shows a string-array element thatwill be used to populate the drop-down.Therearefiveitemsinthearray.
Listing6.6:Theres/values/strings.xmlfile
<?xmlversion=“1.0”encoding=“utf-8”?>
<resources>
<stringname=“app_name”>DropDownNavigationDemo</string>
<stringname=“action_settings”>Settings</string>
<stringname=“hello_world”>Helloworld!</string>
<string-arrayname=“colors”>
<item>White</item>
<item>Red</item>
<item>Green</item>
<item>Blue</item>
<item>Yellow</item>
</string-array>
</resources>
Listing6.7showstheMainActivityclassfortheapplication.
Listing6.7:TheMainActivityclass
packagecom.example.dropdownnavigationdemo;
importandroid.app.ActionBar;
importandroid.app.ActionBar.OnNavigationListener;
importandroid.app.Activity;
importandroid.graphics.Color;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.widget.ArrayAdapter;
importandroid.widget.SpinnerAdapter;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SpinnerAdapterspinnerAdapter=
ArrayAdapter.createFromResource(this,
R.array.colors,
android.R.layout.simple_spinner_dropdown_item);
ActionBaractionBar=getActionBar();
actionBar.setNavigationMode(
ActionBar.NAVIGATION_MODE_LIST);
actionBar.setListNavigationCallbacks(spinnerAdapter,
onNavigationListener);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
OnNavigationListeneronNavigationListener=new
OnNavigationListener(){
@Override
publicbooleanonNavigationItemSelected(
intposition,longitemId){
String[]colors=getResources().
getStringArray(R.array.colors);
StringselectedColor=colors[position];
getWindow().getDecorView().setBackgroundColor(
Color.parseColor(selectedColor));
returntrue;
}
};
}
Figure6.5showsthedropdownnavigation.
Figure6.5:Dropdownnavigationontheactionbar
Notethattheactionbarhasbeenstyledusingthestyles.xmlfileinListing6.8.
Listing6.8:Theres/values/styles.xmlfile
<resources>
<stylename=“AppBaseTheme”parent=“android:Theme.Light”>
</style>
<stylename=“AppTheme”parent=“AppBaseTheme”>
</style>
<stylename=“MyTheme”
parent=”@android:style/Widget.Holo.Light”>
<itemname=“android:actionBarStyle”>@style/MyActionBar</item>
</style>
<stylename=“MyActionBar”
parent=”@android:style/Widget.Holo.Light.ActionBar.Solid.Inverse”>
<item
name=“android:background”>@android:color/holo_blue_bright</item>
</style>
</resources>
FormoreinformationonstylingUIcomponents,seeChapter10,“StylesandThemes.”
GoingBackUpYoucansettheapplicationiconandactivitylabelintheactionbarofanactivitysothattheapplicationwillgoonelevelbackupwhenthe iconispressed.Figure6.6showsanactionbarwhosedisplayHomeAsUppropertyissettotrue(indicatedbytheleftarrowtothe left of the icon). Compare this with the action bar in Figure 6.7 where itsdisplayHomeAsUppropertyissettofalse.
Figure6.6:displayHomeAsUpsettotrue
Figure6.7:displayHomeAsUpsettofalse
ToenabledisplayHomeAsUp, youneed to set theparentActivityName element in theactivitydeclarationintheAndroidmanifest:
<activityandroid:name=“com.example.d1.ShowContactActivity”
android:parentActivityName=”.MainActivity”>
</activity>
You must also leave the displayHomeAsUpEnabled property of the action bar to itsdefaultvalue(true).Settingittofalse,asshowninthecodebelow,willdisableit.
getActionBar().setDisplayHomeAsUpEnabled(false);
SummaryTheactionbarprovidesaspacefortheapplicationicon,applicationnameandnavigationmodes. This chapter showed how to add action items and dropdown navigation to theactionbar.
Chapter7Menus
Menus are a common feature in many graphical user interface (GUI) systems. Theirprimaryroleistoprovideshortcutstocertainactions.
ThischapterlooksatAndroidmenuscloselyandprovidesthreesampleapplications.
OverviewPre-3.0Androiddevicesshippedwitha(hardware)buttonforshowingmenusintheactiveapplication. Starting from Android 3.0, the action bar is the recommended way ofachievingthesamething,ineffectmakingahardwareMenubuttonredundant.Withthehardwaremenubuttongone,“soft”menushavebecomeevenmoreimportantthanever.
TherearethreetypesofmenusinAndroid:
OptionsmenuContextmenuPopupmenu
Theoptionsmenuisthetypeofmenuyounormallyincorporateintheactionbar,asyouhave seen inChapter 6, “TheActionBar.” In this chapter youwill look at the optionsmenumore closely and learn about the other two. Thankfully, nomatter what kind ofmenuyou’reusing inyourapp,youuse the sameAPI.And,yes,youcanusedifferenttypesofmenusinthesameapplication.
Like many other things in Android, menus can be defined declaratively orprogrammatically. The first method offers more flexibility than the second because itallowsyoutochangemenuitemsusingatexteditor.Doingsoprogrammatically,ontheotherhand,wouldrequireyoutochangeyourprogramandrecompileeverytimeyouneedtoedityourmenu.
Herearethethreethingsyouneedtodowhenworkingwithoptionsandcontextmenus.
1.Createamenuinanxmlfileandsaveitundertheres/menudirectory.2. In your activity class, override either onCreateOptionsMenu oronCreateContextMenu, depending on the menu type. Then, in the overriddenmethod,callgetMenuInflater().inflate(),passingthemenutobeused.3. In your activity class, override either onOptionsItemSelected oronContextItemSelected,dependingonthemenutype.
Popupmenusareabitdifferent.Toworkwiththem,dothefollowing:
1.Createamenuinanxmlfileandsaveitundertheres/menudirectory.2. In your activity class, create a PopupMenu object and a
PopupMenu.OnMenuItemClickListener object. In the listener class you define amethodthathandlestheclickeventthatoccurswhenoneofthepopupmenuitemsisselected.
TheMenuFileTo create a menu declaratively, start by creating an XML file and place it under theres/menudirectory.TheXMLfilemusthavethefollowingstructure.
<menuxmlns:android=“http://schemas.android.com/apk/res/android”>
<group>…</group>
<group>…</group>
…
<item>…</item>
<item>…</item>
…
</menu>
Therootelementismenuanditcancontainanynumberofgroupanditemelements.Thegroupelementrepresentsamenugroupandtheitemelementrepresentsamenuitem.
Foreverymenufileyoucreate,AndroidStudiowilladdafieldtoyourR.menuclasssothatyoucanloadthemenuinyourapplication.ThefieldnameisthesameastheXMLfileminustheextension.IftheXMLfileiscalledmain_activity_menu.xml,forexample,thefieldinR.menuwillbecalledmain_activity_menu.
NoteTheRclasswasexplainedinChapter1,“GettingStarted.”
TheOptionsMenuTheOptionsMenuDemoapplicationisasimpleapplicationthatusesanoptionsmenuinitsactionbar.ItissimilartotheapplicationthatdemonstratestheactionbarinChapter6,“TheActionBar.”
Themanifest(AndroidManifest.xmlfile)forthisapplicationisshowninListing7.1.
Listing7.1:ThemanifestforOptionsMenuDemo
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.optionsmenudemo”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“18”
android:targetSdkVersion=“18”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.optionsmenudemo.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
Themanifestdeclaresanactivity,whoseclassiscalledMainActivity.
The menu for this application is defined in the res/menu/options_menu.xml file inListing7.2.Ithasthreemenuitems.
Listing7.2:Theoptions_menu.xmlFile
<menuxmlns:android=“http://schemas.android.com/apk/res/android”>
<item
android:id=”@+id/action_capture”
android:orderInCategory=“100”
android:showAsAction=“ifRoom|withText”
android:icon=”@drawable/icon1”
android:title=”@string/action_capture”/>
<item
android:id=”@+id/action_profile”
android:orderInCategory=“200”
android:showAsAction=“ifRoom|withText”
android:icon=”@drawable/icon2”
android:title=”@string/action_profile”/>
<item
android:id=”@+id/action_about”
android:orderInCategory=“50”
android:showAsAction=“never”
android:title=”@string/action_about”/>
</menu>
Asexplained inChapter 4, “Layouts,” theplus sign in an id attribute indicates that theidentifierisbeingaddedwiththedeclaration.
Thetitlesforthemenuitemsreferencethestringsdefinedintheres/values/strings.xmlfileinListing7.3.
Listing7.3:strings.xmlforOptionsMenuDemo
<?xmlversion=“1.0”encoding=“utf-8”?>
<resources>
<stringname=“app_name”>OptionsMenuDemo</string>
<stringname=“action_capture”>Capture</string>
<stringname=“action_profile”>Profile</string>
<stringname=“action_about”>About</string>
<stringname=“hello_world”>Helloworld!</string>
</resources>
Theactivityclassfortheapplication,theMainActivityclass,isshowninListing7.4.
Listing7.4:MainActivityforOptionsMenuDemo
packagecom.example.optionsmenudemo;
importandroid.app.Activity;
importandroid.app.AlertDialog;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.MenuItem;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.options_menu,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
//Handleclickonmenuitems
switch(item.getItemId()){
caseR.id.action_profile:
showAlertDialog(“Profile”,“YouselectedProfile”);
returntrue;
caseR.id.action_capture:
showAlertDialog(“Settings”,
“YouselectedSettings”);
returntrue;
caseR.id.action_about:
showAlertDialog(“About”,“YouselectedAbout”);
returntrue;
default:
returnsuper.onOptionsItemSelected(item);
}
}
privatevoidshowAlertDialog(Stringtitle,Stringmessage){
AlertDialogalertDialog=new
AlertDialog.Builder(this).create();
alertDialog.setTitle(title);
alertDialog.setMessage(message);
alertDialog.show();
}
}
To use the options menu you need to override the onCreateOptionsMenu andonOptionsItemSelected methods. The onCreateOptionsMenu method is called whenthe activity is built. You should call the menu inflater and inflate your menu here. Inaddition,theonOptionsItemSelectedmethodhandlesmenuitemselection.
Note that theoptionsmenu is integratedwith the activity so that youdonot need tocreateyourownlistenertohandleitemselection.
Ifyou run theapplication,youwill seeanactivity like theone inFigure7.1.Takealookat the actionbar and try selectingoneof themenu items.Every timeyou select amenuitem,anAlertDialogwillbeshowntonotifywhatyouhaveselected.
Figure7.1:OptionsMenuDemo
In Figure 7.1 the buttons on the action bar are rendered without text because theapplication is running inadevicewitha low-resolutionscreen. Ifyourun it inadevicewithahigherresolutionscreen,youmayseetexttotherightofeachbutton.
TheContextMenu
The ContextMenuDemo application shows how you can use a context menu in yourapplication. Themain activity of the application features an image button that you canlong-presstodisplayacontextmenu.
TheAndroidManifest.xmlfileforthisapplicationisprintedinListing7.5.
Listing7.5:AndroidMenifest.xmlforContextMenuDemo
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.contextmenudemo”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“18”
android:targetSdkVersion=“18”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.contextmenudemo.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
Thecontext_menu.xmlfileinListing7.6isamenufilethatdefinesmenuitemsforthecontextmenuusedintheapplication.
Listing7.6:context_menu.xmlforContextMenuDemo
<menuxmlns:android=“http://schemas.android.com/apk/res/android”>
<item
android:id=”@+id/action_rotate”
android:title=”@string/action_rotate”/>
<item
android:id=”@+id/action_resize”
android:title=”@string/action_resize”/>
</menu>
The menu file defines two menu items, whose titles get their values from theres/values/strings.xmlfileinListing7.7.
Listing7.7:strings.xmlforContextMenuDemo
<?xmlversion=“1.0”encoding=“utf-8”?>
<resources>
<stringname=“app_name”>ContextMenuDemo</string>
<stringname=“action_settings”>Settings</string>
<stringname=“action_rotate”>Rotate</string>
<stringname=“action_resize”>Resize</string>
<stringname=“hello_world”>Helloworld!</string>
</resources>
Finally, Listing 7.8 shows theMainActivity class for the application. There are twomethods that you need to override to use a contextmenu,onCreateContextMenuandonContextItemSelected.TheonCreateContextMenumethodiscalledwhentheactivityisbuilt.Youshouldinflateyourmenuhere.
TheonContextItemSelectedmethod is called every timeamenu item in the contextmenuisselected.
Listing7.8:MainActivityforContextMenuDemo
packagecom.example.contextmenudemo;
importandroid.app.Activity;
importandroid.app.AlertDialog;
importandroid.os.Bundle;
importandroid.view.ContextMenu;
importandroid.view.ContextMenu.ContextMenuInfo;
importandroid.view.MenuInflater;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.widget.ImageButton;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ImageButtonimageButton=(ImageButton)
findViewById(R.id.button1);
registerForContextMenu(imageButton);
}
@Override
publicvoidonCreateContextMenu(ContextMenumenu,Viewv,
ContextMenuInfomenuInfo){
super.onCreateContextMenu(menu,v,menuInfo);
MenuInflaterinflater=getMenuInflater();
inflater.inflate(R.menu.context_menu,menu);
}
@Override
publicbooleanonContextItemSelected(MenuItemitem){
switch(item.getItemId()){
caseR.id.action_rotate:
showAlertDialog(“Rotate”,“YouselectedRotate”);
returntrue;
caseR.id.action_resize:
showAlertDialog(“Resize”,“YouselectedResize”);
returntrue;
default:
returnsuper.onContextItemSelected(item);
}
}
privatevoidshowAlertDialog(Stringtitle,Stringmessage){
AlertDialogalertDialog=new
AlertDialog.Builder(this).create();
alertDialog.setTitle(title);
alertDialog.setMessage(message);
alertDialog.show();
}
}
Figure7.2showstheapplication.Ifyoupress(orclick)theimagebuttonlongenough,itwill show the contextmenu.Note that the image comes from theAndroid systemas isdefinedinthelayoutfile.
Figure7.2:Acontextmenu
ThePopupMenuApopupmenuisassociatedwithaviewandisshowneverytimeaneventoccurstotheview. The PopupMenuDemo application shows how to use a popup menu. It uses abutton that displays a popup menu when it is clicked. Listing 7.9 shows theAndroidManifest.xmlfileforPopupMenuDemo.
Listing7.9:AndroidMenifest.xmlforPopupMenuDemo
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.popupmenudemo”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“18”
android:targetSdkVersion=“18”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.popupmenudemo.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
ThemanifestinListing7.9isastandardXMLfilethatyou’veseenmanytimes.IthasoneactivitywithabuttonthatwillactivatethemenushowninListing7.10.
Listing7.10:popup_menu.xmlforPopupMenuDemo
<menuxmlns:android=“http://schemas.android.com/apk/res/android”>
<item
android:id=”@+id/action_delete”
android:title=”@string/action_delete”/>
<item
android:id=”@+id/action_copy”
android:title=”@string/action_copy”/>
</menu>
ThemenuinListing7.10hastwomenuitems.Thetitlesfortheitemsrefertothestringsdefinedintheres/values/strings.xmlfileinListing7.11.
Listing7.11:strings.xmlforPopupMenuDemo
<?xmlversion=“1.0”encoding=“utf-8”?>
<resources>
<stringname=“app_name”>PopupMenuDemo</string>
<stringname=“action_settings”>Settings</string>
<stringname=“action_delete”>Delete</string>
<stringname=“action_copy”>Copy</string>
<stringname=“show_menu”>ShowPopup</string>
</resources>
Finally,Listing7.12showstheMainActivityclassfortheapplication.
Listing7.12:MainActivityforPopupMenuDemo
packagecom.example.popupmenudemo;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.util.Log;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.widget.Button;
importandroid.widget.PopupMenu;
publicclassMainActivityextendsActivity{
PopupMenupopupMenu;
PopupMenu.OnMenuItemClickListenermenuItemClickListener;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
menuItemClickListener=
newPopupMenu.OnMenuItemClickListener(){
@Override
publicbooleanonMenuItemClick(MenuItemitem){
switch(item.getItemId()){
caseR.id.action_delete:
Log.d(“menu”,“Deleteclicked”);
returntrue;
caseR.id.action_copy:
Log.d(“menu”,“Copyclicked”);
returntrue;
default:
returnfalse;
}
}
};
Buttonbutton=(Button)findViewById(R.id.button1);
popupMenu=newPopupMenu(this,button);
popupMenu.setOnMenuItemClickListener(menuItemClickListener);
popupMenu.inflate(R.menu.popup_menu);
}
publicvoidshowPopupMenu(Viewview){
popupMenu.show();
}
}
Unlike the optionsmenu and contextmenu, the popupmenu requires that you create amenuobjectandalistenerobjectforhandlingitemselection.
In the onCreate method ofMainActivity, you create a PopupMenu object and aPopupMenu.OnMenuItemClickListener object. You then pass the listener to thePopupMenu.Thelistenerclasshandlesmenuitemclicks.
TheshowPopupMenumethodinMainActivityisassociatedwiththebuttonusingtheonClick attribute of the button in themain activity layout file. Themethod shows thepopupmenu.
Figure7.3showsthepopupmenuthatdisplayswhenthebuttonisclicked.
Figure7.3:Apopupmenu
Summary
Inthischapteryoulearnedhowtousemenustoprovideshortcutstocertainactions.TherearethreetypesofmenusinAndroid,optionsmenus,contextmenus,andpopupmenus.
Chapter8ListView
AListViewisaviewforshowingascrollablelistofitems,whichmaycomefromalistadapteroranarrayadapter.Selectingan iteminaListView triggersanevent forwhichyoucanwritealistener.
Ifanactivitycontainsonlyoneview that isaListView,youcanextendListActivityinsteadofActivity as your activity class.UsingListActivity is convenient as it comeswithanumberofusefulfeatures.
ThischaptershowshowyoucanusetheListViewandListActivityaswellascreateacustomListAdapterandstyleaListViewinthreesampleapplications.
OverviewTechnically, android.widget.ListView, the template for creating a ListView, is adescendantoftheViewclass.Youcanuseitthesamewayyouwouldotherviews.WhatmakesListViewabittrickytouseisthefactthatyouhavetoobtainadatasourceforitintheformofaListAdapter.TheListAdapteralsosupplythelayoutforeachitemontheListView,sotheListAdapterreallyplaysaveryimportantroleinthelifeofaListView.
The android.widget.ListAdapter interface is a subinterface ofandroid.widget.Adapter.ThecloserelativesofthisinterfaceareshowninFigure8.1.
CreatingaListAdapterisexplainedinthenextsection,“CreatingaListAdapter.”OnceyouhaveaListAdapter,youcanpassittoaListView’ssetAdaptermethod:
listView.setAdapter(listAdapter);
You can alsowrite a listener that implementsAdapterView.OnItemClickListener andpass it to theListView’ssetOnItemClickListenermethod.The listenerwillbenotifiedeverytimealistitemisselectedandyoucanwritecodetohandleit,likeso.
listView.setOnItemClickListener(new
AdapterView.OnItemClickListener(){
@Override
publicvoidonItemClick(AdapterView<?>parent,finalViewview,
intposition,longid){
//handleitem
});
Figure8.1:TheparentsandimplementationsofListAdapter
CreatingAListAdapterAsmentionedintheprevioussection,thetrickiestpartofusingaListViewiscreatingadatasourceforit.YouneedaListAdapterandasyoucanseeinFigure8.1youhaveatleasttwoimplementationsofListAdapterthatyoucanuse.
Oneof theconcrete implementationsofListAdapter is theArrayAdapter class.AnArrayAdapter is backed by an array of objects. The string returned by the toStringmethodofeachobjectisusedtopopulateeachitemintheListView.
TheArrayAdapterclassoffersseveralconstructors.AllofthemrequirethatyoupassaContextandaresourceidentifierthatpointstoalayoutthatcontainsaTextView.ThisisbecauseeachiteminaListViewisaTextView.ThesearesomeoftheconstructorsintheArrayAdapterclass.
publicArrayAdapter(android.content.Contextcontext,intresourceId)
publicArrayAdapter(android.content.Contextcontext,intresourceId,
T[]objects)
Ifyoudonotpassanobjectarraytoaconstructor,youwillhavetopassonelater.Asfortheresourceidentifier,Androidprovidessomepre-definedlayoutsforaListAdapter.Theidentifierstotheselayoutscanbefoundintheandroid.R.layoutclass.Forexample,youcancreateanArrayAdapterusingthiscodesnippetinyouractivity.
ArrayAdapter<String>adapter=newArrayAdapter<String>(this,
android.R.layout.simple_list_item_1,objects);
Using android.R.layout.simple_list_item_1 will create a ListView with the simplestlayout where the text for each item is printed in black. Alternatively, you can useandroid.R.layout.simple_expandable_list_item_1. However, you probably want tocreateyourownlayoutandpassit totheconstructor, instead.ThiswayyouwouldhavemorecontroloverthelookandfeelofyourListView.
MostofthetimeyoucanuseastringarrayasthedatasourceforyourListView.Youcancreateastringarrayprogrammaticallyordeclaratively.Doingitprogrammaticallyissimpleandyoudonothavetodealwithanexternalresource:
String[]objects={“item1”,“item2”,“item-n”};
The disadvantage of this approach is that updating the array would require you torecompileyourclass.Creatinga stringarraydeclaratively,on theotherhand,givesyoumoreflexibilityasyoucaneasilyedittheelements.
Tocreateastringarraydeclaratively,startbycreatingastring-arrayelementinyourstrings.xml file under res/values. For example, the following is a string-array namedplayers.
<string-arrayname=“players”>
<item>Player1</item>
<item>Player2</item>
<item>Player3</item>
<item>Player4</item>
</string-array>
Whenyousave thestrings.xml file,AndroidStudiowillupdateyourRgeneratedclassandaddastaticfinalclassnamedarray,ifnoneexists,aswellasaddaresourceidentifierforthestring-arrayelementtothearrayclass.Asaresult,younowhavethisresourceidentifiertoaccessyourstringarrayfromyourcode:
R.array.players
Toconverttheuser-definedstringarraytoaJavastringarray,usethiscode.
String[]values=getResources().getStringArray(R.array.players);
YoucanthenusethisstringarraytocreateanArrayAdapter.
UsingAListViewThe ListViewDemo1 application shows how to use a ListView that is backed by anArrayAdapter. The array that supplies values to theArrayAdapter is a string arraydefinedinthestrings.xmlfile.Listing8.1showsthestrings.xmlfile.
Listing8.1:Theres/values/strings.xmlfileforListViewDemo1
<?xmlversion=“1.0”encoding=“utf-8”?>
<resources>
<stringname=“app_name”>ListViewDemo1</string>
<stringname=“action_settings”>Settings</string>
<string-arrayname=“players”>
<item>Player1</item>
<item>Player2</item>
<item>Player3</item>
<item>Player4</item>
<item>Player5</item>
</string-array>
</resources>
ThelayoutfortheArrayAdapterisdefinedinthelist_item.xmlfilepresentedinListing8.2.It is locatedunderres/layoutandcontainsaTextViewelement.This layoutwillbeusedasthelayoutforeachitemintheListView.
Listing8.2:Thelist_item.xmlfile
<?xmlversion=“1.0”encoding=“utf-8”?>
<TextViewxmlns:android=“http://schemas.android.com/apk/res/android”
android:id=”@+id/list_item”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”
android:padding=“7dip”
android:textSize=“16sp”
android:textColor=”@android:color/holo_green_dark”
android:textStyle=“bold”>
</TextView>
The application consists of only one activity, MainActivity. The layout file(activity_main.xml)fortheactivityisgiveninListing8.3andtheMainActivityclassinListing8.4.
Listing8.3:Theactivity_main.xmlfileforListViewDemo1
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”>
<ListView
android:id=”@+id/listView1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”/>
</LinearLayout>
Listing8.4:TheMainActivityclassforListViewDemo1
packagecom.example.listviewdemo1;
importandroid.app.Activity;
importandroid.app.AlertDialog;
importandroid.os.Bundle;
importandroid.util.Log;
importandroid.view.Menu;
importandroid.view.View;
importandroid.widget.AdapterView;
importandroid.widget.ArrayAdapter;
importandroid.widget.ListView;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String[]values=getResources().getStringArray(
R.array.players);
ArrayAdapter<String>adapter=newArrayAdapter<String>(
this,R.layout.list_item,values);
ListViewlistView=(ListView)findViewById(R.id.listView1);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new
AdapterView.OnItemClickListener(){
@Override
publicvoidonItemClick(AdapterView<?>parent,
finalViewview,intposition,longid){
Stringitem=(String)
parent.getItemAtPosition(position);
AlertDialog.Builderbuilder=new
AlertDialog.Builder(MainActivity.this);
builder.setMessage(“Selecteditem:”
+item).setTitle(“ListView”);
builder.create().show();
Log.d(“ListView”,“Selecteditem:”+item);
}
});
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
}
Figure8.2showstheapplication.
Figure8.2:AsimpleListView
Extending ListActivity and Writing A CustomAdapterIf your activitywill only have one component that is aListView, you should considerextendingtheListActivityclassinsteadofActivity.WithListActivity,youdonotneedalayoutfileforyouractivity.ListActivityalreadycontainsaListViewandyoudonotneedto attach a listener to it. On top of that, the ListActivity class already defines asetListAdaptermethod,soyoujustneedtocallitinyouronCreatemethod.Inaddition,insteadofcreatinganAdapterView.OnItemClickListener,youjustneedtooverridetheListActivity’s onListItemClick method, which will be called when an item on theListViewgetsselected.
TheListViewDemo2applicationshowshowtouseListActivity.TheapplicationalsodemonstrateshowtocreateacustomListAdapterbyextendingtheArrayAdapterclass
andcreatingalayoutfileforthecustomListAdapter.
ThelayoutfileforthecustomListAdapter inListViewDemo2ispresentedinListing8.5.Itisnamedpretty_adapter.xmlfileandislocatedunderres/layout.
Listing8.5:Thepretty_adapter.xmlfile
<?xmlversion=“1.0”encoding=“utf-8”?>
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<ImageView
android:id=”@+id/icon”
android:layout_width=“36dp”
android:layout_height=“fill_parent”/>
<TextView
android:id=”@+id/label”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”
android:gravity=“center_vertical”
android:padding=“12dp”
android:textSize=“18sp”
android:textColor=”@android:color/holo_blue_bright”/>
</LinearLayout>
Listing8.6showsthecustomadapterclass,calledPrettyAdapter.
Listing8.6:ThePrettyAdapterclass
packagecom.example.listviewdemo2;
importandroid.content.Context;
importandroid.graphics.drawable.Drawable;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.ArrayAdapter;
importandroid.widget.ImageView;
importandroid.widget.TextView;
publicclassPrettyAdapterextendsArrayAdapter<String>{
privateLayoutInflaterinflater;
privateString[]items;
privateDrawableicon;
privateintviewResourceId;
publicPrettyAdapter(Contextcontext,
intviewResourceId,String[]items,Drawableicon){
super(context,viewResourceId,items);
inflater=(LayoutInflater)context
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
this.items=items;
this.icon=icon;
this.viewResourceId=viewResourceId;
}
@Override
publicintgetCount(){
returnitems.length;
}
@Override
publicStringgetItem(intposition){
returnitems[position];
}
@Override
publiclonggetItemId(intposition){
return0;
}
@Override
publicViewgetView(intposition,ViewconvertView,
ViewGroupparent){
convertView=inflater.inflate(viewResourceId,null);
ImageViewimageView=(ImageView)
convertView.findViewById(R.id.icon);
imageView.setImageDrawable(icon);
TextViewtextView=(TextView)
convertView.findViewById(R.id.label);
textView.setText(items[position]);
returnconvertView;
}
}
Thecustomadaptermustoverrideseveralmethods,notably thegetViewmethod,whichmustreturnaViewthatwillbeusedforeachitemontheListView.Inthisexample,theviewcontainsanImageViewandaTextView.ThetextfortheTextViewistakenfromthearraypassedtothePrettyAdapterinstance.
The last piece of the application is theMainActivity class in Listing 8.7. It extendsListActivityandistheonlyactivityintheapplication.
Listing8.7:TheMainActivityclassforListViewDemo2
packagecom.example.listviewdemo2;
importandroid.app.ListActivity;
importandroid.content.Context;
importandroid.content.res.Resources;
importandroid.graphics.drawable.Drawable;
importandroid.os.Bundle;
importandroid.util.Log;
importandroid.view.View;
importandroid.widget.ListView;
publicclassMainActivityextendsListActivity{
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
//Sincewe’reextendingListActivity,wedo
//notneedtocallsetContentView();
Contextcontext=getApplicationContext();
Resourcesresources=context.getResources();
String[]items=resources.getStringArray(
R.array.players);
Drawabledrawable=resources.getDrawable(
R.drawable.pretty);
setListAdapter(newPrettyAdapter(context,
R.layout.pretty_adapter,items,drawable));
}
@Override
publicvoidonListItemClick(ListViewlistView,
Viewview,intposition,longid){
Log.d(“listView2”,“listView:”+listView+
”,view:”+view.getClass()+
”,position:”+position);
}
}
Ifyouruntheapplication,youwillseeanactivitylikethatinFigure8.3.
Figure8.3:UsingcustomadapterinListActivity
StylingtheSelectedItemIt is often desirable that the user be able to see clearly the currently selected item in aListView.Tomake the selected item lookdifferently than the rest of the items, set theListView’schoicemodetoCHOICE_MODE_SINGLE,likeso.
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
Then,when constructing the underlyingListAdapter, use a layoutwith an appropriatestyle.Theeasiest is topass thesimple_list_item_activated_1 field.Forexample,whenusedinaListView, the followingArrayAdapterwillcause theselected itemtohaveabluebackground.
ArrayAdapter<String>adapter=newArrayAdapter<String>(
context,android.R.layout.simple_list_item_activated_1,
array);
If thedefault styledoesnotappeal toyou,youcancreateyourownstylebycreatingaselector. A selector is a drawable that can be used as a background drawable in aTextView.Here isanexampleofaselector file thatmustbesaved in theres/drawabledirectory.
<selector
xmlns:android=“http://schemas.android.com/apk/res/android”>
<itemandroid:state_activated=“true”
android:drawable=”@drawable/activated”/>
</selector>
Theselectormusthaveanitemwhosestate_activatedattributeissettotrueandwhosedrawableattributereferstoanotherdrawable.
TheListViewDemo3applicationcontainsanactivitythatemploystwoListViews thatareplacedsidebyside.ThefirstListViewontheleftisgiventhedefaultstylewhereasthesecondListViewisdecoratedusingacustomstyle.Figure8.4showstheapplication.
Figure8.4:StylingtheselecteditemofaListView
Now,let’slookatthecode.
Let’sstartwiththeactivitylayoutfileinListing8.8.
Listing8.8:Thelayoutfileforthemainactivity(activity_main.xml)
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:orientation=“horizontal”>
<ListView
android:id=”@+id/listView1”
android:layout_weight=“1”
android:layout_width=“0dp”
android:layout_height=“match_parent”/>
<ListView
android:id=”@+id/listView2”
android:layout_weight=“1”
android:layout_width=“0dp”
android:layout_height=“match_parent”/>
</LinearLayout>
ThelayoutusesahorizontalLinearLayoutthatcontainstwoListViews,namedlistView1and listView2, respectively. Both ListViews receive the same value for theirlayout_weightattribute,sotheywillhavethesamewidthwhenrendered.
TheMainActivity class in Listing 8.9 represents the activity for the application. It’sonCreatemethodloadsbothListViewsandpassthemaListAdapter.Inadditon,thefirstListView’s choice mode is set to CHOICE_MODE_SINGLE, making a single itemselectable at a time. The second ListView’s choice mode is set toCHOICE_MODE_MULTIPLE,whichmakesmultipleitemsselectableatatime.
Listing8.9:TheMainActivityclass
packagecom.example.listviewdemo3;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.widget.ArrayAdapter;
importandroid.widget.ListView;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String[]cities={“Rome”,“Venice”,“Basel”};
ArrayAdapter<String>adapter1=new
ArrayAdapter<String>(this,
android.R.layout.simple_list_item_activated_1,
cities);
ListViewlistView1=(ListView)
findViewById(R.id.listView1);
listView1.setAdapter(adapter1);
listView1.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
ArrayAdapter<String>adapter2=new
ArrayAdapter<String>(this,
R.layout.list_item,cities);
ListViewlistView2=(ListView)
findViewById(R.id.listView2);
listView2.setAdapter(adapter2);
listView2.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
}
}
The first ListView’s ListAdapter is given the default layout(simple_list_item_activated_1).ThesecondListView’sListAdapter,ontheotherhand,is set to use a layout that is pointed by R.layout.list_item. This refers to theres/layout/list_item.xmlfileshowninListing8.10.
Listing8.10:Thelist_item.xmlfile
<?xmlversion=“1.0”encoding=“utf-8”?>
<TextViewxmlns:android=“http://schemas.android.com/apk/res/android”
android:id=”@+id/list_item”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”
android:padding=“7dip”
android:textSize=“16sp”
android:textStyle=“bold”
android:background=”@drawable/list_selector”
/>
AlayoutfileforaListViewmustcontainaTextView,asthelist_item.xmlfiledoes.Notethatitsbackgroundattributeisgiventhevaluedrawable/list_selector,whichreferencesthelist_selector.xmlfileinListing8.11.ThisisaselectorfilethatwillbeusedtostyletheselecteditemonlistView2.Theselectorelementcontainsanitemwhosestate_activatedattributeissettotrue,whichmeansitwillbeusedtostyletheselecteditem.Itsdrawableattribute is set to drawable/activated, referring to the drawable/activated.xml file inListing8.12.
Listing8.11:Thedrawable/list_selector.xmlfile
<?xmlversion=“1.0”encoding=“utf-8”?>
<selector
xmlns:android=“http://schemas.android.com/apk/res/android”>
<itemandroid:state_activated=“true”
android:drawable=”@drawable/activated”/>
</selector>
Listing8.12:Thedrawable/activated.xmlfile
<shapexmlns:android=“http://schemas.android.com/apk/res/android”
android:shape=“rectangle”>
<cornersandroid:radius=“8dp”/>
<gradient
android:startColor=”#FFFF0000”
android:endColor=”#FFFF00”
android:angle=“45”/>
</shape>
ThedrawableinListing8.12isbasedonanXMLfilethatcontainsashapewithagivengradientcolor.
RunningtheapplicationwillgiveyouanactivitylikethatinFigure8.4.
SummaryAListView isaviewthatcontainsalistofscrollableitemsandgetsitsdatasourceandlayoutfromaListAdapter,whichinturncanbecreatedfromanArrayAdapter.Inthischapter you learned how to use the ListView. You also learned how to use theListActivityandstyletheselecteditemonaListView.
Chapter9GridView
AGridView is a view that can display a list of scrollable items in a grid. It is like aListVIewexceptthatitdisplayitemsinmultiplecolumns,unlikeaListViewwhereitemsaredisplayedinasinglecolumn.LikeaListView,aGridView tootakesitsdatasourceandlayoutfromaListAdapter.
This chapter shows how you can use theGridView widget and presents a sampleapplication.YoushouldhavereadChapter8,“ListView”beforereadingthischapter.
OverviewTheandroid.widget.GridView class is the template for creating aGridView. Both theGridView andListView classes are direct descendants of android.view.AbsListView.Like aListView, aGridView gets its data source from aListAdapter. Please refer toChapter8,“ListView”formoreinformationontheListAdapter.
You can use aGridView just like youwould other views: by declaring a node in alayoutfile.InthecaseofaGridView,youwouldusethisGridViewelement:
<GridView
android:id=”@+id/gridView1”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”
android:columnWidth=“120dp”
android:numColumns=“auto_fit”
android:verticalSpacing=“10dp”
android:horizontalSpacing=“10dp”
android:stretchMode=“columnWidth”
/>
You can then find theGridView in your activity class using findViewById and pass aListAdaptertoit.
GridViewgridView=(GridView)findViewById(R.id.gridView1);
gridView.setAdapter(listAdapter);
Optionally, you can pass an AdapterView.OnItemClickListener to a GridView’ssetOnItemClickListenermethodtorespondtoitemselection:
gridview.setOnItemClickListener(
newAdapterView.OnItemClickListener(){
publicvoidonItemClick(AdapterView<?>parent,Viewv,int
position,longid){
//dosomethinghere
}
});
UsingtheGridViewTheGridViewDemo1application showsyouhow touse theGridView.Theapplicationonlyhasanactivity,whichusesaGridViewtofillitsentiredisplayarea.TheGridViewinturnusesacustomListAdapterforitsitemsandlayout.
Listing9.1showstheapplicationmanifest.
Listing9.1:TheAndroidManifest.xmlfile
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.gridviewdemo1”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“18”
android:targetSdkVersion=“18”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.gridviewdemo1.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
ThecustomListAdapter that feeds theGridView is an instanceofGridViewAdapter,which is presented in Listing 9.2. GridViewAdapter extendsandroid.widget.BaseAdapter, which in turn implements theandroid.widget.ListAdapterinterface.Therefore,aGridViewAdapterisaListAdapterandcanbepassedtoaGridView’ssetAdaptermethod.
Listing9.2:TheGridViewAdapterclass
packagecom.example.gridviewdemo1;
importandroid.content.Context;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.BaseAdapter;
importandroid.widget.GridView;
importandroid.widget.ImageView;
publicclassGridViewAdapterextendsBaseAdapter{
privateContextcontext;
publicGridViewAdapter(Contextcontext){
this.context=context;
}
privateint[]icons={
android.R.drawable.btn_star_big_off,
android.R.drawable.btn_star_big_on,
android.R.drawable.alert_light_frame,
android.R.drawable.alert_dark_frame,
android.R.drawable.arrow_down_float,
android.R.drawable.gallery_thumb,
android.R.drawable.ic_dialog_map,
android.R.drawable.ic_popup_disk_full,
android.R.drawable.star_big_on,
android.R.drawable.star_big_off,
android.R.drawable.star_big_on
};
@Override
publicintgetCount(){
returnicons.length;
}
@Override
publicObjectgetItem(intposition){
returnnull;
}
@Override
publiclonggetItemId(intposition){
return0;
}
@Override
publicViewgetView(intposition,ViewconvertView,ViewGroupparent){
ImageViewimageView;
if(convertView==null){
imageView=newImageView(context);
imageView.setLayoutParams(newGridView.LayoutParams(100,100));
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setPadding(10,10,10,10);
}else{
imageView=(ImageView)convertView;
}
imageView.setImageResource(icons[position]);
returnimageView;
}
}
GridViewAdapter provides an implementation of the getView method that returns anImageViewdisplayingoneofAndroid’sdefaultdrawables:
privateint[]icons={
android.R.drawable.btn_star_big_off,
android.R.drawable.btn_star_big_on,
android.R.drawable.alert_light_frame,
android.R.drawable.alert_dark_frame,
android.R.drawable.arrow_down_float,
android.R.drawable.gallery_thumb,
android.R.drawable.ic_dialog_map,
android.R.drawable.ic_popup_disk_full,
android.R.drawable.star_big_on,
android.R.drawable.star_big_off,
android.R.drawable.star_big_on
};
Now that you knowwhatGridViewAdapter does, you can focus on the activity. Thelayoutfile for theactivity isprinted inListing9.3. Itonlyconsistsofonecomponent,aGridView.
Listing9.3:Theactivity_main.xmlfile
<?xmlversion=“1.0”encoding=“utf-8”?>
<GridViewxmlns:android=“http://schemas.android.com/apk/res/android”
android:id=”@+id/gridview”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”
android:columnWidth=“90dp”
android:numColumns=“auto_fit”
android:verticalSpacing=“10dp”
android:horizontalSpacing=“10dp”
android:stretchMode=“columnWidth”
android:gravity=“center”
/>
Listing9.4showstheMainActivityclass.
Listing9.4:TheMainActivityclass
packagecom.example.gridviewdemo1;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.View;
importandroid.widget.AdapterView;
importandroid.widget.AdapterView.OnItemClickListener;
importandroid.widget.GridView;
importandroid.widget.Toast;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
GridViewgridview=(GridView)findViewById(R.id.gridview);
gridview.setAdapter(newGridViewAdapter(this));
gridview.setOnItemClickListener(newOnItemClickListener(){
publicvoidonItemClick(AdapterView<?>parent,
Viewview,intposition,longid){
Toast.makeText(MainActivity.this,””+position,
Toast.LENGTH_SHORT).show();
}
});
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
}
MainActivityisasimpleclass,withthebulkofitsbrainresidesinitsonCreatemethod.HereitloadstheGridViewfromthelayoutandpassesaninstanceofGridViewAdapterto theGridView’ssetAdaptermethod. It also creates anOnItemClickListener for theGridView so that every time an item on theGridView is selected, the onItemClickmethodin the listenergetscalled.In thiscase,onItemClick simplycreatesaToast thatshowsthepositionoftheselecteditem.
RunningGridViewDemo1givesyouanactivitythatlooksliketheoneinFigure9.1.
Figure9.1:UsingaGridView
SummaryAGridView isaviewthatcontainsa listofscrollable itemsdisplayed inagrid.LikeaListView, a GridView gets its data and layout from a ListAdapter. In addition, aGridView can also receive an AdapterView.OnItemClickListener to handle itemselection.
Chapter10StylesandThemes
Thelookandfeelofanapplicationaregovernedbythestylesandthemesitisusing.Thischapterdiscussesthesetwoimportanttopicsandshowsyouhowtousethem.
OverviewAviewdeclaration ina layout file canhaveattributes,manyofwhichare style-related,includingtextColor,textSize,background,andtextAppearance.
Style-relatedattributesforanapplicationcanbelumpedinagroupandthegroupcanbegiven a name and moved to a styles.xml file. A styles.xml file that is saved in theres/valuesdirectorywillberecognizedbytheapplicationasastylesfileandthestylesinthefilecanbeusedtostyletheviewsintheapplication.Toapplyastyletoaview,usethestyle attribute. The advantage of creating a style is to make the style reusable andshareable.Stylessupportinheritancesoyoucanextendastyletocreateanewstyle.Hereisanexampleofastyleinastyles.xmlfile.
<stylename=“Style1”>
<itemname=“android:layout_width”>wrap_content</item>
<itemname=“android:layout_height”>wrap_content</item>
<itemname=“android:textColor”>#FFFFFF</item>
<itemname=“android:textStyle”>bold</item>
<itemname=“android:textSize”>25sp</item>
</style>
Toapplythestyletoaview,assignthestylenametothestyleattribute.
<TextView
android:id=”@+id/textView1”
style=”@style/Style1”
android:text=“Style1”/>
Notethatthestyleattribute,unlikeotherattributes,doesnotusetheandroidprefix.So,it’sstyleandnotandroid:style.
TheTextViewelementdeclarationaboveisequivalenttothefollowing.
<TextView
android:id=”@+id/textView1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:textColor=”#FFFFFF”
android:textStyle=“bold”
android:textSize=“25sp”
android:text=“Style1”/>
Thefollowingisastylethatextendsanotherstyle.
<stylename=“Style2”parent=“Style1”>
<itemname=“android:background”>
@android:color/holo_green_light
</item>
</style>
Thesystemprovidesavastcollectionofstylesyoucanuseinyourapplications.Youcanfindareferenceofallavailablestylesintheandroid.R.styleclass.Tousethestyleslistedinthisclassinyourlayoutfile,replaceallunderscoresinthestylenamewithaperiod.Forexample, you can apply the Holo_ButtonBar style with@android:style/Holo.ButtonBar.
<Button
style=”@android:style/Holo.ButtonBar”
android:text=”@string/hello_world”/>
Prefixing the value of the style attribute with android indicates that you are using asystemstyle.
Acopyofthesystemstyles.xmlfilecanbeviewedhere:
https://android.googlesource.com/platform/frameworks/base/+/refs/
heads/master/core/res/res/values/styles.xml
UsingStylesTheStyleDemo1applicationshowshowyoucancreateyourownstyles.
Listing10.1showstheapplication’sstyles.xmlfileintheres/valuesdirectory.
Listing10.1:Thestyles.xmlfile
<resources
xmlns:android=“http://schemas.android.com/apk/res/android”>
<!—Baseapplicationtheme,dependentonAPIlevel.Thistheme
isreplacedbyAppBaseThemefromres/values-vXX/styles.xml
onnewerdevices.
—>
<stylename=“AppBaseTheme”parent=“android:Theme.Light”>
<!—ThemecustomizationsavailableinnewerAPIlevelscan
goinres/values-vXX/styles.xml,whilecustomizations
relatedtobackward-compatibilitycangohere.
—>
</style>
<!—Applicationtheme.—>
<stylename=“AppTheme”parent=“AppBaseTheme”>
<!—AllcustomizationsthatareNOTspecifictoa
particularAPI-levelcangohere.—>
</style>
<stylename=“WhiteOnRed”>
<itemname=“android:layout_width”>wrap_content</item>
<itemname=“android:layout_height”>wrap_content</item>
<itemname=“android:textColor”>#FFFFFF</item>
<itemname=“android:background”>
@android:color/holo_red_light
</item>
<itemname=“android:typeface”>serif</item>
<itemname=“android:textStyle”>bold</item>
<itemname=“android:textSize”>25sp</item>
<itemname=“android:padding”>30dp</item>
</style>
<stylename=“WhiteOnRed.Italic”>
<itemname=“android:textStyle”>bold|italic</item>
</style>
<stylename=“WhiteOnGreen”parent=“WhiteOnRed”>
<itemname=“android:background”>
@android:color/holo_green_light
</item>
</style>
</resources>
Therearefivestylesdefinedinthestyles.xmlfileinListing10.1.Thefirsttwoareaddedby Android Studio when the application was created. They will be explained in the“Themes”sectionlaterinthischapter.
Theotherthreestylesareusedbythemainactivityoftheapplicationinthelayoutfileforthatactivity.ThelayoutfileisshowninListing10.2.
Listing10.2:theactivity_main.xmllayoutfile
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingBottom=”@dimen/activity_vertical_margin”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
tools:context=”.MainActivity”>
<TextView
android:id=”@+id/textView1”
style=”@style/WhiteOnRed”
android:text=“StyleWhiteOnRed”/>
<TextView
android:id=”@+id/textView2”
android:layout_below=”@id/textView1”
android:layout_marginLeft=“20sp”
android:layout_marginTop=“10sp”
style=”@style/WhiteOnRed.Italic”
android:text=“StyleWhiteOnRed.Italic”/>
<TextView
android:id=”@+id/textView3”
android:layout_below=”@id/textView2”
android:layout_toEndOf=”@id/textView2”
style=”@style/WhiteOnGreen”
android:text=“StyleWhiteOnGreen”/>
<TextView
android:id=”@+id/textView4”
android:text=“StyleTextAppearance.Holo.Medium.Inverse”
android:layout_below=”@id/textView2”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
style=”@android:style/TextAppearance.Holo.Medium”/>
</RelativeLayout>
Listing10.3showstheactivitythatusesthelayoutfileinListing10.2.
Listing10.3:TheMainActivityclass
packagecom.example.styledemo1;
importandroid.os.Bundle;
importandroid.app.Activity;
importandroid.view.Menu;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
//Inflatethemenu;thisaddsitemstotheactionbarifit
//ispresent.
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
}
Figure10.1showstheStyleDemo1application.
Figure10.1:Usingstyles
UsingThemesAtheme isastyle that isapplied toanactivityorall theactivities inanapplication.Toapplyathemetoanactivity,usetheandroid:themeattributeintheactivityelementinthemanifest file. For example, the following activity element uses theTheme.Holo.Lighttheme.
<activity
android:name=”…”
android:theme=”@android:style/Theme.Holo.Light”>
</activity>
To apply a theme to the whole application, add the android:theme attribute in theapplicationelementintheAndroidmanifestfile.Forinstance,
<application
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@android:style/Theme.Black.NoTitleBar”>
…
</application>
Androidprovidesacollectionof themesyoucanuse inyourapplication.Acopyof thethemefilecanbefoundhere:
https://android.googlesource.com/platform/frameworks/base/+/refs/
heads/master/core/res/res/values/themes.xml
Figure10.2to10.4showsomeofthethemesthatcomeswithAndroid.
Figure10.2:Theme.Holo.Dialog.NoActionBar
Figure10.3:Theme.Light
Figure10.4:Theme.Holo.Light.DarkActionBar
SummaryAstyleisacollectionofattributesthatdirectlyaffecttheappearanceofaview.Youcanapplyastyletoaviewbyusingthestyleattributeintheview’sdeclarationinalayoutfile.Athemeisastylethatisappliedtoanactivityortheentireapplication.
Chapter11BitmapProcessing
With theAndroidBitmapAPIyoucanmanipulate images in JPG,PNGorGIF format,suchasbychangingthecolorortheopacityofeachpixelintheimage.Inaddition,youcanusetheAPItodown-samplealargeimagetosavememory.Assuch,knowinghowtouse this API is useful, even when you are not writing a photo editor or an imageprocessingapplication.
Thischapterexplainshowtoworkwithbitmapsandprovidesanexample.
OverviewAbitmapisanimagefileformatthatcanstoredigitalimagesindependentlyofthedisplaydevice.Abitmapsimplymeansamapofbits.Todaythetermalsoincludesotherformatsthatsupportlossyandlosslesscompression,suchasJPEG,GIFandPNG.GIFandPNGsupporttransparencyandlosslesscompression,whereasJPEGsupportlossycompressionanddoesnotsupporttransparency.Anotherwayofrepresentingdigitalimagesisthroughmathematicalexpressions.Suchimagesareknownasvectorgraphics.
TheAndroidframeworkprovidesanAPIforprocessingbitmapimages.ThisAPItakesthe form of classes, interfaces, and enums in the android.graphics package and itssubpackages.TheBitmapclassmodelsabitmapimage.ABitmapcanbedisplayedonanactivityusingtheImageViewwidget.
The easiest way to load a bitmap is by using theBitmapFactory class. This classprovidesstaticmethods forconstructingaBitmap froma file,abytearray,anAndroidresourceoranInputStream.Herearesomeofthemethods.
publicstaticBitmapdecodeByteArray(byte[]data,intoffset,
intlength)
publicstaticBitmapdecodeFile(java.lang.StringpathName)
publicstaticBitmapdecodeResource(
android.content.res.Resourcesres,intid)
publicstaticBitmapdecodeStream(java.io.InputStreamis)
For example, to construct aBitmap fromanAndroid resource in an activity class, youwouldusethiscode.
Bitmapbmp=BitmapFactory.decodeResource(getResources(),
R.drawable.image1);
Here,getResources is a method in the android.content.Context class that returns theapplication’s resources (Context is the parent class of Activity). The identifier(R.drawable.image1)allowsAndroidtopickthecorrectimagefromtheresources.
The BitmapFactory class also offers static methods that take options as aBitmapFactory.Optionsobject:
publicstaticBitmapdecodeByteArray(byte[]data,intoffset,
intlength,BitmapFactory.Optionsopts)
publicstaticBitmapdecodeFile(java.lang.StringpathName,
BitmapFactory.Optionsopts)
publicstaticBitmapdecodeResource(android.content.res.Resources
res,intid,BitmapFactory.Optionsopts)
publicstaticBitmapdecodeStream(java.io.InputStreamis,
RectoutPadding,BitmapFactory.Optionsopts)
TherearetwothingsyoucandowithaBitmapFactory.Options.Thefirstisitallowsyoutoconfiguretheresultingbitmapastheclassallowsyoutodown-samplethebitmap,setthe bitmap to be mutable and configure its density. The second is you can use theBitmapFactory.Options to read thepropertiesof abitmapwithout actually loading theimage. For example, you may pass a BitmapFactory.Options to one of the decodemethods inBitmapFactoryandread thesizeof the image. If thesize isconsidered toolarge, then you can down-sample it, saving precious memory. Down-sampling makessensefor largebitmapswhen itdoesnot reducerenderquality.For instance,a20,000x10,000bitmapcanbedown-sampledto2,000x1,000withoutdegradationassumingthedevicescreen resolutiondoesnotexceed2,000x1,000. In theprocess, it savesa lotofmemory.
TodecodeaBitmapwithoutactuallyloadingthebitmap,settheinJustDecodeBoundsfieldoftheBitmapFactory.Optionsobjecttotrue.
BitmapFactory.Optionsopts=newBitmapFactory.Options()
opts.inJustDecodeBounds=true;
IfyoupasstheoptionstooneofthedecodemethodsinBitmapFactory,themethodwillreturnnullandsimplypopulatetheBitmapFactory.Optionsobjectthatyoupassed.Fromthisobject,youcanretrievethebitmapsizeandotherproperties:
intimageHeight=options.outHeight;
intimageWidth=options.outWidth;
StringimageType=options.outMimeType;
The inSampleSize field of BitmapFactor.Options tells the system how to sample abitmap. A value greater than 1 indicates that the image should be down-sampled. Forexample,settingtheinSampleSizefieldto4returnsanimagewhosesizeisaquarterthatoftheoriginalimage.
Regardingthisfield,theAndroiddocumentationsaysthatthedecoderusesafinalvaluebasedonpowersof2,whichmeansyoushouldonlyassignapowerof2,suchas2,4,8,andsoon.However,myowntestshowsthatthisonlyappliestoimagesinJPGformatanddoesnotapplytoPNGs.Forinstance,ifthewidthofaPNGimageis1200,assigning3tothis field returns an imagewith awidth of 400 pixels,whichmeans the inSampleSizevaluedoesnothavetobeapoweroftwo.
Finally,onceyougetaBitmapfromaBitmapFactory,youcanpasstheBitmaptoanImageViewtobedisplayed:
ImageViewimageView1=(ImageView)findViewById(…);
imageView1.setImageBitmap(bitmap);
BitmapProcessingThe BitmapDemo application showcases an activity that shows an ImageView thatdisplays aBitmap that canbe down-sampled.There are four bitmaps (two JPEGs, oneGIF,andonePNG)includedandtheapplicationprovidesabuttontochangebitmaps.Themain(andonly)activityoftheapplicationisshowninFigure11.1.
Listing11.1showstheAndroidManifest.xmlfilefortheapplication.
Listing11.1:TheAndroidManifest.xmlfile
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.bitmapdemo”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“18”
android:targetSdkVersion=“18”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.bitmapdemo.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
Figure11.1:TheBitmapDemoapplication
There isonlyoneactivity in thisapplication.The layout file for theactivity isgiven inListing11.2.
Listing11.2:Theactivity_main.xmlfile
<LinearLayoutxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:orientation=“vertical”
android:gravity=“bottom”
tools:context=”.MainActivity”>
<ImageView
android:id=”@+id/image_view1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:contentDescription=”@string/text_content_desc”/>
<LinearLayout
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:orientation=“horizontal”>
<TextView
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/text_sample_size”/>
<TextView
android:id=”@+id/sample_size”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
/>
<Button
android:onClick=“scaleUp”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/action_scale_up”/>
<Button
android:onClick=“scaleDown”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/action_scale_down”/>
</LinearLayout>
<LinearLayout
android:layout_width=“match_parent”
android:layout_height=“wrap_content”
android:orientation=“horizontal”>
<Button
android:onClick=“changeImage”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=”@string/action_change_image”/>
<TextView
android:id=”@+id/image_info”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”/>
</LinearLayout>
</LinearLayout>
The layout contains a LinearLayout that in turn contains an ImageView and twoLinearLayouts.ThefirstinnerlayoutcontainstwoTextViewsandbuttonsforscalingupanddownthebitmap.ThesecondinnerlayoutcontainsaTextViewtodisplaythebitmapmetadataandabuttontochangethebitmap.
TheMainActivityclassispresentedinListing11.3.
Listing11.3:TheMainActivityclass
packagecom.example.bitmapdemo;
importandroid.app.Activity;
importandroid.graphics.Bitmap;
importandroid.graphics.BitmapFactory;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.View;
importandroid.widget.ImageView;
importandroid.widget.TextView;
publicclassMainActivityextendsActivity{
intsampleSize=2;
intimageId=1;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
refreshImage();
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
publicvoidscaleDown(Viewview){
if(sampleSize<8){
sampleSize++;
refreshImage();
}
}
publicvoidscaleUp(Viewview){
if(sampleSize>2){
sampleSize—;
refreshImage();
}
}
privatevoidrefreshImage(){
BitmapFactory.Optionsoptions=newBitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeResource(getResources(),
R.drawable.image1,options);
intimageHeight=options.outHeight;
intimageWidth=options.outWidth;
StringimageType=options.outMimeType;
StringBuilderimageInfo=newStringBuilder();
intid=R.drawable.image1;
if(imageId==2){
id=R.drawable.image2;
imageInfo.append(“Image2.”);
}elseif(imageId==3){
id=R.drawable.image3;
imageInfo.append(“Image3.”);
}elseif(imageId==4){
id=R.drawable.image4;
imageInfo.append(“Image4.”);
}else{
imageInfo.append(“Image1.”);
}
imageInfo.append(“OriginalDimension:”+imageWidth
+”x”+imageHeight);
imageInfo.append(“.MIMEtype:”+imageType);
options.inSampleSize=sampleSize;
options.inJustDecodeBounds=false;
Bitmapbitmap1=BitmapFactory.decodeResource(
getResources(),id,options);
ImageViewimageView1=(ImageView)
findViewById(R.id.image_view1);
imageView1.setImageBitmap(bitmap1);
TextViewsampleSizeText=(TextView)
findViewById(R.id.sample_size);
sampleSizeText.setText(””+sampleSize);
TextViewinfoText=(TextView)
findViewById(R.id.image_info);
infoText.setText(imageInfo.toString());
}
publicvoidchangeImage(Viewview){
if(imageId<4){
imageId++;
}else{
imageId=1;
}
refreshImage();
}
}
ThescaleDown,scaleUpandchangeImagemethodsareconnected to the threebuttons.AllmethodseventuallycalltherefreshImagemethod.
The refreshImagemethod uses theBitmapFactory.decodeResourcemethod to firstread thepropertiesof thebitmapresource,bypassingaBitmapFactory.OptionswhoseinJustDecodeBoundsfieldissettotrue.Recallthatthisisastrategyforavoidingloadingalargeimagethatwilltakemuchifnotalloftheavailablememory.
BitmapFactory.Optionsoptions=newBitmapFactory.Options();
options.inJustDecodeBounds=true;
BitmapFactory.decodeResource(getResources(),
R.drawable.image1,options);
Itthenreadsthedimensionandimagetypeofthebitmap.
intimageHeight=options.outHeight;
intimageWidth=options.outWidth;
StringimageType=options.outMimeType;
Next,itsetstheinJustDecodeBoundsfieldtofalseandusesthesampleSizevalue(thatthe user can change by clicking the Scale Up or Scale Down button) to set theinSampleSizefieldoftheBitmapFactory.Options,anddecodethebitmapforthesecondtime.
options.inSampleSize=sampleSize;
options.inJustDecodeBounds=false;
Bitmapbitmap1=BitmapFactory.decodeResource(
getResources(),id,options);
The dimension of the resulting Bitmap will be determined by the value of theinSampleSizefield.
SummaryTheAndroidBitmapAPI centers around theBitmapFactory andBitmap classes. Theformer provides static methods for constructing a Bitmap object from an Androidresource, a file, an InputStream, or a byte array. Some of the methods can take aBitmapFactory.Options to determine what kind of bitmap they will produce. TheresultingbitmapcanthenbeassignedtoanImageViewfordisplay.
Chapter12GraphicsandCustomViews
Thanks to Android’s extensive library, you have dozens of views and widgets at yourdisposal. If none of these meets your need, you can create a custom view and drawdirectlyonitusingtheAndroidGraphicsAPI.
This chapter discusses the use of somemembers of the Graphics API to draw on acanvasandcreateacustomview.AsampleapplicationcalledCanvasDemoispresentedattheendofthischapter.
OverviewTheAndroidGraphicsAPIcomprisesthemembersoftheandroid.graphicspackage.TheCanvasclassinthispackageplaysacentralrolein2Dgraphics.YoucangetaninstanceofCanvasfromthesystemandyoudonotneedtocreateoneyourself.Onceyouhaveaninstance ofCanvas, you can call its various methods, such as drawColor, drawArc,drawRect,drawCircle,anddrawText.
InadditiontoCanvas,ColorandPaintarefrequentlyused.AColorobjectrepresentsacolorcodeasanint.TheColorclassdefinesanumberofcolorcodefieldsandmethodsfor creating and converting color ints. Color code fields defined in Color includesBLACK,CYAN,MAGENTA,YELLOW,WHITE,RED,GREENandBLUE.
Take thedrawColormethod inCanvas as an example.Thismethod accepts a colorcodeasanargument.
publicvoiddrawColor(intcolor);
drawColorchangesthecolorofthecanvaswiththespecifiedcolor.Tochangethecanvascolortomagenta,youwouldwrite
canvas.drawColor(Color.MAGENTA);
APaint is required when drawing a shape or text. A Paint determines the color andtransparencyoftheshapeortextdrawnaswellasthefontfamilyandstyleofthetext.
TocreateaPaint,useoneofthePaintclass’sconstructors:
publicPaint()
publicPaint(intflags)
publicPaint(PaintanotherPaint)
Ifyouuse thesecondconstructor,youcanpassoneormore fieldsdefined in thePaintclass. For example, the following code creates a Paint by passing theLINEAR_TEXT_FLAGandANTI_ALIAS_FLAGfields.
Paintpaint=newPaint(
Paint.LINEAR_TEXT_FLAG|Paint.ANTI_ALIAS_FLAG);
HardwareAccelerationModern smart phones and tablets come with a graphic processing unit (GPU), anelectroniccircuit thatspecializes in imagecreationandrendering.StartingwithAndroid3.0, the Android framework will utilize any GPU it can find on a device, resulting inimprovedperformance throughhardware acceleration.Hardware acceleration is enabledbydefaultforanyapplicationtargetingAndroidAPIlevel14orabove.
Unfortunately,currentlynotalldrawingoperationsworkwhenhardwareaccelerationisturned on. You can disable hardware acceleration by setting theandroid:hardwareAccelerated attribute to false in either the application or activityelementinyourandroidmanifestfile.Forexample,toturnoffhardwareaccelerationforthewholeapplication,usethis:
<applicationandroid:hardwareAccelerated=“false”>
Todisablehardwareaccelerationinanactivity,usethis:
<activityandroid:hardwareAccelerated=“false”/>
It is possible to use the android:hardwareAccelerated attribute in both application oractivity levels. For example, the following indicates that all except one activity in theapplicationshouldusehardwareacceleration.
<applicationandroid:hardwareAccelerated=“true”>
<activity…/>
<activityandroid:hardwareAccelerated=“false”/>
</application>
NoteTotryouttheexamplesinthischapter,youmustdisablehardwareacceleration.
CreatingACustomViewTocreateacustomview,extendtheandroid.view.ViewclassoroneofitssubclassesandoverrideitsonDrawmethod.HereisthesignatureofonDraw.
protectedvoidonDraw(android.graphics.Canvascanvas)
The systemcalls theonDrawmethod and pass aCanvas.You can use themethods inCanvas to draw shapes and text. You can also create path and regions to draw morecomplexshapes.
The onDrawmethodmay be calledmany times during the application lifecycle.Assuch, you should not perform expensive operations here, such as allocating objects.ObjectsthatyouneedtouseinonDrawshouldbecreatedsomewhereelse.
Forexample,mostdrawingmethodsinCanvasrequireaPaint.RatherthancreatingaPaintinonDraw,youshouldcreate itat theclass levelandhave itavailable foruse in
onDraw.Thisisillustratedinthefollowingclass.
publicclassMyCustomViewextendsView{
Paintpaint;
{
paint=…//createaPaintobjecthere
}
@Override
protectedvoidonDraw(Canvascanvas){
//usepainthere.
}
}
DrawingBasicShapesTheCanvas class defines methods such as drawLine, drawCircle, and drawRect todrawshapes.Forexample, the followingcodeshowshowyoucandrawshapes inyouronDrawmethod.
Paintpaint=newPaint(Paint.FAKE_BOLD_TEXT_FLAG);
protectedvoidonDraw(Canvascanvas){
//changecanvasbackgroundcolor.
canvas.drawColor(Color.parseColor(“#bababa”));
//drawbasicshapes
canvas.drawLine(5,5,200,5,paint);
canvas.drawLine(5,15,200,15,paint);
canvas.drawLine(5,25,200,25,paint);
paint.setColor(Color.YELLOW);
canvas.drawCircle(50,70,35,paint);
paint.setColor(Color.GREEN);
canvas.drawRect(newRect(100,60,150,80),paint);
paint.setColor(Color.DKGRAY);
canvas.drawOval(newRectF(160,60,250,80),paint);
…
}
Figure12.1showstheresult.
Figure12.1:Basicshapes
DrawingText
To draw text on a canvas, use the drawText method and a Paint. For example, thefollowingcodedrawstextusingdifferentcolors.
//drawtext
textPaint.setTextSize(22);
canvas.drawText(“Welcome”,20,100,textPaint);
textPaint.setColor(Color.MAGENTA);
textPaint.setTextSize(40);
canvas.drawText(“Welcome”,20,140,textPaint);
Figure12.2showsthedrawntext.
Figure12.2:Drawingtext
TransparencyAndroid’sGraphicsAPIsupportstransparency.YoucansetthetransparencybyassigninganalphavaluetothePaintusedindrawing.Considerthefollowingcode.
//transparency
textPaint.setColor(0xFF465574);
textPaint.setTextSize(60);
canvas.drawText(“AndroidRocks”,20,340,textPaint);
//opaquecircle
canvas.drawCircle(80,300,20,paint);
//semi-transparentcircles
paint.setAlpha(110);
canvas.drawCircle(160,300,39,paint);
paint.setColor(Color.YELLOW);
paint.setAlpha(140);
canvas.drawCircle(240,330,30,paint);
paint.setColor(Color.MAGENTA);
paint.setAlpha(30);
canvas.drawCircle(288,350,30,paint);
paint.setColor(Color.CYAN);
paint.setAlpha(100);
canvas.drawCircle(380,330,50,paint);
Figure12.3showssomesemitransparentcircles..
Figure12.3:Transparency
Shaders
AShader is a span of colors. You create a Shader by defining two colors as in thefollowingcode.
//shader
PaintshaderPaint=newPaint();
Shadershader=newLinearGradient(0,400,300,500,Color.RED,
Color.GREEN,Shader.TileMode.CLAMP);
shaderPaint.setShader(shader);
canvas.drawRect(0,400,200,500,shaderPaint);
Figure12.4showsalineargradientshader.
Figure12.4:Usingalineargradientshader
ClippingClippingistheprocessofallocatinganareaonacanvasfordrawing.Theclippedareacanbearectangle,acircle,oranyarbitraryshapeyoucanimagine.Onceyouclipthecanvas,anyotherdrawingthatwouldotherwiseberenderedoutsidetheareawillbeignored.
Figure12.5showsaclipareaintheshapeofastar.Afterthecanvasisclipped,drawntextwillonlybevisiblewithintheclippedarea.
Figure12.5:Anexampleofclipping
TheCanvas classprovides the followingmethods for clipping:clipRect,clipPath,andclipRegion.TheclipRectmethodusesaRectasaclipareaandclipPathusesaPath.Forexample,theclipareainFigure12.5wascreatedusingthiscode.
canvas.clipPath(starPath);
//starPathisaPathintheshapeofastar,seenextsection
//onhowtocreateit.
textPaint.setColor(Color.parseColor(“yellow”));
canvas.drawText(“Android”,350,550,textPaint);
textPaint.setColor(Color.parseColor(“#abde97”));
canvas.drawText(“Android”,400,600,textPaint);
canvas.drawText(“AndroidRocks”,300,650,textPaint);
canvas.drawText(“AndroidRocks”,320,700,textPaint);
canvas.drawText(“AndroidRocks”,360,750,textPaint);
canvas.drawText(“AndroidRocks”,320,800,textPaint);
You’lllearnmoreaboutclippinginthenextsections.
UsingPathsAPathisacollectionofanynumberofstraightlinesegments,quadraticcurves,andcubiccurves.APathcanbeusedforclippingortodrawtexton.
Asanexample,thismethodcreatesastarpath.Ittakesacoordinatethatisthelocationofitscenter.
privatePathcreateStarPath(intx,inty){
Pathpath=newPath();
path.moveTo(0+x,150+y);
path.lineTo(120+x,140+y);
path.lineTo(150+x,0+y);
path.lineTo(180+x,140+y);
path.lineTo(300+x,150+y);
path.lineTo(200+x,190+y);
path.lineTo(250+x,300+y);
path.lineTo(150+x,220+y);
path.lineTo(50+x,300+y);
path.lineTo(100+x,190+y);
path.lineTo(0+x,150+y);
returnpath;
}
ThefollowingcodeshowshowtodrawtextthatcurvesalongaPath.
publicclassCustomViewextendsView{
PathcurvePath;
PainttextPaint=newPaint(Paint.LINEAR_TEXT_FLAG);
{
Typefacetypeface=Typeface.create(Typeface.SERIF,
Typeface.BOLD);
textPaint.setTypeface(typeface);
curvePath=createCurvePath();
}
privatePathcreateCurvePath(){
Pathpath=newPath();
path.addArc(newRectF(400,40,780,300),-210,230);
returnpath;
}
protectedvoidonDraw(Canvascanvas){
…
//drawtextonpath
textPaint.setColor(Color.rgb(155,20,10));
canvas.drawTextOnPath(“Niceartistictouches”,
curvePath,10,10,textPaint);
…
}
}
Figure12.6showsthedrawntext.
Figure12.6:Drawingtextonapath
TheCanvasDemoApplicationTheCanvasDemoapplication featuresacustomviewandcontainsall thecode snippetspresentedinthischapter.Figure12.7showsthemainactivityoftheapplication.
Figure12.7:TheCanvasDemoapplication
Listing 12.1 shows theAndroidManifest.xml file for this application. It only has oneactivity.
Listing12.1:TheAndroidManifest.xmlfile
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.canvasdemo”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“18”
android:targetSdkVersion=“18”/>
<application
android:hardwareAccelerated=“false”
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.canvasdemo.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
Themain actor of the application is theCustomView class in Listing 12.2. It extendsViewandoverridesitsonDrawmethod.
Listing12.2:TheCustomViewclass
packagecom.example.canvasdemo;
importandroid.content.Context;
importandroid.graphics.Canvas;
importandroid.graphics.Color;
importandroid.graphics.LinearGradient;
importandroid.graphics.Paint;
importandroid.graphics.Path;
importandroid.graphics.Rect;
importandroid.graphics.RectF;
importandroid.graphics.Shader;
importandroid.graphics.Typeface;
importandroid.view.View;
publicclassCustomViewextendsView{
publicCustomView(Contextcontext){
super(context);
}
Paintpaint=newPaint(Paint.FAKE_BOLD_TEXT_FLAG);
PathstarPath;
PathcurvePath;
PainttextPaint=newPaint(Paint.LINEAR_TEXT_FLAG);
PaintshaderPaint=newPaint();
{
Typefacetypeface=Typeface.create(
Typeface.SERIF,Typeface.BOLD);
textPaint.setTypeface(typeface);
Shadershader=newLinearGradient(0,400,300,500,
Color.RED,Color.GREEN,Shader.TileMode.CLAMP);
shaderPaint.setShader(shader);
//createstarpath
starPath=createStarPath(300,500);
curvePath=createCurvePath();
}
protectedvoidonDraw(Canvascanvas){
//drawbasicshapes
canvas.drawLine(5,5,200,5,paint);
canvas.drawLine(5,15,200,15,paint);
canvas.drawLine(5,25,200,25,paint);
paint.setColor(Color.YELLOW);
canvas.drawCircle(50,70,35,paint);
paint.setColor(Color.GREEN);
canvas.drawRect(newRect(100,60,150,80),paint);
paint.setColor(Color.DKGRAY);
canvas.drawOval(newRectF(160,60,250,80),paint);
//drawtext
textPaint.setTextSize(22);
canvas.drawText(“Welcome”,20,150,textPaint);
textPaint.setColor(Color.MAGENTA);
textPaint.setTextSize(40);
canvas.drawText(“Welcome”,20,190,textPaint);
//transparency
textPaint.setColor(0xFF465574);
textPaint.setTextSize(60);
canvas.drawText(“AndroidRocks”,20,340,textPaint);
//opaquecircle
canvas.drawCircle(80,300,20,paint);
//semi-transparentcircle
paint.setAlpha(110);
canvas.drawCircle(160,300,39,paint);
paint.setColor(Color.YELLOW);
paint.setAlpha(140);
canvas.drawCircle(240,330,30,paint);
paint.setColor(Color.MAGENTA);
paint.setAlpha(30);
canvas.drawCircle(288,350,30,paint);
paint.setColor(Color.CYAN);
paint.setAlpha(100);
canvas.drawCircle(380,330,50,paint);
//drawtextonpath
textPaint.setColor(Color.rgb(155,20,10));
canvas.drawTextOnPath(“Niceartistictouches”,
curvePath,10,10,textPaint);
//shader
canvas.drawRect(0,400,200,500,shaderPaint);
//createastar-shapedclip
canvas.drawPath(starPath,textPaint);
textPaint.setColor(Color.CYAN);
canvas.clipPath(starPath);
textPaint.setColor(Color.parseColor(“yellow”));
canvas.drawText(“Android”,350,550,textPaint);
textPaint.setColor(Color.parseColor(“#abde97”));
canvas.drawText(“Android”,400,600,textPaint);
canvas.drawText(“AndroidRocks”,300,650,textPaint);
canvas.drawText(“AndroidRocks”,320,700,textPaint);
canvas.drawText(“AndroidRocks”,360,750,textPaint);
canvas.drawText(“AndroidRocks”,320,800,textPaint);
}
privatePathcreateStarPath(intx,inty){
Pathpath=newPath();
path.moveTo(0+x,150+y);
path.lineTo(120+x,140+y);
path.lineTo(150+x,0+y);
path.lineTo(180+x,140+y);
path.lineTo(300+x,150+y);
path.lineTo(200+x,190+y);
path.lineTo(250+x,300+y);
path.lineTo(150+x,220+y);
path.lineTo(50+x,300+y);
path.lineTo(100+x,190+y);
path.lineTo(0+x,150+y);
returnpath;
}
privatePathcreateCurvePath(){
Pathpath=newPath();
path.addArc(newRectF(400,40,780,300),
-210,230);
returnpath;
}
}
TheMainActivity class, given in Listing 12.3, instantiates theCustomView class andpasstheinstancetoitssetContentViewmethod.This isunlikemostapplications in thisbookwhereyoupassalayoutresourceidentifiertoanotheroverloadofsetContentView.
Listing12.3:TheMainActivityclass
packagecom.example.canvasdemo;
importandroid.app.Activity;
importandroid.os.Bundle;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
CustomViewcustomView=newCustomView(this);
setContentView(customView);
}
}
SummaryThe Android SDK comes with a wide range of views that you can use in yourapplications.Ifnoneofthesesuitsyourneed,youcancreateacustomviewanddrawonit.Thischaptershowedyouhowtocreateacustomviewanddrawmultipleshapesonacanvas.
Chapter13Fragments
Apowerful featureadded toAndroid3.0(API level11), fragmentsarecomponents thatcan be embedded into an activity. Unlike custom views, fragments have their ownlifecycleandmayormaynothaveauserinterface.
Thischapterexplainswhatfragmentsareandshowshowtousethem.
TheFragmentLifecycleYou create a fragment by extending the android.app.Fragment class or one of itssubclasses.A fragmentmayormaynot have a user interface.A fragmentwith nouserinterface(UI)normallyactsasaworkerfortheactivitythefragmentisembeddedinto.IfafragmenthasaUI,itmaycontainviewsarrangedinalayoutfilethatwillbeloadedafterthe fragment is created. In many aspects, writing a fragment is similar to writing anactivity.
Inordertocreatefragmentseffectively,youneedtoknowthelifecycleofafragment.Figure13.1showsthelifecycleofafragment.
Thelifecycleofafragmentissimilartothatofanactivity.Forexample,ithascallbackmethodssuchasonCreate,onResumeandonPause.Ontopofthat,thereareadditionalmethods likeonAttach,onActivityCreatedandonDetach.onAttach is called after thefragment is associated with an activity and onActivityCreated gets called after theonCreate method of the activity that contains the fragment is completed. onDetach isinvokedbeforeafragmentisdetachedfromanactivity.
Figure13.1:Thefragmentlifecycle
onAttach.Calledrightafterthefragmentisassociatedwithitsactivity.
onCreate.Calledtocreatethefragmentthefirsttime.onCreateView.Calledwhenitistimetocreatethelayoutforthefragment.Itmustreturnthefragment’srootview.onActivityCreated.Called to tell the fragment that its activity’sonCreatemethodhascompleted.onStart.Calledwhenthefragment’sviewismadevisibletotheuser.onResume. Called when the containing activity enters theResumed state, whichmeanstheactivityisrunning.onPause.Calledwhenthecontainingactivityisbeingpaused.onStop.Calledwhenthecontainingactivityisstopped.onDestroyView.Calledtoallowthefragmenttoreleaseresourcesusedforitsview.onDestroy.Calledtoallowthefragmenttodofinalclean-upbeforeitisdestroyed.onDetach.Calledrightafterthefragmentisdetachedfromitsactivity.
Therearesomesubtledifferencesbetweenanactivityandafragment.Inanactivity,younormallysettheviewfortheactivityinitsonCreatemethodusingthesetContentViewmethod,e.g.
protectedvoidonCreate(android.os.BundlesavedInstanceState){
super(savedInstanceState);
setContentView(R.layout.activity_main);
…
}
In a fragment you normally create a view in its onCreateView method. Here is thesignatureoftheonCreateViewmethod.
publicViewonCreateView(android.view.LayoutInflaterinflater,
android.view.ViewGroupcontainer,
android.os.BundlesavedInstanceState);
NoticedtherearethreeargumentsthatarepassedtoonCreateView?ThefirstargumentisaLayoutInflaterthatyouusetoinflateanyviewinthefragment.Thesecondargumentistheparentviewthefragmentshouldbeattachedto.Thethirdargument,aBundle,ifnotnullcontainsinformationfromthepreviouslysavedstate.
Inanactivity,youcanobtainareferencetoaviewbycallingthefindViewByIdmethodon the activity. In a fragment, you can find a view in the fragment by calling thefindViewByIdontheparentview.
Viewroot=inflater.inflate(R.layout.fragment_names,
container,false);
ViewaView=(View)root.findViewById(id);
Alsonotethatafragmentshouldnotknowanythingaboutitsactivityorotherfragments.Ifyouneedtolistenforaneventthatoccursinafragmentthataffectstheactivityorotherviewsor fragments,donotwrite a listener in the fragment class. Instead, trigger aneweventinresponsetothefragmenteventandlettheactivityhandleit.
Youwilllearnmoreaboutcreatingafragmentinlatersectionsinthischapter.
FragmentManagementTouse a fragment in an activity, use the fragment element in a layout file just as youwould a view. Specify the fragment class name in the android:name attribute and anidentifierintheandroid:idattribute.Hereisanexampleofafragmentelement.
<fragment
android:name=“com.example.MyFragment”
android:id=”@+id/fragment1”
…
/>
Alternatively,Youcanmanagefragmentsprogrammaticallyinyouractivityclassusinganandroid.app.FragmentManager. You can obtain the default instance ofFragmentManagerbycalling thegetFragmentManagermethod inyouractivityclass.Then, call the beginTransaction method on the FragmentManager to obtain aFragmentTransaction.
FragmentManagerfragmentManager=getFragmentManager();
FragmentTransactionfragmentTransaction=
fragmentManager.beginTransaction();
Theandroid.app.FragmentTransactionclassoffersmethodsforadding,removing,andreplacing fragments. Once you’re finished, call FragmentTransaction.commit() tocommityourchanges.
Youcan add a fragment to an activityusingoneof theaddmethodoverloads in theFragmentTransactionclass.Youhavetospecifytowhichviewthefragmentshouldbeaddedto.Normally,youwouldaddafragmenttoaFrameLayoutorsomeothertypeoflayout.HereisoneoftheaddmethodsinFragmentTransaction.
publicabstractFragmentTransactionadd(intcontainerViewId,
Fragmentfragment,Stringtag)
Touseadd,youwouldinstantiateyourfragmentclassandthenspecifytheIDoftheviewto add to. If you pass a tag, you can later retrieve your fragment using thefindFragmentByTagmethodontheFragmentManager.
Ifyouarenotusingatag,youcanusethisaddmethod.
publicabstractFragmentTransactionadd(intcontainerViewId,
Fragmentfragment)
To remove a fragment from an activity, call the remove method on theFragmentTransaction.
publicabstractFragmentTransactionremove(Fragmentfragment)
Andtoreplaceafragmentinaviewwithanotherfragment,usethereplacemethod.
publicabstractFragmentTransactionreplace(intcontainerViewId,
Fragmentfragment,Stringtag)
As a last step once you are finished managing your fragments, call commit on theFragmentTransaction.
publicabstractintcommit()
UsingAFragmentThe FragmentDemo1 application is a sample applicationwith an activity that uses twofragments.Thefirstfragmentlistssomecities.Selectingacitycausesthesecondfragmenttoshowthepictureoftheselectedcity.Sinceproperdesigndictatesthatafragmentshouldnotknowanythingaboutitssurrounding,thefirstfragmentrisesaneventuponreceivinguser selection. The activity handles this new event and causes the second fragment tochange.
Figure13.2showshowFragmentDemo1lookslike.
Figure13.2:Usingfragments
ThemanifestfortheapplicationisprintedinListing13.1.
Listing13.1:TheAndroidManifest.xmlfileforFragmentDemo1
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.fragmentdemo1”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“18”
android:targetSdkVersion=“18”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.fragmentdemo1.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
Nothingextraordinaryhere.Itsimplydeclaresanactivityfortheapplication.
Youusea fragment asyouwouldavieworawidget,bydeclaring it in anactivity’slayout fileorbyprogrammaticallyaddingone.ForFragmentDemo1, two fragmentsareaddedtothelayoutoftheapplication’smainactivity.ThelayoutfileisshowninListing13.2.
Listing13.2:Thelayoutfileforthemainactivity(activity_main.xml)
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“horizontal”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<fragment
android:name=“com.example.fragmentdemo1.NamesFragment”
android:id=”@+id/namesFragment”
android:layout_weight=“1”
android:layout_width=“0dp”
android:layout_height=“match_parent”/>
<fragment
android:name=“com.example.fragmentdemo1.DetailsFragment”
android:id=”@+id/detailsFragment”
android:layout_weight=“2.5”
android:layout_width=“0dp”
android:layout_height=“match_parent”/>
</LinearLayout>
ThelayoutforthemainactivityusesahorizontalLinearLayoutthatsplitsthescreenintotwo panes. The ratio of the pane widths is 1:2.5, as defined by the layout_weightattributesofthefragmentelements.Eachpaneisfilledwithafragment.ThefirstpaneisrepresentedbytheNamesFragmentclassandthesecondpanebytheDetailsFragmentclass.
The first fragment,NamesFragment, gets its layout from the fragment_names.xmlfileinListing13.3.Thisfileislocatedintheres/layoutfolder.
Listing13.3:Thefragment_names.xmlfile
<ListView
xmlns:android=“http://schemas.android.com/apk/res/android”
android:id=”@+id/listView1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:background=”#FFFF55”/>
The layout of NamesFragment is very simple. It contains a single view that is aListView.Thelayout is loadedin theonCreateViewmethodof thefragmentclass(SeeListing13.4).
Listing13.4:TheNamesFragmentclass
packagecom.example.fragmentdemo1;
importandroid.app.Activity;
importandroid.app.Fragment;
importandroid.os.Bundle;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.AdapterView;
importandroid.widget.ArrayAdapter;
importandroid.widget.ListView;
publicclassNamesFragmentextendsFragment{
@Override
publicViewonCreateView(LayoutInflaterinflater,
ViewGroupcontainer,BundlesavedInstanceState){
finalString[]names={“Amsterdam”,“Brussels”,“Paris”};
//useandroid.R.layout.simple_list_item_activated_1
//tohavetheselectediteminadifferentcolor
ArrayAdapter<String>adapter=newArrayAdapter<String>(
getActivity(),
android.R.layout.simple_list_item_activated_1,
names);
Viewview=inflater.inflate(R.layout.fragment_names,
container,false);
finalListViewlistView=(ListView)view.findViewById(
R.id.listView1);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
listView.setOnItemClickListener(new
AdapterView.OnItemClickListener(){
@Override
publicvoidonItemClick(AdapterView<?>parent,
finalViewview,intposition,longid){
if(callback!=null){
callback.onItemSelected(names[position]);
}
}
});
listView.setAdapter(adapter);
returnview;
}
publicinterfaceCallback{
publicvoidonItemSelected(Stringid);
}
privateCallbackcallback;
@Override
publicvoidonAttach(Activityactivity){
super.onAttach(activity);
if(activityinstanceofCallback){
callback=(Callback)activity;
}
}
@Override
publicvoidonDetach(){
super.onDetach();
callback=null;
}
}
TheNamesFragmentclassdefinesaCallbackinterfacethatitsactivitymustimplementtolistentotheitemselectioneventofitsListView.Theactivitycanthenuseit todrivethesecondfragment.TheonAttachmethodmakessurethattheimplementingclassisanActivity.
Thesecondfragment,DetailsFragment,hasalayoutfilethatisgiveninListing13.5.ItcontainsaTextViewandanImageView.TheTextViewdisplaysthenameoftheselectedcityandtheImageViewshowsthepictureoftheselectedcity.
Listing13.5:Thefragment_details.xmlfile
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:background=”#FAFAD2”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<TextView
android:id=”@+id/text1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:textSize=“30sp”/>
<ImageView
android:id=”@+id/imageView1”
android:layout_width=“match_parent”
android:layout_height=“match_parent”/>
</LinearLayout>
TheDetailsFragment class in shown inListing13.6. IthasashowDetailsmethod thatthecontainingactivitycancalltochangethecontentoftheTextViewandImageView.
Listing13.6:TheDetailsFragmentclass
packagecom.example.fragmentdemo1;
importandroid.app.Fragment;
importandroid.os.Bundle;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.ImageView;
importandroid.widget.ImageView.ScaleType;
importandroid.widget.TextView;
publicclassDetailsFragmentextendsFragment{
@Override
publicViewonCreateView(LayoutInflaterinflater,
ViewGroupcontainer,BundlesavedInstanceState){
returninflater.inflate(R.layout.fragment_details,
container,false);
}
publicvoidshowDetails(Stringname){
TextViewtextView=(TextView)
getView().findViewById(R.id.text1);
textView.setText(name);
ImageViewimageView=(ImageView)getView().findViewById(
R.id.imageView1);
imageView.setScaleType(ScaleType.FIT_XY);//stretchimage
if(name.equals(“Amsterdam”)){
imageView.setImageResource(R.drawable.amsterdam);
}elseif(name.equals(“Brussels”)){
imageView.setImageResource(R.drawable.brussels);
}elseif(name.equals(“Paris”)){
imageView.setImageResource(R.drawable.paris);
}
}
}
TheactivityclassforFragmentDemo1ispresentedinListing13.7.
Listing13.7:TheactivityclassforFragmentDemo1
packagecom.example.fragmentdemo1;
importandroid.app.Activity;
importandroid.os.Bundle;
publicclassMainActivityextendsActivity
implementsNamesFragment.Callback{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
publicvoidonItemSelected(Stringvalue){
DetailsFragmentdetails=(DetailsFragment)
getFragmentManager().findFragmentById(
R.id.detailsFragment);
details.showDetails(value);
}
}
The most important thing to note is that the activity class implementsNamesFragment.Callbacksothatitcancapturetheitemclickeventinthefragment.TheonItemSelected method is an implementation for the Callback interface. It calls theshowDetailsmethodinthesecondfragmenttochangethetextandpictureoftheselectedcity.
Extending ListFragment and UsingFragmentManagerFragmentDemo1showedhowyoucouldaddafragmenttoanactivityusingthefragmentelement in the activity’s layout file. In the second sample application,FragmentDemo2,youwilllearnhowtoaddafragmenttoanactivityprogrammatically.
FragmentDemo2 is similar in functionality to its predecessorwith a few differences.Thefirstdifferencepertainstohowthenameandthepictureofaselectedcityareupdated.InFragmentDemo1, the containing activity calls the showDetailsmethod in the secondfragment,passingthecityname.InFragmentDemo2,whenacityisselected,theactivitycreatesanewinstanceofDetailsFragmentandusesittoreplacetheoldinstance.
TheseconddifferenceisthefactthatthefirstfragmentextendsListFragmentinsteadofFragment.ListFragmentisasubclassofFragmentandcontainsaListViewthatfillsits entire view. When subclassing ListFragment, you should override its onCreatemethod and call its setListAdapter method. This is demonstrated in theNamesListFragmentclassinListing13.8.
Listing13.8:TheNamesListFragmentclass
packagecom.example.fragmentdemo2;
importandroid.app.Activity;
importandroid.app.ListFragment;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.AdapterView;
importandroid.widget.ArrayAdapter;
importandroid.widget.ListView;
/*wedon’tneedfragment_names-xmlanymore*/
publicclassNamesListFragmentextendsListFragment{
finalString[]names={“Amsterdam”,“Brussels”,“Paris”};
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
ArrayAdapter<String>adapter=newArrayAdapter<String>(
getActivity(),
android.R.layout.simple_list_item_activated_1,
names);
setListAdapter(adapter);
}
@Override
publicvoidonViewCreated(Viewview,
BundlesavedInstanceState){
//ListViewcanonlybeaccessedhere,notinonCreate()
super.onViewCreated(view,savedInstanceState);
ListViewlistView=getListView();
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
listView.setOnItemClickListener(new
AdapterView.OnItemClickListener(){
@Override
publicvoidonItemClick(AdapterView<?>parent,
finalViewview,intposition,longid){
if(callback!=null){
callback.onItemSelected(names[position]);
}
}
});
}
publicinterfaceCallback{
publicvoidonItemSelected(Stringid);
}
privateCallbackcallback;
@Override
publicvoidonAttach(Activityactivity){
super.onAttach(activity);
if(activityinstanceofCallback){
callback=(Callback)activity;
}
}
@Override
publicvoidonDetach(){
super.onDetach();
callback=null;
}
}
Like theNamesFragment class in FragmentDemo1, theNamesListFragment class inFragmentDemo2 also defines a Callback interface that a containing activity mustimplementtolistentotheListView’sOnItemClickevent.
Thesecondfragment,DetailsFragmentinListing13.9,expectsitsactivitytopasstwoarguments,anameandanimageID.InitsonCreatemethod,thefragmentretrievestheseargumentsandstoretheminclasslevelvariables,nameandimageId.Thevaluesofthevariables are then used in its onCreateView method to populate its TextView andImageView.
Listing13.9:TheDetailsFragmentclass
packagecom.example.fragmentdemo2;
importandroid.app.Fragment;
importandroid.os.Bundle;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.ImageView;
importandroid.widget.ImageView.ScaleType;
importandroid.widget.TextView;
publicclassDetailsFragmentextendsFragment{
intimageId;
Stringname;
publicDetailsFragment(){
}
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
if(getArguments().containsKey(“name”)){
name=getArguments().getString(“name”);
}
if(getArguments().containsKey(“imageId”)){
imageId=getArguments().getInt(“imageId”);
}
}
@Override
publicViewonCreateView(LayoutInflaterinflater,
ViewGroupcontainer,BundlesavedInstanceState){
ViewrootView=inflater.inflate(
R.layout.fragment_details,container,false);
TextViewtextView=(TextView)
rootView.findViewById(R.id.text1);
textView.setText(name);
ImageViewimageView=(ImageView)rootView.findViewById(
R.id.imageView1);
imageView.setScaleType(ScaleType.FIT_XY);//stretchimage
imageView.setImageResource(imageId);
returnrootView;
}
}
Nowthatyouhavelookedat thefragments, takeacloselookat theactivity.ThelayoutfileisgiveninListing13.10.InsteadoftwofragmentelementslikeinFragmentDemo1,theactivity layoutfile inFragmentDemo2hasafragmentelementandaFrameLayout.Thelatteractsasthecontainerforthesecondfragment.
Listing13.10:Theactivity_main.xmlfile
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“horizontal”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<fragment
android:name=“com.example.fragmentdemo2.NamesListFragment”
android:id=”@+id/namesFragment”
android:layout_weight=“1”
android:layout_width=“0dp”
android:layout_height=“match_parent”/>
<FrameLayout
android:id=”@+id/details_container”
android:layout_width=“0dp”
android:layout_height=“match_parent”
android:layout_weight=“2.5”/>
</LinearLayout>
TheactivityclassforFragmentDemo2isgiveninListing13.11.LiketheactivityclassinFragmentDemo1,italsoimplementstheCallbackinterface.However,itsimplementationof the onItemSelected method is different. First, it passes two arguments to theDetailsFragment.Second,everytimeonItemSelectediscalled,anewDetailsFragmentinstanceiscreatedandpassedtotheFrameLayout.
Listing13.11:TheMainActivityclass
packagecom.example.fragmentdemo2;
importandroid.app.Activity;
importandroid.app.FragmentManager;
importandroid.app.FragmentTransaction;
importandroid.os.Bundle;
publicclassMainActivityextendsActivity
implementsNamesListFragment.Callback{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
publicvoidonItemSelected(Stringvalue){
Bundlearguments=newBundle();
arguments.putString(“name”,value);
if(value.equals(“Amsterdam”)){
arguments.putInt(“imageId”,R.drawable.amsterdam);
}elseif(value.equals(“Brussels”)){
arguments.putInt(“imageId”,R.drawable.brussels);
}elseif(value.equals(“Paris”)){
arguments.putInt(“imageId”,R.drawable.paris);
}
DetailsFragmentfragment=newDetailsFragment();
fragment.setArguments(arguments);
FragmentManagerfragmentManager=getFragmentManager();
FragmentTransactionfragmentTransaction=
fragmentManager.beginTransaction();
fragmentTransaction.replace(
R.id.details_container,fragment);
fragmentTransaction.commit();
}
}
Figure13.3showsFragmentDemo2.
Figure13.3:FragmentDemo2
SummaryFragments are components that can be added to an activity. A fragment has its ownlifecycle and has methods that get called when certain phases of its life occur. In thischapteryouhavelearnedtowriteyourownfragments.
Chapter14Multi-PaneLayouts
AnAndroidtabletgenerallyhasalargerscreenthanthatofahandset.Inmanycases,youmightwanttotakeadvantageofthebiggerscreenintabletstodisplaymoreinformationbyusingamulti-panelayout.
Thischapterdiscussesmulti-panelayoutsusingfragmentsthatyoulearnedinChapter13,“Fragments.”
OverviewA tablet has a larger screen than a handset and you can displaymore information on atabletthanonahandset.Ifyouarewritinganapplicationthatneedstolookgoodonbothtypesofdevices,acommonstrategyistosupporttwolayouts.Asingle-panelayoutcanbeusedforhandsetsandamulti-panelayoutfortablets.
Figure 14.1 shows a dual-pane version of an application and Figure 14.2 shows thesameapplicationinsingle-panemode.
Inasinglelayout,youwoulddisplayanactivitythatoftencontainsasinglefragment,which in turnoftencontainsaListView.Selectingan itemon theListViewwouldstartanotheractivity.
In amulti-pane layout, youwouldhave an activity that is big enough for twopanes.Youwould use the same fragment, but this timewhen an item is selected, it updates asecondfragmentinsteadofstartinganotheractivity.
Figure14.1:Dual-panelayout
Figure14.2:Single-panelayout
Thequestionis,howdoyoutellthesystemtopicktherightlayout?PriortoAndroid3.2(APIlevel13)ascreenmayfallintooneofthesecategoriesdependingonitssize:
small,forscreensthatareatleast426dpx320dpnormal,forscreensthatareatleast470dpx320dplarge,forscreensthatareatleast640dpx480dpxlarge,forscreensthatareatleast960dpx720dp
Here,dpstandsfordensityindependentpixel.Youcancalculatethenumberofpixels(px)fromthedpandthescreendensity(indotsperinchordpi)byusingthisformula.
px=dp*(dpi/160)
Tosupportascreencategory,youwouldplaceyourlayoutfilesinthefolderdedicatedtothat category, that is res/layout-small for small screens, res/layout for normal screens,res/layout-large for large screens, andres/layout-xlarge for xlarge screens.To supportboth normal and large screens, you would have layout files in both res/layout andres/layout-largedirectories.
The system is not without limitations, however. For example, a 7” tablet and a 10”tabletwouldbothfallintothexlargecategory,eventhoughtheyprovidedifferentamountsofspace.Toallowfordifferent layouts for7”and10” tablets,Android3.2changed theway it worked. Instead of the four screen sizes, Android 3.2 and later employ a newtechniquethatmeasuresthescreenbasedontheamountofspaceindp,ratherthantryingtomakethelayoutfitthegeneralizedsizegroups.
With the new system, it is easy to provide different layouts for tabletswith a 600dpscreenwidth(suchasinatypical7”tablet)andtabletswitha720dpscreenwidth(suchasinatypical10”tablet).Atypicalhandset,bytheway,hasa320dpscreenwidth.
Now, to support large screen devices for both pre-3.2 devices and later devices, youneedtostorelayoutfilesinbothres/layout-largeandres/layout-sw600dpdirectories.Inother words, for each layout you end up with three files (assuming your layout file iscalledmain.xml):
res/layout/main.xmlfornormalscreensres/layout-large/main.xml for devices running pre-3.2 Android having a largescreenres/layout-sw600dp/main.xml for devices running Android 3.2 or later having alargescreen
Inaddition,ifyourapplicationhasadifferentscreenfor10”tablets,youwillalsoneedares/layout-sw720dp/main.xmlfile.
Themain.xml files in the layout-large and layout-sw600dp directories are identicalandhavingduplicatesthatbothhavetobechangedifoneofthemwasupdatediscertainlyamaintenancenightmare.
To get around it, you can use references.With references, you only need two layoutfiles,one fornormalscreensandone for largescreens,both in theres/layout directory.Assumingthenamesofyourlayoutfilesaremain.xmlandmain_large.xml,toreferencethe latter, you need to have a refs.xml file in both res/values-large and res/values-sw600dp.Thecontentofrefs.xmlwouldbeasfollows.
<resources>
<itemname=“main”type=“layout”>@layout/main_large</item>
</resources>
Figure14.3showsthecontentoftheresdirectory.
Figure14.3:Thestructureoftheresdirectorythatsupportslayoutreferences
Thisway,youstillhavetwoidenticalfiles,therefs.xmlfileinthevalues-largedirectoryandtherefs.xmlfileinthevalues-sw600dpdirectory.However,thesearereferencefilesthatdonotneedtobeupdatedifthelayoutchanges.
AMulti-PaneExampleMultiPaneDemoisanapplicationthatsupportssmallandlargescreens.Forlargescreensitshowsanactivitythatusesamulti-panelayoutconsistingoftwofragments.Forsmallerscreens,anotheractivitywillbeshownthatcontainsonlyonefragment.
Theeasiestwaytocreateamulti-paneapplicationisbyusingAndroidStudio.Asusual,youwouldusetheNewAndroidApplicationwizardasdescribedinChapter1,“GettingStarted.”However,insteadofcreatingablankactivityasinChapter1,youshouldselectMaster/DetailFlow,asshowninFigure14.4.
Figure14.4:SelectingMaster/DetailFlowactivity
AfteryoureachthewindowinFigure14.4,clickNext.Inthewindowthatappearsnext(SeeFigure14.5),selectthenameforyouritem(s)andclickFinish.
Figure14.5:Choosingnamesfortheitems
AndroidStudio supportsmulti-pane/single-pane layouts by creating twoversions of thelayout file for the main activity. The single-pane version is stored in the res/layoutdirectory and the multi-pane version in the res/layout-sw600dp directory. When theapplication is launched, the main activity automatically selects the correct layout filedependingonthescreenresolution.
AndroidStudioalsocreatesamulti-paneapplicationthatsupportsAndroid3.0andlateraswellaspre-3.0Android.Ifyoudon’tneedtosupportolderdevices,however,youcanremovethesupportclasses.Theadvantageisyouwillhaveanapkfilethatisabout30KBlighter.
TheAndroidManifest.xmlfilefortheapplicationisgiveninListing14.1.
Listing14.1:TheAndroidManifest.xmlfile
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.multipanedemo”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“18”
android:targetSdkVersion=“18”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=”.ItemListActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<activity
android:name=”.ItemDetailActivity”
android:label=”@string/title_item_detail”
android:parentActivityName=”.ItemListActivity”>
<meta-data
android:name=“android.support.PARENT_ACTIVITY”
android:value=”.ItemListActivity”/>
</activity>
</application>
</manifest>
Theapplicationhastwoactivities.Themainactivityisusedinbothsingle-paneandmulti-paneenvironments.Thesecondactivityisusedinthesingle-paneenvironmentonly.
TheLayoutsandActivitiesAsyoucanseeinthemanifest,theItemListActivityclassistheactivityclassthatwillbeinstantiatedwhentheapplicationislaunched.ThisclassisshowninListing14.2.
Listing14.2:TheItemListActivityclass
packagecom.example.multipanedemo;
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.os.Bundle;
publicclassItemListActivityextendsActivity
implementsItemListFragment.Callbacks{
privatebooleantwoPane;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_item_list);
if(findViewById(R.id.item_detail_container)!=null){
twoPane=true;
//Intwo-panemode,listitemsshouldbegiventhe
//‘activated’statewhentouched.
((ItemListFragment)getFragmentManager()
.findFragmentById(R.id.item_list))
.setActivateOnItemClick(true);
}
}
/**
*Callbackmethodfrom{@linkItemListFragment.Callbacks}
*indicatingthattheitemwiththegivenIDwasselected.
*/
@Override
publicvoidonItemSelected(Stringid){
if(twoPane){
Bundlearguments=newBundle();
arguments.putString(ItemDetailFragment.ARG_ITEM_ID,id);
ItemDetailFragmentfragment=newItemDetailFragment();
fragment.setArguments(arguments);
getFragmentManager().beginTransaction()
.replace(R.id.item_detail_container,fragment)
.commit();
}else{
//Insingle-panemode,simplystartthedetailactivity
//fortheselecteditemID.
IntentdetailIntent=newIntent(this,ItemDetailActivity.class);
detailIntent.putExtra(ItemDetailFragment.ARG_ITEM_ID,id);
startActivity(detailIntent);
}
}
}
TheonCreatemethodinItemListActivityloadsthelayoutindicatedbylayoutidentifierR.layout.activity_item_list.
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_item_list);
…
Indeviceswithsmallerscreens, theres/layout/activity_item_list.xmlwillbe loaded. Indeviceswithlargerscreens,thesystemwilltrytolocatetheactivity_item_list.xmlfileineithertheres/layout-largeorres/layout-sw600dpdirectory.
The multi-pane activity_item_list.xml file in res/layout/sw600dp is used in deviceswithalargescreen.ThislayoutfileispresentedinListing14.3.
Listing14.3:Theres/layout-sw600dp/activity_item_list.xmlfile(multi-pane)
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_marginLeft=“16dp”
android:layout_marginRight=“16dp”
android:baselineAligned=“false”
android:divider=”?android:attr/dividerHorizontal”
android:orientation=“horizontal”
android:showDividers=“middle”
tools:context=”.ItemListActivity”>
<!—
Thislayoutisatwo-panelayoutfortheItems
master/detailflow.
—>
<fragmentandroid:id=”@+id/item_list”
android:name=“com.example.multipanedemo.ItemListFragment”
android:layout_width=“0dp”
android:layout_height=“match_parent”
android:layout_weight=“1”
tools:layout=”@android:layout/list_content”/>
<FrameLayoutandroid:id=”@+id/item_detail_container”
android:layout_width=“0dp”
android:layout_height=“match_parent”
android:layout_weight=“3”/>
</LinearLayout>
Theactivity_item_list.xml layoutfilefeaturesahorizontalLinearLayout thatsplitsthescreenintotwopanes.TheleftpaneconsistsofafragmentthatcontainsaListView.Theright pane contains a FrameLayout to which instances of another fragment calledItemDetailFragment can be added. Listing 14.4 shows the layout forItemDetailFragment.
Listing14.4:Thefragment_item_detail.xmlfile
<TextViewxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:id=”@+id/item_detail”
style=”?android:attr/textAppearanceLarge”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:padding=“16dp”
android:textIsSelectable=“true”
tools:context=”.ItemDetailFragment”/>
For smaller screens, two activities will be used. The main activity will load theactivity_item_list.xmllayoutfileinListing14.5.Thislayoutcontainsthesamefragmentusedbytheleftpaneinthemulti-panelayout.
Listing14.5:Theres/layout/activity_item_list.xmlfile(single-pane)
<fragmentxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:id=”@+id/item_list”
android:name=“com.example.multipanedemo.ItemListFragment”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_marginLeft=“16dp”
android:layout_marginRight=“16dp”
tools:context=”.ItemListActivity”
tools:layout=”@android:layout/list_content”/>
TheFragmentClassesThetwofragmentclassesaregiveninListing14.6andListing14.7,respectively.
Listing14.6:TheItemListFragmentclass
packagecom.example.multipanedemo;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.app.ListFragment;
importandroid.view.View;
importandroid.widget.ArrayAdapter;
importandroid.widget.ListView;
importcom.example.multipanedemo.dummy.DummyContent;
publicclassItemListFragmentextendsListFragment{
privatestaticfinalStringSTATE_ACTIVATED_POSITION=“activated_position”;
/**
*Thefragment’scurrentcallbackobject,whichisnotifiedof
*listitemclicks.
*/
privateCallbacksmCallbacks=sDummyCallbacks;
/**
*Thecurrentactivateditemposition.Onlyusedontablets.
*/
privateintmActivatedPosition=ListView.INVALID_POSITION;
/**
*Acallbackinterfacethatallactivitiescontainingthis
*fragmentmustimplement.Thismechanismallows
*activitiestobenotifiedofitemselections.
*/
publicinterfaceCallbacks{
/**
*Callbackforwhenanitemhasbeenselected.
*/
publicvoidonItemSelected(Stringid);
}
/**
*Adummyimplementationofthe{@linkCallbacks}interface
*thatdoesnothing.Usedonlywhenthisfragmentisnot
*attachedtoanactivity.
*/
privatestaticCallbackssDummyCallbacks=newCallbacks(){
@Override
publicvoidonItemSelected(Stringid){
}
};
/**
*Mandatoryemptyconstructorforthefragmentmanagerto
*instantiatethefragment(e.g.uponscreenorientation
*changes).
*/
publicItemListFragment(){
}
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
//TODO:replacewithareallistadapter.
setListAdapter(newArrayAdapter<DummyContent.DummyItem>(
getActivity(),
android.R.layout.simple_list_item_activated_1,
android.R.id.text1,
DummyContent.ITEMS));
}
@Override
publicvoidonViewCreated(Viewview,BundlesavedInstanceState){
super.onViewCreated(view,savedInstanceState);
//Restorethepreviouslyserializedactivateditem
//position.
if(savedInstanceState!=null
&&savedInstanceState.containsKey(
STATE_ACTIVATED_POSITION)){
setActivatedPosition(savedInstanceState.getInt(
STATE_ACTIVATED_POSITION));
}
}
@Override
publicvoidonAttach(Activityactivity){
super.onAttach(activity);
//Activitiescontainingthisfragmentmustimplementits
//callbacks.
if(!(activityinstanceofCallbacks)){
thrownewIllegalStateException(
“Activitymustimplementfragment’scallbacks.”);
}
mCallbacks=(Callbacks)activity;
}
@Override
publicvoidonDetach(){
super.onDetach();
//Resettheactivecallbacksinterfacetothedummy
//implementation.
mCallbacks=sDummyCallbacks;
}
@Override
publicvoidonListItemClick(ListViewlistView,Viewview,int
position,longid){
super.onListItemClick(listView,view,position,id);
//Notifytheactivecallbacksinterface(theactivity,if
//thefragmentisattachedtoone)thatanitemhasbeen
//selected.
mCallbacks.onItemSelected(DummyContent.ITEMS.get(
position).id);
}
@Override
publicvoidonSaveInstanceState(BundleoutState){
super.onSaveInstanceState(outState);
if(mActivatedPosition!=ListView.INVALID_POSITION){
//Serializeandpersisttheactivateditemposition.
outState.putInt(STATE_ACTIVATED_POSITION,
mActivatedPosition);
}
}
/**
*Turnsonactivate-on-clickmode.Whenthismodeison,list
*itemswillbe
*giventhe‘activated’statewhentouched.
*/
publicvoidsetActivateOnItemClick(booleanactivateOnItemClick){
//WhensettingCHOICE_MODE_SINGLE,ListViewwill
//automatically
//giveitemsthe‘activated’statewhentouched.
getListView().setChoiceMode(activateOnItemClick
?ListView.CHOICE_MODE_SINGLE
:ListView.CHOICE_MODE_NONE);
}
privatevoidsetActivatedPosition(intposition){
if(position==ListView.INVALID_POSITION){
getListView().setItemChecked(mActivatedPosition,false);
}else{
getListView().setItemChecked(position,true);
}
mActivatedPosition=position;
}
}
TheItemListFragment class extendsListFragment and gets the data for itsListViewfrom aDummyContent class. It also provides aCallbacks interface that any activityusingthisfragmentmustimplementtohandletheListItemClickeventoftheListView.IntheonAttachmethod,thefragmentmakessuretheactivityclassimplementsCallbacksand replaces the content ofmCallbackswith the activity, in effect delegating the eventhandlingtotheactivity.
Listing14.7:TheItemDetailFragmentclass
packagecom.example.multipanedemo;
importandroid.os.Bundle;
importandroid.app.Fragment;
importandroid.view.LayoutInflater;
importandroid.view.View;
importandroid.view.ViewGroup;
importandroid.widget.TextView;
importcom.example.multipanedemo.dummy.DummyContent;
/**
*AfragmentrepresentingasingleItemdetailscreen.
*Thisfragmentiseithercontainedina{@linkItemListActivity}
*intwo-panemode(ontablets)ora{@linkItemDetailActivity}
*onhandsets.
*/
publicclassItemDetailFragmentextendsFragment{
/**
*ThefragmentargumentrepresentingtheitemIDthatthis
*fragmentrepresents.
*/
publicstaticfinalStringARG_ITEM_ID=“item_id”;
/**
*Thedummycontentthisfragmentispresenting.
*/
privateDummyContent.DummyItemmItem;
/**
*Mandatoryemptyconstructorforthefragmentmanagerto
*instantiatethefragment(e.g.uponscreenorientation
*changes).
*/
publicItemDetailFragment(){
}
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
if(getArguments().containsKey(ARG_ITEM_ID)){
//Loadthedummycontentspecifiedbythefragment
//arguments.Inareal-worldscenario,useaLoader
//toloadcontentfromacontentprovider.
mItem=DummyContent.ITEM_MAP.get(
getArguments().getString(ARG_ITEM_ID));
}
}
@Override
publicViewonCreateView(LayoutInflaterinflater,ViewGroup
container,BundlesavedInstanceState){
ViewrootView=
inflater.inflate(R.layout.fragment_item_detail,
container,false);
//ShowthedummycontentastextinaTextView.
if(mItem!=null){
((TextView)rootView.findViewById(R.id.item_detail))
.setText(mItem.content);
}
returnrootView;
}
}
RunningtheApplicationFigure 14.6 and Figure 14.7 show the MultipaneDemo1 application on a tablet and ahandset,respectively.
Figure14.6:Multi-panelayoutonalargescreen
Figure14.7:Single-panelayoutonasmallscreen
SummaryTogiveyourusersthebestexperience,youmaywanttousedifferentlayoutsfordifferentscreensizes. In thischapter,you learned thatagoodstrategy toachieve that is touseamulti-panelayoutfortabletsandasingle-panelayoutforhandsets.
Chapter15Animation
Animation is an interesting feature in Android that has been available since the verybeginning (APILevel1). In this chapteryouwill learn touseanAnimationAPIcalledproperty animation, which was added to Honeycomb (API Level 11). The newAPI ismorepowerfulthanthepreviousanimationtechnologycalledviewanimation.Youshouldusepropertyanimationinnewprojects.
OverviewThePropertyAnimationAPIconsistsoftypesintheandroid.animationpackage.TheoldanimationAPI, called view animation, resides in theandroid.view.animation package.ThischapterfocusesonthenewanimationAPIanddoesnotdiscusstheoldertechnology.Italsodoesnotdiscussdrawableanimation,whichisthetypeofanimationthatworksbyloading a series of images, played one after another like a roll of film. For moreinformation on drawable animation, see the documentation forandroid.graphics.drawable.AnimationDrawable.
PropertyAnimationThepowerhousebehindpropertyanimationistheandroid.animation.Animatorclass.Itis an abstract class, so you do not use this class directly. Instead, you use one of itssubclasses, either ValueAnimator or ObjectAnimator, to create an animation. Inaddition, the AnimatorSet class, another subclass of Animator, is designed to runmultipleanimationsinparallelorsequentially.
Alltheseclassesresideinthesamepackageandthissectionlooksattheseclasses.
AnimatorTheAnimator class is an abstract class that provides methods that are inherited bysubclasses.There isamethod for setting the targetobject tobeanimated (setTarget),amethod for setting the duration (setDuration), and amethod for starting the animation(start).ThestartmethodcanbecalledmorethanonceonanAnimatorobject.
In addition, this class provides an addListener method that takes anAnimator.AnimatorListenerinstance.TheAnimatorListenerinterfaceisdefinedinsidetheAnimator class and provides methods that will be called by the system upon theoccurrence of certain events. You can implement any of thesemethods if youwant torespondtoacertainevent.
ThefollowingaremethodsinAnimatorListener.
voidonAnimationStart(Animatoranimation);
voidonAnimationEnd(Animatoranimation);
voidonAnimationCancel(Animatoranimation);
voidonAnimationRepeat(Animatoranimation);
Forexample,theonAnimationStartmethodiscalledwhentheanimationstartsandtheonAnimationEndmethodiscalledwhenitends.
ValueAnimatorAValueAnimatorcreatesananimationbycalculatingavaluethattransitionsfromastartvalueandtoanendvalue.YouspecifywhatthestartvalueandendvalueshouldbewhenconstructingtheValueAnimator.ByregisteringanUpdateListenertoaValueAnimator,youcanreceiveanupdateateachframe,givingyouachancetoupdateyourobject(s).
HerearetwostaticfactorymethodsthatyoucanusetoconstructaValueAnimator.
publicstaticValueAnimatorofFloat(float…values)
publicstaticValueAnimatorofInt(int…values)
Whichmethodyoushouldusedependsonwhetheryouwanttoreceiveanintorafloatineachframe.
Once you create a ValueAnimator, you should create an implementation ofAnimationUpdateListener, write your animation code under its onAnimationUpdatemethod,andregisterthelistenerwiththeValueAnimator.Hereisanexample.
valueAnimator.addUpdateListener(new
ValueAnimator.AnimatorUpdateListener(){
@Override
publicvoidonAnimationUpdate(ValueAnimatoranimation){
Floatvalue=(Float)animation.getAnimatedValue();
//usevaluetosetapropertyormultipleproperties
//Example:view.setRotationX(value);
}
});
Finally, call theValueAnimator’s setDuration method to set a duration and its startmethodtostarttheanimation.IfyoudonotcallsetDuration,thedefaultmethod(300ms)willbeused.
MoreonusingValueAnimatorisgivenintheexamplebelow.
ObjectAnimatorTheObjectAnimatorclassoffers theeasiestwaytoanimateanobject,mostprobablyaView, by continually updating one of its properties. To create an animation, create anObjectAnimator using one of its factory methods, passing a target object, a propertyname, and the start and end values for the property. In recognition of the fact that apropertycanhaveanintvalue,afloatvalue,oranothertypeofvalue,ObjectAnimatorprovidesthreestaticmethods:ofInt,ofFloat,andofObject.Herearetheirsignatures.
publicstaticObjectAnimatorofInt(java.lang.Objecttarget,
java.lang.StringpropertyName,int…values)
publicstaticObjectAnimatorofFloat(java.lang.Objecttarget,
java.lang.StringpropertyName,float…values)
publicstaticObjectAnimatorofObject(java.lang.Objecttarget,
java.lang.StringpropertyName,java.lang.Object…values)
Youcanpassoneortwoargumentstothevaluesargument.Ifyoupasstwoarguments,thefirstwillbeusedasthestartvalueandthesecondtheendvalue.Ifyoupassoneargument,thevaluewillbeusedastheendvalueandthecurrentvalueofthepropertywillbeusedasthestartvalue.
Once you have an ObjectAnimator, call the setDuration method on theObjectAnimatortosetthedurationandthestartmethodtostartit.HereisanexampleofanimatingtherotationpropertyofaView.
ObjectAnimatorobjectAnimator=ObjectAnimator.ofFloat(view,
“rotationY”,0F,720.0F);//rotate720degrees.
objectAnimator.setDuration(2000);//2000milliseconds
objectAnimator.start();
Runningtheanimationwillcausetheviewtomaketwofullcircleswithintwoseconds.
Asyoucansee,youjustneedtwoorthreelinesofcodetocreateapropertyanimationusing ObjectAnimator. You will learn more about ObjectAnimator in the examplebelow.
AnimatorSetAnAnimatorSet isuseful ifyouwant toplaya setof animations in a certainorder.Adirect subclass of Animator, the AnimatorSet class allows you to play multipleanimations together or one after another. Once you’re finished deciding how youranimationsshouldbecalled,callthestartmethodontheAnimatorSettostartit.
TheplayTogethermethodarrangesthesuppliedanimationstoplaytogether.Therearetwooverridesforthismethod.
publicvoidplayTogether(java.util.Collection<Animator>items)
publicvoidplayTogether(Animator…items)
TheplaySequentiallymethodarrangesthesuppliedanimationstoplaysequentially.Ittoohastwooverrides.
publicvoidplaySequentially(Animator…items)
publicvoidplaySequentially(java.util.List<Animator>items)
AnAnimationProjectThe AnimationDemo project uses the ValueAnimator, ObjectAnimator, andAnimatorSet to animate an ImageView. It provides three buttons to play differentanimations.
ThemanifestfortheapplicationisgiveninListing15.1.
Listing15.1:ThemanifestforAnimationDemo
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.animationdemo”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“11”
android:targetSdkVersion=“18”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.animationdemo.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
NotethattheminimumSDKlevelis11(Honeycomb).
Theapplicationhasoneactivity,whoselayoutisprintedinListing15.2
Listing15.2:Theactivity_main.xmlfile
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingBottom=”@dimen/activity_vertical_margin”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:orientation=“vertical”
tools:context=”.MainActivity”>
<LinearLayout
android:layout_width=“match_parent”
android:layout_height=“wrap_content”>
<Button
android:id=”@+id/button1”
android:text=”@string/button_animate1”
android:textColor=”#ff4433”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:onClick=“animate1”/>
<Button
android:id=”@+id/button2”
android:text=”@string/button_animate2”
android:textColor=”#33ff33”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:onClick=“animate2”/>
<Button
android:id=”@+id/button3”
android:text=”@string/button_animate3”
android:textColor=”#3398ff”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:onClick=“animate3”/>
</LinearLayout>
<ImageView
android:id=”@+id/imageView1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“top|center”
android:src=”@drawable/photo1”/>
</LinearLayout>
ThelayoutdefinesanImageViewandthreeButtons.
Finally,Listing15.3showstheMainActivityclassfortheapplication.Therearethreeevent-processingmethods(animate1,animate2,andanimate3)thateachusesadifferentanimationmethod.
Listing15.3:TheMainActivityclass
packagecom.example.animationdemo;
importandroid.animation.AnimatorSet;
importandroid.animation.ObjectAnimator;
importandroid.animation.ValueAnimator;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.View;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
publicvoidanimate1(Viewsource){
Viewview=findViewById(R.id.imageView1);
ObjectAnimatorobjectAnimator=ObjectAnimator.ofFloat(
view,“rotationY”,0F,720.0F);
objectAnimator.setDuration(2000);
objectAnimator.start();
}
publicvoidanimate2(Viewsource){
finalViewview=findViewById(R.id.imageView1);
ValueAnimatorvalueAnimator=ValueAnimator.ofFloat(0F,
7200F);
valueAnimator.setDuration(15000);
valueAnimator.addUpdateListener(new
ValueAnimator.AnimatorUpdateListener(){
@Override
publicvoidonAnimationUpdate(ValueAnimatoranimation){
Floatvalue=(Float)animation.getAnimatedValue();
view.setRotationX(value);
if(value<3600){
view.setTranslationX(value/20);
view.setTranslationY(value/20);
}else{
view.setTranslationX((7200-value)/20);
view.setTranslationY((7200-value)/20);
}
}
});
valueAnimator.start();
}
publicvoidanimate3(Viewsource){
Viewview=findViewById(R.id.imageView1);
ObjectAnimatorobjectAnimator1=
ObjectAnimator.ofFloat(view,“translationY”,0F,
300.0F);
ObjectAnimatorobjectAnimator2=
ObjectAnimator.ofFloat(view,“translationX”,0F,
300.0F);
objectAnimator1.setDuration(2000);
objectAnimator2.setDuration(2000);
AnimatorSetanimatorSet=newAnimatorSet();
animatorSet.playTogether(objectAnimator1,objectAnimator2);
ObjectAnimatorobjectAnimator3=
ObjectAnimator.ofFloat(view,“rotation”,0F,
1440F);
objectAnimator3.setDuration(4000);
animatorSet.play(objectAnimator3).after(objectAnimator2);
animatorSet.start();
}
}
Run theapplicationandclick thebuttons toplay theanimations.Figure15.1shows theapplication.
Figure15.1:Animationdemo
SummaryIn this chapter you learned about the new Animation API in Android, the PropertyAnimation system. In particular, you learned about the android.animation.Animatorclassand its subclasses,ValueAnimatorandObjectAnimator.Youalso learned tousetheAnimatorSetclasstoperformmultipleanimations.
Chapter16Preferences
Android comes with a SharedPreferences interface that can be used to manageapplicationsettingsaskey/valuepairs.SharedPreferencesalsotakescareofthewritingofdatatoafile.Inaddition,AndroidprovidesthePreferenceAPIwithuserinterface(UI)classes thatare linked to thedefaultSharedPreferences instance so thatyoucaneasilycreateaUIformodifyingapplicationsettings.
ThischapterdiscussesSharedPreferencesandthePreferenceAPIindetail.
SharedPreferencesThe android.content.SharedPreferences interface provides methods for storing andreadingapplicationsettings.YoucanobtainthedefaultinstanceofSharedPreferencesbycallingthegetDefaultSharedPreferencesstaticmethodofPreferenceManager,passingaContext.
PreferenceManager.getDefaultSharedPreferences(context);
ToreadavaluefromtheSharedPreferences,useoneofthefollowingmethods.
publicintgetInt(java.lang.Stringkey,intdefault)
publicbooleangetBoolean(java.lang.Stringkey,booleandefault)
publicfloatgetFloat(java.lang.Stringkey,floatdefault)
publiclonggetLong(java.lang.Stringkey,longdefault)
publicintgetString(java.lang.Stringkey,java.lang.Stringdefault)
publicjava.util.Set<java.lang.String>getStringSet(
java.lang.Stringkey,java.util.Set<java.lang.String>default)
ThegetXXXmethodsreturnthevalueassociatedwiththespecifiedkeyifthepairexists.Otherwise,itreturnsthespecifieddefaultvalue.
To first check if a SharedPreferences contains a key/value pair, use the containsmethod,whichreturnstrueifthespecifiedkeyexists.
publicbooleancontains(java.lang.Stringkey)
Ontopofthat,youcanusethegetAllmethodtogetallkey-valuepairsasaMap.
publicjava.util.Map<java.lang.String,?>getAll()
Values stored in aSharedPreferences are persisted automatically andwill survive usersessions.Thevalueswillbedeletedwhentheapplicationisuninstalled.
ThePreferenceAPITo store a key-value pair in a SharedPreferences, you normally use the AndroidPreference API to create a user interface that the user can use to edit settings. The
android.preference.Preferenceclassisthemainclassforthis.Someofitssubclassesare
CheckBoxPreferenceEditTextPreferenceListPreferenceDialogPreference
AninstanceofaPreferencesubclasscorrespondstoasetting.
YoucouldcreateaPreferenceatruntime,butthebestwaytocreateoneisbyusinganXML file to lay out your preferences and then use aPreferenceFragment to load theXMLfile.TheXMLfilemusthaveaPreferenceScreen rootelementand iscommonlynamedpreferences.xmlandshouldbesavedtoanxmldirectoryunderres.
NotePriortoAndroid3.0,thePreferenceActivitywasoftenusedtoloadapreferencexmlfile.Thisclassisnowdeprecatedandshouldnotbeused.UsePreferenceFragment,instead.
YouwilllearnhowtousePreferenceinthefollowingexample.
UsingPreferencesThePreferenceDemo1application showsyouhowyou canuseSharedPreferences andthe Preference API. It has two activities. The first activity shows the values of threeapplication settings by reading themwhen the activity is resumed. The second activitycontainsaPreferenceFragmentthatallowstheusertochangeeachofthesettings.
Figures16.1and16.2showthemainactivityandthesecondactivity,respectively.
Figure16.1:ThemainactivityofPreferenceDemo1
Figure16.2:TheSettingsActivityactivity
TheAndroidManifest.xml filefor theapplication,whichdescribes the twoactivities, isshowninListing16.1.
Listing16.1:TheAndroidManifest.xmlfile
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.preferencedemo1”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“19”
android:targetSdkVersion=“19”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.preferencedemo1.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<activity
android:name=“com.example.preferencedemo1.SettingsActivity”
android:parentActivityName=”.MainActivity”
android:label=””>
</activity>
</application>
</manifest>
ThefirstactivityhasaverysimplelayoutthatfeaturesasoleTextViewasispresentedbytheactivity_main.xmlfileinListing16.2.
Listing16.2:Thelayoutfileforthefirstactivity(activity_main.xml)
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingBottom=”@dimen/activity_vertical_margin”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”>
<TextView
android:id=”@+id/info”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:textSize=“30sp”/>
</RelativeLayout>
TheMainActivityclass,showninListing16.3,istheactivityclassforthefirstactivity.Itreads three settings from the default SharedPreferences in its onResume method anddisplaysthevaluesintheTextView.
Listing16.3:TheMainActivityclass
packagecom.example.preferencedemo1;
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.content.SharedPreferences;
importandroid.os.Bundle;
importandroid.preference.PreferenceManager;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.widget.TextView;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
publicvoidonResume(){
super.onResume();
SharedPreferencessharedPref=PreferenceManager.
getDefaultSharedPreferences(this);
booleanallowMultipleUsers=sharedPref.getBoolean(
SettingsActivity.ALLOW_MULTIPLE_USERS,false);
StringenvId=sharedPref.getString(
SettingsActivity.ENVIRONMENT_ID,””);
Stringaccount=sharedPref.getString(
SettingsActivity.ACCOUNT,””);
TextViewtextView=(TextView)findViewById(R.id.info);
textView.setText(“Allowmultipleusers:”+
allowMultipleUsers+”\nEnvironmentId:”+envId
+”\nAccount:”+account);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
switch(item.getItemId()){
caseR.id.action_settings:
startActivity(newIntent(this,
SettingsActivity.class));
returntrue;
default:
returnsuper.onOptionsItemSelected(item);
}
}
}
In addition, the MainActivity class overrides the onCreateOptionsMenu andonOptionsItemSelectedmethodssothataSettingsactionappearsontheactionbarandclickingitwillstartthesecondactivity,SettingsActivity.
SettingsActivity,presentedinListing16.4,containsadefaultlayoutthatisreplacedbyan instance of SettingsFragment when the activity is created. Pay attention to theonCreatemethodoftheclass.Ifthelastlinesofcodeinthemethodlooksforeigntoyou,pleasefirstreadChapter13,“Fragments.”
Listing16.4:TheSettingsActivityclass
packagecom.example.preferencedemo1;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.Menu;
publicclassSettingsActivityextendsActivity{
publicstaticfinalStringALLOW_MULTIPLE_USERS=
“allowMultipleUsers”;
publicstaticfinalStringENVIRONMENT_ID=“envId”;
publicstaticfinalStringACCOUNT=“account”;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
getActionBar().setDisplayHomeAsUpEnabled(true);
getFragmentManager()
.beginTransaction()
.replace(android.R.id.content,
newSettingsFragment()).commit();
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_settings,menu);
returntrue;
}
}
Note that theSettingsActivityclassdeclares threepublicstatic finals thatdefinesettingkeys.Thefieldsareusedinternallyaswellasfromotherclasses.
TheSettingsFragmentclassisasubclassofPreferenceFragment.Itisasimpleclassthat simplycallsaddPreferencesFromResource to load theXMLdocument containingthe layout for three Preference subclasses. The SettingsFragment class is shown inListing16.5andtheXMLfileinListing16.6.
Listing16.5:TheSettingsFragmentclass
packagecom.example.preferencedemo1;
importandroid.os.Bundle;
importandroid.preference.PreferenceFragment;
publicclassSettingsFragmentextendsPreferenceFragment{
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
//LoadthepreferencesfromanXMLresource
addPreferencesFromResource(R.xml.preferences);
}
}
Listing16.6:Theres/xml/preferences.xmlfile
<PreferenceScreen
xmlns:android=“http://schemas.android.com/apk/res/android”>
<PreferenceCategoryandroid:title=“Category1”>
<CheckBoxPreference
android:key=“allowMultipleUsers”
android:title=“Allowmultipleusers”
android:summary=“Allowmultipleusers”/>
</PreferenceCategory>
<PreferenceCategoryandroid:title=“Category2”>
<EditTextPreference
android:key=“envId”
android:title=“EnvironmentId”
android:dialogTitle=“EnvironmentId”/>
<EditTextPreference
android:key=“account”
android:title=“Account”/>
</PreferenceCategory>
</PreferenceScreen>
Thepreferences.xmlfilegroupsthePreferencesubclassesintotwocategories.InthefirstcategoryisaCheckBoxPreferencelinkedtotheallowMultipleUserskey.InthesecondcategoryaretwoEditTextPreferenceslinkedtoenvIdandaccount.
SummaryAneasywaytomanageapplicationsettingsisbyusingthePreferenceAPIandthedefaultSharedPreferences.Inthischapteryoulearnedhowtouseboth.
Chapter17WorkingwithFiles
Readingfromandwritingtoafilearesomeofthemostcommonoperationsinanytypeofapplication,includingAndroid.Inthischapter,youwilllearnhowAndroidstructuresitsstorageareasandhowtousetheAndroidFileAPI.
OverviewAndroid devices offer two storage areas, internal and external. The internal storage isprivatetotheapplication.Theuserandotherapplicationscannotaccessit.
Theexternalstorageiswhereyoustorefilesthatwillbesharedwithotherapplicationsorthattheuserwillbeabletoaccess.Forexample,thebuilt-inCameraapplicationstoresdigitalimagefilesintheexternalstoragesotheusercaneasilycopythemtoacomputer.
InternalStorageAllapplicationscanreadfromandwrite to internalstorage.The locationof the internalstorage is /data/data/[app package], so if your application package iscom.example.myapp, the internal directory for this application is/data/data/com.example.myapp.TheContextclassprovidesvariousmethodstoaccessthe internalstorage fromyourapplication.Youshoulduse thesemethods toaccess filesyou store in the internal storage and should not hardcode the location of the internalstorage. (Recall that Activity is a subclass of Context, so you can call public andprotectedmethodsinContextfromyouractivityclass).HerearethemethodsinContextforworkingwithfilesandstreamsintheinternalstorage.
publicjava.io.FilegetFilesDir()
Returnsthepathtothedirectorydedicatedtoyourapplicationininternalstorage.
publicjava.io.FileOutputStreamopenFileOutput(
java.lang.Stringname,intmode)
OpensaFileOutputStreamintheapplication’ssectionofinternalstorage.
publicjava.io.FileInputStreamopenFileInput(java.lang.Stringname)
OpensaFileInputStreamforreading.Thenameargumentisthenameofthefiletoopenandcannotcontainpathseparators.
publicjava.io.FilegetFilesDir()
Obtainstheabsolutepathtothefilesystemdirectorywhereinternalfilesaresaved.
publicjava.io.FilegetDir(java.lang.Stringname,intmode)
Creates or retrieves an existing directory within the application’s internal storagespace. Thename argument is the name of the directory to retrieve and themodeargumentshouldbegivenoneofthese:MODE_PRIVATEforthedefaultoperationorMODE_WORLD_READABLEorMODE_WORLD_WRITEABLEtocontrolpermissions.
publicbooleandeleteFile(java.lang.StringfileName)
Deletesafilesavedontheinternalstorage.Themethodreturnstrue if thefilewassuccessfullydeleted.
publicjava.lang.String[]FileList()
ReturnsanarrayofstringsnamingthefilesassociatedwiththisContext’sapplicationpackage.
ExternalStorageTherearetwotypesoffilesthatcanbewrittentoexternalstorage,privatefilesandpublicfiles.Privatefilesareprivatetotheapplicationandwillbedeletedwhentheapplicationisuninstalled.Publicfiles,ontheotherhand,aremeanttobesharedwithotherapplicationsoraccessibletotheuser.
Externalstoragemayberemovable.Assuch,thereisadifferencebetweenfilesstoredin internal storage and files stored in external storage as public files. Files in internalstoragearesecureandcannotbeaccessedbytheuserorotherapplications.Publicfilesinexternalstoragedonotenjoythesamelevelofsecurityastheusercanremovethestorageandusesometooltoaccessthefiles.
Sinceexternal storagecanbe removed,whenyou try to read fromorwrite to it,youshouldfirsttestifexternalstorageisavailable.Tryingtoaccessexternalstoragewhenitisunavailablemaycrashyourapplication.
Toinquireifexternalstorageisavailable,useoneofthesemethods.
publicbooleanisExternalStorageWritable(){
Stringstate=Environment.getExternalStorageState();
returnEnvironment.MEDIA_MOUNTED.equals(state);
}
publicbooleanisExternalStorageReadable(){
Stringstate=Environment.getExternalStorageState();
return(Environment.MEDIA_MOUNTED.equals(state)||
Environment.MEDIA_MOUNTED_READ_ONLY.equals(state));
}
You can use the getExternalFilesDir method on theContext to get the directory forstoringprivatefilesontheexternalstorage.
Public files can be stored in the directory returned by thegetExternalStoragePublicDirectorymethodoftheandroid.os.Environmentclass.Hereisthesignatureofthismethod.
publicstaticjava.io.FilegetExternalStoragePublicDirectory(
java.lang.Stringtype)
Here, type is a directory under the root directory. TheEnvironment class provides thefollowingfieldsthatyoucanuseforvariousfiletypes.
Directory_ALARMSDirectory_DCIMDirectory_DOCUMENTSDirectory_DOWNLOADSDirectory_MOVIESDirectory_MUSICDirectory_NOTIFICATIONSDirectory_PICTURESDirectory_PODCASTSDirectory_RINGTONES
Forexample,musicfilesshouldbestoredinthedirectoryreturnedbythiscode.
Filedir=Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES)
Writingtoexternalstoragerequiresuserpermission.Toasktheusertograntyoureadandwriteaccesstoexternalstorage,addthisinyourmanifest.
<uses-permission
android:name=“android.permission.WRITE_EXTERNAL_STORAGE”/>
Currently, ifyourapplicationonlyneeds to readfromexternalstorage,youdonotneedspecial permissions.However, thiswill change in the future so you should declare thisuses-permission element in yourmanifest if youneed to read fromexternal storage sothatyourapplicationwillkeepworkingafterthechangetakeseffect.
<uses-permission
android:name=“android.permission.READ_EXTERNAL_STORAGE”/>
CreatingaNotesApplicationTheFileDemo1applicationisasimpleapplicationformanagingnotes.Anotehasatitleandabodyandeachnoteisstoredasafile,usingthetitleasthefilename.Theusercanviewalistofnotes,viewanote,createanewnote,anddeleteanote.
The application has two activities, MainActivity and AddNoteActivity. TheMainActivity activity uses aListView that lists all note titles in the system.Themain
activity contains a ListView that lists all note titles. Selecting a note title from theListViewshowsthenoteintheTextViewbesidetheListView.
Figures17.1and17.2showtheMainActivity activity andAddNoteActivityactivity,respectively.TheMainActivityactivitycontainsaListViewthatlistsallnotetitlesandaTextView that shows the body of the selected note. Its action bar also contains twobuttons, Add and Delete. Add starts theAddNoteActivity activity. Delete deletes theselectednote.
Figure17.1:MainActivity
Figure17.2:AddNoteActivity
Let’s now take a look at the application code. Listing 17.1 shows theAndroidManifest.xmlfileforthisapplication.
Listing17.1:TheAndroidManifest.xmlfile
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.filedemo1”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“19”
android:targetSdkVersion=“19”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.filedemo1.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<activity
android:name=“com.example.filedemo1.AddNoteActivity”
android:label=”@string/title_activity_add_note”>
</activity>
</application>
</manifest>
Themanifestdeclaresthetwoactivitiesintheapplication.Theactivityclassofthemainactivity,MainActivity,isshowninListing17.2.
Listing17.2:TheMainActivityclass
packagecom.example.filedemo1;
importjava.io.BufferedReader;
importjava.io.File;
importjava.io.FileReader;
importjava.io.IOException;
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.widget.AdapterView;
importandroid.widget.AdapterView.OnItemClickListener;
importandroid.widget.ArrayAdapter;
importandroid.widget.ListView;
importandroid.widget.TextView;
publicclassMainActivityextendsActivity{
privateStringselectedItem;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListViewlistView=(ListView)findViewById(
R.id.listView1);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
listView.setOnItemClickListener(
newOnItemClickListener(){
@Override
publicvoidonItemClick(AdapterView<?>adapterView,
Viewview,intposition,longid){
readNote(position);
}
});
}
@Override
publicvoidonResume(){
super.onResume();
refreshList();
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
//Handlepressesontheactionbaritems
switch(item.getItemId()){
caseR.id.action_add:
startActivity(newIntent(this,
AddNoteActivity.class));
returntrue;
caseR.id.action_delete:
deleteNote();
returntrue;
default:
returnsuper.onOptionsItemSelected(item);
}
}
privatevoidrefreshList(){
ListViewlistView=(ListView)findViewById(
R.id.listView1);
String[]titles=fileList();
ArrayAdapter<String>arrayAdapter=
newArrayAdapter<String>(
this,
android.R.layout.simple_list_item_activated_1,
titles);
listView.setAdapter(arrayAdapter);
}
privatevoidreadNote(intposition){
String[]titles=fileList();
if(titles.length>position){
selectedItem=titles[position];
Filedir=getFilesDir();
Filefile=newFile(dir,selectedItem);
FileReaderfileReader=null;
BufferedReaderbufferedReader=null;
try{
fileReader=newFileReader(file);
bufferedReader=newBufferedReader(fileReader);
StringBuildersb=newStringBuilder();
Stringline=bufferedReader.readLine();
while(line!=null){
sb.append(line);
line=bufferedReader.readLine();
}
((TextView)findViewById(R.id.textView1)).
setText(sb.toString());
}catch(IOExceptione){
}finally{
if(bufferedReader!=null){
try{
bufferedReader.close();
}catch(IOExceptione){
}
}
if(fileReader!=null){
try{
fileReader.close();
}catch(IOExceptione){
}
}
}
}
}
privatevoiddeleteNote(){
if(selectedItem!=null){
deleteFile(selectedItem);
selectedItem=null;
((TextView)findViewById(R.id.textView1)).setText(””);
refreshList();
}
}
}
TheMainActivityclasscontainsaListViewanditsonCreatemethodsetstheListView’schoicemodeandpassesalistenertoit.
ListViewlistView=(ListView)findViewById(
R.id.listView1);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
listView.setOnItemClickListener(…)
NolistadapterispassedtotheListViewintheonCreatemethod.Instead,theonResumemethodcallstherefreshNotesmethod,whichpassesanewListAdaptertotheListVieweverytimeonResumeiscalled.ThereasonwhyanewListAdapterneedstobecreatedeverytimeonResumeiscalledisbecausethemainactivitycancalltheAddNoteActivityfortheusertoaddanote.IftheuserdoesaddanoteandleavetheAddNoteActivity,themainactivityneedstoincludethenewnote,hencetheneedtorefreshtheListView.
NoteAutomatic refresh in a ListView can be done using a Cursor. See Chapter 18,
“WorkingwiththeDatabase.”
ThereadNotemethod,whichgetscalledwhenalistitemisselected,startsbygettingallfilenamesintheinternalstorage.
String[]titles=fileList();
It thenretrievesthenote titleanduses it tocreateafile,usingthedirectoryreturnedbygetFilesDirastheparent.
if(titles.length>position){
selectedItem=titles[position];
Filedir=getFilesDir();
Filefile=newFile(dir,selectedItem);
ThereadNotemethod thenuses aFileReader andaBufferedReader to read thenote,onelineatatime,andsetsthevalueoftheTextView.
FileReaderfileReader=null;
BufferedReaderbufferedReader=null;
try{
fileReader=newFileReader(file);
bufferedReader=newBufferedReader(fileReader);
StringBuildersb=newStringBuilder();
Stringline=bufferedReader.readLine();
while(line!=null){
sb.append(line);
line=bufferedReader.readLine();
}
((TextView)findViewById(R.id.textView1)).
setText(sb.toString());
TheAddNoteActivityclassisshowninListing17.3.
Listing17.3:TheAddNoteActivityclass
packagecom.example.filedemo1;
importjava.io.File;
importjava.io.PrintWriter;
importandroid.app.Activity;
importandroid.app.AlertDialog;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.widget.EditText;
publicclassAddNoteActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_note);
}
publicvoidcancel(Viewview){
finish();
}
publicvoidaddNote(Viewview){
StringfileName=((EditText)
findViewById(R.id.noteTitle))
.getText().toString();
Stringbody=((EditText)findViewById(R.id.noteBody))
.getText().toString();
Fileparent=getFilesDir();
Filefile=newFile(parent,fileName);
PrintWriterwriter=null;
try{
writer=newPrintWriter(file);
writer.write(body);
finish();
}catch(Exceptione){
showAlertDialog(“Erroraddingnote”,e.getMessage());
}finally{
if(writer!=null){
try{
writer.close();
}catch(Exceptione){
}
}
}
}
privatevoidshowAlertDialog(Stringtitle,Stringmessage){
AlertDialogalertDialog=new
AlertDialog.Builder(this).create();
alertDialog.setTitle(title);
alertDialog.setMessage(message);
alertDialog.show();
}
}
TheAddNoteActivityclasshastwopublicmethodsthatactasclicklistenerstothetwobuttons in its layout,cancelandaddNote.Thecancelmethodsimplycloses theactivity.TheaddNotemethodreads thevalues in theTextViewsandcreatea file in the internalstorageusingaPrintWriter.
AccessingthePublicStorageThe second example in this chapter, FileDemo2, shows how you can access the publicstorage.FileDemo2isafilebrowserthatshowsthecontentofastandarddirectory.ThereisonlyoneactivityinFileDemo2anditisshowninFigure17.3.
Figure17.3:FileDemo2
The layout file for the activity is presented in Listing 17.4. It is aLinearLayout thatcontainstwoListViews.TheListViewontheleftlistsseveralfrequentlyuseddirectories.TheListViewontherightshowsthecontentoftheselecteddirectory.
Listing17.4:ThelayoutfileforFileDemo2’sactivity
<LinearLayoutxmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:orientation=“horizontal”>
<ListView
android:id=”@+id/listView1”
android:layout_width=“0sp”
android:layout_weight=“1”
android:layout_height=“match_parent”
android:background=”#ababff”/>
<ListView
android:id=”@+id/listView2”
android:layout_width=“0sp”
android:layout_height=“wrap_content”
android:layout_weight=“2”/>
</LinearLayout>
TheactivityclassforFileDemo2isshowninListing17.5.
Listing17.5:TheMainActivityclass
packagecom.example.filedemo2;
importjava.io.File;
importjava.util.Arrays;
importjava.util.List;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.os.Environment;
importandroid.view.Menu;
importandroid.view.View;
importandroid.widget.AdapterView;
importandroid.widget.AdapterView.OnItemClickListener;
importandroid.widget.ArrayAdapter;
importandroid.widget.ListView;
publicclassMainActivityextendsActivity{
classKeyValue{
publicStringkey;
publicStringvalue;
publicKeyValue(Stringkey,Stringvalue){
this.key=key;
this.value=value;
}
@Override
publicStringtoString(){
returnkey;
}
}
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
finalList<KeyValue>keyValues=Arrays.asList(
newKeyValue(“Alarms”,Environment.DIRECTORY_ALARMS),
newKeyValue(“DCIM”,Environment.DIRECTORY_DCIM),
newKeyValue(“Downloads”,
Environment.DIRECTORY_DOWNLOADS),
newKeyValue(“Movies”,Environment.DIRECTORY_MOVIES),
newKeyValue(“Music”,Environment.DIRECTORY_MUSIC),
newKeyValue(“Notifications”,
Environment.DIRECTORY_NOTIFICATIONS),
newKeyValue(“Pictures”,
Environment.DIRECTORY_PICTURES),
newKeyValue(“Podcasts”,
Environment.DIRECTORY_PODCASTS),
newKeyValue(“Ringtones”,
Environment.DIRECTORY_RINGTONES)
);
ArrayAdapter<KeyValue>arrayAdapter=new
ArrayAdapter<KeyValue>(this,
android.R.layout.simple_list_item_activated_1,
keyValues);
ListViewlistView1=(ListView)
findViewById(R.id.listView1);
listView1.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
listView1.setAdapter(arrayAdapter);
listView1.setOnItemClickListener(new
OnItemClickListener(){
@Override
publicvoidonItemClick(AdapterView<?>adapterView,
Viewview,intposition,longid){
KeyValuekeyValue=keyValues.get(position);
listDir(keyValue.value);
}
});
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
privatevoidlistDir(Stringdir){
Fileparent=Environment
.getExternalStoragePublicDirectory(dir);
String[]files=null;
if(parent==null||parent.list()==null){
files=newString[0];
}else{
files=parent.list();
}
ArrayAdapter<String>arrayAdapter=new
ArrayAdapter<String>(this,
android.R.layout.simple_list_item_activated_1,
files);
ListViewlistView2=(ListView)
findViewById(R.id.listView2);
listView2.setAdapter(arrayAdapter);
}
}
ThefirstthingtonoteistheKeyValueclassintheactivityclass.Thisisasimpleclasstohold a pair of strings. It is used in the onCreate method to pair selected keys withdirectoriesdefinedintheEnvironmentclass.
newKeyValue(“Alarms”,Environment.DIRECTORY_ALARMS),
newKeyValue(“DCIM”,Environment.DIRECTORY_DCIM),
newKeyValue(“Downloads”,
Environment.DIRECTORY_DOWNLOADS),
newKeyValue(“Movies”,Environment.DIRECTORY_MOVIES),
newKeyValue(“Music”,Environment.DIRECTORY_MUSIC),
newKeyValue(“Notifications”,
Environment.DIRECTORY_NOTIFICATIONS),
newKeyValue(“Pictures”,
Environment.DIRECTORY_PICTURES),
newKeyValue(“Podcasts”,
Environment.DIRECTORY_PODCASTS),
newKeyValue(“Ringtones”,
Environment.DIRECTORY_RINGTONES)
TheseKeyValueinstancesarethenusedtofeedthefirstListView.
ArrayAdapter<KeyValue>arrayAdapter=new
ArrayAdapter<KeyValue>(this,
android.R.layout.simple_list_item_activated_1,
keyValues);
ListViewlistView1=(ListView)
findViewById(R.id.listView1);
listView1.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
listView1.setAdapter(arrayAdapter);
TheListView also gets a listener that listens for itsOnItemClick event and calls thelistDirmethodwhenoneofthedirectoriesintheListViewisselected.
listView1.setOnItemClickListener(new
OnItemClickListener(){
@Override
publicvoidonItemClick(AdapterView<?>adapterView,
Viewview,intposition,longid){
KeyValuekeyValue=keyValues.get(position);
listDir(keyValue.value);
}
});
The listDir method list all files in the selected directory and feed them to anArrayAdapterthatinturngetspassedtothesecondListView.
privatevoidlistDir(Stringdir){
Fileparent=Environment
.getExternalStoragePublicDirectory(dir);
String[]files=null;
if(parent==null||parent.list()==null){
files=newString[0];
}else{
files=parent.list();
}
ArrayAdapter<String>arrayAdapter=new
ArrayAdapter<String>(this,
android.R.layout.simple_list_item_activated_1,
files);
ListViewlistView2=(ListView)
findViewById(R.id.listView2);
listView2.setAdapter(arrayAdapter);
}
SummaryYouusetheFileAPItoworkwithfilesinAndroidapplications.Inadditiontomasteringthis API, in order to work with files effectively in Android, you need to know howAndroidstructuresitsstoragesystemandthefile-relatedmethodsdefinedintheContextandEnvironmentclasses.
Chapter18WorkingwiththeDatabase
AndroidhasitsowntechnologyforworkingwithdatabasesandithasnothingtodowithJava Database Connectivity (JDBC), the technology Java developers use for accessingdata in a relational database. In addition, Android ships with SQLite, an open sourcedatabase.
This chapter shows how to work with the Android Database API and the SQLitedatabase.
OverviewAndroid comes with its own Database API. The API consists of two packages,android.database and android.database.sqlite. Android ships with SQLite, an opensourcerelationaldatabasethatpartiallyimplementsSQL-92,thethirdrevisionoftheSQLstandard.
Currentlyatversion3,SQLiteoffersaminimumnumberofdata types: Integer,Real,Text,Blob,andNumeric.OneinterestingfeatureofSQLiteisthatanintegerprimarykeyisautomaticallyauto-incrementedwhenarowisinsertedwithoutpassingavalueforthefield.
MoreinformationonSQLitecanbefoundhere:
http://sqlite.org
TheDatabaseAPIThe SQLiteDatabase and SQLiteOpenHelper classes, both part ofandroid.database.sqlite,arethetwomostfrequentlyusedclassesintheDatabaseAPI.Intheandroid.databasepackage,theCursorinterfaceisoneofthemostimportanttypes.
Thethreetypesarediscussedindetailinthefollowingsubsections.
TheSQLiteOpenHelperClassTouseadatabase inyourAndroidapplication,extendSQLiteOpenHelper tohelpwithdatabase and table creation as well as connecting to the database. In a subclass ofSQLiteOpenHelper,youneedtodothefollowing.
Provideaconstructorthatcallsitssuper,passing,amongothers,theContextandthedatabasename.OverridetheonCreateandonUpgrademethods.
Forexample,hereisaconstructorforasubclassofSQLiteOpenHelper.
publicSubClassOfSQLiteOpenHelper(Contextcontext){
super(context,
“mydatabase”,//databasename
null,
1//dbversion
);
}
TheonCreatemethodthatneedstobeoverriddenhasthefollowingsignature.
publicvoidonCreate(SQLiteDatabasedatabase)
ThesystemwillcallonCreatethefirsttimeaccesstooneofthetablesisrequired.InthismethodimplementationyoushouldcalltheexecSQLmethodontheSQLiteDatabaseandpassanSQLstatementforcreatingyourtable(s).Hereisanexample.
@Override
publicvoidonCreate(SQLiteDatabasedb){
Stringsql=“CREATETABLE”+TABLE_NAME
+”(“+ID_FIELD+”INTEGER,”
+FIRST_NAME_FIELD+”TEXT,”
+LAST_NAME_FIELD+”TEXT,”
+PHONE_FIELD+”TEXT,”
+EMAIL_FIELD+”TEXT,”
+”PRIMARYKEY(“+ID_FIELD+”));”;
db.execSQL(sql);
}
SQLiteOpenHelper automaticallymanages connections to the underlying database. Toretrievethedatabaseinstance,calloneofthesemethods,bothofwhichreturnaninstanceofSQLiteDatabase.
publicSQLiteDatabasegetReadableDatabase()
publicSQLiteDatabasegetWritableDatabase()
Thefirsttimeoneofthesemethodsiscalledadatabasewillbecreatedifnoneexists.ThedifferencebetweengetReadableDatabaseandgetWritableDatabaseistheformercanbeusedforread-onlywhereasthelattercanbeusedtoreadfromandwritetothedatabase.
TheSQLiteDatabaseClassOnceyougetaSQLiteDatabasefromaSQLiteOpenHelper’sgetReadableDatabaseorgetWritableDatabasemethod,youcanmanipulatethedatainthedatabasebycallingtheSQLiteDatabase’s insert or execSQL method. For example, to add a record, call theinsertmethodwhosesignatureisasfollows.
publiclonginsert(Stringtable,StringnullColumnHack,
ContentValuesvalues)
Here,tableisthenameofthetableandvaluesisanandroid.content.ContentValuesthatcontainspairsof fieldnames/values tobe inserted to the table.Thismethod returns therowidentifierforthenewrow.
Forinstance,thefollowingcodeinsertsarecordintotheemployeestablepassingthreefieldvalues.
SQLiteDatabasedb=this.getWritableDatabase();
//thisisaninstanceofSQLiteOpenHelper
ContentValuesvalues=newContentValues();
values.put(“first_name”,“Joe”);
values.put(“last_name”,“Average”);
values.put(“position”,“SystemAnalyst”);
longid=db.insert(“employees”,null,values);
db.close();
To update or delete a record, use the update or delete method, respectively. Thesignaturesofthesemethodsareasfollows.
publicintdelete(java.lang.Stringtable,
java.lang.StringwhereClause,java.lang.String[]whereArgs)
publicintupdate(java.lang.Stringtable,
android.content.ContentValuesvalues,
java.lang.StringwhereClause,java.lang.String[]whereArgs)
Examplesofthetwomethodsareshownintheaccompanyingapplication.
ToexecuteaSQLstatement,usetheexecSQLmethod.
publicvoidexecSQL(java.lang.Stringsql)
Finally, toretrieverecords,useoneofthequerymethods.Oneofthemethodoverloadshasthissignature.
publicandroid.database.Cursorquery(java.lang.Stringtable,
java.lang.String[]columns,java.lang.Stringselection,
java.lang.String[]selectionArgs,
java.lang.StringgroupBy,
java.lang.Stringhaving,
java.lang.StringorderBy,hava.lang.Stringlimit)
You can find an example on how to use this method in the sample applicationaccompanyingthischapter.
Onethingtonote:ThedatareturnedbythequerymethodiscontainedinaninstanceofCursor,aninterestingtypeexplainedinthenextsection.
TheCursorInterfaceCalling the query method on a SQLiteDatabase returns a Cursor. A Cursor, animplementationoftheandroid.database.Cursorinterface,providesreadandwriteaccesstotheresultsetreturnedbyadatabasequery.
Toreadarowofdata throughaCursor,youfirstneedtopoint theCursor toadatarow by calling its moveToFirst, moveToNext, moveToPrevious, moveToLast, ormoveToPosition method. moveToFirst moves the Cursor to the first row andmoveToNexttothenextrow.moveToLast,youmayhaveguessedcorrectly,movesitto
the last record and moveToPrevious to the previous row. moveToPosition takes anintegerandmovestheCursortothespecifiedposition.
OnceyoumovetheCursortoadatarow,youcanreadacolumnvalueintherowbycalling the Cursor’s getInt, getFloat, getLong, getString, getShort, or getDoublemethod,passingthecolumnindex.
An interesting aspect of Cursor is that it can be used as the data source for aListAdapter,which in turn can be used to feed aListView. The advantage of using aCursor foraListView is that theCursor canmanageyourdata. Inotherwords, if thedataisupdated,theCursorcanself-refreshtheListView.Thisisaveryusefulfeatureasyouthenhaveonefewerthingtoworryabout.
ExampleThe DatabaseDemo1 application is an application for managing contacts in a SQLitedatabase. A contact is a data structure that contains a person’s contact details. Theapplication has three activities, MainActivity, AddContactActivity, andShowContactActivity.
ThemainactivityshowsthelistofcontactsandisshowninFigure18.1.
Figure18.1:Themainactivity
The main activity offers an Add button on its action bar that will start theAddContactActivityactivity ifpressed.The latteractivitycontainsa formforaddinganewcontactandisshowninFigure18.2.
Figure18.2:AddContactActivity
ThemainactivityalsousesaListViewtodisplayallcontactsinthedatabase.Pressinganitem on the list activates the ShowContactActivity activity, which is shown in Figure18.3.
Figure18.3:ShowContactActivity
The ShowContactActivity activity allows the user to delete the shown contact bypressing the Delete button on the action bar. Pressing the button prompts the user toconfirm if he or she reallywishes to delete the contact.The user can press the activitylabeltogobacktothemainactivity.
ThethreeactivitiesintheapplicationaredeclaredinthemanifestpresentedinListing18.1.
Listing18.1:TheAndroidManifest.xmlfile
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.databasedemo1”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“11”
android:targetSdkVersion=“18”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=”.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<activityandroid:name=”.AddContactActivity”
android:parentActivityName=”.MainActivity”
android:label=”@string/title_activity_add_contact”>
</activity>
<activityandroid:name=”.ShowContactActivity”
android:parentActivityName=”.MainActivity”
android:label=”@string/title_activity_show_contact”>
</activity>
</application>
</manifest>
DatabaseDemo1isasimpleapplicationthatfeaturesoneobjectmodel,theContactclassinListing18.2.ThisisaPOJOwithfiveproperties,id,firstName,lastName,phone,andemail.
Listing18.2:TheContactclass
packagecom.example.databasedemo1;
publicclassContact{
privatelongid;
privateStringfirstName;
privateStringlastName;
privateStringphone;
privateStringemail;
publicContact(){
}
publicContact(StringfirstName,StringlastName,
Stringphone,Stringemail){
this.firstName=firstName;
this.lastName=lastName;
this.phone=phone;
this.email=email;
}
//getandsetmethodsnotshowntosavespace
}
Nowcomes themost importantclass in thisapplication, theDatabaseManagerclass inListing18.3.Thisclassencapsulatesmethodsforaccessingdatainthedatabase.Theclass
extendsSQLiteOpenHelper and implements itsonCreate and onUpdatemethods andprovidesmethods formanaging contacts, addContact,deleteContact, updateContact,getAllContacts,andgetContact.
Listing18.3:TheDatabaseManagerclass
packagecom.example.databasedemo1;
importjava.util.ArrayList;
importjava.util.List;
importandroid.content.ContentValues;
importandroid.content.Context;
importandroid.database.Cursor;
importandroid.database.sqlite.SQLiteDatabase;
importandroid.database.sqlite.SQLiteOpenHelper;
importandroid.util.Log;
publicclassDatabaseManagerextendsSQLiteOpenHelper{
publicstaticfinalStringTABLE_NAME=“contacts”;
publicstaticfinalStringID_FIELD=“_id”;
publicstaticfinalStringFIRST_NAME_FIELD=“first_name”;
publicstaticfinalStringLAST_NAME_FIELD=“last_name”;
publicstaticfinalStringPHONE_FIELD=“phone”;
publicstaticfinalStringEMAIL_FIELD=“email”;
publicDatabaseManager(Contextcontext){
super(context,
/*dbname=*/“contacts_db2”,
/*cursorFactory=*/null,
/*dbversion=*/1);
}
@Override
publicvoidonCreate(SQLiteDatabasedb){
Log.d(“db”,“onCreate”);
Stringsql=“CREATETABLE”+TABLE_NAME
+”(“+ID_FIELD+”INTEGER,”
+FIRST_NAME_FIELD+”TEXT,”
+LAST_NAME_FIELD+”TEXT,”
+PHONE_FIELD+”TEXT,”
+EMAIL_FIELD+”TEXT,”
+”PRIMARYKEY(“+ID_FIELD+”));”;
db.execSQL(sql);
}
@Override
publicvoidonUpgrade(SQLiteDatabasedb,intarg1,intarg2){
Log.d(“db”,“onUpdate”);
db.execSQL(“DROPTABLEIFEXISTS”+TABLE_NAME);
//re-createthetable
onCreate(db);
}
publicContactaddContact(Contactcontact){
Log.d(“db”,“addContact”);
SQLiteDatabasedb=this.getWritableDatabase();
ContentValuesvalues=newContentValues();
values.put(FIRST_NAME_FIELD,contact.getFirstName());
values.put(LAST_NAME_FIELD,contact.getLastName());
values.put(PHONE_FIELD,contact.getPhone());
values.put(EMAIL_FIELD,contact.getEmail());
longid=db.insert(TABLE_NAME,null,values);
contact.setId(id);
db.close();
returncontact;
}
//Gettingsinglecontact
ContactgetContact(longid){
SQLiteDatabasedb=this.getReadableDatabase();
Cursorcursor=db.query(TABLE_NAME,newString[]{
ID_FIELD,FIRST_NAME_FIELD,LAST_NAME_FIELD,
PHONE_FIELD,EMAIL_FIELD},ID_FIELD+”=?”,
newString[]{String.valueOf(id)},null,
null,null,null);
if(cursor!=null){
cursor.moveToFirst();
Contactcontact=newContact(
cursor.getString(1),
cursor.getString(2),
cursor.getString(3),
cursor.getString(4));
contact.setId(cursor.getLong(0));
returncontact;
}
returnnull;
}
//GettingAllContacts
publicList<Contact>getAllContacts(){
List<Contact>contacts=newArrayList<Contact>();
StringselectQuery=“SELECT*FROM”+TABLE_NAME;
SQLiteDatabasedb=this.getWritableDatabase();
Cursorcursor=db.rawQuery(selectQuery,null);
while(cursor.moveToNext()){
Contactcontact=newContact();
contact.setId(Integer.parseInt(cursor.getString(0)));
contact.setFirstName(cursor.getString(1));
contact.setLastName(cursor.getString(2));
contact.setPhone(cursor.getString(3));
contact.setEmail(cursor.getString(4));
contacts.add(contact);
}
returncontacts;
}
publicCursorgetContactsCursor(){
StringselectQuery=“SELECT*FROM”+TABLE_NAME;
SQLiteDatabasedb=this.getWritableDatabase();
returndb.rawQuery(selectQuery,null);
}
publicintupdateContact(Contactcontact){
SQLiteDatabasedb=this.getWritableDatabase();
ContentValuesvalues=newContentValues();
values.put(FIRST_NAME_FIELD,contact.getFirstName());
values.put(LAST_NAME_FIELD,contact.getLastName());
values.put(PHONE_FIELD,contact.getPhone());
values.put(EMAIL_FIELD,contact.getEmail());
returndb.update(TABLE_NAME,values,ID_FIELD+”=?”,
newString[]{String.valueOf(contact.getId())});
}
publicvoiddeleteContact(longid){
SQLiteDatabasedb=this.getWritableDatabase();
db.delete(TABLE_NAME,ID_FIELD+”=?”,
newString[]{String.valueOf(id)});
db.close();
}
}
TheDatabaseManagerclass isusedbyall thethreeactivityclasses.TheMainActivityclassemploysaListViewthatgetsitsdataandlayoutfromaListAdapterthatinturngetsitsdatafromacursor.TheAddContactActivityclassreceivesthedetailsofanewcontactand inserts it into the database by calling theDatabaseManager class’s addContactmethod.TheShowContactActivityclassretrievesthedetailsofthepressedcontactiteminthemainactivityandusestheDatabaseManagerclass’sgetContactmethodtoachievethis.Iftheuserdecidestodeletetheshowncontact,ShowContactActivitywillresorttoDatabaseManagertodeleteit.
TheMainActivity,AddContactActivity,andShowContactActivityclassesaregiveninListing18.4,Listing18.5,andListing18.6,respectively.
Listing18.4:TheMainActivityclass
packagecom.example.databasedemo1;
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.database.Cursor;
importandroid.os.Bundle;
importandroid.support.v4.widget.CursorAdapter;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.widget.AdapterView;
importandroid.widget.AdapterView.OnItemClickListener;
importandroid.widget.ListAdapter;
importandroid.widget.ListView;
importandroid.widget.SimpleCursorAdapter;
publicclassMainActivityextendsActivity{
DatabaseManagerdbMgr;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListViewlistView=(ListView)findViewById(
R.id.listView);
dbMgr=newDatabaseManager(this);
Cursorcursor=dbMgr.getContactsCursor();
startManagingCursor(cursor);
ListAdapteradapter=newSimpleCursorAdapter(
this,
android.R.layout.two_line_list_item,
cursor,
newString[]{DatabaseManager.FIRST_NAME_FIELD,
DatabaseManager.LAST_NAME_FIELD},
newint[]{android.R.id.text1,android.R.id.text2},
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
listView.setAdapter(adapter);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
listView.setOnItemClickListener(
newOnItemClickListener(){
@Override
publicvoidonItemClick(AdapterView<?>adapterView,
Viewview,intposition,longid){
Intentintent=newIntent(
getApplicationContext(),
ShowContactActivity.class);
intent.putExtra(“id”,id);
startActivity(intent);
}
});
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
switch(item.getItemId()){
caseR.id.action_add:
startActivity(newIntent(this,
AddContactActivity.class));
returntrue;
default:
returnsuper.onOptionsItemSelected(item);
}
}
}
Listing18.5:TheAddContactActivityclass
packagecom.example.databasedemo1;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.View;
importandroid.widget.TextView;
publicclassAddContactActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_contact);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.add_contact,menu);
returntrue;
}
publicvoidcancel(Viewview){
finish();
}
publicvoidaddContact(Viewview){
DatabaseManagerdbMgr=newDatabaseManager(this);
StringfirstName=((TextView)findViewById(
R.id.firstName)).getText().toString();
StringlastName=((TextView)findViewById(
R.id.lastName)).getText().toString();
Stringphone=((TextView)findViewById(
R.id.phone)).getText().toString();
Stringemail=((TextView)findViewById(
R.id.email)).getText().toString();
Contactcontact=newContact(firstName,lastName,
phone,email);
dbMgr.addContact(contact);
finish();
}
}
Listing18.6:TheShowContactActivityclass
packagecom.example.databasedemo1;
importandroid.app.Activity;
importandroid.app.AlertDialog;
importandroid.content.DialogInterface;
importandroid.os.Bundle;
importandroid.util.Log;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.widget.TextView;
publicclassShowContactActivityextendsActivity{
longcontactId;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_show_contact);
getActionBar().setDisplayHomeAsUpEnabled(true);
Bundleextras=getIntent().getExtras();
if(extras!=null){
contactId=extras.getLong(“id”);
DatabaseManagerdbMgr=newDatabaseManager(this);
Contactcontact=dbMgr.getContact(contactId);
if(contact!=null){
((TextView)findViewById(R.id.firstName))
.setText(contact.getFirstName());
((TextView)findViewById(R.id.lastName))
.setText(contact.getLastName());
((TextView)findViewById(R.id.phone))
.setText(contact.getPhone());
((TextView)findViewById(R.id.email))
.setText(contact.getEmail());
}else{
Log.d(“db”,“contactnull”);
}
}
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.show_contact,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
switch(item.getItemId()){
caseR.id.action_delete:
deleteContact();
returntrue;
default:
returnsuper.onOptionsItemSelected(item);
}
}
privatevoiddeleteContact(){
newAlertDialog.Builder(this)
.setTitle(“Pleaseconfirm”)
.setMessage(
“Areyousureyouwanttodelete”+
“thiscontact?”)
.setPositiveButton(“Yes”,
newDialogInterface.OnClickListener(){
publicvoidonClick(
DialogInterfacedialog,
intwhichButton){
DatabaseManagerdbMgr=
newDatabaseManager(
getApplicationContext());
dbMgr.deleteContact(contactId);
dialog.dismiss();
finish();
}
})
.setNegativeButton(“No”,
newDialogInterface.OnClickListener(){
publicvoidonClick(
DialogInterfacedialog,
intwhich){
dialog.dismiss();
}
})
.create()
.show();
}
}
SummaryThe Android Database API makes it easy to work with relational databases. Theandroid.databaseandandroid.database.sqlitepackagescontainsclassesand interfacesthat support access to a SQLite database, which is the default database shipped withAndroid.InthischapteryoulearnedhowtousethethreemostfrequentlyusedtypesintheAPI,theSQLiteOpenHelperclass,theSQLiteDatabaseclass,andtheCursorinterface.
Chapter19TakingPictures
Almost allAndroidhandsets and tablets comewithoneor twocameras.Youcanuse acameratotakestillpicturesbystartinganactivityinthebuilt-inCameraapplicationorusetheCameraAPI.
Thischaptershowshowtousebothapproaches.
OverviewAnAndroidapplicationcancallanotherapplicationtouseoneortwofeaturesofferedbythe latter.Forexample, to sendanemail fromyourapplication,youcanuse thedefaultEmail application rather thanwritingyourownapp. In thecaseof takingapicture, theeasiestway to do this is by using theCamera application. To activateCamera, use thefollowingcode.
intrequestCode=…;
Intentintent=newIntent(MediaStore.ACTION_IMAGE_CAPTURE);
startActivityForResult(intent,requestCode);
Basically, you need to create an Intent by passingMediaStore.ACTION_IMAGE_CAPTUREtotheIntentclass’sconstructor.Then,youneed tocallstartActivityForResult fromyouractivitypassing theIntentanda requestcode.Therequestcodecanbeany integeryourheartdesires.Youwill learnshortly thepurposeofpassingarequestcode.
TotellCamerawheretosavethetakenpicture,youcanpassaUritotheIntent.Hereisthecompletecode.
intrequestCode=…;
Intentintent=newIntent(MediaStore.ACTION_IMAGE_CAPTURE);
Uriuri=…;
intent.putExtra(MediaStore.EXTRA_OUTPUT,uri);
startActivityForResult(intent,requestCode);
When theuserclosesCameraafter takingapictureorcanceling theoperation,Androidwill notifyyour applicationby calling theonActivityResultmethod in the activity thatcalledCamera.ThisgivesyoutheopportunitytosavethepicturetakenusingCamera.ThesignatureofonActivityResultisasfollows.
protectedvoidonActivityResult(intrequestCode,intresultCode,
android.content.Intentdata)
The system calls onActivityResult by passing three arguments. The first argument,requestCode, is the request code passed when calling startActivityForResult. Therequestcodeisimportantifyoucallotheractivitiesfromyouractivity,passingadifferentrequestcodeeachtime.SinceyoucanonlyhaveoneonActivityResultimplementationin
your activity, all calls to startActivityForResultwill share the sameonActivityResult,andyouneedtoknowwhichactivitycausedonActivityResult tobecalledbycheckingtherequestcode.
The second argument to onActivityResult is a result code. The value can be eitherActivity.RESULT_OK or Activity.RESULT_CANCELED or a user defined value.Activity.RESULT_OK indicates that the operation succeeded andActivity.RESULT_CANCELEDindicatesthattheoperationwascanceled.
The third argument toonActivityResult contains data from the called activity if theoperationwassuccessful.
UsingCameraiseasy.However,ifCameradoesnotsuityourneeds,youcanalsousethe Camera API directly. This is not as easy as using Camera, but the API lets youconfiguremanyaspectsofthecamera.
Thesamplesaccompanyingthischaptershowyoubothmethods.
UsingCameraTousethecamera,youneedtheseinyourmanifest.
<uses-featureandroid:name=“android.hardware.camera”/>
<uses-permissionandroid:name=“android.permission.CAMERA”/>
TheCameraDemoapplicationshowshowtousethebuilt-inintenttoactivatetheCameraapplicationanduseittotakeapicture.CameraDemohasonlyoneactivity,whichsportstwo button on its action bar, ShowCamera andEmail.TheShowCamera button startsCameraandtheEmailbuttonemailsthepicture.TheapplicationisshowninFigure19.1.
Figure19.1:CameraDemo
Let’sstartdissectingthecode,startingfromthemanifest.
Listing19.1:Themanifest
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.camerademo”>
<uses-featureandroid:name=“android.hardware.camera”/>
<uses-permissionandroid:name=“android.permission.CAMERA”/>
<uses-
permissionandroid:name=“android.permission.WRITE_EXTERNAL_STORAGE”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.camerademo.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
menufile(themain.xmlfileinListing19.1)thatcontainstwomenuitemsfortheactionbar.
Listing19.1:Themenufile(menu_main.xml)
<menuxmlns:android=“http://schemas.android.com/apk/res/android”>
<item
android:id=”@+id/action_camera”
android:orderInCategory=“100”
android:showAsAction=“ifRoom”
android:title=”@string/action_show_camera”/>
<item
android:id=”@+id/action_email”
android:orderInCategory=“200”
android:showAsAction=“ifRoom”
android:title=”@string/action_email”/>
</menu>
ThelayoutfileforthemainactivityispresentedinListing19.2.ItcontainsanImageViewforshowingthetakenpicture.TheactivityclassitselfisshowninListing19.3.
Listing19.2:Theactivity_main.xmlfile
<RelativeLayoutxmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingBottom=”@dimen/activity_vertical_margin”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”>
<ImageView
android:id=”@+id/imageView”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
/>
</RelativeLayout>
Listing19.3:TheMainActivityclass
packagecom.example.camerademo;
importjava.io.File;
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.net.Uri;
importandroid.os.Bundle;
importandroid.os.Environment;
importandroid.provider.MediaStore;
importandroid.util.Log;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.widget.ImageView;
importandroid.widget.Toast;
publicclassMainActivityextendsActivity{
privatestaticfinalintCAPTURE_IMAGE_ACTIVITY_REQUEST_CODE=100;
FilepictureDir=newFile(Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES),“CameraDemo”);
privatestaticfinalStringFILE_NAME=“image01.jpg”;
privateUrifileUri;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(!pictureDir.exists()){
pictureDir.mkdirs();
}
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
switch(item.getItemId()){
caseR.id.action_camera:
showCamera();
returntrue;
caseR.id.action_email:
emailPicture();
returntrue;
default:
returnsuper.onContextItemSelected(item);
}
}
privatevoidshowCamera(){
Intentintent=newIntent(
MediaStore.ACTION_IMAGE_CAPTURE);
Fileimage=newFile(pictureDir,FILE_NAME);
fileUri=Uri.fromFile(image);
intent.putExtra(MediaStore.EXTRA_OUTPUT,fileUri);
//checkifthedevicehasacamera:
if(intent.resolveActivity(getPackageManager())!=null){
startActivityForResult(intent,
CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE);
}
}
@Override
protectedvoidonActivityResult(intrequestCode,
intresultCode,Intentdata){
if(requestCode==
CAPTURE_IMAGE_ACTIVITY_REQUEST_CODE){
if(resultCode==RESULT_OK){
ImageViewimageView=(ImageView)
findViewById(R.id.imageView);
Fileimage=newFile(pictureDir,FILE_NAME);
fileUri=Uri.fromFile(image);
imageView.setImageURI(fileUri);
}elseif(resultCode==RESULT_CANCELED){
Toast.makeText(this,“Actioncancelled”,
Toast.LENGTH_LONG).show();
}else{
Toast.makeText(this,“Error”,
Toast.LENGTH_LONG).show();
}
}
}
privatevoidemailPicture(){
IntentemailIntent=newIntent(
android.content.Intent.ACTION_SEND);
emailIntent.setType(“application/image”);
emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL,
newString[]{“me@example.com”});
emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT,
“Newphoto”);
emailIntent.putExtra(android.content.Intent.EXTRA_TEXT,
“FromMyApp”);
emailIntent.putExtra(Intent.EXTRA_STREAM,fileUri);
startActivity(Intent.createChooser(emailIntent,
“Sendmail…”));
}
}
TheShowCamerabuttoninMainActivitycallstheshowCameramethod.ThismethodstartsCamerabycallingstartActivityForResult.TheemailPicturemethodstartsanotheractivitythatinturnactivatesthedefaultEmailapplication.
TheCameraAPIAt the center of the Camera API is the android.hardware.Camera class. ACamerarepresentsadigitalcamera.
Every camera has a viewfinder, through which the photographer can see what thecamera isseeing.Aviewfindercanbeopticalorelectronic.Ananalogcameranormallyoffersanopticalviewfinder,whichisareversedtelescopemountedonthecamerabody.Somedigitalcamerashaveanelectronicviewfinderandsomehaveanelectroniconeplusanopticalone.OnanAndroidtabletandhandset,thewholescreenorpartofthescreenisnormallyusedasaviewfinder.
Inanapplicationthatusesacamera, theandroid.view.SurfaceViewclassisnormallyusedasaviewfinder.SurfaceViewisasubclassofViewand,assuch,canbeaddedtoanactivity by declaring it in a layout file using the SurfaceView element. The area of aSurfaceView will be continuously updated with what the camera sees. You control a
SurfaceViewthroughitsSurfaceHolder,whichyoucanobtainbycallingthegetHoldermethodontheSurfaceView.SurfaceHolderisaninterfaceintheandroid.viewpackage.
Therefore,whenworkingwithacamera,youneedtomanageaninstanceofCameraaswellasaSurfaceHolder.
ManagingACameraWhenworkingwiththeCameraAPI,youshouldstartbycheckingifthedevicedoeshaveacamera.Youmustalsodeterminewhichcameratouseifadevicehasmultiplecameras.YoudoitbycallingtheopenstaticmethodoftheCameraclass.
Cameracamera=null;
try{
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.GINGERBREAD){
camera=Camera.open(0);
}else{
camera=Camera.open();
}
}catch(Exceptione){
e.printStackTrace();
}
For pre-Gingerbread Android (Android version 2.3), use the no-argument methodoverload.ForAndroidversion2.3,usetheoverloadthattakesaninteger.
publicstaticCameraopen(intcameraId)
Passing0tothemethodgivesyouthefirstcamera,1thesecondcamera,andsoon.
Youshouldenclosethecalltoopeninatryblockasitmaythrowanexception.
OnceyouobtainaCamera,passaSurfaceHolder to thesetPreviewDisplaymethodontheCamera.
publicvoidsetPreviewDisplay(android.view.SurfaceHolderholder)
IfsetPreviewDisplayreturnssuccessfully,callthecamera’sstartPreviewmethodandtheSurfaceViewcontrolledbytheSurfaceHolderwillstartdisplayingwhatthecamerasees.
To takeapicture, call thecamera’s takePicturemethod.After apicture is taken, thepreviewwillstopsoyouwillneedtocallstartPreviewagaintotakeanotherpicture.
When you are finishedwith the camera, call stopPreview and release to release thecamera.
Optionally, you can configure the camera after you call open by calling itsgetParametersmethod,modifyingtheparameters,andpassingthembacktothecamerausingthesetParametersmethod.
WiththetakePicturemethodyoucandecidewhattodototheresultingrawandJPEGimagesfromthecamera.ThesignatureoftakePictureisasfollows.
publicfinalvoidtakePicture(Camera.ShutterCallbackshutter,
Camera.PictureCallbackraw,Camera.PictureCallbackpostview,
Camera.PictureCallbackjpeg)
Thefourparametersarethese.
shutter.Thecallbackforimagecapturemoment.Forexample,youcanpasscodethatplaysaclicksoundtomakeitmorelikearealcamera.raw.Thecallbackforuncompressedimagedata.postview.Thecallbackwithpostviewimagedata.jpeg.ThecallbackforJPEGimagedata.
YouwilllearnhowtouseCameraintheCameraAPIDemoapplication.
ManagingASurfaceHolderA SurfaceHolder communicates with its user through a series of methods inSurfaceHolder.Callback.TomanageaSurfaceHolder,youneedtopassaninstanceofSurfaceHolder.CallbacktotheSurfaceHolder’saddCallbackmethod.
SurfaceHolder.CallbackexposesthesethreemethodsthattheSurfaceHolderwillcallinresponsetoevents.
publicabstractvoidsurfaceChanged(SurfaceHolderholder,
intformat,intwidth,intheight)
Calledafteranystructuralchanges(formatorsize)havebeenmadetothesurface.
publicabstractvoidsurfaceCreated(SurfaceHolderholder)
Calledafterthesurfaceisfirstcreated.
publicabstractvoidsurfaceDestroyed(SurfaceHolderholder)
Calledbeforeasurfaceisbeingdestroyed.
For instance, you might want to link a SurfaceHolder with aCamera right after theSurfaceHolder is created. Therefore, you might want to override the surfaceCreatedmethodwiththiscode.
@Override
publicvoidsurfaceCreated(SurfaceHolderholder){
try{
camera.setPreviewDisplay(holder);
camera.startPreview();
}catch(Exceptione){
Log.d(“camera”,e.getMessage());
}
}
UsingtheCameraAPITheCameraAPIDemo application demonstrates the use of theCameraAPI to take stillpictures.ItusesaSurfaceViewasaviewfinderandabuttontotakeapicture.Clickingthebuttontakesthepictureandemitsabeepsound.Afterapictureistaken,theSurfaceViewfreezesfor twosecondstogivetheuser thechangeto inspect thepictureandrestart thecamerapreviewtoallowtheusertotakeanotherpicture.Allpicturesaregivenarandomnameandstoredintheexternalstorage.
Theapplicationhasoneactivity,whoselayoutisshowninListing19.4.
Listing19.4:Thelayoutfile(activity_main.xml)
<LinearLayoutxmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”>
<Button
android:id=”@+id/button1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“center”
android:onClick=“takePicture”
android:text=”@string/button_take”/>
<SurfaceView
android:id=”@+id/surfaceview”
android:layout_width=“match_parent”
android:layout_height=“match_parent”/>
</LinearLayout>
ThelayoutfeaturesaLinearLayoutcontainingabuttonandaSurfaceView.TheactivityclassispresentedinListing19.5.
Listing19.5:TheMainActivityclass
packagecom.example.cameraapidemo;
importjava.io.File;
importjava.io.FileNotFoundException;
importjava.io.FileOutputStream;
importjava.io.IOException;
importandroid.app.Activity;
importandroid.hardware.Camera;
importandroid.hardware.Camera.PictureCallback;
importandroid.hardware.Camera.ShutterCallback;
importandroid.media.AudioManager;
importandroid.media.SoundPool;
importandroid.net.Uri;
importandroid.os.Build;
importandroid.os.Bundle;
importandroid.os.Environment;
importandroid.os.Handler;
importandroid.provider.Settings;
importandroid.util.Log;
importandroid.view.Menu;
importandroid.view.SurfaceHolder;
importandroid.view.SurfaceView;
importandroid.view.View;
importandroid.widget.Button;
importandroid.widget.Toast;
publicclassMainActivityextendsActivity
implementsSurfaceHolder.Callback{
privateCameracamera;
SoundPoolsoundPool;
intbeepId;
FilepictureDir=newFile(Environment
.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES),
“CameraAPIDemo”);
privatestaticfinalStringTAG=“camera”;
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
pictureDir.mkdirs();
soundPool=newSoundPool(1,
AudioManager.STREAM_NOTIFICATION,0);
Uriuri=Settings.System.DEFAULT_RINGTONE_URI;
beepId=soundPool.load(uri.getPath(),1);
SurfaceViewsurfaceView=(SurfaceView)
findViewById(R.id.surfaceview);
surfaceView.getHolder().addCallback(this);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
@Override
publicvoidonResume(){
super.onResume();
try{
if(Build.VERSION.SDK_INT>=
Build.VERSION_CODES.GINGERBREAD){
camera=Camera.open(0);
}else{
camera=Camera.open();
}
}catch(Exceptione){
e.printStackTrace();
}
}
@Override
publicvoidonPause(){
super.onPause();
if(camera!=null){
try{
camera.release();
camera=null;
}catch(Exceptione){
e.printStackTrace();
}
}
}
privatevoidenableButton(booleanenabled){
Buttonbutton=(Button)findViewById(R.id.button1);
button.setEnabled(enabled);
}
publicvoidtakePicture(Viewview){
enableButton(false);
camera.takePicture(shutterCallback,null,
pictureCallback);
}
privateShutterCallbackshutterCallback=
newShutterCallback(){
@Override
publicvoidonShutter(){
//playsound
soundPool.play(beepId,1.0f,1.0f,0,0,1.0f);
}
};
privatePictureCallbackpictureCallback=
newPictureCallback(){
@Override
publicvoidonPictureTaken(byte[]data,
finalCameracamera){
Toast.makeText(MainActivity.this,“Savingimage”,
Toast.LENGTH_LONG)
.show();
FilepictureFile=newFile(pictureDir,
System.currentTimeMillis()+”.jpg”);
try{
FileOutputStreamfos=newFileOutputStream(
pictureFile);
fos.write(data);
fos.close();
}catch(FileNotFoundExceptione){
Log.d(TAG,e.getMessage());
}catch(IOExceptione){
Log.d(TAG,e.getMessage());
}
Handlerhandler=newHandler();
handler.postDelayed(newRunnable(){
@Override
publicvoidrun(){
try{
enableButton(true);
camera.startPreview();
}catch(Exceptione){
Log.d(“camera”,
“Errorstartingcamerapreview:”
+e.getMessage());
}
}
},2000);
}
};
@Override
publicvoidsurfaceCreated(SurfaceHolderholder){
try{
camera.setPreviewDisplay(holder);
camera.startPreview();
}catch(Exceptione){
Log.d(“camera”,e.getMessage());
}
}
@Override
publicvoidsurfaceChanged(SurfaceHolderholder,
intformat,intw,inth3){
if(holder.getSurface()==null){
Log.d(TAG,“surfacedoesnotexist,return”);
return;
}
try{
camera.setPreviewDisplay(holder);
camera.startPreview();
}catch(Exceptione){
Log.d(“camera”,e.getMessage());
}
}
@Override
publicvoidsurfaceDestroyed(SurfaceHolderholder){
Log.d(TAG,“surfaceDestroyed”);
}
}
TheMainActivity class uses a Camera and a SurfaceView. The latter continuouslydisplayswhat the camera sees.Since aCamera takes a lot of resources to operate, theMainActivity releases the camerawhen the application stops and re-opens itwhen theapplicationresumes.
TheMainActivityclassalsoimplementsSurfaceHolder.CallbackandpassesitselftotheSurfaceHolderoftheSurfaceView itemploysasaviewfinder.Thisisshowninthe
followinglinesintheonCreatemethod.
SurfaceViewsurfaceView=(SurfaceView)
findViewById(R.id.surfaceview);
surfaceView.getHolder().addCallback(this);
InbothsurfaceCreatedandsurfaceChangedmethods thatMainActivityoverrides, theclasscalls thecamera’ssetPreviewDisplayandstartPreviewmethods.Thismakessurewhen the camera is linkedwith aSurfaceHolder, theSurfaceHolder has already beencreated.
Another important point inMainActivity is the takePicturemethod that gets calledwhentheuserpressesthebutton.
publicvoidtakePicture(Viewview){
enableButton(false);
camera.takePicture(shutterCallback,null,
pictureCallback);
}
ThetakePicturemethoddisablesthebuttonsothatnomorepicturecanbetakenuntilthepicture is saved and calls the takePicture method on the Camera, passing aCamera.ShutterCallbackandaCamera.PictureCallback.NotethatcallingtakePictureonaCameraalsostopspreviewingtheimageontheSurfaceHolderlinkedtothecamera.
TheCamera.ShutterCallbackinMainActivityhasonemethod,onShutter,thatplaysasoundfromthesoundpool.
@Override
publicvoidonShutter(){
//playsound
soundPool.play(beepId,1.0f,1.0f,0,0,1.0f);
}
TheCamera.PictureCallbackalsohasonemethod,onPictureTaken,whosesignatureisthis.
publicvoidonPictureTaken(byte[]data,finalCameracamera)
Thismethod is called by theCamera’s takePicture method and receives a byte arraycontainingthephotoimage.
TheonPictureTakenmethodimplementationinMainActivitydoesthreethings.First,itdisplaysamessageusingtheToast.Second,itsavesthebytearrayintoafile.ThenameofthefileisgeneratedusingSystem.currentTimeMillis().Finally, themethodcreatesaHandler to schedule a task thatwill be executed in two seconds. The task enables thebuttonandcallsthecamera’sstartPreviewsothattheviewfinderwillstartworkingagain.
Figure19.2showstheCameraAPIDemoapplication.
Figure19.2:TheCameraAPIDemoapplication
SummaryAndroidoffers twooptionsforapplicationsthatneedtotakestillpictures:useabuilt-in
intenttostartCameraorusetheCameraAPI.ThefirstoptionistheeasieronetousebutlacksthefeaturesthattheCameraAPIprovides.
Thischaptershowedhowtousebothmethods.
Chapter20MakingVideos
Theeasiestwaytoprovidevideo-makingcapabilityinyourapplicationistouseabuilt-inintent to activate an existing activity.However, if youneedmore thanwhat the defaultapplicationcanprovide,youneedtogetyourhandsdirtyandworkwiththeAPIdirectly.
Thischaptershowshowtousebothmethodsformakingvideos.
UsingtheBuilt-inIntentIfyouchoosetousethedefaultCameraapplicationformakingvideo,youcanactivatetheapplicationwiththesethreelinesofcode.
intrequestCode=…;
Intentintent=newIntent(MediaStore.ACTION_VIDEO_CAPTURE);
startActivityForResult(intent,requestCode);
Basically, you need to create an Intent object by passingMediaStore.ACTION_VIDEO_CAPTURE to its constructor and pass it to thestartActivityForResultmethodinyouractivityclass.Youcanchooseanyintegerfortherequestcode thatyouwillpassas thesecondargument tostartActivityForResult. Thismethodwill pause the current activity and startCamera andmake it ready to capture avideo.
Whenyouexit fromCamera,eitherbycanceling theoperationorwhenyouaredonemaking a video, the system will resume your original activity (the activity where youcalled startActivityForResult) and call its onActivityResult method. If you areinterested in saving or processing the captured video, you must overrideonActivityResult.Itssignatureisasfollows.
protectedvoidonActivityResult(intrequestCode,intresultCode,android.content.Intentdata)
The system calls onActivityResult by passing three arguments. The first argument,requestCode, is the request code passedwhen you called startActivityForResult.Therequestcode is important ifyouarecallingotheractivities fromyouractivity,passingadifferent request code for each activity. Since you can only have oneonActivityResultimplementation inyouractivity, all calls tostartActivityForResultwill share the sameonActivityResult, andyouneed toknowwhichactivity causedonActivityResult to becalledbycheckingthevalueoftherequestcode.
The second argument to onActivityResult is a result code. The value can be eitherActivity.RESULT_OK or Activity.RESULT_CANCELED or a user defined value.Activity.RESULT_OK indicates that the operation succeeded andActivity.RESULT_CANCELEDindicatesthattheoperationwascanceled.
ThethirdargumenttoonActivityResultcontainsdatafromCameraiftheoperationwassuccessful.
Inaddition,youneedthefollowinguses-featureelement inyourmanifest to indicatethatyourapplicationneedstousethecamerahardwareofthedevice.
<uses-featureandroid:name=“android.hardware.camera”
android:required=“true”/>
Asanexample,considertheVideoDemoapplicationthathasanactivitywithabuttononitsactionbar.Youcanpress thisbutton toactivateCamerafor thepurposeofmakingavideo.TheVideoDemoapplicationisshowninFigure20.1.
Figure20.1:VideoDemo
TheAndroidManifest.xmlfileinListing20.1showstheactivityusedintheapplicationaswellasause-featureelement.
Listing20.1:TheAndroidManifest.xmlfile
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.videodemo”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“16”
android:targetSdkVersion=“19”/>
<uses-featureandroid:name=“android.hardware.camera”
android:required=“true”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.videodemo.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
Thereisonlyoneactivityintheapplication,theMainActivityactivity.TheactivityreadsthemenufileinListing20.2topopulateitsactionbar.
Listing20.2:Themenufile(menu_main.xml)
<menuxmlns:android=“http://schemas.android.com/apk/res/android”>
<item
android:id=”@+id/action_camera”
android:orderInCategory=“100”
android:showAsAction=“always”
android:title=”@string/action_camera”/>
</menu>
The activity also uses the layout file in Listing 20.3 to set its view. There is only aFrameLayoutwithaVideoViewelementthatisusedtodisplaythevideofile.
Listing20.3:Theactivity_main.xmlfile
<FrameLayoutxmlns:android=“http://schemas.android.com/apk/res/android”
android:layout_width=“match_parent”
android:layout_height=“match_parent”>
<VideoView
android:id=”@+id/videoView”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:layout_gravity=“center”>
</VideoView>
</FrameLayout>
NotethatIuseaFrameLayouttoenclosetheVideoViewtocenterit.Forsomereason,aLinearLayoutoraRelativeLayoutwillnotcenterit.
Finally,theMainActivityclassispresentedinListing20.4.Youshouldknowbynowthat theonOptionsItemSelectedmethod is calledwhen the amenu item is pressed. Inshort, pressing the Camera button on the action bar calls the showCamera method.showCamera constructs a built-in Intent and passes it to startActivityForResult toactivatethevideomakingfeatureinCamera.
Listing20.4:TheMainActivityclass
packagecom.example.videodemo;
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.net.Uri;
importandroid.os.Bundle;
importandroid.provider.MediaStore;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.widget.MediaController;
importandroid.widget.Toast;
importandroid.widget.VideoView;
publicclassMainActivityextendsActivity{
privatestaticfinalintREQUEST_CODE=200;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
switch(item.getItemId()){
caseR.id.action_camera:
showCamera();
returntrue;
default:
returnsuper.onContextItemSelected(item);
}
}
privatevoidshowCamera(){
//cannotsetthevideofile
Intentintent=newIntent(
MediaStore.ACTION_VIDEO_CAPTURE);
//checkifthedevicehasacamera:
if(intent.resolveActivity(getPackageManager())!=null){
startActivityForResult(intent,REQUEST_CODE);
}else{
Toast.makeText(this,“Openingcamerafailed”,
Toast.LENGTH_LONG).show();
}
}
@Override
protectedvoidonActivityResult(intrequestCode,
intresultCode,Intentdata){
if(requestCode==REQUEST_CODE){
if(resultCode==RESULT_OK){
if(data!=null){
Uriuri=data.getData();
VideoViewvideoView=(VideoView)
findViewById(R.id.videoView);
videoView.setVideoURI(uri);
videoView.setMediaController(
newMediaController(this));
videoView.requestFocus();
}
}elseif(resultCode==RESULT_CANCELED){
Toast.makeText(this,“Actioncancelled”,
Toast.LENGTH_LONG).show();
}else{
Toast.makeText(this,“Error”,Toast.LENGTH_LONG)
.show();
}
}
}
}
What is interesting is the implementation of the onActivityResult method, which getscalledwhentheuser leavesCamera.If theresultcodeisRESULT_OKanddata isnotnull,themethodcallsthegetDatamethodondatatogetaUripointingtothelocationofthevideo.Next, it finds theVideoViewwidget and set itsvideoURI property andcallstwoothermethodsontheVideoView,setMediaControllerandrequestFocus.
protectedvoidonActivityResult(intrequestCode,
intresultCode,Intentdata){
if(requestCode==REQUEST_CODE){
if(resultCode==RESULT_OK){
if(data!=null){
Uriuri=data.getData();
VideoViewvideoView=(VideoView)
findViewById(R.id.videoView);
videoView.setVideoURI(uri);
videoView.setMediaController(
newMediaController(this));
videoView.requestFocus();
}
…
PassingaMediaControllerdecoratestheVideoViewwithamediacontrollerthatcanbeusedtoplayandstopthevideo.CallingrequestFocus()ontheVideoViewsetsfocusonthewidget.
MediaRecorderIfyouchoosetodealwiththeAPIdirectlyratherthanusingtheCameratoprovideyourapplication with video-making capability, you need to know the details ofMediaRecorder.
The android.media.MediaRecorder class can be used to record audio and video.Figure20.2showsthevariousstatesaMediaRecordcanbein.
Figure20.2:TheMediaRecorderstatediagram
TocaptureavideowithaMediaRecorder,ofcourseyouneedaninstanceofit.So,thefirstthingtodoistocreateaMediaRecorder.
MediaRecordermediaRecorder=newMediaRecorder();
Then, as you can see in Figure 20.2, to record a video, you have to bring theMediaRecorder to the Initialized state, followed by theDataSourceConfigured and
Preparedstatesbycallingcertainmethods.
To transitionaMediaRecorder to the Initialized state, call the setAudioSource andsetVideoSource methods to set the audio and video sources. The valid value forsetAudioSource isoneof the fieldsdefined in theMediaRecorder.AudioSourceclass,which areCAMCORDER,DEFAULT,MIC,REMOTE_SUBMIX,VOICE_CALL,VOICE_COMMUNICATION, VOICE_DOWNLINK, VOICE_RECOGNITION,andVOICE_UPLINK.
The valid value for setVideoSource is one of the fields in theMediaRecorder.VideoSourceclass,whichareCAMERAandDEFAULT.
OncetheMediaRecorderisintheInitializedstate,callitssetOutputFormatmethod,passing one of the file formats in the MediaRecorder.OutputFormat class. Thefollowing fields are defined: AAC_ADTS, AMR_NB, AMR_WB, DEFAULT,MPEG_4,RAW_AMR,andTHREE_GPP.
Successfully calling setOutputFormat brings the MediaRecorder to theDataSourceConfigured state. You just need to call prepare to prepare theMediaRecorder.
Tostartrecording,callthestartmethod.Itwillkeeprecordinguntilstopiscalledoranerroroccurs.AnerrormayoccuriftheMediaRecorderrunsoutofspacetostorevideoorifaspecifiedmaximumrecordtimeisexceeded.
Once you stop aMediaRecorder, it goes back to the initial state. Youmust take itthroughthepreviousthreestatesagaintorecordanothervideo.
AlsonotethataMediaRecorderusesalotofresourcesanditisprudenttoreleasetheresources by calling its release method if theMediaRecorder is not being used. Forexample, you should release theMediaRecorder when the activity is paused. Once aMediaRecorderisreleased,thesameinstancecannotbereusedtorecordanothervideo.
UsingMediaRecorderTheVideoRecorderapplicationdemonstrateshowtousetheMediaRecordertorecordavideo.IthasoneactivitythatcontainsabuttonandaSurfaceView.Thebuttonisusedtostart and stop recordingwhereas theSurfaceView for displayingwhat the camera sees.SurfaceViewwasexplainedindetailinChapter19,“TakingPictures.”
ThelayoutfilefortheactivityisshowninListing20.5andtheactivityclassinListing20.6.
Listing20.5:Thelayoutfile(activity_main.xml)
<LinearLayoutxmlns:android=“http://schemas.android.com/apk/res/android”
android:orientation=“vertical”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”>
<Button
android:id=”@+id/button1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_marginLeft=“33dp”
android:layout_marginTop=“22dp”
android:onClick=“startStopRecording”
android:text=”@string/button_start”/>
<SurfaceView
android:id=”@+id/surfaceView”
android:layout_width=“match_parent”
android:layout_height=“match_parent”/>
</LinearLayout>
Listing20.6:TheMainActivityclass
packagecom.example.videorecorder;
importjava.io.File;
importjava.io.IOException;
importandroid.app.Activity;
importandroid.media.MediaRecorder;
importandroid.os.Bundle;
importandroid.os.Environment;
importandroid.view.SurfaceHolder;
importandroid.view.SurfaceView;
importandroid.view.View;
importandroid.widget.Button;
publicclassMainActivityextendsActivity{
privateMediaRecordermediaRecorder;
privateFileoutputDir;
privatebooleanrecording=false;
@Override
publicvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
FilemoviesDir=Environment
.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MOVIES);
outputDir=newFile(moviesDir,
“VideoRecorder”);
outputDir.mkdirs();
setContentView(R.layout.activity_main);
}
@Override
protectedvoidonResume(){
super.onResume();
mediaRecorder=newMediaRecorder();
initAndConfigureMediaRecorder();
}
@Override
protectedvoidonPause(){
super.onPause();
if(recording){
try{
mediaRecorder.stop();
}catch(IllegalStateExceptione){
}
}
releaseMediaRecorder();
Buttonbutton=(Button)findViewById(R.id.button1);
button.setText(“Start”);
recording=false;
}
privatevoidreleaseMediaRecorder(){
if(mediaRecorder!=null){
mediaRecorder.reset();
mediaRecorder.release();
mediaRecorder=null;
}
}
privatevoidinitAndConfigureMediaRecorder(){
mediaRecorder.setAudioSource(
MediaRecorder.AudioSource.CAMCORDER);
mediaRecorder
.setVideoSource(MediaRecorder.VideoSource.CAMERA);
mediaRecorder.setOutputFormat(
MediaRecorder.OutputFormat.MPEG_4);
mediaRecorder.setVideoFrameRate(10);//makeitverylow
mediaRecorder.setVideoEncoder(
MediaRecorder.VideoEncoder.MPEG_4_SP);
mediaRecorder.setAudioEncoder(
MediaRecorder.AudioEncoder.AMR_NB);
StringoutputFile=newFile(outputDir,
System.currentTimeMillis()+”.mp4”)
.getAbsolutePath();
mediaRecorder.setOutputFile(outputFile);
SurfaceViewsurfaceView=(SurfaceView)
findViewById(R.id.surfaceView);
SurfaceHoldersurfaceHolder=surfaceView.getHolder();
mediaRecorder.setPreviewDisplay(surfaceHolder
.getSurface());
}
publicvoidstartStopRecording(Viewview){
Buttonbutton=(Button)findViewById(R.id.button1);
if(recording){
button.setText(“Start”);
try{
mediaRecorder.stop();
}catch(IllegalStateExceptione){
}
releaseMediaRecorder();
}else{
button.setText(“Stop”);
if(mediaRecorder==null){
mediaRecorder=newMediaRecorder();
initAndConfigureMediaRecorder();
}
//prepareMediaRecorder
try{
mediaRecorder.prepare();
}catch(IllegalStateExceptione){
e.printStackTrace();
}catch(IOExceptione){
e.printStackTrace();
}
mediaRecorder.start();
}
recording=!recording;
}
}
Let’s start with the onCreate method. It does an important job which is to create adirectoryforallvideoscapturedunderthedefaultdirectoryformoviefiles.
FilemoviesDir=Environment
.getExternalStoragePublicDirectory(
Environment.DIRECTORY_MOVIES);
outputDir=newFile(moviesDir,
“VideoRecorder”);
outputDir.mkdirs();
TheothertwoimportantmethodsareonResumeandonPause.InonResumeyoucreateanew instance of MediaRecorder and initialize and configure it by callinginitAndConfigureMediaRecorder.Whyanewinstanceeverytime?Becauseonceused,aMediaRecordercannotbereused.
In onPause, you stop the MediaRecorder if it is recording and call thereleaseMediaRecordermethodtoreleasetheMediaRecorder.
Now, let’s have a look at the initAndConfigureMediaRecorder andreleaseMediaRecordermethods.
Asthenameimplies,initAndConfigureMediaRecorderinitializesandconfigurestheMediaRecorder created by the onResume method. It calls various methods inMediaRecorder to transition it to the Initialized andDataSourceConfigured states. ItalsopassestheSurfaceoftheSurfaceViewtodisplaywhatthecamerasees.
SurfaceViewsurfaceView=(SurfaceView)
findViewById(R.id.surfaceView);
SurfaceHoldersurfaceHolder=surfaceView.getHolder();
mediaRecorder.setPreviewDisplay(surfaceHolder
.getSurface());
Inthisstate,theMediaRecorderjustwaitsuntiltheuserpressestheStartbutton.Whenithappens, thestartStopRecordingmethod iscalled,which in turncalls theprepareandstartmethodsontheMediaRecorder.ItalsochangestheStartbuttontoaStopbutton.
WhentheuserpressestheStopbutton,theMediaRecorder’sstopmethodiscalledand
theMediaRecorder is released. The Stop button is changed back to a Start button,waitingforanotherturn.
SummaryTwo methods are available if you want to equip your application with video-makingcapability. The first, the easy one, is by creating the default intent and passing it tostartActivityForResult. The second method is to useMediaRecorder directly. Thismethodisharderbutbringswithitthefullfeaturesofthedevicecamera.
Thischaptershowedhowtousebothmethodstomakevideo.
Chapter21TheSoundRecorder
TheAndroidplatformshipswithamultitudeofAPIs,includingoneforrecordingaudioandvideo.InthischapteryoulearnhowtousetheMediaRecorderclasstosamplesoundlevels.ThisisthesameclassyouusedformakingvideosinChapter20,“MakingVideos.”
TheMediaRecorderClassSupportformultimediaisrocksolidinAndroid.Thereareclassesthatyoucanusetoplayaudio and video as well as record them. In the SoundMeter project discussed in thischapter, you will use the MediaRecorder class to sample sound or noiselevels.MediaRecorderisusedtorecordaudioandvideo.Theoutputcanbewrittentoafileandtheinputsourcecanbeeasilyselected.Itisrelativelyeasytousetoo.YoustartbyinstantiatingtheMediaRecorderclass.
MediaRecordermediaRecorder=newMediaRecorder();
Then, configure the instance by calling its setAudioSource, setVideoSource,setOutputFormat, setAudioEncoder, setOutputFile, or other methods. Next, preparetheMediaRecorderbycallingitspreparemethod:
mediaRecorder.prepare();
NotethatpreparemaythrowexceptioniftheMediaRecorderisnotconfiguredpropertyorifyoudonothavetherightpermissions.
Tostartrecording,callitsstartmethod.Tostoprecording,callstop.
Whenyou’redonewithaMediaRecorder,callitsresetmethodtoreturnittoitsinitialstateanditsreleasemethodtoreleaseresourcesitcurrentlyholds.
mediaRecorder.reset();
mediaRecorder.release();
ExampleNowthatyouknowhowtousetheMediaRecorder,let’stakealookattheSoundMeterproject. The application samples sound amplitudes at certain intervals and displays thecurrentlevelasabar.
Asusual,let’sstartbylookingatthemanifest(theAndroidManifest.xmlfile)fortheproject.ItisgiveninListing21.1.
Listing21.1:ThemanifestforSoundMeter
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.soundmeter”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“8”
android:targetSdkVersion=“17”/>
<uses-permissionandroid:name=“android.permission.RECORD_AUDIO”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.soundmeter.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
Onethingtonotehereistheuseoftheuses-permissionelementinthemanifesttoaskfortheuser’spermissiontorecordaudio.Ifyoudon’tincludethiselement,yourapplicationwillnotwork.Also,iftheuserdoesnotconsent,theapplicationwillnotinstall.
Thereisonlyoneactivityinthisprojectascanbeseeninthemanifest.
Listing21.2showsthelayoutfileforthemainactivity.ARelativeLayout isusedforthemaindisplayanditcontainsaTextViewfordisplayingthecurrentsoundlevelandabuttonthatwillactasasoundindicator.
Listing21.2:Theres/layout/activity_main.xmlfileinSoundMeter
<RelativeLayoutxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingBottom=”@dimen/activity_vertical_margin”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
tools:context=”.MainActivity”>
<TextView
android:id=”@+id/level”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”/>
<Button
android:id=”@+id/button1”
style=”?android:attr/buttonStyleSmall”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignLeft=”@+id/level”
android:layout_below=”@+id/level”
android:background=”#ff0000”
android:layout_marginTop=“30dp”/>
</RelativeLayout>
There are two Java classes in this application.The first one, given inListing21.3, is aclasscalledSoundMeterthatencapsulatesaMediaRecorderandexposesthreemethodstomanageit.Thefirstmethod,start,createsaninstanceofMediaRecorder,configuresit, andstarts it.Thesecondmethod,stop, stops theMediaRecorder.The thirdmethod,getAmplitude,returnsadoubleindicatingthesampledsoundlevel.
Listing21.3:TheSoundMeterclass
packagecom.example.soundmeter;
importjava.io.IOException;
importandroid.media.MediaRecorder;
publicclassSoundMeter{
privateMediaRecordermediaRecorder;
booleanstarted=false;
publicvoidstart(){
if(started){
return;
}
if(mediaRecorder==null){
mediaRecorder=newMediaRecorder();
mediaRecorder.setAudioSource(
MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(
MediaRecorder.OutputFormat.THREE_GPP);
mediaRecorder.setAudioEncoder(
MediaRecorder.AudioEncoder.AMR_NB);
mediaRecorder.setOutputFile(“/dev/null”);
try{
mediaRecorder.prepare();
}catch(IllegalStateExceptione){
e.printStackTrace();
}catch(IOExceptione){
e.printStackTrace();
}
mediaRecorder.start();
started=true;
}
}
publicvoidstop(){
if(mediaRecorder!=null){
mediaRecorder.stop();
mediaRecorder.release();
mediaRecorder=null;
started=false;
}
}
publicdoublegetAmplitude(){
returnmediaRecorder.getMaxAmplitude()/100;
}
}
ThesecondJavaclass,MainActivity, is themainactivityclass for theapplication. It ispresentedinListing21.4.
Listing21.4:TheMainActivityclassinSoundMeter
packagecom.example.soundmeter;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.os.Handler;
importandroid.view.Menu;
importandroid.widget.Button;
importandroid.widget.TextView;
publicclassMainActivityextendsActivity{
Handlerhandler=newHandler();
SoundMetersoundMeter=newSoundMeter();
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
//Inflatethemenu;thisaddsitemstotheactionbarifit
//ispresent.
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
@Override
publicvoidonStart(){
super.onStart();
soundMeter.start();
handler.postDelayed(pollTask,150);
}
@Override
publicvoidonPause(){
soundMeter.stop();
super.onPause();
}
privateRunnablepollTask=newRunnable(){
@Override
publicvoidrun(){
doubleamplitude=soundMeter.getAmplitude();
TextViewtextView=(TextView)findViewById(R.id.level);
textView.setText(“amp:”+amplitude);
Buttonbutton=(Button)findViewById(R.id.button1);
button.setWidth((int)amplitude*10);
handler.postDelayed(pollTask,150);
}
};
}
TheMainActivityclassoverrides twoactivity lifecyclemethods,onStartandonPause.YoumayrecallthatthesystemcallsonStartrightafteranactivitywascreatedorafteritwas restarted.The systemcallsonPausewhen the activitywas paused because anotheractivitywasstartedorbecauseanimportanteventoccurred.IntheMainActivityclass,theonStart method starts the SoundMeter and the onPause method stops it. TheMainActivity class also uses a Handler to sample the sound level every 150milliseconds.
Figure 21.1 shows the application. The horizontal bar shows the current soundamplitude.
Figure21.1:TheSoundMeterapplication
SummaryIn this chapter you learned to use theMediaRecorder class to record audio. You alsocreatedanapplicationforsamplingnoiselevels.
Chapter22HandlingtheHandler
Oneof themost interestinganduseful types in theAndroidSDK is theHandler class.Mostofthetime,itisusedtoprocessmessagesandscheduleatasktorunatafuturetime.
Thischapterexplainswhattheclassisgoodforandoffersexamples.
OverviewThe android.os.Handler class is an exciting utility class that, among others, can bescheduleddoexecuteaRunnableatafuturetime.AnytaskassignedtoaHandlerwillrunontheHandler’sthread.Inturn,theHandlerrunsonthethreadthatcreatedit,whichinmostcaseswouldbe theUI thread.Assuch,youshouldnotschedulea long-runningtaskwithaHandlerbecauseitwouldmakeyourapplicationfreeze.However,youcanuseaHandler to handle a long-running task if you canbe split the task into smaller parts,whichyoulearnhowtoachieveinthissection.
To schedule a task to run at a future time, call theHandler class’spostDelayed orpostAtTimemethod.
publicfinalbooleanpostDelayed(Runnabletask,longx)
public final boolean postAtTime(Runnable task, long time)postDelayed runs a task xmillisecondsafterthemethodiscalled.Forexample,ifyouwantaRunnabletostartfivesecondsfromnow,usethiscode.
Handlerhandler=newHandler();
handler.postDelayed(runnable, 5000);postAtTime runs a task at a certain time in thefuture.Forexample,ifyouwantatasktorunsixsecondslater,writethis.
Handlerhandler=newHandler();
handler.postAtTime(runnable,6000+System.currentTimeMillis());
ExampleAs an example, consider theHandlerDemo project that usesHandler to animate anImageView. The animation performed is simple: show an image for 400milliseconds,thenhide it for400milliseconds, and repeat this five times.Theentire taskwould takeaboutfoursecondsifalltheworkisdoneinaforloopthatsleepsfor400millisecondsateachiteration.UsingtheHandler,however,youcansplit this into10smallerparts thateachtakeslessthanonemillisecond(theexacttimewoulddependonthedevicerunningit).TheUI thread is releasedduringeach400mswait so that itcancater for somethingelse.
Note
Android offers animation APIs that you should use for all animation tasks. ThisexampleusesHandlertoanimateacontrolsimplytoillustratetheuseofHandler.
Listing22.1showsthemanifest(theAndroidManifest.xmlfile)fortheproject.
Listing22.1:ThemanifestforHandlerDemo
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.handlerdemo”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“8”
android:targetSdkVersion=“17”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=”.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
Nothing spectacular in the manifest. It shows that there is one activity namedMainActivity.ThelayoutfilefortheactivityisgiveninListing22.2.
Listing22.2:Theres/layout/activity_main.xmlfileinHandlerTest
<RelativeLayoutxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingBottom=”@dimen/activity_vertical_margin”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
tools:context=”.MainActivity”>
<ImageView
android:id=”@+id/imageView1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignParentLeft=“true”
android:layout_alignParentTop=“true”
android:layout_marginLeft=“51dp”
android:layout_marginTop=“58dp”
android:src=”@drawable/surprise”/>
<Button
android:id=”@+id/button1”
style=”?android:attr/buttonStyleSmall”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_alignRight=”@+id/imageView1”
android:layout_below=”@+id/imageView1”
android:layout_marginRight=“18dp”
android:layout_marginTop=“65dp”
android:onClick=“buttonClicked”
android:text=“Button”/>
</RelativeLayout>
ThemainlayoutforMainActivityisaRelativeLayoutthatcontainsanImageViewtobeanimatedandabuttontostartanimation.
Now look at theMainActivity class in Listing 22.3. This is the main core of theapplication.
Listing22.3:TheMainActivityclassinHandlerDemo
packagecom.example.handlerdemo;
importandroid.app.Activity;
importandroid.os.Bundle;
importandroid.os.Handler;
importandroid.view.Menu;
importandroid.view.View;
importandroid.widget.ImageView;
publicclassMainActivityextendsActivity{
intcounter=0;
Handlerhandler=newHandler();
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
getUserAttention();
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
//Inflatethemenu;thisaddsitemstotheactionbarifit
//ispresent.
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
publicvoidbuttonClicked(Viewview){
counter=0;
getUserAttention();
}
privatevoidgetUserAttention(){
handler.post(task);
}
Runnabletask=newRunnable(){
@Override
publicvoidrun(){
ImageViewimageView=(ImageView)
findViewById(R.id.imageView1);
if(counter%2==0){
imageView.setVisibility(View.INVISIBLE);
}else{
imageView.setVisibility(View.VISIBLE);
}
counter++;
if(counter<8){
handler.postDelayed(this,400);
}
}
};
}
ThebrainofthisactivityareaRunnablecalledtask,whichanimatestheImageView,andthe getUserAttention method that calls the postDelayed method on aHandler. TheRunnable sets theImageView’svisibility toVisibleorInvisibledependingonwhetherthevalueofthecountervariableisoddoreven.
IfyouruntheHandlerDemoproject,you’llseesomethingsimilartothescreenshotinFigure 22.1. Note how the ImageView flashes to get your attention. Try clicking thebuttonseveraltimesquicklytomaketheimageflashfaster.Canyouexplainwhyitgoesfasterasyouclick?
Figure22.1:TheHandlerTestapplication
SummaryIn thischapteryoulearnedabout theHandlerclassandwriteanapplicationthatmakesuseoftheclass.
Chapter23AsynchronousTasks
Thischapter talksaboutasynchronous tasksandexplainshow tohandle themusing theAsyncTaskclass.Italsopresentsaphotoeditorapplicationthatillustrateshowthisclassshouldbeused.
OverviewTheandroid.os.AsyncTaskclassisautilityclassthatmakesiteasytohandlebackgroundprocesses and publish progress updates on theUI thread. This class ismeant for shortoperationsthatlastatmostafewseconds.Forlong-runningbackgroundtasks,youshouldusetheJavaConcurrencyUtilitiesframework.
The AsyncTask class comes with a set of public methods and a set of protectedmethods. The public methods are for executing and canceling its task. The executemethodstartsanasynchronousoperationandcancelcancelsit.Theprotectedmethodsareforyou tooverride ina subclass.ThedoInBackgroundmethod,aprotectedmethod, isthe most important method in this class and provides the logic for the asynchronousoperation.
There is alsoapublishProgressmethod, also a protectedmethod,which is normallycalledmultipletimesfromdoInBackground.Typically,youwillwritecode toupdateaprogressbarorsomeotherUIcomponenthere.
Then thereare twoonCancelledmethods foryou towritewhat shouldhappen if theoperationwascanceled(i.e.iftheAsyncTask’scancelmethodwascalled).
ExampleAs an example, the PhotoEditor application that accompanies this book uses theAsyncTaskclasstoperformimageoperationsthateachtakesafewseconds.AsyncTaskis used so as not to jam the UI thread. Two image operations, invert and blur, aresupported.
Theapplicationmanifest(theAndroidManifest.xmlfile)isprintedinListing23.1.
Listing23.1:ThemanifestforPhotoEditor
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.photoeditor”
android:versionCode=“1”
android:versionName=“1.0”>
<uses-sdk
android:minSdkVersion=“8”
android:targetSdkVersion=“17”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.photoeditor.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
The layout file, printed in Listing 23.2, shows that the application uses a verticalLinearLayout tohouseanImageView,aProgressBar, and twobuttons.The latter arecontainedinahorizontalLinearLayout.Thefirstbuttonisusedtostartthebluroperationandthesecondtostarttheinvertoperation.
Listing23.2:Theres/layout/activity_main.xmlfileinPhotoEditor
<LinearLayoutxmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“fill_parent”
android:layout_height=“fill_parent”
android:orientation=“vertical”
android:paddingLeft=“16dp”
android:paddingRight=“16dp”>
<LinearLayout
android:layout_height=“wrap_content”
android:layout_width=“fill_parent”
android:orientation=“horizontal”>
<Button
android:id=”@+id/blurButton”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:onClick=“doBlur”
android:text=”@string/blur_button_text”/>
<Button
android:id=”@+id/button2”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:onClick=“doInvert”
android:text=”@string/invert_button_text”/>
</LinearLayout>
<ProgressBar
android:id=”@+id/progressBar1”
style=”?android:attr/progressBarStyleHorizontal”
android:layout_width=“fill_parent”
android:layout_height=“10dp”/>
<ImageView
android:id=”@+id/imageView1”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:layout_gravity=“top|center”
android:src=”@drawable/photo1”/>
</LinearLayout>
Finally,theMainActivityclassforthisprojectisgiveninListing23.3.
Listing23.3:TheMainActivityclassinPhotoEditor
packagecom.example.photoeditor;
importandroid.app.Activity;
importandroid.graphics.Bitmap;
importandroid.graphics.drawable.BitmapDrawable;
importandroid.os.AsyncTask;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.View;
importandroid.widget.ImageView;
importandroid.widget.ProgressBar;
publicclassMainActivityextendsActivity{
privateProgressBarprogressBar;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
progressBar=(ProgressBar)findViewById(R.id.progressBar1);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
//Inflatethemenu;thisaddsitemstotheactionbarifit
//ispresent.
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
publicvoiddoBlur(Viewview){
BlurImageTasktask=newBlurImageTask();
ImageViewimageView=(ImageView)
findViewById(R.id.imageView1);
Bitmapbitmap=((BitmapDrawable)
imageView.getDrawable()).getBitmap();
task.execute(bitmap);
}
publicvoiddoInvert(Viewview){
InvertImageTasktask=newInvertImageTask();
ImageViewimageView=(ImageView)
findViewById(R.id.imageView1);
Bitmapbitmap=((BitmapDrawable)
imageView.getDrawable()).getBitmap();
task.execute(bitmap);
}
privateclassInvertImageTaskextendsAsyncTask<Bitmap,Integer,
Bitmap>{
protectedBitmapdoInBackground(Bitmap…bitmap){
Bitmapinput=bitmap[0];
Bitmapresult=input.copy(input.getConfig(),
/*isMutable’*/true);
intwidth=input.getWidth();
intheight=input.getHeight();
for(inti=0;i<height;i++){
for(intj=0;j<width;j++){
intpixel=input.getPixel(j,i);
inta=pixel&0xff000000;
a=a|(~pixel&0x00ffffff);
result.setPixel(j,i,a);
}
intprogress=(int)(100*(i+1)/height);
publishProgress(progress);
}
returnresult;
}
protectedvoidonProgressUpdate(Integer…values){
progressBar.setProgress(values[0]);
}
protectedvoidonPostExecute(Bitmapresult){
ImageViewimageView=(ImageView)
findViewById(R.id.imageView1);
imageView.setImageBitmap(result);
progressBar.setProgress(0);
}
}
privateclassBlurImageTaskextendsAsyncTask<Bitmap,Integer,
Bitmap>{
protectedBitmapdoInBackground(Bitmap…bitmap){
Bitmapinput=bitmap[0];
Bitmapresult=input.copy(input.getConfig(),
/*isMutable=*/true);
intwidth=bitmap[0].getWidth();
intheight=bitmap[0].getHeight();
intlevel=7;
for(inti=0;i<height;i++){
for(intj=0;j<width;j++){
intpixel=bitmap[0].getPixel(j,i);
inta=pixel&0xff000000;
intr=(pixel>>16)&0xff;
intg=(pixel>>8)&0xff;
intb=pixel&0xff;
r=(r+level)/2;
g=(g+level)/2;
b=(b+level)/2;
intgray=a|(r<<16)|(g<<8)|b;
result.setPixel(j,i,gray);
}
intprogress=(int)(100*(i+1)/height);
publishProgress(progress);
}
returnresult;
}
protectedvoidonProgressUpdate(Integer…values){
progressBar.setProgress(values[0]);
}
protectedvoidonPostExecute(Bitmapresult){
ImageViewimageView=(ImageView)
findViewById(R.id.imageView1);
imageView.setImageBitmap(result);
progressBar.setProgress(0);
}
}
}
The MainActivity class contains two private classes, InvertImageTask andBlurImageTask,whichextendAsyncTask.TheInvertImageTasktaskisexecutedwhentheInvertbuttonisclickedandtheBlurImageTaskwhentheBlurbuttonisclicked.
ThedoInBackgroundmethod ineach taskprocesses theImageViewbitmap inaforloop.AteachiterationitcallsthepublishProgressmethodtoupdatetheprogressbar.
Figure23.1showstheinitialbitmapandFigure23.2showsthebitmapafteraninvertoperation.
Figure23.1:TheImageEditorapplication
Figure23.2:Thebitmapafterinvert
SummaryIn this chapter you learned to use the AsyncTask class and created a photo editorapplicationthatusesit.
Chapter24Services
Sofarinthisbook,everythingyouhavelearnedisrelatedtoactivities.ItisnowtimetopresentanotherAndroidcomponent,theservice.Aservicehasnouserinterfaceandrunsinthebackground.Itissuitableforlong-runningoperations.Thischapterexplainshowtocreateaserviceandprovidesanexample.
OverviewAsalreadymentioned,aserviceisacomponentthatperformalongrunningoperationinthebackground.Aservicewillcontinuetorunevenaftertheapplicationthatstartedithasbeenstopped.Aservicerunsonthesameprocessastheapplicationinwhichtheserviceisdeclaredand in theapplication’smain thread.Assuch, if a service takesa long time tocomplete, it should runon a separate thread.Thegood thing is, running a service on aseparatethreadiseasyifyouextendacertainclassintheServiceAPI.
Aservicecantakeoneoftwoforms.Itcanbestartedorbound.Aserviceisstartedifanother component starts it. It can run in the background indefinitely even after thecomponent that started it is no longer in service or destroyed.A service is bound if anapplication component binds to it.A bound service acts like a server in a client-serverrelationship, taking requests fromotherapplicationcomponentsand returning results.Aservicecanalsobestartedandbound.
Intermsofaccessibility,aservicecanbemadeprivateorpublic.Apublicservicecanbeinvokedbyanyapplication.Aprivateservice,ontheotherhand,canonlybeinvokedbyacomponentinthesameapplicationinwhichtheserviceisdeclared.
TheServiceAPITocreateaserviceyoumustwriteaclassthatextendsandroid.app.Serviceoritssubclassandroid.app.IntentService.SubclassingIntentService is easier because it requires youtooverridefewermethods.However,extendingServiceallowsyoumorecontrol.
IfyoudecidetosubclassService,youmayneedtooverridethecallbackmethodsinit.ThesemethodsarelistedinTable24.1.
Method
Description
onStartCommand
Thismethodiscalledwhenanotherapplicationcomponentcallstheservice’sstartServicetostartit.
onBind Thismethodisinvokedwhenanotherapplicationcomponentcallestheservice’sbindServicetobindwithit.
onCreate
Thismethodisinvokedwhentheserviceisfirstcreated.
onDestroy
Thismethodisinvokedwhentheserviceisbeingdestroyed.
Table24.1:TheServiceclass’scallbackmethods
IfyouextendIntentService, youhave tooverride its abstractmethodonHandleIntent.Hereisthesignatureofthismethod.
protectedabstractvoidonHandleIntent(
android.content.Intentintent)
TheimplementationofonHandleIntentshouldcontaincodethatneedstobeexecutedbytheservice.AlsonotethatonHandleIntentisalwaysrunonaseparateworkerthread.
DeclaringAServiceA service must be declared in the manifest using the service element under<application>.TheattributesthatmayappearintheserviceelementareshowninTable24.2.
Attribute
Description
enabled
Indicateswhethertheserviceshouldbeenabled.Thevalueiseithertrue(default)orfalse.
exported
Acceptsavalueoftrueorfalsetoindicatewhetherornottheservicecanbestartedorinvokedfromotherapplications.
icon
Aniconrepresentingthisservice.
isolatedProcess
Acceptsavalueoftrueorfalseindicatingwhethertheserviceshouldberunasaseparateprocess.
label
Alabelforthisservice.
name
Thefully-qualifiednameoftheserviceclass.
permission
Thenameofapermissionthatthatanentitymusthaveinordertolaunchtheserviceorbindtoit.
process
Thenameoftheprocesswheretheserviceistorun.
Table24.2:Theattributesoftheserviceelement
For example, here is the declaration of a service element that can be invoked by otherapplications.
<application>
…
<serviceandroid:name=“com.example.MyService”
android:exported=“true”/>
</application>
AServiceExampleThisexampleisanAndroidapplicationthatletsyoudownloadwebpagesandstorethemforofflineviewingwhenyouhavenoInternetaccess.
Thereare twoactivitiesandoneservice. In themainactivity (shown inFigure24.1),youcanenterURLsofthewebsiteswhosecontentsyouwanttostoreinyourdevice.Justtype aURL in each line and click theFETCHWEBPAGES button to start theURLService.
Figure24.1:TheMainactivity
ClickVIEWPAGESontheactionbartoviewthestoredcontents.YouwillseethesecondactivitylikethatshowninFigure24.2.
Figure24.2:TheViewactivity
TheViewactivity’sviewareaconsistsofaSpinnerandaWebView.Thespinnercontainsencoded URLs that have been fetched. Select a URL to display the content in theWebView.
Nowthatyouhaveanideaofwhattheappdoes,let’stakealookatthecode.
Asusual,youstartfromthemanifest.ItdescribestheapplicationandislistedinListing24.1.
Listing24.1:Themanifest
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.urlservice”>
<uses-permissionandroid:name=“android.permission.INTERNET”/>
<uses-permission
android:name=“android.permission.ACCESS_NETWORK_STATE”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=”.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<activity
android:name=”.ViewActivity”
android:parentActivityName=”.MainActivity”
android:label=”@string/title_activity_view”>
</activity>
<service
android:name=”.URLService”
android:exported=“true”/>
</application>
</manifest>
As you can see the application element contains two activity elements and a serviceelement.There are also twouses-permission elements togive the application to accessthe Internet. They are android.permission. INTERNET andandroid.permission.ACCESS_NETWORK_STATE.
Listing24.1:Themainactivityclass
packagecom.example.urlservice;
importandroid.content.Intent;
importandroid.os.StrictMode;
importandroid.support.v7.app.ActionBarActivity;
importandroid.os.Bundle;
importandroid.util.Log;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.widget.EditText;
publicclassMainActivityextendsActionBarActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
StrictMode.ThreadPolicypolicy=new
StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
intid=item.getItemId();
if(id==R.id.action_view){
Intentintent=newIntent(this,ViewActivity.class);
startActivity(intent);
returntrue;
}
returnsuper.onOptionsItemSelected(item);
}
publicvoidfetchWebPages(Viewview){
EditTexteditText=(EditText)findViewById(R.id.urlsEditText);
Intentintent=newIntent(this,URLService.class);
intent.putExtra(“urls”,editText.getText().toString());
startService(intent);
}
}
Listing24.2:Theviewactivityclass
packagecom.example.urlservice;
importandroid.os.Bundle;
importandroid.support.v7.app.ActionBarActivity;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.webkit.WebView;
importandroid.widget.AdapterView;
importandroid.widget.ArrayAdapter;
importandroid.widget.Spinner;
importjava.io.BufferedReader;
importjava.io.File;
importjava.io.FileNotFoundException;
importjava.io.FileReader;
importjava.io.IOException;
publicclassViewActivityextendsActionBarActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_view);
Spinnerspinner=(Spinner)findViewById(R.id.spinner);
FilesaveDir=getFilesDir();
if(saveDir.exists()){
Filedir=newFile(saveDir,“URLService”);
dir=saveDir;
if(dir.exists()){
String[]files=dir.list();
ArrayAdapter<String>dataAdapter=
newArrayAdapter<String>(this,
android.R.layout.simple_spinner_item,files);
dataAdapter.setDropDownViewResource(
android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(dataAdapter);
spinner.setOnItemSelectedListener(
newAdapterView.OnItemSelectedListener(){
@Override
publicvoidonItemSelected(AdapterView<?>
adapterView,Viewview,intpos,
longid){
//openfile
ObjectitemAtPosition=adapterView
.getItemAtPosition(pos);
Filefile=newFile(getFilesDir(),
itemAtPosition.toString());
FileReaderfileReader=null;
BufferedReaderbufferedReader=null;
try{
fileReader=newFileReader(file);
bufferedReader=
newBufferedReader(fileReader);
StringBuildersb=newStringBuilder();
Stringline=bufferedReader.readLine();
while(line!=null){
sb.append(line);
line=bufferedReader.readLine();
}
WebViewwebView=(WebView)
findViewById(R.id.webview);
webView.loadData(sb.toString(),
“text/html”,“utf-8”);
}catch(FileNotFoundExceptione){
}catch(IOExceptione){
}
}
@Override
publicvoidonNothingSelected(AdapterView<?>
adapterView){
}
});
}
}
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_view,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
intid=item.getItemId();
returnsuper.onOptionsItemSelected(item);
}
}
Themostimportantpieceoftheapplication,theserviceclass,isshowninListing24.3.ItextendsIntentServiceandimplementsitsonHandleIntentmethod.
Listing24.3:Theserviceclass
packagecom.example.urlservice;
importandroid.app.IntentService;
importandroid.content.Intent;
importjava.io.BufferedReader;
importjava.io.File;
importjava.io.InputStreamReader;
importjava.io.PrintWriter;
importjava.net.MalformedURLException;
importjava.net.URL;
importjava.util.StringTokenizer;
publicclassURLServiceextendsIntentService{
publicURLService(){
super(“URLService”);
}
@Override
protectedvoidonHandleIntent(Intentintent){
Stringurls=intent.getStringExtra(“urls”);
if(urls==null){
return;
}
StringTokenizertokenizer=newStringTokenizer(urls);
inttokenCount=tokenizer.countTokens();
intindex=0;
String[]targets=newString[tokenCount];
while(tokenizer.hasMoreTokens()){
targets[index++]=tokenizer.nextToken();
}
FilesaveDir=getFilesDir();
fetchPagesAndSave(saveDir,targets);
}
privatevoidfetchPagesAndSave(FilesaveDir,String[]targets){
for(Stringtarget:targets){
URLurl=null;
try{
url=newURL(target);
}catch(MalformedURLExceptione){
e.printStackTrace();
}
StringfileName=target.replaceAll(“/”,”-“)
.replaceAll(“:”,”-“);
Filefile=newFile(saveDir,fileName);
PrintWriterwriter=null;
BufferedReaderreader=null;
try{
writer=newPrintWriter(file);
reader=newBufferedReader(
newInputStreamReader(url.openStream()));
Stringline;
while((line=reader.readLine())!=null){
writer.write(line);
}
}catch(Exceptione){
}finally{
if(writer!=null){
try{
writer.close();
}catch(Exceptione){
}
}
if(reader!=null){
try{
reader.close();
}catch(Exceptione){
}
}
}
}
}
}
The onHandleIntent method receives an array of URLs and uses a StringTokenizer toextract each URL from the array. Each URL is used to populate a string array namedtargets,whichisthenpassedtothefetchPagesAndSavemethod.Thismethodemploysajava.net.URL to sendanHTTPrequest foreach targetandsaves itscontent in internalstorage.
SummaryAserviceisanapplicationcomponentthatrunsinthebackground.Despitethefactthatitrunsinthebackground,aserviceisnotaprocessanddoesnotrunonaseparatethread.Instead,aservicerunsonthemainthreadoftheapplicationthatinvokedtheservice.
You can write a service by extending android.app.Service orandroid.app.IntentService.
Chapter25BroadcastReceivers
TheAndroid system constantly broadcasts intents that occur during the running of theoperating system and applications. In addition, applications can also broadcast user-defined intents.Youcancapitalizeon thesebroadcastsbywritingbroadcast receivers inyourapplication.
Thischapterexplainshowtocreatebroadcastreceivers.
OverviewAbroadcastreceiver,orareceiverforshort,isanapplicationcomponentthatlistenstoacertain intent broadcast, similar to Java listeners that listen to events.Table 25.1 showsintent actions defined in the android.content.Intent class for which you can write areceiver.
Action
Description
ACTION_TIME_TICK
Thecurrenttimehaschanged.Senteveryminute.
ACTION_TIME_CHANGED
Thetimehasbeenset.
ACTION_TIMEZONE_CHANGED
Thetimezonehaschanged.
ACTION_BOOT_COMPLETED
Thesystemhasfinishedbooting.
ACTION_PACKAGE_ADDED
Anewapplicationpackagehasbeeninstalledonthedevice.
ACTION_PACKAGE_CHANGED
Anapplicationpackagehasbeenchanged.
ACTION_PACKAGE_REMOVED
Anapplicationpackagehasbeenremoved.
ACTION_PACKAGE_RESTARTED Theuserhasrestartedapackage.
ACTION_PACKAGE_DATA_CLEARED
Theuserhasclearedthedataofapackage.
ACTION_UID_REMOVED
AuserUIDhasbeenremoved.
ACTION_BATTERY_CHANGED
Thebattery’schargingstate,levelorotherdetailhaschanged.
ACTION_POWER_CONNECTED
Externalpowerhasbeenconnectedtothedevice.
ACTION_POWER_DISCONNECTED
Externalpowerhasbeendisconnectedfromthedevice.
ACTION_SHUTDOWN
Thedeviceisabouttoshutdown
Table25.1:Intentactionsforreceivingabroadcast
To create a receiver, youmust extend theandroid.content.BroadcastReceiver class orone of its subclasses. In your class, you must provide an implementation for theonReceivemethod,whichgetscalledwhenanintentforwhichthereceiverisregisteredisbroadcast.ThesignatureofonReceiveisasfollows.
publicabstractvoidonReceive(Contextcontext,Intentintent)
YouthenhavetoregisteryourclassintheapplicationmanifestusingthereceiverelementorprogrammaticallybycallingContext.registerReceiver().
BroadcastReceiver-basedClockAndroidcomeswithwidgetsthatcanshowtime.However,youcanalsocreateyourownclockwidgetthatisbasedontheACTION_TIME_TICKbroadcast.Recallthatthisintentactionisbroadcasteveryminute,whichissuitableforaclock.
The BroadcastReceiverDemo1 project features such a clock. It is a simple app thatconsists of a broadcast receiver and an activity. The receiver class is instantiated andregistered every time the activity’sonResumemethod is called. It is deregisteredwhenonPauseisinvoked.
TheclassforthemainactivityisgiveninListing25.1
Listing25.1:TheMainActivityclass
packagecom.example.broadcastreceiverdemo1;
importjava.util.Calendar;
importandroid.app.Activity;
importandroid.content.BroadcastReceiver;
importandroid.content.Context;
importandroid.content.Intent;
importandroid.content.IntentFilter;
importandroid.os.Bundle;
importandroid.text.format.DateFormat;
importandroid.util.Log;
importandroid.view.Menu;
importandroid.widget.TextView;
publicclassMainActivityextendsActivity{
BroadcastReceiverreceiver;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
publicvoidonResume(){
super.onResume();
setTime();
receiver=newBroadcastReceiver(){
@Override
publicvoidonReceive(Contextcontext,Intentintent){
setTime();
}
};
IntentFilterintentFilter=newIntentFilter(
Intent.ACTION_TIME_TICK);
this.registerReceiver(receiver,intentFilter);
}
publicvoidonPause(){
this.unregisterReceiver(receiver);
super.onPause();
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
privatevoidsetTime(){
Calendarcalendar=Calendar.getInstance();
CharSequencenewTime=DateFormat.format(
“kk:mm”,calendar);
TextViewtextView=(TextView)findViewById(
R.id.textView1);
textView.setText(newTime);
}
}
AnimportantpartoftheapplicationistheonReceivemethodofthereceiver:
@Override
publicvoidonReceive(Contextcontext,Intentintent){
setTime();
}
ItisverysimplemethodwithonelineofcodethatcallsthesetTimemethod.ThesetTimemethodobtainsthecurrenttimefromaCalendarandupdatesaTextView.
Another importantpartof theapplication is thecode that registers the receiver in theactivity’sonResumemethod.To register a receiver, you need to create an IntentFilterspecifying an intent action thatwill cause the receiver to be triggered. In this case theintentactionisACTION_TIME_TICK.
IntentFilterintentFilter=newIntentFilter(
Intent.ACTION_TIME_TICK);
this.registerReceiver(receiver,intentFilter);
YouthenpassthereceiverandtheIntentFiltertoregisterthereceiver.
Figure25.1showsthebroadcastreceiver-basedclock.
Figure25.1:Areceiver-basedclock
CancelingANotificationChapter 3, “UI Components” explains the various Android UI components includingnotifications.Aproblem lingers:Touchinganotification’sactionUIdoesnotcancel thenotification.Onestrategytosolvethisissueisbysendingauser-definedbroadcastwhentheactionUIistouchedandwritingabroadcastforthat.
RecallthatanotificationactionrequiresaPendingIntentandaPendingIntentcanbeprogrammedtosendabroadcast.Tosolvetheproblem,createauser-definedintentactioncalledcancel_notificationandthecorrespondingPendingIntent:
IntentcancelIntent=newIntent(“cancel_notification”);
PendingIntentcancelPendingIntent=
PendingIntent.getBroadcast(this,100,cancelIntent,0);
ThisPendingIntentcanthenbeusedtoregisteranotification.
TheCancelNotificationDemoprojectshowshowthiscanbeachieved.Theapplicationismadesimpleandconsistsofanactivitythatcontainsabroadcastreceiver.
ThelayoutfileforthemainactivityisgiveninListing25.2.
Listing25.2:Thelayoutfileforthemainactivity
<LinearLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:orientation=“horizontal”>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:onClick=“setNotification”
android:text=“SetNotification”/>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:onClick=“clearNotification”
android:text=“ClearNotification”/>
</LinearLayout>
Thelayoutfeaturestwobuttons,oneforsettinganotificationandoneforcancalingit.
TheMainActivity class for the application is listed in Listing 25.3. The activity’sonCreate method instantiates a receiver whose onReceive method cancels thenotification.
Listing25.3:TheMainActivityclass
packagecom.example.cancelnotificationdemo;
importandroid.app.Activity;
importandroid.app.Notification;
importandroid.app.NotificationManager;
importandroid.app.PendingIntent;
importandroid.content.BroadcastReceiver;
importandroid.content.Context;
importandroid.content.Intent;
importandroid.content.IntentFilter;
importandroid.os.Bundle;
importandroid.util.Log;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.view.View;
publicclassMainActivityextendsActivity{
privatestaticfinalStringCANCEL_NOTIFICATION_ACTION
=“cancel_notification”;
intnotificationId=1002;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
BroadcastReceiverreceiver=newBroadcastReceiver(){
@Override
publicvoidonReceive(Contextcontext,Intentintent){
NotificationManagernotificationManager=
(NotificationManager)getSystemService(
NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId);
}
};
IntentFilterfilter=newIntentFilter();
filter.addAction(CANCEL_NOTIFICATION_ACTION);
this.registerReceiver(receiver,filter);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
publicvoidsetNotification(Viewview){
IntentcancelIntent=newIntent(“cancel_notification”);
PendingIntentcancelPendingIntent=
PendingIntent.getBroadcast(this,100,
cancelIntent,0);
Notificationnotification=newNotification.Builder(this)
.setContentTitle(“StopPress”)
.setContentText(
“Everyonegetsextravacationweek!”)
.setSmallIcon(android.R.drawable.star_on)
.setAutoCancel(true)
.addAction(android.R.drawable.btn_dialog,
“Dismiss”,cancelPendingIntent)
.build();
NotificationManagernotificationManager=
(NotificationManager)getSystemService(
NOTIFICATION_SERVICE);
notificationManager.notify(notificationId,notification);
}
publicvoidclearNotification(Viewview){
NotificationManagernotificationManager=
(NotificationManager)getSystemService(
NOTIFICATION_SERVICE);
notificationManager.cancel(notificationId);
}
}
Again,notethepartthatregisterthereceiver:
IntentFilterfilter=newIntentFilter();
filter.addAction(CANCEL_NOTIFICATION_ACTION);
this.registerReceiver(receiver,filter);
Here,IcreateanIntentFilterthatspecifiesauser-definedaction(cancel_notification)andpassitalongwiththereceivertotheregisterReceivermethod.
ThemainactivityisshowninFigure25.2.
Figure25.2:CancelNotificationDemo
NowtouchontheSetNotificationbuttonandopenthenotificationdrawer.Youshouldsee
anotificationlikethatshowninFigure25.3.
Figure25.3:Thenotificationdrawer
IfyoutouchontheDismissbutton,abroadcastwillbesentandreceivedbythereceiverintheactivity.Asaresult,thenotificationwillbecanceled.
SummaryA broadcast receiver is an application component that listens to intent broadcasts. Tocreateareceiveryoumustcreateaclass thatextendsandroid.content.BroadcastReceiverand implements its onReceive method. To register a receiver, you can either add areceiver element in the application manifest or do so programmatically by callingContext.registerReceiver().Ineithercase,youmustdefineanIntentFilterthatspecifieswhatintentshouldcausethereceivertobetriggered.
Chapter26TheAlarmService
Android devices maintain an internal alarm service that can be used to schedule jobs.Amazingly, asyouwill findout in this chapter, theAPI isveryeasy touse, seamlesslyhiding the complexity of its lower-level code. This chapter explains how to use it andpresentsanexample.
OverviewOneofthebuilt-inservicesavailabletoallAndroiddevelopersisthealarmservice.Withit you can schedule an action to take place at a later time. The operation can beprogrammed to be carried out once or repeatedly. The Clock application, for example,includesanalarmclockthatreliesonthisservice.
It is extremely easy to use. All you need is encapsulate the operation you intend toscheduleinaPendingIntentandpassittothesystem-wideAlarmManagerinstance.TheAlarmManagerclassispartoftheandroid.apppackageandaninstanceisalreadythere,maintainedbythesystem.YoucanretrievetheAlarmManagerbyusingthislineofcode:
AlarmManageralarmMgr=
(AlarmManager)getSystemService(Context.ALARM_SERVICE);
ThePendingIntent is explained in Chapter 3, “UIComponents,” but basically it is anintent to be invoked at a future time, hence the name PendingIntent. You can use aPendingIntenttostartanactivity,startaservice,orbroadcastanotification.
Toscheduleajob,callthesetorsetExactmethodofAlarmManager.Theirsignaturesareasfollows.
publicvoidset(inttype,longtriggerTime,PendingIntentoperation)
publicvoidsetExact(inttype,longtriggerTime,
PendingIntentoperation)
Asthename implies,setExact causes the system to try todeliver thealarmascloseaspossibletothespecifiedtriggertime.Ontheotherhand,thedeliveryofthejobpassedtosetmaybedeferredbutwillnotbeearlier.
For both methods, the type is one of the following constants declared inAlarmManager.
ELAPSED_REALTIME. The trigger time is a long representing the number ofmillisecondsthathaveelapsedsincethelastboot.Itdoesnowakeupthedeviceifthealarmgoesoffwhilethedeviceisasleep.ELAPSED_REALTIME_WAKEUP. The trigger time is a long representing thenumberofmillisecondsthathaveelapsedsincethelastbootItwakesupthedeviceif
thealarmgoesoffwhilethedeviceisasleep.RTC.The trigger time isa long representing thenumberofmilliseconds thathaveelapsedsinceJanuary1,197000:00:00.0UTC.Itdoesnotwakeupthedeviceifthealarmgoesoffwhilethedeviceisasleep.RTC_WAKEUP.ThetriggertimeisalongrepresentingthenumberofmillisecondsthathaveelapsedsinceJanuary1,197000:00:00.0UTC. Itwakesup thedevice ifthealarmgoesoffwhilethedeviceisasleep.
Forexample,toscheduleanjobtostartfivesecondsfromnow,usethis:
alarmManager.set(AlarmManager.RTC,System.currentTimeMillis()+
5000,pendingIntent)
Toschedulearepeatingjob,use thesetRepeatingorsetInexactRepeatingmethod.Thesignaturesofthesemethodsareasfollows.
publicvoidsetInexactRepeating(inttype,longtriggerAtMillis,
longintervalMillis,PendingIntentoperation)
publicvoidsetRepeating(inttype,longtriggerAtMillis,
longintervalMillis,PendingIntentoperation)
In Android API levels lower than 19, setRepeating delivers an exact delivery time.However, starting the API level 19, setRepeating is also inexact, so it is the same assetInexactRepeating.Foranexactrepeatingjob,scheduleitwithsetExact,andscheduleanewjobattheendoftheexecutionofthecurrentjob.
ExampleThefollowingexampleshowshowtoscheduleanalarmthatsetsoffinfiveminutes.ThisislikethealarmclockintheClockapplication,butsettinganalarmisassimpleasatouchofabutton.Theapplicationalsoshowshowtowakeupanactivitywhenthealarmsetsoffwhilethedeviceisasleep.
Theapplicationhas twoactivities,whicharedeclared in themanifest inListing26.1.ThefirstactivityisthemainactivitythatthatwillbelaunchedwhentheusertouchesontheapplicationiconontheHomescreen.Thesecondactivity,calledWakeUpActivity,istheactivitythatwillbestartedwhenanalarmsetsoff.
Listing26.1:Themanifest
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifest
xmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.alarmmanagerdemo1”>
<uses-permissionandroid:name=“android.permission.WAKE_LOCK”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=”.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<activity
android:name=”.WakeUpActivity”
android:label=”@string/title_activity_wake_up”>
</activity>
</application>
</manifest>
Themainactivitycontainsabutton,whichtheusercanpresstosetanalarm.TheactivitylayoutfileisshowninListing26.2.NotethatthebuttondeclarationincludestheonClickattributethatreferstheasetAlarmmethod.
Listing26.2:Thelayoutfileofthemainactivity
<RelativeLayout
xmlns:android=“http://schemas.android.com/apk/res/android”
xmlns:tools=“http://schemas.android.com/tools”
android:layout_width=“match_parent”
android:layout_height=“match_parent”
android:paddingLeft=”@dimen/activity_horizontal_margin”
android:paddingRight=”@dimen/activity_horizontal_margin”
android:paddingTop=”@dimen/activity_vertical_margin”
android:paddingBottom=”@dimen/activity_vertical_margin”
tools:context=”.MainActivity”>
<Button
android:layout_width=“wrap_content”
android:layout_height=“wrap_content”
android:text=“5MinuteAlarm”
android:id=”@+id/button”
android:layout_alignParentLeft=“true”
android:layout_alignParentStart=“true”
android:layout_marginTop=“77dp”
android:onClick=“setAlarm”/>
</RelativeLayout>
Listing26.3presentstheMainActivityclassfortheapplication.
Listing26.3:TheMainActivityclass
packagecom.example.alarmmanagerdemo1;
importandroid.app.Activity;
importandroid.app.AlarmManager;
importandroid.app.PendingIntent;
importandroid.content.Context;
importandroid.content.Intent;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.widget.Toast;
importjava.util.Calendar;
importjava.util.Date;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
intid=item.getItemId();
if(id==R.id.action_settings){
returntrue;
}
returnsuper.onOptionsItemSelected(item);
}
publicvoidsetAlarm(Viewview){
Calendarcalendar=Calendar.getInstance();
calendar.add(Calendar.MINUTE,5);
DatefiveMinutesLater=calendar.getTime();
Toast.makeText(this,“Thealarmwillsetoffat”+
fiveMinutesLater,Toast.LENGTH_LONG).show();
Intentintent=newIntent(this,WakeUpActivity.class);
PendingIntentsender=PendingIntent.getActivity(
this,0,intent,0);
AlarmManageralarmMgr=(AlarmManager)getSystemService(
Context.ALARM_SERVICE);
alarmMgr.set(AlarmManager.RTC_WAKEUP,
fiveMinutesLater.getTime(),sender);
}
}
LookatthesetAlarmmethodintheMainActivityclass.AftercreatingaDatethatpointsto a time fiveminutes fromnow, themethodcreates aPendingIntent encapsulating anIntentthatwilllaunchtheWakeUpActivityactivity.
Intentintent=newIntent(this,WakeUpActivity.class);
PendingIntentsender=PendingIntent.getActivity(
this,0,intent,0);
It then retrieves the AlarmManager and set an alarm by passing the time and thePendingIntent.
AlarmManageralarmMgr=(AlarmManager)getSystemService(
Context.ALARM_SERVICE);
alarmMgr.set(AlarmManager.RTC_WAKEUP,
fiveMinutesLater.getTime(),sender);
Finally,Listing26.4showstheWakeUpActivityclass.
Listing26.4:TheWakeUpActivityclass
packagecom.example.alarmmanagerdemo1;
importandroid.app.Activity;
importandroid.app.Notification;
importandroid.app.NotificationManager;
importandroid.os.Bundle;
importandroid.util.Log;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.view.Window;
importandroid.view.WindowManager;
publicclassWakeUpActivityextendsActivity{
privatefinalintNOTIFICATION_ID=1004;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
finalWindowwindow=getWindow();
Log.d(“wakeup”,“called.oncreate”);
window.addFlags(
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
|WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
|WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
setContentView(R.layout.activity_wake_up);
addNotification();
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_wake_up,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
intid=item.getItemId();
if(id==R.id.action_settings){
returntrue;
}
returnsuper.onOptionsItemSelected(item);
}
publicvoiddismiss(Viewview){
NotificationManagernotificationMgr=(NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
notificationMgr.cancel(NOTIFICATION_ID);
this.finish();
}
privatevoidaddNotification(){
NotificationManagernotificationMgr=(NotificationManager)
getSystemService(NOTIFICATION_SERVICE);
Notificationnotification=newNotification.Builder(this)
.setContentTitle(“Wakeup”)
.setSmallIcon(android.R.drawable.star_on)
.setAutoCancel(false)
.build();
notification.defaults|=Notification.DEFAULT_SOUND;
notification.defaults|=Notification.DEFAULT_LIGHTS;
notification.defaults|=Notification.DEFAULT_VIBRATE;
notification.flags|=Notification.FLAG_INSISTENT;
notification.flags|=Notification.FLAG_AUTO_CANCEL;
notificationMgr.notify(NOTIFICATION_ID,notification);
}
}
Atfirstblush,theWakeUpActivityclasslookslikeotheractivitiesyou’vewrittensofar,but take a close look at closely at theonCreatemethod. The following code that addsflagstothewindowisneededtowakeupthedeviceandshowtheactivityifthedeviceisasleepwhenthealarmsetsoff.
finalWindowwindow=getWindow();
Log.d(“wakeup”,“called.oncreate”);
window.addFlags(
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
|WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
|WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
setContentView(R.layout.activity_wake_up);
It then calls the private addNotification method to add a notification. Remember thatChapter3,“UIComponents”explainshowtousenotifications.
Figure26.1showstheapplication’smainactivity.Touchthebuttontosetanalarm.
Figure26.1:A5-minutealarm
SummaryThealarmserviceisoneofthebuilt-inservicesavailabletoAndroiddevelopers.Withityoucanscheduleanactiontotakeplaceatalatertime.Theoperationcanbeprogrammedtobecarriedoutonceorrepeatedly.
Chapter27ContentProviders
A content provider is an Android component used for encapsulating data that is to besharedwithotherapplications.Howtheactualdataisstored,beitinarelationaldatabaseora fileoramixofboth, isnot important.What is important is thatacontentprovideroffersastandardwayofaccessingdatainotherapplications.
This chapter discusses the content provider and explains how to access data in aproviderusingacontentresolver.
OverviewYoualreadylearnedhowtostorefilesanddatainarelationaldatabase.Ifyourdataneedsto be sharedwith other applications, you need a content provider that encapsulates thestoreddata.Donotuseacontentprovider ifyourdata is tobeconsumedonlybyothercomponentsinthesameapplication.
Tocreateacontentprovider,youextendtheandroid.content.ContentProviderclass.ThisclassoffersCRUDmethods,namelymethodsforcreating, retrieving,updatinganddeleting data. Then, the subclass of ContentProvider has to be registered in theapplicationmanifestusingtheproviderelement,locatedunder<application>.Thisclassisdiscussedfurtherinthenextsection.
Once a content provider is registered, components under the same application canaccess it, but not other applications. To offer the data to other applications, you mustdeclare a read permission and a write permission. Alternatively, you can declare onepermissionforbothreadandwrite.Hereisanexample:
<provider
android:name=”.provider.ElectricCarContentProvider”
android:authorities=“com.example.contentproviderdemo1”
android:enabled=“true”
android:exported=“true”
android:readPermission=“com.example.permission.READ_DATA”
android:writePermission=“com.example.permission.WRITE_DATA”>
</provider>
Inaddition,youneedtousethepermissionelementtore-declarethepermissionsinyourmanifest:
<permission
android:name=“com.example.permission.READ_DATA”
android:protectionLevel=“normal”/>
<permission
android:name=“com.example.permission.WRITE_DATA”
android:protectionLevel=“normal”/>
<application…/>
Thepermission names can be anything as long as it does not conflictwith the existingones.Assuch,itisagoodideatoincludeyourdomainaspartofyourpermissionnames.
DatainacontentproviderisreferencedbyauniqueURI.TheconsumersofacontentprovidermustknowthisURIinordertoaccessthecontentprovider’sdata.
Theapplicationcontainingacontentproviderdoesnotneedtoberunningforitsdatatobeaccessed.
Androidcomeswithanumberofdefaultcontentproviders,suchasCalendar,Contacts,WordDictionary, etc. To access a content provider, you use theandroid.content.ContentResolver object that you can retrieve by callingContext.getContentResolver(). Among methods in the ContentResolver class aremethods with identical names as the CRUD methods in the ContentProvider class.Calling one of these methods on the ContentResolver invokes the identically-namedmethodinthetargetContentProvider.
Anapplicationneedingaccesstodatainacontentprovidermustdeclarethatitintendstousethedata,sotheuserinstallingtheappisawareofwhatdatawillbeexposedtotheapplication. The consuming application must use the uses-permission element in itsmanifest.Hereisanexample.
<uses-permission
android:name=“com.example.permission.READ_DATA”/>
<uses-permission
android:name=“com.example.permission.WRITE_DATA”/>
TheContentProviderClassThissectionintroducestheCRUDmethodsintheContentProviderclass.Primarily,youneedtoknowhowtoaccesstheunderlyingdatawhenoverridingthesemethods.Youcanstorethedatainanyformat,but,asyouwillsoonfindout,itmakesperfectsensetostorethedatainarelationaldatabase.
ThedatainacontentproviderisidentifiedbyURIshavingthisformat:
content://authority/table
The authority serves as anAndroid internal name and should be your domain name isreverse.Rightafteritisthetablename.
Torefertoasingledataitem,youusethisformat:
content://authority/table/index
Forexample,supposetheauthorityiscom.example.providerandthedata isstoredinarelationaldatabasetablenamedcustomers,thefirstrowisidentifiedbythisURI:
content://com.example.provider/customers/1
The rest of the section discusses ContentProvider methods for accessing andmanipulatingtheunderlyingdata.
ThequeryMethodToaccesstheunderlyingdata,usethequerymethod.Hereisitssignature:
publicabstractandroid.database.Cursorquery(android.net.Uriuri,
java.lang.String[]projection,java.lang.Stringselection,
java.lang.String[]selectionArgs,
java.lang.StringsortOrder)
uri is theURI identifying the data. The projection is an array containing names of thecolumns to be included. The selection defines which data items to select and theselectionArgs contains arguments for the selection. Finally, the sortOrder defines thecolumnbasedonwhichthedataistobesorted.
TheinsertMethodTheinsertmethodiscalledtoaddadataitem.Thesignatureofthismethodisasfollows.
publicabstractandroid.net.Uriinsert(android.net.Uriuri,
ContentValuesvalues)
Youpasscolumnkey/valuepairsinaContentValuesobjecttothismethod.UsetheputmethodsofContentValuestoaddakey/valuepair.
TheupdateMethodYou use thismethod to update a data item or a set of data items. The signature of themethod allows you to pass new values in a ContentValues as well as a selection todeterminewhichdataitemswillbeaffected.Hereisthesignatureofupdate.
publicabstractintupdate(android.net.Uriuri,
ContentValuesvalues,java.lang.Stringselection,
java.lang.String[]selectionArgs)
Theupdatemethodreturnsthenumberofdataitemsaffected.
ThedeleteMethodThedeletemethoddeletesadataitemorasetofdataitems.Youcanpassaselectionandselectionargumentstotellthecontentproviderwhichdataitemsshouldbedeleted.Hereisthesignatureofdelete.
publicabstractintdelete(android.net.Uriuri,
java.lang.Stringselection,
java.lang.String[]selectionArgs)
Thedeletemethodreturnsthenumberofrecordsdeleted.
CreatingAContentProvider
TheContentProviderDemo1 project is an application that contains a provider and threeactivities.Theappisforgreencarenthusiastsandallowstheusertomanageelectriccars.The underlying data is stored in a SQLite database. As the activities are in the sameapplicationas theprovider, theydonotneedspecialpermissionstoaccess thedata.TheContentResolverDemo1projectinthenextsectiondemonstrateshowtoaccessthecontentproviderfromadifferentapplication.
Asalways, Iwillstartbyshowing theapplicationmanifest,which isgiven inListing27.1.
Listing27.1:ThemanifestofContentProviderDemo1
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.contentproviderdemo1”>
<permission
android:name=“com.example.permission.READ_ELECTRIC_CARS”
android:protectionLevel=“normal”/>
<permission
android:name=“com.example.permission.WRITE_ELECTRIC_CARS”
android:protectionLevel=“normal”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=”.activity.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<category
android:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
<activity
android:name=”.activity.AddElectricCarActivity”
android:parentActivityName=”.activity.MainActivity”
android:label=”@string/app_name”>
</activity>
<activity
android:name=”.activity.ShowElectricCarActivity”
android:parentActivityName=”.activity.MainActivity”
android:label=”@string/app_name”>
</activity>
<provider
android:name=”.provider.ElectricCarContentProvider”
android:authorities=“com.example.contentproviderdemo1”
android:enabled=“true”
android:exported=“true”
android:readPermission=“com.example.permission.
READ_ELECTRIC_CARS”
android:writePermission=“com.example.permission.
WRITE_ELECTRIC_CARS”>
</provider>
</application>
</manifest>
Payspecialattention to the lines inbold.Under<application> therearedeclarationsofthree activities and a provider. There are also twopermission elements that define thepermissionsthatexternalapplicationsneedtorequesttoaccessthecontentprovider.
Thecontentprovider,representedbytheElectricCarContentProviderclass,isshownin Listing 27.2. Note the static final CONTENT_URI that defines the URI for theprovider.NotealsothatElectricCarContentProviderusesadatabasemanagerthattakescareofdataaccessandmanipulation.
Listing27.2:Thecontentprovider
packagecom.example.contentproviderdemo1.provider;
importandroid.content.ContentProvider;
importandroid.content.ContentUris;
importandroid.content.ContentValues;
importandroid.database.Cursor;
importandroid.net.Uri;
importandroid.util.Log;
publicclassElectricCarContentProviderextendsContentProvider{
publicstaticfinalUriCONTENT_URI=
Uri.parse(“content://com.example.contentproviderdemo1”
+”/electric_cars”);
publicElectricCarContentProvider(){
}
@Override
publicintdelete(Uriuri,Stringselection,
String[]selectionArgs){
Stringid=uri.getPathSegments().get(1);
returndbMgr.deleteElectricCar(id);
}
@Override
publicStringgetType(Uriuri){
thrownewUnsupportedOperationException(“Notimplemented”);
}
@Override
publicUriinsert(Uriuri,ContentValuesvalues){
longid=getDatabaseManager().addElectricCar(values);
returnContentUris.withAppendedId(CONTENT_URI,id);
}
@Override
publicbooleanonCreate(){
//initializecontentprovideronstartup
//forthisexample,nothingtodo
returntrue;
}
@Override
publicCursorquery(Uriuri,String[]projection,
Stringselection,
String[]selectionArgs,
StringsortOrder){
if(uri.equals(CONTENT_URI)){
returngetDatabaseManager()
.getElectricCarsCursor(projection,selection,
selectionArgs,sortOrder);
}else{
returnnull;
}
}
@Override
publicintupdate(Uriuri,ContentValuesvalues,
Stringselection,
String[]selectionArgs){
Stringid=uri.getPathSegments().get(1);
Log.d(“provider”,“updateinCP.uri:”+uri);
DatabaseManagerdatabaseManager=getDatabaseManager();
Stringmake=values.getAsString(“make”);
Stringmodel=values.getAsString(“model”);
returndatabaseManager.updateElectricCar(id,make,model);
}
privateDatabaseManagerdbMgr;
privateDatabaseManagergetDatabaseManager(){
if(dbMgr==null){
dbMgr=newDatabaseManager(getContext());
}
returndbMgr;
}
}
ElectricCarContentProvider extends ContentProvider and overrides all its abstractmethods for CRUD operations. At the end of the class there is a definition ofDatabaseManager and a method named getDatabaseManager that returns aDatabaseManager.TheDatabaseManager ispresented inListing27.3. It is similar totheDatabaseManagerclassdiscussedinChapter18,“WorkingwiththeDatabase”whichexplainshowitworksindetail.Pleaserefertothischapterifyouhaveforgottenhowtoworkwithrelationaldatabases.
Listing27.3:Thedatabasemanager
packagecom.example.contentproviderdemo1.provider;
importandroid.content.ContentValues;
importandroid.content.Context;
importandroid.database.Cursor;
importandroid.database.sqlite.SQLiteDatabase;
importandroid.database.sqlite.SQLiteOpenHelper;
importandroid.util.Log;
publicclassDatabaseManagerextendsSQLiteOpenHelper{
publicstaticfinalStringTABLE_NAME=“electric_cars”;
publicstaticfinalStringID_FIELD=“_id”;
publicstaticfinalStringMAKE_FIELD=“make”;
publicstaticfinalStringMODEL_FIELD=“model”;
publicDatabaseManager(Contextcontext){
super(context,
/*dbname=*/“vehicles_db”,
/*cursorFactory=*/null,
/*dbversion=*/1);
}
@Override
publicvoidonCreate(SQLiteDatabasedb){
Stringsql=“CREATETABLE”+TABLE_NAME
+”(“+ID_FIELD+”INTEGER,”
+MAKE_FIELD+”TEXT,”
+MODEL_FIELD+”TEXT,”
+”PRIMARYKEY(“+ID_FIELD+”));”;
db.execSQL(sql);
}
@Override
publicvoidonUpgrade(SQLiteDatabasedb,intarg1,
intarg2){
db.execSQL(“DROPTABLEIFEXISTS”+TABLE_NAME);
//re-createthetable
onCreate(db);
}
publiclongaddElectricCar(ContentValuesvalues){
Log.d(“db”,“addElectricCar”);
SQLiteDatabasedb=this.getWritableDatabase();
returndb.insert(TABLE_NAME,null,values);
}
//ObtainssingleElectricCar
ContentValuesgetElectricCar(longid){
SQLiteDatabasedb=this.getReadableDatabase();
Cursorcursor=db.query(TABLE_NAME,newString[]{
ID_FIELD,MAKE_FIELD,MODEL_FIELD},
ID_FIELD+”=?”,
newString[]{String.valueOf(id)},null,
null,null,null);
if(cursor!=null){
cursor.moveToFirst();
ContentValuesvalues=newContentValues();
values.put(“id”,cursor.getLong(0));
values.put(“make”,cursor.getString(1));
values.put(“model”,cursor.getString(2));
returnvalues;
}
returnnull;
}
publicCursorgetElectricCarsCursor(String[]projection,
Stringselection,
String[]selectionArgs,StringsortOrder){
SQLiteDatabasedb=this.getReadableDatabase();
Log.d(“provider:”,“projection:”+projection);
Log.d(“provider:”,“selection:”+selection);
Log.d(“provider:”,“selArgs:”+selectionArgs);
returndb.query(TABLE_NAME,projection,
selection,
selectionArgs,
sortOrder,
null,null,null);
}
publicintupdateElectricCar(Stringid,Stringmake,
Stringmodel){
SQLiteDatabasedb=this.getWritableDatabase();
ContentValuesvalues=newContentValues();
values.put(MAKE_FIELD,make);
values.put(MODEL_FIELD,model);
returndb.update(TABLE_NAME,values,ID_FIELD+”=?”,
newString[]{id});
}
publicintdeleteElectricCar(Stringid){
SQLiteDatabasedb=this.getWritableDatabase();
returndb.delete(TABLE_NAME,ID_FIELD+”=?”,
newString[]{id});
}
}
The CONTENT_URI in ElectricCarContentProvider specifies the URI used foraccessingthecontentprovider.However,clientapplicationsshouldonlyknowthecontentofthisURIanddonotneedtodependonthisclass.TheUtilclassinListing27.4containsacopyoftheURIfortheclientsofthecontentprovider.
Listing27.4:TheUtilclass
packagecom.example.contentproviderdemo1;
importandroid.net.Uri;
publicclassUtil{
publicstaticfinalUriCONTENT_URI=
Uri.parse(“content://com.example.contentproviderdemo1”+
”/electric_cars”);
publicstaticfinalStringID_FIELD=“_id”;
publicstaticfinalStringMAKE_FIELD=“make”;
publicstaticfinalStringMODEL_FIELD=“model”;
}
Listings27.5,27.6and27.7areactivityclassesthataccessthecontentprovider.Theyallaccess the content provider by using the ContentResolver object created for the
application.YouretrieveitbycallinggetContentResolverfromtheactivityclasses.
Listing27.5:TheMainActivityclass
packagecom.example.contentproviderdemo1.activity;
importandroid.app.Activity;
importandroid.content.Intent;
importandroid.database.Cursor;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.widget.AdapterView;
importandroid.widget.AdapterView.OnItemClickListener;
importandroid.widget.CursorAdapter;
importandroid.widget.ListAdapter;
importandroid.widget.ListView;
importandroid.widget.SimpleCursorAdapter;
importcom.example.contentproviderdemo1.R;
importcom.example.contentproviderdemo1.Util;
publicclassMainActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ListViewlistView=(ListView)findViewById(
R.id.listView);
Cursorcursor=getContentResolver().query(
Util.CONTENT_URI,
/*projection=*/newString[]{
Util.ID_FIELD,Util.MAKE_FIELD,
Util.MODEL_FIELD},
/*selection=*/null,
/*selectionArgs=*/null,
/*sortOrder=*/“make”);
startManagingCursor(cursor);
ListAdapteradapter=newSimpleCursorAdapter(
this,
android.R.layout.two_line_list_item,
cursor,
newString[]{Util.MAKE_FIELD,
Util.MODEL_FIELD},
newint[]{android.R.id.text1,android.R.id.text2},
CursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);
listView.setAdapter(adapter);
listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE);
listView.setOnItemClickListener(
newOnItemClickListener(){
@Override
publicvoidonItemClick(
AdapterView<?>adapterView,
Viewview,intposition,longid){
Intentintent=newIntent(
getApplicationContext(),
ShowElectricCarActivity.class);
intent.putExtra(“id”,id);
startActivity(intent);
}
});
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.menu_main,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
switch(item.getItemId()){
caseR.id.action_add:
startActivity(newIntent(this,
AddElectricCarActivity.class));
returntrue;
default:
returnsuper.onOptionsItemSelected(item);
}
}
}
Listing27.6:TheAddElectricCarActivityclass
packagecom.example.contentproviderdemo1.activity;
importandroid.app.Activity;
importandroid.content.ContentValues;
importandroid.os.Bundle;
importandroid.view.Menu;
importandroid.view.View;
importandroid.widget.EditText;
importcom.example.contentproviderdemo1.provider.
ElectricCarContentProvider;
importcom.example.contentproviderdemo1.R;
publicclassAddElectricCarActivityextendsActivity{
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_add_electric_car);
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.add_electric_car,menu);
returntrue;
}
publicvoidcancel(Viewview){
finish();
}
publicvoidaddElectricCar(Viewview){
Stringmake=((EditText)findViewById(
R.id.make)).getText().toString();
Stringmodel=((EditText)findViewById(
R.id.model)).getText().toString();
ContentValuesvalues=newContentValues();
values.put(“make”,make);
values.put(“model”,model);
getContentResolver().insert(
ElectricCarContentProvider.CONTENT_URI,values);
finish();
}
}
Listing27.7:TheShowElectricCarActivityclass
packagecom.example.contentproviderdemo1.activity;
importandroid.app.Activity;
importandroid.app.AlertDialog;
importandroid.content.ContentUris;
importandroid.content.ContentValues;
importandroid.content.DialogInterface;
importandroid.database.Cursor;
importandroid.net.Uri;
importandroid.os.Bundle;
importandroid.util.Log;
importandroid.view.Menu;
importandroid.view.MenuItem;
importandroid.view.View;
importandroid.widget.EditText;
importandroid.widget.TextView;
importcom.example.contentproviderdemo1.R;
importcom.example.contentproviderdemo1.Util;
publicclassShowElectricCarActivityextendsActivity{
longelectricCarId;
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_show_electric_car);
getActionBar().setDisplayHomeAsUpEnabled(true);
Bundleextras=getIntent().getExtras();
if(extras!=null){
electricCarId=extras.getLong(“id”);
Cursorcursor=getContentResolver().query(
Util.CONTENT_URI,
/*projection=*/newString[]{
Util.ID_FIELD,Util.MAKE_FIELD,
Util.MODEL_FIELD},
/*selection=*/“_id=?”,
/*selectionArgs*/newString[]{
Long.toString(electricCarId)},
/*sortOrder*/null);
if(cursor!=null&&cursor.moveToFirst()){
Stringmake=cursor.getString(1);
Stringmodel=cursor.getString(2);
((TextView)findViewById(R.id.make))
.setText(make);
((TextView)findViewById(R.id.model))
.setText(model);
}
}
}
@Override
publicbooleanonCreateOptionsMenu(Menumenu){
getMenuInflater().inflate(R.menu.show_electric_car,menu);
returntrue;
}
@Override
publicbooleanonOptionsItemSelected(MenuItemitem){
switch(item.getItemId()){
caseR.id.action_delete:
deleteElectricCar();
returntrue;
default:
returnsuper.onOptionsItemSelected(item);
}
}
privatevoiddeleteElectricCar(){
newAlertDialog.Builder(this)
.setTitle(“Pleaseconfirm”)
.setMessage(
“Areyousureyouwanttodelete”+
“thiselectriccar?”)
.setPositiveButton(“Yes”,
newDialogInterface.OnClickListener(){
publicvoidonClick(
DialogInterfacedialog,
intwhichButton){
Uriuri=ContentUris.withAppendedId(
Util.CONTENT_URI,electricCarId);
getContentResolver().delete(
uri,null,null);
dialog.dismiss();
finish();
}
})
.setNegativeButton(“No”,
newDialogInterface.OnClickListener(){
publicvoidonClick(
DialogInterfacedialog,
intwhich){
dialog.dismiss();
}
})
.create()
.show();
}
publicvoidupdateElectricCar(Viewview){
Uriuri=ContentUris.withAppendedId(Util.CONTENT_URI,
electricCarId);
ContentValuesvalues=newContentValues();
values.put(Util.MAKE_FIELD,
((EditText)findViewById(R.id.make)).getText()
.toString());
values.put(Util.MODEL_FIELD,
((EditText)findViewById(R.id.model)).getText()
.toString());
getContentResolver().update(uri,values,null,null);
finish();
}
}
Asthecontentproviderisaccessedfromcomponentsinthesameapplication,youshouldnotexpecttoencounteranyproblems.Figure27.1showsaListViewinthemainactivity.Ofcourse,whenyoufirstruntheapplication,thelistwillbeempty.
Figure27.1:Themainactivity
TouchtheAddbuttonontheactionbartoaddanelectriccar.Figure27.2showshowtheAddactivitylookslike.
Figure27.2:TheAddElectricCarActivity
Type in amake and amodel and touch theAdd button to add a vehicle.Alternatively,touchtheCancelbuttontocancel.Youwillberedirectedtothemainactivity.
From themainactivity,youcan select a car toviewandedit thedetails.Figure27.3showstheShowElectricCarActivityactivity.
Figure27.3:TheShowElectricCarActivity
Youcanupdateacarordeleteitfromthisactivity.
ConsumingAContentProviderThe second application in this chapter, the ContentResolverDemo1 project, shows howyou can access a content provider from a different application. The only differencebetween accessing a content provider from the same application and an externalapplicationisthatyouhavetorequestapermissiontoaccesstheproviderinthemanifestoftheexternalapplication.
Listing27.8showsthemanifestfortheContentResolverDemo1project.
Listing27.8:ThemanifestforContentResolverDemo1
<?xmlversion=“1.0”encoding=“utf-8”?>
<manifestxmlns:android=“http://schemas.android.com/apk/res/android”
package=“com.example.contentresolverdemo1”>
<uses-
permissionandroid:name=“com.example.permission.READ_ELECTRIC_CARS”/>
<uses-
permissionandroid:name=“com.example.permission.WRITE_ELECTRIC_CARS”/>
<application
android:allowBackup=“true”
android:icon=”@drawable/ic_launcher”
android:label=”@string/app_name”
android:theme=”@style/AppTheme”>
<activity
android:name=“com.example.contentresolverdemo1.MainActivity”
android:label=”@string/app_name”>
<intent-filter>
<actionandroid:name=“android.intent.action.MAIN”/>
<categoryandroid:name=“android.intent.category.LAUNCHER”/>
</intent-filter>
</activity>
</application>
</manifest>
The application contains one activity that shows data from the content provider. Theactivity class is a copyof theMainActivity class in theContentProviderDemo1project.TheactivityisshowninFigure27.4.
Figure27.4:Showingdatafromacontentprovider
SummaryA content provider is an Android component used for encapsulating data that is to besharedwithotherapplications.ThischaptershowshowyoucancreateacontentproviderandconsumeitsdatafromanexternalapplicationusingaContentResolver.
AppendixAInstallingtheJDK
You need the Java SE Development Kit (JDK) to create Android applications. Thisappendixshowsyouhowtodownloadandinstallit.
DownloadingandInstallingtheJDKBefore you can start compiling and running your programs, you need to download andinstall the JDK as well as configure some system environment variables. You candownload the latest version of the JDK forWindows,Linux, andMacOSX from thisOraclewebsite:
http://www.oracle.com/technetwork/java/javase/downloads/index.html
Ifyouclick theDownload linkon thepage,you’llbe redirected toapage that letsyouselectaninstallationforyourplatform:Windows,Linux,SolarisorMacOSX.ThesamelinkalsoprovidestheJRE.However,fordevelopmentyouneedtheJDKnotonlytheJRE,whichisonlygoodforrunningcompiledJavaclasses.TheJDKincludestheJRE.
AfterdownloadingtheJDK,youneedtoinstallit.Installationvariesfromoneoperatingsystemtoanother.Thesesubsectionsdetailtheinstallationprocess.
InstallingonWindowsInstallingonWindowsiseasy.Simplydouble-clicktheexecutablefileinyoudownloadedinWindowsExplorerandfollowtheinstructions.FigureA.1showsthefirstdialogoftheinstallationwizard.
FigureA.1:InstallingtheJDK8onWindows
InstallingonLinuxOnLinuxplatforms,theJDKisavailableintwoinstallationformats.
RPM,forLinuxplatformsthatsupportstheRPMpackagemanagementsystem,suchasRedHatandSuSE.Self-extractingpackage.Acompressedfilecontainingpackagestobeinstalled.
IfyouareusingtheRPM,followthesesteps:
1.Becomerootbyusingthesucommand2.Extractthedownloadedfile.3.Changedirectorytowherethedownloadedfileislocatedandtype:
chmoda+xrpmFile
whererpmFileistheRPMfile.4.RuntheRPMfile:
./rpmFile
Ifyouareusingtheself-extractingbinaryinstallation,followthesesteps.
1.Extractthedownloadedfile.2.Usechmodtogivethefiletheexecutepermissions:
chmoda+xbinFile
Here,binFileisthedownloadedbinfileforyourplatform.3.Changedirectorytothelocationwhereyouwantthefilestobeinstalled.4. Run the self-extracting binary. Execute the downloaded file with the pathprepended to it.For example, if the file is in the currentdirectory, prepend itwith“./”:
./binFile
InstallingonMacOSXToinstalltheJDK8onMacOSX,youneedanIntel-basedcomputerrunningOSX10.8(MountainLion)or later.Youalsoneedadministratorprivileges. Installation is straight-forward.
1.Double-clickonthe.dmgfileyoudownloaded.2.IntheFinderwindowthatappears,double-clickthepackageicon.3.Onthefirstwindowthatappears,clickContinue.4.TheInstallationTypewindowappears.ClickInstall.5.Awindowappearsthatsays“Installeristryingtoinstallnewsoftware.Typeyourpasswordtoallowthis.”EnteryourAdminpassword.6.ClickInstallSoftwaretostarttheinstallation.
SettingSystemEnvironmentVariablesAfteryouinstalltheJDK,youcanstartcompilingandrunningJavaprograms.However,youcanonly invoke thecompiler and the JRE from the locationof the javac and javaprogramsorbyincludingtheinstallationpathinyourcommand.Tomakecompilingandrunningprogramseasier, it is important thatyouset thePATHenvironmentvariableonyourcomputersothatyoucaninvokejavacandjavafromanydirectory.
SettingthePathEnvironmentVariableonWindows
TosetthePATHenvironmentvariableonWindows,dothesesteps:
1.ClickStart,Settings,ControlPanel.2.Double-clickSystem.3.SelecttheAdvancedtabandthenclickonEnvironmentVariables.4.LocatethePathenvironmentvariableintheUserVariablesorSystemVariablespanes.ThevalueofPathisaseriesofdirectoriesseparatedbysemicolons.Now,addthefullpathtothebindirectoryofyourJavainstallationdirectorytotheendoftheexistingvalueofPath.Thedirectorylookssomethinglike:
C:\ProgramFiles\Java\jdk1.8.0_<version>\bin
5.ClickSet,OK,orApply.
SettingthePathEnvironmentVariableonUNIXandLinux
Settingthepathenvironmentvariableontheseoperatingsystemsdependsontheshellyouuse.FortheCshell,addthefollowingtotheendofyour~/.cshrcfile:
setpath=(path/to/jdk/bin$path)
wherepath/to/jdk/binisthebindirectoryunderyourJDKinstallationdirectory.
For the Bourne Again shell, add this line to the end of your ~/.bashrc or~/.bash_profilefile:
exportPATH=/path/to/jdk/bin:$PATH
Here,path/to/jdk/binisthebindirectoryunderyourJDKinstallationdirectory.
TestingtheInstallationTo confirm that you have installed the JDK correctly, type javac on the command linefromanydirectoryonyourmachine.Ifyouseeinstructionsonhowtocorrectlyrunjavac,thenyouhavesuccessfullyinstalledit.Ontheotherhand,ifyoucanonlyrunjavacfromthebindirectoryoftheJDKinstallationdirectory,yourPATHenvironmentvariablewasnotconfiguredproperly.
DownloadingJavaAPIDocumentationWhen programming Java, youwill invariably use classes from the core libraries. Evenseasoned programmers look up the documentation for those libraries when they arecoding.Therefore,youshoulddownloadthedocumentationfromhere.
http://www.oracle.com/technetwork/java/javase/downloads/index.html
(Youneedtoscrolldownuntilyousee“JavaSE8Documentation.”)
TheAPIisalsoavailableonlinehere:
http://download.oracle.com/javase/8/docs/api
AppendixBUsingtheADTBundle
ThisappendixshowshowyoucancreateanAndroidapplicationusingtheADTBundle.Italsoexplainshowtosetupanemulatorsoyoucandevelop,test,debug,andrunAndroidapplicationsevenifyoudonothavearealAndroiddevice.
InstallingtheADTIfyoualreadyhaveEclipseonyourlocalmachine,youcaninstalltheADTplug-inonlyandworkwith your existingEclipse.However, note that it is easier to install theADTbundle.Ifyouchoosetoinstall theADTplug-in, informationonhowtoproceedwithitcanbefoundhere.
http://developer.android.com/sdk/installing/installing-adt.html
ToinstalltheADTBundle,firstdownloadtheADTbundlefromthissite.
http://developer.android.com/sdk/index.html
Unpackthedownloadedpackagetoyourworkspace.Themaindirectorywillcontaintwofolders, eclipse and sdk. Navigate to the eclipse folder and double-click the Eclipseprogramtostart it.Youwillbeasked toselectaworkspace.After that, theEclipseIDEwill open.Themainwindow is shown in FigureB.1.Note that the application icon ofADTEclipseisdifferentfromthatof“regular”Eclipse.
FigureB.1:TheADTwindow
NowyouarereadytowriteyourfirstAndroidapplication.
CreatingAnApplicationCreatinganAndroidapplicationwiththeADTBundleisaseasyasafewmouseclicks.ThissectionshowshowtocreateaHelloWorldapplication,packageit,andrunitonanemulator.MakesureyouhaveinstalledtheADTBundlebyfollowingtheinstructionsinIntroduction.
Next,followthesesteps.
1.ClicktheNewmenuinEclipseandselectAndroidApplicationProject.NotethatinthisbookEclipsereferstotheversionofEclipseincludedintheADTBundleorEclipsewiththeADTplug-ininstalled.TheNewAndroidApplicationwindowwillopenasshowninFigureB.2.
FigureB.2:TheNewAndroidApplicationwindow
2.Typeinthedetailsofthenewapplication.IntheApplicationNamefield,enterthename you want your application to appear on the Android device. In theProjectName field, typeaname foryourproject.This canbe the sameas the applicationnameoradifferentname.Then,entera Javapackagename in thePackageNamefield. The package namewill uniquely identify your application. Even though youcanuseanystringthatqualifiesasaJavapackage,thepackagenameshouldbeyourdomainname in reverseorder.Forexample, ifyourdomainname isexample.com,yourpackagenameshouldbecom.example,followedbytheprojectname.
Now,rightunderthetextboxesarefourdropdownboxes.TheMinimumRequiredSDKdropdowncontainsalistofAndroidSDKlevels.Thelowerthelevel,themoredevices your application can runon, but the fewerAPIs and features you canuse.TheTargetSDKboxshouldbegiventhehighestAPIlevelyourapplicationwillbedevelopedandtestedagainst.TheCompileWithdropdownshouldcontainthetargetAPI to compile your code against. Finally, theTheme dropdown should contain athemeforyourapplication.Foryourfirstapplication,usethesamevaluesasthoseshowninFigureB.2.3.ClickNext.Youwill seeawindowsimilar to theone inFigureB.3.Accept thedefaultsettings.
FigureB.3:Configuringyourapplication
4. ClickNext again. The next window that appears will look like the window inFigureB.4.Hereyoucanchooseaniconforyourapplication.Ifyoudon’t likethedefault image icon,clickClipart and selectone from the list. Inaddition,youcanusetextasyouriconifyousowish.
FigureB.4:Selectingalaunchericon
5.ClickNextagainandyouwillbepromptedtoselectanactivity(SeeFigureB.5).LeaveBlankActivityselected.
FigureB.5:Selectinganactivitytype
6.ClickNextonemoretime.ThenextwindowwillappearasshowninFigureB.6.
FigureB.6:Enteringtheactivityandlayoutnames
7.AcceptthesuggestedactivityandlayoutnamesandclickFinish.TheADTBundlewillcreateyourapplicationandyou’llseeyourprojectlikethescreenshotinFigureB.7.
FigureB.7:ThenewAndroidproject
IntherootdirectoryofEclipse’sPackageExplorer(ontheleft),you’llfindthefollowingfiles:
AndroidManifest.xml file. This is an XML document that describes yourapplication.AniconfileinPNGformat.Aproject.propertiesfilethatspecifiestheAndroidtargetAPIlevel.
Ontopofthat,therearethefollowingfolders.
src.Thisisyoursourcecodefolder.gen.ThisiswheregeneratedJavaclassesarekept.ThegeneratedJavaclassesallowyourJavasourcetousevaluesdefinedinthelayoutfileandotherresourcefiles.Youshouldnoteditgeneratedfilesyourself.bin.Thisiswheretheprojectbuildwillbesavedin.TheapplicationAPKwillalsobefoundhereafteryouhaverunyourapplicationsuccessfully.libs.ContainsAndroidlibraryfiles.res. Contains resource files. Underneath this directory are these directories:drawable-xxx(containingimagesforvariousscreenresolutions),layout(containinglayoutfiles),menu(containingmenufiles),andvalues(containingstringandothervalues).
One of the advantages of developingAndroid applications with an IDE, such as ADTEclipse, it knows when you add a resource under the res directory and responds byupdating the R generated class so that you can easily load the resource from yourprogram.Youwilllearnthispowerfulfeatureinthechapterstocome.
RunningAnApplicationonAnEmulatorTheADTBundlecomeswithanemulatortorunyourapplicationsonifyoudon’thavearealdevice.Thefollowingarethestepsforrunningyourapplicationonanemulator.
1.ClicktheAndroidprojectontheEclipseProjectExplorer,thenclickRun>RunAs>AndroidApplication.2.TheAndroidDeviceChooserwindowwillpopup (seeFigureB.8). (Onceyouconfigureit,itwillnotappearthenexttimeyoutrytorunyourapplication).
FigureB.8:TheAndroidDeviceChooserwindow
3.HereyoucanchoosetorunyourapplicationonarealAndroiddevice(anAndroidphoneortablet)oranAndroidVirtualDevice(emulator).InFigureB.8youdonotsee a running Android device because no real device is connected, so click theLaunchanewAndroidVirtualDeviceradiobutton,andclicktheManagerbuttonontheright.TheAndroidVirtualDeviceManagerwindowwillappear(SeeFigureB.9).
FigureB.9:AndroidVirtualDeviceManager
4.ClickNewontheAndroidVirtualDevicespanetodisplaytheCreatenewAVDwindows(SeeFigureB.10)
.
FigureB.10:Creatinganewvirtualdevice
5.Click theDevice drop-down to view the list of virtual devices available.Here I
chooseNexus7.Then,giveyourdeviceaname.Thenamemustnotcontainspacesoranyspecialcharacters.6.Chooseatargetandifyou’reusingWindows,reducetheRAMto768.Forsomereason,itmaycrashifyou’reusingmorethan768MBRAMonWindows.7.MyoptionsareshowninthescreenshotinFigureB.11.
FigureB.11:Enteringvaluesforanewvirtualdevice
8.ClickOK.TheCreatenewAndroidVirtualDevice (AVD)windowwill closeandyou’llbebackattheAndroidVirtualDeviceManagerwindow.YourAVDwillbelistedthere,asshowninFigureB.12.
FigureB.12:Thelistofvirtualdevicesavailable
9.Now,click theAVDname (Nexus7) to select it and theStart andotherbuttonswill be enabled.Click theStart button to start theAVD.Youwill see theLaunchOptionspopuplikethatinFigureB.13.
FigureB.13:TheLaunchOptionspopup
10.ClickLaunchtolaunchyourAVD.You’llseeawindowlikethatinFigureB.14whenit’slaunching.
FigureB.14:Startingtheemulator
Itwill takeafewminutesormoredependingonyourcomputerspeed.FigureB.15showstheemulatorwhenitisready.
FigureB.15:TheAndroidemulator
As you know, the emulator emulates an Android device. You need to unlock thescreenbytouching(orclicking)thebluecircleatthebottom.
If your application does not open automatically, locate the application icon anddouble-clickonit.FigureB.16showstheHelloWorldapplication.
FigureB.16:Yourfirstapplicationontheemulator
Duringdevelopment,leavetheemulatorrunningwhileyouedityourcode.Thisway,theemulatordoesnotneedtobeloadedagaineverytimeyourunyourapplication.
LoggingJavaprogrammersliketouseloggingutilities,suchasCommonsLoggingandLog4J,tologmessages.TheAndroidframeworkprovidestheandroid.util.Logclassforthesamepurpose.TheLogclasscomeswithmethodstologmessagesatdifferentloglevels.Themethodnamesareshort:d(debug),i(info),v(verbose),w (warning),e (error),andwtf(whataterriblefailure).
Thismethodsallowyoutowriteatagandthetext.Forexample,
Log.e(“activity”,“Somethingwentwrong”);
Duringdevelopment,messagesloggedusingtheLogclasswillappearintheLogCatviewinEclipse.Ifyoudon’tseeit,clickWindow→ShowView→LogCatorWindow→ShowView→Other→LogCat.
Thegood thingaboutLogCat is thatmessagesatdifferent log levelsaredisplayed indifferent colors. In addition, each message has a tag and this makes it easy to find amessage.Inaddition,LogCatallowsyoutosavemessagestoafileandfilterthemessagessoonlymessagesofinteresttoyouarevisible.
TheLogCatviewisshowninFigureB.17.
FigureB.17:TheLogCatview
Anyruntimeexceptionthrown,includingthestacktrace,willalsobeshowninLogCat,soyoucaneasilyidentifywhichlineofcodeiscausingtheproblem.
DebuggingAnApplicationEven though Android applications do not run on the JVM, debugging an AndroidapplicationinEclipsedoesnotfeelthatdifferentfromdebuggingJavaapplications.
Theeasiestway todebuganapplication isbyprintingmessagesusing theLog class.However, if this does not help andyouneed to trace your application, you canuse thedebuggingtoolsinAndroid.
Tryaddingalinebreakpointinyourcodebydouble-clickingthebartotheleftofthecodeeditor.FigureB.18showsalinebreakpointinthecodeeditor.
FigureB.18:Alinebreakpoint
Now, debug your application by clicking the project icon in the Project Explorer andselectingRun→DebugAs→AndroidApplication.
Eclipse will display a dialog asking you whether you want to open the Debugperspective.ClickYes,andyouwillseetheDebugperspectiveliketheoneinFigureB.19.
FigureB.19:TheDebugperspective
Here,youcanstepintoyourcode,viewyourvariables,andsoon.
In addition to a debugger, Android also ships with Dalvik Debug Monitor Server(DDMS), which consists of a set of debugging tools. You can display the DDMS inEclipsebyshowingtheDDMSperspective.(SeeFigureB.20).
FigureB.20:TheDDMSperspectiveinEclipse
YouwillseeLogCatasoneoftheviewsintheDDMSperspective.However,youcanalsouseDDMStodoanyofthese:
Verifythatadeviceisconnected.ViewheapusageforaprocessCheckobjectmemoryallocationBrowsethefilesystemonadeviceExaminethreadinformationMonitornetworktraffic