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.