/*
* Copyright (C) 2013 FMSoft (http://www.fmsoft.cn)
*
* 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 org.espier.ios7.ui;
import java.util.ArrayList;
import org.espier.ios7ui.R;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
/**
* IPhone like password lock view. 4 number password.
* @author mingming
*/
public class IPhoneLockPasswordView extends View {
public static final int MSG_PASSWORD_DONE = 100;
public static final int DELAY_TIME = 100;
// UI cell id defined
private static final int ID_BASE = 0;
private static final int ID_NUMBER_0 = 0; //ID_BASE + 1;
/*private static final int ID_NUMBER_1 = ID_BASE + 2;
private static final int ID_NUMBER_2 = ID_BASE + 3;
private static final int ID_NUMBER_3 = ID_BASE + 4;
private static final int ID_NUMBER_4 = ID_BASE + 5;
private static final int ID_NUMBER_5 = ID_BASE + 6;
private static final int ID_NUMBER_6 = ID_BASE + 7;
private static final int ID_NUMBER_7 = ID_BASE + 8;
private static final int ID_NUMBER_8 = ID_BASE + 9;
private static final int ID_NUMBER_9 = ID_BASE + 10;*/
private static final int ID_DISPLAY_1 = 0; //ID_BASE + 11;
private static final int ID_DISPLAY_2 = 1; //ID_BASE + 12;
private static final int ID_DISPLAY_3 = 2; //ID_BASE + 13;
private static final int ID_DISPLAY_4 = 3; //ID_BASE + 14;
private static final int ID_BUTTON_BACK = ID_BASE + 15;
// number cell data
private static final int CELL_DISPLAY = -1;
private static final int CELL_NUMBER_0 = 0;
private static final int CELL_NUMBER_1 = 1;
private static final int CELL_NUMBER_2 = 2;
private static final int CELL_NUMBER_3 = 3;
private static final int CELL_NUMBER_4 = 4;
private static final int CELL_NUMBER_5 = 5;
private static final int CELL_NUMBER_6 = 6;
private static final int CELL_NUMBER_7 = 7;
private static final int CELL_NUMBER_8 = 8;
private static final int CELL_NUMBER_9 = 9;
private static final int CELL_KEY_BACK = 10;
private static final int CANCLE_INPUT = -9999;
private Cell mFocusCell;
private NumberCell mNumberCells[];
private DisplayCell mDisplayCells[];
private KeyCell mKeyCellBack;
private Context mContext = null;
private ArrayList<Cell> mChildList;
private InputState mInputState;
private OnPasswordLister mPasswordLister;
private Paint mPaint;
private int mWidth;
private int mHeight;
private int mCellWidth;
private int mCellHeight;
// UI item value
private int mFrameworkColor;
private int mGridColor;
private int mDisplayBkColor;
private int mDisplayInputColor;
private int mNumberColor;
private int mFocusColor;
private float mFrameworkWidth;
private float mGridWidth;
private float mDisplayScale;
private float mDisplayNumberScale;
private float mNumberScale;
private float mRoundRx;
private float mRoundRy;
private float mBackAspect;
private float mBackScale;
private float mBackItemScale;
private float mBackWidth;
enum InputState {
NotInput,
Input_1,
Input_2,
Input_3,
InputDone,
}
public interface OnPasswordLister {
void onPasswordDone(String password);
}
public IPhoneLockPasswordView(Context context) {
super(context);
initView(context);
}
public IPhoneLockPasswordView(Context context, AttributeSet attr) {
super(context, attr);
initView(context);
}
/*public IPhoneLockPasswordLayout(Context context, AttributeSet attr, int defStyle) {
super(context, attr, defStyle);
// TODO Auto-generated constructor stub
initView(context);
}*/
private void initView(Context context) {
int i;
float base = 100.0f;
Resources res;
mContext = context;
mChildList = new ArrayList<Cell> ();
// create display cell
mDisplayCells = new DisplayCell[4];
for (i = 0; i < 4; i++) {
mDisplayCells[i] = new DisplayCell(ID_DISPLAY_1 + i, CELL_DISPLAY, i, false);
addChildCell(mDisplayCells[i]);
}
// create back key cell
mKeyCellBack = new KeyCell(ID_BUTTON_BACK, CELL_KEY_BACK, 0, true);
addChildCell(mKeyCellBack);
// create number cell
mNumberCells = new NumberCell[10];
for (i = CELL_NUMBER_0; i < CELL_NUMBER_9 + 1; i++) {
mNumberCells[i] = new NumberCell(ID_NUMBER_0 + i, i, i, true);
addChildCell(mNumberCells[i]);
}
mPaint = new Paint();
res = mContext.getResources();
mFrameworkColor = res.getColor(R.color.lock_password_framework_color);
mGridColor = res.getColor(R.color.lock_password_grid_color);
mDisplayBkColor = res.getColor(R.color.lock_password_display_bk_color);
mDisplayInputColor = res.getColor(R.color.lock_password_display_input_color);
mNumberColor = res.getColor(R.color.lock_password_number_color);
mFocusColor = res.getColor(R.color.lock_password_focus_color);
base = (float) res.getInteger(R.integer.lock_password_base);
mFrameworkWidth = res.getInteger(R.integer.lock_password_framework_width) / base;
mGridWidth = res.getInteger(R.integer.lock_password_grid_width) / base;
mDisplayScale = res.getInteger(R.integer.lock_password_display_scale) / base;
mDisplayNumberScale = res.getInteger(R.integer.lock_password_display_number_scale) / base;
mNumberScale = res.getInteger(R.integer.lock_password_number_scale) / base;
mRoundRx = res.getInteger(R.integer.lock_password_round_radius_rx) / base;
mRoundRy = res.getInteger(R.integer.lock_password_round_radius_ry) / base;
mBackAspect = res.getInteger(R.integer.lock_password_back_aspect) / base;
mBackScale = res.getInteger(R.integer.lock_password_back_scale) / base;
mBackItemScale = res.getInteger(R.integer.lock_password_back_item_scale) / base;
mBackWidth = res.getInteger(R.integer.lock_password_back_width) / base;
mInputState = InputState.NotInput;
mPasswordLister = null;
mFocusCell = null;
mWidth = getWidth();
mHeight = getHeight();
mCellWidth = mWidth / 5;
mCellHeight = mHeight / 3;
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
int i;
int dx, dy;
int displayWidth;
int displayHeight;
int numberWidth;
int numberHeight;
mWidth = w;
mHeight = h;
// 3 cell in one line
mCellHeight = h / 3;
// 4 display cell and 1 back key cell in one row
mCellWidth = w / 5;
displayWidth = (int) (mCellWidth * mDisplayScale);
displayHeight = (int) (mCellHeight * mDisplayScale);
dx = (mCellWidth - displayWidth) / 2;
dy = (mCellHeight - displayHeight) / 2;
// 5 number cell in one row
numberWidth = w / 5;
numberHeight = mCellHeight;
// set display cell rect
for (i = 0; i < mDisplayCells.length; i++) {
mDisplayCells[i].mRect.left = dx + (displayWidth * i) + (dx * 2 * i);
mDisplayCells[i].mRect.top = dy;
mDisplayCells[i].mRect.right = mDisplayCells[i].mRect.left + displayWidth;
mDisplayCells[i].mRect.bottom = mDisplayCells[i].mRect.top + displayHeight;
}
// set back key cell rect
mKeyCellBack.mRect.left = dx + (displayWidth * i) + (dx * 2 * i);
mKeyCellBack.mRect.top = dy;
mKeyCellBack.mRect.right = mKeyCellBack.mRect.left + displayWidth;
mKeyCellBack.mRect.bottom = mKeyCellBack.mRect.top + displayHeight;
// set number cell rect, 5 cell in one row
for (i = 0; i < mNumberCells.length; i++) {
mNumberCells[i].mRect.left = (numberWidth * (i % 5));
mNumberCells[i].mRect.top = mCellHeight * (1 +(i / 5));
mNumberCells[i].mRect.right = mNumberCells[i].mRect.left + numberWidth;
mNumberCells[i].mRect.bottom = mNumberCells[i].mRect.top + numberHeight;
}
}
/*@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int widthSpecMode = MeasureSpec.getMode(widthMeasureSpec);
int widthSpecSize = MeasureSpec.getSize(widthMeasureSpec);
int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
if (widthSpecMode == MeasureSpec.UNSPECIFIED
|| heightSpecMode == MeasureSpec.UNSPECIFIED) {
throw new RuntimeException("CellLayout cannot have UNSPECIFIED dimensions");
}
int i;
int childCount;
int cellWidth;
int cellHeight;
int orientation;
LayoutParams lp;
View childView;
childCount = getChildCount();
orientation = getOrientation();
for (i = 0; i < childCount; i++) {
childView = (View) getChildAt(i);
if (null == childView) {
continue;
}
if (HORIZONTAL == orientation) {
cellWidth = widthSpecSize / childCount;
cellHeight = heightSpecSize;
} else {
cellWidth = widthSpecSize;
cellHeight = heightSpecSize / childCount;
}
lp = (LayoutParams) childView.getLayoutParams();
lp.width = cellWidth;
lp.height = cellHeight;
int childWidthMeasureSpec =
MeasureSpec.makeMeasureSpec(lp.width, MeasureSpec.EXACTLY);
int childheightMeasureSpec =
MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
childView.measure(childWidthMeasureSpec, childheightMeasureSpec);
}
setMeasuredDimension(widthSpecSize, heightSpecSize);
}*/
public boolean dispatchTouchEvent(MotionEvent event) {
Cell childCell = null;
Cell targetCell = null;
int x = (int) event.getX();
int y = (int) event.getY();
int action = (event.getAction() & MotionEvent.ACTION_MASK);
for (int i = 0; i < mChildList.size(); i++) {
childCell = mChildList.get(i);
// we just let one cell in touch
if (childCell.getRect().contains(x, y)) {
//childCell.onTouchEvent(event);
targetCell = childCell;
break;
}
}
// lock one pressed target cell
if (null == mFocusCell) {
switch(action) {
case MotionEvent.ACTION_DOWN:
mFocusCell = targetCell;
break;
default:
break;
}
} else {
switch(action) {
case MotionEvent.ACTION_UP:
targetCell = mFocusCell;
mFocusCell = null;
break;
case MotionEvent.ACTION_MOVE:
targetCell = mFocusCell;
break;
default:
break;
}
}
if (null != targetCell) {
targetCell.onTouchEvent(event);
}
// return true means I want to receive the touch event
//return super.dispatchTouchEvent(event);
return true;
}
@Override
protected void onDraw(Canvas canvas) {
int i;
float startX, startY;
float endX, endY;
RectF rectF = new RectF();
mPaint.setAntiAlias(true);
// draw child cell
for (i = 0; i < mChildList.size(); i++) {
mChildList.get(i).onDraw(canvas);
}
// draw grid
mPaint.setStrokeWidth(mGridWidth);
mPaint.setColor(mGridColor);
for (i = 1; i < 3; i++) {
startX = 0;
startY = mCellHeight * i;
endX = mWidth;
endY = startY;
canvas.drawLine(startX, startY, endX, endY, mPaint);
}
for (i = 1; i < 5; i++) {
startX = mCellWidth * i;
startY = mCellHeight * 1;
endX = startX;
endY = mHeight;
canvas.drawLine(startX, startY, endX, endY, mPaint);
}
// draw rectangle framework
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setStrokeWidth(mFrameworkWidth);
mPaint.setColor(mFrameworkColor);
rectF.left = 0.0f;
rectF.top = 0.0f;
rectF.right = mWidth;
rectF.bottom = mHeight;
canvas.drawRoundRect(rectF, mRoundRx, mRoundRy, mPaint);
}
public void setOnPasswordLister(OnPasswordLister lister) {
mPasswordLister = lister;
}
public void resetInput() {
for (int i = 0; i < mDisplayCells.length; i++) {
mDisplayCells[i].setInputedValue(CANCLE_INPUT);
}
mInputState = InputState.NotInput;
}
private void addChildCell(Cell child) {
if (null == mChildList) {
mChildList = new ArrayList<Cell> ();
}
mChildList.add(child);
child.setParent(this);
}
private void onCellClick(Cell cell) {
int value = cell.getValue();
int engine = cell.getCellValue();
if (InputState.NotInput == mInputState) {
if (isInputNumber(engine)) {
// switch state
mInputState = InputState.Input_1;
// on move to state Input_1
mDisplayCells[ID_DISPLAY_1].setInputedValue(value);
}
} else if (InputState.Input_1 == mInputState) {
if (isInputNumber(engine)) {
mInputState = InputState.Input_2;
mDisplayCells[ID_DISPLAY_2].setInputedValue(value);
} else if (CELL_KEY_BACK == engine) {
mInputState = InputState.NotInput;
mDisplayCells[ID_DISPLAY_1].setInputedValue(CANCLE_INPUT);
}
} else if (InputState.Input_2 == mInputState) {
if (isInputNumber(engine)) {
mInputState = InputState.Input_3;
mDisplayCells[ID_DISPLAY_3].setInputedValue(value);
} else if (CELL_KEY_BACK == engine) {
mInputState = InputState.Input_1;
mDisplayCells[ID_DISPLAY_2].setInputedValue(CANCLE_INPUT);
}
} else if (InputState.Input_3 == mInputState) {
if (isInputNumber(engine)) {
//mInputState = InputState.NotInput;
mInputState = InputState.InputDone;
mDisplayCells[ID_DISPLAY_4].setInputedValue(value);
// password input done call the callback
if (null != mPasswordLister) {
mPasswordLister.onPasswordDone(String.format("%d%d%d%d",
mDisplayCells[ID_DISPLAY_1].getValue(),
mDisplayCells[ID_DISPLAY_2].getValue(),
mDisplayCells[ID_DISPLAY_3].getValue(),
mDisplayCells[ID_DISPLAY_4].getValue()));
}
} else if (CELL_KEY_BACK == engine) {
mInputState = InputState.Input_2;
mDisplayCells[ID_DISPLAY_3].setInputedValue(CANCLE_INPUT);
}
} else if (InputState.InputDone == mInputState) {
// do noting
}
}
private boolean isInputNumber(int cellValue) {
boolean ret = false;
if (CELL_NUMBER_0 == cellValue ||
CELL_NUMBER_1 == cellValue ||
CELL_NUMBER_2 == cellValue ||
CELL_NUMBER_3 == cellValue ||
CELL_NUMBER_4 == cellValue ||
CELL_NUMBER_5 == cellValue ||
CELL_NUMBER_6 == cellValue ||
CELL_NUMBER_7 == cellValue ||
CELL_NUMBER_8 == cellValue ||
CELL_NUMBER_9 == cellValue) {
ret = true;
}
return ret;
}
/**
* ************************************************
* lock password UI element cell class
* ************************************************
*/
abstract class Cell {
int mID;
int mValue;
int mCellValue;
boolean mPressed;
boolean mInputed;
boolean mInputable;
Rect mRect;
View mParent;
Cell() {
mID = ID_BASE;
mRect = new Rect();
}
Cell(int id, int cellValue, int value, boolean inputable) {
mID = id;
mCellValue = cellValue;
mValue = value;
mInputable = inputable;
mRect = new Rect();
}
abstract void onDraw(Canvas canvas);
void setParent(View parent) {
mParent = parent;
}
void setInputedValue(int value) {
if (mInputable) {
return;
}
if (CANCLE_INPUT == value) {
mInputed = false;
} else {
mInputed = true;
}
mValue = value;
mParent.invalidate(mRect);
}
Rect getRect() {
return mRect;
}
int getCellValue() {
return mCellValue;
}
int getValue() {
return mValue;
}
void onTouchEvent(MotionEvent event) {
if (!mInputable) {
return;
}
switch(event.getAction()) {
case MotionEvent.ACTION_DOWN:
mPressed = true;
mParent.invalidate(mRect);
break;
case MotionEvent.ACTION_UP:
mPressed = false;
onCellClick(this);
mParent.invalidate(mRect);
break;
case MotionEvent.ACTION_CANCEL:
mPressed = false;
mParent.invalidate(mRect);
break;
default:
break;
}
}
}
class NumberCell extends Cell {
public NumberCell() {
super();
}
public NumberCell(int id, int cellValue, int value, boolean inputable) {
super(id, cellValue, value, inputable);
}
@Override
void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
String text;
Rect rect = new Rect();
// just for test
//int color;
//color = 0xff0000ff + mValue * 1000;
//mPaint.setStyle(Paint.Style.FILL);
//mPaint.setColor(color);
//canvas.drawRect(mRect, mPaint);
mPaint.setTextAlign(Paint.Align.CENTER);
mPaint.setTextSize((mRect.width()) * mNumberScale);
text = String.format("%d", mValue);
mPaint.getTextBounds(text, 0, text.length(), rect);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mNumberColor);
canvas.drawText(String.format("%d", mValue),
mRect.left + mRect.width() / 2,
mRect.top + ((mRect.height() / 2) + (rect.height() / 2)),
mPaint);
if (mPressed) {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mFocusColor);
canvas.drawRect(mRect, mPaint);
}
}
}
class DisplayCell extends Cell {
public DisplayCell() {
super();
}
public DisplayCell(int id, int cellValue, int value, boolean inputable) {
super(id, cellValue, value, inputable);
}
@Override
void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
// draw background
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mDisplayBkColor);
canvas.drawRect(mRect, mPaint);
// draw input number
if (mInputed) {
int square;
RectF rectF = new RectF();
if (mRect.width() <= mRect.height()) {
square = (int) (mRect.width() * mDisplayNumberScale);
} else {
square = (int) (mRect.height() * mDisplayNumberScale);
}
rectF.left = mRect.left + (mRect.width() - square) / 2;
rectF.top = mRect.top + (mRect.height() - square) / 2;
rectF.right = rectF.left + square;
rectF.bottom = rectF.top + square;
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mDisplayInputColor);
canvas.drawArc(rectF, 0, 360, true, mPaint);
}
}
}
class KeyCell extends Cell {
public KeyCell() {
super();
}
public KeyCell(int id, int cellValue, int value, boolean inputable) {
super(id, cellValue, value, inputable);
}
@Override
void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
float w, h;
float arrowW, rectW;
float nextX, nextY;
Path path = new Path();
w = (mRect.width() * mBackScale);
h = Math.min(mRect.height() * mBackScale, w / mBackAspect);
arrowW = w / (1 + mBackAspect);
rectW = w - arrowW;
// create the arrow path
nextX = mRect.left + (mRect.width() - w) / 2;
nextY = mRect.top + mRect.height() / 2;
path.moveTo(nextX, nextY);
nextX = nextX + arrowW;
nextY = nextY - h / 2;
path.lineTo(nextX, nextY);
nextX = nextX + rectW;
path.lineTo(nextX, nextY);
nextY = nextY + h;
path.lineTo(nextX, nextY);
nextX = nextX - rectW;
path.lineTo(nextX, nextY);
nextX = nextX - arrowW;
nextY = nextY - h / 2;
path.lineTo(nextX, nextY);
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mNumberColor);
canvas.drawPath(path, mPaint);
// draw "X"
mPaint.setStyle(Paint.Style.STROKE);
mPaint.setColor(mDisplayInputColor);
mPaint.setStrokeWidth(mBackWidth);
nextX = nextX + arrowW + ((rectW * (1 - mBackItemScale)) / 2);
nextY = mRect.top + (mRect.height() - (h * mBackItemScale)) / 2;
canvas.drawLine(nextX, nextY,
nextX + (rectW * mBackItemScale), nextY + (h * mBackItemScale),
mPaint);
canvas.drawLine(nextX, nextY + (h * mBackItemScale),
nextX + (rectW * mBackItemScale), nextY,
mPaint);
if (mPressed) {
mPaint.setStyle(Paint.Style.FILL);
mPaint.setColor(mFocusColor);
canvas.drawPath(path, mPaint);
}
}
}
/**
* ************************************************
* end lock password UI element cell class
* ************************************************
*/
}