AlertController.java revision 58c14e5c83aa8f836aff6e48ad90e6976948f1ac
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.setVisibility(View.VISIBLE); 360 mIconView.setImageDrawable(icon); 361 } else { 362 mIconView.setVisibility(View.GONE); 363 } 364 } 365 } 366 367 /** 368 * @param attrId the attributeId of the theme-specific drawable 369 * to resolve the resourceId for. 370 * 371 * @return resId the resourceId of the theme-specific drawable 372 */ 373 public int getIconAttributeResId(int attrId) { 374 TypedValue out = new TypedValue(); 375 mContext.getTheme().resolveAttribute(attrId, out, true); 376 return out.resourceId; 377 } 378 379 public ListView getListView() { 380 return mListView; 381 } 382 383 public Button getButton(int whichButton) { 384 switch (whichButton) { 385 case DialogInterface.BUTTON_POSITIVE: 386 return mButtonPositive; 387 case DialogInterface.BUTTON_NEGATIVE: 388 return mButtonNegative; 389 case DialogInterface.BUTTON_NEUTRAL: 390 return mButtonNeutral; 391 default: 392 return null; 393 } 394 } 395 396 @SuppressWarnings({"UnusedDeclaration"}) 397 public boolean onKeyDown(int keyCode, KeyEvent event) { 398 return mScrollView != null && mScrollView.executeKeyEvent(event); 399 } 400 401 @SuppressWarnings({"UnusedDeclaration"}) 402 public boolean onKeyUp(int keyCode, KeyEvent event) { 403 return mScrollView != null && mScrollView.executeKeyEvent(event); 404 } 405 406 /** 407 * Resolves whether a custom or default panel should be used. Removes the 408 * default panel if a custom panel should be used. If the resolved panel is 409 * a view stub, inflates before returning. 410 * 411 * @param customPanel the custom panel 412 * @param defaultPanel the default panel 413 * @return the panel to use 414 */ 415 @Nullable 416 private ViewGroup resolvePanel(@Nullable View customPanel, @Nullable View defaultPanel) { 417 if (customPanel == null) { 418 // Inflate the default panel, if needed. 419 if (defaultPanel instanceof ViewStub) { 420 defaultPanel = ((ViewStub) defaultPanel).inflate(); 421 } 422 423 return (ViewGroup) defaultPanel; 424 } 425 426 // Remove the default panel entirely. 427 if (defaultPanel != null) { 428 final ViewParent parent = defaultPanel.getParent(); 429 if (parent instanceof ViewGroup) { 430 ((ViewGroup) parent).removeView(defaultPanel); 431 } 432 } 433 434 // Inflate the custom panel, if needed. 435 if (customPanel instanceof ViewStub) { 436 customPanel = ((ViewStub) customPanel).inflate(); 437 } 438 439 return (ViewGroup) customPanel; 440 } 441 442 private void setupView() { 443 final View parentPanel = mWindow.findViewById(R.id.parentPanel); 444 final View defaultTopPanel = parentPanel.findViewById(R.id.topPanel); 445 final View defaultContentPanel = parentPanel.findViewById(R.id.contentPanel); 446 final View defaultButtonPanel = parentPanel.findViewById(R.id.buttonPanel); 447 448 // Install custom content before setting up the title or buttons so 449 // that we can handle panel overrides. 450 final ViewGroup customPanel = (ViewGroup) parentPanel.findViewById(R.id.customPanel); 451 setupCustomContent(customPanel); 452 453 final View customTopPanel = customPanel.findViewById(R.id.topPanel); 454 final View customContentPanel = customPanel.findViewById(R.id.contentPanel); 455 final View customButtonPanel = customPanel.findViewById(R.id.buttonPanel); 456 457 // Resolve the correct panels and remove the defaults, if needed. 458 final ViewGroup topPanel = resolvePanel(customTopPanel, defaultTopPanel); 459 final ViewGroup contentPanel = resolvePanel(customContentPanel, defaultContentPanel); 460 final ViewGroup buttonPanel = resolvePanel(customButtonPanel, defaultButtonPanel); 461 462 setupContent(contentPanel); 463 setupButtons(buttonPanel); 464 setupTitle(topPanel); 465 466 final boolean hasCustomPanel = customPanel != null 467 && customPanel.getVisibility() != View.GONE; 468 final boolean hasTopPanel = topPanel != null 469 && topPanel.getVisibility() != View.GONE; 470 final boolean hasButtonPanel = buttonPanel != null 471 && buttonPanel.getVisibility() != View.GONE; 472 473 // Only display the text spacer if we don't have buttons. 474 if (!hasButtonPanel) { 475 if (contentPanel != null) { 476 final View spacer = contentPanel.findViewById(R.id.textSpacerNoButtons); 477 if (spacer != null) { 478 spacer.setVisibility(View.VISIBLE); 479 } 480 } 481 } 482 483 if (hasTopPanel) { 484 // Only clip scrolling content to padding if we have a title. 485 if (mScrollView != null) { 486 mScrollView.setClipToPadding(true); 487 } 488 } 489 490 // Update scroll indicators as needed. 491 if (!hasCustomPanel) { 492 final View content = mListView != null ? mListView : mScrollView; 493 if (content != null) { 494 final int indicators = (hasTopPanel ? ViewCompat.SCROLL_INDICATOR_TOP : 0) 495 | (hasButtonPanel ? ViewCompat.SCROLL_INDICATOR_BOTTOM : 0); 496 setScrollIndicators(contentPanel, content, indicators, 497 ViewCompat.SCROLL_INDICATOR_TOP | ViewCompat.SCROLL_INDICATOR_BOTTOM); 498 } 499 } 500 501 final ListView listView = mListView; 502 if (listView != null && mAdapter != null) { 503 listView.setAdapter(mAdapter); 504 final int checkedItem = mCheckedItem; 505 if (checkedItem > -1) { 506 listView.setItemChecked(checkedItem, true); 507 listView.setSelection(checkedItem); 508 } 509 } 510 } 511 512 private void setScrollIndicators(ViewGroup contentPanel, View content, 513 final int indicators, final int mask) { 514 // Set up scroll indicators (if present). 515 View indicatorUp = mWindow.findViewById(R.id.scrollIndicatorUp); 516 View indicatorDown = mWindow.findViewById(R.id.scrollIndicatorDown); 517 518 if (Build.VERSION.SDK_INT >= 23) { 519 // We're on Marshmallow so can rely on the View APIs 520 ViewCompat.setScrollIndicators(content, indicators, mask); 521 // We can also remove the compat indicator views 522 if (indicatorUp != null) { 523 contentPanel.removeView(indicatorUp); 524 } 525 if (indicatorDown != null) { 526 contentPanel.removeView(indicatorDown); 527 } 528 } else { 529 // First, remove the indicator views if we're not set to use them 530 if (indicatorUp != null && (indicators & ViewCompat.SCROLL_INDICATOR_TOP) == 0) { 531 contentPanel.removeView(indicatorUp); 532 indicatorUp = null; 533 } 534 if (indicatorDown != null && (indicators & ViewCompat.SCROLL_INDICATOR_BOTTOM) == 0) { 535 contentPanel.removeView(indicatorDown); 536 indicatorDown = null; 537 } 538 539 if (indicatorUp != null || indicatorDown != null) { 540 final View top = indicatorUp; 541 final View bottom = indicatorDown; 542 543 if (mMessage != null) { 544 // We're just showing the ScrollView, set up listener. 545 mScrollView.setOnScrollChangeListener( 546 new NestedScrollView.OnScrollChangeListener() { 547 @Override 548 public void onScrollChange(NestedScrollView v, int scrollX, 549 int scrollY, 550 int oldScrollX, int oldScrollY) { 551 manageScrollIndicators(v, top, bottom); 552 } 553 }); 554 // Set up the indicators following layout. 555 mScrollView.post(new Runnable() { 556 @Override 557 public void run() { 558 manageScrollIndicators(mScrollView, top, bottom); 559 } 560 }); 561 } else if (mListView != null) { 562 // We're just showing the AbsListView, set up listener. 563 mListView.setOnScrollListener(new AbsListView.OnScrollListener() { 564 @Override 565 public void onScrollStateChanged(AbsListView view, int scrollState) {} 566 567 @Override 568 public void onScroll(AbsListView v, int firstVisibleItem, 569 int visibleItemCount, int totalItemCount) { 570 manageScrollIndicators(v, top, bottom); 571 } 572 }); 573 // Set up the indicators following layout. 574 mListView.post(new Runnable() { 575 @Override 576 public void run() { 577 manageScrollIndicators(mListView, top, bottom); 578 } 579 }); 580 } else { 581 // We don't have any content to scroll, remove the indicators. 582 if (top != null) { 583 contentPanel.removeView(top); 584 } 585 if (bottom != null) { 586 contentPanel.removeView(bottom); 587 } 588 } 589 } 590 } 591 } 592 593 private void setupCustomContent(ViewGroup customPanel) { 594 final View customView; 595 if (mView != null) { 596 customView = mView; 597 } else if (mViewLayoutResId != 0) { 598 final LayoutInflater inflater = LayoutInflater.from(mContext); 599 customView = inflater.inflate(mViewLayoutResId, customPanel, false); 600 } else { 601 customView = null; 602 } 603 604 final boolean hasCustomView = customView != null; 605 if (!hasCustomView || !canTextInput(customView)) { 606 mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 607 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 608 } 609 610 if (hasCustomView) { 611 final FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom); 612 custom.addView(customView, new LayoutParams(MATCH_PARENT, MATCH_PARENT)); 613 614 if (mViewSpacingSpecified) { 615 custom.setPadding( 616 mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, mViewSpacingBottom); 617 } 618 619 if (mListView != null) { 620 ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0; 621 } 622 } else { 623 customPanel.setVisibility(View.GONE); 624 } 625 } 626 627 private void setupTitle(ViewGroup topPanel) { 628 if (mCustomTitleView != null) { 629 // Add the custom title view directly to the topPanel layout 630 LayoutParams lp = new LayoutParams( 631 LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); 632 633 topPanel.addView(mCustomTitleView, 0, lp); 634 635 // Hide the title template 636 View titleTemplate = mWindow.findViewById(R.id.title_template); 637 titleTemplate.setVisibility(View.GONE); 638 } else { 639 mIconView = (ImageView) mWindow.findViewById(android.R.id.icon); 640 641 final boolean hasTextTitle = !TextUtils.isEmpty(mTitle); 642 if (hasTextTitle) { 643 // Display the title if a title is supplied, else hide it. 644 mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle); 645 mTitleView.setText(mTitle); 646 647 // Do this last so that if the user has supplied any icons we 648 // use them instead of the default ones. If the user has 649 // specified 0 then make it disappear. 650 if (mIconId != 0) { 651 mIconView.setImageResource(mIconId); 652 } else if (mIcon != null) { 653 mIconView.setImageDrawable(mIcon); 654 } else { 655 // Apply the padding from the icon to ensure the title is 656 // aligned correctly. 657 mTitleView.setPadding(mIconView.getPaddingLeft(), 658 mIconView.getPaddingTop(), 659 mIconView.getPaddingRight(), 660 mIconView.getPaddingBottom()); 661 mIconView.setVisibility(View.GONE); 662 } 663 } else { 664 // Hide the title template 665 final View titleTemplate = mWindow.findViewById(R.id.title_template); 666 titleTemplate.setVisibility(View.GONE); 667 mIconView.setVisibility(View.GONE); 668 topPanel.setVisibility(View.GONE); 669 } 670 } 671 } 672 673 private void setupContent(ViewGroup contentPanel) { 674 mScrollView = (NestedScrollView) mWindow.findViewById(R.id.scrollView); 675 mScrollView.setFocusable(false); 676 mScrollView.setNestedScrollingEnabled(false); 677 678 // Special case for users that only want to display a String 679 mMessageView = (TextView) contentPanel.findViewById(android.R.id.message); 680 if (mMessageView == null) { 681 return; 682 } 683 684 if (mMessage != null) { 685 mMessageView.setText(mMessage); 686 } else { 687 mMessageView.setVisibility(View.GONE); 688 mScrollView.removeView(mMessageView); 689 690 if (mListView != null) { 691 final ViewGroup scrollParent = (ViewGroup) mScrollView.getParent(); 692 final int childIndex = scrollParent.indexOfChild(mScrollView); 693 scrollParent.removeViewAt(childIndex); 694 scrollParent.addView(mListView, childIndex, 695 new LayoutParams(MATCH_PARENT, MATCH_PARENT)); 696 } else { 697 contentPanel.setVisibility(View.GONE); 698 } 699 } 700 } 701 702 private static void manageScrollIndicators(View v, View upIndicator, View downIndicator) { 703 if (upIndicator != null) { 704 upIndicator.setVisibility( 705 ViewCompat.canScrollVertically(v, -1) ? View.VISIBLE : View.INVISIBLE); 706 } 707 if (downIndicator != null) { 708 downIndicator.setVisibility( 709 ViewCompat.canScrollVertically(v, 1) ? View.VISIBLE : View.INVISIBLE); 710 } 711 } 712 713 private void setupButtons(ViewGroup buttonPanel) { 714 int BIT_BUTTON_POSITIVE = 1; 715 int BIT_BUTTON_NEGATIVE = 2; 716 int BIT_BUTTON_NEUTRAL = 4; 717 int whichButtons = 0; 718 mButtonPositive = (Button) buttonPanel.findViewById(android.R.id.button1); 719 mButtonPositive.setOnClickListener(mButtonHandler); 720 721 if (TextUtils.isEmpty(mButtonPositiveText)) { 722 mButtonPositive.setVisibility(View.GONE); 723 } else { 724 mButtonPositive.setText(mButtonPositiveText); 725 mButtonPositive.setVisibility(View.VISIBLE); 726 whichButtons = whichButtons | BIT_BUTTON_POSITIVE; 727 } 728 729 mButtonNegative = (Button) buttonPanel.findViewById(android.R.id.button2); 730 mButtonNegative.setOnClickListener(mButtonHandler); 731 732 if (TextUtils.isEmpty(mButtonNegativeText)) { 733 mButtonNegative.setVisibility(View.GONE); 734 } else { 735 mButtonNegative.setText(mButtonNegativeText); 736 mButtonNegative.setVisibility(View.VISIBLE); 737 738 whichButtons = whichButtons | BIT_BUTTON_NEGATIVE; 739 } 740 741 mButtonNeutral = (Button) buttonPanel.findViewById(android.R.id.button3); 742 mButtonNeutral.setOnClickListener(mButtonHandler); 743 744 if (TextUtils.isEmpty(mButtonNeutralText)) { 745 mButtonNeutral.setVisibility(View.GONE); 746 } else { 747 mButtonNeutral.setText(mButtonNeutralText); 748 mButtonNeutral.setVisibility(View.VISIBLE); 749 750 whichButtons = whichButtons | BIT_BUTTON_NEUTRAL; 751 } 752 753 final boolean hasButtons = whichButtons != 0; 754 if (!hasButtons) { 755 buttonPanel.setVisibility(View.GONE); 756 } 757 } 758 759 public static class AlertParams { 760 public final Context mContext; 761 public final LayoutInflater mInflater; 762 763 public int mIconId = 0; 764 public Drawable mIcon; 765 public int mIconAttrId = 0; 766 public CharSequence mTitle; 767 public View mCustomTitleView; 768 public CharSequence mMessage; 769 public CharSequence mPositiveButtonText; 770 public DialogInterface.OnClickListener mPositiveButtonListener; 771 public CharSequence mNegativeButtonText; 772 public DialogInterface.OnClickListener mNegativeButtonListener; 773 public CharSequence mNeutralButtonText; 774 public DialogInterface.OnClickListener mNeutralButtonListener; 775 public boolean mCancelable; 776 public DialogInterface.OnCancelListener mOnCancelListener; 777 public DialogInterface.OnDismissListener mOnDismissListener; 778 public DialogInterface.OnKeyListener mOnKeyListener; 779 public CharSequence[] mItems; 780 public ListAdapter mAdapter; 781 public DialogInterface.OnClickListener mOnClickListener; 782 public int mViewLayoutResId; 783 public View mView; 784 public int mViewSpacingLeft; 785 public int mViewSpacingTop; 786 public int mViewSpacingRight; 787 public int mViewSpacingBottom; 788 public boolean mViewSpacingSpecified = false; 789 public boolean[] mCheckedItems; 790 public boolean mIsMultiChoice; 791 public boolean mIsSingleChoice; 792 public int mCheckedItem = -1; 793 public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener; 794 public Cursor mCursor; 795 public String mLabelColumn; 796 public String mIsCheckedColumn; 797 public boolean mForceInverseBackground; 798 public AdapterView.OnItemSelectedListener mOnItemSelectedListener; 799 public OnPrepareListViewListener mOnPrepareListViewListener; 800 public boolean mRecycleOnMeasure = true; 801 802 /** 803 * Interface definition for a callback to be invoked before the ListView 804 * will be bound to an adapter. 805 */ 806 public interface OnPrepareListViewListener { 807 808 /** 809 * Called before the ListView is bound to an adapter. 810 * @param listView The ListView that will be shown in the dialog. 811 */ 812 void onPrepareListView(ListView listView); 813 } 814 815 public AlertParams(Context context) { 816 mContext = context; 817 mCancelable = true; 818 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 819 } 820 821 public void apply(AlertController dialog) { 822 if (mCustomTitleView != null) { 823 dialog.setCustomTitle(mCustomTitleView); 824 } else { 825 if (mTitle != null) { 826 dialog.setTitle(mTitle); 827 } 828 if (mIcon != null) { 829 dialog.setIcon(mIcon); 830 } 831 if (mIconId != 0) { 832 dialog.setIcon(mIconId); 833 } 834 if (mIconAttrId != 0) { 835 dialog.setIcon(dialog.getIconAttributeResId(mIconAttrId)); 836 } 837 } 838 if (mMessage != null) { 839 dialog.setMessage(mMessage); 840 } 841 if (mPositiveButtonText != null) { 842 dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText, 843 mPositiveButtonListener, null); 844 } 845 if (mNegativeButtonText != null) { 846 dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText, 847 mNegativeButtonListener, null); 848 } 849 if (mNeutralButtonText != null) { 850 dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText, 851 mNeutralButtonListener, null); 852 } 853 // For a list, the client can either supply an array of items or an 854 // adapter or a cursor 855 if ((mItems != null) || (mCursor != null) || (mAdapter != null)) { 856 createListView(dialog); 857 } 858 if (mView != null) { 859 if (mViewSpacingSpecified) { 860 dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, 861 mViewSpacingBottom); 862 } else { 863 dialog.setView(mView); 864 } 865 } else if (mViewLayoutResId != 0) { 866 dialog.setView(mViewLayoutResId); 867 } 868 869 /* 870 dialog.setCancelable(mCancelable); 871 dialog.setOnCancelListener(mOnCancelListener); 872 if (mOnKeyListener != null) { 873 dialog.setOnKeyListener(mOnKeyListener); 874 } 875 */ 876 } 877 878 private void createListView(final AlertController dialog) { 879 final ListView listView = (ListView) mInflater.inflate(dialog.mListLayout, null); 880 final ListAdapter adapter; 881 882 if (mIsMultiChoice) { 883 if (mCursor == null) { 884 adapter = new ArrayAdapter<CharSequence>( 885 mContext, dialog.mMultiChoiceItemLayout, android.R.id.text1, mItems) { 886 @Override 887 public View getView(int position, View convertView, ViewGroup parent) { 888 View view = super.getView(position, convertView, parent); 889 if (mCheckedItems != null) { 890 boolean isItemChecked = mCheckedItems[position]; 891 if (isItemChecked) { 892 listView.setItemChecked(position, true); 893 } 894 } 895 return view; 896 } 897 }; 898 } else { 899 adapter = new CursorAdapter(mContext, mCursor, false) { 900 private final int mLabelIndex; 901 private final int mIsCheckedIndex; 902 903 { 904 final Cursor cursor = getCursor(); 905 mLabelIndex = cursor.getColumnIndexOrThrow(mLabelColumn); 906 mIsCheckedIndex = cursor.getColumnIndexOrThrow(mIsCheckedColumn); 907 } 908 909 @Override 910 public void bindView(View view, Context context, Cursor cursor) { 911 CheckedTextView text = (CheckedTextView) view.findViewById( 912 android.R.id.text1); 913 text.setText(cursor.getString(mLabelIndex)); 914 listView.setItemChecked(cursor.getPosition(), 915 cursor.getInt(mIsCheckedIndex) == 1); 916 } 917 918 @Override 919 public View newView(Context context, Cursor cursor, ViewGroup parent) { 920 return mInflater.inflate(dialog.mMultiChoiceItemLayout, 921 parent, false); 922 } 923 924 }; 925 } 926 } else { 927 final int layout; 928 if (mIsSingleChoice) { 929 layout = dialog.mSingleChoiceItemLayout; 930 } else { 931 layout = dialog.mListItemLayout; 932 } 933 934 if (mCursor != null) { 935 adapter = new SimpleCursorAdapter(mContext, layout, mCursor, 936 new String[] { mLabelColumn }, new int[] { android.R.id.text1 }); 937 } else if (mAdapter != null) { 938 adapter = mAdapter; 939 } else { 940 adapter = new CheckedItemAdapter(mContext, layout, android.R.id.text1, mItems); 941 } 942 } 943 944 if (mOnPrepareListViewListener != null) { 945 mOnPrepareListViewListener.onPrepareListView(listView); 946 } 947 948 /* Don't directly set the adapter on the ListView as we might 949 * want to add a footer to the ListView later. 950 */ 951 dialog.mAdapter = adapter; 952 dialog.mCheckedItem = mCheckedItem; 953 954 if (mOnClickListener != null) { 955 listView.setOnItemClickListener(new OnItemClickListener() { 956 @Override 957 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 958 mOnClickListener.onClick(dialog.mDialog, position); 959 if (!mIsSingleChoice) { 960 dialog.mDialog.dismiss(); 961 } 962 } 963 }); 964 } else if (mOnCheckboxClickListener != null) { 965 listView.setOnItemClickListener(new OnItemClickListener() { 966 @Override 967 public void onItemClick(AdapterView<?> parent, View v, int position, long id) { 968 if (mCheckedItems != null) { 969 mCheckedItems[position] = listView.isItemChecked(position); 970 } 971 mOnCheckboxClickListener.onClick( 972 dialog.mDialog, position, listView.isItemChecked(position)); 973 } 974 }); 975 } 976 977 // Attach a given OnItemSelectedListener to the ListView 978 if (mOnItemSelectedListener != null) { 979 listView.setOnItemSelectedListener(mOnItemSelectedListener); 980 } 981 982 if (mIsSingleChoice) { 983 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 984 } else if (mIsMultiChoice) { 985 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 986 } 987 dialog.mListView = listView; 988 } 989 } 990 991 private static class CheckedItemAdapter extends ArrayAdapter<CharSequence> { 992 public CheckedItemAdapter(Context context, int resource, int textViewResourceId, 993 CharSequence[] objects) { 994 super(context, resource, textViewResourceId, objects); 995 } 996 997 @Override 998 public boolean hasStableIds() { 999 return true; 1000 } 1001 1002 @Override 1003 public long getItemId(int position) { 1004 return position; 1005 } 1006 } 1007} 1008