CheckedTextView.java revision 8de1494557cf1d00c1c3fce439138a28de7fbd61
1/* 2 * Copyright (C) 2007 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.widget; 18 19import com.android.internal.R; 20 21import android.content.Context; 22import android.content.res.TypedArray; 23import android.graphics.Canvas; 24import android.graphics.drawable.Drawable; 25import android.util.AttributeSet; 26import android.view.Gravity; 27import android.view.RemotableViewMethod; 28import android.view.ViewDebug; 29import android.view.accessibility.AccessibilityEvent; 30import android.view.accessibility.AccessibilityNodeInfo; 31 32 33/** 34 * An extension to TextView that supports the {@link android.widget.Checkable} interface. 35 * This is useful when used in a {@link android.widget.ListView ListView} where the it's 36 * {@link android.widget.ListView#setChoiceMode(int) setChoiceMode} has been set to 37 * something other than {@link android.widget.ListView#CHOICE_MODE_NONE CHOICE_MODE_NONE}. 38 * 39 * @attr ref android.R.styleable#CheckedTextView_checked 40 * @attr ref android.R.styleable#CheckedTextView_checkMark 41 */ 42public class CheckedTextView extends TextView implements Checkable { 43 private boolean mChecked; 44 private int mCheckMarkResource; 45 private Drawable mCheckMarkDrawable; 46 private int mBasePadding; 47 private int mCheckMarkWidth; 48 private boolean mNeedRequestlayout; 49 50 private static final int[] CHECKED_STATE_SET = { 51 R.attr.state_checked 52 }; 53 54 public CheckedTextView(Context context) { 55 this(context, null); 56 } 57 58 public CheckedTextView(Context context, AttributeSet attrs) { 59 this(context, attrs, R.attr.checkedTextViewStyle); 60 } 61 62 public CheckedTextView(Context context, AttributeSet attrs, int defStyleAttr) { 63 this(context, attrs, defStyleAttr, 0); 64 } 65 66 public CheckedTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 67 super(context, attrs, defStyleAttr, defStyleRes); 68 69 final TypedArray a = context.obtainStyledAttributes( 70 attrs, R.styleable.CheckedTextView, defStyleAttr, defStyleRes); 71 72 Drawable d = a.getDrawable(R.styleable.CheckedTextView_checkMark); 73 if (d != null) { 74 setCheckMarkDrawable(d); 75 } 76 77 boolean checked = a.getBoolean(R.styleable.CheckedTextView_checked, false); 78 setChecked(checked); 79 80 a.recycle(); 81 } 82 83 public void toggle() { 84 setChecked(!mChecked); 85 } 86 87 @ViewDebug.ExportedProperty 88 public boolean isChecked() { 89 return mChecked; 90 } 91 92 /** 93 * <p>Changes the checked state of this text view.</p> 94 * 95 * @param checked true to check the text, false to uncheck it 96 */ 97 public void setChecked(boolean checked) { 98 if (mChecked != checked) { 99 mChecked = checked; 100 refreshDrawableState(); 101 notifyViewAccessibilityStateChangedIfNeeded( 102 AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED); 103 } 104 } 105 106 107 /** 108 * Set the checkmark to a given Drawable, identified by its resourece id. This will be drawn 109 * when {@link #isChecked()} is true. 110 * 111 * @param resid The Drawable to use for the checkmark. 112 * 113 * @see #setCheckMarkDrawable(Drawable) 114 * @see #getCheckMarkDrawable() 115 * 116 * @attr ref android.R.styleable#CheckedTextView_checkMark 117 */ 118 public void setCheckMarkDrawable(int resid) { 119 if (resid != 0 && resid == mCheckMarkResource) { 120 return; 121 } 122 123 mCheckMarkResource = resid; 124 125 Drawable d = null; 126 if (mCheckMarkResource != 0) { 127 d = getContext().getDrawable(mCheckMarkResource); 128 } 129 setCheckMarkDrawable(d); 130 } 131 132 /** 133 * Set the checkmark to a given Drawable. This will be drawn when {@link #isChecked()} is true. 134 * 135 * @param d The Drawable to use for the checkmark. 136 * 137 * @see #setCheckMarkDrawable(int) 138 * @see #getCheckMarkDrawable() 139 * 140 * @attr ref android.R.styleable#CheckedTextView_checkMark 141 */ 142 public void setCheckMarkDrawable(Drawable d) { 143 if (mCheckMarkDrawable != null) { 144 mCheckMarkDrawable.setCallback(null); 145 unscheduleDrawable(mCheckMarkDrawable); 146 } 147 mNeedRequestlayout = (d != mCheckMarkDrawable); 148 if (d != null) { 149 d.setCallback(this); 150 d.setVisible(getVisibility() == VISIBLE, false); 151 d.setState(CHECKED_STATE_SET); 152 setMinHeight(d.getIntrinsicHeight()); 153 154 mCheckMarkWidth = d.getIntrinsicWidth(); 155 d.setState(getDrawableState()); 156 } else { 157 mCheckMarkWidth = 0; 158 } 159 mCheckMarkDrawable = d; 160 161 // Do padding resolution. This will call internalSetPadding() and do a 162 // requestLayout() if needed. 163 resolvePadding(); 164 } 165 166 @RemotableViewMethod 167 @Override 168 public void setVisibility(int visibility) { 169 super.setVisibility(visibility); 170 171 if (mCheckMarkDrawable != null) { 172 mCheckMarkDrawable.setVisible(visibility == VISIBLE, false); 173 } 174 } 175 176 @Override 177 public void jumpDrawablesToCurrentState() { 178 super.jumpDrawablesToCurrentState(); 179 180 if (mCheckMarkDrawable != null) { 181 mCheckMarkDrawable.jumpToCurrentState(); 182 } 183 } 184 185 @Override 186 protected boolean verifyDrawable(Drawable who) { 187 return who == mCheckMarkDrawable || super.verifyDrawable(who); 188 } 189 190 /** 191 * Gets the checkmark drawable 192 * 193 * @return The drawable use to represent the checkmark, if any. 194 * 195 * @see #setCheckMarkDrawable(Drawable) 196 * @see #setCheckMarkDrawable(int) 197 * 198 * @attr ref android.R.styleable#CheckedTextView_checkMark 199 */ 200 public Drawable getCheckMarkDrawable() { 201 return mCheckMarkDrawable; 202 } 203 204 /** 205 * @hide 206 */ 207 @Override 208 protected void internalSetPadding(int left, int top, int right, int bottom) { 209 super.internalSetPadding(left, top, right, bottom); 210 setBasePadding(isLayoutRtl()); 211 } 212 213 @Override 214 public void onRtlPropertiesChanged(int layoutDirection) { 215 super.onRtlPropertiesChanged(layoutDirection); 216 updatePadding(); 217 } 218 219 private void updatePadding() { 220 resetPaddingToInitialValues(); 221 int newPadding = (mCheckMarkDrawable != null) ? 222 mCheckMarkWidth + mBasePadding : mBasePadding; 223 if (isLayoutRtl()) { 224 mNeedRequestlayout |= (mPaddingLeft != newPadding); 225 mPaddingLeft = newPadding; 226 } else { 227 mNeedRequestlayout |= (mPaddingRight != newPadding); 228 mPaddingRight = newPadding; 229 } 230 if (mNeedRequestlayout) { 231 requestLayout(); 232 mNeedRequestlayout = false; 233 } 234 } 235 236 private void setBasePadding(boolean isLayoutRtl) { 237 if (isLayoutRtl) { 238 mBasePadding = mPaddingLeft; 239 } else { 240 mBasePadding = mPaddingRight; 241 } 242 } 243 244 @Override 245 protected void onDraw(Canvas canvas) { 246 super.onDraw(canvas); 247 248 final Drawable checkMarkDrawable = mCheckMarkDrawable; 249 if (checkMarkDrawable != null) { 250 final int verticalGravity = getGravity() & Gravity.VERTICAL_GRAVITY_MASK; 251 final int height = checkMarkDrawable.getIntrinsicHeight(); 252 253 int y = 0; 254 255 switch (verticalGravity) { 256 case Gravity.BOTTOM: 257 y = getHeight() - height; 258 break; 259 case Gravity.CENTER_VERTICAL: 260 y = (getHeight() - height) / 2; 261 break; 262 } 263 264 final boolean isLayoutRtl = isLayoutRtl(); 265 final int width = getWidth(); 266 final int top = y; 267 final int bottom = top + height; 268 final int left; 269 final int right; 270 if (isLayoutRtl) { 271 left = mBasePadding; 272 right = left + mCheckMarkWidth; 273 } else { 274 right = width - mBasePadding; 275 left = right - mCheckMarkWidth; 276 } 277 checkMarkDrawable.setBounds(mScrollX + left, top, mScrollX + right, bottom); 278 checkMarkDrawable.draw(canvas); 279 280 final Drawable background = getBackground(); 281 if (background != null) { 282 background.setHotspotBounds(mScrollX + left, top, mScrollX + right, bottom); 283 } 284 } 285 } 286 287 @Override 288 protected int[] onCreateDrawableState(int extraSpace) { 289 final int[] drawableState = super.onCreateDrawableState(extraSpace + 1); 290 if (isChecked()) { 291 mergeDrawableStates(drawableState, CHECKED_STATE_SET); 292 } 293 return drawableState; 294 } 295 296 @Override 297 protected void drawableStateChanged() { 298 super.drawableStateChanged(); 299 300 if (mCheckMarkDrawable != null) { 301 int[] myDrawableState = getDrawableState(); 302 303 // Set the state of the Drawable 304 mCheckMarkDrawable.setState(myDrawableState); 305 306 invalidate(); 307 } 308 } 309 310 @Override 311 public void drawableHotspotChanged(float x, float y) { 312 super.drawableHotspotChanged(x, y); 313 314 if (mCheckMarkDrawable != null) { 315 mCheckMarkDrawable.setHotspot(x, y); 316 } 317 } 318 319 @Override 320 public void onInitializeAccessibilityEvent(AccessibilityEvent event) { 321 super.onInitializeAccessibilityEvent(event); 322 event.setClassName(CheckedTextView.class.getName()); 323 event.setChecked(mChecked); 324 } 325 326 @Override 327 public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { 328 super.onInitializeAccessibilityNodeInfo(info); 329 info.setClassName(CheckedTextView.class.getName()); 330 info.setCheckable(true); 331 info.setChecked(mChecked); 332 } 333} 334