AlertController.java revision acaff9dcb3cefc4f47ba16ca1168d8537b3a24e6
1/* 2 * Copyright (C) 2015 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package android.support.v7.app; 18 19import android.content.Context; 20import android.content.DialogInterface; 21import android.content.res.TypedArray; 22import android.database.Cursor; 23import android.graphics.drawable.Drawable; 24import android.os.Build; 25import android.os.Handler; 26import android.os.Message; 27import android.support.annotation.Nullable; 28import android.support.v4.view.ViewCompat; 29import android.support.v4.widget.NestedScrollView; 30import android.support.v7.appcompat.R; 31import android.text.TextUtils; 32import android.util.TypedValue; 33import android.view.KeyEvent; 34import android.view.LayoutInflater; 35import android.view.View; 36import android.view.ViewGroup; 37import android.view.ViewGroup.LayoutParams; 38import android.view.ViewParent; 39import android.view.ViewStub; 40import android.view.Window; 41import android.view.WindowManager; 42import android.widget.AbsListView; 43import android.widget.AdapterView; 44import android.widget.AdapterView.OnItemClickListener; 45import android.widget.ArrayAdapter; 46import android.widget.Button; 47import android.widget.CheckedTextView; 48import android.widget.CursorAdapter; 49import android.widget.FrameLayout; 50import android.widget.ImageView; 51import android.widget.LinearLayout; 52import android.widget.ListAdapter; 53import android.widget.ListView; 54import android.widget.SimpleCursorAdapter; 55import android.widget.TextView; 56 57import java.lang.ref.WeakReference; 58 59import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 60 61class AlertController { 62 private final Context mContext; 63 private final AppCompatDialog mDialog; 64 private final Window mWindow; 65 66 private CharSequence mTitle; 67 private CharSequence mMessage; 68 private ListView mListView; 69 private View mView; 70 71 private int mViewLayoutResId; 72 73 private int mViewSpacingLeft; 74 private int mViewSpacingTop; 75 private int mViewSpacingRight; 76 private int mViewSpacingBottom; 77 private boolean mViewSpacingSpecified = false; 78 79 private Button mButtonPositive; 80 private CharSequence mButtonPositiveText; 81 private Message mButtonPositiveMessage; 82 83 private Button mButtonNegative; 84 private CharSequence mButtonNegativeText; 85 private Message mButtonNegativeMessage; 86 87 private Button mButtonNeutral; 88 private CharSequence mButtonNeutralText; 89 private Message mButtonNeutralMessage; 90 91 private NestedScrollView mScrollView; 92 93 private int mIconId = 0; 94 private Drawable mIcon; 95 96 private ImageView mIconView; 97 private TextView mTitleView; 98 private TextView mMessageView; 99 private View mCustomTitleView; 100 101 private ListAdapter mAdapter; 102 103 private int mCheckedItem = -1; 104 105 private int mAlertDialogLayout; 106 private int mButtonPanelSideLayout; 107 private int mListLayout; 108 private int mMultiChoiceItemLayout; 109 private int mSingleChoiceItemLayout; 110 private int mListItemLayout; 111 112 private int mButtonPanelLayoutHint = AlertDialog.LAYOUT_HINT_NONE; 113 114 private Handler mHandler; 115 116 private final View.OnClickListener mButtonHandler = new View.OnClickListener() { 117 @Override 118 public void onClick(View v) { 119 final Message m; 120 if (v == mButtonPositive && mButtonPositiveMessage != null) { 121 m = Message.obtain(mButtonPositiveMessage); 122 } else if (v == mButtonNegative && mButtonNegativeMessage != null) { 123 m = Message.obtain(mButtonNegativeMessage); 124 } else if (v == mButtonNeutral && mButtonNeutralMessage != null) { 125 m = Message.obtain(mButtonNeutralMessage); 126 } else { 127 m = null; 128 } 129 130 if (m != null) { 131 m.sendToTarget(); 132 } 133 134 // Post a message so we dismiss after the above handlers are executed 135 mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialog) 136 .sendToTarget(); 137 } 138 }; 139 140 private static final class ButtonHandler extends Handler { 141 // Button clicks have Message.what as the BUTTON{1,2,3} constant 142 private static final int MSG_DISMISS_DIALOG = 1; 143 144 private WeakReference<DialogInterface> mDialog; 145 146 public ButtonHandler(DialogInterface dialog) { 147 mDialog = new WeakReference<>(dialog); 148 } 149 150 @Override 151 public void handleMessage(Message msg) { 152 switch (msg.what) { 153 154 case DialogInterface.BUTTON_POSITIVE: 155 case DialogInterface.BUTTON_NEGATIVE: 156 case DialogInterface.BUTTON_NEUTRAL: 157 ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what); 158 break; 159 160 case MSG_DISMISS_DIALOG: 161 ((DialogInterface) msg.obj).dismiss(); 162 } 163 } 164 } 165 166 public AlertController(Context context, AppCompatDialog di, Window window) { 167 mContext = context; 168 mDialog = di; 169 mWindow = window; 170 mHandler = new ButtonHandler(di); 171 172 final TypedArray a = context.obtainStyledAttributes(null, R.styleable.AlertDialog, 173 R.attr.alertDialogStyle, 0); 174 175 mAlertDialogLayout = a.getResourceId(R.styleable.AlertDialog_android_layout, 0); 176 mButtonPanelSideLayout = a.getResourceId(R.styleable.AlertDialog_buttonPanelSideLayout, 0); 177 178 mListLayout = a.getResourceId(R.styleable.AlertDialog_listLayout, 0); 179 mMultiChoiceItemLayout = a.getResourceId(R.styleable.AlertDialog_multiChoiceItemLayout, 0); 180 mSingleChoiceItemLayout = a 181 .getResourceId(R.styleable.AlertDialog_singleChoiceItemLayout, 0); 182 mListItemLayout = a.getResourceId(R.styleable.AlertDialog_listItemLayout, 0); 183 184 a.recycle(); 185 } 186 187 static boolean canTextInput(View v) { 188 if (v.onCheckIsTextEditor()) { 189 return true; 190 } 191 192 if (!(v instanceof ViewGroup)) { 193 return false; 194 } 195 196 ViewGroup vg = (ViewGroup) v; 197 int i = vg.getChildCount(); 198 while (i > 0) { 199 i--; 200 v = vg.getChildAt(i); 201 if (canTextInput(v)) { 202 return true; 203 } 204 } 205 206 return false; 207 } 208 209 public void installContent() { 210 /* We use a custom title so never request a window title */ 211 mDialog.supportRequestWindowFeature(Window.FEATURE_NO_TITLE); 212 final int contentView = selectContentView(); 213 mDialog.setContentView(contentView); 214 setupView(); 215 } 216 217 private int selectContentView() { 218 if (mButtonPanelSideLayout == 0) { 219 return mAlertDialogLayout; 220 } 221 if (mButtonPanelLayoutHint == AlertDialog.LAYOUT_HINT_SIDE) { 222 return mButtonPanelSideLayout; 223 } 224 return mAlertDialogLayout; 225 } 226 227 public void setTitle(CharSequence title) { 228 mTitle = title; 229 if (mTitleView != null) { 230 mTitleView.setText(title); 231 } 232 } 233 234 /** 235 * @see AlertDialog.Builder#setCustomTitle(View) 236 */ 237 public void setCustomTitle(View customTitleView) { 238 mCustomTitleView = customTitleView; 239 } 240 241 public void setMessage(CharSequence message) { 242 mMessage = message; 243 if (mMessageView != null) { 244 mMessageView.setText(message); 245 } 246 } 247 248 /** 249 * Set the view resource to display in the dialog. 250 */ 251 public void setView(int layoutResId) { 252 mView = null; 253 mViewLayoutResId = layoutResId; 254 mViewSpacingSpecified = false; 255 } 256 257 /** 258 * Set the view to display in the dialog. 259 */ 260 public void setView(View view) { 261 mView = view; 262 mViewLayoutResId = 0; 263 mViewSpacingSpecified = false; 264 } 265 266 /** 267 * Set the view to display in the dialog along with the spacing around that view 268 */ 269 public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight, 270 int viewSpacingBottom) { 271 mView = view; 272 mViewLayoutResId = 0; 273 mViewSpacingSpecified = true; 274 mViewSpacingLeft = viewSpacingLeft; 275 mViewSpacingTop = viewSpacingTop; 276 mViewSpacingRight = viewSpacingRight; 277 mViewSpacingBottom = viewSpacingBottom; 278 } 279 280 /** 281 * Sets a hint for the best button panel layout. 282 */ 283 public void setButtonPanelLayoutHint(int layoutHint) { 284 mButtonPanelLayoutHint = layoutHint; 285 } 286 287 /** 288 * Sets a click listener or a message to be sent when the button is clicked. 289 * You only need to pass one of {@code listener} or {@code msg}. 290 * 291 * @param whichButton Which button, can be one of 292 * {@link DialogInterface#BUTTON_POSITIVE}, 293 * {@link DialogInterface#BUTTON_NEGATIVE}, or 294 * {@link DialogInterface#BUTTON_NEUTRAL} 295 * @param text The text to display in positive button. 296 * @param listener The {@link DialogInterface.OnClickListener} to use. 297 * @param msg The {@link Message} to be sent when clicked. 298 */ 299 public void setButton(int whichButton, CharSequence text, 300 DialogInterface.OnClickListener listener, Message msg) { 301 302 if (msg == null && listener != null) { 303 msg = mHandler.obtainMessage(whichButton, listener); 304 } 305 306 switch (whichButton) { 307 308 case DialogInterface.BUTTON_POSITIVE: 309 mButtonPositiveText = text; 310 mButtonPositiveMessage = msg; 311 break; 312 313 case DialogInterface.BUTTON_NEGATIVE: 314 mButtonNegativeText = text; 315 mButtonNegativeMessage = msg; 316 break; 317 318 case DialogInterface.BUTTON_NEUTRAL: 319 mButtonNeutralText = text; 320 mButtonNeutralMessage = msg; 321 break; 322 323 default: 324 throw new IllegalArgumentException("Button does not exist"); 325 } 326 } 327 328 /** 329 * Specifies the icon to display next to the alert title. 330 * 331 * @param resId the resource identifier of the drawable to use as the icon, 332 * or 0 for no icon 333 */ 334 public void setIcon(int resId) { 335 mIcon = null; 336 mIconId = resId; 337 338 if (mIconView != null) { 339 if (resId != 0) { 340 mIconView.setVisibility(View.VISIBLE); 341 mIconView.setImageResource(mIconId); 342 } else { 343 mIconView.setVisibility(View.GONE); 344 } 345 } 346 } 347 348 /** 349 * Specifies the icon to display next to the alert title. 350 * 351 * @param icon the drawable to use as the icon or null for no icon 352 */ 353 public void setIcon(Drawable icon) { 354 mIcon = icon; 355 mIconId = 0; 356 357 if (mIconView != null) { 358 if (icon != null) { 359 mIconView.setImageDrawable(icon); 360 } else { 361 mIconView.setVisibility(View.GONE); 362 } 363 } 364 } 365 366 /** 367 * @param attrId the attributeId of the theme-specific drawable 368 * to resolve the resourceId for. 369 * 370 * @return resId the resourceId of the theme-specific drawable 371 */ 372 public int getIconAttributeResId(int attrId) { 373 TypedValue out = new TypedValue(); 374 mContext.getTheme().resolveAttribute(attrId, out, true); 375 return out.resourceId; 376 } 377 378 public ListView getListView() { 379 return mListView; 380 } 381 382 public Button getButton(int whichButton) { 383 switch (whichButton) { 384 case DialogInterface.BUTTON_POSITIVE: 385 return mButtonPositive; 386 case DialogInterface.BUTTON_NEGATIVE: 387 return mButtonNegative; 388 case DialogInterface.BUTTON_NEUTRAL: 389 return mButtonNeutral; 390 default: 391 return null; 392 } 393 } 394 395 @SuppressWarnings({"UnusedDeclaration"}) 396 public boolean onKeyDown(int keyCode, KeyEvent event) { 397 return mScrollView != null && mScrollView.executeKeyEvent(event); 398 } 399 400 @SuppressWarnings({"UnusedDeclaration"}) 401 public boolean onKeyUp(int keyCode, KeyEvent event) { 402 return mScrollView != null && mScrollView.executeKeyEvent(event); 403 } 404 405 /** 406 * Resolves whether a custom or default panel should be used. Removes the 407 * default panel if a custom panel should be used. If the resolved panel is 408 * a view stub, inflates before returning. 409 * 410 * @param customPanel the custom panel 411 * @param defaultPanel the default panel 412 * @return the panel to use 413 */ 414 @Nullable 415 private ViewGroup resolvePanel(@Nullable View customPanel, @Nullable View defaultPanel) { 416 if (customPanel == null) { 417 // Inflate the default panel, if needed. 418 if (defaultPanel instanceof ViewStub) { 419 defaultPanel = ((ViewStub) defaultPanel).inflate(); 420 } 421 422 return (ViewGroup) defaultPanel; 423 } 424 425 // Remove the default panel entirely. 426 if (defaultPanel != null) { 427 final ViewParent parent = defaultPanel.getParent(); 428 if (parent instanceof ViewGroup) { 429 ((ViewGroup) parent).removeView(defaultPanel); 430 } 431 } 432 433 // Inflate the custom panel, if needed. 434 if (customPanel instanceof ViewStub) { 435 customPanel = ((ViewStub) customPanel).inflate(); 436 } 437 438 return (ViewGroup) customPanel; 439 } 440 441 private void setupView() { 442 final View parentPanel = mWindow.findViewById(R.id.parentPanel); 443 final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel); 444 final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel); 445 final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel); 446 447 // Install custom content before setting up the title or buttons so 448 // that we can handle panel overrides. 449 final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel); 450 setupCustomContent(customPanel); 451 452 final View customTopPanel = customPanel.findViewById(R.id.topPanel); 453 final View customContentPanel = customPanel.findViewById(R.id.contentPanel); 454 final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel); 455 456 // Resolve the correct panels and remove the defaults, if needed. 457 final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel); 458 final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel); 459 final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel); 460 461 setupContent(contentPanel); 462 setupButtons(buttonPanel); 463 setupTitle(topPanel); 464 465 final boolean hasCustomPanel = customPanel != null 466 && customPanel.getVisibility() != View.GONE; 467 final boolean hasTopPanel = topPanel != null 468 && topPanel.getVisibility() != View.GONE; 469 final boolean hasButtonPanel = buttonPanel != null 470 && buttonPanel.getVisibility() != View.GONE; 471 472 // Only display the text spacer if we don't have buttons. 473 if (!hasButtonPanel) { 474 if (contentPanel != null) { 475 final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons); 476 if (spacer != null) { 477 spacer.setVisibility(View.VISIBLE); 478 } 479 } 480 } 481 482 if (hasTopPanel) { 483 // Only clip scrolling content to padding if we have a title. 484 if (mScrollView != null) { 485 mScrollView.setClipToPadding(true); 486 } 487 } 488 489 // Update scroll indicators as needed. 490 if (!hasCustomPanel) { 491 final View content = mListView != null ? mListView : mScrollView; 492 if (content != null) { 493 final int indicators = (hasTopPanel ? ViewCompat.SCROLL_INDICATOR_TOP : 0) 494 | (hasButtonPanel ? ViewCompat.SCROLL_INDICATOR_BOTTOM : 0); 495 setScrollIndicators(contentPanel, content, indicators, 496 ViewCompat.SCROLL_INDICATOR_TOP | ViewCompat.SCROLL_INDICATOR_BOTTOM); 497 } 498 } 499 500 final ListView listView = mListView; 501 if (listView != null && mAdapter != null) { 502 listView.setAdapter(mAdapter); 503 final int checkedItem = mCheckedItem; 504 if (checkedItem > -1) { 505 listView.setItemChecked(checkedItem, true); 506 listView.setSelection(checkedItem); 507 } 508 } 509 } 510 511 private void setScrollIndicators(ViewGroup contentPanel, View content, 512 final int indicators, final int mask) { 513 // Set up scroll indicators (if present). 514 View indicatorUp = mWindow.findViewById(R.id.scrollIndicatorUp); 515 View indicatorDown = mWindow.findViewById(R.id.scrollIndicatorDown); 516 517 if (Build.VERSION.SDK_INT >= 23) { 518 // We're on Marshmallow so can rely on the View APIs 519 ViewCompat.setScrollIndicators(content, indicators, mask); 520 // We can also remove the compat indicator views 521 if (indicatorUp != null) { 522 contentPanel.removeView(indicatorUp); 523 } 524 if (indicatorDown != null) { 525 contentPanel.removeView(indicatorDown); 526 } 527 } else { 528 // First, remove the indicator views if we're not set to use them 529 if (indicatorUp != null && (indicators & ViewCompat.SCROLL_INDICATOR_TOP) == 0) { 530 contentPanel.removeView(indicatorUp); 531 indicatorUp = null; 532 } 533 if (indicatorDown != null && (indicators & ViewCompat.SCROLL_INDICATOR_BOTTOM) == 0) { 534 contentPanel.removeView(indicatorDown); 535 indicatorDown = null; 536 } 537 538 if (indicatorUp != null || indicatorDown != null) { 539 final View top = indicatorUp; 540 final View bottom = indicatorDown; 541 542 if (mMessage != null) { 543 // We're just showing the ScrollView, set up listener. 544 mScrollView.setOnScrollChangeListener( 545 new NestedScrollView.OnScrollChangeListener() { 546 @Override 547 public void onScrollChange(NestedScrollView v, int scrollX, 548 int scrollY, 549 int oldScrollX, int oldScrollY) { 550 manageScrollIndicators(v, top, bottom); 551 } 552 }); 553 // Set up the indicators following layout. 554 mScrollView.post(new Runnable() { 555 @Override 556 public void run() { 557 manageScrollIndicators(mScrollView, top, bottom); 558 } 559 }); 560 } else if (mListView != null) { 561 // We're just showing the AbsListView, set up listener. 562 mListView.setOnScrollListener(new AbsListView.OnScrollListener() { 563 @Override 564 public void onScrollStateChanged(AbsListView view, int scrollState) {} 565 566 @Override 567 public void onScroll(AbsListView v, int firstVisibleItem, 568 int visibleItemCount, int totalItemCount) { 569 manageScrollIndicators(v, top, bottom); 570 } 571 }); 572 // Set up the indicators following layout. 573 mListView.post(new Runnable() { 574 @Override 575 public void run() { 576 manageScrollIndicators(mListView, top, bottom); 577 } 578 }); 579 } else { 580 // We don't have any content to scroll, remove the indicators. 581 if (top != null) { 582 contentPanel.removeView(top); 583 } 584 if (bottom != null) { 585 contentPanel.removeView(bottom); 586 } 587 } 588 } 589 } 590 } 591 592 private void setupCustomContent(ViewGroup customPanel) { 593 final View customView; 594 if (mView != null) { 595 customView = mView; 596 } else if (mViewLayoutResId != 0) { 597 final LayoutInflater inflater = LayoutInflater.from(mContext); 598 customView = inflater.inflate(mViewLayoutResId, customPanel, false); 599 } else { 600 customView = null; 601 } 602 603 final boolean hasCustomView = customView != null; 604 if (!hasCustomView || !canTextInput(customView)) { 605 mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 606 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 607 } 608 609 if (hasCustomView) { 610 final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom); 611 custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT)); 612 613 if (mViewSpacingSpecified) { 614 custom.setPadding( 615 mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom); 616 } 617 618 if (mListView != null) { 619 ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0; 620 } 621 } else { 622 customPanel.setVisibility(View.GONE); 623 } 624 } 625 626 private void setupTitle(ViewGroup topPanel) { 627 if (mCustomTitleView != null) { 628 // Add the custom title view directly to the topPanel layout 629 LayoutParams lp = new LayoutParams( 630 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 631 632 topPanel.addView(mCustomTitleView, 0, lp); 633 634 // Hide the title template 635 View titleTemplate = mWindow.findViewById(R.id.title_template); 636 titleTemplate.setVisibility(View.GONE); 637 } else { 638 mIconView = (ImageView) mWindow.findViewById(android.R.id.icon); 639 640 final boolean hasTextTitle = !TextUtils.isEmpty(mTitle); 641 if (hasTextTitle) { 642 // Display the title if a title is supplied, else hide it. 643 mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle); 644 mTitleView.setText(mTitle); 645 646 // Do this last so that if the user has supplied any icons we 647 // use them instead of the default ones. If the user has 648 // specified 0 then make it disappear. 649 if (mIconId != 0) { 650 mIconView.setImageResource(mIconId); 651 } else if (mIcon != null) { 652 mIconView.setImageDrawable(mIcon); 653 } else { 654 // Apply the padding from the icon to ensure the title is 655 // aligned correctly. 656 mTitleView.setPadding(mIconView.getPaddingLeft(), 657 mIconView.getPaddingTop(), 658 mIconView.getPaddingRight(), 659 mIconView.getPaddingBottom()); 660 mIconView.setVisibility(View.GONE); 661 } 662 } else { 663 // Hide the title template 664 final View titleTemplate = mWindow.findViewById(R.id.title_template); 665 titleTemplate.setVisibility(View.GONE); 666 mIconView.setVisibility(View.GONE); 667 topPanel.setVisibility(View.GONE); 668 } 669 } 670 } 671 672 private void setupContent(ViewGroup contentPanel) { 673 mScrollView = (NestedScrollView) mWindow.findViewById(R.id.scrollView); 674 mScrollView.setFocusable(false); 675 mScrollView.setNestedScrollingEnabled(false); 676 677 // Special case for users that only want to display a String 678 mMessageView = (TextView) contentPanel.findViewById(android.R.id.message); 679 if (mMessageView == null) { 680 return; 681 } 682 683 if (mMessage != null) { 684 mMessageView.setText(mMessage); 685 } else { 686 mMessageView.setVisibility(View.GONE); 687 mScrollView.removeView(mMessageView); 688 689 if (mListView != null) { 690 final ViewGroup scrollParent = (ViewGroup) mScrollView.getParent(); 691 final int childIndex = scrollParent.indexOfChild(mScrollView); 692 scrollParent.removeViewAt(childIndex); 693 scrollParent.addView(mListView, childIndex, 694 new LayoutParams(MATCH_PARENT, MATCH_PARENT)); 695 } else { 696 contentPanel.setVisibility(View.GONE); 697 } 698 } 699 } 700 701 private static void manageScrollIndicators(View v, View upIndicator, View downIndicator) { 702 if (upIndicator != null) { 703 upIndicator.setVisibility( 704 ViewCompat.canScrollVertically(v, -1) ? View.VISIBLE : View.INVISIBLE); 705 } 706 if (downIndicator != null) { 707 downIndicator.setVisibility( 708 ViewCompat.canScrollVertically(v, 1) ? View.VISIBLE : View.INVISIBLE); 709 } 710 } 711 712 private void setupButtons(ViewGroup buttonPanel) { 713 int BIT_BUTTON_POSITIVE = 1; 714 int BIT_BUTTON_NEGATIVE = 2; 715 int BIT_BUTTON_NEUTRAL = 4; 716 int whichButtons = 0; 717 mButtonPositive = (Button) buttonPanel.findViewById(android.R.id.button1); 718 mButtonPositive.setOnClickListener(mButtonHandler); 719 720 if (TextUtils.isEmpty(mButtonPositiveText)) { 721 mButtonPositive.setVisibility(View.GONE); 722 } else { 723 mButtonPositive.setText(mButtonPositiveText); 724 mButtonPositive.setVisibility(View.VISIBLE); 725 whichButtons = whichButtons | BIT_BUTTON_POSITIVE; 726 } 727 728 mButtonNegative = (Button) buttonPanel.findViewById(android.R.id.button2); 729 mButtonNegative.setOnClickListener(mButtonHandler); 730 731 if (TextUtils.isEmpty(mButtonNegativeText)) { 732 mButtonNegative.setVisibility(View.GONE); 733 } else { 734 mButtonNegative.setText(mButtonNegativeText); 735 mButtonNegative.setVisibility(View.VISIBLE); 736 737 whichButtons = whichButtons | BIT_BUTTON_NEGATIVE; 738 } 739 740 mButtonNeutral = (Button) buttonPanel.findViewById(android.R.id.button3); 741 mButtonNeutral.setOnClickListener(mButtonHandler); 742 743 if (TextUtils.isEmpty(mButtonNeutralText)) { 744 mButtonNeutral.setVisibility(View.GONE); 745 } else { 746 mButtonNeutral.setText(mButtonNeutralText); 747 mButtonNeutral.setVisibility(View.VISIBLE); 748 749 whichButtons = whichButtons | BIT_BUTTON_NEUTRAL; 750 } 751 752 final boolean hasButtons = whichButtons != 0; 753 if (!hasButtons) { 754 buttonPanel.setVisibility(View.GONE); 755 } 756 } 757 758 public static class AlertParams { 759 public final Context mContext; 760 public final LayoutInflater mInflater; 761 762 public int mIconId = 0; 763 public Drawable mIcon; 764 public int mIconAttrId = 0; 765 public CharSequence mTitle; 766 public View mCustomTitleView; 767 public CharSequence mMessage; 768 public CharSequence mPositiveButtonText; 769 public DialogInterface.OnClickListener mPositiveButtonListener; 770 public CharSequence mNegativeButtonText; 771 public DialogInterface.OnClickListener mNegativeButtonListener; 772 public CharSequence mNeutralButtonText; 773 public DialogInterface.OnClickListener mNeutralButtonListener; 774 public boolean mCancelable; 775 public DialogInterface.OnCancelListener mOnCancelListener; 776 public DialogInterface.OnDismissListener mOnDismissListener; 777 public DialogInterface.OnKeyListener mOnKeyListener; 778 public CharSequence[] mItems; 779 public ListAdapter mAdapter; 780 public DialogInterface.OnClickListener mOnClickListener; 781 public int mViewLayoutResId; 782 public View mView; 783 public int mViewSpacingLeft; 784 public int mViewSpacingTop; 785 public int mViewSpacingRight; 786 public int mViewSpacingBottom; 787 public boolean mViewSpacingSpecified = false; 788 public boolean[] mCheckedItems; 789 public boolean mIsMultiChoice; 790 public boolean mIsSingleChoice; 791 public int mCheckedItem = -1; 792 public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener; 793 public Cursor mCursor; 794 public String mLabelColumn; 795 public String mIsCheckedColumn; 796 public boolean mForceInverseBackground; 797 public AdapterView.OnItemSelectedListener mOnItemSelectedListener; 798 public OnPrepareListViewListener mOnPrepareListViewListener; 799 public boolean mRecycleOnMeasure = true; 800 801 /** 802 * Interface definition for a callback to be invoked before the ListView 803 * will be bound to an adapter. 804 */ 805 public interface OnPrepareListViewListener { 806 807 /** 808 * Called before the ListView is bound to an adapter. 809 * @param listView The ListView that will be shown in the dialog. 810 */ 811 void onPrepareListView(ListView listView); 812 } 813 814 public AlertParams(Context context) { 815 mContext = context; 816 mCancelable = true; 817 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 818 } 819 820 public void apply(AlertController dialog) { 821 if (mCustomTitleView != null) { 822 dialog.setCustomTitle(mCustomTitleView); 823 } else { 824 if (mTitle != null) { 825 dialog.setTitle(mTitle); 826 } 827 if (mIcon != null) { 828 dialog.setIcon(mIcon); 829 } 830 if (mIconId != 0) { 831 dialog.setIcon(mIconId); 832 } 833 if (mIconAttrId != 0) { 834 dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId)); 835 } 836 } 837 if (mMessage != null) { 838 dialog.setMessage(mMessage); 839 } 840 if (mPositiveButtonText != null) { 841 dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText, 842 mPositiveButtonListener, null); 843 } 844 if (mNegativeButtonText != null) { 845 dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText, 846 mNegativeButtonListener, null); 847 } 848 if (mNeutralButtonText != null) { 849 dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText, 850 mNeutralButtonListener, null); 851 } 852 // For a list, the client can either supply an array of items or an 853 // adapter or a cursor 854 if ((mItems != null) || (mCursor != null) || (mAdapter != null)) { 855 createListView(dialog); 856 } 857 if (mView != null) { 858 if (mViewSpacingSpecified) { 859 dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, 860 mViewSpacingBottom); 861 } else { 862 dialog.setView(mView); 863 } 864 } else if (mViewLayoutResId != 0) { 865 dialog.setView(mViewLayoutResId); 866 } 867 868 /* 869 dialog.setCancelable(mCancelable); 870 dialog.setOnCancelListener(mOnCancelListener); 871 if (mOnKeyListener != null) { 872 dialog.setOnKeyListener(mOnKeyListener); 873 } 874 */ 875 } 876 877 private void createListView(final AlertController dialog) { 878 final ListView listView = (ListView) mInflater.inflate(dialog.mListLayout, null); 879 final ListAdapter adapter; 880 881 if (mIsMultiChoice) { 882 if (mCursor == null) { 883 adapter = new ArrayAdapter<CharSequence>( 884 mContext, dialog.mMultiChoiceItemLayout, android.R.id.text1, mItems) { 885 @Override 886 public View getView(int position, View convertView, ViewGroup parent) { 887 View view = super.getView(position, convertView, parent); 888 if (mCheckedItems != null) { 889 boolean isItemChecked = mCheckedItems[position]; 890 if (isItemChecked) { 891 listView.setItemChecked(position, true); 892 } 893 } 894 return view; 895 } 896 }; 897 } else { 898 adapter = new CursorAdapter(mContext, mCursor, false) { 899 private final int mLabelIndex; 900 private final int mIsCheckedIndex; 901 902 { 903 final Cursor cursor = getCursor(); 904 mLabelIndex = cursor.getColumnIndexOrThrow(mLabelColumn); 905 mIsCheckedIndex = cursor.getColumnIndexOrThrow(mIsCheckedColumn); 906 } 907 908 @Override 909 public void bindView(View view, Context context, Cursor cursor) { 910 CheckedTextView text = (CheckedTextView) view.findViewById( 911 android.R.id.text1); 912 text.setText(cursor.getString(mLabelIndex)); 913 listView.setItemChecked(cursor.getPosition(), 914 cursor.getInt(mIsCheckedIndex) == 1); 915 } 916 917 @Override 918 public View newView(Context context, Cursor cursor, ViewGroup parent) { 919 return mInflater.inflate(dialog.mMultiChoiceItemLayout, 920 parent, false); 921 } 922 923 }; 924 } 925 } else { 926 final int layout; 927 if (mIsSingleChoice) { 928 layout = dialog.mSingleChoiceItemLayout; 929 } else { 930 layout = dialog.mListItemLayout; 931 } 932 933 if (mCursor != null) { 934 adapter = new SimpleCursorAdapter(mContext, layout, mCursor, 935 new String[] { mLabelColumn }, new int[] { android.R.id.text1 }); 936 } else if (mAdapter != null) { 937 adapter = mAdapter; 938 } else { 939 adapter = new CheckedItemAdapter(mContext, layout, android.R.id.text1, mItems); 940 } 941 } 942 943 if (mOnPrepareListViewListener != null) { 944 mOnPrepareListViewListener.onPrepareListView(listView); 945 } 946 947 /* Don't directly set the adapter on the ListView as we might 948 * want to add a footer to the ListView later. 949 */ 950 dialog.mAdapter = adapter; 951 dialog.mCheckedItem = mCheckedItem; 952 953 if (mOnClickListener != null) { 954 listView.setOnItemClickListener(new OnItemClickListener() { 955 @Override 956 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 957 mOnClickListener.onClick(dialog.mDialog, position); 958 if (!mIsSingleChoice) { 959 dialog.mDialog.dismiss(); 960 } 961 } 962 }); 963 } else if (mOnCheckboxClickListener != null) { 964 listView.setOnItemClickListener(new OnItemClickListener() { 965 @Override 966 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 967 if (mCheckedItems != null) { 968 mCheckedItems[position] = listView.isItemChecked(position); 969 } 970 mOnCheckboxClickListener.onClick( 971 dialog.mDialog, position, listView.isItemChecked(position)); 972 } 973 }); 974 } 975 976 // Attach a given OnItemSelectedListener to the ListView 977 if (mOnItemSelectedListener != null) { 978 listView.setOnItemSelectedListener(mOnItemSelectedListener); 979 } 980 981 if (mIsSingleChoice) { 982 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 983 } else if (mIsMultiChoice) { 984 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 985 } 986 dialog.mListView = listView; 987 } 988 } 989 990 private static class CheckedItemAdapter extends ArrayAdapter<CharSequence> { 991 public CheckedItemAdapter(Context context, int resource, int textViewResourceId, 992 CharSequence[] objects) { 993 super(context, resource, textViewResourceId, objects); 994 } 995 996 @Override 997 public boolean hasStableIds() { 998 return true; 999 } 1000 1001 @Override 1002 public long getItemId(int position) { 1003 return position; 1004 } 1005 } 1006} 1007