• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package android.widget;
18 
19 import android.content.Context;
20 import android.content.res.TypedArray;
21 import android.graphics.drawable.shapes.RectShape;
22 import android.graphics.drawable.shapes.Shape;
23 import android.util.AttributeSet;
24 
25 import com.android.internal.R;
26 
27 /**
28  * A RatingBar is an extension of SeekBar and ProgressBar that shows a rating in
29  * stars. The user can touch/drag or use arrow keys to set the rating when using
30  * the default size RatingBar. The smaller RatingBar style (
31  * {@link android.R.attr#ratingBarStyleSmall}) and the larger indicator-only
32  * style ({@link android.R.attr#ratingBarStyleIndicator}) do not support user
33  * interaction and should only be used as indicators.
34  * <p>
35  * When using a RatingBar that supports user interaction, placing widgets to the
36  * left or right of the RatingBar is discouraged.
37  * <p>
38  * The number of stars set (via {@link #setNumStars(int)} or in an XML layout)
39  * will be shown when the layout width is set to wrap content (if another layout
40  * width is set, the results may be unpredictable).
41  * <p>
42  * The secondary progress should not be modified by the client as it is used
43  * internally as the background for a fractionally filled star.
44  *
45  * @attr ref android.R.styleable#RatingBar_numStars
46  * @attr ref android.R.styleable#RatingBar_rating
47  * @attr ref android.R.styleable#RatingBar_stepSize
48  * @attr ref android.R.styleable#RatingBar_isIndicator
49  */
50 public class RatingBar extends AbsSeekBar {
51 
52     /**
53      * A callback that notifies clients when the rating has been changed. This
54      * includes changes that were initiated by the user through a touch gesture
55      * or arrow key/trackball as well as changes that were initiated
56      * programmatically.
57      */
58     public interface OnRatingBarChangeListener {
59 
60         /**
61          * Notification that the rating has changed. Clients can use the
62          * fromUser parameter to distinguish user-initiated changes from those
63          * that occurred programmatically. This will not be called continuously
64          * while the user is dragging, only when the user finalizes a rating by
65          * lifting the touch.
66          *
67          * @param ratingBar The RatingBar whose rating has changed.
68          * @param rating The current rating. This will be in the range
69          *            0..numStars.
70          * @param fromUser True if the rating change was initiated by a user's
71          *            touch gesture or arrow key/horizontal trackbell movement.
72          */
onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser)73         void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser);
74 
75     }
76 
77     private int mNumStars = 5;
78 
79     private int mProgressOnStartTracking;
80 
81     private OnRatingBarChangeListener mOnRatingBarChangeListener;
82 
RatingBar(Context context, AttributeSet attrs, int defStyle)83     public RatingBar(Context context, AttributeSet attrs, int defStyle) {
84         super(context, attrs, defStyle);
85 
86         TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RatingBar,
87                 defStyle, 0);
88         final int numStars = a.getInt(R.styleable.RatingBar_numStars, mNumStars);
89         setIsIndicator(a.getBoolean(R.styleable.RatingBar_isIndicator, !mIsUserSeekable));
90         final float rating = a.getFloat(R.styleable.RatingBar_rating, -1);
91         final float stepSize = a.getFloat(R.styleable.RatingBar_stepSize, -1);
92         a.recycle();
93 
94         if (numStars > 0 && numStars != mNumStars) {
95             setNumStars(numStars);
96         }
97 
98         if (stepSize >= 0) {
99             setStepSize(stepSize);
100         } else {
101             setStepSize(0.5f);
102         }
103 
104         if (rating >= 0) {
105             setRating(rating);
106         }
107 
108         // A touch inside a star fill up to that fractional area (slightly more
109         // than 1 so boundaries round up).
110         mTouchProgressOffset = 1.1f;
111     }
112 
RatingBar(Context context, AttributeSet attrs)113     public RatingBar(Context context, AttributeSet attrs) {
114         this(context, attrs, com.android.internal.R.attr.ratingBarStyle);
115     }
116 
RatingBar(Context context)117     public RatingBar(Context context) {
118         this(context, null);
119     }
120 
121     /**
122      * Sets the listener to be called when the rating changes.
123      *
124      * @param listener The listener.
125      */
setOnRatingBarChangeListener(OnRatingBarChangeListener listener)126     public void setOnRatingBarChangeListener(OnRatingBarChangeListener listener) {
127         mOnRatingBarChangeListener = listener;
128     }
129 
130     /**
131      * @return The listener (may be null) that is listening for rating change
132      *         events.
133      */
getOnRatingBarChangeListener()134     public OnRatingBarChangeListener getOnRatingBarChangeListener() {
135         return mOnRatingBarChangeListener;
136     }
137 
138     /**
139      * Whether this rating bar should only be an indicator (thus non-changeable
140      * by the user).
141      *
142      * @param isIndicator Whether it should be an indicator.
143      */
setIsIndicator(boolean isIndicator)144     public void setIsIndicator(boolean isIndicator) {
145         mIsUserSeekable = !isIndicator;
146         setFocusable(!isIndicator);
147     }
148 
149     /**
150      * @return Whether this rating bar is only an indicator.
151      */
isIndicator()152     public boolean isIndicator() {
153         return !mIsUserSeekable;
154     }
155 
156     /**
157      * Sets the number of stars to show. In order for these to be shown
158      * properly, it is recommended the layout width of this widget be wrap
159      * content.
160      *
161      * @param numStars The number of stars.
162      */
setNumStars(final int numStars)163     public void setNumStars(final int numStars) {
164         if (numStars <= 0) {
165             return;
166         }
167 
168         mNumStars = numStars;
169 
170         // This causes the width to change, so re-layout
171         requestLayout();
172     }
173 
174     /**
175      * Returns the number of stars shown.
176      * @return The number of stars shown.
177      */
getNumStars()178     public int getNumStars() {
179         return mNumStars;
180     }
181 
182     /**
183      * Sets the rating (the number of stars filled).
184      *
185      * @param rating The rating to set.
186      */
setRating(float rating)187     public void setRating(float rating) {
188         setProgress(Math.round(rating * getProgressPerStar()));
189     }
190 
191     /**
192      * Gets the current rating (number of stars filled).
193      *
194      * @return The current rating.
195      */
getRating()196     public float getRating() {
197         return getProgress() / getProgressPerStar();
198     }
199 
200     /**
201      * Sets the step size (granularity) of this rating bar.
202      *
203      * @param stepSize The step size of this rating bar. For example, if
204      *            half-star granularity is wanted, this would be 0.5.
205      */
setStepSize(float stepSize)206     public void setStepSize(float stepSize) {
207         if (stepSize <= 0) {
208             return;
209         }
210 
211         final float newMax = mNumStars / stepSize;
212         final int newProgress = (int) (newMax / getMax() * getProgress());
213         setMax((int) newMax);
214         setProgress(newProgress);
215     }
216 
217     /**
218      * Gets the step size of this rating bar.
219      *
220      * @return The step size.
221      */
getStepSize()222     public float getStepSize() {
223         return (float) getNumStars() / getMax();
224     }
225 
226     /**
227      * @return The amount of progress that fits into a star
228      */
getProgressPerStar()229     private float getProgressPerStar() {
230         if (mNumStars > 0) {
231             return 1f * getMax() / mNumStars;
232         } else {
233             return 1;
234         }
235     }
236 
237     @Override
getDrawableShape()238     Shape getDrawableShape() {
239         // TODO: Once ProgressBar's TODOs are fixed, this won't be needed
240         return new RectShape();
241     }
242 
243     @Override
onProgressRefresh(float scale, boolean fromUser)244     void onProgressRefresh(float scale, boolean fromUser) {
245         super.onProgressRefresh(scale, fromUser);
246 
247         // Keep secondary progress in sync with primary
248         updateSecondaryProgress(getProgress());
249 
250         if (!fromUser) {
251             // Callback for non-user rating changes
252             dispatchRatingChange(false);
253         }
254     }
255 
256     /**
257      * The secondary progress is used to differentiate the background of a
258      * partially filled star. This method keeps the secondary progress in sync
259      * with the progress.
260      *
261      * @param progress The primary progress level.
262      */
updateSecondaryProgress(int progress)263     private void updateSecondaryProgress(int progress) {
264         final float ratio = getProgressPerStar();
265         if (ratio > 0) {
266             final float progressInStars = progress / ratio;
267             final int secondaryProgress = (int) (Math.ceil(progressInStars) * ratio);
268             setSecondaryProgress(secondaryProgress);
269         }
270     }
271 
272     @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)273     protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
274         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
275 
276         if (mSampleTile != null) {
277             // TODO: Once ProgressBar's TODOs are gone, this can be done more
278             // cleanly than mSampleTile
279             final int width = mSampleTile.getWidth() * mNumStars;
280             setMeasuredDimension(resolveSize(width, widthMeasureSpec), mMeasuredHeight);
281         }
282     }
283 
284     @Override
onStartTrackingTouch()285     void onStartTrackingTouch() {
286         mProgressOnStartTracking = getProgress();
287 
288         super.onStartTrackingTouch();
289     }
290 
291     @Override
onStopTrackingTouch()292     void onStopTrackingTouch() {
293         super.onStopTrackingTouch();
294 
295         if (getProgress() != mProgressOnStartTracking) {
296             dispatchRatingChange(true);
297         }
298     }
299 
300     @Override
onKeyChange()301     void onKeyChange() {
302         super.onKeyChange();
303         dispatchRatingChange(true);
304     }
305 
dispatchRatingChange(boolean fromUser)306     void dispatchRatingChange(boolean fromUser) {
307         if (mOnRatingBarChangeListener != null) {
308             mOnRatingBarChangeListener.onRatingChanged(this, getRating(),
309                     fromUser);
310         }
311     }
312 
313     @Override
setMax(int max)314     public synchronized void setMax(int max) {
315         // Disallow max progress = 0
316         if (max <= 0) {
317             return;
318         }
319 
320         super.setMax(max);
321     }
322 
323 }
324