AlertController.java revision fcca00accb923d3cbda4e0d6f5540b10e8279cd2
1/* 2 * Copyright (C) 2008 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 com.android.internal.app; 18 19import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; 20 21import com.android.internal.R; 22 23import android.app.AlertDialog; 24import android.content.Context; 25import android.content.DialogInterface; 26import android.content.res.TypedArray; 27import android.database.Cursor; 28import android.graphics.drawable.Drawable; 29import android.os.Handler; 30import android.os.Message; 31import android.text.TextUtils; 32import android.util.AttributeSet; 33import android.util.TypedValue; 34import android.view.Gravity; 35import android.view.KeyEvent; 36import android.view.LayoutInflater; 37import android.view.View; 38import android.view.ViewGroup; 39import android.view.ViewGroup.LayoutParams; 40import android.view.Window; 41import android.view.WindowManager; 42import android.widget.AdapterView; 43import android.widget.AdapterView.OnItemClickListener; 44import android.widget.ArrayAdapter; 45import android.widget.Button; 46import android.widget.CheckedTextView; 47import android.widget.CursorAdapter; 48import android.widget.FrameLayout; 49import android.widget.ImageView; 50import android.widget.LinearLayout; 51import android.widget.ListAdapter; 52import android.widget.ListView; 53import android.widget.ScrollView; 54import android.widget.SimpleCursorAdapter; 55import android.widget.TextView; 56 57import java.lang.ref.WeakReference; 58 59public class AlertController { 60 61 private final Context mContext; 62 private final DialogInterface mDialogInterface; 63 private final Window mWindow; 64 65 private CharSequence mTitle; 66 67 private CharSequence mMessage; 68 69 private ListView mListView; 70 71 private View mView; 72 73 private int mViewSpacingLeft; 74 75 private int mViewSpacingTop; 76 77 private int mViewSpacingRight; 78 79 private int mViewSpacingBottom; 80 81 private boolean mViewSpacingSpecified = false; 82 83 private Button mButtonPositive; 84 85 private CharSequence mButtonPositiveText; 86 87 private Message mButtonPositiveMessage; 88 89 private Button mButtonNegative; 90 91 private CharSequence mButtonNegativeText; 92 93 private Message mButtonNegativeMessage; 94 95 private Button mButtonNeutral; 96 97 private CharSequence mButtonNeutralText; 98 99 private Message mButtonNeutralMessage; 100 101 private ScrollView mScrollView; 102 103 private int mIconId = -1; 104 105 private Drawable mIcon; 106 107 private ImageView mIconView; 108 109 private TextView mTitleView; 110 111 private TextView mMessageView; 112 113 private View mCustomTitleView; 114 115 private boolean mForceInverseBackground; 116 117 private ListAdapter mAdapter; 118 119 private int mCheckedItem = -1; 120 121 private int mAlertDialogLayout; 122 private int mListLayout; 123 private int mMultiChoiceItemLayout; 124 private int mSingleChoiceItemLayout; 125 private int mListItemLayout; 126 127 private Handler mHandler; 128 129 View.OnClickListener mButtonHandler = new View.OnClickListener() { 130 public void onClick(View v) { 131 Message m = null; 132 if (v == mButtonPositive && mButtonPositiveMessage != null) { 133 m = Message.obtain(mButtonPositiveMessage); 134 } else if (v == mButtonNegative && mButtonNegativeMessage != null) { 135 m = Message.obtain(mButtonNegativeMessage); 136 } else if (v == mButtonNeutral && mButtonNeutralMessage != null) { 137 m = Message.obtain(mButtonNeutralMessage); 138 } 139 if (m != null) { 140 m.sendToTarget(); 141 } 142 143 // Post a message so we dismiss after the above handlers are executed 144 mHandler.obtainMessage(ButtonHandler.MSG_DISMISS_DIALOG, mDialogInterface) 145 .sendToTarget(); 146 } 147 }; 148 149 private static final class ButtonHandler extends Handler { 150 // Button clicks have Message.what as the BUTTON{1,2,3} constant 151 private static final int MSG_DISMISS_DIALOG = 1; 152 153 private WeakReference<DialogInterface> mDialog; 154 155 public ButtonHandler(DialogInterface dialog) { 156 mDialog = new WeakReference<DialogInterface>(dialog); 157 } 158 159 @Override 160 public void handleMessage(Message msg) { 161 switch (msg.what) { 162 163 case DialogInterface.BUTTON_POSITIVE: 164 case DialogInterface.BUTTON_NEGATIVE: 165 case DialogInterface.BUTTON_NEUTRAL: 166 ((DialogInterface.OnClickListener) msg.obj).onClick(mDialog.get(), msg.what); 167 break; 168 169 case MSG_DISMISS_DIALOG: 170 ((DialogInterface) msg.obj).dismiss(); 171 } 172 } 173 } 174 175 private static boolean shouldCenterSingleButton(Context context) { 176 TypedValue outValue = new TypedValue(); 177 context.getTheme().resolveAttribute(com.android.internal.R.attr.alertDialogCenterButtons, 178 outValue, true); 179 return outValue.data != 0; 180 } 181 182 public AlertController(Context context, DialogInterface di, Window window) { 183 mContext = context; 184 mDialogInterface = di; 185 mWindow = window; 186 mHandler = new ButtonHandler(di); 187 188 TypedArray a = context.obtainStyledAttributes(null, 189 com.android.internal.R.styleable.AlertDialog, 190 com.android.internal.R.attr.alertDialogStyle, 0); 191 192 mAlertDialogLayout = a.getResourceId(com.android.internal.R.styleable.AlertDialog_layout, 193 com.android.internal.R.layout.alert_dialog); 194 mListLayout = a.getResourceId( 195 com.android.internal.R.styleable.AlertDialog_listLayout, 196 com.android.internal.R.layout.select_dialog); 197 mMultiChoiceItemLayout = a.getResourceId( 198 com.android.internal.R.styleable.AlertDialog_multiChoiceItemLayout, 199 com.android.internal.R.layout.select_dialog_multichoice); 200 mSingleChoiceItemLayout = a.getResourceId( 201 com.android.internal.R.styleable.AlertDialog_singleChoiceItemLayout, 202 com.android.internal.R.layout.select_dialog_singlechoice); 203 mListItemLayout = a.getResourceId( 204 com.android.internal.R.styleable.AlertDialog_listItemLayout, 205 com.android.internal.R.layout.select_dialog_item); 206 207 a.recycle(); 208 } 209 210 static boolean canTextInput(View v) { 211 if (v.onCheckIsTextEditor()) { 212 return true; 213 } 214 215 if (!(v instanceof ViewGroup)) { 216 return false; 217 } 218 219 ViewGroup vg = (ViewGroup)v; 220 int i = vg.getChildCount(); 221 while (i > 0) { 222 i--; 223 v = vg.getChildAt(i); 224 if (canTextInput(v)) { 225 return true; 226 } 227 } 228 229 return false; 230 } 231 232 public void installContent() { 233 /* We use a custom title so never request a window title */ 234 mWindow.requestFeature(Window.FEATURE_NO_TITLE); 235 236 if (mView == null || !canTextInput(mView)) { 237 mWindow.setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM, 238 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); 239 } 240 mWindow.setContentView(mAlertDialogLayout); 241 setupView(); 242 } 243 244 public void setTitle(CharSequence title) { 245 mTitle = title; 246 if (mTitleView != null) { 247 mTitleView.setText(title); 248 } 249 } 250 251 /** 252 * @see AlertDialog.Builder#setCustomTitle(View) 253 */ 254 public void setCustomTitle(View customTitleView) { 255 mCustomTitleView = customTitleView; 256 } 257 258 public void setMessage(CharSequence message) { 259 mMessage = message; 260 if (mMessageView != null) { 261 mMessageView.setText(message); 262 } 263 } 264 265 /** 266 * Set the view to display in the dialog. 267 */ 268 public void setView(View view) { 269 mView = view; 270 mViewSpacingSpecified = false; 271 } 272 273 /** 274 * Set the view to display in the dialog along with the spacing around that view 275 */ 276 public void setView(View view, int viewSpacingLeft, int viewSpacingTop, int viewSpacingRight, 277 int viewSpacingBottom) { 278 mView = view; 279 mViewSpacingSpecified = true; 280 mViewSpacingLeft = viewSpacingLeft; 281 mViewSpacingTop = viewSpacingTop; 282 mViewSpacingRight = viewSpacingRight; 283 mViewSpacingBottom = viewSpacingBottom; 284 } 285 286 /** 287 * Sets a click listener or a message to be sent when the button is clicked. 288 * You only need to pass one of {@code listener} or {@code msg}. 289 * 290 * @param whichButton Which button, can be one of 291 * {@link DialogInterface#BUTTON_POSITIVE}, 292 * {@link DialogInterface#BUTTON_NEGATIVE}, or 293 * {@link DialogInterface#BUTTON_NEUTRAL} 294 * @param text The text to display in positive button. 295 * @param listener The {@link DialogInterface.OnClickListener} to use. 296 * @param msg The {@link Message} to be sent when clicked. 297 */ 298 public void setButton(int whichButton, CharSequence text, 299 DialogInterface.OnClickListener listener, Message msg) { 300 301 if (msg == null && listener != null) { 302 msg = mHandler.obtainMessage(whichButton, listener); 303 } 304 305 switch (whichButton) { 306 307 case DialogInterface.BUTTON_POSITIVE: 308 mButtonPositiveText = text; 309 mButtonPositiveMessage = msg; 310 break; 311 312 case DialogInterface.BUTTON_NEGATIVE: 313 mButtonNegativeText = text; 314 mButtonNegativeMessage = msg; 315 break; 316 317 case DialogInterface.BUTTON_NEUTRAL: 318 mButtonNeutralText = text; 319 mButtonNeutralMessage = msg; 320 break; 321 322 default: 323 throw new IllegalArgumentException("Button does not exist"); 324 } 325 } 326 327 /** 328 * Set resId to 0 if you don't want an icon. 329 * @param resId the resourceId of the drawable to use as the icon or 0 330 * if you don't want an icon. 331 */ 332 public void setIcon(int resId) { 333 mIconId = resId; 334 if (mIconView != null) { 335 if (resId > 0) { 336 mIconView.setImageResource(mIconId); 337 } else if (resId == 0) { 338 mIconView.setVisibility(View.GONE); 339 } 340 } 341 } 342 343 public void setIcon(Drawable icon) { 344 mIcon = icon; 345 if ((mIconView != null) && (mIcon != null)) { 346 mIconView.setImageDrawable(icon); 347 } 348 } 349 350 public void setInverseBackgroundForced(boolean forceInverseBackground) { 351 mForceInverseBackground = forceInverseBackground; 352 } 353 354 public ListView getListView() { 355 return mListView; 356 } 357 358 public Button getButton(int whichButton) { 359 switch (whichButton) { 360 case DialogInterface.BUTTON_POSITIVE: 361 return mButtonPositive; 362 case DialogInterface.BUTTON_NEGATIVE: 363 return mButtonNegative; 364 case DialogInterface.BUTTON_NEUTRAL: 365 return mButtonNeutral; 366 default: 367 return null; 368 } 369 } 370 371 @SuppressWarnings({"UnusedDeclaration"}) 372 public boolean onKeyDown(int keyCode, KeyEvent event) { 373 return mScrollView != null && mScrollView.executeKeyEvent(event); 374 } 375 376 @SuppressWarnings({"UnusedDeclaration"}) 377 public boolean onKeyUp(int keyCode, KeyEvent event) { 378 return mScrollView != null && mScrollView.executeKeyEvent(event); 379 } 380 381 private void setupView() { 382 LinearLayout contentPanel = (LinearLayout) mWindow.findViewById(R.id.contentPanel); 383 setupContent(contentPanel); 384 boolean hasButtons = setupButtons(); 385 386 LinearLayout topPanel = (LinearLayout) mWindow.findViewById(R.id.topPanel); 387 TypedArray a = mContext.obtainStyledAttributes( 388 null, com.android.internal.R.styleable.AlertDialog, com.android.internal.R.attr.alertDialogStyle, 0); 389 boolean hasTitle = setupTitle(topPanel); 390 391 View buttonPanel = mWindow.findViewById(R.id.buttonPanel); 392 if (!hasButtons) { 393 buttonPanel.setVisibility(View.GONE); 394 } 395 396 FrameLayout customPanel = null; 397 if (mView != null) { 398 customPanel = (FrameLayout) mWindow.findViewById(R.id.customPanel); 399 FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.custom); 400 custom.addView(mView, new LayoutParams(MATCH_PARENT, MATCH_PARENT)); 401 if (mViewSpacingSpecified) { 402 custom.setPadding(mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, 403 mViewSpacingBottom); 404 } 405 if (mListView != null) { 406 ((LinearLayout.LayoutParams) customPanel.getLayoutParams()).weight = 0; 407 } 408 } else { 409 mWindow.findViewById(R.id.customPanel).setVisibility(View.GONE); 410 } 411 412 /* Only display the divider if we have a title and a 413 * custom view or a message. 414 */ 415 if (hasTitle && ((mMessage != null) || (mView != null))) { 416 View divider = mWindow.findViewById(R.id.titleDivider); 417 divider.setVisibility(View.VISIBLE); 418 } 419 420 setBackground(topPanel, contentPanel, customPanel, hasButtons, a, hasTitle, buttonPanel); 421 a.recycle(); 422 } 423 424 private boolean setupTitle(LinearLayout topPanel) { 425 boolean hasTitle = true; 426 427 if (mCustomTitleView != null) { 428 // Add the custom title view directly to the topPanel layout 429 LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams( 430 LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); 431 432 topPanel.addView(mCustomTitleView, lp); 433 434 // Hide the title template 435 View titleTemplate = mWindow.findViewById(R.id.title_template); 436 titleTemplate.setVisibility(View.GONE); 437 } else { 438 final boolean hasTextTitle = !TextUtils.isEmpty(mTitle); 439 440 mIconView = (ImageView) mWindow.findViewById(R.id.icon); 441 if (hasTextTitle) { 442 /* Display the title if a title is supplied, else hide it */ 443 mTitleView = (TextView) mWindow.findViewById(R.id.alertTitle); 444 445 mTitleView.setText(mTitle); 446 447 /* Do this last so that if the user has supplied any 448 * icons we use them instead of the default ones. If the 449 * user has specified 0 then make it disappear. 450 */ 451 if (mIconId > 0) { 452 mIconView.setImageResource(mIconId); 453 } else if (mIcon != null) { 454 mIconView.setImageDrawable(mIcon); 455 } else if (mIconId == 0) { 456 457 /* Apply the padding from the icon to ensure the 458 * title is aligned correctly. 459 */ 460 mTitleView.setPadding(mIconView.getPaddingLeft(), 461 mIconView.getPaddingTop(), 462 mIconView.getPaddingRight(), 463 mIconView.getPaddingBottom()); 464 mIconView.setVisibility(View.GONE); 465 } 466 } else { 467 468 // Hide the title template 469 View titleTemplate = mWindow.findViewById(R.id.title_template); 470 titleTemplate.setVisibility(View.GONE); 471 mIconView.setVisibility(View.GONE); 472 topPanel.setVisibility(View.GONE); 473 hasTitle = false; 474 } 475 } 476 return hasTitle; 477 } 478 479 private void setupContent(LinearLayout contentPanel) { 480 mScrollView = (ScrollView) mWindow.findViewById(R.id.scrollView); 481 mScrollView.setFocusable(false); 482 483 // Special case for users that only want to display a String 484 mMessageView = (TextView) mWindow.findViewById(R.id.message); 485 if (mMessageView == null) { 486 return; 487 } 488 489 if (mMessage != null) { 490 mMessageView.setText(mMessage); 491 } else { 492 mMessageView.setVisibility(View.GONE); 493 mScrollView.removeView(mMessageView); 494 495 if (mListView != null) { 496 contentPanel.removeView(mWindow.findViewById(R.id.scrollView)); 497 contentPanel.addView(mListView, 498 new LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)); 499 contentPanel.setLayoutParams(new LinearLayout.LayoutParams(MATCH_PARENT, 0, 1.0f)); 500 } else { 501 contentPanel.setVisibility(View.GONE); 502 } 503 } 504 } 505 506 private boolean setupButtons() { 507 int BIT_BUTTON_POSITIVE = 1; 508 int BIT_BUTTON_NEGATIVE = 2; 509 int BIT_BUTTON_NEUTRAL = 4; 510 int whichButtons = 0; 511 mButtonPositive = (Button) mWindow.findViewById(R.id.button1); 512 mButtonPositive.setOnClickListener(mButtonHandler); 513 514 if (TextUtils.isEmpty(mButtonPositiveText)) { 515 mButtonPositive.setVisibility(View.GONE); 516 } else { 517 mButtonPositive.setText(mButtonPositiveText); 518 mButtonPositive.setVisibility(View.VISIBLE); 519 whichButtons = whichButtons | BIT_BUTTON_POSITIVE; 520 } 521 522 mButtonNegative = (Button) mWindow.findViewById(R.id.button2); 523 mButtonNegative.setOnClickListener(mButtonHandler); 524 525 if (TextUtils.isEmpty(mButtonNegativeText)) { 526 mButtonNegative.setVisibility(View.GONE); 527 } else { 528 mButtonNegative.setText(mButtonNegativeText); 529 mButtonNegative.setVisibility(View.VISIBLE); 530 531 whichButtons = whichButtons | BIT_BUTTON_NEGATIVE; 532 } 533 534 mButtonNeutral = (Button) mWindow.findViewById(R.id.button3); 535 mButtonNeutral.setOnClickListener(mButtonHandler); 536 537 if (TextUtils.isEmpty(mButtonNeutralText)) { 538 mButtonNeutral.setVisibility(View.GONE); 539 } else { 540 mButtonNeutral.setText(mButtonNeutralText); 541 mButtonNeutral.setVisibility(View.VISIBLE); 542 543 whichButtons = whichButtons | BIT_BUTTON_NEUTRAL; 544 } 545 546 if (shouldCenterSingleButton(mContext)) { 547 /* 548 * If we only have 1 button it should be centered on the layout and 549 * expand to fill 50% of the available space. 550 */ 551 if (whichButtons == BIT_BUTTON_POSITIVE) { 552 centerButton(mButtonPositive); 553 } else if (whichButtons == BIT_BUTTON_NEGATIVE) { 554 centerButton(mButtonNeutral); 555 } else if (whichButtons == BIT_BUTTON_NEUTRAL) { 556 centerButton(mButtonNeutral); 557 } 558 } 559 560 return whichButtons != 0; 561 } 562 563 private void centerButton(Button button) { 564 LinearLayout.LayoutParams params = (LinearLayout.LayoutParams) button.getLayoutParams(); 565 params.gravity = Gravity.CENTER_HORIZONTAL; 566 params.weight = 0.5f; 567 button.setLayoutParams(params); 568 View leftSpacer = mWindow.findViewById(R.id.leftSpacer); 569 leftSpacer.setVisibility(View.VISIBLE); 570 View rightSpacer = mWindow.findViewById(R.id.rightSpacer); 571 rightSpacer.setVisibility(View.VISIBLE); 572 } 573 574 private void setBackground(LinearLayout topPanel, LinearLayout contentPanel, 575 View customPanel, boolean hasButtons, TypedArray a, boolean hasTitle, 576 View buttonPanel) { 577 578 /* Get all the different background required */ 579 int fullDark = a.getResourceId( 580 R.styleable.AlertDialog_fullDark, R.drawable.popup_full_dark); 581 int topDark = a.getResourceId( 582 R.styleable.AlertDialog_topDark, R.drawable.popup_top_dark); 583 int centerDark = a.getResourceId( 584 R.styleable.AlertDialog_centerDark, R.drawable.popup_center_dark); 585 int bottomDark = a.getResourceId( 586 R.styleable.AlertDialog_bottomDark, R.drawable.popup_bottom_dark); 587 int fullBright = a.getResourceId( 588 R.styleable.AlertDialog_fullBright, R.drawable.popup_full_bright); 589 int topBright = a.getResourceId( 590 R.styleable.AlertDialog_topBright, R.drawable.popup_top_bright); 591 int centerBright = a.getResourceId( 592 R.styleable.AlertDialog_centerBright, R.drawable.popup_center_bright); 593 int bottomBright = a.getResourceId( 594 R.styleable.AlertDialog_bottomBright, R.drawable.popup_bottom_bright); 595 int bottomMedium = a.getResourceId( 596 R.styleable.AlertDialog_bottomMedium, R.drawable.popup_bottom_medium); 597 598 /* 599 * We now set the background of all of the sections of the alert. 600 * First collect together each section that is being displayed along 601 * with whether it is on a light or dark background, then run through 602 * them setting their backgrounds. This is complicated because we need 603 * to correctly use the full, top, middle, and bottom graphics depending 604 * on how many views they are and where they appear. 605 */ 606 607 View[] views = new View[4]; 608 boolean[] light = new boolean[4]; 609 View lastView = null; 610 boolean lastLight = false; 611 612 int pos = 0; 613 if (hasTitle) { 614 views[pos] = topPanel; 615 light[pos] = false; 616 pos++; 617 } 618 619 /* The contentPanel displays either a custom text message or 620 * a ListView. If it's text we should use the dark background 621 * for ListView we should use the light background. If neither 622 * are there the contentPanel will be hidden so set it as null. 623 */ 624 views[pos] = (contentPanel.getVisibility() == View.GONE) 625 ? null : contentPanel; 626 light[pos] = mListView != null; 627 pos++; 628 if (customPanel != null) { 629 views[pos] = customPanel; 630 light[pos] = mForceInverseBackground; 631 pos++; 632 } 633 if (hasButtons) { 634 views[pos] = buttonPanel; 635 light[pos] = true; 636 } 637 638 boolean setView = false; 639 for (pos=0; pos<views.length; pos++) { 640 View v = views[pos]; 641 if (v == null) { 642 continue; 643 } 644 if (lastView != null) { 645 if (!setView) { 646 lastView.setBackgroundResource(lastLight ? topBright : topDark); 647 } else { 648 lastView.setBackgroundResource(lastLight ? centerBright : centerDark); 649 } 650 setView = true; 651 } 652 lastView = v; 653 lastLight = light[pos]; 654 } 655 656 if (lastView != null) { 657 if (setView) { 658 659 /* ListViews will use the Bright background but buttons use 660 * the Medium background. 661 */ 662 lastView.setBackgroundResource( 663 lastLight ? (hasButtons ? bottomMedium : bottomBright) : bottomDark); 664 } else { 665 lastView.setBackgroundResource(lastLight ? fullBright : fullDark); 666 } 667 } 668 669 /* TODO: uncomment section below. The logic for this should be if 670 * it's a Contextual menu being displayed AND only a Cancel button 671 * is shown then do this. 672 */ 673// if (hasButtons && (mListView != null)) { 674 675 /* Yet another *special* case. If there is a ListView with buttons 676 * don't put the buttons on the bottom but instead put them in the 677 * footer of the ListView this will allow more items to be 678 * displayed. 679 */ 680 681 /* 682 contentPanel.setBackgroundResource(bottomBright); 683 buttonPanel.setBackgroundResource(centerMedium); 684 ViewGroup parent = (ViewGroup) mWindow.findViewById(R.id.parentPanel); 685 parent.removeView(buttonPanel); 686 AbsListView.LayoutParams params = new AbsListView.LayoutParams( 687 AbsListView.LayoutParams.MATCH_PARENT, 688 AbsListView.LayoutParams.MATCH_PARENT); 689 buttonPanel.setLayoutParams(params); 690 mListView.addFooterView(buttonPanel); 691 */ 692// } 693 694 if ((mListView != null) && (mAdapter != null)) { 695 mListView.setAdapter(mAdapter); 696 if (mCheckedItem > -1) { 697 mListView.setItemChecked(mCheckedItem, true); 698 mListView.setSelection(mCheckedItem); 699 } 700 } 701 } 702 703 public static class RecycleListView extends ListView { 704 boolean mRecycleOnMeasure = true; 705 706 public RecycleListView(Context context) { 707 super(context); 708 } 709 710 public RecycleListView(Context context, AttributeSet attrs) { 711 super(context, attrs); 712 } 713 714 public RecycleListView(Context context, AttributeSet attrs, int defStyle) { 715 super(context, attrs, defStyle); 716 } 717 718 @Override 719 protected boolean recycleOnMeasure() { 720 return mRecycleOnMeasure; 721 } 722 } 723 724 public static class AlertParams { 725 public final Context mContext; 726 public final LayoutInflater mInflater; 727 728 public int mIconId = 0; 729 public Drawable mIcon; 730 public CharSequence mTitle; 731 public View mCustomTitleView; 732 public CharSequence mMessage; 733 public CharSequence mPositiveButtonText; 734 public DialogInterface.OnClickListener mPositiveButtonListener; 735 public CharSequence mNegativeButtonText; 736 public DialogInterface.OnClickListener mNegativeButtonListener; 737 public CharSequence mNeutralButtonText; 738 public DialogInterface.OnClickListener mNeutralButtonListener; 739 public boolean mCancelable; 740 public DialogInterface.OnCancelListener mOnCancelListener; 741 public DialogInterface.OnKeyListener mOnKeyListener; 742 public CharSequence[] mItems; 743 public ListAdapter mAdapter; 744 public DialogInterface.OnClickListener mOnClickListener; 745 public View mView; 746 public int mViewSpacingLeft; 747 public int mViewSpacingTop; 748 public int mViewSpacingRight; 749 public int mViewSpacingBottom; 750 public boolean mViewSpacingSpecified = false; 751 public boolean[] mCheckedItems; 752 public boolean mIsMultiChoice; 753 public boolean mIsSingleChoice; 754 public int mCheckedItem = -1; 755 public DialogInterface.OnMultiChoiceClickListener mOnCheckboxClickListener; 756 public Cursor mCursor; 757 public String mLabelColumn; 758 public String mIsCheckedColumn; 759 public boolean mForceInverseBackground; 760 public AdapterView.OnItemSelectedListener mOnItemSelectedListener; 761 public OnPrepareListViewListener mOnPrepareListViewListener; 762 public boolean mRecycleOnMeasure = true; 763 764 /** 765 * Interface definition for a callback to be invoked before the ListView 766 * will be bound to an adapter. 767 */ 768 public interface OnPrepareListViewListener { 769 770 /** 771 * Called before the ListView is bound to an adapter. 772 * @param listView The ListView that will be shown in the dialog. 773 */ 774 void onPrepareListView(ListView listView); 775 } 776 777 public AlertParams(Context context) { 778 mContext = context; 779 mCancelable = true; 780 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 781 } 782 783 public void apply(AlertController dialog) { 784 if (mCustomTitleView != null) { 785 dialog.setCustomTitle(mCustomTitleView); 786 } else { 787 if (mTitle != null) { 788 dialog.setTitle(mTitle); 789 } 790 if (mIcon != null) { 791 dialog.setIcon(mIcon); 792 } 793 if (mIconId >= 0) { 794 dialog.setIcon(mIconId); 795 } 796 } 797 if (mMessage != null) { 798 dialog.setMessage(mMessage); 799 } 800 if (mPositiveButtonText != null) { 801 dialog.setButton(DialogInterface.BUTTON_POSITIVE, mPositiveButtonText, 802 mPositiveButtonListener, null); 803 } 804 if (mNegativeButtonText != null) { 805 dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mNegativeButtonText, 806 mNegativeButtonListener, null); 807 } 808 if (mNeutralButtonText != null) { 809 dialog.setButton(DialogInterface.BUTTON_NEUTRAL, mNeutralButtonText, 810 mNeutralButtonListener, null); 811 } 812 if (mForceInverseBackground) { 813 dialog.setInverseBackgroundForced(true); 814 } 815 // For a list, the client can either supply an array of items or an 816 // adapter or a cursor 817 if ((mItems != null) || (mCursor != null) || (mAdapter != null)) { 818 createListView(dialog); 819 } 820 if (mView != null) { 821 if (mViewSpacingSpecified) { 822 dialog.setView(mView, mViewSpacingLeft, mViewSpacingTop, mViewSpacingRight, 823 mViewSpacingBottom); 824 } else { 825 dialog.setView(mView); 826 } 827 } 828 829 /* 830 dialog.setCancelable(mCancelable); 831 dialog.setOnCancelListener(mOnCancelListener); 832 if (mOnKeyListener != null) { 833 dialog.setOnKeyListener(mOnKeyListener); 834 } 835 */ 836 } 837 838 private void createListView(final AlertController dialog) { 839 final RecycleListView listView = (RecycleListView) 840 mInflater.inflate(dialog.mListLayout, null); 841 ListAdapter adapter; 842 843 if (mIsMultiChoice) { 844 if (mCursor == null) { 845 adapter = new ArrayAdapter<CharSequence>( 846 mContext, dialog.mMultiChoiceItemLayout, R.id.text1, mItems) { 847 @Override 848 public View getView(int position, View convertView, ViewGroup parent) { 849 View view = super.getView(position, convertView, parent); 850 if (mCheckedItems != null) { 851 boolean isItemChecked = mCheckedItems[position]; 852 if (isItemChecked) { 853 listView.setItemChecked(position, true); 854 } 855 } 856 return view; 857 } 858 }; 859 } else { 860 adapter = new CursorAdapter(mContext, mCursor, false) { 861 private final int mLabelIndex; 862 private final int mIsCheckedIndex; 863 864 { 865 final Cursor cursor = getCursor(); 866 mLabelIndex = cursor.getColumnIndexOrThrow(mLabelColumn); 867 mIsCheckedIndex = cursor.getColumnIndexOrThrow(mIsCheckedColumn); 868 } 869 870 @Override 871 public void bindView(View view, Context context, Cursor cursor) { 872 CheckedTextView text = (CheckedTextView) view.findViewById(R.id.text1); 873 text.setText(cursor.getString(mLabelIndex)); 874 listView.setItemChecked(cursor.getPosition(), 875 cursor.getInt(mIsCheckedIndex) == 1); 876 } 877 878 @Override 879 public View newView(Context context, Cursor cursor, ViewGroup parent) { 880 return mInflater.inflate(dialog.mMultiChoiceItemLayout, 881 parent, false); 882 } 883 884 }; 885 } 886 } else { 887 int layout = mIsSingleChoice 888 ? dialog.mSingleChoiceItemLayout : dialog.mListItemLayout; 889 if (mCursor == null) { 890 adapter = (mAdapter != null) ? mAdapter 891 : new ArrayAdapter<CharSequence>(mContext, layout, R.id.text1, mItems); 892 } else { 893 adapter = new SimpleCursorAdapter(mContext, layout, 894 mCursor, new String[]{mLabelColumn}, new int[]{R.id.text1}); 895 } 896 } 897 898 if (mOnPrepareListViewListener != null) { 899 mOnPrepareListViewListener.onPrepareListView(listView); 900 } 901 902 /* Don't directly set the adapter on the ListView as we might 903 * want to add a footer to the ListView later. 904 */ 905 dialog.mAdapter = adapter; 906 dialog.mCheckedItem = mCheckedItem; 907 908 if (mOnClickListener != null) { 909 listView.setOnItemClickListener(new OnItemClickListener() { 910 public void onItemClick(AdapterView parent, View v, int position, long id) { 911 mOnClickListener.onClick(dialog.mDialogInterface, position); 912 if (!mIsSingleChoice) { 913 dialog.mDialogInterface.dismiss(); 914 } 915 } 916 }); 917 } else if (mOnCheckboxClickListener != null) { 918 listView.setOnItemClickListener(new OnItemClickListener() { 919 public void onItemClick(AdapterView parent, View v, int position, long id) { 920 if (mCheckedItems != null) { 921 mCheckedItems[position] = listView.isItemChecked(position); 922 } 923 mOnCheckboxClickListener.onClick( 924 dialog.mDialogInterface, position, listView.isItemChecked(position)); 925 } 926 }); 927 } 928 929 // Attach a given OnItemSelectedListener to the ListView 930 if (mOnItemSelectedListener != null) { 931 listView.setOnItemSelectedListener(mOnItemSelectedListener); 932 } 933 934 if (mIsSingleChoice) { 935 listView.setChoiceMode(ListView.CHOICE_MODE_SINGLE); 936 } else if (mIsMultiChoice) { 937 listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE); 938 } 939 listView.mRecycleOnMeasure = mRecycleOnMeasure; 940 dialog.mListView = listView; 941 } 942 } 943 944} 945