Java tutorial
/* * Copyright 2014 - 2016 Michael Rapp * * 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 de.mrapp.android.preference; import android.annotation.TargetApi; import android.content.Context; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; import android.support.annotation.AttrRes; import android.support.annotation.ColorInt; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.StyleRes; import android.support.v4.content.ContextCompat; import android.util.AttributeSet; import android.view.Gravity; import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.LinearLayout.LayoutParams; import java.util.Locale; import de.mrapp.android.preference.multithreading.ColorPreviewLoader; import de.mrapp.android.util.view.AbstractSavedState; import static de.mrapp.android.util.Condition.ensureAtLeast; import static de.mrapp.android.util.Condition.ensureNotNull; /** * An abstract base class for all preferences, which allow to choose a color. * * @author Michael Rapp * @since 1.4.0 */ public abstract class AbstractColorPickerPreference extends DialogPreference { /** * The formats, which are supported to print textual representations of colors. */ public enum ColorFormat { /** * Formats the color by printing its red, green and blue components. */ RGB(0), /** * Formats the color by printing its alpha, red, green and blue components. */ ARGB(1), /** * Formats the color as a hexadecimal string, consisting of three bytes. */ HEX_3_BYTES(2), /** * Formats the color as a hexadecimal string, consisting of four bytes, including its alpha * component. */ HEX_4_BYTES(3); /** * The value of the format. */ private final int value; /** * Creates a new format. * * @param value * The value of the format as an {@link Integer} value */ ColorFormat(final int value) { this.value = value; } /** * Returns the value of the format. * * @return The value of the format as an {@link Integer} value */ public final int getValue() { return value; } /** * Returns the format, which corresponds to a specific value. * * @param value * The value of the format, which should be returned, as an {@link Integer} value * @return The format, which corresponds to the given value, as an instance of the enum * {@link ColorFormat} */ public static ColorFormat fromValue(final int value) { for (ColorFormat format : values()) { if (format.getValue() == value) { return format; } } throw new IllegalArgumentException("Invalid enum value"); } } /** * Contains all shapes, which can be used to show previews of colors. */ public enum PreviewShape { /** * If the preview is shaped as a circle. */ CIRCLE(0), /** * If the preview is shaped as a square. */ SQUARE(1); /** * The value of the shape. */ private final int value; /** * Creates a new shape. * * @param value * The value of the shape as an {@link Integer} value */ PreviewShape(final int value) { this.value = value; } /** * Returns the value of the shape. * * @return The value of the shape as an {@link Integer} value */ public final int getValue() { return value; } /** * Returns the shape, which corresponds to a specific value. * * @param value * The value of the shape, which should be returned, as an {@link Integer} value * @return The shape, which corresponds to the given value, as an instance of the enum * {@link PreviewShape} */ public static PreviewShape fromValue(final int value) { for (PreviewShape shape : values()) { if (shape.getValue() == value) { return shape; } } throw new IllegalArgumentException("Invalid enum value"); } } /** * A data structure, which allows to save the internal state of an {@link * AbstractColorPickerPreference}. */ public static class SavedState extends AbstractSavedState { /** * A creator, which allows to create instances of the class {@link SavedState} from * parcels. */ public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>() { @Override public SavedState createFromParcel(final Parcel in) { return new SavedState(in); } @Override public SavedState[] newArray(final int size) { return new SavedState[size]; } }; /** * The saved value of the attribute "color". */ public int color; /** * Creates a new data structure, which allows to store the internal state of an {@link * AbstractColorPickerPreference}. This constructor is called by derived classes when saving * their states. * * @param superState * The state of the superclass of this view, as an instance of the type {@link * Parcelable}. The state may not be null */ public SavedState(@NonNull final Parcelable superState) { super(superState); } /** * Creates a new data structure, which allows to store the internal state of an {@link * AbstractColorPickerPreference}. This constructor is used when reading from a parcel. It * reads the state of the superclass. * * @param source * The parcel to read read from as a instance of the class {@link Parcel}. The * parcel may not be null */ public SavedState(@NonNull final Parcel source) { super(source); color = source.readInt(); } @Override public final void writeToParcel(final Parcel destination, final int flags) { super.writeToParcel(destination, flags); destination.writeInt(color); } } /** * The currently persisted color. */ private int color; /** * True, if a preview of the preference's color is shown, false otherwise. */ private boolean showPreview; /** * The size of the preview of the preference's color in pixels. */ private int previewSize; /** * The shape of the preview of the preference's color. */ private PreviewShape previewShape; /** * The border width of the preview of the preference's color in pixels. */ private int previewBorderWidth; /** * The border color of the preview of the preference's color. */ private int previewBorderColor; /** * The background of the preview of the preference's color. */ private Drawable previewBackground; /** * The format, which is used to print a textual representation of the preference's color. */ private ColorFormat colorFormat; /** * The image view, which is used to show a preview of the preference's color. */ private ImageView previewView; /** * The data loader, which is used to load the preview of colors asynchronously. */ private ColorPreviewLoader previewLoader; /** * Initializes the preference. * * @param attributeSet * The attribute set, the attributes should be obtained from, as an instance of the type * {@link AttributeSet} or null, if no attributes should be obtained * @param defaultStyle * The default style to apply to this preference. If 0, no style will be applied (beyond * what is included in the theme). This may either be an attribute resource, whose value * will be retrieved from the current theme, or an explicit style resource * @param defaultStyleResource * A resource identifier of a style resource that supplies default values for the * preference, used only if the default style is 0 or can not be found in the theme. Can * be 0 to not look for defaults */ private void initialize(@Nullable final AttributeSet attributeSet, @AttrRes final int defaultStyle, @StyleRes final int defaultStyleResource) { obtainStyledAttributes(attributeSet, defaultStyle, defaultStyleResource); previewLoader = new ColorPreviewLoader(getContext(), getPreviewBackground(), getPreviewShape(), getPreviewSize(), getPreviewBorderWidth(), getPreviewBorderColor()); } /** * Obtains all attributes from a specific attribute set. * * @param attributeSet * The attribute set, the attributes should be obtained from, as an instance of the type * {@link AttributeSet} or null, if no attributes should be obtained * @param defaultStyle * The default style to apply to this preference. If 0, no style will be applied (beyond * what is included in the theme). This may either be an attribute resource, whose value * will be retrieved from the current theme, or an explicit style resource * @param defaultStyleResource * A resource identifier of a style resource that supplies default values for the * preference, used only if the default style is 0 or can not be found in the theme. Can * be 0 to not look for defaults */ private void obtainStyledAttributes(@Nullable final AttributeSet attributeSet, @AttrRes final int defaultStyle, @StyleRes final int defaultStyleResource) { TypedArray typedArray = getContext().obtainStyledAttributes(attributeSet, R.styleable.AbstractColorPickerPreference, defaultStyle, defaultStyleResource); try { obtainShowPreview(typedArray); obtainPreviewSize(typedArray); obtainPreviewShape(typedArray); obtainPreviewBorderWidth(typedArray); obtainPreviewBorderColor(typedArray); obtainPreviewBackground(typedArray); obtainColorFormat(typedArray); } finally { typedArray.recycle(); } } /** * Obtains the boolean value, which specifies, whether a preview of the preference's color * should be shown, or not, from a specific typed array. * * @param typedArray * The typed array, the boolean value should be obtained from, as an instance of the * class {@link TypedArray}. The typed array may not be null */ private void obtainShowPreview(@NonNull final TypedArray typedArray) { boolean defaultValue = getContext().getResources() .getBoolean(R.bool.color_picker_preference_default_show_preview); showPreview(typedArray.getBoolean(R.styleable.AbstractColorPickerPreference_showPreview, defaultValue)); } /** * Obtains the size of the preview of the preference's color from a specific typed array. * * @param typedArray * The typed array, the size should be obtained from, as an instance of the class {@link * TypedArray}. The typed array may not be null */ private void obtainPreviewSize(@NonNull final TypedArray typedArray) { int defaultValue = getContext().getResources() .getDimensionPixelSize(R.dimen.color_picker_preference_default_preview_size); setPreviewSize(typedArray.getDimensionPixelSize(R.styleable.AbstractColorPickerPreference_previewSize, defaultValue)); } /** * Obtains the shape of the preview of the preference's color from a specific typed array. * * @param typedArray * The typed array, the shape should be obtained from, as an instance of the class * {@link TypedArray}. The typed array may not be null */ private void obtainPreviewShape(@NonNull final TypedArray typedArray) { int defaultValue = getContext().getResources() .getInteger(R.integer.color_picker_preference_default_preview_shape); setPreviewShape(PreviewShape.fromValue( typedArray.getInteger(R.styleable.AbstractColorPickerPreference_previewShape, defaultValue))); } /** * Obtains the border width of the preview of the preference's color from a specific typed * array. * * @param typedArray * The typed array, the border width should be obtained from, as an instance of the * class {@link TypedArray}. The typed array may not be null */ private void obtainPreviewBorderWidth(@NonNull final TypedArray typedArray) { int defaultValue = getContext().getResources() .getDimensionPixelSize(R.dimen.color_picker_preference_default_preview_border_width); setPreviewBorderWidth(typedArray .getDimensionPixelSize(R.styleable.AbstractColorPickerPreference_previewBorderWidth, defaultValue)); } /** * Obtains the border color of the preview of the preference's color from a specific typed * array. * * @param typedArray * The typed array, the border color should be obtained from, as an instance of the * class {@link TypedArray}. The typed array may not be null */ private void obtainPreviewBorderColor(@NonNull final TypedArray typedArray) { int defaultValue = ContextCompat.getColor(getContext(), R.color.color_picker_preference_default_preview_border_color); setPreviewBorderColor( typedArray.getColor(R.styleable.AbstractColorPickerPreference_previewBorderColor, defaultValue)); } /** * Obtains the background of the preview of the preference's color from a specific typed array. * * @param typedArray * The typed array, the background should be obtained from, as an instance of the class * {@link TypedArray}. The typed array may not be null */ private void obtainPreviewBackground(@NonNull final TypedArray typedArray) { int backgroundColor = typedArray.getColor(R.styleable.AbstractColorPickerPreference_previewBackground, -1); if (backgroundColor != -1) { setPreviewBackgroundColor(backgroundColor); } else { int resourceId = typedArray.getResourceId(R.styleable.AbstractColorPickerPreference_previewBackground, R.drawable.color_picker_default_preview_background); setPreviewBackground(ContextCompat.getDrawable(getContext(), resourceId)); } } /** * Obtains the format, which should be used to print a textual representation of the * preference's color, from a specific typed array. * * @param typedArray * The typed array, the color format should be obtained from, as an instance of the * class {@link TypedArray}. The typed array may not be null */ private void obtainColorFormat(@NonNull final TypedArray typedArray) { int defaultValue = getContext().getResources() .getInteger(R.integer.color_picker_preference_default_color_format); setColorFormat(ColorFormat.fromValue( typedArray.getInteger(R.styleable.AbstractColorPickerPreference_colorFormat, defaultValue))); } /** * Creates and returns a textual representation of a color, according to a specific format. * * @param colorFormat * The format, which should be used to format the color, as a value of the enum {@link * ColorFormat}. The format may not be null * @param color * The color, which should be formatted, as an {@link Integer} value * @return A textual representation of the given color as an instance of the type {@link * CharSequence} */ private CharSequence formatColor(final ColorFormat colorFormat, @ColorInt final int color) { ensureNotNull(colorFormat, "The color format may not be null"); if (colorFormat == ColorFormat.RGB) { return String.format(Locale.getDefault(), "R = %d, G = %d, B = %d", Color.red(color), Color.green(color), Color.blue(color)); } else if (colorFormat == ColorFormat.ARGB) { return String.format(Locale.getDefault(), "A = %d, R = %d, G = %d, B = %d", Color.alpha(color), Color.red(color), Color.green(color), Color.blue(color)); } else if (colorFormat == ColorFormat.HEX_3_BYTES) { return String.format("#%06X", (0xFFFFFF & color)); } else { return String.format("#%08X", (color)); } } /** * Adapts the view, which is used to show a preview of the preference's color, depending on the * preference's properties and the currently persisted color. */ private void adaptPreviewView() { if (previewView != null) { if (isPreviewShown()) { previewView.setVisibility(View.VISIBLE); previewView.setLayoutParams(createPreviewLayoutParams()); previewLoader.load(getColor(), previewView); } else { previewView.setVisibility(View.INVISIBLE); previewView.setImageBitmap(null); } } } /** * Creates and returns the layout params of the view, which is used to show a preview of the * preference's color, depending on the preference's properties. * * @return The layout params, which have been created, as an instance of the class {@link * LayoutParams} */ private LayoutParams createPreviewLayoutParams() { LayoutParams layoutParams = new LayoutParams(getPreviewSize(), getPreviewSize()); layoutParams.gravity = Gravity.CENTER_VERTICAL; return layoutParams; } /** * Creates a new preference, which allows to choose a color. * * @param context * The context, which should be used by the preference, as an instance of the class * {@link Context}. The context may not be null */ public AbstractColorPickerPreference(@NonNull final Context context) { this(context, null); } /** * Creates a new preference, which allows to choose a color. * * @param context * The context, which should be used by the preference, as an instance of the class * {@link Context}. The context may not be null * @param attributeSet * The attributes of the XML tag that is inflating the preference, as an instance of the * type {@link AttributeSet} or null, if no attributes are available */ public AbstractColorPickerPreference(@NonNull final Context context, @Nullable final AttributeSet attributeSet) { super(context, attributeSet); initialize(attributeSet, 0, 0); } /** * Creates a new preference, which allows to choose a color. * * @param context * The context, which should be used by the preference, as an instance of the class * {@link Context}. The context may not be null * @param attributeSet * The attributes of the XML tag that is inflating the preference, as an instance of the * type {@link AttributeSet} or null, if no attributes are available * @param defaultStyle * The default style to apply to this preference. If 0, no style will be applied (beyond * what is included in the theme). This may either be an attribute resource, whose value * will be retrieved from the current theme, or an explicit style resource */ public AbstractColorPickerPreference(@NonNull final Context context, @Nullable final AttributeSet attributeSet, @AttrRes final int defaultStyle) { super(context, attributeSet, defaultStyle); initialize(attributeSet, defaultStyle, 0); } /** * Creates a new preference, which allows to choose a color. * * @param context * The context, which should be used by the preference, as an instance of the class * {@link Context}. The context may not be null * @param attributeSet * The attributes of the XML tag that is inflating the preference, as an instance of the * type {@link AttributeSet} or null, if no attributes are available * @param defaultStyle * The default style to apply to this preference. If 0, no style will be applied (beyond * what is included in the theme). This may either be an attribute resource, whose value * will be retrieved from the current theme, or an explicit style resource * @param defaultStyleResource * A resource identifier of a style resource that supplies default values for the * preference, used only if the default style is 0 or can not be found in the theme. Can * be 0 to not look for defaults */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public AbstractColorPickerPreference(@NonNull final Context context, @Nullable final AttributeSet attributeSet, @AttrRes final int defaultStyle, @StyleRes final int defaultStyleResource) { super(context, attributeSet, defaultStyle, defaultStyleResource); initialize(attributeSet, defaultStyle, defaultStyleResource); } /** * Returns the currently persisted color of the preference. * * @return The currently persisted color as an {@link Integer} value */ public final int getColor() { return color; } /** * Sets the current color of the preference. By setting a value, it will be persisted. * * @param color * The color, which should be set, as an {@link Integer} value */ public final void setColor(@ColorInt final int color) { if (this.color != color) { this.color = color; persistInt(color); notifyChanged(); adaptPreviewView(); } } /** * Returns, whether a preview of the preference's color is shown, or not. * * @return True, if a preview of the preference's color is shown, false otherwise */ public final boolean isPreviewShown() { return showPreview; } /** * Sets, whether a preview of the preference's color should be shown, or not. * * @param showPreview * True, if a preview of the preference's color should be shown, false otherwise */ public final void showPreview(final boolean showPreview) { this.showPreview = showPreview; adaptPreviewView(); } /** * Returns the size of the preview of the preference's color. * * @return The size of the preview of the preference's color as an {@link Integer} value in * pixels */ public final int getPreviewSize() { return previewSize; } /** * Sets the size of the preview of the preference's color. * * @param previewSize * The size, which should be set, as an {@link Integer} value in pixels. The size must * be at least 1 */ public final void setPreviewSize(final int previewSize) { ensureAtLeast(previewSize, 1, "The preview size must be at least 1"); this.previewSize = previewSize; if (previewLoader != null) { previewLoader.setSize(previewSize); } } /** * Returns the shape of the preview of the preference's color. * * @return The shape of the preview of the preference's color as a value of the enum {@link * PreviewShape}. The shape may either be <code>CIRCLE</code> or <code>SQUARE</code> */ public final PreviewShape getPreviewShape() { return previewShape; } /** * Sets the shape of the preview of the preference's color. * * @param previewShape * The shape, which should be set, as a value of the enum {@link PreviewShape}. The * shape may not be null */ public final void setPreviewShape(@NonNull final PreviewShape previewShape) { ensureNotNull(previewShape, "The preview shape may not be null"); this.previewShape = previewShape; if (previewLoader != null) { previewLoader.setShape(previewShape); } adaptPreviewView(); } /** * Returns the border width of the preview of the preference's color. * * @return The border width of the preview of the preference's color as an {@link Integer} value * in pixels */ public final int getPreviewBorderWidth() { return previewBorderWidth; } /** * Sets the border width of the preview of the preference's color. * * @param borderWidth * The border width, which should be set, as an {@link Integer} value in pixels. The * border width must be at least 0 */ public final void setPreviewBorderWidth(final int borderWidth) { ensureAtLeast(borderWidth, 0, "The border width must be at least 0"); this.previewBorderWidth = borderWidth; if (previewLoader != null) { previewLoader.setBorderWidth(borderWidth); } adaptPreviewView(); } /** * Returns the border color of the preview of the preference's color. * * @return The border color of the preview of the preference's color as an {@link Integer} value */ public final int getPreviewBorderColor() { return previewBorderColor; } /** * Sets the border color of the preview of the preference's color. * * @param borderColor * The border color, which should be set, as an {@link Integer} value */ public final void setPreviewBorderColor(@ColorInt final int borderColor) { this.previewBorderColor = borderColor; if (previewLoader != null) { previewLoader.setBorderColor(borderColor); } adaptPreviewView(); } /** * Returns the background of the preview of the preference's color. * * @return The background of the preview of the preference's color as an instance of the class * {@link Drawable} */ public final Drawable getPreviewBackground() { return previewBackground; } /** * Sets the background of the preview of the preference's color. * * @param background * The background, which should be set, as an instance of the class {@link Drawable} or * null, if no background should be shown */ public final void setPreviewBackground(final Drawable background) { this.previewBackground = background; if (previewLoader != null) { previewLoader.setBackground(background); } adaptPreviewView(); } /** * Sets the background of the preview of the preference's color. * * @param resourceId * The resource id of the background, which should be set, as an {@link Integer} value. * The resource id must correspond to a valid drawable resource */ public final void setPreviewBackground(@DrawableRes final int resourceId) { setPreviewBackground(ContextCompat.getDrawable(getContext(), resourceId)); } /** * Sets the background color of the preview of the preference's color. * * @param backgroundColor * The background color, which should be set, as an {@link Integer} value */ public final void setPreviewBackgroundColor(@ColorInt final int backgroundColor) { setPreviewBackground(new ColorDrawable(backgroundColor)); } /** * Returns the format, which is used to print a textual representation of the preference's * color. * * @return The format, which is used to print a textual representation of the preference's * color, as a value of the enum {@link ColorFormat}. The format may either be <code>RGB</code>, * <code>ARGB</code>, <code>HEX</code> or <code>AHEX</code> */ public final ColorFormat getColorFormat() { return colorFormat; } /** * Sets the format, which should be used to print a textual representation of the preference's * color. * * @param colorFormat * The format, which should be set, as a value of the enum {@link ColorFormat}. The * format may not be null */ public final void setColorFormat(@NonNull final ColorFormat colorFormat) { ensureNotNull(colorFormat, "The color format may not be null"); this.colorFormat = colorFormat; } @Override public final CharSequence getSummary() { if (isValueShownAsSummary()) { return formatColor(getColorFormat(), getColor()); } else { return super.getSummary(); } } @Override protected final Object onGetDefaultValue(final TypedArray typedArray, final int index) { return typedArray.getInt(index, 0); } @SuppressWarnings("ResourceAsColor") @Override protected final void onSetInitialValue(final boolean restoreValue, final Object defaultValue) { setColor(restoreValue ? getPersistedInt(getColor()) : (Integer) defaultValue); } @Override protected final View onCreateView(final ViewGroup parent) { View view = super.onCreateView(parent); LinearLayout widgetFrame = (LinearLayout) view.findViewById(android.R.id.widget_frame); widgetFrame.setVisibility(View.VISIBLE); previewView = new ImageView(getContext()); widgetFrame.addView(previewView, createPreviewLayoutParams()); adaptPreviewView(); return view; } @Override protected final Parcelable onSaveInstanceState() { Parcelable superState = super.onSaveInstanceState(); if (!isPersistent()) { SavedState savedState = new SavedState(superState); savedState.color = getColor(); return savedState; } return superState; } @Override protected final void onRestoreInstanceState(final Parcelable state) { if (state != null && state instanceof SavedState) { SavedState savedState = (SavedState) state; setColor(savedState.color); super.onRestoreInstanceState(savedState.getSuperState()); } else { super.onRestoreInstanceState(state); } } }