package com.google.appinventor.components.runtime; import android.R; import android.graphics.PorterDuff; import android.graphics.drawable.ClipDrawable; import android.graphics.drawable.LayerDrawable; import android.util.Log; import android.view.View; import android.widget.SeekBar; import com.google.appinventor.components.annotations.DesignerComponent; import com.google.appinventor.components.annotations.DesignerProperty; import com.google.appinventor.components.annotations.PropertyCategory; import com.google.appinventor.components.annotations.SimpleEvent; import com.google.appinventor.components.annotations.SimpleObject; import com.google.appinventor.components.annotations.SimpleProperty; import com.google.appinventor.components.common.ComponentCategory; import com.google.appinventor.components.common.PropertyTypeConstants; import com.google.appinventor.components.common.YaVersion; import com.google.appinventor.components.runtime.util.SdkLevel; /** * This class is used to display a Slider. * <p>The Slider is a progress bar that adds a draggable thumb. You can touch the thumb and drag * left or right to set the slider thumb position. As Slider thumb is dragged, it will trigger * PositionChanged event, reporting the position of the Slider thumb. The Slider uses the following * default values. However these values can be changed through designer or block editor * <ul> * <li>MinValue</li> * <li>MaxValue</li> * <li>ThumbPosition</li> * </ul></p> * * @author kashi01@gmail.com (M. Hossein Amerkashi) * @author hal@mit.edu (Hal Abelson) */ @DesignerComponent(version = YaVersion.SLIDER_COMPONENT_VERSION, description = "A Slider is a progress bar that adds a draggable thumb. You can touch " + "the thumb and drag left or right to set the slider thumb position. " + "As the Slider thumb is dragged, it will trigger the PositionChanged event, " + "reporting the position of the Slider thumb. The reported position of the " + "Slider thumb can be used to dynamically update another component " + "attribute, such as the font size of a TextBox or the radius of a Ball.", category = ComponentCategory.USERINTERFACE) @SimpleObject public class Slider extends AndroidViewComponent implements SeekBar.OnSeekBarChangeListener { private final static String LOG_TAG = "Slider"; private static final boolean DEBUG = false; private final SeekBar seekbar; // slider mix, max, and thumb positions private float minValue; private float maxValue; // thumbPosition is a number between minValue and maxValue private float thumbPosition; private boolean thumbEnabled; // the total slider width private LayerDrawable fullBar; // the part of the slider to the left of the thumb private ClipDrawable beforeThumb; // colors of the bar after and before the thumb position private int rightColor; private int leftColor; private final static int initialRightColor = Component.COLOR_GRAY; private final static String initialRightColorString = Component.DEFAULT_VALUE_COLOR_GRAY; private final static int initialLeftColor = Component.COLOR_ORANGE; private final static String initialLeftColorString = Component.DEFAULT_VALUE_COLOR_ORANGE; // seekbar.getThumb was introduced in API level 16 and the component warns the user // that apps using Sliders won't work if the API level is below 16. But for very old systems the // app won't even *load* because the verifier will reject getThumb. I don't know how old - the // rejection happens on Donut but not on Gingerbread. // The purpose of SeekBarHelper class is to avoid getting rejected by the Android verifier when the // Slider component code is loaded into a device with API level less than Gingerbread. // We do this trick by putting the use of getThumb in the class SeekBarHelper and arranging for // the class to be compiled only if the API level is at least Gingerbread. This same trick is // used in implementing the Sound component. private class SeekBarHelper { public void getThumb(int alpha) { seekbar.getThumb().mutate().setAlpha(alpha); } } public final boolean referenceGetThumb = (SdkLevel.getLevel() >= SdkLevel.LEVEL_JELLYBEAN); /** * Creates a new Slider component. * * @param container container that the component will be placed in */ public Slider(ComponentContainer container) { super(container); seekbar = new SeekBar(container.$context()); fullBar = (LayerDrawable) seekbar.getProgressDrawable(); beforeThumb = (ClipDrawable) fullBar.findDrawableByLayerId(R.id.progress); leftColor = initialLeftColor; rightColor = initialRightColor; setSliderColors(); // Adds the component to its designated container container.$add(this); // Initial property values minValue = Component.SLIDER_MIN_VALUE; maxValue = Component.SLIDER_MAX_VALUE; thumbPosition = Component.SLIDER_THUMB_VALUE; thumbEnabled = true; seekbar.setOnSeekBarChangeListener(this); //NOTE(kashi01): The boundaries for Seekbar are between 0-100 and there is no lower-limit that could // be set. We keep the SeekBar effectively at [0-100] and calculate thumb position within that // range. seekbar.setMax(100); // Based on given minValue, maxValue, and thumbPosition, determine where the seekbar // thumb position would be within normal SeekBar 0-100 range // !!! check this. maybe don't want to pass the args??? setSeekbarPosition(); if (DEBUG) { Log.d(LOG_TAG, "Slider initial min, max, thumb values are: " + MinValue() + "/" + MaxValue() + "/" + ThumbPosition()); } if (DEBUG) { Log.d(LOG_TAG, "API level is " + SdkLevel.getLevel()); } } // NOTE(hal): On old phones, up through 2.2.2 and maybe higher, the color of the bar doesn't // change until the thumb is moved. I'm ignoring that problem. private void setSliderColors() { fullBar.setColorFilter(rightColor,PorterDuff.Mode.SRC); beforeThumb.setColorFilter(leftColor, PorterDuff.Mode.SRC); } /** * Given min, max, thumb position, this method determines where the thumb position would be * within normal SeekBar 0-100 range * * @param min value for slider * @param max value for slider * @param thumb the slider thumb position */ // Set the seekbar position based on minValue, maxValue, and thumbPosition // seekbar position is an integer in the range [0,100] and is determined by MinValue, // MaxValue and ThumbPosition private void setSeekbarPosition() { float seekbarPosition = ((thumbPosition - minValue) / (maxValue - minValue)) * 100; if (DEBUG) { Log.d(LOG_TAG, "Trying to recalculate seekbar position " + minValue + "/" + maxValue + "/" + thumbPosition + "/" + seekbarPosition); } // Set the thumb position on the seekbar seekbar.setProgress((int) seekbarPosition); } /** * Sets whether or not the slider thumb should be shown * * @param enabled Whether or not the slider thumb should be shown */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_BOOLEAN, defaultValue = "True") @SimpleProperty(description = "Sets whether or not to display the slider thumb.", userVisible = true) public void ThumbEnabled(boolean enabled) { thumbEnabled = enabled; int alpha = thumbEnabled ? 255 : 0; if (referenceGetThumb) { new SeekBarHelper().getThumb(alpha); } seekbar.setEnabled(thumbEnabled); } /** * Whether or not the slider thumb is being be shown * * @return Whether or not the slider thumb is being be shown */ @SimpleProperty(category = PropertyCategory.APPEARANCE, description = "Returns whether or not the slider thumb is being be shown", userVisible = true) public boolean ThumbEnabled() { return thumbEnabled; } /** * Sets the slider thumb position. * * @param position the position of the slider thumb. This value should be between * sliderMinValue and sliderMaxValue. If this value is not within the min and * max, then it will be calculated. */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_FLOAT, defaultValue = Component.SLIDER_THUMB_VALUE + "") @SimpleProperty(description = "Sets the position of the slider thumb. " + "If this value is greater than MaxValue, then it will be set to same value as MaxValue. " + "If this value is less than MinValue, then it will be set to same value as MinValue.", userVisible = true) public void ThumbPosition(float position) { // constrain thumbPosition between minValue and maxValue thumbPosition = Math.max(Math.min(position, maxValue), minValue); if (DEBUG) { Log.d(LOG_TAG, "ThumbPosition is set to: " + thumbPosition);} setSeekbarPosition(); PositionChanged(thumbPosition); } /** * Returns the position of slider thumb * * @return the slider thumb position */ @SimpleProperty(category = PropertyCategory.APPEARANCE, description = "Returns the position of slider thumb", userVisible = true) public float ThumbPosition() { return thumbPosition; } @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_FLOAT, defaultValue = Component.SLIDER_MIN_VALUE + "") @SimpleProperty(description = "Sets the minimum value of slider. Changing the minimum value " + "also resets Thumbposition to be halfway between the (new) minimum and the maximum. " + "If the new minimum is greater than the current maximum, then minimum and maximum will " + "both be set to this value. Setting MinValue resets the thumb position to halfway " + "between MinValue and MaxValue and signals the PositionChanged event.", userVisible = true) public void MinValue(float value) { minValue = value; // increase maxValue if necessary to accommodate the new minimum maxValue = Math.max(value, maxValue); if (DEBUG) { Log.d(LOG_TAG, "Min value is set to: " + value); } ThumbPosition ((minValue + maxValue) / 2.0f); } /** * Returns the value of slider min value. * * @return the value of slider min value. */ @SimpleProperty(category = PropertyCategory.APPEARANCE, description = "Returns the value of slider min value.", userVisible = true) public float MinValue() { return minValue; } @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_FLOAT, defaultValue = Component.SLIDER_MAX_VALUE + "") @SimpleProperty(description = "Sets the maximum value of slider. Changing the maximum value " + "also resets Thumbposition to be halfway between the minimum and the (new) maximum. " + "If the new maximum is less than the current minimum, then minimum and maximum will both " + "be set to this value. Setting MaxValue resets the thumb position to halfway " + "between MinValue and MaxValue and signals the PositionChanged event.", userVisible = true) public void MaxValue(float value) { maxValue = value; minValue = Math.min(value, minValue); if (DEBUG) { Log.d (LOG_TAG, "Max value is set to: " + value); } ThumbPosition ((minValue + maxValue) / 2.0f); } /** * Returns the slider max value * * @return the slider max value */ @SimpleProperty(category = PropertyCategory.APPEARANCE, description = "Returns the slider max value.", userVisible = true) public float MaxValue() { return maxValue; } /** * Returns the color of the slider bar to the left of the thumb, as an alpha-red-green-blue * integer, i.e., {@code 0xAARRGGBB}. An alpha of {@code 00} * indicates fully transparent and {@code FF} means opaque. * * @return left color in the format 0xAARRGGBB, which includes * alpha, red, green, and blue components */ @SimpleProperty( description = "The color of slider to the left of the thumb.", category = PropertyCategory.APPEARANCE) public int ColorLeft() { return leftColor; } /** * Specifies the color of the slider bar to the left of the thumb as an alpha-red-green-blue * integer, i.e., {@code 0xAARRGGBB}. An alpha of {@code 00} * indicates fully transparent and {@code FF} means opaque. * * @param argb background color in the format 0xAARRGGBB, which * includes alpha, red, green, and blue components */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_COLOR, defaultValue = initialLeftColorString) @SimpleProperty public void ColorLeft(int argb) { leftColor = argb; setSliderColors(); } /** * Returns the color of the slider bar to the right of the thumb, as an alpha-red-green-blue * integer, i.e., {@code 0xAARRGGBB}. An alpha of {@code 00} * indicates fully transparent and {@code FF} means opaque. * * @return right color in the format 0xAARRGGBB, which includes * alpha, red, green, and blue components */ @SimpleProperty( description = "The color of slider to the left of the thumb.", category = PropertyCategory.APPEARANCE) public int ColorRight() { return rightColor; } /** * Specifies the color of the slider bar to the right of the thumb as an alpha-red-green-blue * integer, i.e., {@code 0xAARRGGBB}. An alpha of {@code 00} * indicates fully transparent and {@code FF} means opaque. * * @param argb background color in the format 0xAARRGGBB, which * includes alpha, red, green, and blue components */ @DesignerProperty(editorType = PropertyTypeConstants.PROPERTY_TYPE_COLOR, defaultValue = initialRightColorString) @SimpleProperty public void ColorRight(int argb) { rightColor = argb; setSliderColors(); } @Override public View getView() { return seekbar; } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { //progress has been changed. Set the sliderThumbPosition and then trigger the event //Now convert this progress value (which is between 0-100), back to a value between the //range that user has set within minValue, maxValue thumbPosition = ((maxValue - minValue) * (float) progress / 100) + minValue; if (DEBUG) { Log.d(LOG_TAG, "onProgressChanged progress value [0-100]: " + progress + ", reporting to user as: " + thumbPosition); } //Trigger the event, reporting this new value PositionChanged(thumbPosition); } /** * Indicates that position of the slider thumb has changed. */ @SimpleEvent public void PositionChanged(float thumbPosition) { EventDispatcher.dispatchEvent(this, "PositionChanged", thumbPosition); } @Override public void onStartTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub } @Override public void onStopTrackingTouch(SeekBar seekBar) { // TODO Auto-generated method stub } /** * Returns the component's vertical height, measured in pixels. * * @return height in pixels */ @Override public int Height() { //NOTE(kashi01): overriding and removing the annotation, because we don't want to give user //ability to change the slider height and don't want display this in our block editor return getView().getHeight(); } /** * Specifies the component's vertical height, measured in pixels. * * @param height in pixels */ @Override public void Height(int height) { //NOTE(kashi01): overriding and removing the annotation, because we don't want to give user //ability to change the slider height and don't want display this in our block editor container.setChildHeight(this, height); } }