diff --git a/.gitignore b/.gitignore index a4c78382..334cfc3b 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ /build /captures .externalNativeBuild +/rc diff --git a/_ArtWork/media_bg_dark.png b/_ArtWork/media_bg_dark.png new file mode 100644 index 00000000..10e8143a Binary files /dev/null and b/_ArtWork/media_bg_dark.png differ diff --git a/_ArtWork/media_bg_dark.xcf b/_ArtWork/media_bg_dark.xcf new file mode 100644 index 00000000..c9351c4d Binary files /dev/null and b/_ArtWork/media_bg_dark.xcf differ diff --git a/app/src/main/res/drawable-hdpi/media_bg_dark.png b/app/src/main/res/drawable-hdpi/media_bg_dark.png new file mode 100644 index 00000000..297a01d9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/media_bg_dark.png differ diff --git a/app/src/main/res/drawable-mdpi/media_bg_dark.png b/app/src/main/res/drawable-mdpi/media_bg_dark.png new file mode 100644 index 00000000..36750312 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/media_bg_dark.png differ diff --git a/app/src/main/res/drawable-xhdpi/media_bg_dark.png b/app/src/main/res/drawable-xhdpi/media_bg_dark.png new file mode 100644 index 00000000..e13cfcd9 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/media_bg_dark.png differ diff --git a/app/src/main/res/drawable-xxhdpi/media_bg_dark.png b/app/src/main/res/drawable-xxhdpi/media_bg_dark.png new file mode 100644 index 00000000..428e901b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/media_bg_dark.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/media_bg_dark.png b/app/src/main/res/drawable-xxxhdpi/media_bg_dark.png new file mode 100644 index 00000000..ba1e9b92 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/media_bg_dark.png differ diff --git a/colorpicker/.gitignore b/colorpicker/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/colorpicker/.gitignore @@ -0,0 +1 @@ +/build diff --git a/colorpicker/build.gradle b/colorpicker/build.gradle new file mode 100644 index 00000000..4631db7b --- /dev/null +++ b/colorpicker/build.gradle @@ -0,0 +1,25 @@ +apply plugin: 'com.android.library' + +android { + + compileSdkVersion 26 + buildToolsVersion '26.0.2' + resourcePrefix "cpv_" + + defaultConfig { + minSdkVersion 14 + targetSdkVersion 26 + } + + lintOptions { + abortOnError false + } +} + +dependencies { + compile 'com.android.support:appcompat-v7:26.1.0' +} + +//apply plugin: 'com.getkeepsafe.dexcount' +//apply from: 'https://raw.githubusercontent.com/jaredrummler/android-artifact-push/master/artifactory/publication.gradle' +//apply from: 'https://raw.githubusercontent.com/jaredrummler/android-artifact-push/master/maven/gradle-mvn-push.gradle' \ No newline at end of file diff --git a/colorpicker/gradle.properties b/colorpicker/gradle.properties new file mode 100644 index 00000000..efbb627e --- /dev/null +++ b/colorpicker/gradle.properties @@ -0,0 +1,22 @@ +VERSION_NAME=2.1.7 +VERSION_CODE=217 +GROUP=com.jrummyapps +ARTIFACT_ID=colorpicker + +POM_NAME=colorpicker +POM_ARTIFACT_ID=colorpicker +POM_PACKAGING=aar + +POM_DESCRIPTION=A simple good looking color picker component for Android +POM_URL=https://github.com/jrummyapps/colorpicker +POM_SCM_URL=https://github.com/jrummyapps/colorpicker +POM_SCM_CONNECTION=scm:git@github.com:jrummyapps/colorpicker.git +POM_SCM_DEV_CONNECTION=scm:git@github.com:jrummyapps/colorpicker.git +POM_LICENCE_NAME=The Apache Software License, Version 2.0 +POM_LICENCE_URL=http://www.apache.org/licenses/LICENSE-2.0.txt +POM_LICENCE_DIST=repo +POM_DEVELOPER_ID=jrummyapps +POM_DEVELOPER_NAME=JRummy Apps Inc. + +SNAPSHOT_REPOSITORY_URL=https://oss.sonatype.org/content/repositories/snapshots +RELEASE_REPOSITORY_URL=https://oss.sonatype.org/service/local/staging/deploy/maven2 \ No newline at end of file diff --git a/colorpicker/proguard-rules.pro b/colorpicker/proguard-rules.pro new file mode 100644 index 00000000..677edc8f --- /dev/null +++ b/colorpicker/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in C:\android-sdk/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/colorpicker/src/main/AndroidManifest.xml b/colorpicker/src/main/AndroidManifest.xml new file mode 100644 index 00000000..2d8ab765 --- /dev/null +++ b/colorpicker/src/main/AndroidManifest.xml @@ -0,0 +1 @@ + diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/AlphaPatternDrawable.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/AlphaPatternDrawable.java new file mode 100644 index 00000000..e70b394c --- /dev/null +++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/AlphaPatternDrawable.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2017 JRummy Apps 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.jrummyapps.android.colorpicker; + +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; + +/** + * This drawable will draw a simple white and gray chessboard pattern. + * It's the pattern you will often see as a background behind a partly transparent image in many applications. + */ +class AlphaPatternDrawable extends Drawable { + + private int rectangleSize = 10; + + private Paint paint = new Paint(); + private Paint paintWhite = new Paint(); + private Paint paintGray = new Paint(); + + private int numRectanglesHorizontal; + private int numRectanglesVertical; + + /** + * Bitmap in which the pattern will be cached. + * This is so the pattern will not have to be recreated each time draw() gets called. + * Because recreating the pattern i rather expensive. I will only be recreated if the size changes. + */ + private Bitmap bitmap; + + AlphaPatternDrawable(int rectangleSize) { + this.rectangleSize = rectangleSize; + paintWhite.setColor(0xFFFFFFFF); + paintGray.setColor(0xFFCBCBCB); + } + + @Override public void draw(Canvas canvas) { + if (bitmap != null && !bitmap.isRecycled()) { + canvas.drawBitmap(bitmap, null, getBounds(), paint); + } + } + + @Override public int getOpacity() { + return 0; + } + + @Override public void setAlpha(int alpha) { + throw new UnsupportedOperationException("Alpha is not supported by this drawable."); + } + + @Override public void setColorFilter(ColorFilter cf) { + throw new UnsupportedOperationException("ColorFilter is not supported by this drawable."); + } + + @Override protected void onBoundsChange(Rect bounds) { + super.onBoundsChange(bounds); + int height = bounds.height(); + int width = bounds.width(); + numRectanglesHorizontal = (int) Math.ceil((width / rectangleSize)); + numRectanglesVertical = (int) Math.ceil(height / rectangleSize); + generatePatternBitmap(); + } + + /** + * This will generate a bitmap with the pattern as big as the rectangle we were allow to draw on. + * We do this to chache the bitmap so we don't need to recreate it each time draw() is called since it takes a few milliseconds + */ + private void generatePatternBitmap() { + if (getBounds().width() <= 0 || getBounds().height() <= 0) { + return; + } + + bitmap = Bitmap.createBitmap(getBounds().width(), getBounds().height(), Config.ARGB_8888); + Canvas canvas = new Canvas(bitmap); + + Rect r = new Rect(); + boolean verticalStartWhite = true; + for (int i = 0; i <= numRectanglesVertical; i++) { + boolean isWhite = verticalStartWhite; + for (int j = 0; j <= numRectanglesHorizontal; j++) { + r.top = i * rectangleSize; + r.left = j * rectangleSize; + r.bottom = r.top + rectangleSize; + r.right = r.left + rectangleSize; + canvas.drawRect(r, isWhite ? paintWhite : paintGray); + isWhite = !isWhite; + } + verticalStartWhite = !verticalStartWhite; + } + } + +} diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPaletteAdapter.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPaletteAdapter.java new file mode 100644 index 00000000..387aa7c2 --- /dev/null +++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPaletteAdapter.java @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2017 JRummy Apps 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.jrummyapps.android.colorpicker; + +import android.content.Context; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.support.v4.graphics.ColorUtils; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseAdapter; +import android.widget.ImageView; + +class ColorPaletteAdapter extends BaseAdapter { + + /*package*/ final OnColorSelectedListener listener; + /*package*/ final int[] colors; + /*package*/ int selectedPosition; + /*package*/ int colorShape; + + ColorPaletteAdapter(OnColorSelectedListener listener, + int[] colors, + int selectedPosition, + @ColorShape int colorShape) { + this.listener = listener; + this.colors = colors; + this.selectedPosition = selectedPosition; + this.colorShape = colorShape; + } + + @Override public int getCount() { + return colors.length; + } + + @Override public Object getItem(int position) { + return colors[position]; + } + + @Override public long getItemId(int position) { + return position; + } + + @Override public View getView(int position, View convertView, ViewGroup parent) { + final ViewHolder holder; + if (convertView == null) { + holder = new ViewHolder(parent.getContext()); + convertView = holder.view; + } else { + holder = (ViewHolder) convertView.getTag(); + } + holder.setup(position); + return convertView; + } + + void selectNone() { + selectedPosition = -1; + notifyDataSetChanged(); + } + + interface OnColorSelectedListener { + + void onColorSelected(int color); + } + + private final class ViewHolder { + + View view; + ColorPanelView colorPanelView; + ImageView imageView; + int originalBorderColor; + + ViewHolder(Context context) { + int layoutResId; + if (colorShape == ColorShape.SQUARE) { + layoutResId = R.layout.cpv_color_item_square; + } else { + layoutResId = R.layout.cpv_color_item_circle; + } + view = View.inflate(context, layoutResId, null); + colorPanelView = (ColorPanelView) view.findViewById(R.id.cpv_color_panel_view); + imageView = (ImageView) view.findViewById(R.id.cpv_color_image_view); + originalBorderColor = colorPanelView.getBorderColor(); + view.setTag(this); + } + + void setup(int position) { + int color = colors[position]; + int alpha = Color.alpha(color); + colorPanelView.setColor(color); + imageView.setImageResource(selectedPosition == position ? R.drawable.cpv_preset_checked : 0); + if (alpha != 255) { + if (alpha <= ColorPickerDialog.ALPHA_THRESHOLD) { + colorPanelView.setBorderColor(color | 0xFF000000); + imageView.setColorFilter(/*color | 0xFF000000*/Color.BLACK, PorterDuff.Mode.SRC_IN); + } else { + colorPanelView.setBorderColor(originalBorderColor); + imageView.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN); + } + } else { + setColorFilter(position); + } + setOnClickListener(position); + } + + private void setOnClickListener(final int position) { + colorPanelView.setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + if (selectedPosition != position) { + selectedPosition = position; + notifyDataSetChanged(); + } + listener.onColorSelected(colors[position]); + } + }); + colorPanelView.setOnLongClickListener(new View.OnLongClickListener() { + @Override public boolean onLongClick(View v) { + colorPanelView.showHint(); + return true; + } + }); + } + + private void setColorFilter(int position) { + if (position == selectedPosition && ColorUtils.calculateLuminance(colors[position]) >= 0.65) { + imageView.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN); + } else { + imageView.setColorFilter(null); + } + } + + } + +} \ No newline at end of file diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPanelView.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPanelView.java new file mode 100644 index 00000000..2bfabbe7 --- /dev/null +++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPanelView.java @@ -0,0 +1,323 @@ +/* + * Copyright (C) 2017 JRummy Apps 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.jrummyapps.android.colorpicker; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapShader; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.annotation.ColorInt; +import android.support.v4.view.GravityCompat; +import android.support.v4.view.ViewCompat; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.Gravity; +import android.view.View; +import android.widget.Toast; +import java.util.Locale; + +/** + * This class draws a panel which which will be filled with a color which can be set. It can be used to show the + * currently selected color which you will get from the {@link ColorPickerView}. + */ +public class ColorPanelView extends View { + + private final static int DEFAULT_BORDER_COLOR = 0xFF6E6E6E; + + private Drawable alphaPattern; + private Paint borderPaint; + private Paint colorPaint; + private Paint alphaPaint; + private Paint originalPaint; + private Rect drawingRect; + private Rect colorRect; + private RectF centerRect = new RectF(); + private boolean showOldColor; + + /* The width in pixels of the border surrounding the color panel. */ + private int borderWidthPx; + private int borderColor = DEFAULT_BORDER_COLOR; + private int color = Color.BLACK; + private int shape; + + public ColorPanelView(Context context) { + this(context, null); + } + + public ColorPanelView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ColorPanelView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs); + } + + @Override public Parcelable onSaveInstanceState() { + Bundle state = new Bundle(); + state.putParcelable("instanceState", super.onSaveInstanceState()); + state.putInt("color", color); + return state; + } + + @Override public void onRestoreInstanceState(Parcelable state) { + if (state instanceof Bundle) { + Bundle bundle = (Bundle) state; + color = bundle.getInt("color"); + state = bundle.getParcelable("instanceState"); + } + super.onRestoreInstanceState(state); + } + + private void init(Context context, AttributeSet attrs) { + TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPanelView); + shape = a.getInt(R.styleable.ColorPanelView_cpv_colorShape, ColorShape.CIRCLE); + showOldColor = a.getBoolean(R.styleable.ColorPanelView_cpv_showOldColor, false); + if (showOldColor && shape != ColorShape.CIRCLE) { + throw new IllegalStateException("Color preview is only available in circle mode"); + } + borderColor = a.getColor(R.styleable.ColorPanelView_cpv_borderColor, DEFAULT_BORDER_COLOR); + a.recycle(); + if (borderColor == DEFAULT_BORDER_COLOR) { + // If no specific border color has been set we take the default secondary text color as border/slider color. + // Thus it will adopt to theme changes automatically. + final TypedValue value = new TypedValue(); + TypedArray typedArray = context.obtainStyledAttributes(value.data, new int[]{android.R.attr.textColorSecondary}); + borderColor = typedArray.getColor(0, borderColor); + typedArray.recycle(); + } + borderWidthPx = DrawingUtils.dpToPx(context, 1); + borderPaint = new Paint(); + borderPaint.setAntiAlias(true); + colorPaint = new Paint(); + colorPaint.setAntiAlias(true); + if (showOldColor) { + originalPaint = new Paint(); + } + if (shape == ColorShape.CIRCLE) { + Bitmap bitmap = ((BitmapDrawable) context.getResources().getDrawable(R.drawable.cpv_alpha)).getBitmap(); + alphaPaint = new Paint(); + alphaPaint.setAntiAlias(true); + BitmapShader shader = new BitmapShader(bitmap, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT); + alphaPaint.setShader(shader); + } + } + + @Override protected void onDraw(Canvas canvas) { + borderPaint.setColor(borderColor); + colorPaint.setColor(color); + if (shape == ColorShape.SQUARE) { + if (borderWidthPx > 0) { + canvas.drawRect(drawingRect, borderPaint); + } + if (alphaPattern != null) { + alphaPattern.draw(canvas); + } + canvas.drawRect(colorRect, colorPaint); + } else if (shape == ColorShape.CIRCLE) { + final int outerRadius = getMeasuredWidth() / 2; + if (borderWidthPx > 0) { + canvas.drawCircle(getMeasuredWidth() / 2, + getMeasuredHeight() / 2, + outerRadius, + borderPaint); + } + if (Color.alpha(color) < 255) { + canvas.drawCircle(getMeasuredWidth() / 2, + getMeasuredHeight() / 2, + outerRadius - borderWidthPx, alphaPaint); + } + if (showOldColor) { + canvas.drawArc(centerRect, 90, 180, true, originalPaint); + canvas.drawArc(centerRect, 270, 180, true, colorPaint); + } else { + canvas.drawCircle(getMeasuredWidth() / 2, + getMeasuredHeight() / 2, + outerRadius - borderWidthPx, + colorPaint); + } + } + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + if (shape == ColorShape.SQUARE) { + int width = MeasureSpec.getSize(widthMeasureSpec); + int height = MeasureSpec.getSize(heightMeasureSpec); + setMeasuredDimension(width, height); + } else if (shape == ColorShape.CIRCLE) { + super.onMeasure(widthMeasureSpec, widthMeasureSpec); + setMeasuredDimension(getMeasuredWidth(), getMeasuredWidth()); + } else { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } + } + + @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (shape == ColorShape.SQUARE || showOldColor) { + drawingRect = new Rect(); + drawingRect.left = getPaddingLeft(); + drawingRect.right = w - getPaddingRight(); + drawingRect.top = getPaddingTop(); + drawingRect.bottom = h - getPaddingBottom(); + if (showOldColor) { + setUpCenterRect(); + } else { + setUpColorRect(); + } + } + } + + private void setUpCenterRect() { + final Rect dRect = drawingRect; + int left = dRect.left + borderWidthPx; + int top = dRect.top + borderWidthPx; + int bottom = dRect.bottom - borderWidthPx; + int right = dRect.right - borderWidthPx; + centerRect = new RectF(left, top, right, bottom); + } + + private void setUpColorRect() { + final Rect dRect = drawingRect; + int left = dRect.left + borderWidthPx; + int top = dRect.top + borderWidthPx; + int bottom = dRect.bottom - borderWidthPx; + int right = dRect.right - borderWidthPx; + colorRect = new Rect(left, top, right, bottom); + alphaPattern = new AlphaPatternDrawable(DrawingUtils.dpToPx(getContext(), 4)); + alphaPattern.setBounds(Math.round(colorRect.left), + Math.round(colorRect.top), + Math.round(colorRect.right), + Math.round(colorRect.bottom)); + } + + /** + * Set the color that should be shown by this view. + * + * @param color + * the color value + */ + public void setColor(int color) { + this.color = color; + invalidate(); + } + + /** + * Get the color currently show by this view. + * + * @return the color value + */ + public int getColor() { + return color; + } + + /** + * Set the original color. This is only used for previewing colors. + * + * @param color + * The original color + */ + public void setOriginalColor(@ColorInt int color) { + if (originalPaint != null) { + originalPaint.setColor(color); + } + } + + /** + * Set the color of the border surrounding the panel. + * + * @param color + * the color value + */ + public void setBorderColor(int color) { + borderColor = color; + invalidate(); + } + + /** + * @return the color of the border surrounding the panel. + */ + public int getBorderColor() { + return borderColor; + } + + /** + * Set the shape. + * + * @param shape + * Either {@link ColorShape#SQUARE} or {@link ColorShape#CIRCLE}. + */ + public void setShape(@ColorShape int shape) { + this.shape = shape; + invalidate(); + } + + /** + * Get the shape + * + * @return Either {@link ColorShape#SQUARE} or {@link ColorShape#CIRCLE}. + */ + @ColorShape public int getShape() { + return shape; + } + + /** + * Show a toast message with the hex color code below the view. + */ + public void showHint() { + final int[] screenPos = new int[2]; + final Rect displayFrame = new Rect(); + getLocationOnScreen(screenPos); + getWindowVisibleDisplayFrame(displayFrame); + final Context context = getContext(); + final int width = getWidth(); + final int height = getHeight(); + final int midy = screenPos[1] + height / 2; + int referenceX = screenPos[0] + width / 2; + if (ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR) { + final int screenWidth = context.getResources().getDisplayMetrics().widthPixels; + referenceX = screenWidth - referenceX; // mirror + } + StringBuilder hint = new StringBuilder("#"); + if (Color.alpha(color) != 255) { + hint.append(Integer.toHexString(color).toUpperCase(Locale.ENGLISH)); + } else { + hint.append(String.format("%06X", 0xFFFFFF & color).toUpperCase(Locale.ENGLISH)); + } + Toast cheatSheet = Toast.makeText(context, hint.toString(), Toast.LENGTH_SHORT); + if (midy < displayFrame.height()) { + // Show along the top; follow action buttons + cheatSheet.setGravity(Gravity.TOP | GravityCompat.END, referenceX, + screenPos[1] + height - displayFrame.top); + } else { + // Show along the bottom center + cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height); + } + cheatSheet.show(); + } + +} diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialog.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialog.java new file mode 100644 index 00000000..7fe8ace5 --- /dev/null +++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialog.java @@ -0,0 +1,879 @@ +/* + * Copyright (C) 2017 JRummy Apps 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.jrummyapps.android.colorpicker; + +import android.app.Activity; +import android.app.Dialog; +import android.app.DialogFragment; +import android.content.Context; +import android.content.DialogInterface; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.PorterDuff; +import android.os.Bundle; +import android.support.annotation.ColorInt; +import android.support.annotation.IntDef; +import android.support.annotation.NonNull; +import android.support.annotation.StringRes; +import android.support.v4.graphics.ColorUtils; +import android.support.v7.app.AlertDialog; +import android.text.Editable; +import android.text.InputFilter; +import android.text.TextWatcher; +import android.util.TypedValue; +import android.view.MotionEvent; +import android.view.View; +import android.view.View.OnTouchListener; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.view.inputmethod.InputMethodManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.GridView; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.SeekBar; +import android.widget.TextView; +import com.jrummyapps.android.colorpicker.ColorPickerView.OnColorChangedListener; +import java.util.Arrays; +import java.util.Locale; + +/** + *

A dialog to pick a color.

+ * + *

The {@link Activity activity} that shows this dialog should implement {@link ColorPickerDialogListener}

+ * + *

Example usage:

+ * + *
+ *   ColorPickerDialog.newBuilder().show(activity);
+ * 
+ */ +public class ColorPickerDialog extends DialogFragment implements OnTouchListener, OnColorChangedListener, TextWatcher { + + private static final String ARG_ID = "id"; + private static final String ARG_TYPE = "dialogType"; + private static final String ARG_COLOR = "color"; + private static final String ARG_ALPHA = "alpha"; + private static final String ARG_PRESETS = "presets"; + private static final String ARG_ALLOW_PRESETS = "allowPresets"; + private static final String ARG_ALLOW_CUSTOM = "allowCustom"; + private static final String ARG_DIALOG_TITLE = "dialogTitle"; + private static final String ARG_SHOW_COLOR_SHADES = "showColorShades"; + private static final String ARG_COLOR_SHAPE = "colorShape"; + + public static final int TYPE_CUSTOM = 0; + public static final int TYPE_PRESETS = 1; + + static final int ALPHA_THRESHOLD = 165; + + /** + * Material design colors used as the default color presets + */ + public static final int[] MATERIAL_COLORS = { + 0xFFF44336, // RED 500 + 0xFFE91E63, // PINK 500 + 0xFFFF2C93, // LIGHT PINK 500 + 0xFF9C27B0, // PURPLE 500 + 0xFF673AB7, // DEEP PURPLE 500 + 0xFF3F51B5, // INDIGO 500 + 0xFF2196F3, // BLUE 500 + 0xFF03A9F4, // LIGHT BLUE 500 + 0xFF00BCD4, // CYAN 500 + 0xFF009688, // TEAL 500 + 0xFF4CAF50, // GREEN 500 + 0xFF8BC34A, // LIGHT GREEN 500 + 0xFFCDDC39, // LIME 500 + 0xFFFFEB3B, // YELLOW 500 + 0xFFFFC107, // AMBER 500 + 0xFFFF9800, // ORANGE 500 + 0xFF795548, // BROWN 500 + 0xFF607D8B, // BLUE GREY 500 + 0xFF9E9E9E, // GREY 500 + }; + + /** + * Create a new Builder for creating a {@link ColorPickerDialog} instance + * + * @return The {@link Builder builder} to create the {@link ColorPickerDialog}. + */ + public static Builder newBuilder() { + return new Builder(); + } + + ColorPickerDialogListener colorPickerDialogListener; + FrameLayout rootView; + int[] presets; + @ColorInt int color; + int dialogType; + int dialogId; + boolean showColorShades; + int colorShape; + + // -- PRESETS -------------------------- + ColorPaletteAdapter adapter; + LinearLayout shadesLayout; + SeekBar transparencySeekBar; + TextView transparencyPercText; + + // -- CUSTOM --------------------------- + ColorPickerView colorPicker; + ColorPanelView newColorPanel; + EditText hexEditText; + boolean showAlphaSlider; + private boolean fromEditText; + + @Override public void onAttach(Activity activity) { + super.onAttach(activity); + if (colorPickerDialogListener == null && activity instanceof ColorPickerDialogListener) { + colorPickerDialogListener = (ColorPickerDialogListener) activity; + } + } + + @Override public Dialog onCreateDialog(Bundle savedInstanceState) { + dialogId = getArguments().getInt(ARG_ID); + showAlphaSlider = getArguments().getBoolean(ARG_ALPHA); + showColorShades = getArguments().getBoolean(ARG_SHOW_COLOR_SHADES); + colorShape = getArguments().getInt(ARG_COLOR_SHAPE); + if (savedInstanceState == null) { + color = getArguments().getInt(ARG_COLOR); + dialogType = getArguments().getInt(ARG_TYPE); + } else { + color = savedInstanceState.getInt(ARG_COLOR); + dialogType = savedInstanceState.getInt(ARG_TYPE); + } + + rootView = new FrameLayout(getActivity()); + if (dialogType == TYPE_CUSTOM) { + rootView.addView(createPickerView()); + } else if (dialogType == TYPE_PRESETS) { + rootView.addView(createPresetsView()); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()) + .setView(rootView) + .setPositiveButton(R.string.cpv_select, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int which) { + colorPickerDialogListener.onColorSelected(dialogId, color); + } + }); + + int dialogTitleStringRes = getArguments().getInt(ARG_DIALOG_TITLE); + if (dialogTitleStringRes != 0) { + builder.setTitle(dialogTitleStringRes); + } + + int neutralButtonStringRes; + if (dialogType == TYPE_CUSTOM && getArguments().getBoolean(ARG_ALLOW_PRESETS)) { + neutralButtonStringRes = R.string.cpv_presets; + } else if (dialogType == TYPE_PRESETS && getArguments().getBoolean(ARG_ALLOW_CUSTOM)) { + neutralButtonStringRes = R.string.cpv_custom; + } else { + neutralButtonStringRes = 0; + } + + if (neutralButtonStringRes != 0) { + builder.setNeutralButton(neutralButtonStringRes, null); + } + + return builder.create(); + } + + @Override public void onStart() { + super.onStart(); + AlertDialog dialog = (AlertDialog) getDialog(); + + // http://stackoverflow.com/a/16972670/1048340 + //noinspection ConstantConditions + dialog.getWindow() + .clearFlags(WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM); + dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE); + + // Do not dismiss the dialog when clicking the neutral button. + Button neutralButton = dialog.getButton(AlertDialog.BUTTON_NEUTRAL); + if (neutralButton != null) { + neutralButton.setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + rootView.removeAllViews(); + switch (dialogType) { + case TYPE_CUSTOM: + dialogType = TYPE_PRESETS; + ((Button) v).setText(R.string.cpv_custom); + rootView.addView(createPresetsView()); + break; + case TYPE_PRESETS: + dialogType = TYPE_CUSTOM; + ((Button) v).setText(R.string.cpv_presets); + rootView.addView(createPickerView()); + } + } + }); + } + } + + @Override public void onDismiss(DialogInterface dialog) { + super.onDismiss(dialog); + colorPickerDialogListener.onDialogDismissed(dialogId); + } + + @Override public void onSaveInstanceState(Bundle outState) { + outState.putInt(ARG_COLOR, color); + outState.putInt(ARG_TYPE, dialogType); + super.onSaveInstanceState(outState); + } + + /** + * Set the callback + * + * @param colorPickerDialogListener + * The callback invoked when a color is selected or the dialog is dismissed. + */ + public void setColorPickerDialogListener(ColorPickerDialogListener colorPickerDialogListener) { + this.colorPickerDialogListener = colorPickerDialogListener; + } + + // region Custom Picker + + View createPickerView() { + View contentView = View.inflate(getActivity(), R.layout.cpv_dialog_color_picker, null); + colorPicker = (ColorPickerView) contentView.findViewById(R.id.cpv_color_picker_view); + ColorPanelView oldColorPanel = (ColorPanelView) contentView.findViewById(R.id.cpv_color_panel_old); + newColorPanel = (ColorPanelView) contentView.findViewById(R.id.cpv_color_panel_new); + ImageView arrowRight = (ImageView) contentView.findViewById(R.id.cpv_arrow_right); + hexEditText = (EditText) contentView.findViewById(R.id.cpv_hex); + + try { + final TypedValue value = new TypedValue(); + TypedArray typedArray = + getActivity().obtainStyledAttributes(value.data, new int[]{android.R.attr.textColorPrimary}); + int arrowColor = typedArray.getColor(0, Color.BLACK); + typedArray.recycle(); + arrowRight.setColorFilter(arrowColor); + } catch (Exception ignored) { + } + + colorPicker.setAlphaSliderVisible(showAlphaSlider); + oldColorPanel.setColor(getArguments().getInt(ARG_COLOR)); + colorPicker.setColor(color, true); + newColorPanel.setColor(color); + setHex(color); + + if (!showAlphaSlider) { + hexEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(6)}); + } + + newColorPanel.setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + if (newColorPanel.getColor() == color) { + colorPickerDialogListener.onColorSelected(dialogId, color); + dismiss(); + } + } + }); + + contentView.setOnTouchListener(this); + colorPicker.setOnColorChangedListener(this); + hexEditText.addTextChangedListener(this); + + hexEditText.setOnFocusChangeListener(new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.showSoftInput(hexEditText, InputMethodManager.SHOW_IMPLICIT); + } + } + }); + + return contentView; + } + + @Override public boolean onTouch(View v, MotionEvent event) { + if (v != hexEditText && hexEditText.hasFocus()) { + hexEditText.clearFocus(); + InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(hexEditText.getWindowToken(), 0); + hexEditText.clearFocus(); + return true; + } + return false; + } + + @Override public void onColorChanged(int newColor) { + color = newColor; + newColorPanel.setColor(newColor); + if (!fromEditText) { + setHex(newColor); + if (hexEditText.hasFocus()) { + InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE); + imm.hideSoftInputFromWindow(hexEditText.getWindowToken(), 0); + hexEditText.clearFocus(); + } + } + fromEditText = false; + } + + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override public void afterTextChanged(Editable s) { + if (hexEditText.isFocused()) { + int color = parseColorString(s.toString()); + if (color != colorPicker.getColor()) { + fromEditText = true; + colorPicker.setColor(color, true); + } + } + } + + private void setHex(int color) { + if (showAlphaSlider) { + hexEditText.setText(String.format("%08X", (color))); + } else { + hexEditText.setText(String.format("%06X", (0xFFFFFF & color))); + } + } + + private int parseColorString(String colorString) throws NumberFormatException { + int a, r, g, b = 0; + if (colorString.startsWith("#")) { + colorString = colorString.substring(1); + } + if (colorString.length() == 0) { + r = 0; + a = 255; + g = 0; + } else if (colorString.length() <= 2) { + a = 255; + r = 0; + b = Integer.parseInt(colorString, 16); + g = 0; + } else if (colorString.length() == 3) { + a = 255; + r = Integer.parseInt(colorString.substring(0, 1), 16); + g = Integer.parseInt(colorString.substring(1, 2), 16); + b = Integer.parseInt(colorString.substring(2, 3), 16); + } else if (colorString.length() == 4) { + a = 255; + r = Integer.parseInt(colorString.substring(0, 2), 16); + g = r; + r = 0; + b = Integer.parseInt(colorString.substring(2, 4), 16); + } else if (colorString.length() == 5) { + a = 255; + r = Integer.parseInt(colorString.substring(0, 1), 16); + g = Integer.parseInt(colorString.substring(1, 3), 16); + b = Integer.parseInt(colorString.substring(3, 5), 16); + } else if (colorString.length() == 6) { + a = 255; + r = Integer.parseInt(colorString.substring(0, 2), 16); + g = Integer.parseInt(colorString.substring(2, 4), 16); + b = Integer.parseInt(colorString.substring(4, 6), 16); + } else if (colorString.length() == 7) { + a = Integer.parseInt(colorString.substring(0, 1), 16); + r = Integer.parseInt(colorString.substring(1, 3), 16); + g = Integer.parseInt(colorString.substring(3, 5), 16); + b = Integer.parseInt(colorString.substring(5, 7), 16); + } else if (colorString.length() == 8) { + a = Integer.parseInt(colorString.substring(0, 2), 16); + r = Integer.parseInt(colorString.substring(2, 4), 16); + g = Integer.parseInt(colorString.substring(4, 6), 16); + b = Integer.parseInt(colorString.substring(6, 8), 16); + } else { + b = -1; + g = -1; + r = -1; + a = -1; + } + return Color.argb(a, r, g, b); + } + + // -- endregion -- + + // region Presets Picker + + View createPresetsView() { + View contentView = View.inflate(getActivity(), R.layout.cpv_dialog_presets, null); + shadesLayout = (LinearLayout) contentView.findViewById(R.id.shades_layout); + transparencySeekBar = (SeekBar) contentView.findViewById(R.id.transparency_seekbar); + transparencyPercText = (TextView) contentView.findViewById(R.id.transparency_text); + GridView gridView = (GridView) contentView.findViewById(R.id.gridView); + + loadPresets(); + + if (showColorShades) { + createColorShades(color); + } else { + shadesLayout.setVisibility(View.GONE); + contentView.findViewById(R.id.shades_divider).setVisibility(View.GONE); + } + + adapter = new ColorPaletteAdapter(new ColorPaletteAdapter.OnColorSelectedListener() { + @Override public void onColorSelected(int newColor) { + if (color == newColor) { + colorPickerDialogListener.onColorSelected(dialogId, color); + dismiss(); + return; + } + color = newColor; + if (showColorShades) { + createColorShades(color); + } + } + }, presets, getSelectedItemPosition(), colorShape); + + gridView.setAdapter(adapter); + + if (showAlphaSlider) { + setupTransparency(); + } else { + contentView.findViewById(R.id.transparency_layout).setVisibility(View.GONE); + contentView.findViewById(R.id.transparency_title).setVisibility(View.GONE); + } + + return contentView; + } + + private void loadPresets() { + int alpha = Color.alpha(color); + presets = getArguments().getIntArray(ARG_PRESETS); + if (presets == null) presets = MATERIAL_COLORS; + boolean isMaterialColors = presets == MATERIAL_COLORS; + presets = Arrays.copyOf(presets, presets.length); // don't update the original array when modifying alpha + if (alpha != 255) { + // add alpha to the presets + for (int i = 0; i < presets.length; i++) { + int color = presets[i]; + int red = Color.red(color); + int green = Color.green(color); + int blue = Color.blue(color); + presets[i] = Color.argb(alpha, red, green, blue); + } + } + presets = unshiftIfNotExists(presets, color); + if (isMaterialColors && presets.length == 19) { + // Add black to have a total of 20 colors if the current color is in the material color palette + presets = pushIfNotExists(presets, Color.argb(alpha, 0, 0, 0)); + } + } + + void createColorShades(@ColorInt final int color) { + final int[] colorShades = getColorShades(color); + + if (shadesLayout.getChildCount() != 0) { + for (int i = 0; i < shadesLayout.getChildCount(); i++) { + FrameLayout layout = (FrameLayout) shadesLayout.getChildAt(i); + final ColorPanelView cpv = (ColorPanelView) layout.findViewById(R.id.cpv_color_panel_view); + ImageView iv = (ImageView) layout.findViewById(R.id.cpv_color_image_view); + cpv.setColor(colorShades[i]); + cpv.setTag(false); + iv.setImageDrawable(null); + } + return; + } + + final int horizontalPadding = getResources().getDimensionPixelSize(R.dimen.cpv_item_horizontal_padding); + + for (final int colorShade : colorShades) { + int layoutResId; + if (colorShape == ColorShape.SQUARE) { + layoutResId = R.layout.cpv_color_item_square; + } else { + layoutResId = R.layout.cpv_color_item_circle; + } + + final View view = View.inflate(getActivity(), layoutResId, null); + final ColorPanelView colorPanelView = (ColorPanelView) view.findViewById(R.id.cpv_color_panel_view); + + ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) colorPanelView.getLayoutParams(); + params.leftMargin = params.rightMargin = horizontalPadding; + colorPanelView.setLayoutParams(params); + colorPanelView.setColor(colorShade); + shadesLayout.addView(view); + + colorPanelView.post(new Runnable() { + @Override public void run() { + // The color is black when rotating the dialog. This is a dirty fix. WTF!? + colorPanelView.setColor(colorShade); + } + }); + + colorPanelView.setOnClickListener(new View.OnClickListener() { + @Override public void onClick(View v) { + if (v.getTag() instanceof Boolean && (Boolean) v.getTag()) { + colorPickerDialogListener.onColorSelected(dialogId, ColorPickerDialog.this.color); + dismiss(); + return; // already selected + } + ColorPickerDialog.this.color = colorPanelView.getColor(); + adapter.selectNone(); + for (int i = 0; i < shadesLayout.getChildCount(); i++) { + FrameLayout layout = (FrameLayout) shadesLayout.getChildAt(i); + ColorPanelView cpv = (ColorPanelView) layout.findViewById(R.id.cpv_color_panel_view); + ImageView iv = (ImageView) layout.findViewById(R.id.cpv_color_image_view); + iv.setImageResource(cpv == v ? R.drawable.cpv_preset_checked : 0); + if (cpv == v && ColorUtils.calculateLuminance(cpv.getColor()) >= 0.65 || + Color.alpha(cpv.getColor()) <= ALPHA_THRESHOLD) { + iv.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN); + } else { + iv.setColorFilter(null); + } + cpv.setTag(cpv == v); + } + } + }); + colorPanelView.setOnLongClickListener(new View.OnLongClickListener() { + @Override public boolean onLongClick(View v) { + colorPanelView.showHint(); + return true; + } + }); + } + } + + private int shadeColor(@ColorInt int color, double percent) { + String hex = String.format("#%06X", (0xFFFFFF & color)); + long f = Long.parseLong(hex.substring(1), 16); + double t = percent < 0 ? 0 : 255; + double p = percent < 0 ? percent * -1 : percent; + long R = f >> 16; + long G = f >> 8 & 0x00FF; + long B = f & 0x0000FF; + int alpha = Color.alpha(color); + int red = (int) (Math.round((t - R) * p) + R); + int green = (int) (Math.round((t - G) * p) + G); + int blue = (int) (Math.round((t - B) * p) + B); + return Color.argb(alpha, red, green, blue); + } + + private int[] getColorShades(@ColorInt int color) { + return new int[]{ + shadeColor(color, 0.9), + shadeColor(color, 0.7), + shadeColor(color, 0.5), + shadeColor(color, 0.333), + shadeColor(color, 0.166), + shadeColor(color, -0.125), + shadeColor(color, -0.25), + shadeColor(color, -0.375), + shadeColor(color, -0.5), + shadeColor(color, -0.675), + shadeColor(color, -0.7), + shadeColor(color, -0.775), + }; + } + + private void setupTransparency() { + int progress = 255 - Color.alpha(color); + transparencySeekBar.setMax(255); + transparencySeekBar.setProgress(progress); + int percentage = (int) ((double) progress * 100 / 255); + transparencyPercText.setText(String.format(Locale.ENGLISH, "%d%%", percentage)); + transparencySeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + int percentage = (int) ((double) progress * 100 / 255); + transparencyPercText.setText(String.format(Locale.ENGLISH, "%d%%", percentage)); + int alpha = 255 - progress; + // update items in GridView: + for (int i = 0; i < adapter.colors.length; i++) { + int color = adapter.colors[i]; + int red = Color.red(color); + int green = Color.green(color); + int blue = Color.blue(color); + adapter.colors[i] = Color.argb(alpha, red, green, blue); + } + adapter.notifyDataSetChanged(); + // update shades: + for (int i = 0; i < shadesLayout.getChildCount(); i++) { + FrameLayout layout = (FrameLayout) shadesLayout.getChildAt(i); + ColorPanelView cpv = (ColorPanelView) layout.findViewById(R.id.cpv_color_panel_view); + ImageView iv = (ImageView) layout.findViewById(R.id.cpv_color_image_view); + if (layout.getTag() == null) { + // save the original border color + layout.setTag(cpv.getBorderColor()); + } + int color = cpv.getColor(); + color = Color.argb(alpha, Color.red(color), Color.green(color), Color.blue(color)); + if (alpha <= ALPHA_THRESHOLD) { + cpv.setBorderColor(color | 0xFF000000); + } else { + cpv.setBorderColor((int) layout.getTag()); + } + if (cpv.getTag() != null && (Boolean) cpv.getTag()) { + // The alpha changed on the selected shaded color. Update the checkmark color filter. + if (alpha <= ALPHA_THRESHOLD) { + iv.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN); + } else { + if (ColorUtils.calculateLuminance(color) >= 0.65) { + iv.setColorFilter(Color.BLACK, PorterDuff.Mode.SRC_IN); + } else { + iv.setColorFilter(Color.WHITE, PorterDuff.Mode.SRC_IN); + } + } + } + cpv.setColor(color); + } + // update color: + int red = Color.red(color); + int green = Color.green(color); + int blue = Color.blue(color); + color = Color.argb(alpha, red, green, blue); + } + + @Override public void onStartTrackingTouch(SeekBar seekBar) { + + } + + @Override public void onStopTrackingTouch(SeekBar seekBar) { + + } + }); + } + + private int[] unshiftIfNotExists(int[] array, int value) { + boolean present = false; + for (int i : array) { + if (i == value) { + present = true; + break; + } + } + if (!present) { + int[] newArray = new int[array.length + 1]; + newArray[0] = value; + System.arraycopy(array, 0, newArray, 1, newArray.length - 1); + return newArray; + } + return array; + } + + private int[] pushIfNotExists(int[] array, int value) { + boolean present = false; + for (int i : array) { + if (i == value) { + present = true; + break; + } + } + if (!present) { + int[] newArray = new int[array.length + 1]; + newArray[newArray.length - 1] = value; + System.arraycopy(array, 0, newArray, 0, newArray.length - 1); + return newArray; + } + return array; + } + + private int getSelectedItemPosition() { + for (int i = 0; i < presets.length; i++) { + if (presets[i] == color) { + return i; + } + } + return -1; + } + + // endregion + + // region Builder + + public static final class Builder { + + @StringRes int dialogTitle = R.string.cpv_default_title; + @DialogType int dialogType = TYPE_PRESETS; + int[] presets = MATERIAL_COLORS; + @ColorInt int color = Color.BLACK; + int dialogId = 0; + boolean showAlphaSlider = false; + boolean allowPresets = true; + boolean allowCustom = true; + boolean showColorShades = true; + @ColorShape int colorShape = ColorShape.CIRCLE; + + /*package*/ Builder() { + + } + + /** + * Set the dialog title string resource id + * + * @param dialogTitle + * The string resource used for the dialog title + * @return This builder object for chaining method calls + */ + public Builder setDialogTitle(@StringRes int dialogTitle) { + this.dialogTitle = dialogTitle; + return this; + } + + /** + * Set which dialog view to show. + * + * @param dialogType + * Either {@link ColorPickerDialog#TYPE_CUSTOM} or {@link ColorPickerDialog#TYPE_PRESETS}. + * @return This builder object for chaining method calls + */ + public Builder setDialogType(@DialogType int dialogType) { + this.dialogType = dialogType; + return this; + } + + /** + * Set the colors used for the presets + * + * @param presets + * An array of color ints. + * @return This builder object for chaining method calls + */ + public Builder setPresets(@NonNull int[] presets) { + this.presets = presets; + return this; + } + + /** + * Set the original color + * + * @param color + * The default color for the color picker + * @return This builder object for chaining method calls + */ + public Builder setColor(int color) { + this.color = color; + return this; + } + + /** + * Set the dialog id used for callbacks + * + * @param dialogId + * The id that is sent back to the {@link ColorPickerDialogListener}. + * @return This builder object for chaining method calls + */ + public Builder setDialogId(int dialogId) { + this.dialogId = dialogId; + return this; + } + + /** + * Show the alpha slider + * + * @param showAlphaSlider + * {@code true} to show the alpha slider. Currently only supported with the {@link ColorPickerView}. + * @return This builder object for chaining method calls + */ + public Builder setShowAlphaSlider(boolean showAlphaSlider) { + this.showAlphaSlider = showAlphaSlider; + return this; + } + + /** + * Show/Hide a neutral button to select preset colors. + * + * @param allowPresets + * {@code false} to disable showing the presets button. + * @return This builder object for chaining method calls + */ + public Builder setAllowPresets(boolean allowPresets) { + this.allowPresets = allowPresets; + return this; + } + + /** + * Show/Hide the neutral button to select a custom color. + * + * @param allowCustom + * {@code false} to disable showing the custom button. + * @return This builder object for chaining method calls + */ + public Builder setAllowCustom(boolean allowCustom) { + this.allowCustom = allowCustom; + return this; + } + + /** + * Show/Hide the color shades in the presets picker + * + * @param showColorShades + * {@code false} to hide the color shades. + * @return This builder object for chaining method calls + */ + public Builder setShowColorShades(boolean showColorShades) { + this.showColorShades = showColorShades; + return this; + } + + /** + * Set the shape of the color panel view. + * + * @param colorShape + * Either {@link ColorShape#CIRCLE} or {@link ColorShape#SQUARE}. + * @return This builder object for chaining method calls + */ + public Builder setColorShape(int colorShape) { + this.colorShape = colorShape; + return this; + } + + /** + * Create the {@link ColorPickerDialog} instance. + * + * @return A new {@link ColorPickerDialog}. + * @see #show(Activity) + */ + public ColorPickerDialog create() { + ColorPickerDialog dialog = new ColorPickerDialog(); + Bundle args = new Bundle(); + args.putInt(ARG_ID, dialogId); + args.putInt(ARG_TYPE, dialogType); + args.putInt(ARG_COLOR, color); + args.putIntArray(ARG_PRESETS, presets); + args.putBoolean(ARG_ALPHA, showAlphaSlider); + args.putBoolean(ARG_ALLOW_CUSTOM, allowCustom); + args.putBoolean(ARG_ALLOW_PRESETS, allowPresets); + args.putInt(ARG_DIALOG_TITLE, dialogTitle); + args.putBoolean(ARG_SHOW_COLOR_SHADES, showColorShades); + args.putInt(ARG_COLOR_SHAPE, colorShape); + dialog.setArguments(args); + return dialog; + } + + /** + * Create and show the {@link ColorPickerDialog} created with this builder. + * + * @param activity + * The current activity. + */ + public void show(Activity activity) { + create().show(activity.getFragmentManager(), "color-picker-dialog"); + } + + } + + @IntDef({TYPE_CUSTOM, TYPE_PRESETS}) + public @interface DialogType { + + } + + // endregion + +} diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialogListener.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialogListener.java new file mode 100644 index 00000000..fd069221 --- /dev/null +++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerDialogListener.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 JRummy Apps 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.jrummyapps.android.colorpicker; + +import android.support.annotation.ColorInt; + +/** + * Callback used for getting the selected color from a color picker dialog. + */ +public interface ColorPickerDialogListener { + + /** + * Callback that is invoked when a color is selected from the color picker dialog. + * + * @param dialogId + * The dialog id used to create the dialog instance. + * @param color + * The selected color + */ + void onColorSelected(int dialogId, @ColorInt int color); + + /** + * Callback that is invoked when the color picker dialog was dismissed. + * + * @param dialogId + * The dialog id used to create the dialog instance. + */ + void onDialogDismissed(int dialogId); + +} diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerView.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerView.java new file mode 100644 index 00000000..253ba6d5 --- /dev/null +++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPickerView.java @@ -0,0 +1,1001 @@ +/* + * Copyright (C) 2017 JRummy Apps 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.jrummyapps.android.colorpicker; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.ComposeShader; +import android.graphics.LinearGradient; +import android.graphics.Paint; +import android.graphics.Paint.Align; +import android.graphics.Paint.Style; +import android.graphics.Point; +import android.graphics.PorterDuff; +import android.graphics.Rect; +import android.graphics.RectF; +import android.graphics.Shader; +import android.graphics.Shader.TileMode; +import android.os.Bundle; +import android.os.Parcelable; +import android.util.AttributeSet; +import android.util.TypedValue; +import android.view.MotionEvent; +import android.view.View; + +/** + * Displays a color picker to the user and allow them to select a color. A slider for the alpha channel is also available. + * Enable it by setting setAlphaSliderVisible(boolean) to true. + */ +public class ColorPickerView extends View { + + private final static int DEFAULT_BORDER_COLOR = 0xFF6E6E6E; + private final static int DEFAULT_SLIDER_COLOR = 0xFFBDBDBD; + + private final static int HUE_PANEL_WDITH_DP = 30; + private final static int ALPHA_PANEL_HEIGH_DP = 20; + private final static int PANEL_SPACING_DP = 10; + private final static int CIRCLE_TRACKER_RADIUS_DP = 5; + private final static int SLIDER_TRACKER_SIZE_DP = 4; + private final static int SLIDER_TRACKER_OFFSET_DP = 2; + + /** + * The width in pixels of the border + * surrounding all color panels. + */ + private final static int BORDER_WIDTH_PX = 1; + + /** + * The width in px of the hue panel. + */ + private int huePanelWidthPx; + /** + * The height in px of the alpha panel + */ + private int alphaPanelHeightPx; + /** + * The distance in px between the different + * color panels. + */ + private int panelSpacingPx; + /** + * The radius in px of the color palette tracker circle. + */ + private int circleTrackerRadiusPx; + /** + * The px which the tracker of the hue or alpha panel + * will extend outside of its bounds. + */ + private int sliderTrackerOffsetPx; + /** + * Height of slider tracker on hue panel, + * width of slider on alpha panel. + */ + private int sliderTrackerSizePx; + + private Paint satValPaint; + private Paint satValTrackerPaint; + + private Paint alphaPaint; + private Paint alphaTextPaint; + private Paint hueAlphaTrackerPaint; + + private Paint borderPaint; + + private Shader valShader; + private Shader satShader; + private Shader alphaShader; + + /* + * We cache a bitmap of the sat/val panel which is expensive to draw each time. + * We can reuse it when the user is sliding the circle picker as long as the hue isn't changed. + */ + private BitmapCache satValBackgroundCache; + /* We cache the hue background to since its also very expensive now. */ + private BitmapCache hueBackgroundCache; + + /* Current values */ + private int alpha = 0xff; + private float hue = 360f; + private float sat = 0f; + private float val = 0f; + + private boolean showAlphaPanel = false; + private String alphaSliderText = null; + private int sliderTrackerColor = DEFAULT_SLIDER_COLOR; + private int borderColor = DEFAULT_BORDER_COLOR; + + /** + * Minimum required padding. The offset from the + * edge we must have or else the finger tracker will + * get clipped when it's drawn outside of the view. + */ + private int mRequiredPadding; + + /** + * The Rect in which we are allowed to draw. + * Trackers can extend outside slightly, + * due to the required padding we have set. + */ + private Rect drawingRect; + + private Rect satValRect; + private Rect hueRect; + private Rect alphaRect; + + private Point startTouchPoint = null; + + private AlphaPatternDrawable alphaPatternDrawable; + private OnColorChangedListener onColorChangedListener; + + public ColorPickerView(Context context) { + this(context, null); + } + + public ColorPickerView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public ColorPickerView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(context, attrs); + } + + @Override public Parcelable onSaveInstanceState() { + Bundle state = new Bundle(); + state.putParcelable("instanceState", super.onSaveInstanceState()); + state.putInt("alpha", alpha); + state.putFloat("hue", hue); + state.putFloat("sat", sat); + state.putFloat("val", val); + state.putBoolean("show_alpha", showAlphaPanel); + state.putString("alpha_text", alphaSliderText); + + return state; + } + + @Override public void onRestoreInstanceState(Parcelable state) { + + if (state instanceof Bundle) { + Bundle bundle = (Bundle) state; + + alpha = bundle.getInt("alpha"); + hue = bundle.getFloat("hue"); + sat = bundle.getFloat("sat"); + val = bundle.getFloat("val"); + showAlphaPanel = bundle.getBoolean("show_alpha"); + alphaSliderText = bundle.getString("alpha_text"); + + state = bundle.getParcelable("instanceState"); + } + super.onRestoreInstanceState(state); + } + + private void init(Context context, AttributeSet attrs) { + //Load those if set in xml resource file. + TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPickerView); + showAlphaPanel = a.getBoolean(R.styleable.ColorPickerView_cpv_alphaChannelVisible, false); + alphaSliderText = a.getString(R.styleable.ColorPickerView_cpv_alphaChannelText); + sliderTrackerColor = a.getColor(R.styleable.ColorPickerView_cpv_sliderColor, 0xFFBDBDBD); + borderColor = a.getColor(R.styleable.ColorPickerView_cpv_borderColor, 0xFF6E6E6E); + a.recycle(); + + applyThemeColors(context); + + huePanelWidthPx = DrawingUtils.dpToPx(getContext(), HUE_PANEL_WDITH_DP); + alphaPanelHeightPx = DrawingUtils.dpToPx(getContext(), ALPHA_PANEL_HEIGH_DP); + panelSpacingPx = DrawingUtils.dpToPx(getContext(), PANEL_SPACING_DP); + circleTrackerRadiusPx = DrawingUtils.dpToPx(getContext(), CIRCLE_TRACKER_RADIUS_DP); + sliderTrackerSizePx = DrawingUtils.dpToPx(getContext(), SLIDER_TRACKER_SIZE_DP); + sliderTrackerOffsetPx = DrawingUtils.dpToPx(getContext(), SLIDER_TRACKER_OFFSET_DP); + + mRequiredPadding = getResources().getDimensionPixelSize(R.dimen.cpv_required_padding); + + initPaintTools(); + + //Needed for receiving trackball motion events. + setFocusable(true); + setFocusableInTouchMode(true); + } + + private void applyThemeColors(Context c) { + // If no specific border/slider color has been + // set we take the default secondary text color + // as border/slider color. Thus it will adopt + // to theme changes automatically. + + final TypedValue value = new TypedValue(); + TypedArray a = c.obtainStyledAttributes(value.data, new int[]{android.R.attr.textColorSecondary}); + + if (borderColor == DEFAULT_BORDER_COLOR) { + borderColor = a.getColor(0, DEFAULT_BORDER_COLOR); + } + + if (sliderTrackerColor == DEFAULT_SLIDER_COLOR) { + sliderTrackerColor = a.getColor(0, DEFAULT_SLIDER_COLOR); + } + + a.recycle(); + } + + private void initPaintTools() { + + satValPaint = new Paint(); + satValTrackerPaint = new Paint(); + hueAlphaTrackerPaint = new Paint(); + alphaPaint = new Paint(); + alphaTextPaint = new Paint(); + borderPaint = new Paint(); + + satValTrackerPaint.setStyle(Style.STROKE); + satValTrackerPaint.setStrokeWidth(DrawingUtils.dpToPx(getContext(), 2)); + satValTrackerPaint.setAntiAlias(true); + + hueAlphaTrackerPaint.setColor(sliderTrackerColor); + hueAlphaTrackerPaint.setStyle(Style.STROKE); + hueAlphaTrackerPaint.setStrokeWidth(DrawingUtils.dpToPx(getContext(), 2)); + hueAlphaTrackerPaint.setAntiAlias(true); + + alphaTextPaint.setColor(0xff1c1c1c); + alphaTextPaint.setTextSize(DrawingUtils.dpToPx(getContext(), 14)); + alphaTextPaint.setAntiAlias(true); + alphaTextPaint.setTextAlign(Align.CENTER); + alphaTextPaint.setFakeBoldText(true); + + } + + @Override protected void onDraw(Canvas canvas) { + if (drawingRect.width() <= 0 || drawingRect.height() <= 0) { + return; + } + + drawSatValPanel(canvas); + drawHuePanel(canvas); + drawAlphaPanel(canvas); + } + + private void drawSatValPanel(Canvas canvas) { + final Rect rect = satValRect; + + if (BORDER_WIDTH_PX > 0) { + borderPaint.setColor(borderColor); + canvas.drawRect(drawingRect.left, drawingRect.top, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, borderPaint); + } + + if (valShader == null) { + //Black gradient has either not been created or the view has been resized. + valShader = new LinearGradient(rect.left, rect.top, rect.left, rect.bottom, 0xffffffff, 0xff000000, TileMode.CLAMP); + } + + //If the hue has changed we need to recreate the cache. + if (satValBackgroundCache == null || satValBackgroundCache.value != hue) { + + if (satValBackgroundCache == null) { + satValBackgroundCache = new BitmapCache(); + } + + //We create our bitmap in the cache if it doesn't exist. + if (satValBackgroundCache.bitmap == null) { + satValBackgroundCache.bitmap = Bitmap + .createBitmap(rect.width(), rect.height(), Config.ARGB_8888); + } + + //We create the canvas once so we can draw on our bitmap and the hold on to it. + if (satValBackgroundCache.canvas == null) { + satValBackgroundCache.canvas = new Canvas(satValBackgroundCache.bitmap); + } + + int rgb = Color.HSVToColor(new float[]{hue, 1f, 1f}); + + satShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, 0xffffffff, rgb, TileMode.CLAMP); + + ComposeShader mShader = new ComposeShader( + valShader, satShader, PorterDuff.Mode.MULTIPLY); + satValPaint.setShader(mShader); + + // Finally we draw on our canvas, the result will be + // stored in our bitmap which is already in the cache. + // Since this is drawn on a canvas not rendered on + // screen it will automatically not be using the + // hardware acceleration. And this was the code that + // wasn't supported by hardware acceleration which mean + // there is no need to turn it of anymore. The rest of + // the view will still be hw accelerated. + satValBackgroundCache.canvas.drawRect(0, 0, + satValBackgroundCache.bitmap.getWidth(), + satValBackgroundCache.bitmap.getHeight(), + satValPaint); + + //We set the hue value in our cache to which hue it was drawn with, + //then we know that if it hasn't changed we can reuse our cached bitmap. + satValBackgroundCache.value = hue; + + } + + // We draw our bitmap from the cached, if the hue has changed + // then it was just recreated otherwise the old one will be used. + canvas.drawBitmap(satValBackgroundCache.bitmap, null, rect, null); + + Point p = satValToPoint(sat, val); + + satValTrackerPaint.setColor(0xff000000); + canvas.drawCircle(p.x, p.y, circleTrackerRadiusPx - DrawingUtils.dpToPx(getContext(), 1), satValTrackerPaint); + + satValTrackerPaint.setColor(0xffdddddd); + canvas.drawCircle(p.x, p.y, circleTrackerRadiusPx, satValTrackerPaint); + + } + + private void drawHuePanel(Canvas canvas) { + final Rect rect = hueRect; + + if (BORDER_WIDTH_PX > 0) { + borderPaint.setColor(borderColor); + + canvas.drawRect(rect.left - BORDER_WIDTH_PX, + rect.top - BORDER_WIDTH_PX, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, + borderPaint); + } + + if (hueBackgroundCache == null) { + hueBackgroundCache = new BitmapCache(); + hueBackgroundCache.bitmap = Bitmap.createBitmap(rect.width(), rect.height(), Config.ARGB_8888); + hueBackgroundCache.canvas = new Canvas(hueBackgroundCache.bitmap); + + int[] hueColors = new int[(int) (rect.height() + 0.5f)]; + + // Generate array of all colors, will be drawn as individual lines. + float h = 360f; + for (int i = 0; i < hueColors.length; i++) { + hueColors[i] = Color.HSVToColor(new float[]{h, 1f, 1f}); + h -= 360f / hueColors.length; + } + + // Time to draw the hue color gradient, + // its drawn as individual lines which + // will be quite many when the resolution is high + // and/or the panel is large. + Paint linePaint = new Paint(); + linePaint.setStrokeWidth(0); + for (int i = 0; i < hueColors.length; i++) { + linePaint.setColor(hueColors[i]); + hueBackgroundCache.canvas.drawLine(0, i, hueBackgroundCache.bitmap.getWidth(), i, linePaint); + } + } + + canvas.drawBitmap(hueBackgroundCache.bitmap, null, rect, null); + + Point p = hueToPoint(hue); + + RectF r = new RectF(); + r.left = rect.left - sliderTrackerOffsetPx; + r.right = rect.right + sliderTrackerOffsetPx; + r.top = p.y - (sliderTrackerSizePx / 2); + r.bottom = p.y + (sliderTrackerSizePx / 2); + + canvas.drawRoundRect(r, 2, 2, hueAlphaTrackerPaint); + } + + private void drawAlphaPanel(Canvas canvas) { + /* + * Will be drawn with hw acceleration, very fast. + * Also the AlphaPatternDrawable is backed by a bitmap + * generated only once if the size does not change. + */ + + if (!showAlphaPanel || alphaRect == null || alphaPatternDrawable == null) return; + + final Rect rect = alphaRect; + + if (BORDER_WIDTH_PX > 0) { + borderPaint.setColor(borderColor); + canvas.drawRect(rect.left - BORDER_WIDTH_PX, + rect.top - BORDER_WIDTH_PX, + rect.right + BORDER_WIDTH_PX, + rect.bottom + BORDER_WIDTH_PX, + borderPaint); + } + + alphaPatternDrawable.draw(canvas); + + float[] hsv = new float[]{hue, sat, val}; + int color = Color.HSVToColor(hsv); + int acolor = Color.HSVToColor(0, hsv); + + alphaShader = new LinearGradient(rect.left, rect.top, rect.right, rect.top, + color, acolor, TileMode.CLAMP); + + alphaPaint.setShader(alphaShader); + + canvas.drawRect(rect, alphaPaint); + + if (alphaSliderText != null && !alphaSliderText.equals("")) { + canvas.drawText(alphaSliderText, rect.centerX(), rect.centerY() + DrawingUtils.dpToPx(getContext(), 4), alphaTextPaint); + } + + Point p = alphaToPoint(alpha); + + RectF r = new RectF(); + r.left = p.x - (sliderTrackerSizePx / 2); + r.right = p.x + (sliderTrackerSizePx / 2); + r.top = rect.top - sliderTrackerOffsetPx; + r.bottom = rect.bottom + sliderTrackerOffsetPx; + + canvas.drawRoundRect(r, 2, 2, hueAlphaTrackerPaint); + } + + private Point hueToPoint(float hue) { + + final Rect rect = hueRect; + final float height = rect.height(); + + Point p = new Point(); + + p.y = (int) (height - (hue * height / 360f) + rect.top); + p.x = rect.left; + + return p; + } + + private Point satValToPoint(float sat, float val) { + + final Rect rect = satValRect; + final float height = rect.height(); + final float width = rect.width(); + + Point p = new Point(); + + p.x = (int) (sat * width + rect.left); + p.y = (int) ((1f - val) * height + rect.top); + + return p; + } + + private Point alphaToPoint(int alpha) { + + final Rect rect = alphaRect; + final float width = rect.width(); + + Point p = new Point(); + + p.x = (int) (width - (alpha * width / 0xff) + rect.left); + p.y = rect.top; + + return p; + + } + + private float[] pointToSatVal(float x, float y) { + + final Rect rect = satValRect; + float[] result = new float[2]; + + float width = rect.width(); + float height = rect.height(); + + if (x < rect.left) { + x = 0f; + } else if (x > rect.right) { + x = width; + } else { + x = x - rect.left; + } + + if (y < rect.top) { + y = 0f; + } else if (y > rect.bottom) { + y = height; + } else { + y = y - rect.top; + } + + result[0] = 1.f / width * x; + result[1] = 1.f - (1.f / height * y); + + return result; + } + + private float pointToHue(float y) { + + final Rect rect = hueRect; + + float height = rect.height(); + + if (y < rect.top) { + y = 0f; + } else if (y > rect.bottom) { + y = height; + } else { + y = y - rect.top; + } + + float hue = 360f - (y * 360f / height); + + return hue; + } + + private int pointToAlpha(int x) { + + final Rect rect = alphaRect; + final int width = rect.width(); + + if (x < rect.left) { + x = 0; + } else if (x > rect.right) { + x = width; + } else { + x = x - rect.left; + } + + return 0xff - (x * 0xff / width); + + } + + @Override public boolean onTouchEvent(MotionEvent event) { + + try{ + this.getParent().requestDisallowInterceptTouchEvent( true ); + }catch(Throwable ignored){ + } + + boolean update = false; + + switch (event.getAction()) { + + case MotionEvent.ACTION_DOWN: + startTouchPoint = new Point((int) event.getX(), (int) event.getY()); + update = moveTrackersIfNeeded(event); + break; + case MotionEvent.ACTION_MOVE: + update = moveTrackersIfNeeded(event); + break; + case MotionEvent.ACTION_UP: + startTouchPoint = null; + update = moveTrackersIfNeeded(event); + break; + } + + if (update) { + if (onColorChangedListener != null) { + onColorChangedListener.onColorChanged(Color.HSVToColor(alpha, new float[]{hue, sat, val})); + } + invalidate(); + return true; + } + + return super.onTouchEvent(event); + } + + private boolean moveTrackersIfNeeded(MotionEvent event) { + if (startTouchPoint == null) { + return false; + } + + boolean update = false; + + int startX = startTouchPoint.x; + int startY = startTouchPoint.y; + + if (hueRect.contains(startX, startY)) { + hue = pointToHue(event.getY()); + + update = true; + } else if (satValRect.contains(startX, startY)) { + float[] result = pointToSatVal(event.getX(), event.getY()); + + sat = result[0]; + val = result[1]; + + update = true; + } else if (alphaRect != null && alphaRect.contains(startX, startY)) { + alpha = pointToAlpha((int) event.getX()); + + update = true; + } + + return update; + } + + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int finalWidth; + int finalHeight; + + int widthMode = MeasureSpec.getMode(widthMeasureSpec); + int heightMode = MeasureSpec.getMode(heightMeasureSpec); + + int widthAllowed = MeasureSpec.getSize(widthMeasureSpec) - getPaddingLeft() - getPaddingRight(); + int heightAllowed = + MeasureSpec.getSize(heightMeasureSpec) - getPaddingBottom() - getPaddingTop(); + + if (widthMode == MeasureSpec.EXACTLY || heightMode == MeasureSpec.EXACTLY) { + //A exact value has been set in either direction, we need to stay within this size. + + if (widthMode == MeasureSpec.EXACTLY && heightMode != MeasureSpec.EXACTLY) { + //The with has been specified exactly, we need to adopt the height to fit. + int h = (widthAllowed - panelSpacingPx - huePanelWidthPx); + + if (showAlphaPanel) { + h += panelSpacingPx + alphaPanelHeightPx; + } + + if (h > heightAllowed) { + //We can't fit the view in this container, set the size to whatever was allowed. + finalHeight = heightAllowed; + } else { + finalHeight = h; + } + + finalWidth = widthAllowed; + + } else if (heightMode == MeasureSpec.EXACTLY && widthMode != MeasureSpec.EXACTLY) { + //The height has been specified exactly, we need to stay within this height and adopt the width. + + int w = (heightAllowed + panelSpacingPx + huePanelWidthPx); + + if (showAlphaPanel) { + w -= (panelSpacingPx + alphaPanelHeightPx); + } + + if (w > widthAllowed) { + //we can't fit within this container, set the size to whatever was allowed. + finalWidth = widthAllowed; + } else { + finalWidth = w; + } + + finalHeight = heightAllowed; + + } else { + //If we get here the dev has set the width and height to exact sizes. For example match_parent or 300dp. + //This will mean that the sat/val panel will not be square but it doesn't matter. It will work anyway. + //In all other senarios our goal is to make that panel square. + + //We set the sizes to exactly what we were told. + finalWidth = widthAllowed; + finalHeight = heightAllowed; + } + + } else { + //If no exact size has been set we try to make our view as big as possible + //within the allowed space. + + //Calculate the needed width to layout using max allowed height. + int widthNeeded = (heightAllowed + panelSpacingPx + huePanelWidthPx); + + //Calculate the needed height to layout using max allowed width. + int heightNeeded = (widthAllowed - panelSpacingPx - huePanelWidthPx); + + if (showAlphaPanel) { + widthNeeded -= (panelSpacingPx + alphaPanelHeightPx); + heightNeeded += panelSpacingPx + alphaPanelHeightPx; + } + + boolean widthOk = false; + boolean heightOk = false; + + if (widthNeeded <= widthAllowed) { + widthOk = true; + } + + if (heightNeeded <= heightAllowed) { + heightOk = true; + } + + if (widthOk && heightOk) { + finalWidth = widthAllowed; + finalHeight = heightNeeded; + } else if (!heightOk && widthOk) { + finalHeight = heightAllowed; + finalWidth = widthNeeded; + } else if (!widthOk && heightOk) { + finalHeight = heightNeeded; + finalWidth = widthAllowed; + } else { + finalHeight = heightAllowed; + finalWidth = widthAllowed; + } + + } + + setMeasuredDimension(finalWidth + getPaddingLeft() + getPaddingRight(), + finalHeight + getPaddingTop() + getPaddingBottom()); + } + + private int getPreferredWidth() { + //Our preferred width and height is 200dp for the square sat / val rectangle. + int width = DrawingUtils.dpToPx(getContext(), 200); + + return (width + huePanelWidthPx + panelSpacingPx); + } + + private int getPreferredHeight() { + int height = DrawingUtils.dpToPx(getContext(), 200); + + if (showAlphaPanel) { + height += panelSpacingPx + alphaPanelHeightPx; + } + return height; + } + + @Override public int getPaddingTop() { + return Math.max(super.getPaddingTop(), mRequiredPadding); + } + + @Override public int getPaddingBottom() { + return Math.max(super.getPaddingBottom(), mRequiredPadding); + } + + @Override public int getPaddingLeft() { + return Math.max(super.getPaddingLeft(), mRequiredPadding); + } + + @Override public int getPaddingRight() { + return Math.max(super.getPaddingRight(), mRequiredPadding); + } + + @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + + drawingRect = new Rect(); + drawingRect.left = getPaddingLeft(); + drawingRect.right = w - getPaddingRight(); + drawingRect.top = getPaddingTop(); + drawingRect.bottom = h - getPaddingBottom(); + + //The need to be recreated because they depend on the size of the view. + valShader = null; + satShader = null; + alphaShader = null; + + // Clear those bitmap caches since the size may have changed. + satValBackgroundCache = null; + hueBackgroundCache = null; + + setUpSatValRect(); + setUpHueRect(); + setUpAlphaRect(); + } + + private void setUpSatValRect() { + //Calculate the size for the big color rectangle. + final Rect dRect = drawingRect; + + int left = dRect.left + BORDER_WIDTH_PX; + int top = dRect.top + BORDER_WIDTH_PX; + int bottom = dRect.bottom - BORDER_WIDTH_PX; + int right = dRect.right - BORDER_WIDTH_PX - panelSpacingPx - huePanelWidthPx; + + if (showAlphaPanel) { + bottom -= (alphaPanelHeightPx + panelSpacingPx); + } + + satValRect = new Rect(left, top, right, bottom); + } + + private void setUpHueRect() { + //Calculate the size for the hue slider on the left. + final Rect dRect = drawingRect; + + int left = dRect.right - huePanelWidthPx + BORDER_WIDTH_PX; + int top = dRect.top + BORDER_WIDTH_PX; + int bottom = dRect.bottom - BORDER_WIDTH_PX - + (showAlphaPanel ? (panelSpacingPx + alphaPanelHeightPx) : 0); + int right = dRect.right - BORDER_WIDTH_PX; + + hueRect = new Rect(left, top, right, bottom); + } + + private void setUpAlphaRect() { + + if (!showAlphaPanel) return; + + final Rect dRect = drawingRect; + + int left = dRect.left + BORDER_WIDTH_PX; + int top = dRect.bottom - alphaPanelHeightPx + BORDER_WIDTH_PX; + int bottom = dRect.bottom - BORDER_WIDTH_PX; + int right = dRect.right - BORDER_WIDTH_PX; + + alphaRect = new Rect(left, top, right, bottom); + + alphaPatternDrawable = new AlphaPatternDrawable(DrawingUtils.dpToPx(getContext(), 4)); + alphaPatternDrawable.setBounds(Math.round(alphaRect.left), Math + .round(alphaRect.top), Math.round(alphaRect.right), Math + .round(alphaRect.bottom)); + } + + /** + * Set a OnColorChangedListener to get notified when the color + * selected by the user has changed. + * + * @param listener + * the listener + */ + public void setOnColorChangedListener(OnColorChangedListener listener) { + onColorChangedListener = listener; + } + + /** + * Get the current color this view is showing. + * + * @return the current color. + */ + public int getColor() { + return Color.HSVToColor(alpha, new float[]{hue, sat, val}); + } + + /** + * Set the color the view should show. + * + * @param color + * The color that should be selected. #argb + */ + public void setColor(int color) { + setColor(color, false); + } + + /** + * Set the color this view should show. + * + * @param color + * The color that should be selected. #argb + * @param callback + * If you want to get a callback to your OnColorChangedListener. + */ + public void setColor(int color, boolean callback) { + + int alpha = Color.alpha(color); + int red = Color.red(color); + int blue = Color.blue(color); + int green = Color.green(color); + + float[] hsv = new float[3]; + + Color.RGBToHSV(red, green, blue, hsv); + + this.alpha = alpha; + hue = hsv[0]; + sat = hsv[1]; + val = hsv[2]; + + if (callback && onColorChangedListener != null) { + onColorChangedListener + .onColorChanged(Color.HSVToColor(this.alpha, new float[]{hue, sat, val})); + } + + invalidate(); + } + + /** + * Set if the user is allowed to adjust the alpha panel. Default is false. + * If it is set to false no alpha will be set. + * + * @param visible + * {@code true} to show the alpha slider + */ + public void setAlphaSliderVisible(boolean visible) { + if (showAlphaPanel != visible) { + showAlphaPanel = visible; + + /* + * Force recreation. + */ + valShader = null; + satShader = null; + alphaShader = null; + hueBackgroundCache = null; + satValBackgroundCache = null; + + requestLayout(); + } + + } + + /** + * Set the color of the tracker slider on the hue and alpha panel. + * + * @param color + * a color value + */ + public void setSliderTrackerColor(int color) { + sliderTrackerColor = color; + hueAlphaTrackerPaint.setColor(sliderTrackerColor); + invalidate(); + } + + /** + * Get color of the tracker slider on the hue and alpha panel. + * + * @return the color value + */ + public int getSliderTrackerColor() { + return sliderTrackerColor; + } + + /** + * Set the color of the border surrounding all panels. + * + * @param color + * a color value + */ + public void setBorderColor(int color) { + borderColor = color; + invalidate(); + } + + /** + * Get the color of the border surrounding all panels. + */ + public int getBorderColor() { + return borderColor; + } + + /** + * Set the text that should be shown in the + * alpha slider. Set to null to disable text. + * + * @param res + * string resource id. + */ + public void setAlphaSliderText(int res) { + String text = getContext().getString(res); + setAlphaSliderText(text); + } + + /** + * 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) { + alphaSliderText = text; + invalidate(); + } + + /** + * Get the current value of the text + * that will be shown in the alpha + * slider. + * + * @return the slider text + */ + public String getAlphaSliderText() { + return alphaSliderText; + } + + private class BitmapCache { + + public Canvas canvas; + public Bitmap bitmap; + public float value; + } + + public interface OnColorChangedListener { + + void onColorChanged(int newColor); + } + +} diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPreference.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPreference.java new file mode 100644 index 00000000..999446f1 --- /dev/null +++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorPreference.java @@ -0,0 +1,214 @@ +/* + * Copyright (C) 2017 JRummy Apps 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.jrummyapps.android.colorpicker; + +import android.app.Activity; +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.preference.Preference; +import android.support.annotation.ColorInt; +import android.support.annotation.NonNull; +import android.util.AttributeSet; +import android.view.View; +import com.jrummyapps.android.colorpicker.ColorPickerDialog.DialogType; + +/** + * A Preference to select a color + */ +public class ColorPreference extends Preference implements ColorPickerDialogListener { + + private static final int SIZE_NORMAL = 0; + private static final int SIZE_LARGE = 1; + + private OnShowDialogListener onShowDialogListener; + private int color = Color.BLACK; + private boolean showDialog; + @DialogType + private int dialogType; + private int colorShape; + private boolean allowPresets; + private boolean allowCustom; + private boolean showAlphaSlider; + private boolean showColorShades; + private int previewSize; + private int[] presets; + private int dialogTitle; + + public ColorPreference(Context context, AttributeSet attrs) { + super(context, attrs); + init(attrs); + } + + public ColorPreference(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(attrs); + } + + private void init(AttributeSet attrs) { + setPersistent(true); + TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ColorPreference); + showDialog = a.getBoolean(R.styleable.ColorPreference_cpv_showDialog, true); + //noinspection WrongConstant + dialogType = a.getInt(R.styleable.ColorPreference_cpv_dialogType, ColorPickerDialog.TYPE_PRESETS); + colorShape = a.getInt(R.styleable.ColorPreference_cpv_colorShape, ColorShape.CIRCLE); + allowPresets = a.getBoolean(R.styleable.ColorPreference_cpv_allowPresets, true); + allowCustom = a.getBoolean(R.styleable.ColorPreference_cpv_allowCustom, true); + showAlphaSlider = a.getBoolean(R.styleable.ColorPreference_cpv_showAlphaSlider, false); + showColorShades = a.getBoolean(R.styleable.ColorPreference_cpv_showColorShades, true); + previewSize = a.getInt(R.styleable.ColorPreference_cpv_previewSize, SIZE_NORMAL); + final int presetsResId = a.getResourceId(R.styleable.ColorPreference_cpv_colorPresets, 0); + dialogTitle = a.getResourceId(R.styleable.ColorPreference_cpv_dialogTitle, R.string.cpv_default_title); + if (presetsResId != 0) { + presets = getContext().getResources().getIntArray(presetsResId); + } else { + presets = ColorPickerDialog.MATERIAL_COLORS; + } + if (colorShape == ColorShape.CIRCLE) { + setWidgetLayoutResource( + previewSize == SIZE_LARGE ? R.layout.cpv_preference_circle_large : R.layout.cpv_preference_circle); + } else { + setWidgetLayoutResource( + previewSize == SIZE_LARGE ? R.layout.cpv_preference_square_large : R.layout.cpv_preference_square + ); + } + a.recycle(); + } + + @Override protected void onClick() { + super.onClick(); + if (onShowDialogListener != null) { + onShowDialogListener.onShowColorPickerDialog((String) getTitle(), color); + } else if (showDialog) { + ColorPickerDialog dialog = ColorPickerDialog.newBuilder() + .setDialogType(dialogType) + .setDialogTitle(dialogTitle) + .setColorShape(colorShape) + .setPresets(presets) + .setAllowPresets(allowPresets) + .setAllowCustom(allowCustom) + .setShowAlphaSlider(showAlphaSlider) + .setShowColorShades(showColorShades) + .setColor(color) + .create(); + dialog.setColorPickerDialogListener(ColorPreference.this); + Activity activity = (Activity) getContext(); + dialog.show(activity.getFragmentManager(), getFragmentTag()); + } + } + + @Override protected void onAttachedToActivity() { + super.onAttachedToActivity(); + + if (showDialog) { + Activity activity = (Activity) getContext(); + ColorPickerDialog fragment = + (ColorPickerDialog) activity.getFragmentManager().findFragmentByTag(getFragmentTag()); + if (fragment != null) { + // re-bind preference to fragment + fragment.setColorPickerDialogListener(this); + } + } + } + + @Override protected void onBindView(View view) { + super.onBindView(view); + ColorPanelView preview = (ColorPanelView) view.findViewById(R.id.cpv_preference_preview_color_panel); + if (preview != null) { + preview.setColor(color); + } + } + + @Override protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { + if (restorePersistedValue) { + color = getPersistedInt(0xFF000000); + } else { + color = (Integer) defaultValue; + persistInt(color); + } + } + + @Override protected Object onGetDefaultValue(TypedArray a, int index) { + return a.getInteger(index, Color.BLACK); + } + + @Override public void onColorSelected(int dialogId, @ColorInt int color) { + saveValue(color); + } + + @Override public void onDialogDismissed(int dialogId) { + // no-op + } + + /** + * Set the new color + * + * @param color + * The newly selected color + */ + public void saveValue(@ColorInt int color) { + this.color = color; + persistInt(this.color); + notifyChanged(); + callChangeListener(color); + } + + /** + * Set the colors shown in the {@link ColorPickerDialog}. + * + * @param presets An array of color ints + */ + public void setPresets(@NonNull int[] presets) { + this.presets = presets; + } + + /** + * Get the colors that will be shown in the {@link ColorPickerDialog}. + * + * @return An array of color ints + */ + public int[] getPresets() { + return presets; + } + + /** + * The listener used for showing the {@link ColorPickerDialog}. + * Call {@link #saveValue(int)} after the user chooses a color. + * If this is set then it is up to you to show the dialog. + * + * @param listener + * The listener to show the dialog + */ + public void setOnShowDialogListener(OnShowDialogListener listener) { + onShowDialogListener = listener; + } + + /** + * The tag used for the {@link ColorPickerDialog}. + * + * @return The tag + */ + public String getFragmentTag() { + return "color_" + getKey(); + } + + public interface OnShowDialogListener { + + void onShowColorPickerDialog(String title, int currentColor); + } + +} diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorShape.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorShape.java new file mode 100644 index 00000000..d26d46b0 --- /dev/null +++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/ColorShape.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2017 JRummy Apps 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.jrummyapps.android.colorpicker; + +import android.support.annotation.IntDef; + +/** + * The shape of the color preview + */ +@IntDef({ColorShape.SQUARE, ColorShape.CIRCLE}) +public @interface ColorShape { + + int SQUARE = 0; + + int CIRCLE = 1; + +} diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/DrawingUtils.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/DrawingUtils.java new file mode 100644 index 00000000..1c9ce013 --- /dev/null +++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/DrawingUtils.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2017 JRummy Apps 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.jrummyapps.android.colorpicker; + +import android.content.Context; +import android.util.DisplayMetrics; +import android.util.TypedValue; + +final class DrawingUtils { + + static int dpToPx(Context c, float dipValue) { + DisplayMetrics metrics = c.getResources().getDisplayMetrics(); + float val = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dipValue, metrics); + int res = (int) (val + 0.5); // Round + // Ensure at least 1 pixel if val was > 0 + return res == 0 && val > 0 ? 1 : res; + } + +} diff --git a/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/NestedGridView.java b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/NestedGridView.java new file mode 100644 index 00000000..9528fca3 --- /dev/null +++ b/colorpicker/src/main/java/com/jrummyapps/android/colorpicker/NestedGridView.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2017 JRummy Apps 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.jrummyapps.android.colorpicker; + +import android.content.Context; +import android.support.annotation.RestrictTo; +import android.util.AttributeSet; +import android.widget.GridView; + +@RestrictTo(RestrictTo.Scope.LIBRARY) +public class NestedGridView extends GridView { + + public NestedGridView(Context context) { + super(context); + } + + public NestedGridView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public NestedGridView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + @Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + int expandSpec = MeasureSpec.makeMeasureSpec(Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST); + super.onMeasure(widthMeasureSpec, expandSpec); + } + +} diff --git a/colorpicker/src/main/res/drawable-hdpi/cpv_alpha.png b/colorpicker/src/main/res/drawable-hdpi/cpv_alpha.png new file mode 100644 index 00000000..e3fe9c85 Binary files /dev/null and b/colorpicker/src/main/res/drawable-hdpi/cpv_alpha.png differ diff --git a/colorpicker/src/main/res/drawable-xhdpi/cpv_alpha.png b/colorpicker/src/main/res/drawable-xhdpi/cpv_alpha.png new file mode 100644 index 00000000..c51c6120 Binary files /dev/null and b/colorpicker/src/main/res/drawable-xhdpi/cpv_alpha.png differ diff --git a/colorpicker/src/main/res/drawable-xxhdpi/cpv_alpha.png b/colorpicker/src/main/res/drawable-xxhdpi/cpv_alpha.png new file mode 100644 index 00000000..c81fc262 Binary files /dev/null and b/colorpicker/src/main/res/drawable-xxhdpi/cpv_alpha.png differ diff --git a/colorpicker/src/main/res/drawable/cpv_btn_background.xml b/colorpicker/src/main/res/drawable/cpv_btn_background.xml new file mode 100644 index 00000000..bf0d4e0e --- /dev/null +++ b/colorpicker/src/main/res/drawable/cpv_btn_background.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/colorpicker/src/main/res/drawable/cpv_btn_background_pressed.xml b/colorpicker/src/main/res/drawable/cpv_btn_background_pressed.xml new file mode 100644 index 00000000..65986ced --- /dev/null +++ b/colorpicker/src/main/res/drawable/cpv_btn_background_pressed.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/colorpicker/src/main/res/drawable/cpv_ic_arrow_right_black_24dp.xml b/colorpicker/src/main/res/drawable/cpv_ic_arrow_right_black_24dp.xml new file mode 100644 index 00000000..1c610c71 --- /dev/null +++ b/colorpicker/src/main/res/drawable/cpv_ic_arrow_right_black_24dp.xml @@ -0,0 +1,10 @@ + + + diff --git a/colorpicker/src/main/res/drawable/cpv_preset_checked.xml b/colorpicker/src/main/res/drawable/cpv_preset_checked.xml new file mode 100644 index 00000000..44c47664 --- /dev/null +++ b/colorpicker/src/main/res/drawable/cpv_preset_checked.xml @@ -0,0 +1,9 @@ + + + diff --git a/colorpicker/src/main/res/layout-w360dp/cpv_dialog_color_picker.xml b/colorpicker/src/main/res/layout-w360dp/cpv_dialog_color_picker.xml new file mode 100644 index 00000000..0cb8773d --- /dev/null +++ b/colorpicker/src/main/res/layout-w360dp/cpv_dialog_color_picker.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/colorpicker/src/main/res/layout/cpv_color_item_circle.xml b/colorpicker/src/main/res/layout/cpv_color_item_circle.xml new file mode 100644 index 00000000..51d59be5 --- /dev/null +++ b/colorpicker/src/main/res/layout/cpv_color_item_circle.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/colorpicker/src/main/res/layout/cpv_color_item_square.xml b/colorpicker/src/main/res/layout/cpv_color_item_square.xml new file mode 100644 index 00000000..e6d991d4 --- /dev/null +++ b/colorpicker/src/main/res/layout/cpv_color_item_square.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/colorpicker/src/main/res/layout/cpv_dialog_color_picker.xml b/colorpicker/src/main/res/layout/cpv_dialog_color_picker.xml new file mode 100644 index 00000000..28289dce --- /dev/null +++ b/colorpicker/src/main/res/layout/cpv_dialog_color_picker.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/colorpicker/src/main/res/layout/cpv_dialog_presets.xml b/colorpicker/src/main/res/layout/cpv_dialog_presets.xml new file mode 100644 index 00000000..e7d2dfd9 --- /dev/null +++ b/colorpicker/src/main/res/layout/cpv_dialog_presets.xml @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/colorpicker/src/main/res/layout/cpv_preference_circle.xml b/colorpicker/src/main/res/layout/cpv_preference_circle.xml new file mode 100644 index 00000000..17d75c7d --- /dev/null +++ b/colorpicker/src/main/res/layout/cpv_preference_circle.xml @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/colorpicker/src/main/res/layout/cpv_preference_circle_large.xml b/colorpicker/src/main/res/layout/cpv_preference_circle_large.xml new file mode 100644 index 00000000..66efe8e8 --- /dev/null +++ b/colorpicker/src/main/res/layout/cpv_preference_circle_large.xml @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/colorpicker/src/main/res/layout/cpv_preference_square.xml b/colorpicker/src/main/res/layout/cpv_preference_square.xml new file mode 100644 index 00000000..7943193c --- /dev/null +++ b/colorpicker/src/main/res/layout/cpv_preference_square.xml @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/colorpicker/src/main/res/layout/cpv_preference_square_large.xml b/colorpicker/src/main/res/layout/cpv_preference_square_large.xml new file mode 100644 index 00000000..6835795e --- /dev/null +++ b/colorpicker/src/main/res/layout/cpv_preference_square_large.xml @@ -0,0 +1,7 @@ + \ No newline at end of file diff --git a/colorpicker/src/main/res/values-it-rIT/strings.xml b/colorpicker/src/main/res/values-it-rIT/strings.xml new file mode 100644 index 00000000..1e24ac7b --- /dev/null +++ b/colorpicker/src/main/res/values-it-rIT/strings.xml @@ -0,0 +1,22 @@ + + + + Personalizzato + Seleziona un Colore + Predefiniti + Seleziona + Trasparenza + diff --git a/colorpicker/src/main/res/values-ja/strings.xml b/colorpicker/src/main/res/values-ja/strings.xml new file mode 100644 index 00000000..29bcd725 --- /dev/null +++ b/colorpicker/src/main/res/values-ja/strings.xml @@ -0,0 +1,23 @@ + + + + + 色を選択 + プリセット + カスタム + 選択 + 透明度 + \ No newline at end of file diff --git a/colorpicker/src/main/res/values/attrs.xml b/colorpicker/src/main/res/values/attrs.xml new file mode 100644 index 00000000..a77738fc --- /dev/null +++ b/colorpicker/src/main/res/values/attrs.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/colorpicker/src/main/res/values/dimen.xml b/colorpicker/src/main/res/values/dimen.xml new file mode 100644 index 00000000..b79627d5 --- /dev/null +++ b/colorpicker/src/main/res/values/dimen.xml @@ -0,0 +1,11 @@ + + + 6dp + 66dp + 34dp + 58dp + 50dp + 8dp + 28dp + 40dp + diff --git a/colorpicker/src/main/res/values/ids.xml b/colorpicker/src/main/res/values/ids.xml new file mode 100644 index 00000000..88824f42 --- /dev/null +++ b/colorpicker/src/main/res/values/ids.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/colorpicker/src/main/res/values/strings.xml b/colorpicker/src/main/res/values/strings.xml new file mode 100644 index 00000000..416b5d67 --- /dev/null +++ b/colorpicker/src/main/res/values/strings.xml @@ -0,0 +1,23 @@ + + + + + Select a Color + Presets + Custom + Select + Transparency + \ No newline at end of file diff --git a/colorpicker/src/main/res/values/styles.xml b/colorpicker/src/main/res/values/styles.xml new file mode 100644 index 00000000..77f9c8de --- /dev/null +++ b/colorpicker/src/main/res/values/styles.xml @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file