/******************************************************************************* * Copyright 2012-present Pixate, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ******************************************************************************/ /** * */ package com.pixate.freestyle.pxcomponentkit.view; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Rect; import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.util.SparseArray; import android.widget.Button; import com.pixate.freestyle.cg.paints.PXLinearGradient; import com.pixate.freestyle.cg.paints.PXPaint; import com.pixate.freestyle.cg.paints.PXSolidPaint; import com.pixate.freestyle.cg.shapes.PXRectangle; import com.pixate.freestyle.cg.shapes.PXShapeDocument; import com.pixate.freestyle.cg.shapes.PXShapeGroup; import com.pixate.freestyle.util.ObjectUtil; import com.pixate.freestyle.util.PXColorUtil; /** * A PX {@link Drawable} for rendering a {@link PXButton}. This drawable can be * assigned as a background {@link Drawable} for a {@link Button}. */ public class PXButtonDrawable extends PXSceneDrawable { private static final int DEFAULT_INSET_X = 2; private static final int DEFAULT_INSET_Y = 2; public static final String CORNER_RADIUS_KEY = "getCornerRadius"; public static final String RADIUS_X_KEY = "radiusX"; public static final String RADIUS_Y_KEY = "radiusY"; private float _cornerRadius; private float _borderWidth; private PXRectangle background; private PXRectangle foreground; private SparseArray<PXPaint> foregroundPaints; private SparseArray<PXPaint> backgroundPaints; /** * Constructs a new PXButtonDrawable. */ public PXButtonDrawable() { // Constructs with a null PXScene. The scene will be created on demand // when drawing. this(0, 0); } /** * Constructs a new PXButtonDrawable. * * @param minHeight * @param minWidth */ public PXButtonDrawable(int minHeight, int minWidth) { // Constructs with a null PXScene. The scene will be created on demand // when drawing. super(null, minHeight, minWidth); this._cornerRadius = 8; this._borderWidth = 1; } @Override public void draw(Canvas canvas) { loadScene(); // update background Rect clipBounds = canvas.getClipBounds(); background.setFillColor(getColorByState(backgroundPaints, getState())); background.setBounds(new RectF(clipBounds)); background.setCornerRadius(_cornerRadius); // update foreground RectF forgroundBounds = new RectF(clipBounds); forgroundBounds.inset(DEFAULT_INSET_X, DEFAULT_INSET_Y); foreground.setFillColor(getColorByState(foregroundPaints, getState())); foreground.setBounds(forgroundBounds); foreground.setCornerRadius(_cornerRadius - _borderWidth); super.draw(canvas); } private static PXPaint getColorByState(SparseArray<PXPaint> paints, int[] state) { for (int i = state.length - 1; i >= 0; i--) { switch (state[i]) { case android.R.attr.state_enabled: case android.R.attr.state_pressed: case android.R.attr.state_empty: return paints.get(state[i]); } } return null; } /** * Returns <code>true</code> for the {@link PXButtonDrawable}. * * @see android.graphics.drawable.Drawable#isStateful() */ @Override public boolean isStateful() { return true; } /* * (non-Javadoc) * @see com.pixate.freestyle.pxengine.view.BasePXShapeDrawable#onBoundsChange(android * .graphics.Rect) */ @Override protected void onBoundsChange(Rect bounds) { scene = null; super.onBoundsChange(bounds); } @Override protected boolean onStateChange(int[] state) { scene = null; // FIXME - This is a temporary patch to a problem we have when setting // the drawable on a button, and the button does not visually react to // clicks. Calling button.setTextColor seems to effect the behavior, but // for now this will do. invalidateSelf(); return true; } @Override public PXShapeDocument loadScene() { if (scene != null) { return scene; } // build fills if (foregroundPaints == null || backgroundPaints == null) { buildDefaultFills(); } Rect clipBounds = getBounds(); // create background rectangle background = new PXRectangle(new RectF(clipBounds)); // create foreground rectangle Rect forgroundBounds = new Rect(clipBounds); forgroundBounds.inset(DEFAULT_INSET_X, DEFAULT_INSET_Y); foreground = new PXRectangle(new RectF(forgroundBounds)); // build group of rectangles PXShapeGroup group = new PXShapeGroup(); group.addShape(background); group.addShape(foreground); // build scene for group scene = new PXShapeDocument(); scene.setShape(group); return scene; } private void buildDefaultFills() { // *** Foreground Paints // normal/selected PXLinearGradient gradient = new PXLinearGradient(); gradient.addColor(Color.HSVToColor(255, new float[] { 0F, 0F, 1F })); gradient.addColor(Color.HSVToColor(255, new float[] { 0F, 0F, 0.63F })); // TODO // withOffset:1 setForegroundPaint(gradient, android.R.attr.state_enabled); // highlighted gradient = new PXLinearGradient(); gradient.addColor(Color.HSVToColor(255, new float[] { 0F, 0F, 0.421F })); gradient.addColor(Color.HSVToColor(255, new float[] { 0F, 0F, 0.39F })); // TODO // withOffset:1 // TODO - test if this is equivalent to UIControlStateHighlighted setForegroundPaint(gradient, android.R.attr.state_pressed); // disabled gradient = new PXLinearGradient(); gradient.addColor(Color.HSVToColor(255, new float[] { 0F, 0F, 0.5F })); gradient.addColor(Color.HSVToColor(255, new float[] { 0F, 0.63F, 0.5F })); // TODO // withOffset:1 // TODO - test if this can be used as equivalent to // UIControlStateDisabled setForegroundPaint(gradient, android.R.attr.state_empty); // *** Background Paints PXSolidPaint solid = new PXSolidPaint(Color.HSVToColor(255, new float[] { 0, 0, 0.734F })); setBackgroundPaint(solid, android.R.attr.state_enabled); // UIControlStateNormal setBackgroundPaint(solid, android.R.attr.state_pressed); setBackgroundPaint(solid, android.R.attr.state_empty); } public void setBackgroundPaint(PXPaint paint, int state) { if (backgroundPaints == null) { backgroundPaints = new SparseArray<PXPaint>(3); } PXPaint prevPaint = backgroundPaints.get(state); if (!ObjectUtil.areEqual(prevPaint, paint)) { backgroundPaints.put(state, paint); invalidateSelf(); } } public void setForegroundPaint(PXPaint paint, int state) { if (foregroundPaints == null) { foregroundPaints = new SparseArray<PXPaint>(3); } PXPaint prevPaint = foregroundPaints.get(state); if (!ObjectUtil.areEqual(prevPaint, paint)) { foregroundPaints.put(state, paint); invalidateSelf(); } } /** * @return the corner radius */ public float getCornerRadius() { return _cornerRadius; } /** * @param cornerRadius the corner radius to set */ public void setCornerRadius(float cornerRadius) { if (cornerRadius != this._cornerRadius) { this._cornerRadius = cornerRadius; this.scene = null; invalidateSelf(); } } /** * @return the border width */ public float getBorderWidth() { return _borderWidth; } /** * @param border width the border width to set */ public void setBorderWidth(float borderWidth) { if (borderWidth != this._borderWidth) { this._borderWidth = borderWidth; this.scene = null; invalidateSelf(); } } // Utility functions /** * Set tint color on a given {@link PXButton} * * @param button * @param color */ public static void setTintColor(PXButton button, int color) { setTintColor(button, (PXButtonDrawable) button.getBackground(), color); } /** * Set tint color on a given {@link Button}. This method will set an * instance of this {@link PXButtonDrawable} as the {@link Button}s * background drawable. * * @param button A {@link Button}. In case the button does not have a * {@link PXButtonDrawable} as a background, this method will * attach one and theme it. * @param color */ @SuppressWarnings("deprecation") public static void setTintColor(Button button, int color) { Drawable drawable = button.getBackground(); if (drawable instanceof PXButtonDrawable) { setTintColor(button, (PXButtonDrawable) drawable, color); } else { Drawable prevDrawable = button.getBackground(); PXButtonDrawable pxDrawable; if (prevDrawable != null) { pxDrawable = new PXButtonDrawable(prevDrawable.getMinimumHeight(), prevDrawable.getMinimumWidth()); } else { // TODO: API 16 supports getMinimunHeight and getMinimumWidth pxDrawable = new PXButtonDrawable(button.getHeight(), button.getWidth()); } button.setBackgroundDrawable(pxDrawable); setTintColor(button, pxDrawable, color); } } /** * Set tint color on a given {@link Button}, using an instance of this * drawable and a color. * * @param button * @param drawable * @param color */ public static void setTintColor(Button button, PXButtonDrawable drawable, int color) { int alpha = Color.alpha(color); float saturationDelta = .06F; float lightnessDelta = .11F; float[] hsl = new float[3]; PXColorUtil.colorToHsl(color, hsl); int bottomColor = PXColorUtil.hslToColor(alpha, hsl[0], hsl[1] - saturationDelta, hsl[2] - lightnessDelta); int topColor = PXColorUtil.hslToColor(alpha, hsl[0], hsl[1] + saturationDelta, hsl[2] + lightnessDelta); int borderColorTop = PXColorUtil.hslToColor(alpha, hsl[0], hsl[1] - saturationDelta, hsl[2] - lightnessDelta * 1.3F); int borderColorBottom = PXColorUtil.hslToColor(alpha, hsl[0], hsl[1] - saturationDelta, hsl[2] * .5F); // Force a new scene on the next redraw. drawable.resetScene(); // Set the colors for the states. drawable.setBackgroundPaint( PXLinearGradient.gradientFromStartColor(borderColorTop, borderColorBottom), android.R.attr.state_enabled); drawable.setForegroundPaint(PXLinearGradient.gradientFromStartColor(topColor, bottomColor), android.R.attr.state_enabled); // set the button's text color if (hsl[2] < 0.5) { // Cannot use Color.WHITE. Once we use it, the button's clicking // stops working!... button.setTextColor(PXColorUtil.createColorStateList(Color.argb(255, 254, 254, 254))); } else { button.setTextColor(PXColorUtil.createColorStateList(Color.BLACK)); } } }