package com.beardedhen.androidbootstrap;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
import android.support.annotation.ColorInt;
import android.text.TextPaint;
import com.beardedhen.androidbootstrap.api.attributes.BootstrapBrand;
import com.beardedhen.androidbootstrap.api.attributes.ViewGroupPosition;
import com.beardedhen.androidbootstrap.api.defaults.DefaultBootstrapBrand;
import com.beardedhen.androidbootstrap.api.defaults.ExpandDirection;
import com.beardedhen.androidbootstrap.utils.ColorUtils;
import com.beardedhen.androidbootstrap.utils.DimenUtils;
/**
* Provides a factory for generating Drawables which are used as the backgrounds for Bootstrap Views.
* This avoids needing to define hundreds of XML resource files.
*/
class BootstrapDrawableFactory {
/**
* Generates a background drawable for a Bootstrap Button
*
* @param context the current context
* @param brand the bootstrap brand
* @param strokeWidth the stroke width in px
* @param cornerRadius the corner radius in px
* @param position the position of the button in its parent view
* @param showOutline whether the button should be outlined
* @param rounded whether the corners should be rounded
* @return a background drawable for the BootstrapButton
*/
static Drawable bootstrapButton(Context context,
BootstrapBrand brand,
int strokeWidth,
int cornerRadius,
ViewGroupPosition position,
boolean showOutline,
boolean rounded) {
GradientDrawable defaultGd = new GradientDrawable();
GradientDrawable activeGd = new GradientDrawable();
GradientDrawable disabledGd = new GradientDrawable();
defaultGd.setColor(showOutline ? Color.TRANSPARENT : brand.defaultFill(context));
activeGd.setColor(showOutline ? brand.activeFill(context) : brand.activeFill(context));
disabledGd.setColor(showOutline ? Color.TRANSPARENT : brand.disabledFill(context));
defaultGd.setStroke(strokeWidth, brand.defaultEdge(context));
activeGd.setStroke(strokeWidth, brand.activeEdge(context));
disabledGd.setStroke(strokeWidth, brand.disabledEdge(context));
if (showOutline && brand instanceof DefaultBootstrapBrand) {
DefaultBootstrapBrand db = (DefaultBootstrapBrand) brand;
if (db == DefaultBootstrapBrand.SECONDARY) {
int color = ColorUtils.resolveColor(R.color.bootstrap_brand_secondary_border, context);
defaultGd.setStroke(strokeWidth, color);
activeGd.setStroke(strokeWidth, color);
disabledGd.setStroke(strokeWidth, color);
}
}
setupDrawableCorners(position, rounded, cornerRadius, defaultGd, activeGd, disabledGd);
return setupStateDrawable(position, strokeWidth, defaultGd, activeGd, disabledGd);
}
/**
* Generates a Drawable for a Bootstrap Label background, according to state parameters
*
* @param context the current context
* @param bootstrapBrand the BootstrapBrand theming whose colors should be used
* @param rounded whether the corners should be rounded or not
* @param height the view height in px
* @return the Bootstrap Label background
*/
static Drawable bootstrapLabel(Context context,
BootstrapBrand bootstrapBrand,
boolean rounded,
float height) {
int cornerRadius = (int) DimenUtils.pixelsFromDpResource(context, R.dimen.bootstrap_default_corner_radius);
GradientDrawable drawable = new GradientDrawable();
drawable.setColor(bootstrapBrand.defaultFill(context));
// corner radius should be half height if rounded as a "Pill" label
drawable.setCornerRadius(rounded ? height / 2 : cornerRadius);
return drawable;
}
/**
* Generates a Drawable for a Bootstrap Edit Text background
*
* @param context the current context
* @param bootstrapBrand the BootstrapBrand theming whose colors should be used
* @param rounded whether the corners should be rounded or not
* @return the Bootstrap Edit Text background
*/
static Drawable bootstrapEditText(Context context,
BootstrapBrand bootstrapBrand,
float strokeWidth,
float cornerRadius,
boolean rounded) {
StateListDrawable drawable = new StateListDrawable();
GradientDrawable activeDrawable = new GradientDrawable();
GradientDrawable disabledDrawable = new GradientDrawable();
GradientDrawable defaultDrawable = new GradientDrawable();
activeDrawable.setColor(ColorUtils.resolveColor(android.R.color.white, context));
disabledDrawable.setColor(ColorUtils.resolveColor(android.R.color.white, context));
defaultDrawable.setColor(ColorUtils.resolveColor(android.R.color.white, context));
if (rounded) {
activeDrawable.setCornerRadius(cornerRadius);
disabledDrawable.setCornerRadius(cornerRadius);
defaultDrawable.setCornerRadius(cornerRadius);
}
// stroke is larger when focused
int defaultBorder = ColorUtils.resolveColor(R.color.bootstrap_brand_secondary_border, context);
int disabledBorder = ColorUtils.resolveColor(R.color.bootstrap_edittext_disabled, context);
activeDrawable.setStroke((int) strokeWidth, bootstrapBrand.defaultEdge(context));
disabledDrawable.setStroke((int) strokeWidth, disabledBorder);
defaultDrawable.setStroke((int) strokeWidth, defaultBorder);
drawable.addState(new int[]{android.R.attr.state_focused}, activeDrawable);
drawable.addState(new int[]{-android.R.attr.state_enabled}, disabledDrawable);
drawable.addState(new int[]{}, defaultDrawable);
return drawable;
}
static Drawable bootstrapCircleThumbnail(Context context,
BootstrapBrand bootstrapBrand,
@ColorInt int outerBorderWidth,
@ColorInt int bg) {
GradientDrawable drawable = new GradientDrawable();
drawable.setShape(GradientDrawable.OVAL);
drawable.setColor(bg);
drawable.setStroke(outerBorderWidth, bootstrapBrand.defaultEdge(context));
return drawable;
}
static Drawable bootstrapThumbnail(Context context,
BootstrapBrand bootstrapBrand,
@ColorInt int outerBorderWidth,
@ColorInt int bg,
boolean rounded) {
GradientDrawable drawable = new GradientDrawable();
drawable.setShape(GradientDrawable.RECTANGLE);
drawable.setColor(bg);
drawable.setStroke(outerBorderWidth, bootstrapBrand.defaultEdge(context));
float r = DimenUtils.pixelsFromDpResource(context, R.dimen.bthumbnail_rounded_corner);
if (rounded) {
drawable.setCornerRadii(new float[]{r, r, r, r, r, r, r, r});
}
return drawable;
}
static Drawable bootstrapAlert(Context context,
BootstrapBrand bootstrapBrand) {
GradientDrawable disabledGd = new GradientDrawable();
int strokeWidth = context.getResources().getDimensionPixelSize(R.dimen.bootstrap_alert_stroke_width);
disabledGd.setColor(ColorUtils.increaseOpacityFromInt(context, bootstrapBrand.getColor(),
40));
disabledGd.setCornerRadius(6);
disabledGd.setStroke(strokeWidth, ColorUtils.increaseOpacityFromInt(context, bootstrapBrand.getColor(), 70));
return disabledGd;
}
/**
* Generates a colorstatelist for a bootstrap button
*
* @param context the current context
* @param outline whether the button is outlined
* @param brand the button brand
* @return the color state list
*/
static ColorStateList bootstrapButtonText(Context context, boolean outline, BootstrapBrand brand) {
int defaultColor = outline ? brand.defaultFill(context) : brand.defaultTextColor(context);
int activeColor = outline ? ColorUtils.resolveColor(android.R.color.white, context) : brand.activeTextColor(context);
int disabledColor = outline ? brand.disabledFill(context) : brand.disabledTextColor(context);
if (outline && brand instanceof DefaultBootstrapBrand) { // special case
DefaultBootstrapBrand db = (DefaultBootstrapBrand) brand;
if (db == DefaultBootstrapBrand.SECONDARY) {
defaultColor = ColorUtils.resolveColor(R.color.bootstrap_brand_secondary_border, context);
disabledColor = defaultColor;
}
}
return new ColorStateList(getStateList(), getColorList(defaultColor, activeColor, disabledColor));
}
static Drawable bootstrapWell(@ColorInt int backgroundColor, int cornerRadius, int strokeWidth, @ColorInt int strokeColor) {
GradientDrawable background = new GradientDrawable();
background.setColor(backgroundColor);
background.setCornerRadius(cornerRadius);
background.setStroke(strokeWidth, strokeColor);
return background;
}
private static StateListDrawable setupStateDrawable(ViewGroupPosition position, int strokeWidth, GradientDrawable defaultGd, GradientDrawable activeGd, GradientDrawable disabledGd) {
StateListDrawable stateListDrawable = new StateListDrawable();
LayerDrawable defaultLayer = new LayerDrawable(new Drawable[]{defaultGd});
LayerDrawable activeLayer = new LayerDrawable(new Drawable[]{activeGd});
LayerDrawable disabledLayer = new LayerDrawable(new Drawable[]{disabledGd});
LayerDrawable[] ldAry = new LayerDrawable[]{defaultLayer, activeLayer, disabledLayer};
int n = strokeWidth * -1;
// use LayerDrawable to hide strokes on one side of the drawable (if needed), using negative insets
if (position != null) {
switch (position) {
case MIDDLE_HORI:
setInsetOnLayers(ldAry, n, 0, 0, 0);
break;
case END:
setInsetOnLayers(ldAry, n, 0, 0, 0);
break;
case MIDDLE_VERT:
setInsetOnLayers(ldAry, 0, n, 0, 0);
break;
case BOTTOM:
setInsetOnLayers(ldAry, 0, n, 0, 0);
}
}
if (Build.VERSION.SDK_INT >= 14) {
stateListDrawable.addState(new int[]{android.R.attr.state_hovered}, activeLayer);
}
stateListDrawable.addState(new int[]{android.R.attr.state_activated}, activeLayer);
stateListDrawable.addState(new int[]{android.R.attr.state_focused}, activeLayer);
stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, activeLayer);
stateListDrawable.addState(new int[]{android.R.attr.state_selected}, activeLayer);
stateListDrawable.addState(new int[]{-android.R.attr.state_enabled}, disabledLayer);
stateListDrawable.addState(new int[]{}, defaultLayer);
return stateListDrawable;
}
private static void setupDrawableCorners(ViewGroupPosition position, boolean rounded, int r,
GradientDrawable defaultGd, GradientDrawable activeGd, GradientDrawable disabledGd) {
if (rounded) {
if (position == ViewGroupPosition.SOLO) {
defaultGd.setCornerRadius(r);
activeGd.setCornerRadius(r);
disabledGd.setCornerRadius(r);
}
else {
float[] radii; // X/Y pairs for top-left, top-right, bottom-right, bottom-left.
switch (position) {
case MIDDLE_HORI:
radii = new float[]{0, 0, 0, 0, 0, 0, 0, 0};
break;
case MIDDLE_VERT:
radii = new float[]{0, 0, 0, 0, 0, 0, 0, 0};
break;
case TOP:
radii = new float[]{r, r, r, r, 0, 0, 0, 0};
break;
case BOTTOM:
radii = new float[]{0, 0, 0, 0, r, r, r, r};
break;
case START:
radii = new float[]{r, r, 0, 0, 0, 0, r, r,};
break;
case END:
radii = new float[]{0, 0, r, r, r, r, 0, 0};
break;
default:
radii = new float[]{0, 0, 0, 0, 0, 0, 0, 0};
break;
}
defaultGd.setCornerRadii(radii);
activeGd.setCornerRadii(radii);
disabledGd.setCornerRadii(radii);
}
}
}
private static int[][] getStateList() {
if (Build.VERSION.SDK_INT >= 14) {
return new int[][]
{new int[]{android.R.attr.state_hovered}, new int[]{android.R.attr.state_activated},
new int[]{android.R.attr.state_focused}, new int[]{android.R.attr.state_selected},
new int[]{android.R.attr.state_pressed}, new int[]{android.R.attr.state_hovered},
new int[]{-android.R.attr.state_enabled}, new int[]{}};
}
else {
return new int[][]
{new int[]{android.R.attr.state_activated}, new int[]{android.R.attr.state_focused},
new int[]{android.R.attr.state_selected}, new int[]{android.R.attr.state_pressed},
new int[]{android.R.attr.state_hovered}, new int[]{-android.R.attr.state_enabled},
new int[]{}};
}
}
private static int[] getColorList(int defaultColor, int activeColor, int disabledColor) {
if (Build.VERSION.SDK_INT >= 14) {
return new int[]{activeColor, activeColor, activeColor, activeColor, activeColor,
activeColor, disabledColor, defaultColor};
}
else {
return new int[]{activeColor, activeColor, activeColor, activeColor, activeColor,
disabledColor, defaultColor};
}
}
static StateListDrawable bootstrapDropDownArrow(Context context, int width, int height, ExpandDirection expandDirection, boolean outline, BootstrapBrand brand) {
StateListDrawable stateListDrawable = new StateListDrawable();
int defaultColor = outline ? brand.defaultFill(context) : brand.defaultTextColor(context);
int activeColor = outline ? ColorUtils.resolveColor(android.R.color.white, context) : brand.activeTextColor(context);
int disabledColor = outline ? brand.disabledFill(context) : brand.disabledTextColor(context);
if (Build.VERSION.SDK_INT >= 14) {
stateListDrawable.addState(new int[]{android.R.attr.state_hovered}, createArrowIcon(context, width, height, activeColor, expandDirection));
}
stateListDrawable.addState(new int[]{android.R.attr.state_activated}, createArrowIcon(context, width, height, activeColor, expandDirection));
stateListDrawable.addState(new int[]{android.R.attr.state_focused}, createArrowIcon(context, width, height, activeColor, expandDirection));
stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, createArrowIcon(context, width, height, activeColor, expandDirection));
stateListDrawable.addState(new int[]{android.R.attr.state_selected}, createArrowIcon(context, width, height, activeColor, expandDirection));
stateListDrawable.addState(new int[]{-android.R.attr.state_enabled}, createArrowIcon(context, width, height, disabledColor, expandDirection));
stateListDrawable.addState(new int[]{}, createArrowIcon(context, width, height, defaultColor, expandDirection));
return stateListDrawable;
}
static StateListDrawable bootstrapAlertCloseIcon(Context context, int width, int height, int inset) {
StateListDrawable stateListDrawable = new StateListDrawable();
int defaultColor = ColorUtils.resolveColor(R.color.bootstrap_alert_cross_default, context);
int activeColor = ColorUtils.resolveColor(R.color.bootstrap_gray, context);
int disabledColor = ColorUtils.resolveColor(R.color.bootstrap_alert_cross_default, context);
if (Build.VERSION.SDK_INT >= 14) {
stateListDrawable.addState(new int[]{android.R.attr.state_hovered}, createCloseCrossIcon(context, width, height, activeColor, inset));
}
stateListDrawable.addState(new int[]{android.R.attr.state_activated}, createCloseCrossIcon(context, width, height, activeColor, inset));
stateListDrawable.addState(new int[]{android.R.attr.state_focused}, createCloseCrossIcon(context, width, height, activeColor, inset));
stateListDrawable.addState(new int[]{android.R.attr.state_pressed}, createCloseCrossIcon(context, width, height, activeColor, inset));
stateListDrawable.addState(new int[]{android.R.attr.state_selected}, createCloseCrossIcon(context, width, height, activeColor, inset));
stateListDrawable.addState(new int[]{-android.R.attr.state_enabled}, createCloseCrossIcon(context, width, height, disabledColor, inset));
stateListDrawable.addState(new int[]{}, createCloseCrossIcon(context, width, height, defaultColor, inset));
return stateListDrawable;
}
/**
* Creates arrow icon that depends on ExpandDirection
*
* @param context context
* @param width width of icon in pixels
* @param height height of icon in pixels
* @param color arrow color
* @param expandDirection arrow direction
* @return icon drawable
*/
private static Drawable createArrowIcon(Context context, int width, int height, int color, ExpandDirection expandDirection) {
Bitmap canvasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(canvasBitmap);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setStrokeWidth(1);
paint.setColor(color);
paint.setAntiAlias(true);
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
switch (expandDirection) {
case UP:
path.moveTo(0, (height / 3) * 2);
path.lineTo(width, (height / 3) * 2);
path.lineTo(width / 2, height / 3);
path.lineTo(0, (height / 3) * 2);
break;
case DOWN:
path.moveTo(0, height / 3);
path.lineTo(width, height / 3);
path.lineTo(width / 2, (height / 3) * 2);
path.lineTo(0, height / 3);
break;
}
path.close();
canvas.drawPath(path, paint);
return new BitmapDrawable(context.getResources(), canvasBitmap);
}
private static Drawable createCloseCrossIcon(Context context, int width, int height, int color, int inset) {
Bitmap canvasBitmap = Bitmap.createBitmap(width + inset * 2, height + inset * 2, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(canvasBitmap);
Paint paint = new Paint();
paint.setStyle(Paint.Style.FILL_AND_STROKE);
paint.setStrokeWidth(3);
paint.setColor(color);
paint.setAntiAlias(true);
Path path = new Path();
path.setFillType(Path.FillType.EVEN_ODD);
path.moveTo(inset, inset);
path.lineTo(width + inset, height + inset);
path.moveTo(width + inset, inset);
path.lineTo(inset, height + inset);
path.close();
canvas.drawPath(path, paint);
return new BitmapDrawable(context.getResources(), canvasBitmap);
}
public static Drawable createBadgeDrawable(Context context, BootstrapBrand brand, int width,
int height, String badgeText, boolean insideAnObject) {
if (badgeText == null) {
return null;
}
else {
Paint badgePaint = new Paint();
Rect canvasBounds = new Rect();
TextPaint badgeTextPaint = new TextPaint();
badgePaint.setFlags(Paint.ANTI_ALIAS_FLAG);
badgeTextPaint.setFlags(Paint.ANTI_ALIAS_FLAG);
badgeTextPaint.setTextAlign(Paint.Align.CENTER);
badgeTextPaint.setTextSize((float) (height * 0.7));
if (insideAnObject) {
badgePaint.setColor(brand.defaultTextColor(context));
badgeTextPaint.setColor(brand.defaultFill(context));
}
else {
badgePaint.setColor(brand.defaultFill(context));
badgeTextPaint.setColor(brand.defaultTextColor(context));
}
int rectLength = (int) badgeTextPaint.measureText(badgeText);
Bitmap canvasBitmap = Bitmap.createBitmap(width + rectLength, height, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(canvasBitmap);
canvas.getClipBounds(canvasBounds);
int firstCircleDx = canvasBounds.left + canvasBounds.height() / 2;
int circleDy = canvasBounds.height() / 2;
int circleRadius = canvasBounds.height() / 2;
int secondCircleDx = firstCircleDx + rectLength;
Rect rect = new Rect();
rect.left = firstCircleDx;
rect.top = 0;
rect.right = rect.left + rectLength;
rect.bottom = canvasBounds.height();
canvas.drawCircle(firstCircleDx, circleDy, circleRadius, badgePaint);
canvas.drawRect(rect, badgePaint);
canvas.drawCircle(secondCircleDx, circleDy, circleRadius, badgePaint);
canvas.drawText(badgeText, canvasBounds.width() / 2, canvasBounds.height() / 2 - ((badgeTextPaint.descent() +
badgeTextPaint.ascent()) / 2),
badgeTextPaint);
return new BitmapDrawable(context.getResources(), canvasBitmap);
}
}
static ColorStateList bootstrapDropDownViewText(Context context) {
int defaultColor = ColorUtils.resolveColor(R.color.bootstrap_gray_dark, context);
int activeColor = ColorUtils.resolveColor(android.R.color.black, context);
int disabledColor = ColorUtils.resolveColor(R.color.bootstrap_gray_light, context);
return new ColorStateList(getStateList(), getColorList(defaultColor, activeColor, disabledColor));
}
private static void setInsetOnLayers(LayerDrawable[] ary, int l, int t, int r, int b) {
for (LayerDrawable ld : ary) {
ld.setLayerInset(0, l, t, r, b);
}
}
}