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