/*
* Copyright (C) 2014 The CyanogenMod 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 afzkl.development.mColorPicker.views;
import afzkl.development.mColorPicker.views.ColorPickerView.OnColorChangedListener;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Color;
import android.graphics.PixelFormat;
import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.Spanned;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.ScrollView;
import android.widget.TextView;
import java.util.Locale;
import java.util.concurrent.atomic.AtomicInteger;
/**
* A view use directly into a dialog. It contains a one {@link ColorPickerView}
* and two {@link ColorPanelView} (the current color and the new color)
*/
public class ColorDialogView extends RelativeLayout
implements OnColorChangedListener, TextWatcher {
private static final int DEFAULT_MARGIN_DP = 16;
private static final int DEFAULT_PANEL_HEIGHT_DP = 32;
private static final int DEFAULT_TEXT_SIZE_SP = 12;
private static final int DEFAULT_LABEL_TEXT_SIZE_SP = 18;
private ColorPickerView mPickerView;
private ColorPanelView mCurrentColorView;
private ColorPanelView mNewColorView;
private TextView tvCurrent;
private TextView tvNew;
private TextView tvColorLabel;
private EditText etColor;
private String mCurrentLabelText = "Current:"; //$NON-NLS-1$
private String mNewLabelText = "New:"; //$NON-NLS-1$
private String mColorLabelText = "Color:"; //$NON-NLS-1$
private static final AtomicInteger sNextGeneratedId = new AtomicInteger(1);
/**
* Constructor of ColorDialogView
*
* @param context The current context
*/
public ColorDialogView(Context context) {
this(context, null);
}
/**
* Constructor of ColorDialogView
*
* @param context The current context
* @param attrs The attributes of the XML tag that is inflating the view.
*/
public ColorDialogView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
/**
* Constructor of ColorDialogView
*
* @param context The current context
* @param attrs The attributes of the XML tag that is inflating the view.
* @param defStyle The default style to apply to this view. 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 ColorDialogView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
init();
}
/**
* Method that initializes the view. This method loads all the necessary
* information and create an appropriate layout for the view
*/
private void init() {
// To fight color branding.
((Activity)getContext()).getWindow().setFormat(PixelFormat.RGBA_8888);
// Create the scrollview over the dialog
final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP);
ScrollView sv = new ScrollView(getContext());
sv.setId(internalGenerateViewId());
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
lp.setMargins(dlgMarging, 0, dlgMarging, 0);
sv.setLayoutParams(lp);
sv.setScrollBarStyle(View.SCROLLBARS_OUTSIDE_INSET);
// Now the vertical layout
LinearLayout ll = new LinearLayout(getContext());
ll.setId(internalGenerateViewId());
lp = new RelativeLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.MATCH_PARENT);
ll.setLayoutParams(lp);
ll.setOrientation(LinearLayout.VERTICAL);
sv.addView(ll);
// Creates the color input field
int id = createColorInput(ll);
// Creates the color picker
id = createColorPicker(ll, id);
// Creates the current color and new color panels
id = createColorsPanel(ll, id);
// Add the scrollview
addView(sv);
// Sets the input color
this.etColor.setText(toHex(this.mNewColorView.getColor()));
}
/**
* Method that creates the color input
*
* @param parent The parent layout
*/
private int createColorInput(ViewGroup parent) {
final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP);
LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
android.view.ViewGroup.LayoutParams.MATCH_PARENT);
lp2.setMargins(0, 0, dlgMarging, 0);
this.tvColorLabel = new TextView(getContext());
this.tvColorLabel.setText(this.mColorLabelText);
this.tvColorLabel.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_LABEL_TEXT_SIZE_SP);
this.tvColorLabel.setGravity(Gravity.BOTTOM | Gravity.LEFT);
this.tvColorLabel.setLayoutParams(lp2);
lp2 = new LinearLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
this.etColor = new EditText(getContext());
this.etColor.setSingleLine();
this.etColor.setGravity(Gravity.TOP | Gravity.LEFT);
this.etColor.setCursorVisible(true);
this.etColor.setImeOptions(
EditorInfo.IME_ACTION_DONE | EditorInfo.IME_FLAG_NO_FULLSCREEN);
this.etColor.setInputType(
InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS);
this.etColor.setLayoutParams(lp2);
InputFilter[] filters = new InputFilter[2];
filters[0] = new InputFilter.LengthFilter(8);
filters[1] = new InputFilter() {
@Override
public CharSequence filter(CharSequence source, int start,
int end, Spanned dest, int dstart, int dend) {
if (start >= end) return ""; //$NON-NLS-1$
String s = source.subSequence(start, end).toString();
StringBuilder sb = new StringBuilder();
int cc = s.length();
for (int i = 0; i < cc; i++) {
char c = s.charAt(i);
if ((c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F')) {
sb.append(c);
}
}
return sb.toString().toUpperCase(Locale.getDefault());
}
};
this.etColor.setFilters(filters);
this.etColor.addTextChangedListener(this);
LinearLayout ll1 = new LinearLayout(getContext());
ll1.setId(internalGenerateViewId());
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
lp.setMargins(dlgMarging, 0, dlgMarging, 0);
lp.addRule(RelativeLayout.ALIGN_PARENT_TOP);
ll1.setLayoutParams(lp);
ll1.addView(this.tvColorLabel);
ll1.addView(this.etColor);
parent.addView(ll1);
return ll1.getId();
}
/**
* Method that creates the color picker
*
* @param parent The parent layout
* @param belowOf The anchor view
* @return id The layout id
*/
private int createColorPicker(ViewGroup parent, int belowOf) {
final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP);
this.mPickerView = new ColorPickerView(getContext());
this.mPickerView.setId(internalGenerateViewId());
this.mPickerView.setOnColorChangedListener(this);
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
lp.setMargins(dlgMarging, 0, dlgMarging, 0);
lp.addRule(RelativeLayout.BELOW, belowOf);
this.mPickerView.setLayoutParams(lp);
parent.addView(this.mPickerView);
return this.mPickerView.getId();
}
/**
* Method that creates the colors panel (current and new)
*
* @param parent The parent layout
* @param belowOf The anchor view
* @return id The layout id
*/
private int createColorsPanel(ViewGroup parent, int belowOf) {
final int dlgMarging = (int)convertDpToPixel(DEFAULT_MARGIN_DP);
final int panelHeight = (int)convertDpToPixel(DEFAULT_PANEL_HEIGHT_DP);
LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
1);
// Titles
this.tvCurrent = new TextView(getContext());
lp2 = new LinearLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
1);
this.tvCurrent.setLayoutParams(lp2);
this.tvCurrent.setText(this.mCurrentLabelText);
this.tvCurrent.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP);
this.tvNew = new TextView(getContext());
this.tvNew.setLayoutParams(lp2);
this.tvNew.setText(this.mNewLabelText);
this.tvNew.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP);
TextView sep1 = new TextView(getContext());
lp2 = new LinearLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
0);
lp2.setMargins(dlgMarging, 0, dlgMarging, 0);
sep1.setLayoutParams(lp2);
sep1.setText(" "); //$NON-NLS-1$
sep1.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP);
LinearLayout ll1 = new LinearLayout(getContext());
ll1.setId(internalGenerateViewId());
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
lp.setMargins(dlgMarging, 0, dlgMarging, dlgMarging/2);
lp.addRule(RelativeLayout.BELOW, belowOf);
ll1.setLayoutParams(lp);
ll1.addView(this.tvCurrent);
ll1.addView(sep1);
ll1.addView(this.tvNew);
parent.addView(ll1);
// Color panels
lp2 = new LinearLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
1);
this.mCurrentColorView = new ColorPanelView(getContext());
this.mCurrentColorView.setLayoutParams(lp2);
this.mNewColorView = new ColorPanelView(getContext());
this.mNewColorView.setLayoutParams(lp2);
TextView sep2 = new TextView(getContext());
lp2 = new LinearLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
0);
lp2.setMargins(dlgMarging, 0, dlgMarging, 0);
sep2.setLayoutParams(lp2);
sep2.setText("-"); //$NON-NLS-1$
sep2.setTextSize(TypedValue.COMPLEX_UNIT_SP, DEFAULT_TEXT_SIZE_SP);
LinearLayout ll2 = new LinearLayout(getContext());
ll2.setId(internalGenerateViewId());
lp = new RelativeLayout.LayoutParams(
android.view.ViewGroup.LayoutParams.MATCH_PARENT, panelHeight);
lp.setMargins(dlgMarging, 0, dlgMarging, dlgMarging/2);
lp.addRule(RelativeLayout.BELOW, ll1.getId());
ll2.setLayoutParams(lp);
ll2.addView(this.mCurrentColorView);
ll2.addView(sep2);
ll2.addView(this.mNewColorView);
parent.addView(ll2);
return ll2.getId();
}
/**
* Method that returns the color of the picker
*
* @return The ARGB color
*/
public int getColor() {
return this.mPickerView.getColor();
}
/**
* Method that set the color of the picker
*
* @param argb The ARGB color
*/
public void setColor(int argb) {
setColor(argb, false);
}
/**
* Method that set the color of the picker
*
* @param argb The ARGB color
* @param fromEditText If the call comes from the EditText
*/
private void setColor(int argb, boolean fromEditText) {
this.mPickerView.setColor(argb, false);
this.mCurrentColorView.setColor(argb);
this.mNewColorView.setColor(argb);
if (!fromEditText) {
this.etColor.setText(toHex(this.mNewColorView.getColor()));
}
}
/**
* Method that display/hide the alpha slider
*
* @param show If the alpha slider should be shown
*/
public void showAlphaSlider(boolean show) {
this.mPickerView.setAlphaSliderVisible(show);
}
/**
* Set the text that should be shown in the alpha slider.
* Set to null to disable text.
*
* @param text Text that should be shown.
*/
public void setAlphaSliderText(String text) {
this.mPickerView.setAlphaSliderText(text);
}
/**
* Set the text that should be shown in the actual color panel.
* Set to null to disable text.
*
* @param text Text that should be shown.
*/
public void setCurrentColorText(String text) {
this.mCurrentLabelText = text;
this.tvCurrent.setText(this.mCurrentLabelText);
}
/**
* Set the text that should be shown in the new color panel.
* Set to null to disable text.
*
* @param text Text that should be shown.
*/
public void setNewColorText(String text) {
this.mNewLabelText = text;
this.tvNew.setText(this.mNewLabelText);
}
/**
* Set the text that should be shown in the label of the color input.
* Set to null to disable text.
*
* @param text Text that should be shown.
*/
public void setColorLabelText(String text) {
this.mColorLabelText = text;
this.tvColorLabel.setText(this.mColorLabelText);
}
/**
* {@inheritDoc}
*/
@Override
public void onColorChanged(int color) {
this.mNewColorView.setColor(color);
this.etColor.removeTextChangedListener(this);
this.etColor.setText(toHex(this.mNewColorView.getColor()));
this.etColor.addTextChangedListener(this);
}
/**
* {@inheritDoc}
*/
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {/**NON BLOCK**/}
/**
* {@inheritDoc}
*/
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {/**NON BLOCK**/}
/**
* {@inheritDoc}
*/
@Override
public void afterTextChanged(Editable s) {
if (s.length() == 8) {
try {
setColor(toARGB(s.toString()), true);
} catch (Exception e) {/**NON BLOCK**/}
}
}
/**
* This method converts dp unit to equivalent device specific value in pixels.
*
* @param ctx The current context
* @param dp A value in dp (Device independent pixels) unit
* @return float A float value to represent Pixels equivalent to dp according to device
*/
private float convertDpToPixel(float dp) {
Resources resources = getContext().getResources();
DisplayMetrics metrics = resources.getDisplayMetrics();
return dp * (metrics.densityDpi / 160f);
}
/**
* Method that converts an ARGB color to its hex string color representation
*
* @param argb The ARGB color
* @return String The hex string representation of the color
*/
private static String toHex(int argb) {
StringBuilder sb = new StringBuilder();
sb.append(toHexString((byte)Color.alpha(argb)));
sb.append(toHexString((byte)Color.red(argb)));
sb.append(toHexString((byte)Color.green(argb)));
sb.append(toHexString((byte)Color.blue(argb)));
return sb.toString();
}
/**
* Method that converts an hex string color representation to an ARGB color
*
* @param hex The hex string representation of the color
* @return int The ARGB color
*/
private static int toARGB(String hex) {
return Color.parseColor("#" + hex); //$NON-NLS-1$
}
/**
* Method that converts a byte into its hex string representation
*
* @param v The value to convert
* @return String The hex string representation
*/
private static String toHexString(byte v) {
String hex = Integer.toHexString(v & 0xff);
if (hex.length() == 1) {
hex = "0" + hex; //$NON-NLS-1$
}
return hex.toUpperCase(Locale.getDefault());
}
/**
* Generate a value suitable for use in {@link #setId(int)}.
* This value will not collide with ID values generated at build time by aapt for R.id.
*
* @return a generated ID value
*/
private static int internalGenerateViewId() {
for (;;) {
final int result = sNextGeneratedId.get();
// aapt-generated IDs have the high byte nonzero; clamp to the range under that.
int newValue = result + 1;
if (newValue > 0x00FFFFFF) newValue = 1; // Roll over to 1, not 0.
if (sNextGeneratedId.compareAndSet(result, newValue)) {
return result;
}
}
}
}