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