Sunday, June 9, 2013

Overflowing ListView





What this project is : 
  • Overflowing list lays out items across a view when list is scrolled to bottom
  • Allows interaction on list items as well as view covered
  • A behaviour like clippingChildren to false with some initial top padding
What this project is NOT: 

  • Best way to do this, its a hacky solution, an ideal solution would be overriding AdapterView or a ViewGroup
Why did I implement this: 

  • Imitating an implementation from an iOS app.
Source: 

Monday, November 19, 2012

Infinite Scrolling with reusable views iOS

UIScrollView in iOS provides a large ground to play with in iOS, UITableView being one of those.
But as the ground is large and opportunity as well, memory of a device should not be incorrectly used.


The example, that we have today shows "recycled" or "reusable" views to create an infinite scrolling behaviour. 

Some highlights of the implementation

  • There are 3 views at most at a single time
  • UIScrollView.contentSize is set to compose all of the 3
  • Whenever scrolling is performed, UIScrollView.contentOffset is set back to center.
  • After contentOffset is at center, depending upon the scrolling (left or right) the new center position is calculated and view at center is replaced by the view expected for the position. This happens smoothly enough and user doesn't suspect the happening behind scene.
  • After center view is created the view before and after is created as well.
  • If the current position is the first  (index 0) the view before is the last view(viewCount-1) and if current position is last (index, viewCount-1) next view is first (index 0). Giving a circular scrolling behaviour
The example demonstrates the same, with infinite scroll through various iPhone versions. Complete example can be downloaded from : https://bitbucket.org/shardul/ios/src

We achieve this by subclassing UIScrollView, lets look at the implementation.

InfiniteScrollView.h


//
//  InfinitePagingScrollView.h
//  InfinitePagingScrollView
//
//  Created by Shardul Prabhu on 18/11/12.
//  Copyright (c) 2012 Shardul Prabhu. All rights reserved.
//

#import 

@protocol InfinitePagingScrollViewDelegate;

@interface InfinitePagingScrollView : UIScrollView

@property (nonatomic,assign) id delegateForViews;

@end

@protocol InfinitePagingScrollViewDelegate

- (UIView*)setupView:(UIView*)reusedView forPosition:(NSUInteger) position;
- (NSUInteger) noOfViews;
- (void)clearView:(UIView*)reusableView;

@end


We will use delegation model of implementing.
InfinitePagingScrollView.h declares InfinitePagingScrollViewDelegate which requires implementation of


- (UIView*)setupView:(UIView*)reusedView forPosition:(NSUInteger) position;
 
    This method should return a UIView to be shown defined position. If reusedView is nil a new view should be created, otherwise, the same view can be reused by just updating the display logic.

- (NSUInteger) noOfViews;

   The total no of views the delegate wants to scroll through

- (void)clearView:(UIView*)reusableView;

   Before sending the reusedView to setupView method call, clearView for the same is called. So appropriate clearance can be made.

InfiniteScrollView.m


//
//  InfinitePagingScrollView.m
//  InfinitePagingScrollView
//
//  Created by Shardul Prabhu on 18/11/12.
//  Copyright (c) 2012 Shardul Prabhu. All rights reserved.
//

#import "InfinitePagingScrollView.h"

@protocol InfinitePagingScrollViewDelegate;

static const NSUInteger numOfReusableViews = 3;

@interface InfinitePagingScrollView(){
    
    NSMutableArray  *visibleViews;
    UIView          *containerView;
    NSUInteger      currentPosition;
}

@end

@implementation InfinitePagingScrollView

@synthesize delegateForViews=_delegateForViews;

- (id)initWithCoder:(NSCoder *)aDecoder{
    if((self= [super initWithCoder:aDecoder])){
        
        visibleViews=[[NSMutableArray alloc] init];
        
        containerView = [[UIView alloc] init];
        
        [self addSubview:containerView];
        
        [self setShowsHorizontalScrollIndicator:NO];
        
    }
    return self;
}

- (void)layoutSubviews{
    [super layoutSubviews];
    
    if(self.delegateForViews){
        
        CGPoint contentOffset = self.contentOffset;
        
        if([self.delegateForViews noOfViews]>numOfReusableViews){
            NSUInteger centerIndex=visibleViews.count/2;
            NSUInteger noOfViews=[self.delegateForViews noOfViews];
            UIView *centerView=[visibleViews objectAtIndex:centerIndex];
            
            CGPoint centerViewOrigin=centerView.frame.origin;
            CGSize centerViewSize=centerView.frame.size;
            CGFloat offsetDifference=contentOffset.x-centerViewOrigin.x;
            CGFloat offsetDifferenceAbs=fabs(contentOffset.x-centerViewOrigin.x);
            
            if(offsetDifferenceAbs>=centerViewSize.width){
                
                if(offsetDifference<0){
                    currentPosition--;
                }else{
                    currentPosition++;
                }
                
                self.contentOffset=centerViewOrigin;
                
                currentPosition=[self getPosition:currentPosition noOfViews:noOfViews];
                
                [self.delegateForViews clearView:centerView];
                [self.delegateForViews setupView:centerView forPosition:currentPosition];
                
                for (int i=centerIndex-1; i>=0; i--) {
                    UIView* prevView=[visibleViews objectAtIndex:i];
                    [self.delegateForViews clearView:prevView];
                    [self.delegateForViews setupView:prevView forPosition:
                            [self getPosition:currentPosition-1 noOfViews:noOfViews]];
                }
                
                for (int i=centerIndex+1; i<visibleViews.count; i++) {
                    UIView* nextView=[visibleViews objectAtIndex:i];
                    [self.delegateForViews clearView:nextView];
                    [self.delegateForViews setupView:nextView forPosition:
                            [self getPosition:currentPosition+1 noOfViews:noOfViews]];
                }
                
            }
        }
        
    }
    
}

- (NSUInteger)getPosition:(NSUInteger) aPosition noOfViews:(NSUInteger) count{
    if(aPosition==-1){
        aPosition=count-1;
    }
    else if(aPosition==(count)){
        aPosition=0;
    }
    return aPosition;
}


- (void)setDelegateForViews:(id<InfinitePagingScrollViewDelegate>)aDelegateForViews{
    _delegateForViews=aDelegateForViews;
    [self invalidateViews];
}


- (void) invalidateViews{
    if(self.delegateForViews){
        currentPosition=1;
        NSUInteger noOfViews=MIN(numOfReusableViews, [self.delegateForViews noOfViews]);
        
        containerView.frame= CGRectMake(0, 0, self.frame.size.width*noOfViews,
                                        self.frame.size.height);
        self.contentSize=CGSizeMake(self.frame.size.width*noOfViews, self.frame.size.height);
        for (int i=0; i<noOfViews; i++) {
            UIView* view=[self.delegateForViews setupView:nil forPosition:i];
            CGRect frame=view.frame;
            view.frame=CGRectMake(frame.origin.x+(frame.size.width*i),
                                  frame.origin.y,
                                  frame.size.width,
                                  frame.size.height);
            [containerView addSubview:view];
            [visibleViews addObject:view];
        }
        
        
    }
}

@end


layoutSubViews is where all the magic happens. Offsets are adjusted depending upon the scroll and delegate calls are made accordingly.

ViewController.m


//
//  ViewController.m
//  InfinitePagingScrollView
//
//  Created by Shardul Prabhu on 18/11/12.
//  Copyright (c) 2012 Shardul Prabhu. All rights reserved.
//

#import "ViewController.h"
#import "InfinitePagingScrollView.h"

@interface ViewController () <InfinitePagingScrollViewDelegate>{
    NSArray         *iPhoneVersions;
}

@property (weak, nonatomic) IBOutlet  InfinitePagingScrollView *infiniteScrollView;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    iPhoneVersions=[[NSArray alloc] initWithObjects:@"iPhone",@"iPhone 3G",@"iPhone 3GS",
                    @"iPhone 4",@"iPhone 4S",@"iPhone 5", nil];
}

- (void)viewDidAppear:(BOOL)animated{
    self.infiniteScrollView.delegateForViews=self;
}

- (NSUInteger)noOfViews{
    return iPhoneVersions.count;
}

- (UIView*)setupView:(UIView *)reusableView forPosition:(NSUInteger)position{
    UIView* view=reusableView;
    
    if(view==nil){
        UIStoryboard *storyboard = [UIStoryboard
                                    storyboardWithName:@"MainStoryboard_iPhone" bundle:nil];
        UIViewController *viewController = [storyboard
                            instantiateViewControllerWithIdentifier:@"iPhoneViewController"];
        view=viewController.view;
        [(UILabel*)view setTextAlignment:NSTextAlignmentCenter];
        view.frame=self.infiniteScrollView.frame;
    }
    
    [(UILabel*)view setText:[iPhoneVersions objectAtIndex:position]];
    [view setBackgroundColor:[UIColor colorWithWhite:1.0-(position/2.0*0.1) alpha:1.0]];
    [view setUserInteractionEnabled:YES];
    
    UITapGestureRecognizer* tapGesture=[[UITapGestureRecognizer alloc]
                                        initWithTarget:self action:@selector(tapped:)];
    [view addGestureRecognizer:tapGesture];
    
    return view;
}

- (void)clearView:(UIView*)reusableView{
    [reusableView setBackgroundColor:[UIColor clearColor]];
    [(UILabel*)reusableView setFont:[UIFont systemFontOfSize:16.0]];
    [reusableView removeGestureRecognizer:[reusableView.gestureRecognizers lastObject]];
}

- (void)tapped:(UIGestureRecognizer*) gestureRecognizer{
    [((UILabel*)gestureRecognizer.view) setFont:[UIFont boldSystemFontOfSize:16.0]];
}

@end

This shows the implementation of protocol. When new views are setup a UITapGestureRecognizer is set which makes the label bold and clearView sets back to normal.

I hope this was helpful, and will help you as a developer. Don't forget to share this with your friends, and comment below. I am open to refinement as well, I am learning iOS as well. :-)

Thursday, September 6, 2012

Google Now Cards Layout

If you are a fan of Google's android applications, you must haven't missed cards animation that Google Now brings in on Android 4.1, Jelly Bean.

Google Now Cards Layout on Jellybean

Lets see how to create these smooth animations.

You can browse the source here : https://github.com/shardul/Android/tree/master/NowLayout

Step 1 : Animations and Styles

There are two animations of similar sliding transition, differing slightly for alternating cards (technically Views). One rotating in from left and other for right. 

slide_up_left.xml


<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_interpolator" >

    <translate
        android:duration="@integer/config_slide_time"
        android:fromYDelta="100%p"
        android:toYDelta="0" />

    <alpha
        android:duration="@integer/config_slide_time"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />

    <rotate
        android:duration="@integer/config_slide_time"
        android:fromDegrees="25"
        android:pivotX="0"
        android:pivotY="0"
        android:toDegrees="0" />

</set>

slide_up_right.xml


<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:interpolator="@android:anim/accelerate_interpolator" >

    <translate
        android:duration="@integer/config_slide_time"
        android:fromYDelta="100%p"
        android:toYDelta="0" />

    <alpha
        android:duration="@integer/config_slide_time"
        android:fromAlpha="0.0"
        android:toAlpha="1.0" />

    <rotate
        android:duration="@integer/config_slide_time"
        android:fromDegrees="-25"
        android:pivotX="100%"
        android:pivotY="0"
        android:toDegrees="0" />

</set>

These animations will create alternate card animations.

NowLayout.java


package com.shardul.nowlayout;

import android.content.Context;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewTreeObserver.OnGlobalLayoutListener;
import android.view.animation.AnimationUtils;
import android.widget.LinearLayout;

public class NowLayout extends LinearLayout implements OnGlobalLayoutListener {

 public NowLayout(Context context, AttributeSet attrs) {
  super(context, attrs);
  initLayoutObserver();

 }

 public NowLayout(Context context) {
  super(context);
  initLayoutObserver();
 }

 private void initLayoutObserver() {
        // force vertical orientation and add observer
                setOrientation(LinearLayout.VERTICAL);
  getViewTreeObserver().addOnGlobalLayoutListener(this);
 }

 @Override
 public void onGlobalLayout() {
  getViewTreeObserver().removeGlobalOnLayoutListener(this);

  final int heightPx =getContext().getResources().
                getDisplayMetrics().heightPixels;

  boolean inversed = false;
  final int childCount = getChildCount();

 for (int i = 0; i < childCount; i++) {
  View child = getChildAt(i);

  int[] location = new int[2];

  child.getLocationOnScreen(location);

  if (location[1] > heightPx) {
   break;
  }

  if (!inversed) {
   child.startAnimation(
                        AnimationUtils.loadAnimation(getContext(),
      R.anim.slide_up_left));
   } else {
   child.startAnimation(
                        AnimationUtils.loadAnimation(getContext(),
      R.anim.slide_up_right));
   }

   inversed = !inversed;
  }

 }

}


This is where all the magic will happen. To create alternating animations we can definitely grab each child and start respective animation in activity but a better approach would be to create custom layout and handle animation there which is a better approach to take.

Integrating 


Now we have to make use of NowLayout in our activity.

main.xml


<ScrollView 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:fillViewport="true" >

    <com.shardul.nowlayout.NowLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#e3e3e3"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/txtView"
            style="@style/nowCardStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/hello_world"
            tools:context=".MainActivity" />

        <TextView
            android:id="@+id/txtView"
            style="@style/nowCardStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/hello_world"
            tools:context=".MainActivity" />

        <TextView
            android:id="@+id/txtView"
            style="@style/nowCardStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/hello_world"
            tools:context=".MainActivity" />

        <TextView
            android:id="@+id/txtView"
            style="@style/nowCardStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/hello_world"
            tools:context=".MainActivity" />

        <TextView
            android:id="@+id/txtView"
            style="@style/nowCardStyle"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@string/hello_world"
            tools:context=".MainActivity" />
    </com.shardul.nowlayout.NowLayout>

</ScrollView>

MainActivity.java


package com.shardul.nowlayout;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity {

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
 }

}

And there you have it. Run the app and see sliding cards similar to Google Now app.

Make sure you share with your friends if you like what you created.

You can also learn about Creating Chrome Style Help Popups on Android.

Wednesday, September 5, 2012

SOAP Calls With SOAPIntentService

WikiSOAP, originally defined as Simple Object Access Protocol, is a protocol specification for exchanging structured information in the implementation of Web Services in computer networks. It relies on Extensible Markup Language (XML) for its message format, and usually relies on otherApplication Layer protocols, most notably Hypertext Transfer Protocol (HTTP) and Simple Mail Transfer Protocol (SMTP), for message negotiation and transmission.

Being a developer, various server interaction methodologies come across you. SOAP being one of them when high security and reliability of transmission is concerned during server communication. Though it adds overhead (large payloads) it largely being used for enterprise level apps.

By default, SOAP isn't bundled with Android (unlike JSON). We have external libraries available which can be used to achieve this task.

One of these is kSoap2, official project http://code.google.com/p/ksoap2-android/

Lets talk about SOAPIntentService


Android Docs : IntentService is a base class for Services that handle asynchronous requests (expressed as Intents) on demand. Clients send requests through startService(Intent) calls; the service is started as needed, handles each Intent in turn using a worker thread, and stops itself when it runs out of work.

This "work queue processor" pattern is commonly used to offload tasks from an application's main thread. The IntentService class exists to simplify this pattern and take care of the mechanics. To use it, extend IntentService and implement onHandleIntent(Intent). IntentService will receive the Intents, launch a worker thread, and stop the service as appropriate.

All requests are handled on a single worker thread -- they may take as long as necessary (and will not block the application's main loop), but only one request will be processed at a time.

SOAPIntentService working

SOAP Intent Service, accepts a call and executes it on another thread, and responds back to caller via 
Broadcast Receiver

It's also designed to cache on failure when POST is set as request type.

You can acquire source from here : https://github.com/shardul/Android/tree/master/SoapIntentService

Usage


// initialise request helper with appropriate parameters from wsdl definition
SOAPRequestHelper soapRequestHelper = new SOAPRequestHelper(namespace,
    action, methodName, url);

// set properties for request using SOAPNameValuePair instances
SOAPNameValuePair[] props = new SOAPNameValuePair[2];
props[0]=new SOAPNameValuePair("param1","value1");
props[1]=new SOAPNameValuePair("param2","value2");

soapRequestHelper.setProperties(props);


// optionally set headers for request using SOAPNameValuePair instances

SOAPNameValuePair[] headers = new SOAPNameValuePair[2];
headers[0]=new SOAPNameValuePair("header1","headervalue1");
headers[1]=new SOAPNameValuePair("header2","headervalue2");

soapRequestHelper.setHeaders(headers);


//perform a call to SOAPIntentService using

soapRequestHelper.performSOAPRequest(this, responseReceiver);


responseReceiver defined above is a BroadcastReceiver and is an optional parameter. It should only be used when caller do bothers about the response. responseReceiver will be internally registered thus it should accompany a context.unregisterReceiver(responseReceiver) when service call is dealt with.

Heres how responseReceiver code definition should look like


private class ResponseReceiver extends BroadcastReceiver {

@Override
public void onReceive(Context context, Intent intent) {
       // initialise response received
 SOAPResponseHelper soapResponseHelper = new SOAPResponseHelper(
    intent);

        //check if theres an exception
 if (!soapResponseHelper.isException()) {
  responseMap = soapResponseHelper.getResponseMap();
  showDialog(DIALOG_RESPONSE);
 } else {
 Toast.makeText(SoapIntentActivityActivity.this,
 soapResponseHelper.getException().toString(),Toast.LENGTH_LONG).show();
 }
 }

}

Additionally caching can be enabled on the call by setting requestType as post of SOAPRequetHelper class


soapRequestHelper.setRequestType(SOAPRequestHelper.REQUEST_TYPE_POST)

And don't forget to include internet permission in your AndroidManifest.xml


<uses-permission android:name="android.permission.INTERNET" />


For further implementation details, you can browse sample code here.

https://github.com/shardul/Android/blob/master/SoapIntentService/src/com/shardul/android/soapservice/activities/SoapIntentActivityActivity.java

Feel free to email me or post comments regarding implementation and enhancements.

Wednesday, August 29, 2012

Chrome Style Help Popups

If you love browsing on your phone with Google Chrome browser, you must have seen these quick help popups that appear when you first start Chrome.

Chrome Style Help Popups Demo

Those "floating" popups will surely come handy when you want to show some quick info about your in-app widgets or to be more technical Views in your application. 

You can pull / download entire Eclipse project source from my Github repo :


Or stick through to see how to create one of these.

Step 1 : Creating Resources Required

First lets create some resources that will create look and feel of those popups. Create a new Android project and add following resources to the project directory

Background: 


<shape android:shape="rectangle" 
  xmlns:android="http://schemas.android.com/apk/res/android">

    <corners android:radius="4dp">

    <solid android:color="#3a68a9">

    <stroke android:color="#32588c" android:width="1dp">

</stroke></solid></corners></shape>


This will define a new shape drawable, save this in res/drawable/blue_bg.xml


Floating Animation

Create new animation which will be used to create a floating fill to our popups, in this case defined animation is much faster as that in default Chrome application but you can play with it according to your needs. Following snippet creates an animation which will repeat it self for infinite times.


<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android" >

    <translate
        android:interpolator="@android:anim/bounce_interpolator"
        android:duration="1000"
        android:fillAfter="true"
        android:fromYDelta="0"
        android:pivotX="50%"
        android:pivotY="50%"
        android:repeatCount="infinite"
        android:repeatMode="reverse"
        android:toYDelta="5%" />

</set>

Save it as res/anim/float_anim.xml.

Step 2 : Popup definition files

Now lets start with popup declarations. To match look and feel, lets define a layout, composing an up arrow (ImageView) , text (TextView) and down arrow (ImageView). 

Popup layout


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <ImageView
        android:id="@+id/arrow_up"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/down_arrow" />

    <TextView
        android:id="@+id/text"
        style="@style/ChromeStylePopup"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="@drawable/blue_bg"
        android:scrollbars="vertical" />

    <ImageView
        android:id="@+id/arrow_down"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="-1dip"
        android:src="@drawable/up_arrow" />

</LinearLayout>

Save this layout as res/layout/popup.xml

You will have to import 2 arrow images one pointing up and other pointing down to compile this.

This layout is also referring to an external style definition, which we will create next


<resources>

    <style name="ChromeStylePopup" parent="android:Widget.TextView">
        <item name="android:gravity">center</item>
        <item name="android:minHeight">50dp</item>
        <item name="android:minWidth">75dp</item>
        <item name="android:maxWidth">150dp</item>
        <item name="android:padding">6dp</item>
        <item name="android:textColor">@android:color/white</item>
        <item name="android:textStyle">bold</item>
        <item name="android:textAppearance">?android:attr/textAppearanceMedium</item>
    </style>

</resources>

This wil go into res/values/styles.xml.

Step 3 : ChromeHelpPopup.java


package com.example.chromestylehelppopups;

import android.content.Context;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.text.method.ScrollingMovementMethod;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.view.animation.AnimationUtils;
import android.widget.ImageView;
import android.widget.PopupWindow;
import android.widget.TextView;

public class ChromeHelpPopup {

 protected WindowManager mWindowManager;

 protected Context mContext;
 protected PopupWindow mWindow;

 private TextView mHelpTextView;
 private ImageView mUpImageView;
 private ImageView mDownImageView;
 protected View mView;

 protected Drawable mBackgroundDrawable = null;
 protected ShowListener showListener;

 public ChromeHelpPopup(Context context, String text, int viewResource) {
  mContext = context;
  mWindow = new PopupWindow(context);

  mWindowManager = (WindowManager) context
    .getSystemService(Context.WINDOW_SERVICE);


  LayoutInflater layoutInflater = (LayoutInflater) context
    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

  setContentView(layoutInflater.inflate(viewResource, null));

  mHelpTextView = (TextView) mView.findViewById(R.id.text);
  mUpImageView = (ImageView) mView.findViewById(R.id.arrow_up);
  mDownImageView = (ImageView) mView.findViewById(R.id.arrow_down);

  mHelpTextView.setMovementMethod(ScrollingMovementMethod.getInstance());
  mHelpTextView.setSelected(true);
 }

 public ChromeHelpPopup(Context context) {
  this(context, "", R.layout.popup);

 }

 public ChromeHelpPopup(Context context, String text) {
  this(context);

  setText(text);
 }

 public void show(View anchor) {
  preShow();

  int[] location = new int[2];

  anchor.getLocationOnScreen(location);

  Rect anchorRect = new Rect(location[0], location[1], location[0]
    + anchor.getWidth(), location[1] + anchor.getHeight());

  mView.measure(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);

  int rootHeight = mView.getMeasuredHeight();
  int rootWidth = mView.getMeasuredWidth();

  final int screenWidth = mWindowManager.getDefaultDisplay().getWidth();
  final int screenHeight = mWindowManager.getDefaultDisplay().getHeight();

  int yPos = anchorRect.top - rootHeight;

  boolean onTop = true;

  if (anchorRect.top < screenHeight / 2) {
   yPos = anchorRect.bottom;
   onTop = false;
  }

  int whichArrow, requestedX;

  whichArrow = ((onTop) ? R.id.arrow_down : R.id.arrow_up);
  requestedX = anchorRect.centerX();

  View arrow = whichArrow == R.id.arrow_up ? mUpImageView
    : mDownImageView;
  View hideArrow = whichArrow == R.id.arrow_up ? mDownImageView
    : mUpImageView;

  final int arrowWidth = arrow.getMeasuredWidth();

  arrow.setVisibility(View.VISIBLE);

  ViewGroup.MarginLayoutParams param = (ViewGroup.MarginLayoutParams) arrow
    .getLayoutParams();

  hideArrow.setVisibility(View.INVISIBLE);

  int xPos = 0;

  // ETXTREME RIGHT CLIKED
  if (anchorRect.left + rootWidth > screenWidth) {
   xPos = (screenWidth - rootWidth);
  }
  // ETXTREME LEFT CLIKED
  else if (anchorRect.left - (rootWidth / 2) < 0) {
   xPos = anchorRect.left;
  }
  // INBETWEEN
  else {
   xPos = (anchorRect.centerX() - (rootWidth / 2));
  }

  param.leftMargin = (requestedX - xPos) - (arrowWidth / 2);

  if (onTop) {
   mHelpTextView.setMaxHeight(anchorRect.top - anchorRect.height());

  } else {
   mHelpTextView.setMaxHeight(screenHeight - yPos);
  }

  mWindow.showAtLocation(anchor, Gravity.NO_GRAVITY, xPos, yPos);

  mView.setAnimation(AnimationUtils.loadAnimation(mContext,
    R.anim.float_anim));

 }

 protected void preShow() {
  if (mView == null)
   throw new IllegalStateException("view undefined");

  
  
  if (showListener != null) {
   showListener.onPreShow();
   showListener.onShow();
  }

  if (mBackgroundDrawable == null)
   mWindow.setBackgroundDrawable(new BitmapDrawable());
  else
   mWindow.setBackgroundDrawable(mBackgroundDrawable);

  mWindow.setWidth(WindowManager.LayoutParams.WRAP_CONTENT);
  mWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
  mWindow.setTouchable(true);
  mWindow.setFocusable(true);
  mWindow.setOutsideTouchable(true);

  mWindow.setContentView(mView);
 }

 public void setBackgroundDrawable(Drawable background) {
  mBackgroundDrawable = background;
 }

 public void setContentView(View root) {
  mView = root;

  mWindow.setContentView(root);
 }

 public void setContentView(int layoutResID) {
  LayoutInflater inflator = (LayoutInflater) mContext
    .getSystemService(Context.LAYOUT_INFLATER_SERVICE);

  setContentView(inflator.inflate(layoutResID, null));
 }

 public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
  mWindow.setOnDismissListener(listener);
 }

 public void dismiss() {
  mWindow.dismiss();
  if (showListener != null) {
   showListener.onDismiss();
  }
 }

 public void setText(String text) {
  mHelpTextView.setText(text);
 }

 public static interface ShowListener {
  void onPreShow();
  void onDismiss();
  void onShow();
 }

 public void setShowListener(ShowListener showListener) {
  this.showListener = showListener;
 }
}


Above is the magic code that will glue everything up and will create floating chrome style popups for you. There are few I would like you to focus on :

  1. Constructors are used to initialise view of the popup, along with additional secondary constructors initializing texts.
  2. This also defines a static interface ShowListener which can be used to give callbacks.
  3. To invoke popups show() should be called with an "Anchor" view.
  4. show() is the magic method where all calculations of screen alignment is done.
You have everything you need!

Step 4 : Lets do some popups!

Lets stress test this popup we created, create a new layout xml file and save it in res/layout/main.xml


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_gravity="center"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:weightSum="5" >

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />
    </LinearLayout>
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:weightSum="5" >

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />
    </LinearLayout>
    
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:weightSum="5" >

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />
    </LinearLayout>
    
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:weightSum="5" >

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />
    </LinearLayout>
    
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:weightSum="5" >

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />
    </LinearLayout>
    
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:weightSum="5" >

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />
    </LinearLayout>
    
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:weightSum="5" >

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />
    </LinearLayout>
    
    
    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1"
        android:weightSum="5" >

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />

        <Button
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_weight="1"
            android:onClick="clicked"
            android:text="Popup"
            tools:context=".MainActivity" />
    </LinearLayout>
    

</LinearLayout>

This will create rows of buttons saying "Popup" and will call clicked method of referring activity when clicked. Considering MainActivity.java is your launcher activity lets update its source to

MainActivity.java


package com.example.chromestylehelppopups;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;

public class MainActivity extends Activity {

 @Override
 public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.main);
 }

 @Override
 protected void onResume() {
  super.onResume();
 }

 public void clicked(View view) {
  ChromeHelpPopup chromeHelpPopup = new ChromeHelpPopup(MainActivity.this,"Hello!");
  chromeHelpPopup.show(view);
 }

}

And you are done! Wasn't that easy ? After you run this application, you should see something like this.

Creating Chrome Style Help Popups final output

Don't forget to add comments :)

You can also visit another post which describes Creating Google Now Cards on Android.