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