package com.forfan.bigbang.view;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
import android.text.TextUtils;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.ListView;
import android.widget.RadioButton;
import android.widget.ScrollView;
import android.widget.TextView;
import com.forfan.bigbang.R;
import com.forfan.bigbang.util.ViewUtil;
/**
* Created by Rey on 12/16/2014.
*/
public class SimpleDialog extends Dialog {
private TextView mMessage;
private InternalScrollView mScrollView;
private InternalListView mListView;
private InternalAdapter mAdapter;
private int mMessageTextAppearanceId;
private int mMessageTextColor;
private int mRadioButtonStyle;
private int mCheckBoxStyle;
private int mItemHeight;
private int mItemTextAppearance;
private int mMode;
protected static final int MODE_NONE = 0;
protected static final int MODE_MESSAGE = 1;
protected static final int MODE_ITEMS = 2;
protected static final int MODE_MULTI_ITEMS = 3;
protected static final int MODE_CUSTOM = 4;
/**
* Interface definition for a callback to be invoked when the checked state of an item changed.
*/
public interface OnSelectionChangedListener{
/**
* Called when the checked state of an item changed.
* @param index The index of item.
* @param selected The checked state.
*/
public void onSelectionChanged(int index, boolean selected);
}
private OnSelectionChangedListener mOnSelectionChangedListener;
public SimpleDialog(Context context) {
super(context, R.style.Material_App_Dialog_Simple_Light);
}
public SimpleDialog(Context context, int style) {
super(context, style);
}
@Override
protected void onCreate() {
messageTextAppearance(R.style.TextAppearance_AppCompat_Body1);
itemHeight(ViewGroup.LayoutParams.WRAP_CONTENT);
itemTextAppearance(R.style.TextAppearance_AppCompat_Body1);
}
@Override
public Dialog applyStyle(int resId) {
super.applyStyle(resId);
if(resId == 0)
return this;
TypedArray a = getContext().obtainStyledAttributes(resId, R.styleable.SimpleDialog);
int textAppearance = 0;
int textColor = 0;
boolean textColorDefined = false;
for(int i = 0, count = a.getIndexCount(); i < count; i++){
int attr = a.getIndex(i);
if(attr == R.styleable.SimpleDialog_di_messageTextAppearance)
textAppearance = a.getResourceId(attr, 0);
else if(attr == R.styleable.SimpleDialog_di_messageTextColor) {
textColor = a.getColor(attr, 0);
textColorDefined = true;
}
else if(attr == R.styleable.SimpleDialog_di_radioButtonStyle)
radioButtonStyle(a.getResourceId(attr, 0));
else if(attr == R.styleable.SimpleDialog_di_checkBoxStyle)
checkBoxStyle(a.getResourceId(attr, 0));
else if(attr == R.styleable.SimpleDialog_di_itemHeight)
itemHeight(a.getDimensionPixelSize(attr, 0));
else if(attr == R.styleable.SimpleDialog_di_itemTextAppearance)
itemTextAppearance(a.getResourceId(attr, 0));
}
a.recycle();
if(textAppearance != 0)
messageTextAppearance(textAppearance);
if(textColorDefined)
messageTextColor(textColor);
return this;
}
@Override
public Dialog clearContent() {
super.clearContent();
mMode = MODE_NONE;
return this;
}
@Override
public Dialog title(CharSequence title){
boolean titleVisible = !TextUtils.isEmpty(title);
contentMargin(mContentPadding, titleVisible ? 0 : mContentPadding, mContentPadding, 0);
return super.title(title);
}
@Override
public Dialog contentView(View v) {
if(mScrollView == null)
initScrollView();
if(mScrollView.getChildAt(0) != v && v != null) {
mScrollView.removeAllViews();
mScrollView.addView(v);
mMode = MODE_CUSTOM;
super.contentView(mScrollView);
}
return this;
}
private void initScrollView(){
mScrollView = new InternalScrollView(getContext());
mScrollView.setPadding(0, 0, 0, mContentPadding - mActionPadding_h);
mScrollView.setClipToPadding(false);
mScrollView.setFillViewport(true);
mScrollView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
ViewCompat.setLayoutDirection(mScrollView, View.LAYOUT_DIRECTION_INHERIT);
}
private void initMessageView(){
mMessage = new TextView(getContext());
mMessage.setTextAppearance(getContext(), mMessageTextAppearanceId);
mMessage.setTextColor(mMessageTextColor);
mMessage.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
}
/**
* Set a message text to this SimpleDialog.
* @param message
* @return The SimpleDialog for chaining methods.
*/
public SimpleDialog message(CharSequence message){
if(mScrollView == null)
initScrollView();
if(mMessage == null)
initMessageView();
if(mScrollView.getChildAt(0) != mMessage) {
mScrollView.removeAllViews();
mScrollView.addView(mMessage);
}
mMessage.setText(message);
if(!TextUtils.isEmpty(message)) {
mMode = MODE_MESSAGE;
super.contentView(mScrollView);
}
return this;
}
public TextView getMessageTextView() {
return mMessage;
}
/**
* Set a message text to this SimpleDialog.
* @param id The resourceId of text.
* @return The SimpleDialog for chaining methods.
*/
public SimpleDialog message(int id){
return message(id == 0 ? null : getContext().getResources().getString(id));
}
/**
* Sets the text color, size, style of the message view from the specified TextAppearance resource.
* @param resId The resourceId value.
* @return The SimpleDialog for chaining methods.
*/
public SimpleDialog messageTextAppearance(int resId){
if(mMessageTextAppearanceId != resId){
mMessageTextAppearanceId = resId;
if(mMessage != null)
mMessage.setTextAppearance(getContext(), mMessageTextAppearanceId);
}
return this;
}
/**
* Sets the text color of the message view.
* @param color The color value.
* @return The SimpleDialog for chaining methods.
*/
public SimpleDialog messageTextColor(int color){
if(mMessageTextColor != color){
mMessageTextColor = color;
if(mMessage != null)
mMessage.setTextColor(color);
}
return this;
}
/**
* Sets the style of radio button.
* @param resId The resourceId of style.
* @return The SimpleDialog for chaining methods.
*/
public SimpleDialog radioButtonStyle(int resId){
if(mRadioButtonStyle != resId){
mRadioButtonStyle = resId;
if(mAdapter != null && mMode == MODE_ITEMS)
mAdapter.notifyDataSetChanged();
}
return this;
}
/**
* Sets the style of check box.
* @param resId The resourceId of style.
* @return The SimpleDialog for chaining methods.
*/
public SimpleDialog checkBoxStyle(int resId){
if(mCheckBoxStyle != resId){
mCheckBoxStyle = resId;
if(mAdapter != null && mMode == MODE_MULTI_ITEMS)
mAdapter.notifyDataSetChanged();
}
return this;
}
/**
* Sets the height of item
* @param height The size in pixels.
* @return The SimpleDialog for chaining methods.
*/
public SimpleDialog itemHeight(int height){
if(mItemHeight != height){
mItemHeight = height;
if(mAdapter != null)
mAdapter.notifyDataSetChanged();
}
return this;
}
/**
* Sets the text color, size, style of the item view from the specified TextAppearance resource.
* @param resId The resourceId value.
* @return The SimpleDialog for chaining methods.
*/
public SimpleDialog itemTextAppearance(int resId){
if(mItemTextAppearance != resId){
mItemTextAppearance = resId;
if(mAdapter != null)
mAdapter.notifyDataSetChanged();
}
return this;
}
private void initListView(){
mListView = new InternalListView(getContext());
mListView.setDividerHeight(0);
mListView.setCacheColorHint(0x00000000);
mListView.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_OVERLAY);
mListView.setClipToPadding(false);
mListView.setSelector(BlankDrawable.getInstance());
mListView.setPadding(0, 0, 0, mContentPadding - mActionPadding_h);
mListView.setVerticalFadingEdgeEnabled(false);
mListView.setOverScrollMode(ListView.OVER_SCROLL_NEVER);
ViewCompat.setLayoutDirection(mListView, ViewCompat.LAYOUT_DIRECTION_INHERIT);
mAdapter = new InternalAdapter();
mListView.setAdapter(mAdapter);
}
/**
* Set the list of items in single-choice mode.
* @param items The list of items.
* @param selectedIndex The index of selected item.
* @return The SimpleDialog for chaining methods.
*/
public SimpleDialog items(CharSequence[] items, int selectedIndex){
if(mListView == null)
initListView();
mMode = MODE_ITEMS;
mAdapter.setItems(items, selectedIndex);
super.contentView(mListView);
return this;
}
/**
* Set the list of items in multi-choice mode.
* @param items The list of items.
* @param selectedIndexes The indexes of selected items.
* @return The SimpleDialog for chaining methods.
*/
public SimpleDialog multiChoiceItems(CharSequence[] items, int... selectedIndexes){
if(mListView == null)
initListView();
mMode = MODE_MULTI_ITEMS;
mAdapter.setItems(items, selectedIndexes);
super.contentView(mListView);
return this;
}
/**
* Set a listener will be called when the checked state of a item is changed.
* @param listener The {@link OnSelectionChangedListener} will be called.
* @return The SimpleDialog for chaining methods.
*/
public SimpleDialog onSelectionChangedListener(OnSelectionChangedListener listener){
mOnSelectionChangedListener = listener;
return this;
}
/**
* @return The list of index of all selected items.
*/
public int[] getSelectedIndexes(){
return mAdapter == null ? null : mAdapter.getSelectedIndexes();
}
/**
* @return The list of value of all selected items.
*/
public CharSequence[] getSelectedValues(){
return mAdapter.getSelectedValues();
}
/**
* @return The index of selected item.
*/
public int getSelectedIndex(){
return mAdapter == null ? -1 : mAdapter.getLastSelectedIndex();
}
/**
* @return The value of selected item.
*/
public CharSequence getSelectedValue(){
return mAdapter.getLastSelectedValue();
}
private class InternalScrollView extends ScrollView {
private boolean mIsRtl = false;
public InternalScrollView(Context context) {
super(context);
}
public void onRtlPropertiesChanged(int layoutDirection) {
boolean rtl = layoutDirection == LAYOUT_DIRECTION_RTL;
if(mIsRtl != rtl) {
mIsRtl = rtl;
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
View v = getChildAt(0);
if (v != null && v == mMessage)
mMessage.setTextDirection(mIsRtl ? View.TEXT_DIRECTION_RTL : View.TEXT_DIRECTION_LTR);
}
requestLayout();
}
}
public boolean isLayoutRtl(){
return mIsRtl;
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
View child = getChildAt(0);
showDivider(child != null && child.getMeasuredHeight() > getMeasuredHeight() - getPaddingTop() - getPaddingBottom());
}
}
private class InternalListView extends ListView {
private boolean mIsRtl = false;
public InternalListView(Context context) {
super(context);
}
public void onRtlPropertiesChanged(int layoutDirection) {
boolean rtl = layoutDirection == LAYOUT_DIRECTION_RTL;
if(mIsRtl != rtl) {
mIsRtl = rtl;
requestLayout();
}
}
public boolean isLayoutRtl(){
return mIsRtl;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
if(heightMode == MeasureSpec.UNSPECIFIED){
if(mItemHeight != ViewGroup.LayoutParams.WRAP_CONTENT)
heightMeasureSpec = MeasureSpec.makeMeasureSpec(mItemHeight * getAdapter().getCount() + getPaddingTop() + getPaddingBottom(), MeasureSpec.EXACTLY);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
int totalHeight = 0;
int childCount = getChildCount();
for(int i = 0; i < childCount; i++)
totalHeight += getChildAt(i).getMeasuredHeight();
showDivider(totalHeight > getMeasuredHeight() || (totalHeight == getMeasuredHeight() && getAdapter().getCount() > childCount));
}
}
private class InternalAdapter extends BaseAdapter implements CompoundButton.OnCheckedChangeListener{
private CharSequence[] mItems;
private boolean[] mSelected;
private int mLastSelectedIndex;
public void setItems(CharSequence[] items, int... selectedIndexes){
mItems = items;
if(mSelected == null || mSelected.length != items.length)
mSelected = new boolean[items.length];
for(int i = 0; i < mSelected.length; i++)
mSelected[i] = false;
if(selectedIndexes != null)
for(int index : selectedIndexes)
if(index >= 0 && index < mSelected.length) {
mSelected[index] = true;
mLastSelectedIndex = index;
}
notifyDataSetChanged();
}
public int getLastSelectedIndex(){
return mLastSelectedIndex;
}
public CharSequence getLastSelectedValue(){
return mItems[mLastSelectedIndex];
}
public int[] getSelectedIndexes(){
int count = 0;
for(int i = 0; i < mSelected.length; i++)
if(mSelected[i])
count++;
if(count == 0)
return null;
int[] result = new int[count];
count = 0;
for(int i = 0; i < mSelected.length; i++)
if(mSelected[i]){
result[count] = i;
count++;
}
return result;
}
public CharSequence[] getSelectedValues(){
int count = 0;
for(int i = 0; i < mSelected.length; i++)
if(mSelected[i])
count++;
if(count == 0)
return null;
CharSequence[] result = new CharSequence[count];
count = 0;
for(int i = 0; i < mSelected.length; i++)
if(mSelected[i]){
result[count] = mItems[i];
count++;
}
return result;
}
@Override
public int getCount() {
return mItems == null ? 0 : mItems.length;
}
@Override
public Object getItem(int position) {
return mItems == null ? 0 : mItems[position];
}
@Override
public long getItemId(int position) {
return 0;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
CompoundButton v = (CompoundButton)convertView;
if(v == null) {
if(mMode == MODE_MULTI_ITEMS){
v = new CheckBox(parent.getContext());
// ViewUtil.applyStyle(v, mCheckBoxStyle);
}
else{
v = new RadioButton(parent.getContext());
// ViewUtil.applyStyle(v, mRadioButtonStyle);
}
if(mItemHeight != ViewGroup.LayoutParams.WRAP_CONTENT)
v.setMinHeight(mItemHeight);
v.setGravity(Gravity.START | Gravity.CENTER_VERTICAL);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
v.setTextDirection(((InternalListView)parent).isLayoutRtl() ? View.TEXT_DIRECTION_RTL : View.TEXT_DIRECTION_LTR);
v.setTextAppearance(v.getContext(), mItemTextAppearance);
v.setCompoundDrawablePadding(mContentPadding);
}
v.setTag(position);
v.setText(mItems[position]);
if(v instanceof CheckBox)
((CheckBox) v).setChecked(mSelected[position]);
else
((RadioButton) v).setChecked(mSelected[position]);
v.setOnCheckedChangeListener(this);
return v;
}
@Override
public void onCheckedChanged(android.widget.CompoundButton v, boolean isChecked) {
int position = (Integer)v.getTag();
if(mSelected[position] != isChecked) {
mSelected[position] = isChecked;
if(mOnSelectionChangedListener != null)
mOnSelectionChangedListener.onSelectionChanged(position, mSelected[position]);
}
if(mMode == MODE_ITEMS && isChecked && mLastSelectedIndex != position){
mSelected[mLastSelectedIndex] = false;
if(mOnSelectionChangedListener != null)
mOnSelectionChangedListener.onSelectionChanged(mLastSelectedIndex, false);
CompoundButton child = (CompoundButton) mListView.getChildAt(mLastSelectedIndex - mListView.getFirstVisiblePosition());
if(child != null)
child.setChecked(false);
mLastSelectedIndex = position;
}
}
}
public static class Builder extends Dialog.Builder implements OnSelectionChangedListener {
protected int mMode;
protected CharSequence mMessage;
protected CharSequence[] mItems;
protected int[] mSelectedIndexes;
public Builder(){
super(R.style.Material_App_Dialog_Simple_Light);
}
public Builder(int styleId){
super(styleId);
}
public Builder message(CharSequence message){
mMode = MODE_MESSAGE;
mMessage = message;
return this;
}
public Builder items(CharSequence[] items, int selectedIndex){
mMode = MODE_ITEMS;
mItems = items;
mSelectedIndexes = new int[]{selectedIndex};
return this;
}
public Builder multiChoiceItems(CharSequence[] items, int... selectedIndexes){
mMode = MODE_MULTI_ITEMS;
mItems = items;
mSelectedIndexes = selectedIndexes;
return this;
}
public int getSelectedIndex(){
if(mMode == MODE_ITEMS || mMode == MODE_MULTI_ITEMS)
return mSelectedIndexes[0];
return -1;
}
public CharSequence getSelectedValue(){
int index = getSelectedIndex();
return index >= 0 ? mItems[index] : null;
}
public int[] getSelectedIndexes(){
if(mMode == MODE_ITEMS || mMode == MODE_MULTI_ITEMS)
return mSelectedIndexes;
return null;
}
public CharSequence[] getSelectedValues(){
int[] indexes = getSelectedIndexes();
if(indexes == null || indexes.length == 0)
return null;
CharSequence[] result = new CharSequence[indexes.length];
for(int i = 0; i < indexes.length; i++)
result[i] = mItems[indexes[i]];
return result;
}
@Override
protected Dialog onBuild(Context context, int styleId) {
SimpleDialog dialog = new SimpleDialog(context, styleId);
switch (mMode){
case MODE_MESSAGE:
dialog.message(mMessage);
break;
case MODE_ITEMS:
dialog.items(mItems, mSelectedIndexes == null ? 0 : mSelectedIndexes[0]);
dialog.onSelectionChangedListener(this);
break;
case MODE_MULTI_ITEMS:
dialog.multiChoiceItems(mItems, mSelectedIndexes);
dialog.onSelectionChangedListener(this);
break;
}
return dialog;
}
@Override
public void onSelectionChanged(int index, boolean selected) {
switch (mMode){
case MODE_ITEMS:
if(selected) {
if (mSelectedIndexes == null)
mSelectedIndexes = new int[]{index};
else
mSelectedIndexes[0] = index;
}
break;
case MODE_MULTI_ITEMS:
mSelectedIndexes = ((SimpleDialog)mDialog).getSelectedIndexes();
break;
}
}
protected Builder(Parcel in) {
super(in);
}
@Override
protected void onReadFromParcel(Parcel in) {
mMode = in.readInt();
switch (mMode){
case MODE_MESSAGE:
mMessage = (CharSequence)in.readValue(null);
break;
case MODE_ITEMS: {
Parcelable[] values = (Parcelable[]) in.readArray(null);
if (values != null && values.length > 0) {
mItems = new CharSequence[values.length];
for (int i = 0; i < mItems.length; i++)
mItems[i] = (CharSequence) values[i];
} else
mItems = null;
mSelectedIndexes = new int[]{in.readInt()};
break;
}
case MODE_MULTI_ITEMS: {
Parcelable[] values = (Parcelable[]) in.readArray(null);
if (values != null && values.length > 0) {
mItems = new CharSequence[values.length];
for (int i = 0; i < mItems.length; i++)
mItems[i] = (CharSequence) values[i];
} else
mItems = null;
int length = in.readInt();
if(length > 0) {
mSelectedIndexes = new int[length];
in.readIntArray(mSelectedIndexes);
}
break;
}
}
}
@Override
protected void onWriteToParcel(Parcel dest, int flags) {
dest.writeInt(mMode);
switch (mMode){
case MODE_MESSAGE:
dest.writeValue(mMessage);
break;
case MODE_ITEMS:
dest.writeArray(mItems);
dest.writeInt(mSelectedIndexes == null ? 0 : mSelectedIndexes[0]);
break;
case MODE_MULTI_ITEMS:
dest.writeArray(mItems);
int length = mSelectedIndexes == null ? 0 : mSelectedIndexes.length;
dest.writeInt(length);
if(length > 0)
dest.writeIntArray(mSelectedIndexes);
break;
}
}
public static final Creator<Builder> CREATOR = new Creator<Builder>() {
public Builder createFromParcel(Parcel in) {
return new Builder(in);
}
public Builder[] newArray(int size) {
return new Builder[size];
}
};
}
}