package me.jfenn.colorpickerdialog.dialogs; import android.content.Context; import android.content.res.ColorStateList; import android.graphics.Color; import android.os.Build; import android.os.Bundle; import android.os.Parcelable; import android.text.Editable; import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import com.google.android.material.tabs.TabLayout; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.view.ContextThemeWrapper; import androidx.appcompat.widget.AppCompatEditText; import androidx.viewpager.widget.ViewPager; import me.jfenn.colorpickerdialog.R; import me.jfenn.colorpickerdialog.adapters.ColorPickerPagerAdapter; import me.jfenn.colorpickerdialog.utils.ArrayUtils; import me.jfenn.colorpickerdialog.utils.ColorUtils; import me.jfenn.colorpickerdialog.utils.DelayedInstantiation; import me.jfenn.colorpickerdialog.views.color.SmoothColorView; import me.jfenn.colorpickerdialog.views.picker.HSVPickerView; import me.jfenn.colorpickerdialog.views.picker.PickerView; import me.jfenn.colorpickerdialog.views.picker.PresetPickerView; import me.jfenn.colorpickerdialog.views.picker.RGBPickerView; public class ColorPickerDialog extends PickerDialog<ColorPickerDialog> { private static final String INST_KEY_ALPHA = "me.jfenn.colorpickerdialog.INST_KEY_ALPHA"; private static final String INST_KEY_PRESETS = "me.jfenn.colorpickerdialog.INST_KEY_PRESETS"; private static final String INST_KEY_PICKERS = "me.jfenn.colorpickerdialog.INST_KEY_PICKERS"; private SmoothColorView colorView; private AppCompatEditText colorHex; private TabLayout tabLayout; private ViewPager slidersPager; private ColorPickerPagerAdapter slidersAdapter; private DelayedInstantiation[] pickers; private boolean isAlphaEnabled = true; private int[] presets = new int[0]; private boolean shouldIgnoreNextHex = false; @Override protected void init() { withPickers(); } @Override public String getTitle() { String title = super.getTitle(); return title != null ? title : getString(R.string.colorPickerDialog_dialogName); } @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); if (savedInstanceState != null) { isAlphaEnabled = savedInstanceState.getBoolean(INST_KEY_ALPHA, isAlphaEnabled); int[] presets = savedInstanceState.getIntArray(INST_KEY_PRESETS); if (presets != null) this.presets = presets; String[] pickerClassNames = savedInstanceState.getStringArray(INST_KEY_PICKERS); if (pickerClassNames != null && pickerClassNames.length > 0) { pickers = new DelayedInstantiation[pickerClassNames.length]; for (int i = 0; i < pickerClassNames.length; i++) { try { Class tClass = Class.forName(pickerClassNames[i]); Constructor constructor = tClass.getConstructor(Context.class); constructor.setAccessible(true); pickers[i] = DelayedInstantiation.from(tClass, Context.class) .withInstantiator(new DelayedInstantiation.ConstructionInstantiator<PickerView>(constructor) { @Nullable @Override public PickerView instantiate(Object... args) { PickerView view = super.instantiate(args); try { Method method = view.getClass().getDeclaredMethod("onRestoreInstanceState", Parcelable.class); method.setAccessible(true); method.invoke(view, new Object[]{null}); } catch (Exception ignored) { // can't _really_ do anything here... rip, I guess } return view; } }); } catch (Exception ignored) { // TODO: exception handling } } } } } @Override public void onSaveInstanceState(@NonNull Bundle outState) { super.onSaveInstanceState(outState); outState.putBoolean(INST_KEY_ALPHA, isAlphaEnabled); outState.putIntArray(INST_KEY_PRESETS, presets); String[] pickerClassNames = new String[pickers.length]; for (int i = 0; i < pickers.length; i++) { pickerClassNames[i] = pickers[i].gettClassName(); } outState.putStringArray(INST_KEY_PICKERS, pickerClassNames); } /** * Specify whether alpha values should be enabled. This parameter * defaults to true. * * @param isAlphaEnabled Whether alpha values are enabled. * @return "This" dialog instance, for method chaining. */ public ColorPickerDialog withAlphaEnabled(boolean isAlphaEnabled) { this.isAlphaEnabled = isAlphaEnabled; return this; } /** * Enables the preset picker view and applies the passed presets. Passing * nothing will enable the picker view with the default preset values. * * @param presets The preset colors to use. * @return "This" dialog instance, for method chaining. */ public ColorPickerDialog withPresets(@ColorInt int... presets) { this.presets = presets; DelayedInstantiation<PresetPickerView> presetPicker = getPicker(PresetPickerView.class); if (presetPicker == null) { pickers = ArrayUtils.push(pickers, DelayedInstantiation.from(PresetPickerView.class, Context.class) .withInstantiator(new DelayedInstantiation.Instantiator<PresetPickerView>() { @Override public PresetPickerView instantiate(Object... args) { return new PresetPickerView((Context) args[0]) .withPresets(ColorPickerDialog.this.presets); } })); } return this; } /** * Add an unidentified picker view to the dialog, if it doesn't already * exist. This class is instantiated by the dialog, to keep the view's * Context consistent with the rest of the styled components. * * If the picker view already exists in the dialog, this will throw an * error. * * @param pickerClass The class of the picker view to add. * @return "This" dialog instance, for method chaining. */ public <T extends PickerView> ColorPickerDialog withPicker(Class<T> pickerClass) { DelayedInstantiation<T> picker = getPicker(pickerClass); if (picker == null) { try { picker = DelayedInstantiation.from(pickerClass, Context.class); } catch (Exception e) { return null; } pickers = ArrayUtils.push(pickers, picker); } else return null; return this; } /** * Determine whether a particular picker view is enabled, and return * it. If not, this will return null. * * @param pickerClass The class of the PickerView. * @return The view, if it is enabled; null if not. */ @Nullable public <T extends PickerView> DelayedInstantiation<T> getPicker(Class<T> pickerClass) { for (DelayedInstantiation picker : pickers) { if (picker.getClass().equals(pickerClass)) return (DelayedInstantiation<T>) picker; } return null; } /** * Set the picker views used by the dialog. If this method is called with * no arguments, the default pickers will be used; an RGB and HSV picker. * * @param pickers The picker views to use. * @return "This" dialog instance, for method chaining. */ public ColorPickerDialog withPickers(Class... pickers) { if (pickers.length == 0) { this.pickers = new DelayedInstantiation[]{ DelayedInstantiation.from(RGBPickerView.class, Context.class), DelayedInstantiation.from(HSVPickerView.class, Context.class) }; } else { this.pickers = new DelayedInstantiation[pickers.length]; for (int i = 0; i < pickers.length; i++) { this.pickers[i] = DelayedInstantiation.from(pickers[i], Context.class); } } return this; } @Nullable @Override public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); View v = inflater.inflate(R.layout.colorpicker_dialog_color_picker, container, false); colorView = v.findViewById(R.id.color); colorHex = v.findViewById(R.id.colorHex); tabLayout = v.findViewById(R.id.tabLayout); slidersPager = v.findViewById(R.id.slidersPager); Context context = new ContextThemeWrapper(getContext(), getTheme()); PickerView[] pickers = new PickerView[this.pickers.length]; for (int i = 0; i < pickers.length; i++) { pickers[i] = (PickerView) this.pickers[i].instantiate(context); if (!pickers[i].hasActivityRequestHandler()) pickers[i].withActivityRequestHandler(this); } slidersAdapter = new ColorPickerPagerAdapter(getContext(), pickers); slidersAdapter.setListener(this); slidersAdapter.setAlphaEnabled(isAlphaEnabled); slidersAdapter.setColor(getColor()); slidersPager.setAdapter(slidersAdapter); slidersPager.addOnPageChangeListener(slidersAdapter); tabLayout.setupWithViewPager(slidersPager); colorHex.addTextChangedListener(new TextWatcher() { @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) { Editable editable = colorHex.getText(); if (editable != null && !shouldIgnoreNextHex) { String str = editable.toString(); if (str.length() == (isAlphaEnabled ? 9 : 7)) { try { slidersAdapter.updateColor(Color.parseColor(str), true); } catch (Exception ignored) { } } } else shouldIgnoreNextHex = false; } }); v.findViewById(R.id.confirm).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { confirm(); } }); v.findViewById(R.id.cancel).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { dismiss(); } }); onColorPicked(null, getColor()); return v; } @Override public void onColorPicked(@Nullable PickerView pickerView, @ColorInt int color) { super.onColorPicked(pickerView, color); colorView.setColor(color, pickerView != null && !pickerView.isTrackingTouch()); shouldIgnoreNextHex = true; colorHex.setText(String.format(isAlphaEnabled ? "#%08X" : "#%06X", isAlphaEnabled ? color : (0xFFFFFF & color))); colorHex.clearFocus(); int textColor = ColorUtils.isColorDark(ColorUtils.withBackground(color, Color.WHITE)) ? Color.WHITE : Color.BLACK; colorHex.setTextColor(textColor); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) colorHex.setBackgroundTintList(ColorStateList.valueOf(textColor)); } }