/*******************************************************************************
* 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.views;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.text.Layout;
import android.text.StaticLayout;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.HapticFeedbackConstants;
import android.view.MotionEvent;
import android.widget.CompoundButton;
import com.pixate.freestyle.R;
/**
* The switch has two states similar to a checkbox or toggle button. But unlike
* the two, you see the other state which is currently not selected. The switch
* flips from the left to the right side, selecting either the right or the left
* side.
*/
public class Switch extends CompoundButton {
// left and right text
private CharSequence textLeft;
private CharSequence textRight;
// the background
private Drawable drawableBackground;
// the switch
private Drawable drawableSwitch;
private Drawable drawableSwitchOff;
// helper for left and right text
private Layout layoutLeft;
private Layout layoutRight;
// min width of the whole switch
private int switchMinWidth;
// actual width and height of the switch
private int width;
private int height;
// the padding left+right inside of the switch
private int innerPadding;
// the space between the text to the left of the switch and the switch
private int switchPadding;
// the colors for the text of the switch
private int textColorChecked;
private int textColorUnChecked;
private OnClickListener onClickListener;
/**
* Construct a new Switch with default styling
*
* @param context The Context that will determine this widget's theming
*/
public Switch(Context context) {
this(context, null);
}
/**
* Construct a new Switch with default styling, overriding specific style
* attributes as requested.
*
* @param context The Context that will determine this widget's theming.
* @param attrs Specification of attributes that should deviate from default
* styling.
*/
public Switch(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.switchStyle);
}
/**
* Construct a new Switch with a default style determined by the given theme
* attribute, overriding specific style attributes as requested.
*
* @param context The Context that will determine this widget's theming.
* @param attrs Specification of attributes that should deviate from the
* default styling.
* @param defStyle An attribute ID within the active theme containing a
* reference to the default style for this widget. e.g.
* android.R.attr.switchStyle.
*/
public Switch(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this.getPaint().setAntiAlias(true);
// load the default values
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.Switch, defStyle, 0);
this.textLeft = a.getText(R.styleable.Switch_textLeft);
this.textRight = a.getText(R.styleable.Switch_textRight);
this.switchMinWidth = a.getDimensionPixelSize(R.styleable.Switch_switchMinWidth, 50);
this.textColorUnChecked = a.getColor(R.styleable.Switch_colorUnChecked, Color.WHITE);
this.textColorChecked = a.getColor(R.styleable.Switch_colorChecked, Color.WHITE);
this.drawableBackground = a.getDrawable(R.styleable.Switch_backgroundDrawable);
this.drawableSwitch = a.getDrawable(R.styleable.Switch_switchDrawable);
this.drawableSwitchOff = a.getDrawable(R.styleable.Switch_switchDrawableOff);
this.switchPadding = a.getDimensionPixelSize(R.styleable.Switch_switchPadding, 32);
this.innerPadding = a.getDimensionPixelSize(R.styleable.Switch_innerPadding, 20);
this.setChecked(a.getBoolean(R.styleable.Switch_isChecked, false));
a.recycle();
// throw an error if the texts have not been set
if (this.textLeft == null || this.textRight == null)
throw new IllegalStateException(
"Either textLeft or textRight is null. Please them via the attributes with the same name in the layout");
}
/**
* Sets the text displayed on the left side.
*
* @param textLeft The left text. Not <code>null</code>
*/
public void setTextLeft(CharSequence textLeft) {
if (textLeft == null)
throw new IllegalArgumentException("The text for the left side must not be null!");
this.textLeft = textLeft;
this.requestLayout();
}
/**
* Returns the text on the left
*
* @return the text on the left. <code>null</code> shouldn't be possible,
* due to the button throwing an error if the texts are
* <code>null</code>
*/
public CharSequence getTextLeft() {
return this.textLeft;
}
/**
* Returns the text on the right.
*
* @return the text on the right. <code>null</code> shouldn't be possible,
* due to the button throwing an error if the texts are
* <code>null</code>
*/
public CharSequence getTextRight() {
return this.textRight;
}
/**
* Sets the text displayed on the right side
*
* @param textRight The right text. Not <code>null</code>
*/
public void setTextRight(CharSequence textRight) {
if (textRight == null)
throw new IllegalArgumentException("The text for the right side must not be null!");
this.textRight = textRight;
this.requestLayout();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// create the helper layouts if necessary
if (this.layoutLeft == null)
this.layoutLeft = this.makeLayout(this.textLeft);
if (this.layoutRight == null)
this.layoutRight = this.makeLayout(this.textRight);
// find the larger text so both halfs of the switch are equally wide
final int maxTextWidth = Math.max(this.layoutLeft.getWidth(), this.layoutRight.getWidth());
// calculate the width for the whole switch
int actualWidth = Math.max(this.switchMinWidth, maxTextWidth * 2 + this.getPaddingLeft()
+ this.getPaddingRight() + this.innerPadding * 4);
// calculate the height of the switch
// TODO if you want to have a padding-top and padding-bottom, add here
final int switchHeight = Math.max(this.drawableBackground.getIntrinsicHeight(),
this.drawableSwitch.getIntrinsicHeight());
this.width = actualWidth;
this.height = switchHeight;
// recalculate the width if there is a text
if (this.getText() != null)
actualWidth += this.makeLayout(this.getText()).getWidth() + this.switchPadding;
// set the dimensions for this view
setMeasuredDimension(actualWidth, switchHeight);
}
@Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
// calculate the left and right values
int right = this.getWidth() - this.getPaddingRight();
int left = right - this.width;
// draw background
this.drawableBackground.setBounds(left, 0, right, this.height);
this.drawableBackground.draw(canvas);
// draw switch
if (this.isChecked()) {
this.drawableSwitch.setBounds(left + this.width / 2, 0, right, this.height);
this.drawableSwitch.draw(canvas);
} else {
this.drawableSwitchOff.setBounds(left, 0, left + (this.width / 2), this.height);
this.drawableSwitchOff.draw(canvas);
}
// save canvas before translation (0x0)
canvas.save();
// draw left text
if (this.isChecked()) {
this.getPaint().setColor(
this.isChecked() ? this.textColorChecked : this.textColorUnChecked);
canvas.translate(left + (this.width / 2 - this.layoutRight.getWidth()) / 2 + this.width
/ 2, (this.height - this.layoutRight.getHeight()) / 2);
this.layoutLeft.draw(canvas);
canvas.restore();
}
// draw right text
if (!this.isChecked()) {
this.getPaint().setColor(
!this.isChecked() ? this.textColorChecked : this.textColorUnChecked);
canvas.translate(left + (this.width / 2 - this.layoutLeft.getWidth()) / 2,
(this.height - this.layoutLeft.getHeight()) / 2);
this.layoutRight.draw(canvas);
canvas.restore();
}
}
@Override
public int getCompoundPaddingRight() {
int padding = super.getCompoundPaddingRight() + this.width;
if (!TextUtils.isEmpty(getText()))
padding += this.switchPadding;
return padding;
}
@Override
public void setOnClickListener(OnClickListener l) {
super.setOnClickListener(l);
this.onClickListener = l;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
this.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY);
this.setChecked(!this.isChecked());
invalidate();
// call the onClickListener
if (this.onClickListener != null)
this.onClickListener.onClick(this);
return false;
}
return super.onTouchEvent(event);
}
/**
* Make a layout for the text
*
* @param text The text. Neither <code>null</code> nor empty
* @return The layout
*/
private Layout makeLayout(CharSequence text) {
return new StaticLayout(text, this.getPaint(), (int) Math.ceil(Layout.getDesiredWidth(
text, this.getPaint())), Layout.Alignment.ALIGN_NORMAL, 1f, 0, true);
}
@Override
protected void drawableStateChanged() {
// TODO don't use the 9-patches directly use a *.xml drawable instead
super.drawableStateChanged();
int[] myDrawableState = getDrawableState();
if (this.drawableSwitch != null) {
this.drawableSwitch.setState(myDrawableState);
}
if (this.drawableSwitchOff != null) {
this.drawableSwitchOff.setState(myDrawableState);
}
invalidate();
}
}