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