package com.beardedhen.androidbootstrap; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Build; import android.text.Html; import android.util.AttributeSet; import android.view.Gravity; import android.view.TouchDelegate; import android.view.View; import android.view.animation.AccelerateInterpolator; import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.widget.ImageView; import android.widget.RelativeLayout; import android.widget.TextView; import com.beardedhen.androidbootstrap.api.attributes.BootstrapBrand; import com.beardedhen.androidbootstrap.api.defaults.DefaultBootstrapBrand; import com.beardedhen.androidbootstrap.utils.DimenUtils; import com.beardedhen.androidbootstrap.utils.ViewUtils; import java.util.concurrent.atomic.AtomicInteger; /** * See * <a href="http://getbootstrap.com/components/#badges">http://getbootstrap.com/components/#alerts</a> */ @BetaApi public class BootstrapAlert extends RelativeLayout implements Animation.AnimationListener { /** * Provides methods which receive callbacks in response to changes in the view visibility. */ public interface VisibilityChangeListener{ /** * Called when the alert is set to be dismissed with an animation. * @param alert the alert */ void onAlertDismissStarted(BootstrapAlert alert); /** * Called when the alert is no longer visible. * @param alert the alert */ void onAlertDismissCompletion(BootstrapAlert alert); /** * Called when the alert set to appear with an animation * @param alert the alert */ void onAlertAppearStarted(BootstrapAlert alert); /** * Called when the alert is now visible. * @param alert the alert */ void onAlertAppearCompletion(BootstrapAlert alert); } private ImageView closeButton; private BootstrapBrand bootstrapBrand; private String strongText; private String messageText; private float baselineFontSize; private float baselinePadding; private AlphaAnimation fadeInAnimation; private AlphaAnimation fadeOutAnimation; private boolean userDismissible; private VisibilityChangeListener visibilityChangeListener; private static final AtomicInteger nextGeneratedId = new AtomicInteger(1); public BootstrapAlert(Context context) { super(context); initialise(null); } public BootstrapAlert(Context context, AttributeSet attrs) { super(context, attrs); initialise(attrs); } public BootstrapAlert(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); initialise(attrs); } private void initialise(AttributeSet attrs) { TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.BootstrapAlert); try { int typeOrdinal = a.getInt(R.styleable.BootstrapAlert_bootstrapBrand, -1); this.bootstrapBrand = DefaultBootstrapBrand.fromAttributeValue(typeOrdinal); strongText = a.getString(R.styleable.BootstrapAlert_strongText); messageText = a.getString(R.styleable.BootstrapAlert_messageText); userDismissible = a.getBoolean(R.styleable.BootstrapAlert_dismissible, false); if (strongText == null) { strongText = ""; } if (messageText == null) { messageText = ""; } } finally { a.recycle(); } baselineFontSize = DimenUtils.pixelsFromSpResource(getContext(), R.dimen.bootstrap_button_default_font_size); baselinePadding = DimenUtils.pixelsFromDpResource(getContext(), R.dimen.bootstrap_alert_paddings); setupAnimations(); updateBootstrapState(); } private void updateBootstrapState() { TextView alertText = new TextView(getContext()); closeButton = new ImageView(getContext()); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { alertText.setId(generateViewUniqueId()); closeButton.setId(generateViewUniqueId()); } else { alertText.setId(View.generateViewId()); closeButton.setId(View.generateViewId()); } LayoutParams textParams = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); LayoutParams closeParams = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); textParams.addRule(RelativeLayout.LEFT_OF, closeButton.getId()); closeParams.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE); alertText.setLayoutParams(textParams); alertText.setTextSize(baselineFontSize); alertText.setGravity(Gravity.START); alertText.setTextColor( BootstrapDrawableFactory.bootstrapButtonText(getContext(), true, bootstrapBrand)); alertText.setText(Html.fromHtml(String.format("<b>%s</b>%s", strongText, (strongText.length() > 0 ? " " + messageText : messageText)))); closeButton.setLayoutParams(closeParams); Drawable buttonBg = BootstrapDrawableFactory.bootstrapAlertCloseIcon( getContext(), (int) baselineFontSize, (int) baselineFontSize, DimenUtils.dpToPixels(6)); ViewUtils.setBackgroundDrawable(closeButton, buttonBg); Drawable bg = BootstrapDrawableFactory.bootstrapAlert(getContext(), bootstrapBrand); ViewUtils.setBackgroundDrawable(this, bg); addView(alertText); if (userDismissible) { addView(closeButton); ((View) closeButton.getParent()).post(new Runnable() { @Override public void run() { Rect bounds = new Rect(); closeButton.getHitRect(bounds); bounds.top -= DimenUtils.dpToPixels(6); bounds.bottom += DimenUtils.dpToPixels(6); bounds.left -= DimenUtils.dpToPixels(6); bounds.right += DimenUtils.dpToPixels(6); TouchDelegate touchDelegate = new TouchDelegate(bounds, closeButton); if (View.class.isInstance(closeButton.getParent())) { ((View) closeButton.getParent()).setTouchDelegate(touchDelegate); } } }); closeButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { dismiss(true); } }); } int vert = (int) (baselinePadding * 1.5); int hori = (int) (baselinePadding * 1.5); setPadding(hori, vert, hori, vert); } private void setupAnimations() { fadeInAnimation = new AlphaAnimation(0.0f, 1.0f); fadeInAnimation.setDuration(300); fadeInAnimation.setInterpolator(new AccelerateInterpolator()); fadeInAnimation.setAnimationListener(this); fadeOutAnimation = new AlphaAnimation(1.0f, 0.0f); fadeOutAnimation.setDuration(300); fadeOutAnimation.setInterpolator(new AccelerateInterpolator()); fadeOutAnimation.setAnimationListener(this); } /** * Hides the alert with an animation, setting its visibility to {@link View#GONE} * @param animated whether the dismissal should be animated or not */ public void dismiss(boolean animated) { if (animated) { if (visibilityChangeListener != null) { visibilityChangeListener.onAlertDismissStarted(this); } startAnimation(fadeOutAnimation); } else { setVisibility(GONE); } } /** * Shows the alert with an animation, setting its visibility to {@link View#VISIBLE} * @param animated whether the appearance should be animated or not */ public void show(boolean animated) { if (animated) { if (visibilityChangeListener != null) { visibilityChangeListener.onAlertAppearStarted(this); } startAnimation(fadeInAnimation); } else { setVisibility(VISIBLE); } } /** * Retrieves whether the user can dismiss the dialog or not * @return true if dismissable */ public boolean isUserDismissible() { return userDismissible; } /** * Sets whether the user can dismiss the dialog or not. * @param userDismissible true if dismissable */ public void setUserDismissible(boolean userDismissible) { this.userDismissible = userDismissible; updateBootstrapState(); } /** * Sets a {@link VisibilityChangeListener} that will be notified on changes * * @param visibilityChangeListener the listener */ public void setVisibilityChangeListener(VisibilityChangeListener visibilityChangeListener) { this.visibilityChangeListener = visibilityChangeListener; } private int generateViewUniqueId() { for (; ; ) { final int result = nextGeneratedId.get(); // aapt-generated IDs have the high byte nonzero; clamp to the range under that. int newValue = result + 1; if (newValue > 0x00FFFFFF) { newValue = 1; // Roll over to 1, not 0. } if (nextGeneratedId.compareAndSet(result, newValue)) { return result; } } } @Override public void setVisibility(int visibility) { super.setVisibility(visibility); if (visibilityChangeListener != null) { if (GONE == visibility) { visibilityChangeListener.onAlertDismissCompletion(this); } else if (VISIBLE == visibility) { visibilityChangeListener.onAlertAppearCompletion(this); } } } // Animation change listener @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { if (animation == fadeInAnimation) { setVisibility(VISIBLE); } else if (animation == fadeOutAnimation) { setVisibility(GONE); } else { throw new IllegalStateException("Unsupported animation attempted to use this listener"); } } @Override public void onAnimationRepeat(Animation animation) { } }