package com.orhanobut.dialogplus; import android.app.Activity; import android.content.Context; import android.view.*; import android.view.animation.Animation; import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.FrameLayout; import androidx.annotation.NonNull; import androidx.annotation.Nullable; public class DialogPlus { private static final int INVALID = -1; /** * DialogPlus base layout root view */ private final ViewGroup rootView; /** * DialogPlus content container which is a different layout rather than base layout */ private final ViewGroup contentContainer; /** * Determines whether dialog should be dismissed by back button or touch in the black overlay */ private final boolean isCancelable; /** * Determines whether dialog is showing dismissing animation and avoid to repeat it */ private boolean isDismissing; /** * Listener for the user to take action by clicking any item */ private final OnItemClickListener onItemClickListener; /** * Listener for the user to take action by clicking views in header or footer */ private final OnClickListener onClickListener; /** * Listener to notify the user that dialog has been dismissed */ private final OnDismissListener onDismissListener; /** * Listener to notify the user that dialog has been canceled */ private final OnCancelListener onCancelListener; /** * Listener to notify back press */ private final OnBackPressListener onBackPressListener; /** * Content */ private final Holder holder; /** * basically activity root view */ private final ViewGroup decorView; private final Animation outAnim; private final Animation inAnim; DialogPlus(DialogPlusBuilder builder) { LayoutInflater layoutInflater = LayoutInflater.from(builder.getContext()); Activity activity = (Activity) builder.getContext(); holder = builder.getHolder(); onItemClickListener = builder.getOnItemClickListener(); onClickListener = builder.getOnClickListener(); onDismissListener = builder.getOnDismissListener(); onCancelListener = builder.getOnCancelListener(); onBackPressListener = builder.getOnBackPressListener(); isCancelable = builder.isCancelable(); /* * Avoid getting directly from the decor view because by doing that we are overlapping the black soft key on * nexus device. I think it should be tested on different devices but in my opinion is the way to go. * @link http://stackoverflow.com/questions/4486034/get-root-view-from-current-activity */ decorView = activity.getWindow().getDecorView().findViewById(android.R.id.content); rootView = (ViewGroup) layoutInflater.inflate(R.layout.base_container, decorView, false); rootView.setLayoutParams(builder.getOutmostLayoutParams()); View outmostView = rootView.findViewById(R.id.dialogplus_outmost_container); outmostView.setBackgroundResource(builder.getOverlayBackgroundResource()); contentContainer = rootView.findViewById(R.id.dialogplus_content_container); contentContainer.setLayoutParams(builder.getContentParams()); outAnim = builder.getOutAnimation(); inAnim = builder.getInAnimation(); initContentView( layoutInflater, builder.getHeaderView(), builder.isFixedHeader(), builder.getFooterView(), builder.isFixedFooter(), builder.getAdapter(), builder.getContentPadding(), builder.getContentMargin() ); initCancelable(); if (builder.isExpanded()) { initExpandAnimator(activity, builder.getDefaultContentHeight(), builder.getContentParams().gravity); } } /** * Creates a new dialog builder */ public static DialogPlusBuilder newDialog(@NonNull Context context) { return new DialogPlusBuilder(context); } /** * Displays the dialog if it is not shown already. */ public void show() { if (isShowing()) { return; } onAttached(rootView); } /** * Checks if the dialog is shown */ public boolean isShowing() { View view = decorView.findViewById(R.id.dialogplus_outmost_container); return view != null; } /** * Dismisses the displayed dialog. */ public void dismiss() { if (isDismissing) { return; } outAnim.setAnimationListener(new Animation.AnimationListener() { @Override public void onAnimationStart(Animation animation) { } @Override public void onAnimationEnd(Animation animation) { decorView.post(new Runnable() { @Override public void run() { decorView.removeView(rootView); isDismissing = false; if (onDismissListener != null) { onDismissListener.onDismiss(DialogPlus.this); } } }); } @Override public void onAnimationRepeat(Animation animation) { } }); contentContainer.startAnimation(outAnim); isDismissing = true; } /** * Checks the given resource id and return the corresponding view if it exists. * * @return null if it is not found */ @Nullable public View findViewById(int resourceId) { return contentContainer.findViewById(resourceId); } /** * Returns header view if it was set. */ @Nullable public View getHeaderView() { return holder.getHeader(); } /** * Returns footer view if it was set. */ @Nullable public View getFooterView() { return holder.getFooter(); } /** * Returns holder view. */ @NonNull public View getHolderView() { return holder.getInflatedView(); } private void initExpandAnimator(Activity activity, int defaultHeight, int gravity) { Display display = activity.getWindowManager().getDefaultDisplay(); int displayHeight = display.getHeight() - Utils.getStatusBarHeight(activity); if (defaultHeight == 0) { defaultHeight = (displayHeight * 2) / 5; } final View view = holder.getInflatedView(); if (!(view instanceof AbsListView)) { return; } final AbsListView absListView = (AbsListView) view; view.setOnTouchListener(ExpandTouchListener.newListener( activity, absListView, contentContainer, gravity, displayHeight, defaultHeight )); } /** * It is called in order to create content */ private void initContentView(LayoutInflater inflater, View header, boolean fixedHeader, View footer, boolean fixedFooter, BaseAdapter adapter, int[] padding, int[] margin) { View contentView = createView(inflater, header, fixedHeader, footer, fixedFooter, adapter); FrameLayout.LayoutParams params = new FrameLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT ); params.setMargins(margin[0], margin[1], margin[2], margin[3]); contentView.setLayoutParams(params); getHolderView().setPadding(padding[0], padding[1], padding[2], padding[3]); contentContainer.addView(contentView); } /** * It is called to set whether the dialog is cancellable by pressing back button or * touching the black overlay */ private void initCancelable() { if (!isCancelable) { return; } View view = rootView.findViewById(R.id.dialogplus_outmost_container); view.setOnTouchListener(onCancelableTouchListener); } /** * it is called when the content view is created * * @param inflater used to inflate the content of the dialog * @return any view which is passed */ private View createView(LayoutInflater inflater, @Nullable View headerView, boolean fixedHeader, @Nullable View footerView, boolean fixedFooter, BaseAdapter adapter) { View view = holder.getView(inflater, rootView); if (holder instanceof ViewHolder) { assignClickListenerRecursively(view); } if (headerView != null) { assignClickListenerRecursively(headerView); holder.addHeader(headerView, fixedHeader); } if (footerView != null) { assignClickListenerRecursively(footerView); holder.addFooter(footerView, fixedFooter); } if (adapter != null && holder instanceof HolderAdapter) { HolderAdapter holderAdapter = (HolderAdapter) holder; holderAdapter.setAdapter(adapter); holderAdapter.setOnItemClickListener(new OnHolderListener() { @Override public void onItemClick(@NonNull Object item, @NonNull View view, int position) { if (onItemClickListener == null) { return; } onItemClickListener.onItemClick(DialogPlus.this, item, view, position); } }); } return view; } /** * Loop among the views in the hierarchy and assign listener to them */ private void assignClickListenerRecursively(View parent) { if (parent == null) { return; } if (parent instanceof ViewGroup) { ViewGroup viewGroup = (ViewGroup) parent; int childCount = viewGroup.getChildCount(); for (int i = childCount - 1; i >= 0; i--) { View child = viewGroup.getChildAt(i); assignClickListenerRecursively(child); } } setClickListener(parent); } /** * It is used to setListener on view that have a valid id associated */ private void setClickListener(View view) { if (view.getId() == INVALID) { return; } //adapterview does not support click listener if (view instanceof AdapterView) { return; } view.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (onClickListener == null) { return; } onClickListener.onClick(DialogPlus.this, v); } }); } /** * It is called when the show() method is called * * @param view is the dialog plus view */ private void onAttached(View view) { decorView.addView(view); contentContainer.startAnimation(inAnim); contentContainer.requestFocus(); holder.setOnKeyListener(new View.OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { switch (event.getAction()) { case KeyEvent.ACTION_UP: if (keyCode == KeyEvent.KEYCODE_BACK) { if (onBackPressListener != null) { onBackPressListener.onBackPressed(DialogPlus.this); } if (isCancelable) { onBackPressed(DialogPlus.this); } return true; } break; default: break; } return false; } }); } /** * Invoked when back button is pressed. Automatically dismiss the dialog. */ public void onBackPressed(@NonNull DialogPlus dialogPlus) { if (onCancelListener != null) { onCancelListener.onCancel(DialogPlus.this); } dismiss(); } /** * Called when the user touch on black overlay in order to dismiss the dialog */ private final View.OnTouchListener onCancelableTouchListener = new View.OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { if (event.getAction() == MotionEvent.ACTION_DOWN) { if (onCancelListener != null) { onCancelListener.onCancel(DialogPlus.this); } dismiss(); } return false; } }; }