/*
* Copyright (c) 2016 Google Inc.
*
* 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 com.google.samples.apps.iosched.ui.widget;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.content.res.ResourcesCompat;
import android.util.AttributeSet;
import android.view.View;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
import android.widget.ImageView;
import android.widget.LinearLayout;
import com.google.samples.apps.iosched.R;
import com.google.samples.apps.iosched.util.AccessibilityUtils;
/**
* Replacement for a {@link android.widget.RatingBar} widget for providing ratings for sessions.
* Provides ease of use for accessibility for use with Talkback and other assistive technologies.
* <p/>
* TODO: add explanation of attributes.
*/
public class CustomRatingBar extends LinearLayout implements View.OnClickListener {
/**
* The default maximum rating value if no specific value is specified.
*/
private static final int DEFAULT_MAX_RATING = 5;
/**
* The drawable used if no unfilled drawable resource is specified.
*/
private static final int DEFAULT_UNFILLED_DRAWABLE_ID = R.drawable.ratingbar_star_unfilled;
/**
* The drawable used if no filled drawable resource is specified.
*/
private static final int DEFAULT_FILLED_DRAWABLE_ID = R.drawable.ratingbar_star_filled;
/**
* The maximum permitted rating.
*/
private int mMaxRating;
/**
* The {@link Drawable} used to show a filled value. A rating of M out of N stars is
* represented by M filled drawables and N - M unfilled drawables.
*/
private Drawable mFilledDrawable;
/**
* The {@link Drawable} used to show an unfilled value. A rating of M out of N stars is
* represented by M filled drawables and N - M unfilled drawables.
*/
private Drawable mUnfilledDrawable;
/**
* The rating specified by the user.
*/
private int mRating = 0;
public CustomRatingBar(Context context) {
this(context, null, 0);
}
public CustomRatingBar(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CustomRatingBar(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
TypedArray typedArray = context.getTheme().obtainStyledAttributes(
attrs,
R.styleable.CustomRatingBar,
0, 0);
try {
mMaxRating = typedArray.getInt(R.styleable.CustomRatingBar_maxRating,
DEFAULT_MAX_RATING);
mFilledDrawable = typedArray.getDrawable(R.styleable.CustomRatingBar_filledDrawable);
if (mFilledDrawable == null) {
mFilledDrawable = ResourcesCompat.getDrawable(getResources(),
DEFAULT_FILLED_DRAWABLE_ID, null);
}
mUnfilledDrawable = typedArray.getDrawable(R.styleable.CustomRatingBar_unfilledDrawable);
if (mUnfilledDrawable == null) {
mUnfilledDrawable = ResourcesCompat.getDrawable(getResources(),
DEFAULT_UNFILLED_DRAWABLE_ID, null);
}
} finally {
typedArray.recycle();
}
setSaveEnabled(true);
}
public int getRating() {
return mRating;
}
@Override
public void onClick(final View v) {
mRating = (int) v.getTag();
drawRatingViews();
int eventType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN ?
AccessibilityEvent.TYPE_ANNOUNCEMENT : AccessibilityEvent.TYPE_VIEW_FOCUSED;
sendAccessibilityEvent(eventType);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
drawRatingViews();
}
@Override
public void sendAccessibilityEvent(final int eventType) {
if (!AccessibilityUtils.isAccessibilityEnabled(getContext())) {
return;
}
super.sendAccessibilityEvent(eventType);
AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
// Get the Talkback text.
event.getText().add(getContext().getResources().getString(
R.string.feedback_rating_confirmation, mRating, mMaxRating));
event.setEnabled(true);
AccessibilityManager accessibilityManager = (AccessibilityManager) getContext()
.getSystemService(Context.ACCESSIBILITY_SERVICE);
accessibilityManager.sendAccessibilityEvent(event);
}
/**
* Creates or updates the views used for creating a rating.
*/
private void drawRatingViews() {
if (this.getChildCount() == 0) {
createRatingViews();
} else {
updateRatingViews();
}
}
/**
* Creates ({@link ImageView}s) used to submit a rating using unfilled drawables and adds them to
* the layout.
*/
private void createRatingViews() {
for (int i = 0; i < mMaxRating; i++) {
ImageView imageView = new ImageView(getContext());
imageView.setLayoutParams(
new android.view.ViewGroup.LayoutParams(LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT));
int tagValue = i + 1;
imageView.setTag(tagValue);
imageView.setContentDescription(getContext().getString(
R.string.feedback_rating_value, tagValue));
imageView.setImageDrawable(mUnfilledDrawable);
imageView.setOnClickListener(this);
addView(imageView);
}
}
/**
* Updates ({@link ImageView}s) used to submit a rating, using filled drawables to denote the
* rating created by the user.
*/
private void updateRatingViews() {
for (int i = 0; i < mMaxRating; i++) {
ImageView view = (ImageView) this.getChildAt(i);
view.setImageDrawable(i + 1 <= mRating ? mFilledDrawable : mUnfilledDrawable);
}
}
@Override
protected Parcelable onSaveInstanceState() {
Parcelable superState = super.onSaveInstanceState();
SavedState savedState = new SavedState(superState);
savedState.rating = mRating;
return savedState;
}
@Override
protected void onRestoreInstanceState(final Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mRating = ss.rating;
drawRatingViews();
}
/**
* Helper class to help retain state during orientation change.
*/
private static class SavedState extends BaseSavedState {
int rating;
SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel in) {
super(in);
rating = in.readInt();
}
@Override
public void writeToParcel(Parcel out, int flags) {
super.writeToParcel(out, flags);
out.writeInt(rating);
}
public static final Parcelable.Creator<SavedState> CREATOR
= new Parcelable.Creator<SavedState>() {
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
}
}