/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.widget; import android.content.Context; import android.content.res.TypedArray; import android.graphics.drawable.shapes.RectShape; import android.graphics.drawable.shapes.Shape; import android.util.AttributeSet; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo; import com.android.internal.R; /** * RatingBar 是基于 SeekBar 和 ProgressBar 的扩展,用星型来显示等级评定. * 使用 RatingBar 的默认大小时,用户可以触摸、拖动或使用方向键来设置评分. * 它有小 RatingBar 样式({@link android.R.attr#ratingBarStyleSmall}) * 和大的({@link android.R.attr#ratingBarStyleIndicator})只用于显示的两种样式. * 大的样式不支持用户交互,仅能用于显示. * <p> * 当使用可以支持用户交互的 RatingBar 时,无论将小部件放在它的左边还是右边都是不合适的. * <p> * 只有当布局的宽被设置为“<code>wrap content</code>”时,设置的星星数量 * (通过函数 {@link #setNumStars(int)} 或者在 XML 布局文件中定义)将显示出来 * (如果宽度设置为其他布局模式,结果不可预知). * <p> * 次级进度一般不应该被修改,因为他仅仅是被当作星型部分内部的填充背景. * * @attr ref android.R.styleable#RatingBar_numStars * @attr ref android.R.styleable#RatingBar_rating * @attr ref android.R.styleable#RatingBar_stepSize * @attr ref android.R.styleable#RatingBar_isIndicator * @author translate by wallace2010 * @author translate by madgoat * @author convert by cnmahj */ public class RatingBar extends AbsSeekBar { /** * 当评分等级改变时通知客户端的回调函数. * 这包括用户通过手势、方向键或轨迹球触发的改变,以及编程触发的改变. */ public interface OnRatingBarChangeListener { /** * 通知评分等级已经被修改. * 客户端可以使用 fromUser 参数区分用户触发的改变还是编程触发的改变. * 当用户拖拽时,将不会连续不断的被调用,仅仅当用户最终离开触摸结束评分时调用. * * @param ratingBar 评分修改的 RatingBar. * @param rating 当前评分分数.取值范围为0到星型的数量. * @param fromUser 如果评分改变是由用户触摸手势、方向键或轨迹球移动触发的,则返回 true. */ void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser); } private int mNumStars = 5; private int mProgressOnStartTracking; private OnRatingBarChangeListener mOnRatingBarChangeListener; public RatingBar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RatingBar, defStyle, 0); final int numStars = a.getInt(R.styleable.RatingBar_numStars, mNumStars); setIsIndicator(a.getBoolean(R.styleable.RatingBar_isIndicator, !mIsUserSeekable)); final float rating = a.getFloat(R.styleable.RatingBar_rating, -1); final float stepSize = a.getFloat(R.styleable.RatingBar_stepSize, -1); a.recycle(); if (numStars > 0 && numStars != mNumStars) { setNumStars(numStars); } if (stepSize >= 0) { setStepSize(stepSize); } else { setStepSize(0.5f); } if (rating >= 0) { setRating(rating); } // A touch inside a star fill up to that fractional area (slightly more // than 1 so boundaries round up). mTouchProgressOffset = 1.1f; } public RatingBar(Context context, AttributeSet attrs) { this(context, attrs, com.android.internal.R.attr.ratingBarStyle); } public RatingBar(Context context) { this(context, null); } /** * 设置当评分等级发生改变时回调的监听器. * * @param listener 监听器. */ public void setOnRatingBarChangeListener(OnRatingBarChangeListener listener) { mOnRatingBarChangeListener = listener; } /** * @return 监听评分改变事件的监听器(可能为空). */ public OnRatingBarChangeListener getOnRatingBarChangeListener() { return mOnRatingBarChangeListener; } /** * 设置当前的评分条是否仅仅是个指示器(这样用户就不能进行修改操作了) * * @param isIndicator 是否是一个指示器. * * @attr ref android.R.styleable#RatingBar_isIndicator */ public void setIsIndicator(boolean isIndicator) { mIsUserSeekable = !isIndicator; setFocusable(!isIndicator); } /** * @return 判断当前的评分条是否仅仅是一个指示器(注:即能否被修改). * * @attr ref android.R.styleable#RatingBar_isIndicator */ public boolean isIndicator() { return !mIsUserSeekable; } /** * 设置显示的星型的数量.为了能够正常显示它们,建议将当前小部件的布局宽度设置为 * “<code>wrap content</code>”. * * @param numStars 星型的数量. */ public void setNumStars(final int numStars) { if (numStars <= 0) { return; } mNumStars = numStars; // This causes the width to change, so re-layout requestLayout(); } /** * 返回显示的星型数量. * @return 显示的星型数量. */ public int getNumStars() { return mNumStars; } /** * 设置分数(星型的数量). * * @param rating 设置的分数. */ public void setRating(float rating) { setProgress(Math.round(rating * getProgressPerStar())); } /** * 获取当前的评分(填充的星型的数量). * * @return 当前的评分. */ public float getRating() { return getProgress() / getProgressPerStar(); } /** * 设置当前评分条的步长(粒度). * * @param stepSize 评分条的步进.例如:如果想要半个星星的粒度,则它的值为 0.5. */ public void setStepSize(float stepSize) { if (stepSize <= 0) { return; } final float newMax = mNumStars / stepSize; final int newProgress = (int) (newMax / getMax() * getProgress()); setMax((int) newMax); setProgress(newProgress); } /** * 获取评分条的步长. * * @return 步长. */ public float getStepSize() { return (float) getNumStars() / getMax(); } /** * @return The amount of progress that fits into a star */ private float getProgressPerStar() { if (mNumStars > 0) { return 1f * getMax() / mNumStars; } else { return 1; } } @Override Shape getDrawableShape() { // TODO: Once ProgressBar's TODOs are fixed, this won't be needed return new RectShape(); } @Override void onProgressRefresh(float scale, boolean fromUser) { super.onProgressRefresh(scale, fromUser); // Keep secondary progress in sync with primary updateSecondaryProgress(getProgress()); if (!fromUser) { // Callback for non-user rating changes dispatchRatingChange(false); } } /** * The secondary progress is used to differentiate the background of a * partially filled star. This method keeps the secondary progress in sync * with the progress. * * @param progress The primary progress level. */ private void updateSecondaryProgress(int progress) { final float ratio = getProgressPerStar(); if (ratio > 0) { final float progressInStars = progress / ratio; final int secondaryProgress = (int) (Math.ceil(progressInStars) * ratio); setSecondaryProgress(secondaryProgress); } } @Override protected synchronized void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mSampleTile != null) { // TODO: Once ProgressBar's TODOs are gone, this can be done more // cleanly than mSampleTile final int width = mSampleTile.getWidth() * mNumStars; setMeasuredDimension(resolveSizeAndState(width, widthMeasureSpec, 0), getMeasuredHeight()); } } @Override void onStartTrackingTouch() { mProgressOnStartTracking = getProgress(); super.onStartTrackingTouch(); } @Override void onStopTrackingTouch() { super.onStopTrackingTouch(); if (getProgress() != mProgressOnStartTracking) { dispatchRatingChange(true); } } @Override void onKeyChange() { super.onKeyChange(); dispatchRatingChange(true); } void dispatchRatingChange(boolean fromUser) { if (mOnRatingBarChangeListener != null) { mOnRatingBarChangeListener.onRatingChanged(this, getRating(), fromUser); } } @Override public synchronized void setMax(int max) { // Disallow max progress = 0 if (max <= 0) { return; } super.setMax(max); } @Override public void onInitializeAccessibilityEvent(AccessibilityEvent event) { super.onInitializeAccessibilityEvent(event); event.setClassName(RatingBar.class.getName()); } @Override public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfo(info); info.setClassName(RatingBar.class.getName()); } }