Screen slides are transitions between one entire screen to another and are common with UIs like setup wizards or slideshows. This Android tutorial shows how to create image slideshow with ViewPager (provided as part of Android support library). ViewPager can animate screen slides automatically.
Here’s what a screen slide looks like – transition from one screen to the next:
Project Description:
In this Android Image Slideshow tutorial we will be doing the following,
- Create an image slideshow along with image description using ViewPager and display circle indicator to show the current position in the slideshow.
- Enclose ViewPager and circle indicator in a layout and provide a border.
- Retrieve and parse JSON (product information) from remote server and store it as list of products.
- Handle ViewPager item clicks to display product details in another fragment (ProductDetailFragment)
- Proper back navigation of fragments on back key pressed.
- Handle fragment orientation changes and retain the state of the fragment.
We will also be using the following library projects,
- Universal Image Loader for Android for asynchronous image loading.
- ViewPagerIndicator library to bring circle indicator with ViewPager.
Prerequisites
- Place the Universal Image Loader JAR file in your libs folder and add it to project’s “Build Path”.
Create JSON File
Create a new JSON file and name it as products.json and copy-paste the following content. Place this file in a remote server, for example Apache server’s “www” folder or
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | { "status": true, "data": { "products": [ { "id": "434", "name": "Pattern - Fractal Wallpaper", "image_url": "http://images5.alphacoders.com/350/350374.jpg" }, { "id": "431", "name": "Mickey Mouse", "image_url": "http://www.iconsdb.com/icons/download/icon-sets/sketchy-pink/mickey-mouse-20-512.png" }, { "id": "424", "name": "Pattern - Wallpaper", "image_url": "http://images7.alphacoders.com/421/421423.jpg" }, { "id": "426", "name": "Batman", "image_url": "http://www.iconsdb.com/icons/download/icon-sets/sketchy-pink/batman-6-512.png" }, { "id": "419", "name": "Pattern - Music", "image_url": "http://images3.alphacoders.com/169/169085.jpg" } ] } } |
Android Project
Create a new Android project and name it as AndroidImageSlideShow.
Resources
colors.xml
Create a new file res/values/colors.xml and copy paste the following content.
1 2 3 4 5 6 7 8 | <?xml version="1.0" encoding="utf-8"?> <resources> <color name="holo_blue_dark">#ff0099cc</color> <color name="view_divider_color">#BABABA</color> <color name="view_divider_color_2">#DADADA</color> </resources> |
strings.xml
Open res/values/strings.xml and edit to have the content as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 | <?xml version="1.0" encoding="utf-8"?> <resources> <string name="app_name">AndroidImageSlideShow</string> <string name="action_settings">Settings</string> <string name="product_name">Product Name</string> <string name="txt_image_slider">Image Slideshow</string> <string name="no_internet_connection">No Internet Connection</string> <string name="time_out">Time Out</string> <string name="error_occured">Error Occurred</string> </resources> |
indicator_tags.xml
Create a new file res/values/indicator_tags.xml and copy the following content. The attributes defined here are used by circle page indicator. You can use this file to change the circle indicator fill color, stroke color, stroke width, circle radius and any other related styles.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 | <?xml version="1.0" encoding="utf-8"?> <resources> <bool name="default_circle_indicator_centered">true</bool> <color name="default_circle_indicator_fill_color">#FFFF0000</color> <color name="default_circle_indicator_page_color">#00000000</color> <integer name="default_circle_indicator_orientation">0</integer> <dimen name="default_circle_indicator_radius">4dp</dimen> <bool name="default_circle_indicator_snap">false</bool> <color name="default_circle_indicator_stroke_color">#FFDDDDDD</color> <dimen name="default_circle_indicator_stroke_width">1dp</dimen> <declare-styleable name="ViewPagerIndicator"> <!-- Style of the circle indicator. --> <attr name="vpiCirclePageIndicatorStyle" format="reference" /> <!-- Style of the icon indicator's views. --> <attr name="vpiIconPageIndicatorStyle" format="reference" /> <!-- Style of the line indicator. --> <attr name="vpiLinePageIndicatorStyle" format="reference" /> <!-- Style of the title indicator. --> <attr name="vpiTitlePageIndicatorStyle" format="reference" /> <!-- Style of the tab indicator's tabs. --> <attr name="vpiTabPageIndicatorStyle" format="reference" /> <!-- Style of the underline indicator. --> <attr name="vpiUnderlinePageIndicatorStyle" format="reference" /> </declare-styleable> <attr name="centered" format="boolean" /> <attr name="selectedColor" format="color" /> <attr name="strokeWidth" format="dimension" /> <attr name="unselectedColor" format="color" /> <declare-styleable name="CirclePageIndicator"> <!-- Whether or not the indicators should be centered. --> <attr name="centered" /> <!-- Color of the filled circle that represents the current page. --> <attr name="fillColor" format="color" /> <!-- Color of the filled circles that represents pages. --> <attr name="pageColor" format="color" /> <!-- Orientation of the indicator. --> <attr name="android:orientation" /> <!-- Radius of the circles. This is also the spacing between circles. --> <attr name="radius" format="dimension" /> <!-- Whether or not the selected indicator snaps to the circles. --> <attr name="snap" format="boolean" /> <!-- Color of the open circles. --> <attr name="strokeColor" format="color" /> <!-- Width of the stroke used to draw the circles. --> <attr name="strokeWidth" /> <!-- View background --> <attr name="android:background" /> </declare-styleable> <declare-styleable name="LinePageIndicator"> <!-- Whether or not the indicators should be centered. --> <attr name="centered" /> <!-- Color of the unselected lines that represent the pages. --> <attr name="unselectedColor" /> <!-- Color of the selected line that represents the current page. --> <attr name="selectedColor" /> <!-- Width of each indicator line. --> <attr name="lineWidth" format="dimension" /> <!-- Width of each indicator line's stroke. --> <attr name="strokeWidth" /> <!-- Width of the gap between each indicator line. --> <attr name="gapWidth" format="dimension" /> <!-- View background --> <attr name="android:background" /> </declare-styleable> <declare-styleable name="TitlePageIndicator"> <!-- Screen edge padding. --> <attr name="clipPadding" format="dimension" /> <!-- Color of the footer line and indicator. --> <attr name="footerColor" format="color" /> <!-- Height of the footer line. --> <attr name="footerLineHeight" format="dimension" /> <!-- Style of the indicator. Default is triangle. --> <attr name="footerIndicatorStyle"> <enum name="none" value="0" /> <enum name="triangle" value="1" /> <enum name="underline" value="2" /> </attr> <!-- Height of the indicator above the footer line. --> <attr name="footerIndicatorHeight" format="dimension" /> <!-- Left and right padding of the underline indicator. --> <attr name="footerIndicatorUnderlinePadding" format="dimension" /> <!-- Padding between the bottom of the title and the footer. --> <attr name="footerPadding" format="dimension" /> <!-- Position of the line. --> <attr name="linePosition"> <enum name="bottom" value="0" /> <enum name="top" value="1" /> </attr> <!-- Color of the selected title. --> <attr name="selectedColor" /> <!-- Whether or not the selected item is displayed as bold. --> <attr name="selectedBold" format="boolean" /> <!-- Color of regular titles. --> <attr name="android:textColor" /> <!-- Size of title text. --> <attr name="android:textSize" /> <!-- Padding between titles when bumping into each other. --> <attr name="titlePadding" format="dimension" /> <!-- Padding between titles and the top of the View. --> <attr name="topPadding" format="dimension" /> <!-- View background --> <attr name="android:background" /> </declare-styleable> <declare-styleable name="UnderlinePageIndicator"> <!-- Whether or not the selected indicator fades. --> <attr name="fades" format="boolean" /> <!-- Length of the delay to fade the indicator. --> <attr name="fadeDelay" format="integer" /> <!-- Length of the indicator fade to transparent. --> <attr name="fadeLength" format="integer" /> <!-- Color of the selected line that represents the current page. --> <attr name="selectedColor" /> <!-- View background --> <attr name="android:background" /> </declare-styleable> </resources> |
img_border.xml
Create a new file res/drawable/img_border.xml and copy the following content. This brings a border to ViewPager and circle page indicator layout.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:dither="true" android:shape="rectangle" > <solid android:color="#FFFFFF" /> <stroke android:width="1dp" android:color="#AAAAAA" /> <padding android:bottom="1dp" android:left="1dp" android:right="1dp" android:top="1dp" /> </shape> |
Layout files
activity_main.xml
This is the main layout file which uses a Framelayout to hold fragments. Open res/layout/activity_main.xml and edit to have the content as shown below.
1 2 3 4 5 6 7 8 9 10 11 12 | <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" tools:context=".MainActivity" > <FrameLayout android:id="@+id/content_frame" android:layout_width="match_parent" android:layout_height="match_parent" /> </RelativeLayout> |
fragment_home.xml
This layout file is used by HomeFragment to display image slide show using ViewPager. Here it encloses ViewPager and circle page indicator in a single layout by displaying circle page indicator over ViewPager.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 | <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:padding="10dp" tools:context=".MainActivity" > <RelativeLayout android:id="@+id/img_slide_header_layout" android:layout_width="fill_parent" android:layout_height="wrap_content" > <TextView android:id="@+id/txt_image_slider" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignParentLeft="true" android:padding="4dp" android:paddingLeft="20dp" android:text="@string/txt_image_slider" android:textColor="@color/holo_blue_dark" android:textSize="16sp" android:textStyle="bold" /> <View android:id="@+id/div1" android:layout_width="fill_parent" android:layout_height="2dp" android:layout_below="@+id/txt_image_slider" android:layout_marginBottom="7dp" android:layout_marginTop="4dp" android:background="@color/holo_blue_dark" /> </RelativeLayout> <RelativeLayout android:id="@+id/img_slideshow_layout" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@+id/img_slide_header_layout" android:layout_marginTop="10dp" android:background="@drawable/img_border" > <android.support.v4.view.ViewPager android:id="@+id/view_pager" android:layout_width="fill_parent" android:layout_height="150dp" /> <TextView android:id="@+id/img_name" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@+id/indicator" android:background="#88343434" android:ellipsize="end" android:paddingBottom="2dp" android:paddingLeft="5dp" android:paddingRight="2dp" android:paddingTop="2dp" android:singleLine="true" android:textColor="#ededed" /> <com.androidopentutorials.imageslideshow.utils.CirclePageIndicator android:id="@+id/indicator" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_below="@+id/view_pager" android:padding="10dip" /> </RelativeLayout> <View android:id="@+id/div_a" android:layout_width="wrap_content" android:layout_height="1dp" android:layout_below="@+id/img_slideshow_layout" android:background="@color/view_divider_color" /> <View android:id="@+id/div_b" android:layout_width="wrap_content" android:layout_height="1dp" android:layout_below="@+id/div_a" android:background="@color/sliding_list_divider_color" /> </RelativeLayout> |
vp_image.xml
This layout file is for the content of a fragment to be used by ViewPager. The file contains an image view.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns: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:padding="10dp" > <ImageView android:id="@+id/image_display" android:layout_width="fill_parent" android:layout_height="fill_parent" android:contentDescription="@string/txt_image_slider" /> </RelativeLayout> |
fragmet_pdt_detail.xml
This layout file is used by ProductDetailFragment to display product details when a page (fragment) in ViewPager is clicked.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 | <?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/pdt_detail_root_layout" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="#FFFFFF" > <RelativeLayout android:id="@+id/header_layout" android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="5dp" > <RelativeLayout android:id="@+id/header_title_layout" android:layout_width="90dp" android:layout_height="wrap_content" > <ImageView android:id="@+id/product_detail_img" android:layout_width="match_parent" android:layout_height="100dp" android:contentDescription="@string/app_name" android:scaleType="fitCenter" android:src="@drawable/ic_launcher" /> </RelativeLayout> <TextView android:id="@+id/product_id_text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="10dp" android:layout_marginTop="10dp" android:paddingTop="10dp" android:layout_toRightOf="@+id/header_title_layout" android:textSize="15sp" android:textStyle="bold" /> </RelativeLayout> <View android:id="@+id/div_line1" android:layout_width="match_parent" android:layout_height="1dp" android:layout_below="@+id/header_layout" android:background="@color/view_divider_color" /> <View android:id="@+id/div_line2" android:layout_width="match_parent" android:layout_height="1dp" android:layout_below="@+id/div_line1" android:background="@color/sliding_list_divider_color" /> <ScrollView android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_below="@+id/div_line1" android:background="#EDEDED" android:padding="10dp" android:scrollbarStyle="outsideOverlay" > <LinearLayout android:id="@+id/rlayout_detail" android:layout_width="fill_parent" android:layout_height="wrap_content" android:orientation="vertical" > <RelativeLayout android:id="@+id/pdt_name_layout" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" android:background="@android:color/white" android:paddingBottom="10dp" > <TextView android:id="@+id/pdt_name_title" android:layout_width="wrap_content" android:layout_height="wrap_content" android:paddingBottom="4dp" android:paddingLeft="12dp" android:paddingRight="4dp" android:paddingTop="4dp" android:text="@string/product_name" android:textColor="@color/holo_blue_dark" android:textSize="15sp" android:textStyle="bold" /> <View android:id="@+id/div_a" android:layout_width="wrap_content" android:layout_height="2dp" android:layout_below="@+id/pdt_name_title" android:layout_marginLeft="10dp" android:layout_marginRight="10dp" android:background="@color/view_divider_color" /> <TextView android:id="@+id/pdt_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignLeft="@+id/div_a" android:layout_below="@+id/div_a" android:layout_marginLeft="5dp" android:layout_marginTop="7dp" /> </RelativeLayout> </LinearLayout> </ScrollView> <!-- <ProgressBar android:id="@+id/progressBar" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:visibility="gone" /> --> </RelativeLayout> |
Sources
TagName
In src folder, create a new class TagName in the package com.androidopentutorials.imageslideshow.utils. This class defines tag and key names defined in JSON.
1 2 3 4 5 6 7 8 9 10 11 | package com.androidopentutorials.imageslideshow.utils; public class TagName { public static final String TAG_STATUS = "status"; public static final String TAG_DATA = "data"; public static final String TAG_PRODUCTS = "products"; public static final String KEY_NAME = "name"; public static final String KEY_IMAGE_URL = "image_url"; public static final String KEY_ID = "id"; } |
CheckNetworkConnection
In src folder, create a new class CheckNetworkConnection in the package com.androidopentutorials.imageslideshow.utils.
Before our app attempts to connect to the network, it should check to see whether a network connection is available using getActiveNetworkInfo() and isConnected(). Remember, the device may be out of range of a network, or the user may have disabled both Wi-Fi and mobile data access.
We use this class to check whether the device has internet connection before sending request to receive remote JSON.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | package com.androidopentutorials.imageslideshow.utils; import android.content.Context; import android.net.ConnectivityManager; import android.net.NetworkInfo; public class CheckNetworkConnection { public static boolean isConnectionAvailable(Context context) { ConnectivityManager connectivityManager = (ConnectivityManager) context .getSystemService(Context.CONNECTIVITY_SERVICE); if (connectivityManager != null) { NetworkInfo netInfo = connectivityManager.getActiveNetworkInfo(); if (netInfo != null && netInfo.isConnected() && netInfo.isConnectedOrConnecting() && netInfo.isAvailable()) { return true; } } return false; } } |
PageIndicator
In src folder, create a new interface PageIndicator in the package com.androidopentutorials.imageslideshow.utils. This is a class from Android ViewPagerIndicator library which is used to draw a circle indicator over the ViewPager. This interface is responsible for showing an visual indicator indicating the currently visible view.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | /* * Copyright (C) 2011 Patrik Akerfeldt * Copyright (C) 2011 Jake Wharton * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.androidopentutorials.imageslideshow.utils; import android.support.v4.view.ViewPager; /** * A PageIndicator is responsible to show an visual indicator on the total views * number and the current visible view. */ public interface PageIndicator extends ViewPager.OnPageChangeListener { /** * Bind the indicator to a ViewPager. * * @param view */ void setViewPager(ViewPager view); /** * Bind the indicator to a ViewPager. * * @param view * @param initialPosition */ void setViewPager(ViewPager view, int initialPosition); /** * <p>Set the current page of both the ViewPager and indicator.</p> * * <p>This <strong>must</strong> be used if you need to set the page before * the views are drawn on screen (e.g., default start page).</p> * * @param item */ void setCurrentItem(int item); /** * Set a page change listener which will receive forwarded events. * * @param listener */ void setOnPageChangeListener(ViewPager.OnPageChangeListener listener); /** * Notify the indicator that the fragment list has changed. */ void notifyDataSetChanged(); } |
CirclePageIndicator
In src folder, create a new class CirclePageIndicator in the package com.androidopentutorials.imageslideshow.utils. This is also a class from Android ViewPagerIndicator library which implements the above interface.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 | /* * Copyright (C) 2011 Patrik Akerfeldt * Copyright (C) 2011 Jake Wharton * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.androidopentutorials.imageslideshow.utils; import static android.graphics.Paint.ANTI_ALIAS_FLAG; import static android.widget.LinearLayout.HORIZONTAL; import static android.widget.LinearLayout.VERTICAL; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Paint.Style; import android.graphics.drawable.Drawable; import android.os.Parcel; import android.os.Parcelable; import android.support.v4.view.MotionEventCompat; import android.support.v4.view.ViewConfigurationCompat; import android.support.v4.view.ViewPager; import android.util.AttributeSet; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; import com.androidopentutorials.imageslideshow.R; /** * Draws circles (one for each view). The current view position is filled and * others are only stroked. */ public class CirclePageIndicator extends View implements PageIndicator { private static final int INVALID_POINTER = -1; private float mRadius; private final Paint mPaintPageFill = new Paint(ANTI_ALIAS_FLAG); private final Paint mPaintStroke = new Paint(ANTI_ALIAS_FLAG); private final Paint mPaintFill = new Paint(ANTI_ALIAS_FLAG); private ViewPager mViewPager; private ViewPager.OnPageChangeListener mListener; private int mCurrentPage; private int mSnapPage; private float mPageOffset; private int mScrollState; private int mOrientation; private boolean mCentered; private boolean mSnap; private int mTouchSlop; private float mLastMotionX = -1; private int mActivePointerId = INVALID_POINTER; private boolean mIsDragging; public CirclePageIndicator(Context context) { this(context, null); } public CirclePageIndicator(Context context, AttributeSet attrs) { this(context, attrs, R.attr.vpiCirclePageIndicatorStyle); } public CirclePageIndicator(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); if (isInEditMode()) return; //Load defaults from resources final Resources res = getResources(); final int defaultPageColor = res.getColor(R.color.default_circle_indicator_page_color); //default_circle_indicator_fill_color final int defaultFillColor = res.getColor(R.color.holo_blue_dark); final int defaultOrientation = res.getInteger(R.integer.default_circle_indicator_orientation); final int defaultStrokeColor = res.getColor(R.color.default_circle_indicator_stroke_color); final float defaultStrokeWidth = res.getDimension(R.dimen.default_circle_indicator_stroke_width); final float defaultRadius = res.getDimension(R.dimen.default_circle_indicator_radius); final boolean defaultCentered = res.getBoolean(R.bool.default_circle_indicator_centered); final boolean defaultSnap = res.getBoolean(R.bool.default_circle_indicator_snap); //Retrieve styles attributes TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CirclePageIndicator, defStyle, 0); mCentered = a.getBoolean(R.styleable.CirclePageIndicator_centered, defaultCentered); mOrientation = a.getInt(R.styleable.CirclePageIndicator_android_orientation, defaultOrientation); mPaintPageFill.setStyle(Style.FILL); mPaintPageFill.setColor(a.getColor(R.styleable.CirclePageIndicator_pageColor, defaultPageColor)); mPaintStroke.setStyle(Style.STROKE); mPaintStroke.setColor(a.getColor(R.styleable.CirclePageIndicator_strokeColor, defaultStrokeColor)); mPaintStroke.setStrokeWidth(a.getDimension(R.styleable.CirclePageIndicator_strokeWidth, defaultStrokeWidth)); mPaintFill.setStyle(Style.FILL); mPaintFill.setColor(a.getColor(R.styleable.CirclePageIndicator_fillColor, defaultFillColor)); mRadius = a.getDimension(R.styleable.CirclePageIndicator_radius, defaultRadius); mSnap = a.getBoolean(R.styleable.CirclePageIndicator_snap, defaultSnap); Drawable background = a.getDrawable(R.styleable.CirclePageIndicator_android_background); if (background != null) { setBackgroundDrawable(background); } a.recycle(); final ViewConfiguration configuration = ViewConfiguration.get(context); mTouchSlop = ViewConfigurationCompat.getScaledPagingTouchSlop(configuration); } public void setCentered(boolean centered) { mCentered = centered; invalidate(); } public boolean isCentered() { return mCentered; } public void setPageColor(int pageColor) { mPaintPageFill.setColor(pageColor); invalidate(); } public int getPageColor() { return mPaintPageFill.getColor(); } public void setFillColor(int fillColor) { mPaintFill.setColor(fillColor); invalidate(); } public int getFillColor() { return mPaintFill.getColor(); } public void setOrientation(int orientation) { switch (orientation) { case HORIZONTAL: case VERTICAL: mOrientation = orientation; requestLayout(); break; default: throw new IllegalArgumentException("Orientation must be either HORIZONTAL or VERTICAL."); } } public int getOrientation() { return mOrientation; } public void setStrokeColor(int strokeColor) { mPaintStroke.setColor(strokeColor); invalidate(); } public int getStrokeColor() { return mPaintStroke.getColor(); } public void setStrokeWidth(float strokeWidth) { mPaintStroke.setStrokeWidth(strokeWidth); invalidate(); } public float getStrokeWidth() { return mPaintStroke.getStrokeWidth(); } public void setRadius(float radius) { mRadius = radius; invalidate(); } public float getRadius() { return mRadius; } public void setSnap(boolean snap) { mSnap = snap; invalidate(); } public boolean isSnap() { return mSnap; } @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); if (mViewPager == null) { return; } final int count = mViewPager.getAdapter().getCount(); if (count == 0) { return; } if (mCurrentPage >= count) { setCurrentItem(count - 1); return; } int longSize; int longPaddingBefore; int longPaddingAfter; int shortPaddingBefore; if (mOrientation == HORIZONTAL) { longSize = getWidth(); longPaddingBefore = getPaddingLeft(); longPaddingAfter = getPaddingRight(); shortPaddingBefore = getPaddingTop(); } else { longSize = getHeight(); longPaddingBefore = getPaddingTop(); longPaddingAfter = getPaddingBottom(); shortPaddingBefore = getPaddingLeft(); } final float threeRadius = mRadius * 3; final float shortOffset = shortPaddingBefore + mRadius; float longOffset = longPaddingBefore + mRadius; if (mCentered) { longOffset += ((longSize - longPaddingBefore - longPaddingAfter) / 2.0f) - ((count * threeRadius) / 2.0f); } float dX; float dY; float pageFillRadius = mRadius; if (mPaintStroke.getStrokeWidth() > 0) { pageFillRadius -= mPaintStroke.getStrokeWidth() / 2.0f; } //Draw stroked circles for (int iLoop = 0; iLoop < count; iLoop++) { float drawLong = longOffset + (iLoop * threeRadius); if (mOrientation == HORIZONTAL) { dX = drawLong; dY = shortOffset; } else { dX = shortOffset; dY = drawLong; } // Only paint fill if not completely transparent if (mPaintPageFill.getAlpha() > 0) { canvas.drawCircle(dX, dY, pageFillRadius, mPaintPageFill); } // Only paint stroke if a stroke width was non-zero if (pageFillRadius != mRadius) { canvas.drawCircle(dX, dY, mRadius, mPaintStroke); } } //Draw the filled circle according to the current scroll float cx = (mSnap ? mSnapPage : mCurrentPage) * threeRadius; if (!mSnap) { cx += mPageOffset * threeRadius; } if (mOrientation == HORIZONTAL) { dX = longOffset + cx; dY = shortOffset; } else { dX = shortOffset; dY = longOffset + cx; } canvas.drawCircle(dX, dY, mRadius, mPaintFill); } public boolean onTouchEvent(android.view.MotionEvent ev) { if (super.onTouchEvent(ev)) { return true; } if ((mViewPager == null) || (mViewPager.getAdapter().getCount() == 0)) { return false; } final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; switch (action) { case MotionEvent.ACTION_DOWN: mActivePointerId = MotionEventCompat.getPointerId(ev, 0); mLastMotionX = ev.getX(); break; case MotionEvent.ACTION_MOVE: { final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, mActivePointerId); final float x = MotionEventCompat.getX(ev, activePointerIndex); final float deltaX = x - mLastMotionX; if (!mIsDragging) { if (Math.abs(deltaX) > mTouchSlop) { mIsDragging = true; } } if (mIsDragging) { mLastMotionX = x; if (mViewPager.isFakeDragging() || mViewPager.beginFakeDrag()) { mViewPager.fakeDragBy(deltaX); } } break; } case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_UP: if (!mIsDragging) { final int count = mViewPager.getAdapter().getCount(); final int width = getWidth(); final float halfWidth = width / 2f; final float sixthWidth = width / 6f; if ((mCurrentPage > 0) && (ev.getX() < halfWidth - sixthWidth)) { if (action != MotionEvent.ACTION_CANCEL) { mViewPager.setCurrentItem(mCurrentPage - 1); } return true; } else if ((mCurrentPage < count - 1) && (ev.getX() > halfWidth + sixthWidth)) { if (action != MotionEvent.ACTION_CANCEL) { mViewPager.setCurrentItem(mCurrentPage + 1); } return true; } } mIsDragging = false; mActivePointerId = INVALID_POINTER; if (mViewPager.isFakeDragging()) mViewPager.endFakeDrag(); break; case MotionEventCompat.ACTION_POINTER_DOWN: { final int index = MotionEventCompat.getActionIndex(ev); mLastMotionX = MotionEventCompat.getX(ev, index); mActivePointerId = MotionEventCompat.getPointerId(ev, index); break; } case MotionEventCompat.ACTION_POINTER_UP: final int pointerIndex = MotionEventCompat.getActionIndex(ev); final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); if (pointerId == mActivePointerId) { final int newPointerIndex = pointerIndex == 0 ? 1 : 0; mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); } mLastMotionX = MotionEventCompat.getX(ev, MotionEventCompat.findPointerIndex(ev, mActivePointerId)); break; } return true; } @Override public void setViewPager(ViewPager view) { if (mViewPager == view) { return; } if (mViewPager != null) { mViewPager.setOnPageChangeListener(null); } if (view.getAdapter() == null) { throw new IllegalStateException("ViewPager does not have adapter instance."); } mViewPager = view; mViewPager.setOnPageChangeListener(this); invalidate(); } @Override public void setViewPager(ViewPager view, int initialPosition) { setViewPager(view); setCurrentItem(initialPosition); } @Override public void setCurrentItem(int item) { if (mViewPager == null) { throw new IllegalStateException("ViewPager has not been bound."); } mViewPager.setCurrentItem(item); mCurrentPage = item; invalidate(); } @Override public void notifyDataSetChanged() { invalidate(); } @Override public void onPageScrollStateChanged(int state) { mScrollState = state; if (mListener != null) { mListener.onPageScrollStateChanged(state); } } @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { mCurrentPage = position; mPageOffset = positionOffset; invalidate(); if (mListener != null) { mListener.onPageScrolled(position, positionOffset, positionOffsetPixels); } } @Override public void onPageSelected(int position) { if (mSnap || mScrollState == ViewPager.SCROLL_STATE_IDLE) { mCurrentPage = position; mSnapPage = position; invalidate(); } if (mListener != null) { mListener.onPageSelected(position); } } @Override public void setOnPageChangeListener(ViewPager.OnPageChangeListener listener) { mListener = listener; } /* * (non-Javadoc) * * @see android.view.View#onMeasure(int, int) */ @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (mOrientation == HORIZONTAL) { setMeasuredDimension(measureLong(widthMeasureSpec), measureShort(heightMeasureSpec)); } else { setMeasuredDimension(measureShort(widthMeasureSpec), measureLong(heightMeasureSpec)); } } /** * Determines the width of this view * * @param measureSpec * A measureSpec packed into an int * @return The width of the view, honoring constraints from measureSpec */ private int measureLong(int measureSpec) { int result; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if ((specMode == MeasureSpec.EXACTLY) || (mViewPager == null)) { //We were told how big to be result = specSize; } else { //Calculate the width according the views count final int count = mViewPager.getAdapter().getCount(); result = (int)(getPaddingLeft() + getPaddingRight() + (count * 2 * mRadius) + (count - 1) * mRadius + 1); //Respect AT_MOST value if that was what is called for by measureSpec if (specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); } } return result; } /** * Determines the height of this view * * @param measureSpec * A measureSpec packed into an int * @return The height of the view, honoring constraints from measureSpec */ private int measureShort(int measureSpec) { int result; int specMode = MeasureSpec.getMode(measureSpec); int specSize = MeasureSpec.getSize(measureSpec); if (specMode == MeasureSpec.EXACTLY) { //We were told how big to be result = specSize; } else { //Measure the height result = (int)(2 * mRadius + getPaddingTop() + getPaddingBottom() + 1); //Respect AT_MOST value if that was what is called for by measureSpec if (specMode == MeasureSpec.AT_MOST) { result = Math.min(result, specSize); } } return result; } @Override public void onRestoreInstanceState(Parcelable state) { SavedState savedState = (SavedState)state; super.onRestoreInstanceState(savedState.getSuperState()); mCurrentPage = savedState.currentPage; mSnapPage = savedState.currentPage; requestLayout(); } @Override public Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); SavedState savedState = new SavedState(superState); savedState.currentPage = mCurrentPage; return savedState; } static class SavedState extends BaseSavedState { int currentPage; public SavedState(Parcelable superState) { super(superState); } private SavedState(Parcel in) { super(in); currentPage = in.readInt(); } @Override public void writeToParcel(Parcel dest, int flags) { super.writeToParcel(dest, flags); dest.writeInt(currentPage); } @SuppressWarnings("UnusedDeclaration") public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { @Override public SavedState createFromParcel(Parcel in) { return new SavedState(in); } @Override public SavedState[] newArray(int size) { return new SavedState[size]; } }; } } |
FileUtils
In src folder, create a new class FileUtils in the package com.androidopentutorials.imageslideshow.utils. This class has utility methods to close Reader, Writer, InputStream and OutputStream which is used by JSONParser class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | package com.androidopentutorials.imageslideshow.utils; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.Reader; import java.io.Writer; public class FileUtils { public static void close(Reader reader) { if (reader != null) try { reader.close(); } catch (IOException e) { } } public static void close(Writer writer) { if (writer != null) try { writer.close(); } catch (IOException e) { } } public static void close(InputStream inputStream) { if (inputStream != null) try { inputStream.close(); } catch (IOException e) { } } public static void close(OutputStream outputStream) { if (outputStream != null) try { outputStream.close(); } catch (IOException e) { } } } |
GetJSONObject
In src folder, create a new class GetJSONObject in the package com.androidopentutorials.imageslideshow.json. For the given URL, it gets JSONObject. It checks for Android version, if it is greater than FROYO then it uses HttpURLConnection else uses HttpClient. For more information on this refer Android Http Clients.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | package com.androidopentutorials.imageslideshow.json; import java.io.IOException; import org.json.JSONException; import org.json.JSONObject; import android.os.Build; public class GetJSONObject { public static JSONObject getJSONObject(String url) throws IOException, JSONException { JSONParser jsonParser = new JSONParser(); JSONObject jsonObject = null; // Use HttpURLConnection if (Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO) { jsonObject = jsonParser.getJSONHttpURLConnection(url); } else { // use HttpClient jsonObject = jsonParser.getJSONHttpClient(url); } return jsonObject; } } |
JSONParser
In src folder, create a new class JSONParser in the package com.androidopentutorials.imageslideshow.json. This class opens a connection to the remote json url, creates a reader object, retrieves the json string and returns a JSONObject.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | package com.androidopentutorials.imageslideshow.json; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; import org.json.JSONException; import org.json.JSONObject; import android.util.Log; import com.androidopentutorials.imageslideshow.utils.FileUtils; public class JSONParser { static String json = ""; private HttpURLConnection httpConnection; InputStream inputStream = null; BufferedReader reader = null; public JSONParser() { } private void openHttpUrlConnection(String urlString) throws IOException { Log.d("urlstring in parser", urlString+""); URL url = new URL(urlString); URLConnection connection = url.openConnection(); httpConnection = (HttpURLConnection) connection; httpConnection.setConnectTimeout(30000); httpConnection.setRequestMethod("GET"); httpConnection.connect(); } private void openHttpClient(String urlString) throws IOException { DefaultHttpClient httpClient = new DefaultHttpClient(); HttpParams httpParams = httpClient.getParams(); HttpConnectionParams.setConnectionTimeout(httpParams, 30000); HttpGet httpGet = new HttpGet(urlString); HttpResponse httpResponse = httpClient.execute(httpGet); HttpEntity httpEntity = httpResponse.getEntity(); inputStream = httpEntity.getContent(); reader = new BufferedReader( new InputStreamReader(inputStream, "UTF-8"), 8); } // using HttpClient for <= Froyo public JSONObject getJSONHttpClient(String url) throws ClientProtocolException, IOException, JSONException { JSONObject jsonObject = null; try { openHttpClient(url); StringBuilder sb = new StringBuilder(); String line = null; while ((line = reader.readLine()) != null) { sb.append(line + "\n"); } json = sb.toString(); Log.d("json", json); jsonObject = new JSONObject(json); } finally { FileUtils.close(inputStream); FileUtils.close(reader); } return jsonObject; } // using HttpURLConnection for > Froyo public JSONObject getJSONHttpURLConnection(String urlString) throws IOException, JSONException { BufferedReader reader = null; StringBuffer output = new StringBuffer(""); InputStream stream = null; JSONObject jsonObject = null; try { openHttpUrlConnection(urlString); if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_OK) { stream = httpConnection.getInputStream(); reader = new BufferedReader(new InputStreamReader(stream, "UTF-8"), 8); String line = ""; while ((line = reader.readLine()) != null) output.append(line + "\n"); json = output.toString(); jsonObject = new JSONObject(json); } } finally { FileUtils.close(stream); FileUtils.close(reader); } return jsonObject; } } |
JsonReader
In src folder, create a new class JsonReader in the package com.androidopentutorials.imageslideshow.json. This class has an utility method which takes JSONObject as parameter, parses it and returns a list of products.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | package com.androidopentutorials.imageslideshow.json; import java.util.ArrayList; import java.util.List; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import com.androidopentutorials.imageslideshow.bean.Product; import com.androidopentutorials.imageslideshow.utils.TagName; public class JsonReader { public static List<Product> getHome(JSONObject jsonObject) throws JSONException { List<Product> products = new ArrayList<Product>(); JSONArray jsonArray = jsonObject.getJSONArray(TagName.TAG_PRODUCTS); Product product; for (int i = 0; i < jsonArray.length(); i++) { product = new Product(); JSONObject productObj = jsonArray.getJSONObject(i); product.setId(productObj.getInt(TagName.KEY_ID)); product.setName(productObj.getString(TagName.KEY_NAME)); product.setImageUrl(productObj.getString(TagName.KEY_IMAGE_URL)); products.add(product); } return products; } } |
Product
In src folder, create a new class Product in the package com.androidopentutorials.imageslideshow.bean. This is a bean class which represents a single product.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 | package com.androidopentutorials.imageslideshow.bean; import android.os.Parcel; import android.os.Parcelable; public class Product implements Parcelable { private int id; private String name; private String imageUrl; public Product() { super(); } private Product(Parcel in) { super(); this.id = in.readInt(); this.name = in.readString(); this.imageUrl = in.readString(); } @Override public int describeContents() { return 0; } @Override public void writeToParcel(Parcel parcel, int flags) { parcel.writeInt(getId()); parcel.writeString(getName()); parcel.writeString(getImageUrl()); } public static final Parcelable.Creator<Product> CREATOR = new Parcelable.Creator<Product>() { public Product createFromParcel(Parcel in) { return new Product(in); } public Product[] newArray(int size) { return new Product[size]; } }; public int getId() { return id; } public void setId(int id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getImageUrl() { return imageUrl; } public void setImageUrl(String imageUrl) { this.imageUrl = imageUrl; } public static Parcelable.Creator<Product> getCreator() { return CREATOR; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + id; return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Product other = (Product) obj; if (id != other.id) return false; return true; } @Override public String toString() { return "Product [id=" + id + ", name=" + name + ", imageUrl=" + imageUrl + "]"; } } |
ImageSlideAdapter
In src folder, create a new class ImageSlideAdapter in the package com.androidopentutorials.imageslideshow.adapter.
- This class is an implementation of PagerAdapter to populate pages inside a ViewPager.
- When you implement a PagerAdapter, you must override the following methods at minimum:
- instantiateItem(ViewGroup, int)
- destroyItem(ViewGroup, int, Object)
- getCount()
- isViewFromObject(View, Object)
- When the ImageView is clicked, it starts a FragmentTransaction and replaces the content frame with ProductDetailFragment.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | package com.androidopentutorials.imageslideshow.adapter; import java.util.Collections; import java.util.LinkedList; import java.util.List; import android.app.Activity; import android.graphics.Bitmap; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentTransaction; import android.support.v4.view.PagerAdapter; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageView; import com.androidopentutorials.imageslideshow.R; import com.androidopentutorials.imageslideshow.bean.Product; import com.androidopentutorials.imageslideshow.fragment.HomeFragment; import com.androidopentutorials.imageslideshow.fragment.ProductDetailFragment; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.assist.ImageLoadingListener; import com.nostra13.universalimageloader.core.assist.SimpleImageLoadingListener; import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; public class ImageSlideAdapter extends PagerAdapter { ImageLoader imageLoader = ImageLoader.getInstance(); DisplayImageOptions options; private ImageLoadingListener imageListener; FragmentActivity activity; List<Product> products; HomeFragment homeFragment; public ImageSlideAdapter(FragmentActivity activity, List<Product> products, HomeFragment homeFragment) { this.activity = activity; this.homeFragment = homeFragment; this.products = products; options = new DisplayImageOptions.Builder() .showImageOnFail(R.drawable.ic_error) .showStubImage(R.drawable.ic_launcher) .showImageForEmptyUri(R.drawable.ic_empty).cacheInMemory() .cacheOnDisc().build(); imageListener = new ImageDisplayListener(); } @Override public int getCount() { return products.size(); } @Override public View instantiateItem(ViewGroup container, final int position) { LayoutInflater inflater = (LayoutInflater) activity .getSystemService(Activity.LAYOUT_INFLATER_SERVICE); View view = inflater.inflate(R.layout.vp_image, container, false); ImageView mImageView = (ImageView) view .findViewById(R.id.image_display); mImageView.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { Bundle arguments = new Bundle(); Fragment fragment = null; Log.d("position adapter", "" + position); Product product = (Product) products.get(position); arguments.putParcelable("singleProduct", product); // Start a new fragment fragment = new ProductDetailFragment(); fragment.setArguments(arguments); FragmentTransaction transaction = activity .getSupportFragmentManager().beginTransaction(); transaction.replace(R.id.content_frame, fragment, ProductDetailFragment.ARG_ITEM_ID); transaction.addToBackStack(ProductDetailFragment.ARG_ITEM_ID); transaction.commit(); } }); imageLoader.displayImage( ((Product) products.get(position)).getImageUrl(), mImageView, options, imageListener); container.addView(view); return view; } @Override public void destroyItem(ViewGroup container, int position, Object object) { container.removeView((View) object); } @Override public boolean isViewFromObject(View view, Object object) { return view == object; } private static class ImageDisplayListener extends SimpleImageLoadingListener { static final List<String> displayedImages = Collections .synchronizedList(new LinkedList<String>()); @Override public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { if (loadedImage != null) { ImageView imageView = (ImageView) view; boolean firstDisplay = !displayedImages.contains(imageUri); if (firstDisplay) { FadeInBitmapDisplayer.animate(imageView, 500); displayedImages.add(imageUri); } } } } } |
HomeFragment
In src folder, create a new class HomeFragment in the package com.androidopentutorials.imageslideshow.fragment.
- ViewPager.setCurrentItem() is used to animate screen slides automatically.
- We use Handler to make ViewPager auto slide after five (5) seconds.
- If the user slides the screen then we make ViewPager auto slide after ten (10) seconds.
Steps:
- In onResume(), if there are no products (==null) it sends a request where it executes a background AsyncTask to read remote JSON. It parses this JSON using the JsonReader.getHome() and returns list of products. In onPostExecute() it create ImageSlideAdapter and sets it in ViewPager.
- For ViewPager, we set touch listener and for MotionEvent.ACTION_UP (which is executed on ViewPager touch release) we post a runnable to the handler queue to be executed after ten (10) seconds which changes teh slide to next image.
- We also set a page change listener which changes the image name to reflect the current image view.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 | package com.androidopentutorials.imageslideshow.fragment; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.List; import org.json.JSONObject; import com.androidopentutorials.imageslideshow.R; import com.androidopentutorials.imageslideshow.adapter.ImageSlideAdapter; import com.androidopentutorials.imageslideshow.bean.Product; import com.androidopentutorials.imageslideshow.json.GetJSONObject; import com.androidopentutorials.imageslideshow.json.JsonReader; import com.androidopentutorials.imageslideshow.utils.CheckNetworkConnection; import com.androidopentutorials.imageslideshow.utils.CirclePageIndicator; import com.androidopentutorials.imageslideshow.utils.PageIndicator; import com.androidopentutorials.imageslideshow.utils.TagName; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.view.ViewPager; import android.support.v4.view.ViewPager.OnPageChangeListener; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.View.OnTouchListener; import android.view.ViewGroup; import android.widget.TextView; public class HomeFragment extends Fragment { public static final String ARG_ITEM_ID = "home_fragment"; private static final long ANIM_VIEWPAGER_DELAY = 5000; private static final long ANIM_VIEWPAGER_DELAY_USER_VIEW = 10000; // UI References private ViewPager mViewPager; TextView imgNameTxt; PageIndicator mIndicator; AlertDialog alertDialog; List<Product> products; RequestImgTask task; boolean stopSliding = false; String message; private Runnable animateViewPager; private Handler handler; String url = "http://192.168.3.119:8080/products.json"; FragmentActivity activity; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); activity = getActivity(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_home, container, false); findViewById(view); mIndicator.setOnPageChangeListener(new PageChangeListener()); mViewPager.setOnPageChangeListener(new PageChangeListener()); mViewPager.setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { v.getParent().requestDisallowInterceptTouchEvent(true); switch (event.getAction()) { case MotionEvent.ACTION_CANCEL: break; case MotionEvent.ACTION_UP: // calls when touch release on ViewPager if (products != null && products.size() != 0) { stopSliding = false; runnable(products.size()); handler.postDelayed(animateViewPager, ANIM_VIEWPAGER_DELAY_USER_VIEW); } break; case MotionEvent.ACTION_MOVE: // calls when ViewPager touch if (handler != null && stopSliding == false) { stopSliding = true; handler.removeCallbacks(animateViewPager); } break; } return false; } }); return view; } private void findViewById(View view) { mViewPager = (ViewPager) view.findViewById(R.id.view_pager); mIndicator = (CirclePageIndicator) view.findViewById(R.id.indicator); imgNameTxt = (TextView) view.findViewById(R.id.img_name); } public void runnable(final int size) { handler = new Handler(); animateViewPager = new Runnable() { public void run() { if (!stopSliding) { if (mViewPager.getCurrentItem() == size - 1) { mViewPager.setCurrentItem(0); } else { mViewPager.setCurrentItem( mViewPager.getCurrentItem() + 1, true); } handler.postDelayed(animateViewPager, ANIM_VIEWPAGER_DELAY); } } }; } @Override public void onResume() { if (products == null) { sendRequest(); } else { mViewPager.setAdapter(new ImageSlideAdapter(activity, products, HomeFragment.this)); mIndicator.setViewPager(mViewPager); imgNameTxt.setText("" + ((Product) products.get(mViewPager.getCurrentItem())) .getName()); runnable(products.size()); //Re-run callback handler.postDelayed(animateViewPager, ANIM_VIEWPAGER_DELAY); } super.onResume(); } @Override public void onPause() { if (task != null) task.cancel(true); if (handler != null) { //Remove callback handler.removeCallbacks(animateViewPager); } super.onPause(); } private void sendRequest() { if (CheckNetworkConnection.isConnectionAvailable(activity)) { task = new RequestImgTask(activity); task.execute(url); } else { message = getResources().getString(R.string.no_internet_connection); showAlertDialog(message, true); } } public void showAlertDialog(String message, final boolean finish) { alertDialog = new AlertDialog.Builder(activity).create(); alertDialog.setMessage(message); alertDialog.setCancelable(false); alertDialog.setButton(AlertDialog.BUTTON_POSITIVE, "OK", new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); if (finish) activity.finish(); } }); alertDialog.show(); } private class PageChangeListener implements OnPageChangeListener { @Override public void onPageScrollStateChanged(int state) { if (state == ViewPager.SCROLL_STATE_IDLE) { if (products != null) { imgNameTxt.setText("" + ((Product) products.get(mViewPager .getCurrentItem())).getName()); } } } @Override public void onPageScrolled(int arg0, float arg1, int arg2) { } @Override public void onPageSelected(int arg0) { } } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); } private class RequestImgTask extends AsyncTask<String, Void, List<Product>> { private final WeakReference<Activity> activityWeakRef; Throwable error; public RequestImgTask(Activity context) { this.activityWeakRef = new WeakReference<Activity>(context); } @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected List<Product> doInBackground(String... urls) { try { JSONObject jsonObject = getJsonObject(urls[0]); if (jsonObject != null) { boolean status = jsonObject.getBoolean(TagName.TAG_STATUS); if (status) { JSONObject jsonData = jsonObject .getJSONObject(TagName.TAG_DATA); if (jsonData != null) { products = JsonReader.getHome(jsonData); } else { message = jsonObject.getString(TagName.TAG_DATA); } } else { message = jsonObject.getString(TagName.TAG_DATA); } } } catch (Exception e) { e.printStackTrace(); } return products; } /** * It returns jsonObject for the specified url. * * @param url * @return JSONObject */ public JSONObject getJsonObject(String url) { JSONObject jsonObject = null; try { jsonObject = GetJSONObject.getJSONObject(url); } catch (Exception e) { } return jsonObject; } @Override protected void onPostExecute(List<Product> result) { super.onPostExecute(result); if (activityWeakRef != null && !activityWeakRef.get().isFinishing()) { if (error != null && error instanceof IOException) { message = getResources().getString(R.string.time_out); showAlertDialog(message, true); } else if (error != null) { message = getResources().getString(R.string.error_occured); showAlertDialog(message, true); } else { products = result; if (result != null) { if (products != null && products.size() != 0) { mViewPager.setAdapter(new ImageSlideAdapter( activity, products, HomeFragment.this)); mIndicator.setViewPager(mViewPager); imgNameTxt.setText("" + ((Product) products.get(mViewPager .getCurrentItem())).getName()); runnable(products.size()); handler.postDelayed(animateViewPager, ANIM_VIEWPAGER_DELAY); } else { imgNameTxt.setText("No Products"); } } else { } } } } } } |
ProductDetailFragment
In src folder, create a new class ProductDetailFragment in the package com.androidopentutorials.imageslideshow.fragment. This class gets a single product from bundle and displays product image, id and name.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | package com.androidopentutorials.imageslideshow.fragment; import java.util.Collections; import java.util.LinkedList; import java.util.List; import android.app.Activity; import android.graphics.Bitmap; import android.os.Bundle; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; import com.androidopentutorials.imageslideshow.R; import com.androidopentutorials.imageslideshow.bean.Product; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.assist.ImageLoadingListener; import com.nostra13.universalimageloader.core.assist.SimpleImageLoadingListener; import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; public class ProductDetailFragment extends Fragment { TextView pdtIdTxt; TextView pdtNameTxt; ImageView pdtImg; Activity activity; ImageLoader imageLoader = ImageLoader.getInstance(); DisplayImageOptions options; private ImageLoadingListener imageListener; Product product; public static final String ARG_ITEM_ID = "pdt_detail_fragment"; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); activity = getActivity(); options = new DisplayImageOptions.Builder() .showImageOnFail(R.drawable.ic_launcher) .showStubImage(R.drawable.ic_launcher) .showImageForEmptyUri(R.drawable.ic_launcher).cacheInMemory() .cacheOnDisc().build(); imageListener = new ImageDisplayListener(); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View view = inflater.inflate(R.layout.fragment_pdt_detail, container, false); findViewById(view); Bundle bundle = this.getArguments(); if (bundle != null) { product = bundle.getParcelable("singleProduct"); setProductItem(product); } return view; } private void findViewById(View view) { pdtNameTxt = (TextView) view.findViewById(R.id.pdt_name); pdtIdTxt = (TextView) view.findViewById(R.id.product_id_text); pdtImg = (ImageView) view.findViewById(R.id.product_detail_img); } private static class ImageDisplayListener extends SimpleImageLoadingListener { static final List<String> displayedImages = Collections .synchronizedList(new LinkedList<String>()); @Override public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { if (loadedImage != null) { ImageView imageView = (ImageView) view; boolean firstDisplay = !displayedImages.contains(imageUri); if (firstDisplay) { FadeInBitmapDisplayer.animate(imageView, 500); displayedImages.add(imageUri); } } } } private void setProductItem(Product resultProduct) { pdtNameTxt.setText("" + resultProduct.getName()); pdtIdTxt.setText("Product Id: " + resultProduct.getId()); imageLoader.displayImage(resultProduct.getImageUrl(), pdtImg, options, imageListener); } } |
MainActivity
In src folder, create a new class MainActivity in the package com.androidopentutorials.imageslideshow.
- This is the main activity class.
- When the app starts, it begins a new FragmentTransaction and starts HomeFragment.
- It handles proper back navigation of fragments on back key pressed by overriding onBackPressed().
- It handles fragment orientation changes and retains the fragment state by overriding onSaveInstanceState().
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 | package com.androidopentutorials.imageslideshow; import android.os.Bundle; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentTransaction; import android.view.Menu; import com.androidopentutorials.imageslideshow.fragment.HomeFragment; import com.androidopentutorials.imageslideshow.fragment.ProductDetailFragment; public class MainActivity extends FragmentActivity { private Fragment contentFragment; HomeFragment homeFragment; ProductDetailFragment pdtDetailFragment; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); FragmentManager fragmentManager = getSupportFragmentManager(); if (savedInstanceState != null) { if (savedInstanceState.containsKey("content")) { String content = savedInstanceState.getString("content"); if (content.equals(ProductDetailFragment.ARG_ITEM_ID)) { if (fragmentManager .findFragmentByTag(ProductDetailFragment.ARG_ITEM_ID) != null) { contentFragment = fragmentManager .findFragmentByTag(ProductDetailFragment.ARG_ITEM_ID); } } } if (fragmentManager.findFragmentByTag(HomeFragment.ARG_ITEM_ID) != null) { homeFragment = (HomeFragment) fragmentManager .findFragmentByTag(HomeFragment.ARG_ITEM_ID); contentFragment = homeFragment; } } else { homeFragment = new HomeFragment(); switchContent(homeFragment, HomeFragment.ARG_ITEM_ID); } } @Override protected void onSaveInstanceState(Bundle outState) { if (contentFragment instanceof HomeFragment) { outState.putString("content", HomeFragment.ARG_ITEM_ID); } else { outState.putString("content", ProductDetailFragment.ARG_ITEM_ID); } super.onSaveInstanceState(outState); } public void switchContent(Fragment fragment, String tag) { FragmentManager fragmentManager = getSupportFragmentManager(); while (fragmentManager.popBackStackImmediate()) ; if (fragment != null) { FragmentTransaction transaction = fragmentManager .beginTransaction(); transaction.replace(R.id.content_frame, fragment, tag); // Only ProductDetailFragment is added to the back stack. if (!(fragment instanceof HomeFragment)) { transaction.addToBackStack(tag); } transaction.commit(); contentFragment = fragment; } } @Override public void onBackPressed() { FragmentManager fm = getSupportFragmentManager(); if (fm.getBackStackEntryCount() > 0) { super.onBackPressed(); } else if (contentFragment instanceof HomeFragment || fm.getBackStackEntryCount() == 0) { finish(); } } @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.main, menu); return true; } } |
AppData
In src folder, create a new class AppData in the package com.androidopentutorials.imageslideshow.
Universal ImageLoader configuration (ImageLoaderConfiguration) is global for application hence create a class which extends android.app.Application and create a global configuration and initialize ImageLoader.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 |