Java tutorial
/* * Copyright 2015 Rey Pham. * Modifications Copyright(C) 2016 Fred Grott * * 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.github.shareme.gwsmaterialuikit.library.material.app; import android.annotation.TargetApi; import android.content.Context; import android.content.DialogInterface; import android.content.res.ColorStateList; import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.NonNull; import android.support.v4.view.ViewCompat; import android.support.v7.widget.CardView; import android.text.TextUtils; import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.FrameLayout; import com.github.shareme.gwsmaterialuikit.library.R; import com.github.shareme.gwsmaterialuikit.library.material.drawable.BlankDrawable; import com.github.shareme.gwsmaterialuikit.library.material.drawable.RippleDrawable; import com.github.shareme.gwsmaterialuikit.library.material.util.ThemeUtil; import com.github.shareme.gwsmaterialuikit.library.material.util.ViewUtil; import com.github.shareme.gwsmaterialuikit.library.material.widget.Button; import com.github.shareme.gwsmaterialuikit.library.material.widget.TextView; /** * Created by Rey on 12/10/2014. */ public class Dialog extends android.app.Dialog { private ContainerFrameLayout mContainer; private int mLayoutWidth = ViewGroup.LayoutParams.WRAP_CONTENT; private int mLayoutHeight = ViewGroup.LayoutParams.WRAP_CONTENT; private int mMaxWidth; private int mMaxHeight; protected TextView mTitle; protected Button mPositiveAction; protected Button mNegativeAction; protected Button mNeutralAction; private View mContent; private DialogCardView mCardView; protected int mContentPadding; protected int mActionHeight; protected int mActionOuterHeight; protected int mActionOuterPadding; protected int mActionMinWidth; protected int mActionPadding; protected int mDialogHorizontalPadding; protected int mDialogVerticalPadding; protected int mInAnimationId; protected int mOutAnimationId; private final Handler mHandler = new Handler(); private final Runnable mDismissAction = new Runnable() { public void run() { //dirty fix for java.lang.IllegalArgumentException: View not attached to window manager try { Dialog.super.dismiss(); } catch (IllegalArgumentException ex) { } } }; private boolean mLayoutActionVertical = false; private boolean mCancelable = true; private boolean mCanceledOnTouchOutside = true; private boolean mDismissPending = false; /** * The viewId of title view. */ public static final int TITLE = ViewUtil.generateViewId(); /** * The viewId of positive action button. */ public static final int ACTION_POSITIVE = ViewUtil.generateViewId(); /** * The viewId of negative action button. */ public static final int ACTION_NEGATIVE = ViewUtil.generateViewId(); /** * The viewId of neutral action button. */ public static final int ACTION_NEUTRAL = ViewUtil.generateViewId(); public Dialog(Context context) { this(context, R.style.Material_App_Dialog_Light); } public Dialog(Context context, int style) { super(context, style); //Override style to ensure not show window's title or background. //TODO: find a way to ensure windowIsFloating attribute is false. requestWindowFeature(Window.FEATURE_NO_TITLE); getWindow().setBackgroundDrawable(BlankDrawable.getInstance()); WindowManager.LayoutParams layout = getWindow().getAttributes(); layout.width = ViewGroup.LayoutParams.MATCH_PARENT; layout.height = ViewGroup.LayoutParams.MATCH_PARENT; layout.windowAnimations = R.style.DialogNoAnimation; getWindow().setAttributes(layout); init(context, style); } private void init(Context context, int style) { mContentPadding = ThemeUtil.dpToPx(context, 24); mActionMinWidth = ThemeUtil.dpToPx(context, 64); mActionHeight = ThemeUtil.dpToPx(context, 36); mActionOuterHeight = ThemeUtil.dpToPx(context, 48); mActionPadding = ThemeUtil.dpToPx(context, 8); mActionOuterPadding = ThemeUtil.dpToPx(context, 16); mDialogHorizontalPadding = ThemeUtil.dpToPx(context, 40); mDialogVerticalPadding = ThemeUtil.dpToPx(context, 24); mCardView = new DialogCardView(context); mContainer = new ContainerFrameLayout(context); mTitle = new TextView(context); mPositiveAction = new Button(context); mNegativeAction = new Button(context); mNeutralAction = new Button(context); mCardView.setPreventCornerOverlap(false); mCardView.setUseCompatPadding(true); mTitle.setId(TITLE); mTitle.setGravity(Gravity.START); mTitle.setPadding(mContentPadding, mContentPadding, mContentPadding, mContentPadding - mActionPadding); mPositiveAction.setId(ACTION_POSITIVE); mPositiveAction.setPadding(mActionPadding, 0, mActionPadding, 0); mPositiveAction.setBackgroundResource(0); mNegativeAction.setId(ACTION_NEGATIVE); mNegativeAction.setPadding(mActionPadding, 0, mActionPadding, 0); mNegativeAction.setBackgroundResource(0); mNeutralAction.setId(ACTION_NEUTRAL); mNeutralAction.setPadding(mActionPadding, 0, mActionPadding, 0); mNeutralAction.setBackgroundResource(0); mContainer.addView(mCardView); mCardView.addView(mTitle); mCardView.addView(mPositiveAction); mCardView.addView(mNegativeAction); mCardView.addView(mNeutralAction); backgroundColor(ThemeUtil.windowBackground(context, 0xFFFFFFFF)); elevation(ThemeUtil.dpToPx(context, 4)); cornerRadius(ThemeUtil.dpToPx(context, 2)); dimAmount(0.5f); layoutDirection(ViewCompat.LAYOUT_DIRECTION_LOCALE); titleTextAppearance(R.style.TextAppearance_AppCompat_Title); actionTextAppearance(R.style.TextAppearance_AppCompat_Button); dividerColor(0x1E000000); dividerHeight(ThemeUtil.dpToPx(context, 1)); cancelable(true); canceledOnTouchOutside(true); clearContent(); onCreate(); applyStyle(style); super.setContentView(mContainer); } protected void onCreate() { } public Dialog applyStyle(int resId) { Context context = getContext(); TypedArray a = context.obtainStyledAttributes(resId, R.styleable.Dialog); int layout_width = mLayoutWidth; int layout_height = mLayoutHeight; boolean layoutParamsDefined = false; int titleTextAppearance = 0; int titleTextColor = 0; boolean titleTextColorDefined = false; int actionBackground = 0; int actionRipple = 0; int actionTextAppearance = 0; ColorStateList actionTextColors = null; int positiveActionBackground = 0; int positiveActionRipple = 0; int positiveActionTextAppearance = 0; ColorStateList positiveActionTextColors = null; int negativeActionBackground = 0; int negativeActionRipple = 0; int negativeActionTextAppearance = 0; ColorStateList negativeActionTextColors = null; int neutralActionBackground = 0; int neutralActionRipple = 0; int neutralActionTextAppearance = 0; ColorStateList neutralActionTextColors = null; for (int i = 0, count = a.getIndexCount(); i < count; i++) { int attr = a.getIndex(i); if (attr == R.styleable.Dialog_android_layout_width) { layout_width = a.getLayoutDimension(attr, ViewGroup.LayoutParams.WRAP_CONTENT); layoutParamsDefined = true; } else if (attr == R.styleable.Dialog_android_layout_height) { layout_height = a.getLayoutDimension(attr, ViewGroup.LayoutParams.WRAP_CONTENT); layoutParamsDefined = true; } else if (attr == R.styleable.Dialog_di_maxWidth) maxWidth(a.getDimensionPixelOffset(attr, 0)); else if (attr == R.styleable.Dialog_di_maxHeight) maxHeight(a.getDimensionPixelOffset(attr, 0)); else if (attr == R.styleable.Dialog_di_dimAmount) dimAmount(a.getFloat(attr, 0)); else if (attr == R.styleable.Dialog_di_backgroundColor) backgroundColor(a.getColor(attr, 0)); else if (attr == R.styleable.Dialog_di_maxElevation) maxElevation(a.getDimensionPixelOffset(attr, 0)); else if (attr == R.styleable.Dialog_di_elevation) elevation(a.getDimensionPixelOffset(attr, 0)); else if (attr == R.styleable.Dialog_di_cornerRadius) cornerRadius(a.getDimensionPixelOffset(attr, 0)); else if (attr == R.styleable.Dialog_di_layoutDirection) layoutDirection(a.getInteger(attr, 0)); else if (attr == R.styleable.Dialog_di_titleTextAppearance) titleTextAppearance = a.getResourceId(attr, 0); else if (attr == R.styleable.Dialog_di_titleTextColor) { titleTextColor = a.getColor(attr, 0); titleTextColorDefined = true; } else if (attr == R.styleable.Dialog_di_actionBackground) actionBackground = a.getResourceId(attr, 0); else if (attr == R.styleable.Dialog_di_actionRipple) actionRipple = a.getResourceId(attr, 0); else if (attr == R.styleable.Dialog_di_actionTextAppearance) actionTextAppearance = a.getResourceId(attr, 0); else if (attr == R.styleable.Dialog_di_actionTextColor) actionTextColors = a.getColorStateList(attr); else if (attr == R.styleable.Dialog_di_positiveActionBackground) positiveActionBackground = a.getResourceId(attr, 0); else if (attr == R.styleable.Dialog_di_positiveActionRipple) positiveActionRipple = a.getResourceId(attr, 0); else if (attr == R.styleable.Dialog_di_positiveActionTextAppearance) positiveActionTextAppearance = a.getResourceId(attr, 0); else if (attr == R.styleable.Dialog_di_positiveActionTextColor) positiveActionTextColors = a.getColorStateList(attr); else if (attr == R.styleable.Dialog_di_negativeActionBackground) negativeActionBackground = a.getResourceId(attr, 0); else if (attr == R.styleable.Dialog_di_negativeActionRipple) negativeActionRipple = a.getResourceId(attr, 0); else if (attr == R.styleable.Dialog_di_negativeActionTextAppearance) negativeActionTextAppearance = a.getResourceId(attr, 0); else if (attr == R.styleable.Dialog_di_negativeActionTextColor) negativeActionTextColors = a.getColorStateList(attr); else if (attr == R.styleable.Dialog_di_neutralActionBackground) neutralActionBackground = a.getResourceId(attr, 0); else if (attr == R.styleable.Dialog_di_neutralActionRipple) neutralActionRipple = a.getResourceId(attr, 0); else if (attr == R.styleable.Dialog_di_neutralActionTextAppearance) neutralActionTextAppearance = a.getResourceId(attr, 0); else if (attr == R.styleable.Dialog_di_neutralActionTextColor) neutralActionTextColors = a.getColorStateList(attr); else if (attr == R.styleable.Dialog_di_inAnimation) inAnimation(a.getResourceId(attr, 0)); else if (attr == R.styleable.Dialog_di_outAnimation) outAnimation(a.getResourceId(attr, 0)); else if (attr == R.styleable.Dialog_di_dividerColor) dividerColor(a.getColor(attr, 0)); else if (attr == R.styleable.Dialog_di_dividerHeight) dividerHeight(a.getDimensionPixelOffset(attr, 0)); else if (attr == R.styleable.Dialog_di_cancelable) cancelable(a.getBoolean(attr, true)); else if (attr == R.styleable.Dialog_di_canceledOnTouchOutside) canceledOnTouchOutside(a.getBoolean(attr, true)); } a.recycle(); if (layoutParamsDefined) layoutParams(layout_width, layout_height); if (titleTextAppearance != 0) titleTextAppearance(titleTextAppearance); if (titleTextColorDefined) titleColor(titleTextColor); if (actionBackground != 0) actionBackground(actionBackground); if (actionRipple != 0) actionRipple(actionRipple); if (actionTextAppearance != 0) actionTextAppearance(actionTextAppearance); if (actionTextColors != null) actionTextColor(actionTextColors); if (positiveActionBackground != 0) positiveActionBackground(positiveActionBackground); if (positiveActionRipple != 0) positiveActionRipple(positiveActionRipple); if (positiveActionTextAppearance != 0) positiveActionTextAppearance(positiveActionTextAppearance); if (positiveActionTextColors != null) positiveActionTextColor(positiveActionTextColors); if (negativeActionBackground != 0) negativeActionBackground(negativeActionBackground); if (negativeActionRipple != 0) negativeActionRipple(negativeActionRipple); if (negativeActionTextAppearance != 0) negativeActionTextAppearance(negativeActionTextAppearance); if (negativeActionTextColors != null) negativeActionTextColor(negativeActionTextColors); if (neutralActionBackground != 0) neutralActionBackground(neutralActionBackground); if (neutralActionRipple != 0) neutralActionRipple(neutralActionRipple); if (neutralActionTextAppearance != 0) neutralActionTextAppearance(neutralActionTextAppearance); if (neutralActionTextColors != null) neutralActionTextColor(neutralActionTextColors); return this; } /** * Clear the content of this Dialog. * @return The Dialog for chaining methods. */ public Dialog clearContent() { title(0); positiveAction(0); positiveActionClickListener(null); negativeAction(0); negativeActionClickListener(null); neutralAction(0); neutralActionClickListener(null); contentView(null); return this; } /** * Set the params of this Dialog layout. * @param width The width param. Can be the size in pixels, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} or {@link ViewGroup.LayoutParams#MATCH_PARENT}. * @param height The height param. Can be the size in pixels, or {@link ViewGroup.LayoutParams#WRAP_CONTENT} or {@link ViewGroup.LayoutParams#MATCH_PARENT}. * @return The Dialog for chaining methods. */ public Dialog layoutParams(int width, int height) { mLayoutWidth = width; mLayoutHeight = height; return this; } /** * Set the maximum width of this Dialog layout. * @param width The maximum width in pixels. * @return The Dialog for chaining methods. */ public Dialog maxWidth(int width) { mMaxWidth = width; return this; } /** * Set the maximum height of this Dialog layout. * @param height The maximum height in pixels. * @return The Dialog for chaining methods. */ public Dialog maxHeight(int height) { mMaxHeight = height; return this; } /** * Set the dim amount of the region outside this Dialog. * @param amount The dim amount in [0..1]. * @return The Dialog for chaining methods. */ public Dialog dimAmount(float amount) { Window window = getWindow(); if (amount > 0f) { window.addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); WindowManager.LayoutParams lp = window.getAttributes(); lp.dimAmount = amount; window.setAttributes(lp); } else window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND); return this; } /** * Set the background color of this Dialog * @param color The color value. * @return The Dialog for chaining methods. */ public Dialog backgroundColor(int color) { mCardView.setCardBackgroundColor(color); return this; } /** * Set the elevation value of this Dialog. * @param elevation * @return The Dialog for chaining methods. */ public Dialog elevation(float elevation) { if (mCardView.getMaxCardElevation() < elevation) mCardView.setMaxCardElevation(elevation); mCardView.setCardElevation(elevation); return this; } /** * Set the maximum elevation value of this Dialog. * @param elevation * @return The Dialog for chaining methods. */ public Dialog maxElevation(float elevation) { mCardView.setMaxCardElevation(elevation); return this; } /** * Set the corner radius of this Dialog. * @param radius The corner radius. * @return The Dialog for chaining methods. */ public Dialog cornerRadius(float radius) { mCardView.setRadius(radius); return this; } /** * Set the divider's color of this Dialog. * @param color The color value. * @return The Dialog for chaining methods. */ public Dialog dividerColor(int color) { mCardView.setDividerColor(color); return this; } /** * Set the height of divider of this Dialog. * @param height The size value in pixels. * @return The Dialog for chaining methods. */ public Dialog dividerHeight(int height) { mCardView.setDividerHeight(height); return this; } /** * Set the title of this Dialog. * @param title The title text. * @return The Dialog for chaining methods. */ public Dialog title(CharSequence title) { mTitle.setText(title); mTitle.setVisibility(TextUtils.isEmpty(title) ? View.GONE : View.VISIBLE); return this; } /** * Set the title of this Dialog. * @param id The resourceId of text. * @return The Dialog for chaining methods. */ public Dialog title(int id) { return title(id == 0 ? null : getContext().getResources().getString(id)); } @Override public void setTitle(CharSequence title) { title(title); } @Override public void setTitle(int titleId) { title(titleId); } /** * Set the text's color of Dialog's title. * @param color The color value. * @return The Dialog for chaining methods. */ public Dialog titleColor(int color) { mTitle.setTextColor(color); return this; } /** * Sets the text color, size, style of the title view from the specified TextAppearance resource. * @param resId The resourceId value. * @return The Dialog for chaining methods. */ public Dialog titleTextAppearance(int resId) { mTitle.setTextAppearance(getContext(), resId); return this; } /** * Set the background drawable of all action buttons. * @param id The resourceId of drawable. * @return The Dialog for chaining methods. */ public Dialog actionBackground(int id) { positiveActionBackground(id); negativeActionBackground(id); neutralActionBackground(id); return this; } /** * Set the background drawable of all action buttons. * @param drawable The background drawable. * @return The Dialog for chaining methods. */ public Dialog actionBackground(Drawable drawable) { positiveActionBackground(drawable); negativeActionBackground(drawable); neutralActionBackground(drawable); return this; } /** * Set the RippleEffect of all action buttons. * @param resId The resourceId of style. * @return The Dialog for chaining methods. */ public Dialog actionRipple(int resId) { positiveActionRipple(resId); negativeActionRipple(resId); neutralActionRipple(resId); return this; } /** * Sets the text color, size, style of all action buttons from the specified TextAppearance resource. * @param resId The resourceId value. * @return The Dialog for chaining methods. */ public Dialog actionTextAppearance(int resId) { positiveActionTextAppearance(resId); negativeActionTextAppearance(resId); neutralActionTextAppearance(resId); return this; } /** * Sets the text color of all action buttons. * @param color * @return The Dialog for chaining methods. */ public Dialog actionTextColor(ColorStateList color) { positiveActionTextColor(color); negativeActionTextColor(color); neutralActionTextColor(color); return this; } /** * Sets the text color of all action buttons. * @param color * @return The Dialog for chaining methods. */ public Dialog actionTextColor(int color) { positiveActionTextColor(color); negativeActionTextColor(color); neutralActionTextColor(color); return this; } /** * Set the text of positive action button. * @param action * @return The Dialog for chaining methods. */ public Dialog positiveAction(CharSequence action) { mPositiveAction.setText(action); mPositiveAction.setVisibility(TextUtils.isEmpty(action) ? View.GONE : View.VISIBLE); return this; } /** * Set the text of positive action button. * @param id The resourceId of text. * @return The Dialog for chaining methods. */ public Dialog positiveAction(int id) { return positiveAction(id == 0 ? null : getContext().getResources().getString(id)); } /** * Set the background drawable of positive action button. * @param drawable The background drawable. * @return The Dialog for chaining methods. */ public Dialog positiveActionBackground(Drawable drawable) { ViewUtil.setBackground(mPositiveAction, drawable); return this; } /** * Set the background drawable of positive action button. * @param id The resourceId of drawable. * @return The Dialog for chaining methods. */ public Dialog positiveActionBackground(int id) { return positiveActionBackground(id == 0 ? null : getContext().getResources().getDrawable(id)); } /** * Set the RippleEffect of positive action button. * @param resId The resourceId of style. * @return The Dialog for chaining methods. */ public Dialog positiveActionRipple(int resId) { RippleDrawable drawable = new RippleDrawable.Builder(getContext(), resId).build(); return positiveActionBackground(drawable); } /** * Sets the text color, size, style of positive action button from the specified TextAppearance resource. * @param resId The resourceId value. * @return The Dialog for chaining methods. */ public Dialog positiveActionTextAppearance(int resId) { mPositiveAction.setTextAppearance(getContext(), resId); return this; } /** * Sets the text color of positive action button. * @param color * @return The Dialog for chaining methods. */ public Dialog positiveActionTextColor(ColorStateList color) { mPositiveAction.setTextColor(color); return this; } /** * Sets the text color of positive action button. * @param color * @return The Dialog for chaining methods. */ public Dialog positiveActionTextColor(int color) { mPositiveAction.setTextColor(color); return this; } /** * Set a listener will be called when positive action button is clicked. * @param listener The {@link View.OnClickListener} will be called. * @return The Dialog for chaining methods. */ public Dialog positiveActionClickListener(View.OnClickListener listener) { mPositiveAction.setOnClickListener(listener); return this; } /** * Set the text of negative action button. * @param action * @return The Dialog for chaining methods. */ public Dialog negativeAction(CharSequence action) { mNegativeAction.setText(action); mNegativeAction.setVisibility(TextUtils.isEmpty(action) ? View.GONE : View.VISIBLE); return this; } /** * Set the text of negative action button. * @param id The resourceId of text. * @return The Dialog for chaining methods. */ public Dialog negativeAction(int id) { return negativeAction(id == 0 ? null : getContext().getResources().getString(id)); } /** * Set the background drawable of negative action button. * @param drawable The background drawable. * @return The Dialog for chaining methods. */ public Dialog negativeActionBackground(Drawable drawable) { ViewUtil.setBackground(mNegativeAction, drawable); return this; } /** * Set the background drawable of neagtive action button. * @param id The resourceId of drawable. * @return The Dialog for chaining methods. */ public Dialog negativeActionBackground(int id) { return negativeActionBackground(id == 0 ? null : getContext().getResources().getDrawable(id)); } /** * Set the RippleEffect of negative action button. * @param resId The resourceId of style. * @return The Dialog for chaining methods. */ public Dialog negativeActionRipple(int resId) { RippleDrawable drawable = new RippleDrawable.Builder(getContext(), resId).build(); return negativeActionBackground(drawable); } /** * Sets the text color, size, style of negative action button from the specified TextAppearance resource. * @param resId The resourceId value. * @return The Dialog for chaining methods. */ public Dialog negativeActionTextAppearance(int resId) { mNegativeAction.setTextAppearance(getContext(), resId); return this; } /** * Sets the text color of negative action button. * @param color * @return The Dialog for chaining methods. */ public Dialog negativeActionTextColor(ColorStateList color) { mNegativeAction.setTextColor(color); return this; } /** * Sets the text color of negative action button. * @param color * @return The Dialog for chaining methods. */ public Dialog negativeActionTextColor(int color) { mNegativeAction.setTextColor(color); return this; } /** * Set a listener will be called when negative action button is clicked. * @param listener The {@link View.OnClickListener} will be called. * @return The Dialog for chaining methods. */ public Dialog negativeActionClickListener(View.OnClickListener listener) { mNegativeAction.setOnClickListener(listener); return this; } /** * Set the text of neutral action button. * @param action * @return The Dialog for chaining methods. */ public Dialog neutralAction(CharSequence action) { mNeutralAction.setText(action); mNeutralAction.setVisibility(TextUtils.isEmpty(action) ? View.GONE : View.VISIBLE); return this; } /** * Set the text of neutral action button. * @param id The resourceId of text. * @return The Dialog for chaining methods. */ public Dialog neutralAction(int id) { return neutralAction(id == 0 ? null : getContext().getResources().getString(id)); } /** * Set the background drawable of neutral action button. * @param drawable The background drawable. * @return The Dialog for chaining methods. */ public Dialog neutralActionBackground(Drawable drawable) { ViewUtil.setBackground(mNeutralAction, drawable); return this; } /** * Set the background drawable of neutral action button. * @param id The resourceId of drawable. * @return The Dialog for chaining methods. */ public Dialog neutralActionBackground(int id) { return neutralActionBackground(id == 0 ? null : getContext().getResources().getDrawable(id)); } /** * Set the RippleEffect of neutral action button. * @param resId The resourceId of style. * @return The Dialog for chaining methods. */ public Dialog neutralActionRipple(int resId) { RippleDrawable drawable = new RippleDrawable.Builder(getContext(), resId).build(); return neutralActionBackground(drawable); } /** * Sets the text color, size, style of neutral action button from the specified TextAppearance resource. * @param resId The resourceId value. * @return The Dialog for chaining methods. */ public Dialog neutralActionTextAppearance(int resId) { mNeutralAction.setTextAppearance(getContext(), resId); return this; } /** * Sets the text color of neutral action button. * @param color * @return The Dialog for chaining methods. */ public Dialog neutralActionTextColor(ColorStateList color) { mNeutralAction.setTextColor(color); return this; } /** * Sets the text color of neutral action button. * @param color * @return The Dialog for chaining methods. */ public Dialog neutralActionTextColor(int color) { mNeutralAction.setTextColor(color); return this; } /** * Set a listener will be called when neutral action button is clicked. * @param listener The {@link View.OnClickListener} will be called. * @return The Dialog for chaining methods. */ public Dialog neutralActionClickListener(View.OnClickListener listener) { mNeutralAction.setOnClickListener(listener); return this; } /** * Set the layout direction of this Dialog * @param direction The layout direction value. Can be {@link View#LAYOUT_DIRECTION_LTR}, {@link View#LAYOUT_DIRECTION_RTL} or {@link View#LAYOUT_DIRECTION_LOCALE} * @return The Dialog for chaining methods. */ public Dialog layoutDirection(int direction) { ViewCompat.setLayoutDirection(mCardView, direction); return this; } /** * Set the animation when Dialog enter the screen. * @param resId The resourceId of animation. * @return The Dialog for chaining methods. */ public Dialog inAnimation(int resId) { mInAnimationId = resId; return this; } /** * Set the animation when Dialog exit the screen. * @param resId The resourceId of animation. * @return The Dialog for chaining methods. */ public Dialog outAnimation(int resId) { mOutAnimationId = resId; return this; } /** * Indicate that Dialog should show divider when the content is longer than container view. * @param show * @return The Dialog for chaining methods. */ public Dialog showDivider(boolean show) { mCardView.setShowDivider(show); return this; } /** * Set the content view of this Dialog. * @param v The content view. * @return The Dialog for chaining methods. */ public Dialog contentView(View v) { if (mContent != v) { if (mContent != null) mCardView.removeView(mContent); mContent = v; } if (mContent != null) mCardView.addView(mContent); return this; } /** * Set the content view of this Dialog. * @param layoutId The reourceId of layout. * @return The Dialog for chaining methods. */ public Dialog contentView(int layoutId) { if (layoutId == 0) return this; View v = LayoutInflater.from(getContext()).inflate(layoutId, null); return contentView(v); } /** * Sets whether this dialog is cancelable with the * {@link android.view.KeyEvent#KEYCODE_BACK BACK} key. * @return The Dialog for chaining methods. */ public Dialog cancelable(boolean cancelable) { super.setCancelable(cancelable); mCancelable = cancelable; return this; } /** * Sets whether this dialog is canceled when touched outside the window's * bounds. If setting to true, the dialog is set to be cancelable if not * already set. * * @param cancel Whether the dialog should be canceled when touched outside * @return The Dialog for chaining methods. */ public Dialog canceledOnTouchOutside(boolean cancel) { super.setCanceledOnTouchOutside(cancel); mCanceledOnTouchOutside = cancel; return this; } /** * Set the margin between content view and Dialog border. * @param margin The margin size in pixels. * @return The Dialog for chaining methods. */ public Dialog contentMargin(int margin) { mCardView.setContentMargin(margin); return this; } /** * Set the margin between content view and Dialog border. * @param left The left margin size in pixels. * @param top The top margin size in pixels. * @param right The right margin size in pixels. * @param bottom The bottom margin size in pixels. * @return The Dialog for chaining methods. */ public Dialog contentMargin(int left, int top, int right, int bottom) { mCardView.setContentMargin(left, top, right, bottom); return this; } @Override public void setCancelable(boolean flag) { cancelable(flag); } @Override public void setCanceledOnTouchOutside(boolean cancel) { canceledOnTouchOutside(cancel); } @Override public void setContentView(@NonNull View v) { contentView(v); } @Override public void setContentView(int layoutId) { contentView(layoutId); } @Override public void setContentView(@NonNull View v, ViewGroup.LayoutParams params) { contentView(v); } @Override public void addContentView(@NonNull View view, ViewGroup.LayoutParams params) { contentView(view); } @Override protected void onStart() { super.onStart(); mCardView.setVisibility(View.VISIBLE); if (mInAnimationId != 0) mCardView.getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { mCardView.getViewTreeObserver().removeOnPreDrawListener(this); Animation anim = AnimationUtils.loadAnimation(mCardView.getContext(), mInAnimationId); mCardView.startAnimation(anim); return false; } }); } /** * Dismiss Dialog immediately without showing out animation. */ public void dismissImmediately() { super.dismiss(); if (mHandler != null) mHandler.removeCallbacks(mDismissAction); } @Override public void dismiss() { if (!isShowing() || mDismissPending) return; if (mOutAnimationId != 0) { Animation anim = AnimationUtils.loadAnimation(mContainer.getContext(), mOutAnimationId); anim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { mDismissPending = true; } @Override public void onAnimationRepeat(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { mDismissPending = false; mCardView.setVisibility(View.GONE); mHandler.post(mDismissAction); } }); mCardView.startAnimation(anim); } else mHandler.post(mDismissAction); } private class ContainerFrameLayout extends FrameLayout { private boolean mClickOutside = false; public ContainerFrameLayout(Context context) { super(context); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = MeasureSpec.getSize(widthMeasureSpec); int heightSize = MeasureSpec.getSize(heightMeasureSpec); mCardView.measure(widthMeasureSpec, heightMeasureSpec); setMeasuredDimension(widthSize, heightSize); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int childLeft = (right - left - mCardView.getMeasuredWidth()) / 2; int childTop = (bottom - top - mCardView.getMeasuredHeight()) / 2; int childRight = childLeft + mCardView.getMeasuredWidth(); int childBottom = childTop + mCardView.getMeasuredHeight(); mCardView.layout(childLeft, childTop, childRight, childBottom); } private boolean isOutsideDialog(float x, float y) { return x < mCardView.getLeft() + mCardView.getPaddingLeft() || x > mCardView.getRight() - mCardView.getPaddingRight() || y < mCardView.getTop() + mCardView.getPaddingTop() || y > mCardView.getBottom() - mCardView.getPaddingBottom(); } @Override public boolean onTouchEvent(MotionEvent event) { boolean handled = super.onTouchEvent(event); if (handled) return true; switch (event.getAction()) { case MotionEvent.ACTION_DOWN: if (isOutsideDialog(event.getX(), event.getY())) { mClickOutside = true; return true; } return false; case MotionEvent.ACTION_MOVE: return mClickOutside; case MotionEvent.ACTION_CANCEL: mClickOutside = false; return false; case MotionEvent.ACTION_UP: if (mClickOutside && isOutsideDialog(event.getX(), event.getY())) { mClickOutside = false; if (mCancelable && mCanceledOnTouchOutside) dismiss(); return true; } return false; } return false; } } private class DialogCardView extends CardView { private Paint mDividerPaint; private float mDividerPos = -1f; private boolean mShowDivider = false; private int mContentMarginLeft; private int mContentMarginTop; private int mContentMarginRight; private int mContentMarginBottom; private boolean mIsRtl = false; public DialogCardView(Context context) { super(context); mDividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); mDividerPaint.setStyle(Paint.Style.STROKE); setWillNotDraw(false); } public void setContentMargin(int margin) { setContentMargin(margin, margin, margin, margin); } public void setContentMargin(int left, int top, int right, int bottom) { mContentMarginLeft = left; mContentMarginTop = top; mContentMarginRight = right; mContentMarginBottom = bottom; } public void setDividerColor(int color) { mDividerPaint.setColor(color); invalidate(); } public void setDividerHeight(int height) { mDividerPaint.setStrokeWidth(height); invalidate(); } public void setShowDivider(boolean show) { if (mShowDivider != show) { mShowDivider = show; invalidate(); } } @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1) @Override 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) { int direction = mIsRtl ? TEXT_DIRECTION_RTL : TEXT_DIRECTION_LTR; mTitle.setTextDirection(direction); mPositiveAction.setTextDirection(direction); mNegativeAction.setTextDirection(direction); mNeutralAction.setTextDirection(direction); } requestLayout(); } } @SuppressWarnings("Range") @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthSize = View.MeasureSpec.getSize(widthMeasureSpec); int heightSize = View.MeasureSpec.getSize(heightMeasureSpec); int paddingLeft = Math.max(mDialogHorizontalPadding, mCardView.getPaddingLeft()); int paddingRight = Math.max(mDialogHorizontalPadding, mCardView.getPaddingRight()); int paddingTop = Math.max(mDialogVerticalPadding, mCardView.getPaddingTop()); int paddingBottom = Math.max(mDialogVerticalPadding, mCardView.getPaddingBottom()); int maxWidth = widthSize - paddingLeft - paddingRight; if (mMaxWidth > 0) maxWidth = Math.min(maxWidth, mMaxWidth); int maxHeight = heightSize - paddingTop - paddingBottom; if (mMaxHeight > 0) maxHeight = Math.min(maxHeight, mMaxHeight); int width = mLayoutWidth == ViewGroup.LayoutParams.MATCH_PARENT ? maxWidth : mLayoutWidth; int height = mLayoutHeight == ViewGroup.LayoutParams.MATCH_PARENT ? maxHeight : mLayoutHeight; int widthMs; int heightMs; int titleWidth = 0; int titleHeight = 0; if (mTitle.getVisibility() == View.VISIBLE) { widthMs = View.MeasureSpec.makeMeasureSpec( width == ViewGroup.LayoutParams.WRAP_CONTENT ? maxWidth : width, View.MeasureSpec.AT_MOST); heightMs = View.MeasureSpec.makeMeasureSpec(maxHeight, View.MeasureSpec.AT_MOST); mTitle.measure(widthMs, heightMs); titleWidth = mTitle.getMeasuredWidth(); titleHeight = mTitle.getMeasuredHeight(); } int contentWidth = 0; int contentHeight = 0; if (mContent != null) { widthMs = View.MeasureSpec .makeMeasureSpec((width == ViewGroup.LayoutParams.WRAP_CONTENT ? maxWidth : width) - mContentMarginLeft - mContentMarginRight, View.MeasureSpec.AT_MOST); heightMs = View.MeasureSpec.makeMeasureSpec(maxHeight - mContentMarginTop - mContentMarginBottom, View.MeasureSpec.AT_MOST); mContent.measure(widthMs, heightMs); contentWidth = mContent.getMeasuredWidth(); contentHeight = mContent.getMeasuredHeight(); } int visibleActions = 0; int positiveActionWidth = 0; if (mPositiveAction.getVisibility() == View.VISIBLE) { widthMs = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); heightMs = View.MeasureSpec.makeMeasureSpec(mActionHeight, View.MeasureSpec.EXACTLY); mPositiveAction.measure(widthMs, heightMs); positiveActionWidth = mPositiveAction.getMeasuredWidth(); if (positiveActionWidth < mActionMinWidth) { mPositiveAction.measure( View.MeasureSpec.makeMeasureSpec(mActionMinWidth, View.MeasureSpec.EXACTLY), heightMs); positiveActionWidth = mActionMinWidth; } visibleActions++; } int negativeActionWidth = 0; if (mNegativeAction.getVisibility() == View.VISIBLE) { widthMs = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); heightMs = View.MeasureSpec.makeMeasureSpec(mActionHeight, View.MeasureSpec.EXACTLY); mNegativeAction.measure(widthMs, heightMs); negativeActionWidth = mNegativeAction.getMeasuredWidth(); if (negativeActionWidth < mActionMinWidth) { mNegativeAction.measure( View.MeasureSpec.makeMeasureSpec(mActionMinWidth, View.MeasureSpec.EXACTLY), heightMs); negativeActionWidth = mActionMinWidth; } visibleActions++; } int neutralActionWidth = 0; if (mNeutralAction.getVisibility() == View.VISIBLE) { widthMs = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); heightMs = View.MeasureSpec.makeMeasureSpec(mActionHeight, View.MeasureSpec.EXACTLY); mNeutralAction.measure(widthMs, heightMs); neutralActionWidth = mNeutralAction.getMeasuredWidth(); if (neutralActionWidth < mActionMinWidth) { mNeutralAction.measure( View.MeasureSpec.makeMeasureSpec(mActionMinWidth, View.MeasureSpec.EXACTLY), heightMs); neutralActionWidth = mActionMinWidth; } visibleActions++; } int actionBarWidth = positiveActionWidth + negativeActionWidth + neutralActionWidth + mActionOuterPadding * 2 + mActionPadding * Math.max(0, visibleActions - 1); if (width == ViewGroup.LayoutParams.WRAP_CONTENT) width = Math.min(maxWidth, Math.max(titleWidth, Math.max(contentWidth + mContentMarginLeft + mContentMarginRight, actionBarWidth))); mLayoutActionVertical = actionBarWidth > width; int nonContentHeight = titleHeight + (visibleActions > 0 ? mActionPadding : 0) + mContentMarginTop + mContentMarginBottom; if (mLayoutActionVertical) nonContentHeight += mActionOuterHeight * visibleActions; else nonContentHeight += (visibleActions > 0) ? mActionOuterHeight : 0; if (height == ViewGroup.LayoutParams.WRAP_CONTENT) height = Math.min(maxHeight, contentHeight + nonContentHeight); if (mContent != null) mContent.measure( View.MeasureSpec.makeMeasureSpec(width - mContentMarginLeft - mContentMarginRight, View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(height - nonContentHeight, View.MeasureSpec.EXACTLY)); setMeasuredDimension(width + getPaddingLeft() + getPaddingRight(), height + getPaddingTop() + getPaddingBottom()); } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { int childLeft = 0; int childTop = 0; int childRight = right - left; int childBottom = bottom - top; childLeft += getPaddingLeft(); childTop += getPaddingTop(); childRight -= getPaddingRight(); childBottom -= getPaddingBottom(); if (mTitle.getVisibility() == View.VISIBLE) { if (mIsRtl) mTitle.layout(childRight - mTitle.getMeasuredWidth(), childTop, childRight, childTop + mTitle.getMeasuredHeight()); else mTitle.layout(childLeft, childTop, childLeft + mTitle.getMeasuredWidth(), childTop + mTitle.getMeasuredHeight()); childTop += mTitle.getMeasuredHeight(); } boolean hasAction = mNeutralAction.getVisibility() == View.VISIBLE || mNegativeAction.getVisibility() == View.VISIBLE || mPositiveAction.getVisibility() == View.VISIBLE; if (hasAction) childBottom -= mActionPadding; int temp = (mActionOuterHeight - mActionHeight) / 2; if (hasAction) { if (mLayoutActionVertical) { if (mNeutralAction.getVisibility() == View.VISIBLE) { mNeutralAction.layout(childRight - mActionOuterPadding - mNeutralAction.getMeasuredWidth(), childBottom - mActionOuterHeight + temp, childRight - mActionOuterPadding, childBottom - temp); childBottom -= mActionOuterHeight; } if (mNegativeAction.getVisibility() == View.VISIBLE) { mNegativeAction.layout( childRight - mActionOuterPadding - mNegativeAction.getMeasuredWidth(), childBottom - mActionOuterHeight + temp, childRight - mActionOuterPadding, childBottom - temp); childBottom -= mActionOuterHeight; } if (mPositiveAction.getVisibility() == View.VISIBLE) { mPositiveAction.layout( childRight - mActionOuterPadding - mPositiveAction.getMeasuredWidth(), childBottom - mActionOuterHeight + temp, childRight - mActionOuterPadding, childBottom - temp); childBottom -= mActionOuterHeight; } } else { int actionLeft = childLeft + mActionOuterPadding; int actionRight = childRight - mActionOuterPadding; int actionTop = childBottom - mActionOuterHeight + temp; int actionBottom = childBottom - temp; if (mIsRtl) { if (mPositiveAction.getVisibility() == View.VISIBLE) { mPositiveAction.layout(actionLeft, actionTop, actionLeft + mPositiveAction.getMeasuredWidth(), actionBottom); actionLeft += mPositiveAction.getMeasuredWidth() + mActionPadding; } if (mNegativeAction.getVisibility() == View.VISIBLE) mNegativeAction.layout(actionLeft, actionTop, actionLeft + mNegativeAction.getMeasuredWidth(), actionBottom); if (mNeutralAction.getVisibility() == View.VISIBLE) mNeutralAction.layout(actionRight - mNeutralAction.getMeasuredWidth(), actionTop, actionRight, actionBottom); } else { if (mPositiveAction.getVisibility() == View.VISIBLE) { mPositiveAction.layout(actionRight - mPositiveAction.getMeasuredWidth(), actionTop, actionRight, actionBottom); actionRight -= mPositiveAction.getMeasuredWidth() + mActionPadding; } if (mNegativeAction.getVisibility() == View.VISIBLE) mNegativeAction.layout(actionRight - mNegativeAction.getMeasuredWidth(), actionTop, actionRight, actionBottom); if (mNeutralAction.getVisibility() == View.VISIBLE) mNeutralAction.layout(actionLeft, actionTop, actionLeft + mNeutralAction.getMeasuredWidth(), actionBottom); } childBottom -= mActionOuterHeight; } } mDividerPos = childBottom - mDividerPaint.getStrokeWidth() / 2f; if (mContent != null) mContent.layout(childLeft + mContentMarginLeft, childTop + mContentMarginTop, childRight - mContentMarginRight, childBottom - mContentMarginBottom); } @Override public void draw(Canvas canvas) { super.draw(canvas); if (mShowDivider && (mPositiveAction.getVisibility() == View.VISIBLE || mNegativeAction.getVisibility() == View.VISIBLE || mNeutralAction.getVisibility() == View.VISIBLE)) canvas.drawLine(getPaddingLeft(), mDividerPos, getWidth() - getPaddingRight(), mDividerPos, mDividerPaint); } } public static class Builder implements DialogFragment.Builder, Parcelable { protected int mStyleId; protected int mContentViewId; protected CharSequence mTitle; protected CharSequence mPositive; protected CharSequence mNegative; protected CharSequence mNeutral; protected Dialog mDialog; public Builder() { this(R.style.Material_App_Dialog_Light); } public Builder(int styleId) { mStyleId = styleId; } public Builder style(int styleId) { mStyleId = styleId; return this; } public Builder contentView(int layoutId) { mContentViewId = layoutId; return this; } public Builder title(CharSequence title) { mTitle = title; return this; } public Builder positiveAction(CharSequence action) { mPositive = action; return this; } public Builder negativeAction(CharSequence action) { mNegative = action; return this; } public Builder neutralAction(CharSequence action) { mNeutral = action; return this; } public Dialog getDialog() { return mDialog; } @Override public void onPositiveActionClicked(DialogFragment fragment) { fragment.dismiss(); } @Override public void onNegativeActionClicked(DialogFragment fragment) { fragment.dismiss(); } @Override public void onNeutralActionClicked(DialogFragment fragment) { fragment.dismiss(); } @Override public void onCancel(DialogInterface dialog) { } @Override public void onDismiss(DialogInterface dialog) { } @Override public Dialog build(Context context) { mDialog = onBuild(context, mStyleId); mDialog.title(mTitle).positiveAction(mPositive).negativeAction(mNegative).neutralAction(mNeutral); if (mContentViewId != 0) mDialog.contentView(mContentViewId); onBuildDone(mDialog); return mDialog; } /** * Get a appropriate Dialog instance will be used for styling later. * Child class should override this function to return appropriate Dialog instance. * If you want to apply styling to dialog, or get content view, you should do it in {@link #onBuildDone(Dialog)} * @param context A Context instance. * @param styleId The resourceId of Dialog's style. * @return A Dialog instance will be used for styling later. */ protected Dialog onBuild(Context context, int styleId) { return new Dialog(context, styleId); } /** * This function will be called after Builder done apply styling to Dialog. * @param dialog The Dialog instance. */ protected void onBuildDone(Dialog dialog) { } protected Builder(Parcel in) { mStyleId = in.readInt(); mContentViewId = in.readInt(); mTitle = (CharSequence) in.readParcelable(null); mPositive = (CharSequence) in.readParcelable(null); mNegative = (CharSequence) in.readParcelable(null); mNeutral = (CharSequence) in.readParcelable(null); onReadFromParcel(in); } /** * Child class should override this function and read back any saved attributes. * All read methods should be called after super.onReadFromParcel() call to keep the order. */ protected void onReadFromParcel(Parcel in) { } /** * Child class should override this function and write down all attributes will be saved. * All write methods should be called after super.onWriteToParcel() call to keep the order. */ protected void onWriteToParcel(Parcel dest, int flags) { } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeInt(mStyleId); dest.writeInt(mContentViewId); dest.writeValue(mTitle); dest.writeValue(mPositive); dest.writeValue(mNegative); dest.writeValue(mNeutral); onWriteToParcel(dest, flags); } @Override public int describeContents() { return 0; } 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]; } }; } }