some changes

custom_theming
Thomas 2 years ago
parent 4a8d20ed6b
commit 8ffd5b3a19

@ -96,12 +96,11 @@ allprojects {
}
}
dependencies {
implementation project(':autoimageslider')
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.8.0'
implementation 'com.jaredrummler:colorpicker:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
implementation "com.google.code.gson:gson:2.9.1"
@ -121,15 +120,18 @@ dependencies {
}
implementation "org.jsoup:jsoup:1.15.1"
implementation 'com.github.mergehez:ArgPlayer:v3.1'
implementation project(':autoimageslider')
implementation project(path: ':mytransl')
implementation project(path: ':ratethisapp')
implementation project(path: ':sparkbutton')
implementation project(path: ':colorPicker')
implementation project(path: ':mathjaxandroid')
implementation 'com.burhanrashid52:photoeditor:1.5.1'
implementation("com.vanniktech:android-image-cropper:4.3.3")
implementation project(path: ':mathjaxandroid')
annotationProcessor "com.github.bumptech.glide:compiler:4.12.0"
implementation 'jp.wasabeef:glide-transformations:4.3.0'
implementation 'com.github.penfeizhou.android.animation:glide-plugin:2.23.0'
@ -165,7 +167,6 @@ dependencies {
implementation 'com.r0adkll:slidableactivity:2.1.0'
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
implementation 'androidx.vectordrawable:vectordrawable:1.1.0'
implementation "androidx.fragment:fragment:1.5.5"
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

@ -1067,6 +1067,16 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt
} catch (DBException e) {
e.printStackTrace();
}
if (currentAccount != null && currentInstance == null) {
currentInstance = currentAccount.instance;
currentUserID = currentAccount.user_id;
Handler mainHandler = new Handler(Looper.getMainLooper());
Runnable myRunnable = () -> {
recreate();
};
mainHandler.post(myRunnable);
}
if (currentAccount != null && currentAccount.peertube_account != null) {
//It is a peertube user
Intent intent = getIntent();

@ -30,7 +30,6 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.PreferenceManager;
import com.google.android.material.color.DynamicColors;
import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.one.EmojiOneProvider;
@ -52,6 +51,7 @@ public class BaseActivity extends AppCompatActivity {
EmojiManager.install(new EmojiOneProvider());
}
@SuppressLint("RestrictedApi")
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -70,9 +70,8 @@ public class BaseActivity extends AppCompatActivity {
String currentTheme = sharedpreferences.getString(getString(R.string.SET_THEME_BASE), getString(R.string.SET_DEFAULT_THEME));
//Default automatic switch
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (currentTheme.equals(getString(R.string.SET_DEFAULT_THEME))) {
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
switch (currentNightMode) {
case Configuration.UI_MODE_NIGHT_NO:
String defaultLight = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_LIGHT), "LIGHT");
@ -144,10 +143,8 @@ public class BaseActivity extends AppCompatActivity {
}
}
super.onCreate(savedInstanceState);
boolean dynamicColor = sharedpreferences.getBoolean(getString(R.string.SET_DYNAMICCOLOR), false);
if (dynamicColor) {
DynamicColors.applyToActivityIfAvailable(this);
}
ThemeHelper.applyThemeColor(this);
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.N) {
ThemeHelper.adjustFontScale(this, getResources().getConfiguration());
}

@ -30,7 +30,6 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.PreferenceManager;
import com.google.android.material.color.DynamicColors;
import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.one.EmojiOneProvider;
@ -66,9 +65,8 @@ public class BaseBarActivity extends AppCompatActivity {
}
String currentTheme = sharedpreferences.getString(getString(R.string.SET_THEME_BASE), getString(R.string.SET_DEFAULT_THEME));
//Default automatic switch
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (currentTheme.equals(getString(R.string.SET_DEFAULT_THEME))) {
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
switch (currentNightMode) {
case Configuration.UI_MODE_NIGHT_NO:
String defaultLight = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_LIGHT), "LIGHT");
@ -129,10 +127,8 @@ public class BaseBarActivity extends AppCompatActivity {
}
}
super.onCreate(savedInstanceState);
boolean dynamicColor = sharedpreferences.getBoolean(getString(R.string.SET_DYNAMICCOLOR), false);
if (dynamicColor) {
DynamicColors.applyToActivityIfAvailable(this);
}
ThemeHelper.applyThemeColor(this);
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);

@ -30,7 +30,6 @@ import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.app.AppCompatDelegate;
import androidx.preference.PreferenceManager;
import com.google.android.material.color.DynamicColors;
import com.vanniktech.emoji.EmojiManager;
import com.vanniktech.emoji.one.EmojiOneProvider;
@ -66,9 +65,9 @@ public class BaseTransparentActivity extends AppCompatActivity {
}
String currentTheme = sharedpreferences.getString(getString(R.string.SET_THEME_BASE), getString(R.string.SET_DEFAULT_THEME));
//Default automatic switch
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
if (currentTheme.equals(getString(R.string.SET_DEFAULT_THEME))) {
int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
switch (currentNightMode) {
case Configuration.UI_MODE_NIGHT_NO:
String defaultLight = sharedpreferences.getString(getString(R.string.SET_THEME_DEFAULT_LIGHT), "LIGHT");
@ -129,10 +128,7 @@ public class BaseTransparentActivity extends AppCompatActivity {
}
}
super.onCreate(savedInstanceState);
boolean dynamicColor = sharedpreferences.getBoolean(getString(R.string.SET_DYNAMICCOLOR), false);
if (dynamicColor) {
DynamicColors.applyToActivityIfAvailable(this);
}
ThemeHelper.applyThemeColor(this);
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);

@ -15,6 +15,8 @@ package app.fedilab.android.mastodon.helper;
* see <http://www.gnu.org/licenses>. */
import static android.content.Context.WINDOW_SERVICE;
import static app.fedilab.android.BaseMainActivity.currentInstance;
import static app.fedilab.android.BaseMainActivity.currentUserID;
import android.app.Activity;
import android.content.Context;
@ -23,6 +25,8 @@ import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.os.Build;
import android.util.DisplayMetrics;
import android.util.TypedValue;
@ -37,6 +41,9 @@ import androidx.appcompat.app.AppCompatDelegate;
import androidx.core.content.ContextCompat;
import androidx.preference.PreferenceManager;
import com.google.android.material.color.DynamicColors;
import com.google.android.material.color.DynamicColorsOptions;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@ -278,6 +285,31 @@ public class ThemeHelper {
}
}
public static void applyThemeColor(Activity activity) {
int currentNightMode = activity.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK;
final SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity);
boolean dynamicColor = sharedpreferences.getBoolean(activity.getString(R.string.SET_DYNAMICCOLOR), false);
boolean customAccentEnabled = sharedpreferences.getBoolean(activity.getString(R.string.SET_CUSTOM_ACCENT) + currentUserID + currentInstance, false);
int customAccentLight = sharedpreferences.getInt(activity.getString(R.string.SET_CUSTOM_ACCENT_LIGHT_VALUE) + currentUserID + currentInstance, -1);
int customAccentDark = sharedpreferences.getInt(activity.getString(R.string.SET_CUSTOM_ACCENT_DARK_VALUE) + currentUserID + currentInstance, -1);
if (customAccentEnabled) {
Bitmap bmp = Bitmap.createBitmap(50, 50, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
//Light theme enabled
if (currentNightMode == Configuration.UI_MODE_NIGHT_NO && customAccentLight != -1) {
canvas.drawColor(customAccentLight);
} else if (customAccentDark != -1) {
canvas.drawColor(customAccentDark);
}
DynamicColorsOptions.Builder builder = new DynamicColorsOptions.Builder();
builder.setContentBasedSource(bmp);
DynamicColorsOptions dynamicColorsOptions = builder.build();
DynamicColors.applyToActivityIfAvailable(activity, dynamicColorsOptions);
} else if (dynamicColor) {
DynamicColors.applyToActivityIfAvailable(activity);
}
}
public enum themes {
LIGHT,
DARK,

@ -15,8 +15,13 @@ package app.fedilab.android.mastodon.ui.fragment.settings;
* see <http://www.gnu.org/licenses>. */
import static app.fedilab.android.BaseMainActivity.currentInstance;
import static app.fedilab.android.BaseMainActivity.currentUserID;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import androidx.appcompat.app.AlertDialog;
import androidx.navigation.NavOptions;
@ -24,16 +29,21 @@ import androidx.navigation.Navigation;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
import androidx.preference.PreferenceManager;
import androidx.preference.SwitchPreferenceCompat;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.jaredrummler.android.colorpicker.ColorPreferenceCompat;
import app.fedilab.android.R;
import app.fedilab.android.activities.MainActivity;
import app.fedilab.android.mastodon.helper.Helper;
import es.dmoral.toasty.Toasty;
public class FragmentThemingSettings extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener {
boolean prefChanged = false;
@Override
public void onCreatePreferences(Bundle bundle, String s) {
createPref();
@ -55,22 +65,45 @@ public class FragmentThemingSettings extends PreferenceFragmentCompat implements
if (getPreferenceScreen() != null && getPreferenceScreen().getSharedPreferences() != null) {
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
if (prefChanged) {
Helper.recreateMainActivity(requireActivity());
prefChanged = false;
}
}
}
@Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity());
SharedPreferences.Editor editor = sharedpreferences.edit();
prefChanged = true;
if (key.compareTo(getString(R.string.SET_THEME_BASE)) == 0) {
ListPreference SET_THEME_BASE = findPreference(getString(R.string.SET_THEME_BASE));
if (SET_THEME_BASE != null) {
requireActivity().finish();
startActivity(requireActivity().getIntent());
}
Helper.recreateMainActivity(requireActivity());
} else if (key.compareTo(getString(R.string.SET_CUSTOM_ACCENT)) == 0) {
SwitchPreferenceCompat SET_CUSTOM_ACCENT = findPreference(getString(R.string.SET_CUSTOM_ACCENT));
if (SET_CUSTOM_ACCENT != null) {
editor.putBoolean(getString(R.string.SET_CUSTOM_ACCENT) + MainActivity.currentUserID + MainActivity.currentInstance, SET_CUSTOM_ACCENT.isChecked());
}
} else if (key.compareTo(getString(R.string.SET_CUSTOM_ACCENT_LIGHT_VALUE)) == 0) {
ColorPreferenceCompat SET_CUSTOM_ACCENT_VALUE = findPreference(getString(R.string.SET_CUSTOM_ACCENT_LIGHT_VALUE));
if (SET_CUSTOM_ACCENT_VALUE != null) {
editor.putInt(getString(R.string.SET_CUSTOM_ACCENT_LIGHT_VALUE) + MainActivity.currentUserID + MainActivity.currentInstance, SET_CUSTOM_ACCENT_VALUE.getColor());
}
} else if (key.compareTo(getString(R.string.SET_CUSTOM_ACCENT_DARK_VALUE)) == 0) {
ColorPreferenceCompat SET_CUSTOM_ACCENT_VALUE = findPreference(getString(R.string.SET_CUSTOM_ACCENT_DARK_VALUE));
if (SET_CUSTOM_ACCENT_VALUE != null) {
editor.putInt(getString(R.string.SET_CUSTOM_ACCENT_DARK_VALUE) + MainActivity.currentUserID + MainActivity.currentInstance, SET_CUSTOM_ACCENT_VALUE.getColor());
}
}
//TODO: check if can be removed
Helper.recreateMainActivity(requireActivity());
Log.v(Helper.TAG, "currentUserID: " + currentUserID);
Log.v(Helper.TAG, "currentInstance: " + currentInstance);
editor.apply();
}
@ -83,6 +116,24 @@ public class FragmentThemingSettings extends PreferenceFragmentCompat implements
Toasty.error(requireActivity(), getString(R.string.toast_error), Toasty.LENGTH_SHORT).show();
}
SwitchPreferenceCompat SET_DYNAMIC_COLOR = findPreference(getString(R.string.SET_DYNAMICCOLOR));
SwitchPreferenceCompat SET_CUSTOM_ACCENT = findPreference(getString(R.string.SET_CUSTOM_ACCENT));
ColorPreferenceCompat SET_CUSTOM_ACCENT_DARK_VALUE = findPreference(getString(R.string.SET_CUSTOM_ACCENT_DARK_VALUE));
ColorPreferenceCompat SET_CUSTOM_ACCENT_LIGHT_VALUE = findPreference(getString(R.string.SET_CUSTOM_ACCENT_LIGHT_VALUE));
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
if (SET_DYNAMIC_COLOR != null) {
getPreferenceScreen().removePreference(SET_DYNAMIC_COLOR);
}
if (SET_CUSTOM_ACCENT != null) {
getPreferenceScreen().removePreference(SET_CUSTOM_ACCENT);
}
if (SET_CUSTOM_ACCENT_DARK_VALUE != null) {
getPreferenceScreen().removePreference(SET_CUSTOM_ACCENT_DARK_VALUE);
}
if (SET_CUSTOM_ACCENT_LIGHT_VALUE != null) {
getPreferenceScreen().removePreference(SET_CUSTOM_ACCENT_LIGHT_VALUE);
}
}
Preference SET_CUSTOMIZE_LIGHT_COLORS_ACTION = findPreference(getString(R.string.SET_CUSTOMIZE_LIGHT_COLORS_ACTION));
if (SET_CUSTOMIZE_LIGHT_COLORS_ACTION != null) {

@ -3,4 +3,10 @@
<string name="auto">Auto</string>
<string name="set_custom_accent">Custom accent color</string>
<string name="set_custom_accent_indication">Define a theme color per account</string>
<string name="set_custom_accent_light_value">Light accent color</string>
<string name="set_custom_accent_dark_value">Dark accent color</string>
<string name="set_custom_accent_value_light_description">Color that will be applied to the light theme</string>
<string name="set_custom_accent_value_dark_description">Color that will be applied to the dark theme</string>
</resources>

@ -1037,6 +1037,10 @@
<string name="SET_THEME_BASE" translatable="false">SET_THEME_BASE</string>
<string name="SET_DYNAMICCOLOR" translatable="false">SET_DYNAMICCOLOR</string>
<string name="SET_CUSTOM_ACCENT" translatable="false">SET_CUSTOM_ACCENT</string>
<string name="SET_CUSTOM_ACCENT_LIGHT_VALUE" translatable="false">SET_CUSTOM_ACCENT_LIGHT_VALUE</string>
<string name="SET_CUSTOM_ACCENT_DARK_VALUE" translatable="false">SET_CUSTOM_ACCENT_DARK_VALUE</string>
<string name="SET_CARDVIEW" translatable="false">SET_CARDVIEW</string>
<string name="SET_CUSTOMIZE_LIGHT_COLORS" translatable="false">SET_CUSTOMIZE_LIGHT_COLORS</string>
<string name="SET_CHAT_FOR_CONVERSATION" translatable="false">SET_CHAT_FOR_CONVERSATION</string>

@ -24,6 +24,28 @@
app:summary="@string/set_dynamic_color_indication"
app:title="@string/set_dynamic_color" />
<SwitchPreferenceCompat
app:defaultValue="false"
app:iconSpaceReserved="false"
app:key="@string/SET_CUSTOM_ACCENT"
app:singleLineTitle="false"
app:summary="@string/set_custom_accent_indication"
app:title="@string/set_custom_accent" />
<com.jaredrummler.android.colorpicker.ColorPreferenceCompat
android:dependency="@string/SET_CUSTOM_ACCENT"
android:key="@string/SET_CUSTOM_ACCENT_LIGHT_VALUE"
app:iconSpaceReserved="false"
app:summary="@string/set_custom_accent_value_light_description"
app:title="@string/set_custom_accent_light_value" />
<com.jaredrummler.android.colorpicker.ColorPreferenceCompat
android:dependency="@string/SET_CUSTOM_ACCENT"
android:key="@string/SET_CUSTOM_ACCENT_DARK_VALUE"
app:iconSpaceReserved="false"
app:summary="@string/set_custom_accent_value_dark_description"
app:title="@string/set_custom_accent_dark_value" />
<ListPreference
app:defaultValue="LIGHT"
app:dialogTitle="@string/type_default_theme_light"

@ -0,0 +1 @@
/build

@ -0,0 +1,39 @@
apply plugin: 'com.android.library'
android {
compileSdkVersion 33
defaultConfig {
minSdkVersion 15
targetSdkVersion 33
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
// Add a new configuration to hold your dependencies
configurations {
libConfig
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
//noinspection GradleCompatible
implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'androidx.preference:preference:1.2.0'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test:runner:1.5.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
}

@ -0,0 +1,19 @@
VERSION_NAME=1.1.0
VERSION_CODE=110
GROUP=com.jaredrummler
ARTIFACT_ID=colorpicker
POM_NAME=colorpicker
POM_ARTIFACT_ID=colorpicker
POM_PACKAGING=aar
POM_DESCRIPTION=A simply good looking color picker component for Android
POM_URL=https://github.com/jaredrummler/ColorPicker
POM_SCM_URL=https://github.com/jaredrummler/ColorPicker
POM_SCM_CONNECTION=scm:git@github.com:jaredrummler/ColorPicker.git
POM_SCM_DEV_CONNECTION=scm:git@github.com:jaredrummler/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=jaredrummler
POM_DEVELOPER_NAME=Jared Rummler
SNAPSHOT_REPOSITORY_URL=https://oss.sonatype.org/content/repositories/snapshots
RELEASE_REPOSITORY_URL=https://oss.sonatype.org/service/local/staging/deploy/maven2

@ -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 *;
#}

@ -0,0 +1 @@
<manifest package="com.jaredrummler.android.colorpicker" />

@ -0,0 +1,113 @@
/*
* Copyright (C) 2017 Jared Rummler
*
* 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.jaredrummler.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 final Paint paint = new Paint();
private final Paint paintWhite = new Paint();
private final Paint paintGray = new Paint();
private int rectangleSize = 10;
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;
}
}
}

@ -0,0 +1,150 @@
/*
* Copyright (C) 2017 Jared Rummler
*
* 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.jaredrummler.android.colorpicker;
import android.content.Context;
import android.graphics.Color;
import android.graphics.PorterDuff;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import androidx.core.graphics.ColorUtils;
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 = view.findViewById(R.id.cpv_color_panel_view);
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);
}
}
}
}

@ -0,0 +1,316 @@
/*
* Copyright (C) 2017 Jared Rummler
*
* 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.jaredrummler.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.util.AttributeSet;
import android.util.TypedValue;
import android.view.Gravity;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.ColorInt;
import androidx.core.view.GravityCompat;
import androidx.core.view.ViewCompat;
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));
}
/**
* Get the color currently show by this view.
*
* @return the color value
*/
public int getColor() {
return color;
}
/**
* Set the color that should be shown by this view.
*
* @param color the color value
*/
public void setColor(int color) {
this.color = color;
invalidate();
}
/**
* 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);
}
}
/**
* @return the color of the border surrounding the panel.
*/
public int getBorderColor() {
return borderColor;
}
/**
* Set the color of the border surrounding the panel.
*
* @param color the color value
*/
public void setBorderColor(int color) {
borderColor = color;
invalidate();
}
/**
* Get the shape
*
* @return Either {@link ColorShape#SQUARE} or {@link ColorShape#CIRCLE}.
*/
@ColorShape
public int getShape() {
return shape;
}
/**
* Set the shape.
*
* @param shape Either {@link ColorShape#SQUARE} or {@link ColorShape#CIRCLE}.
*/
public void setShape(@ColorShape int shape) {
this.shape = shape;
invalidate();
}
/**
* 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();
}
}

@ -0,0 +1,968 @@
/*
* Copyright (C) 2017 Jared Rummler
*
* 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.jaredrummler.android.colorpicker;
import android.app.Activity;
import android.app.Dialog;
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.text.Editable;
import android.text.InputFilter;
import android.text.TextWatcher;
import android.util.Log;
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 androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AlertDialog;
import androidx.core.graphics.ColorUtils;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import java.util.Arrays;
import java.util.Locale;
/**
* <p>A dialog to pick a color.</p>
*
* <p>The {@link Activity activity} that shows this dialog should implement {@link ColorPickerDialogListener}</p>
*
* <p>Example usage:</p>
*
* <pre>
* ColorPickerDialog.newBuilder().show(activity);
* </pre>
*/
public class ColorPickerDialog extends DialogFragment implements ColorPickerView.OnColorChangedListener, TextWatcher {
public static final int TYPE_CUSTOM = 0;
public static final int TYPE_PRESETS = 1;
/**
* 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
};
static final int ALPHA_THRESHOLD = 165;
private static final String TAG = "ColorPickerDialog";
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";
private static final String ARG_PRESETS_BUTTON_TEXT = "presetsButtonText";
private static final String ARG_CUSTOM_BUTTON_TEXT = "customButtonText";
private static final String ARG_SELECTED_BUTTON_TEXT = "selectedButtonText";
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;
private final OnTouchListener onPickerTouchListener = new OnTouchListener() {
@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;
}
};
boolean showAlphaSlider;
private int presetsButtonStringRes;
private boolean fromEditText;
private int customButtonStringRes;
/**
* 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();
}
@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(requireActivity());
if (dialogType == TYPE_CUSTOM) {
rootView.addView(createPickerView());
} else if (dialogType == TYPE_PRESETS) {
rootView.addView(createPresetsView());
}
int selectedButtonStringRes = getArguments().getInt(ARG_SELECTED_BUTTON_TEXT);
if (selectedButtonStringRes == 0) {
selectedButtonStringRes = R.string.cpv_select;
}
AlertDialog.Builder builder = new AlertDialog.Builder(requireActivity()).setView(rootView)
.setPositiveButton(selectedButtonStringRes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
onColorSelected(color);
}
});
int dialogTitleStringRes = getArguments().getInt(ARG_DIALOG_TITLE);
if (dialogTitleStringRes != 0) {
builder.setTitle(dialogTitleStringRes);
}
presetsButtonStringRes = getArguments().getInt(ARG_PRESETS_BUTTON_TEXT);
customButtonStringRes = getArguments().getInt(ARG_CUSTOM_BUTTON_TEXT);
int neutralButtonStringRes;
if (dialogType == TYPE_CUSTOM && getArguments().getBoolean(ARG_ALLOW_PRESETS)) {
neutralButtonStringRes = (presetsButtonStringRes != 0 ? presetsButtonStringRes : R.string.cpv_presets);
} else if (dialogType == TYPE_PRESETS && getArguments().getBoolean(ARG_ALLOW_CUSTOM)) {
neutralButtonStringRes = (customButtonStringRes != 0 ? customButtonStringRes : 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(customButtonStringRes != 0 ? customButtonStringRes : R.string.cpv_custom);
rootView.addView(createPresetsView());
break;
case TYPE_PRESETS:
dialogType = TYPE_CUSTOM;
((Button) v).setText(presetsButtonStringRes != 0 ? presetsButtonStringRes : R.string.cpv_presets);
rootView.addView(createPickerView());
}
}
});
}
}
@Override
public void onDismiss(DialogInterface dialog) {
super.onDismiss(dialog);
onDialogDismissed();
}
@Override
public void onSaveInstanceState(Bundle outState) {
outState.putInt(ARG_COLOR, color);
outState.putInt(ARG_TYPE, dialogType);
super.onSaveInstanceState(outState);
}
/**
* Set the callback.
* <p/>
* Note: The preferred way to handle the callback is to have the calling Activity implement
* {@link ColorPickerDialogListener} as this will not survive an orientation change.
*
* @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 = contentView.findViewById(R.id.cpv_color_picker_view);
ColorPanelView oldColorPanel = contentView.findViewById(R.id.cpv_color_panel_old);
newColorPanel = contentView.findViewById(R.id.cpv_color_panel_new);
ImageView arrowRight = contentView.findViewById(R.id.cpv_arrow_right);
hexEditText = 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) {
onColorSelected(color);
dismiss();
}
}
});
contentView.setOnTouchListener(onPickerTouchListener);
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 void onColorChanged(int newColor) {
color = newColor;
if (newColorPanel != null) {
newColorPanel.setColor(newColor);
}
if (!fromEditText && hexEditText != null) {
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 = contentView.findViewById(R.id.shades_layout);
transparencySeekBar = contentView.findViewById(R.id.transparency_seekbar);
transparencyPercText = contentView.findViewById(R.id.transparency_text);
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) {
// Double tab selects the color
ColorPickerDialog.this.onColorSelected(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);
int initialColor = getArguments().getInt(ARG_COLOR);
if (initialColor != color) {
// The user clicked a color and a configuration change occurred. Make sure the initial color is in the presets
presets = unshiftIfNotExists(presets, initialColor);
}
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 = layout.findViewById(R.id.cpv_color_panel_view);
ImageView iv = 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 = 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()) {
onColorSelected(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 = layout.findViewById(R.id.cpv_color_panel_view);
ImageView iv = 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 void onColorSelected(int color) {
if (colorPickerDialogListener != null) {
Log.w(TAG, "Using deprecated listener which may be remove in future releases");
colorPickerDialogListener.onColorSelected(dialogId, color);
return;
}
Activity activity = getActivity();
if (activity instanceof ColorPickerDialogListener) {
((ColorPickerDialogListener) activity).onColorSelected(dialogId, color);
} else {
throw new IllegalStateException("The activity must implement ColorPickerDialogListener");
}
}
private void onDialogDismissed() {
if (colorPickerDialogListener != null) {
Log.w(TAG, "Using deprecated listener which may be remove in future releases");
colorPickerDialogListener.onDialogDismissed(dialogId);
return;
}
Activity activity = getActivity();
if (activity instanceof ColorPickerDialogListener) {
((ColorPickerDialogListener) activity).onDialogDismissed(dialogId);
}
}
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 = layout.findViewById(R.id.cpv_color_panel_view);
ImageView iv = 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
@IntDef({TYPE_CUSTOM, TYPE_PRESETS})
public @interface DialogType {
}
public static final class Builder {
ColorPickerDialogListener colorPickerDialogListener;
@StringRes
int dialogTitle = R.string.cpv_default_title;
@StringRes
int presetsButtonText = R.string.cpv_presets;
@StringRes
int customButtonText = R.string.cpv_custom;
@StringRes
int selectedButtonText = R.string.cpv_select;
@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 the selected button text string resource id
*
* @param selectedButtonText The string resource used for the selected button text
* @return This builder object for chaining method calls
*/
public Builder setSelectedButtonText(@StringRes int selectedButtonText) {
this.selectedButtonText = selectedButtonText;
return this;
}
/**
* Set the presets button text string resource id
*
* @param presetsButtonText The string resource used for the presets button text
* @return This builder object for chaining method calls
*/
public Builder setPresetsButtonText(@StringRes int presetsButtonText) {
this.presetsButtonText = presetsButtonText;
return this;
}
/**
* Set the custom button text string resource id
*
* @param customButtonText The string resource used for the custom button text
* @return This builder object for chaining method calls
*/
public Builder setCustomButtonText(@StringRes int customButtonText) {
this.customButtonText = customButtonText;
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(FragmentActivity)
*/
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);
args.putInt(ARG_PRESETS_BUTTON_TEXT, presetsButtonText);
args.putInt(ARG_CUSTOM_BUTTON_TEXT, customButtonText);
args.putInt(ARG_SELECTED_BUTTON_TEXT, selectedButtonText);
dialog.setArguments(args);
return dialog;
}
/**
* Create and show the {@link ColorPickerDialog} created with this builder.
*
* @param activity The current activity.
*/
public void show(FragmentActivity activity) {
create().show(activity.getSupportFragmentManager(), "color-picker-dialog");
}
}
// endregion
}

@ -0,0 +1,40 @@
/*
* Copyright (C) 2017 Jared Rummler
*
* 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.jaredrummler.android.colorpicker;
import androidx.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);
}

@ -0,0 +1,972 @@
/*
* Copyright (C) 2017 Jared Rummler
*
* 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.jaredrummler.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) {
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();
}
}
/**
* 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 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 the color of the border surrounding all panels.
*/
public int getBorderColor() {
return borderColor;
}
/**
* Set the color of the border surrounding all panels.
*
* @param color a color value
*/
public void setBorderColor(int color) {
borderColor = color;
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;
}
/**
* 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();
}
public interface OnColorChangedListener {
void onColorChanged(int newColor);
}
private class BitmapCache {
public Canvas canvas;
public Bitmap bitmap;
public float value;
}
}

@ -0,0 +1,239 @@
/*
* Copyright (C) 2017 Jared Rummler
*
* 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.jaredrummler.android.colorpicker;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.preference.Preference;
import android.util.AttributeSet;
import android.view.View;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
/**
* 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;
@ColorPickerDialog.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(this);
FragmentActivity activity = (FragmentActivity) getContext();
activity.getSupportFragmentManager()
.beginTransaction()
.add(dialog, getFragmentTag())
.commitAllowingStateLoss();
}
}
@Override
protected void onAttachedToActivity() {
super.onAttachedToActivity();
if (showDialog) {
FragmentActivity activity = (FragmentActivity) getContext();
ColorPickerDialog fragment =
(ColorPickerDialog) activity.getSupportFragmentManager().findFragmentByTag(getFragmentTag());
if (fragment != null) {
// re-bind preference to fragment
fragment.setColorPickerDialogListener(this);
}
}
}
@Override
protected void onBindView(View view) {
super.onBindView(view);
ColorPanelView preview = 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);
}
/**
* Get the color of the key. This should be one of the entries in {@link #getPresets()}.
*
* @return The color of the key
*/
public int getColor() {
return color;
}
/**
* Sets the color selected. This should be one of the entries in {@link #getPresets()}.
*
* @param color The color to set for the key
*/
public void setColor(@ColorInt int color) {
this.color = color;
}
/**
* Get the colors that will be shown in the {@link ColorPickerDialog}.
*
* @return An array of color ints
*/
public int[] getPresets() {
return presets;
}
/**
* Set the colors shown in the {@link ColorPickerDialog}.
*
* @param presets An array of color ints
*/
public void setPresets(@NonNull int[] presets) {
this.presets = 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);
}
}

@ -0,0 +1,234 @@
package com.jaredrummler.android.colorpicker;
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.util.AttributeSet;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentActivity;
import androidx.preference.Preference;
import androidx.preference.PreferenceViewHolder;
/**
* A Preference to select a color
*/
public class ColorPreferenceCompat 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;
@ColorPickerDialog.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 ColorPreferenceCompat(Context context, AttributeSet attrs) {
super(context, attrs);
init(attrs);
}
public ColorPreferenceCompat(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(this);
getActivity().getSupportFragmentManager()
.beginTransaction()
.add(dialog, getFragmentTag())
.commitAllowingStateLoss();
}
}
public FragmentActivity getActivity() {
Context context = getContext();
if (context instanceof FragmentActivity) {
return (FragmentActivity) context;
} else if (context instanceof ContextWrapper) {
Context baseContext = ((ContextWrapper) context).getBaseContext();
if (baseContext instanceof FragmentActivity) {
return (FragmentActivity) baseContext;
}
}
throw new IllegalStateException("Error getting activity from context");
}
@Override
public void onAttached() {
super.onAttached();
if (showDialog) {
ColorPickerDialog fragment =
(ColorPickerDialog) getActivity().getSupportFragmentManager().findFragmentByTag(getFragmentTag());
if (fragment != null) {
// re-bind preference to fragment
fragment.setColorPickerDialogListener(this);
}
}
}
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
ColorPanelView preview = holder.itemView.findViewById(R.id.cpv_preference_preview_color_panel);
if (preview != null) {
preview.setColor(color);
}
}
@Override
protected void onSetInitialValue(Object defaultValue) {
super.onSetInitialValue(defaultValue);
if (defaultValue instanceof Integer) {
color = (Integer) defaultValue;
persistInt(color);
} else {
color = getPersistedInt(0xFF000000);
}
}
@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);
}
/**
* Get the colors that will be shown in the {@link ColorPickerDialog}.
*
* @return An array of color ints
*/
public int[] getPresets() {
return presets;
}
/**
* Set the colors shown in the {@link ColorPickerDialog}.
*
* @param presets An array of color ints
*/
public void setPresets(@NonNull int[] presets) {
this.presets = 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;
}
/**
* Get the color of the key. This should be one of the entries in {@link #getPresets()}.
*
* @return The color of the key
*/
public int getColor() {
return color;
}
/**
* Sets the color selected. This should be one of the entries in {@link #getPresets()}.
*
* @param color The color to set for the key
*/
public void setColor(@ColorInt int color) {
this.color = color;
}
/**
* 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);
}
}

@ -0,0 +1,30 @@
/*
* Copyright (C) 2017 Jared Rummler
*
* 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.jaredrummler.android.colorpicker;
import androidx.annotation.IntDef;
/**
* The shape of the color preview
*/
@IntDef({ColorShape.SQUARE, ColorShape.CIRCLE})
public @interface ColorShape {
int SQUARE = 0;
int CIRCLE = 1;
}

@ -0,0 +1,32 @@
/*
* Copyright (C) 2017 Jared Rummler
*
* 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.jaredrummler.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;
}
}

@ -0,0 +1,45 @@
/*
* Copyright (C) 2017 Jared Rummler
*
* 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.jaredrummler.android.colorpicker;
import android.content.Context;
import android.util.AttributeSet;
import android.widget.GridView;
import androidx.annotation.RestrictTo;
@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);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 81 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 101 B

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/cpv_btn_background_pressed" android:state_focused="true" android:state_pressed="false" />
<item android:drawable="@drawable/cpv_btn_background_pressed" android:state_focused="true" android:state_pressed="true" />
<item android:drawable="@drawable/cpv_btn_background_pressed" android:state_focused="false" android:state_pressed="true" />
</selector>

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:alpha="0.1"
android:bottom="6dp"
android:top="6dp">
<shape>
<solid android:color="#20666666" />
<corners android:radius="3dp" />
</shape>
</item>
</layer-list>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M22,12l-4,-4v3H3v2h15v3z" />
</vector>

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z" />
</vector>

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false">
<com.jaredrummler.android.colorpicker.ColorPanelView
android:id="@+id/cpv_color_panel_view"
android:layout_width="@dimen/cpv_item_size"
android:layout_height="@dimen/cpv_item_size"
android:layout_gravity="center"
android:clickable="true"
app:cpv_colorShape="circle" />
<ImageView
android:id="@+id/cpv_color_image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:clickable="false" />
</FrameLayout>

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clickable="false">
<com.jaredrummler.android.colorpicker.ColorPanelView
android:id="@+id/cpv_color_panel_view"
android:layout_width="@dimen/cpv_item_size"
android:layout_height="@dimen/cpv_item_size"
android:layout_gravity="center"
android:clickable="true"
app:cpv_colorShape="square" />
<ImageView
android:id="@+id/cpv_color_image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:clickable="false" />
</FrameLayout>

@ -0,0 +1,86 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.jaredrummler.android.colorpicker.ColorPickerView
android:id="@id/cpv_color_picker_view"
style="@style/cpv_ColorPickerViewStyle"
android:padding="16dp" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="16dp">
<com.jaredrummler.android.colorpicker.ColorPanelView
android:id="@id/cpv_color_panel_old"
android:layout_width="@dimen/cpv_dialog_preview_width"
android:layout_height="@dimen/cpv_dialog_preview_height"
app:cpv_colorShape="square" />
<ImageView
android:id="@+id/cpv_arrow_right"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:src="@drawable/cpv_ic_arrow_right_black_24dp"
tools:ignore="ContentDescription" />
<com.jaredrummler.android.colorpicker.ColorPanelView
android:id="@id/cpv_color_panel_new"
android:layout_width="@dimen/cpv_dialog_preview_width"
android:layout_height="@dimen/cpv_dialog_preview_height"
app:cpv_colorShape="square" />
<Space
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:descendantFocusability="beforeDescendants"
android:focusableInTouchMode="true"
android:gravity="right"
android:orientation="horizontal"
tools:ignore="RtlHardcoded">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#"
android:typeface="monospace"
tools:ignore="HardcodedText" />
<EditText
android:id="@+id/cpv_hex"
android:layout_width="110dp"
android:layout_height="wrap_content"
android:digits="0123456789ABCDEFabcdef"
android:focusable="true"
android:imeOptions="actionGo"
android:inputType="textNoSuggestions"
android:maxLength="8"
android:maxLines="1"
android:typeface="monospace" />
</LinearLayout>
</LinearLayout>
</LinearLayout>
</ScrollView>

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<com.jaredrummler.android.colorpicker.NestedGridView
android:id="@+id/gridView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:cacheColorHint="@android:color/transparent"
android:clickable="false"
android:columnWidth="@dimen/cpv_column_width"
android:horizontalSpacing="4dp"
android:listSelector="@android:color/transparent"
android:numColumns="auto_fit"
android:stretchMode="spacingWidthUniform"
android:verticalSpacing="8dp" />
<View
android:id="@+id/shades_divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="12dp"
android:layout_marginBottom="12dp"
android:background="?android:attr/dividerVertical" />
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:overScrollMode="never"
android:scrollbars="none">
<LinearLayout
android:id="@+id/shades_layout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:orientation="horizontal">
<!-- views added dynamically -->
</LinearLayout>
</HorizontalScrollView>
<TextView
android:id="@+id/transparency_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="12dp"
android:text="@string/cpv_transparency" />
<LinearLayout
android:id="@+id/transparency_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="100">
<SeekBar
android:id="@+id/transparency_seekbar"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginRight="4dp"
android:layout_weight="85" />
<TextView
android:id="@+id/transparency_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight="15"
android:textStyle="bold"
android:typeface="monospace"
tools:text="100%" />
</LinearLayout>
</LinearLayout>
</ScrollView>

@ -0,0 +1,6 @@
<com.jaredrummler.android.colorpicker.ColorPanelView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@id/cpv_preference_preview_color_panel"
android:layout_width="@dimen/cpv_color_preference_normal"
android:layout_height="@dimen/cpv_color_preference_normal"
app:cpv_colorShape="circle" />

@ -0,0 +1,6 @@
<com.jaredrummler.android.colorpicker.ColorPanelView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@id/cpv_preference_preview_color_panel"
android:layout_width="@dimen/cpv_color_preference_large"
android:layout_height="@dimen/cpv_color_preference_large"
app:cpv_colorShape="circle" />

@ -0,0 +1,6 @@
<com.jaredrummler.android.colorpicker.ColorPanelView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@id/cpv_preference_preview_color_panel"
android:layout_width="@dimen/cpv_color_preference_normal"
android:layout_height="@dimen/cpv_color_preference_normal"
app:cpv_colorShape="square" />

@ -0,0 +1,6 @@
<com.jaredrummler.android.colorpicker.ColorPanelView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@id/cpv_preference_preview_color_panel"
android:layout_width="@dimen/cpv_color_preference_large"
android:layout_height="@dimen/cpv_color_preference_large"
app:cpv_colorShape="square" />

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">اختر لونًا</string>
<string name="cpv_presets">ألوان جاهزة</string>
<string name="cpv_custom">ألوان معدلة</string>
<string name="cpv_select">اختر</string>
<string name="cpv_transparency">الشفافية</string>
</resources>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">Вылучыце колер</string>
<string name="cpv_presets">Перадусталёўкі</string>
<string name="cpv_custom">Асаблівы</string>
<string name="cpv_select">Выбраць</string>
<string name="cpv_transparency">Празрыстасць</string>
</resources>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">Vyberte barvu</string>
<string name="cpv_presets">Přednastavené</string>
<string name="cpv_custom">Vlastní</string>
<string name="cpv_select">Vybrat</string>
<string name="cpv_transparency">Průhlednost</string>
</resources>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">Farbe auswählen</string>
<string name="cpv_presets">Voreinstellungen</string>
<string name="cpv_custom">Benutzerdefiniert</string>
<string name="cpv_select">Auswählen</string>
<string name="cpv_transparency">Transparenz</string>
</resources>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">Selecciona un color</string>
<string name="cpv_presets">Predeterminados</string>
<string name="cpv_custom">Personalizar</string>
<string name="cpv_select">Seleccionar</string>
<string name="cpv_transparency">Transparencia</string>
</resources>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">Sélectionner une couleur</string>
<string name="cpv_presets">Présélections</string>
<string name="cpv_custom">Personnalisée</string>
<string name="cpv_select">Valider</string>
<string name="cpv_transparency">Transparence</string>
</resources>

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string name="cpv_custom">Personalizzato</string>
<string name="cpv_default_title">Seleziona un Colore</string>
<string name="cpv_presets">Predefiniti</string>
<string name="cpv_select">Seleziona</string>
<string name="cpv_transparency">Trasparenza</string>
</resources>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="cpv_default_title">בחירת צבע</string>
<string name="cpv_presets">קבוע מראש</string>
<string name="cpv_custom">מותאם אישית</string>
<string name="cpv_select">בחירה</string>
<string name="cpv_transparency">שקיפות</string>
</resources>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">色を選択</string>
<string name="cpv_presets">プリセット</string>
<string name="cpv_custom">カスタム</string>
<string name="cpv_select">選択</string>
<string name="cpv_transparency">透明度</string>
</resources>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">Selecteer een kleur</string>
<string name="cpv_presets">Vooraf ingesteld</string>
<string name="cpv_custom">Aangepast</string>
<string name="cpv_select">Kiezen</string>
<string name="cpv_transparency">Transparantie</string>
</resources>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">Wybierz kolor</string>
<string name="cpv_presets">Kolory domyślne</string>
<string name="cpv_custom">Dostosuj</string>
<string name="cpv_select">Wybierz</string>
<string name="cpv_transparency">Przezroczystość</string>
</resources>

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">Selecionar uma cor</string>
<string name="cpv_presets">Predefinidos</string>
<string name="cpv_custom">Personalizar</string>
<string name="cpv_select">Selecionar</string>
<string name="cpv_transparency">Transparência</string>
</resources>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">Выберите цвет</string>
<string name="cpv_presets">Предустановки</string>
<string name="cpv_custom">Особый</string>
<string name="cpv_select">Выбрать</string>
<string name="cpv_transparency">Прозрачность</string>
</resources>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">Vyberte farbu</string>
<string name="cpv_presets">Prednastavené</string>
<string name="cpv_custom">Vlastné</string>
<string name="cpv_select">Vybrať</string>
<string name="cpv_transparency">Priehľadnosť</string>
</resources>

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">Renk Seçin</string>
<string name="cpv_presets">Ön Ayarlar</string>
<string name="cpv_custom">Özel</string>
<string name="cpv_select">Seç</string>
<string name="cpv_transparency">Şeffaflık</string>
</resources>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">选择颜色</string>
<string name="cpv_presets">预置颜色</string>
<string name="cpv_custom">自定义颜色</string>
<string name="cpv_select">确认</string>
<string name="cpv_transparency">透明度</string>
</resources>

@ -0,0 +1,38 @@
<resources xmlns:tools="http://schemas.android.com/tools" tools:ignore="ResourceName">
<declare-styleable name="ColorPanelView">
<attr name="cpv_showOldColor" format="boolean" />
<attr name="cpv_colorShape" format="enum">
<enum name="square" value="0" />
<enum name="circle" value="1" />
</attr>
<attr name="cpv_borderColor" format="color|reference" />
</declare-styleable>
<declare-styleable name="ColorPickerView" parent="ColorPanelView">
<attr name="cpv_alphaChannelVisible" format="boolean|reference" />
<attr name="cpv_alphaChannelText" format="string|reference" />
<attr name="cpv_sliderColor" format="color|reference" />
<attr name="cpv_borderColor" />
</declare-styleable>
<declare-styleable name="ColorPreference" parent="ColorPickerDialog">
<attr name="cpv_showAlphaSlider" format="boolean|reference" />
<attr name="cpv_previewSize" format="enum">
<enum name="regular" value="0" />
<enum name="large" value="1" />
</attr>
<attr name="cpv_colorShape" />
<attr name="cpv_dialogTitle" format="reference" />
<attr name="cpv_colorPresets" format="reference" />
<attr name="cpv_dialogType" format="enum">
<enum name="custom" value="0" />
<enum name="preset" value="1" />
</attr>
<attr name="cpv_showColorShades" format="boolean|reference" />
<attr name="cpv_allowPresets" format="boolean|reference" />
<attr name="cpv_allowCustom" format="boolean|reference" />
<attr name="cpv_showDialog" format="boolean|reference" />
</declare-styleable>
</resources>

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<dimen name="cpv_required_padding">6dp</dimen>
<dimen name="cpv_dialog_preview_width">66dp</dimen>
<dimen name="cpv_dialog_preview_height">34dp</dimen>
<dimen name="cpv_column_width">58dp</dimen>
<dimen name="cpv_item_size">50dp</dimen>
<dimen name="cpv_item_horizontal_padding">8dp</dimen>
<dimen name="cpv_color_preference_normal">28dp</dimen>
<dimen name="cpv_color_preference_large">40dp</dimen>
</resources>

@ -0,0 +1,8 @@
<resources>
<item name="cpv_color_picker_view" type="id" />
<item name="cpv_color_panel_old" type="id" />
<item name="cpv_color_panel_new" type="id" />
<item name="cpv_preference_preview_color_panel" type="id" />
</resources>

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?><!-- Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Default title for color picker dialog [CHAR LIMIT=30] -->
<string name="cpv_default_title">Select a Color</string>
<string name="cpv_presets">Presets</string>
<string name="cpv_custom">Custom</string>
<string name="cpv_select">Select</string>
<string name="cpv_transparency">Transparency</string>
</resources>

@ -0,0 +1,25 @@
<!--
~ Copyright (C) 2016 Jared Rummler <jared.rummler@gmail.com>
~
~ 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.
~
-->
<resources xmlns:android="http://schemas.android.com/apk/res/android">
<style name="cpv_ColorPickerViewStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">315dp</item>
</style>
</resources>

@ -5,3 +5,4 @@ include ':mytransl'
include ':ratethisapp'
include ':sparkbutton'
include ':mathjaxandroid'
include ':colorPicker'

Loading…
Cancel
Save