package com.beardedhen.androidbootstrap;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.widget.PopupWindowCompat;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.PopupWindow;
import android.widget.ScrollView;
import android.widget.TextView;
import com.beardedhen.androidbootstrap.api.attributes.BootstrapBrand;
import com.beardedhen.androidbootstrap.api.attributes.ViewGroupPosition;
import com.beardedhen.androidbootstrap.api.defaults.DefaultBootstrapSize;
import com.beardedhen.androidbootstrap.api.defaults.ExpandDirection;
import com.beardedhen.androidbootstrap.api.view.BootstrapSizeView;
import com.beardedhen.androidbootstrap.api.view.OutlineableView;
import com.beardedhen.androidbootstrap.api.view.RoundableView;
import com.beardedhen.androidbootstrap.utils.ColorUtils;
import com.beardedhen.androidbootstrap.utils.DimenUtils;
import com.beardedhen.androidbootstrap.utils.DrawableUtils;
import com.beardedhen.androidbootstrap.utils.ViewUtils;
import java.io.Serializable;
import java.util.regex.Pattern;
/**
* BootstrapButtons are buttons which provide contextual menus, styled with BootstrapBrand colors,
* roundable corners, and an 'outlineable' mode.
*/
@BetaApi
public class BootstrapDropDown extends AwesomeTextView implements View.OnClickListener, RoundableView, OutlineableView, PopupWindow.OnDismissListener {
private static final String TAG = "com.beardedhen.androidbootstrap.BootstrapDropDown";
private static final String KEY_DIRECTION = "com.beardedhen.androidbootstrap.BootstrapDropDown.EXPAND_DIRECTION";
private static final String SEARCH_REGEX_HEADER = "\\{dropdown_header\\}.*";
private static final String SEARCH_REGEX_SEPARATOR = "\\{dropdown_separator\\}.*";
private static final String SEARCH_REGEX_DISABLED = "\\{dropdown_disabled\\}.*";
private static final String REPLACE_REGEX_HEADER = "\\{dropdown_header\\}";
private static final String REPLACE_REGEX_SEPARATOR = "\\{dropdown_separator\\}";
private static final String REPLACE_REGEX_DISABLED = "\\{dropdown_disabled\\}";
private static final int SCREEN_WIDTH_GUESS = 1000;
private ExpandDirection expandDirection;
private PopupWindow dropdownWindow;
private View.OnClickListener clickListener;
private String[] dropdownData;
private OnDropDownItemClickListener onDropDownItemClickListener;
private boolean roundedCorners;
private boolean showOutline;
private float bootstrapSize;
private int itemHeight;
private int dropDownViewHeight;
private int dropDownViewWidth;
private int screenWidth;
private float baselineStrokeWidth;
private float baselineCornerRadius;
private float baselineFontSize;
private float baselineDropDownViewFontSize;
private float baselineItemRightPadding;
private float baselineItemLeftPadding;
private float baselineVertPadding;
private float baselineHoriPadding;
public BootstrapDropDown(Context context) {
super(context);
initialise(null);
}
public BootstrapDropDown(Context context, AttributeSet attrs) {
super(context, attrs);
initialise(attrs);
}
public BootstrapDropDown(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initialise(attrs);
}
private void initialise(AttributeSet attrs) {
TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.BootstrapDropDown);
try {
this.roundedCorners = a.getBoolean(R.styleable.BootstrapDropDown_roundedCorners, false);
this.showOutline = a.getBoolean(R.styleable.BootstrapDropDown_showOutline, false);
int directionOrdinal = a.getInt(R.styleable.BootstrapDropDown_bootstrapExpandDirection, -1);
int dataOrdinal = a.getResourceId(R.styleable.BootstrapDropDown_dropdownResource, -1);
int sizeOrdinal = a.getInt(R.styleable.BootstrapDropDown_bootstrapSize, -1);
expandDirection = ExpandDirection.fromAttributeValue(directionOrdinal);
bootstrapSize = DefaultBootstrapSize.fromAttributeValue(sizeOrdinal).scaleFactor();
itemHeight = a.getDimensionPixelSize(R.styleable.BootstrapDropDown_itemHeight, (int) DimenUtils.pixelsFromDpResource(getContext(), R.dimen.bootstrap_dropdown_default_item_height));
if (isInEditMode()) {
dropdownData = new String[] {"Android Studio", "Layout Preview", "Is Always", "Breaking"};
}
else {
dropdownData = getContext().getResources().getStringArray(dataOrdinal);
}
}
finally {
a.recycle();
}
baselineStrokeWidth = DimenUtils.pixelsFromDpResource(getContext(), R.dimen.bootstrap_dropdown_default_edge_width);
baselineCornerRadius = DimenUtils.pixelsFromDpResource(getContext(), R.dimen.bootstrap_dropdown_default_corner_radius);
baselineFontSize = DimenUtils.pixelsFromSpResource(getContext(), R.dimen.bootstrap_dropdown_default_font_size);
baselineDropDownViewFontSize = DimenUtils.pixelsFromSpResource(getContext(), R.dimen.bootstrap_dropdown_default_item_font_size);
baselineItemLeftPadding = DimenUtils.pixelsFromDpResource(getContext(), R.dimen.bootstrap_dropdown_default_item_left_padding);
baselineItemRightPadding = DimenUtils.pixelsFromDpResource(getContext(), R.dimen.bootstrap_dropdown_default_item_right_padding);
baselineVertPadding = DimenUtils.pixelsFromDpResource(getContext(), R.dimen.bootstrap_button_default_vert_padding);
baselineHoriPadding = DimenUtils.pixelsFromDpResource(getContext(), R.dimen.bootstrap_button_default_hori_padding);
if (isInEditMode()) {
screenWidth = SCREEN_WIDTH_GUESS; // take a sensible guess
}
else {
DisplayMetrics metrics = new DisplayMetrics();
((WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getMetrics(metrics);
screenWidth = metrics.widthPixels;
}
createDropDown();
updateDropDownState();
}
private void createDropDown() {
ScrollView dropdownView = createDropDownView();
dropdownWindow = new PopupWindow();
dropdownWindow.setFocusable(true);
dropdownWindow.setHeight(WindowManager.LayoutParams.WRAP_CONTENT);
if (!isInEditMode()) {
dropdownWindow.setBackgroundDrawable(DrawableUtils.resolveDrawable(android.R.drawable
.dialog_holo_light_frame, getContext()));
}
dropdownWindow.setContentView(dropdownView);
dropdownWindow.setOnDismissListener(this);
dropdownWindow.setAnimationStyle(android.R.style.Animation_Activity);
float longestStringWidth = measureStringWidth(getLongestString(dropdownData))
+ DimenUtils.dpToPixels((baselineItemRightPadding + baselineItemLeftPadding) * bootstrapSize);
if (longestStringWidth < getMeasuredWidth()) {
dropdownWindow.setWidth(DimenUtils.dpToPixels(getMeasuredWidth()));
}
else {
dropdownWindow.setWidth((int) longestStringWidth + DimenUtils.dpToPixels(8));
}
}
private ScrollView createDropDownView() {
final LinearLayout dropdownView = new LinearLayout(getContext());
ScrollView scrollView = new ScrollView(getContext());
int clickableChildCounter = 0;
dropdownView.setOrientation(LinearLayout.VERTICAL);
int height = (int) (itemHeight * bootstrapSize);
LayoutParams childParams = new LayoutParams(LayoutParams.MATCH_PARENT, height);
for (String text : dropdownData) {
TextView childView = new TextView(getContext());
childView.setGravity(Gravity.CENTER_VERTICAL);
childView.setLayoutParams(childParams);
int padding = (int) (baselineItemLeftPadding * bootstrapSize);
childView.setPadding(padding, 0, padding, 0);
childView.setTextSize(baselineDropDownViewFontSize * bootstrapSize);
childView.setTextColor(ColorUtils.resolveColor(android.R.color.black, getContext()));
Drawable background = getContext().obtainStyledAttributes(null, new int[]{
android.R.attr.selectableItemBackground}, 0, 0)
.getDrawable(0);
ViewUtils.setBackgroundDrawable(childView, background);
childView.setTextColor(BootstrapDrawableFactory.bootstrapDropDownViewText(getContext()));
childView.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
dropdownWindow.dismiss();
if (onDropDownItemClickListener != null) {
onDropDownItemClickListener.onItemClick(dropdownView, v, v.getId());
}
}
});
if (Pattern.matches(SEARCH_REGEX_HEADER, text)) {
childView.setText(text.replaceFirst(REPLACE_REGEX_HEADER, ""));
childView.setTextSize((baselineDropDownViewFontSize - 2F) * bootstrapSize);
childView.setClickable(false);
childView.setTextColor(ColorUtils.resolveColor(R.color.bootstrap_gray_light,
getContext()));
}
else if (Pattern.matches(SEARCH_REGEX_SEPARATOR, text)) {
childView = new DividerView(getContext());
childView.setClickable(false);
childView.setLayoutParams(new LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 3));
}
else if (Pattern.matches(SEARCH_REGEX_DISABLED, text)) {
childView.setEnabled(false);
childView.setId(clickableChildCounter++);
childView.setText(text.replaceFirst(REPLACE_REGEX_DISABLED, ""));
}
else {
childView.setText(text);
childView.setId(clickableChildCounter++);
}
dropdownView.addView(childView);
}
dropdownView.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED);
dropDownViewHeight = dropdownView.getMeasuredHeight();
dropDownViewWidth = dropdownView.getMeasuredWidth();
scrollView.setVerticalScrollBarEnabled(false);
scrollView.setHorizontalScrollBarEnabled(false);
scrollView.addView(dropdownView);
cleanData();
return scrollView;
}
private void updateDropDownState() {
super.updateBootstrapState();
BootstrapBrand bootstrapBrand = getBootstrapBrand();
float cornerRadius = baselineCornerRadius;
float strokeWidth = baselineStrokeWidth;
final float fontSize = baselineFontSize * bootstrapSize;
strokeWidth *= bootstrapSize;
super.setOnClickListener(this);
setTextSize(fontSize);
setGravity(Gravity.CENTER);
setCompoundDrawablesWithIntrinsicBounds(null, null,
BootstrapDrawableFactory.bootstrapDropDownArrow(getContext(),
DimenUtils.dpToPixels(8 * bootstrapSize),
DimenUtils.dpToPixels(12 * bootstrapSize),
expandDirection,
showOutline,
getBootstrapBrand())
, null);
setCompoundDrawablePadding(DimenUtils.dpToPixels(8));
setTextColor(BootstrapDrawableFactory.bootstrapButtonText(
getContext(),
showOutline,
bootstrapBrand));
Drawable bg = BootstrapDrawableFactory.bootstrapButton(getContext(),
bootstrapBrand,
(int) strokeWidth,
(int) cornerRadius,
ViewGroupPosition.SOLO,
showOutline,
roundedCorners);
ViewUtils.setBackgroundDrawable(this, bg);
int vert = (int) (baselineVertPadding * bootstrapSize);
int hori = (int) (baselineHoriPadding * bootstrapSize);
setPadding(hori, vert, hori, vert);
}
/**
* Calculating string width
*
* @param text String to calculate
* @return width of String in pixels
*/
private float measureStringWidth(String text) {
Paint mPaint = new Paint();
mPaint.setTextSize(baselineDropDownViewFontSize * bootstrapSize);
return (float) (DimenUtils.dpToPixels(mPaint.measureText(text)));
}
/**
* Searching for longest string in array
*
* @param array input string array
* @return longest string
*/
private String getLongestString(String[] array) {
int maxLength = 0;
String longestString = null;
for (String s : array) {
if (s.length() > maxLength) {
maxLength = s.length();
longestString = s;
}
}
return longestString;
}
private void cleanData() {
String[] cleanArray = new String[dropdownData.length];
for (int i = 0; i < dropdownData.length; i++) {
cleanArray[i] = dropdownData[i].replaceAll(REPLACE_REGEX_HEADER, "")
.replaceAll(REPLACE_REGEX_DISABLED, "")
.replaceAll(REPLACE_REGEX_SEPARATOR, "");
}
dropdownData = cleanArray;
}
/**
* Sets a listener which will be called when an item is clicked in the dropdown.
*
* @param onDropDownItemClickListener the listener
*/
public void setOnDropDownItemClickListener(OnDropDownItemClickListener onDropDownItemClickListener) {
this.onDropDownItemClickListener = onDropDownItemClickListener;
}
@Override public boolean isShowOutline() {
return showOutline;
}
@Override public boolean isRounded() {
return roundedCorners;
}
/**
* Gets the direction in which the dropdown expands.
*
* @return the direction
*/
public ExpandDirection getExpandDirection() {
return expandDirection;
}
/**
* Retrieves the data used to populate the dropdown.
*
* @return a string array of values
*/
public String[] getDropdownData() {
return dropdownData;
}
@Override public void setShowOutline(boolean showOutline) {
this.showOutline = showOutline;
updateDropDownState();
}
@Override public void setRounded(boolean rounded) {
this.roundedCorners = rounded;
updateDropDownState();
}
/**
* Sets the direction in which the dropdown should expand.
*
* @param expandDirection the direction
*/
public void setExpandDirection(ExpandDirection expandDirection) {
this.expandDirection = expandDirection;
updateDropDownState();
}
/**
* Sets the String values which should be used to populate the menu displayed in the dropdown.
*
* @param dropdownData an array of string values.
*/
public void setDropdownData(String[] dropdownData) {
this.dropdownData = dropdownData;
createDropDown();
updateDropDownState();
}
@Override public void onDismiss() {
setSelected(false);
dropdownWindow.getContentView().scrollTo(0, 0);
}
@Override public void onClick(View v) {
if (clickListener != null) {
clickListener.onClick(v);
}
//using 8dip on axisX offset to make dropdown view visually be at start of dropdown itself
//using 4dip on axisY offset to make space between dropdown view and dropdown itself
//all offsets are necessary because of the dialog_holo_light_frame to display correctly on screen(shadow was made by inset)
int gravity;
int axisXOffset;
if (dropDownViewWidth + getX() > screenWidth) {
gravity = Gravity.TOP | Gravity.END;
axisXOffset = DimenUtils.dpToPixels(8);
}
else {
gravity = Gravity.TOP | Gravity.START;
axisXOffset = -DimenUtils.dpToPixels(8);
}
int axisYOffset = DimenUtils.dpToPixels(4);
switch (expandDirection) {
case UP:
PopupWindowCompat.showAsDropDown(dropdownWindow, v,
axisXOffset,
-dropDownViewHeight - getMeasuredHeight() - axisYOffset * 3,
gravity);
break;
case DOWN:
PopupWindowCompat.showAsDropDown(dropdownWindow, v,
axisXOffset,
-axisYOffset,
gravity);
break;
}
setSelected(true);
}
@Override public Parcelable onSaveInstanceState() {
Bundle bundle = new Bundle();
bundle.putParcelable(TAG, super.onSaveInstanceState());
bundle.putBoolean(RoundableView.KEY, roundedCorners);
bundle.putBoolean(OutlineableView.KEY, showOutline);
bundle.putSerializable(KEY_DIRECTION, expandDirection);
bundle.putFloat(BootstrapSizeView.KEY, bootstrapSize);
return bundle;
}
@Override public void onRestoreInstanceState(Parcelable state) {
if (state instanceof Bundle) {
Bundle bundle = (Bundle) state;
this.roundedCorners = bundle.getBoolean(RoundableView.KEY);
this.showOutline = bundle.getBoolean(OutlineableView.KEY);
this.bootstrapSize = bundle.getFloat(BootstrapSizeView.KEY);
Serializable direction = bundle.getSerializable(KEY_DIRECTION);
if (direction instanceof ExpandDirection) {
expandDirection = (ExpandDirection) direction;
}
}
super.onRestoreInstanceState(state);
}
@Override public void setOnClickListener(OnClickListener clickListener) {
this.clickListener = clickListener;
}
/**
* A listener which provides methods relating to {@link BootstrapDropDown}
*/
public interface OnDropDownItemClickListener {
/**
* Called when an item is clicked in a {@link BootstrapDropDown}
*
* @param parent the parent viewgroup
* @param v the view
* @param id the id
*/
void onItemClick(ViewGroup parent, View v, int id);
}
private static class DividerView extends TextView {
private final Paint paint;
public DividerView(Context context) {
super(context);
paint = new Paint();
paint.setColor(ColorUtils.resolveColor(R.color.bootstrap_dropdown_divider, context));
}
@Override protected void onDraw(Canvas canvas) {
canvas.drawLine(0, 1, canvas.getWidth(), 1, paint);
super.onDraw(canvas);
}
}
}