Skip to content

Instantly share code, notes, and snippets.

@xtools-at
Last active July 13, 2016 18:46
Show Gist options
  • Save xtools-at/01759993ab84674769e5388f7b88e2e2 to your computer and use it in GitHub Desktop.
Save xtools-at/01759993ab84674769e5388f7b88e2e2 to your computer and use it in GitHub Desktop.
Android Introduction Overlay Class in CCL
//copied from https://github.com/googlecast/CastCompanionLibrary-android
/*
* Copyright (C) 2015 Google Inc. All Rights Reserved.
*
* 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.google.android.libraries.cast.companionlibrary.widgets;
import com.google.android.libraries.cast.companionlibrary.R;
import com.google.android.libraries.cast.companionlibrary.utils.Utils;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.os.Build;
import android.preference.PreferenceManager;
import android.support.annotation.ColorRes;
import android.support.annotation.DimenRes;
import android.support.annotation.StringRes;
import android.support.v7.app.MediaRouteButton;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.RelativeLayout;
import android.widget.TextView;
/**
* A simple overlay view that can be used to bring user's attention to the cast button. To use
* this overlay, build an instance of this class and call {@link #show()}:
* <pre>
* IntroductoryOverlay overlay = new IntroductoryOverlay.Builder(activity)
* .setMenuItem(mMediaRouteMenuItem)
* .setTitleText(R.string.intro_overlay_text)
* .setOnDismissed(onOverlayDismissedListener)
* .setSingleSTime()
* .build();
* overlay.show();
* </pre>
* Here, {@code mMediaRouteMenuItem} is the {@link MenuItem} reference to the cast button (returned
* when you call
* {@link com.google.android.libraries.cast.companionlibrary.cast.VideoCastManager#addMediaRouterButton(Menu, int)}).
* You can also point to an instance of {@link MediaRouteButton} by calling
* {@link Builder#setMediaRouteButton(MediaRouteButton)}.
* <p>To customize the layout of this view, copy {@code res/layout/ccl_intro_overlay.xml} into your
* project and update that copy (keep the component ids the same). To customize the style, take a
* look at the {@code res/values/intro_overlay.xml}.
* <p>In order to show this overlay at the right time, clients can register to
* {@link com.google.android.libraries.cast.companionlibrary.cast.callbacks.VideoCastConsumer#onCastAvailabilityChanged(boolean)}.
* Management of how often this overlay should be shown is left to the client application.
*/
public class IntroductoryOverlay extends RelativeLayout {
private static final long FADE_OUT_LENGTH_MS = 400;
public static final String FTU_SHOWN_KEY = "ccl_ftu_shown";
private boolean mIsSingleTime;
private TextView mTitleText;
private TextView mSubtitleText;
private Button mButton;
private float mFocusRadius;
private int mOverlayColorId;
private int mCenterY;
private int mCenterX;
private Paint mHolePaint;
private Bitmap mBitmap;
private boolean mIsOverlayVisible;
private static final String ALPHA_PROPERTY = "alpha";
private static final float INVISIBLE_VALUE = 0f;
private OnOverlayDismissedListener mListener;
private IntroductoryOverlay(Builder builder) {
this(builder, null, R.styleable.CustomTheme_CCLIntroOverlayStyle);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public IntroductoryOverlay(Builder builder, AttributeSet attrs, int defStyleAttr) {
super(builder.mContext, attrs, defStyleAttr);
mIsSingleTime = builder.mSingleTime;
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.ccl_intro_overlay, this);
mButton = (Button) findViewById(R.id.button);
mTitleText = (TextView) findViewById(R.id.textTitle);
mSubtitleText = (TextView) findViewById(R.id.textSubtitle);
TypedArray typedArray = getContext().getTheme()
.obtainStyledAttributes(attrs, R.styleable.CCLIntroOverlay,
R.attr.CCLIntroOverlayStyle, R.style.CCLIntroOverlay);
if (builder.mOverlayColor != 0) {
mOverlayColorId = builder.mOverlayColor;
} else {
mOverlayColorId = typedArray
.getColor(R.styleable.CCLIntroOverlay_ccl_IntroBackgroundColor,
Color.argb(0, 0, 0, 0));
}
mFocusRadius = builder.mRadius;
mListener = builder.mListener;
if (mFocusRadius == 0) {
mFocusRadius = typedArray
.getDimension(R.styleable.CCLIntroOverlay_ccl_IntroFocusRadius, 0);
}
View view = builder.mView;
Rect rect = new Rect();
view.getGlobalVisibleRect(rect);
mCenterX = rect.centerX();
mCenterY = rect.centerY();
setFitsSystemWindows(true);
setupHolePaint();
setText(builder.mTitleText, builder.mSubtitleText);
setButton(builder.mButtonText, typedArray);
typedArray.recycle();
}
/**
* Shows the overlay if it is not visible already.
*/
public void show() {
if (mIsSingleTime && isFtuShown()) {
// we are exceeding the max number
mListener = null;
return;
}
if (!mIsOverlayVisible) {
mIsOverlayVisible = true;
((ViewGroup) ((Activity) getContext()).getWindow().getDecorView()).addView(this);
}
}
/**
* Removes the overlay and frees the resources used. It also removes the reference to the
* activity that was used to build this overlay to avoid any leaks. When user taps on the
* button in this overlay, this method will be called automatically. After calling this method,
* for all practical purposes, this component cannot be re-used.
*/
public void remove() {
if (getContext() != null) {
((ViewGroup) ((Activity) getContext()).getWindow().getDecorView()).removeView(this);
}
if (mBitmap != null && !mBitmap.isRecycled()) {
mBitmap.recycle();
}
mBitmap = null;
mListener = null;
}
private void setButton(String text, TypedArray typedArray) {
String buttonText = text;
if (TextUtils.isEmpty(text)) {
buttonText = typedArray.getString(R.styleable.CCLIntroOverlay_ccl_IntroButtonText);
}
int buttonColor = typedArray
.getColor(R.styleable.CCLIntroOverlay_ccl_IntroButtonBackgroundColor,
Color.argb(0, 0, 0, 0));
mButton.setText(buttonText);
mButton.getBackground().setColorFilter(buttonColor, PorterDuff.Mode.MULTIPLY);
mButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View view) {
fadeOut(FADE_OUT_LENGTH_MS);
}
});
}
private void setText(CharSequence titleText, CharSequence detailText) {
if (!TextUtils.isEmpty(titleText)) {
mTitleText.setText(titleText);
}
if (!TextUtils.isEmpty(detailText)) {
mSubtitleText.append(detailText);
}
}
private void setupHolePaint() {
PorterDuffXfermode xfermode = new PorterDuffXfermode(PorterDuff.Mode.MULTIPLY);
mHolePaint = new Paint();
mHolePaint.setColor(0xFFFFFF);
mHolePaint.setAlpha(0);
mHolePaint.setXfermode(xfermode);
mHolePaint.setAntiAlias(true);
}
@Override
protected void dispatchDraw(Canvas canvas) {
mBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas2 = new Canvas(mBitmap);
canvas2.drawColor(mOverlayColorId);
canvas2.drawCircle(mCenterX, mCenterY, mFocusRadius, mHolePaint);
canvas.drawBitmap(mBitmap, 0, 0, null);
super.dispatchDraw(canvas);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return true;
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public void fadeOut(long duration) {
ObjectAnimator oa = ObjectAnimator.ofFloat(this, ALPHA_PROPERTY, INVISIBLE_VALUE);
oa.setDuration(duration).addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
setFtuShown();
if(mListener != null) {
mListener.onOverlayDismissed();
mListener = null;
}
remove();
}
});
oa.start();
}
private void setFtuShown() {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getContext());
sharedPref.edit().putBoolean(FTU_SHOWN_KEY, true).apply();
}
private boolean isFtuShown() {
SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(getContext());
return sharedPref.getBoolean(FTU_SHOWN_KEY, false);
}
/**
* The builder class that is used to instantiate an instance of {@link IntroductoryOverlay}
*/
public static class Builder {
private Context mContext;
@ColorRes
private float mRadius;
private String mButtonText;
private String mTitleText;
private String mSubtitleText;
private int mOverlayColor;
private OnOverlayDismissedListener mListener;
private boolean mSingleTime;
private View mView;
/**
* The constructor for the Builder class. Note that the context passed here must be an
* activity context.
*/
public Builder(Context activityContext) {
mContext = activityContext;
}
public IntroductoryOverlay build() {
Utils.assertNotNull(mView, "MenuItem or MediaRouteButton");
return new IntroductoryOverlay(this);
}
/**
* Set the {@link MenuItem} referencing the cast button.
*/
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
public Builder setMenuItem(MenuItem menuItem) {
mView = menuItem.getActionView();
return this;
}
/**
* Set the {@link MediaRouteButton} that the ovelay should focus on.
*/
public Builder setMediaRouteButton(MediaRouteButton button) {
mView = button;
return this;
}
/**
* Sets the background color of the overlay view. This will override the value that is
* provided in the style resource file. This is optional.
*/
public Builder setOverlayColor(@ColorRes int colorId) {
mOverlayColor = mContext.getResources().getColor(colorId);
return this;
}
/**
* Sets the radius of the "hole" around the cast button. This will override the value
* specified in the resource file and is optional.
*/
public Builder setFocusRadiusId(@DimenRes int radiusId) {
mRadius = mContext.getResources().getDimension(radiusId);
return this;
}
/**
* Sets the radius of the "hole" around the cast button. This will override the value
* specified in the resource file and is optional.
*/
public Builder setFocusRadius(float radius) {
mRadius = radius;
return this;
}
/**
* Sets the text used in the confirmation button on this overlay. This will override the
* value specified in the resource file and is optional.
*/
public Builder setButtonText(String text) {
mButtonText = text;
return this;
}
/**
* Sets the text used in the confirmation button on this overlay. This will override the
* value specified in the resource file and is optional.
*/
public Builder setButtonText(@StringRes int stringId) {
mButtonText = mContext.getResources().getString(stringId);
return this;
}
/**
* Sets the text used in the Title area on the overlay. This will override the
* value specified in the resource file and is optional.
*/
public Builder setTitleText(@StringRes int stringId) {
mTitleText = mContext.getResources().getString(stringId);
return this;
}
/**
* Sets the text used in the Title area on the overlay. This will override the
* value specified in the resource file and is optional.
*/
public Builder setTitleText(String text) {
mTitleText = text;
return this;
}
/**
* Sets the text used in the Subtitle area on the overlay. This is optional.
*/
public Builder setSubtitleText(@StringRes int stringId) {
mSubtitleText = mContext.getResources().getString(stringId);
return this;
}
/**
* Sets the text used in the Subtitle area on the overlay. This is optional.
*/
public Builder setSubtitleText(String text) {
mSubtitleText = text;
return this;
}
/**
* Sets an {@link OnOverlayDismissedListener} listener that will be notified when the
* overlay is dismissed by pressing on the confirmation button.
*/
public Builder setOnDismissed(OnOverlayDismissedListener listener) {
mListener = listener;
return this;
}
/**
* Sets the maximum number of times that this overlay should be shown to 1. If it is needed
* to show this more than once, management of the counter has to be done by the client.
*/
public Builder setSingleTime() {
mSingleTime = true;
return this;
}
}
/**
* An interface to notify the clients when the ovelay is dismissed explicitly when user taps
* on the confirmation button.
*/
public interface OnOverlayDismissedListener {
void onOverlayDismissed();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment