1,密码view控件:
package com.wansi.leadermanager.ui.layouts;
import java.util.ArrayList;
import java.util.List;
import junit.framework.Assert;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import com.wansi.leadermanager.R;
import com.wansi.leadermanager.model.bean.PasswordElement;
import com.wansi.leadermanager.utils.ConvertUtil;
/**
*
* @author lchli
*
*/
public class NineShapePasswordView extends View implements OnTouchListener {
public static interface InputPasswordCallback {
void onInputCorrect(String password);
void onInputWrong();
}
private InputPasswordCallback mSetPasswordCallback;
private int columnsCount;
private CharSequence[] pwdValues;
public void setCallback(InputPasswordCallback callback) {
mSetPasswordCallback = callback;
}
public NineShapePasswordView(Context context, int columns,
CharSequence[] values) {
super(context);
// TODO Auto-generated constructor stub
Assert.assertTrue("columns must>0", columns > 0);
Assert.assertTrue("values cannot be empty.", values != null
&& values.length > 0);
columnsCount = columns;
pwdValues = values;
setting();
}
public NineShapePasswordView(Context context, AttributeSet attrs,
int defStyle) {
super(context, attrs, defStyle);
// TODO Auto-generated constructor stub
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.passwordView);
columnsCount = a.getInt(
R.styleable.passwordView_passwordView_columnsCount, 3);
pwdValues = a
.getTextArray(R.styleable.passwordView_passwordView_pwdValues);
a.recycle();
Assert.assertTrue("columns must>0", columnsCount > 0);
Assert.assertTrue("values cannot be empty.", pwdValues != null
&& pwdValues.length > 0);
//
setting();
}
public NineShapePasswordView(Context context, AttributeSet attrs) {
super(context, attrs);
// TODO Auto-generated constructor stub
TypedArray a = getContext().obtainStyledAttributes(attrs,
R.styleable.passwordView);
columnsCount = a.getInt(
R.styleable.passwordView_passwordView_columnsCount, 3);
pwdValues = a
.getTextArray(R.styleable.passwordView_passwordView_pwdValues);
a.recycle();
Assert.assertTrue("columns must>0", columnsCount > 0);
Assert.assertTrue("values cannot be empty.", pwdValues != null
&& pwdValues.length > 0);
//
setting();
}
private void setting() {
elements = new ArrayList<PasswordElement>(columnsCount);
elementsSeleted = new ArrayList<PasswordElement>(columnsCount);
this.setFocusable(true);
this.setFocusableInTouchMode(true);
this.setOnTouchListener(this);
}
private List<PasswordElement> elements;
private List<PasswordElement> elementsSeleted;
private static final int ELEMENT_H_PADDING = 50;
private static final int ELEMENT_V_PADDING = 50;
private static final int INNER_R_STROKE_WIDTH = 2;
private static final int INNER_R_COLOR = Color.WHITE;
private static final int OUTER_R_STROKE_WIDTH = 5;
private static final int OUTER_R_NORMAL_COLOR = Color.TRANSPARENT;
private static final int OUTER_R_SELECTED_COLOR = Color.GREEN;
private static final int OUTER_R_ERROR_COLOR = Color.RED;
private static final int LINE_STROKE_WIDTH = 6;
private static final int LINE_COLOR = Color.GRAY;
private static final int LINE_ALP = 100;
private Paint mPaint = new Paint();
private Paint mLinePaint = new Paint();
private int dipToPix(float dip) {
return ConvertUtil.dip2px(getContext(), dip);
}
private void init(int w, int h) {
mPaint.setAntiAlias(true);
mPaint.setDither(true);
mPaint.setStyle(Style.STROKE);
//
mLinePaint.setAntiAlias(true);
mLinePaint.setDither(true);
mLinePaint.setStyle(Style.STROKE);
mLinePaint.setStrokeWidth(dipToPix(LINE_STROKE_WIDTH));
mLinePaint.setColor(LINE_COLOR);
mLinePaint.setAlpha(LINE_ALP);
//
int vPadding = dipToPix(ELEMENT_V_PADDING);
int hPadding = dipToPix(ELEMENT_H_PADDING);
int rowR = (w - (columnsCount + 1) * hPadding) / columnsCount / 2;
int elementSize = pwdValues.length;
int rows;
if (elementSize % columnsCount == 0)
rows = elementSize / columnsCount;
else
rows = elementSize / columnsCount + 1;
int colR = (h - (rows + 1) * vPadding) / rows / 2;
int outerR = rowR > colR ? colR : rowR;
//
int newWidth = columnsCount * 2 * outerR + (columnsCount + 1)
* hPadding;
int newHeight = rows * 2 * outerR + (rows + 1) * vPadding;
ViewGroup.LayoutParams lp = this.getLayoutParams();
lp.width = newWidth;
lp.height = newHeight;
this.setLayoutParams(lp);
//
int innerR = outerR / 4;
float defcx = hPadding + outerR;
float defcy = vPadding + outerR;
float cx = defcx;
float cy = defcy;
float innerRstrokeWidth = dipToPix(INNER_R_STROKE_WIDTH);
float outerRstrokeWidth = dipToPix(OUTER_R_STROKE_WIDTH);
PasswordElement tmp;
for (int i = 0; i < elementSize; i++) {
tmp = new PasswordElement();
tmp.setCx(cx);
tmp.setCy(cy);
tmp.setInnerR(innerR);
tmp.setOuterR(outerR);
tmp.setInnerRStrokeWidth(innerRstrokeWidth);
tmp.setOuterRStrokeWidth(outerRstrokeWidth);
tmp.setInnerRColor(INNER_R_COLOR);
tmp.setState(PasswordElement.STATE_NORMAL);
tmp.setValue(pwdValues[i].toString());
tmp.setOuterRNormalColor(OUTER_R_NORMAL_COLOR);
tmp.setOuterRSelectedColor(OUTER_R_SELECTED_COLOR);
tmp.setOuterRErrorColor(OUTER_R_ERROR_COLOR);
elements.add(tmp);
// change location.
if ((i + 1) % columnsCount == 0) {
cx = defcx;
cy = cy + vPadding + 2 * outerR;
} else {
cx = cx + hPadding + outerR * 2;
// cy not changed.
}
}
}
private boolean isInited = false;
@Override
protected void onLayout(boolean changed, int left, int top, int right,
int bottom) {
// TODO Auto-generated method stub
super.onLayout(changed, left, top, right, bottom);
if (!isInited) {
init(this.getWidth(), this.getHeight());
isInited = true;
}
}
@Override
protected void onDraw(Canvas canvas) {
// TODO Auto-generated method stub
if (elements.isEmpty())
return;
for (PasswordElement ele : elements) {// draw circles.
mPaint.setColor(ele.getInnerRColor());
mPaint.setStrokeWidth(ele.getInnerRStrokeWidth());
/*
* draw inner circle.
*/
canvas.drawCircle(ele.getCx(), ele.getCy(), ele.getInnerR(), mPaint);
mPaint.setStrokeWidth(ele.getOuterRStrokeWidth());
mPaint.setColor(ele.getCurrentOuterRcolor());
/*
* draw outer circle.
*/
canvas.drawCircle(ele.getCx(), ele.getCy(), ele.getOuterR(), mPaint);// draw
}// for end.
//
if (!elementsSeleted.isEmpty()) {// draw lines.
for (int i = 0; i < elementsSeleted.size() - 1; i++) {
PasswordElement e = elementsSeleted.get(i);
canvas.drawLine(e.getCx(), e.getCy(), elementsSeleted
.get(i + 1).getCx(),
elementsSeleted.get(i + 1).getCy(), mLinePaint);
}
if (lastMoveX != -1) {
PasswordElement e = elementsSeleted
.get(elementsSeleted.size() - 1);
canvas.drawLine(e.getCx(), e.getCy(), lastMoveX, lastMoveY,
mLinePaint);
}
}
//
super.onDraw(canvas);
}
private void checkFocusedElement(float x, float y) {
for (PasswordElement ele : elements) {
if (isTouchPointInElement(x, y, ele)) {
if (!elementsSeleted.contains(ele)) {// avoid repeat.
ele.setState(PasswordElement.STATE_SELECTED);
elementsSeleted.add(ele);
}
return;
}
}
}
private static boolean isTouchPointInElement(float x, float y,
PasswordElement ele) {
double d = Math.pow((x - ele.getCx()), 2)
+ Math.pow((y - ele.getCy()), 2);
d = Math.sqrt(d);
return d <= ele.getOuterR();
}
private float lastMoveX = -1;
private float lastMoveY = -1;
@Override
public boolean onTouch(View v, MotionEvent event) {
// TODO Auto-generated method stub
float x = event.getX();
float y = event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
resetAllElements();
checkFocusedElement(x, y);
lastMoveX = -1;
lastMoveY = -1;
invalidate();
break;
case MotionEvent.ACTION_MOVE:
checkFocusedElement(x, y);
lastMoveX = x;
lastMoveY = y;
invalidate();
break;
case MotionEvent.ACTION_UP:
// checkFocusedElement(x, y);
boolean isRight = checkSelectedElement();
lastMoveX = -1;
lastMoveY = -1;
invalidate();
//
if (mSetPasswordCallback != null) {
if (isRight)
mSetPasswordCallback.onInputCorrect(getInputPwd());
else
mSetPasswordCallback.onInputWrong();
}
break;
}
return true;
}
private String getInputPwd() {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < elementsSeleted.size(); i++) {
sb.append(elementsSeleted.get(i).getValue());
}
return sb.toString();
}
private void resetAllElements() {
elementsSeleted.clear();
for (PasswordElement e : elements) {
e.setState(PasswordElement.STATE_NORMAL);
}
}
private static final int MIN_REQUIRED_PWD_COUNTS = 4;
private boolean checkSelectedElement() {
if (elementsSeleted.size() < MIN_REQUIRED_PWD_COUNTS) {
for (PasswordElement e : elementsSeleted) {
e.setState(PasswordElement.STATE_ERROR);
}
return false;
} else {
return true;
}
}
}
2,密码item数据模型:
package com.wansi.leadermanager.model.bean;
/**
*
* @author lchli
*
*/
public class PasswordElement {
public static final int STATE_NORMAL = 1;
public static final int STATE_SELECTED = 2;
public static final int STATE_ERROR = 3;
private float cx;
private float cy;
private float innerR;
private int innerRColor;
private float innerRStrokeWidth;
private float outerR;
private float outerRStrokeWidth;
private int outerRNormalColor;
private int outerRSelectedColor;
private int outerRErrorColor;
private int state = STATE_NORMAL;
private String value;
public int getCurrentOuterRcolor() {
switch (state) {
case PasswordElement.STATE_NORMAL:
return outerRNormalColor;
case PasswordElement.STATE_SELECTED:
return outerRSelectedColor;
case PasswordElement.STATE_ERROR:
return outerRErrorColor;
default:
return outerRNormalColor;
}
}
public float getCx() {
return cx;
}
public void setCx(float cx) {
this.cx = cx;
}
public float getCy() {
return cy;
}
public void setCy(float cy) {
this.cy = cy;
}
public float getInnerR() {
return innerR;
}
public void setInnerR(float innerR) {
this.innerR = innerR;
}
public int getInnerRColor() {
return innerRColor;
}
public void setInnerRColor(int innerRColor) {
this.innerRColor = innerRColor;
}
public float getInnerRStrokeWidth() {
return innerRStrokeWidth;
}
public void setInnerRStrokeWidth(float innerRStrokeWidth) {
this.innerRStrokeWidth = innerRStrokeWidth;
}
public float getOuterR() {
return outerR;
}
public void setOuterR(float outerR) {
this.outerR = outerR;
}
public float getOuterRStrokeWidth() {
return outerRStrokeWidth;
}
public void setOuterRStrokeWidth(float outerRStrokeWidth) {
this.outerRStrokeWidth = outerRStrokeWidth;
}
public int getOuterRNormalColor() {
return outerRNormalColor;
}
public void setOuterRNormalColor(int outerRNormalColor) {
this.outerRNormalColor = outerRNormalColor;
}
public int getOuterRSelectedColor() {
return outerRSelectedColor;
}
public void setOuterRSelectedColor(int outerRSelectedColor) {
this.outerRSelectedColor = outerRSelectedColor;
}
public int getOuterRErrorColor() {
return outerRErrorColor;
}
public void setOuterRErrorColor(int outerRErrorColor) {
this.outerRErrorColor = outerRErrorColor;
}
public int getState() {
return state;
}
public void setState(int state) {
this.state = state;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
3,密码属性attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="passwordView">
<!-- 密码列数 -->
<attr name="passwordView_columnsCount" format="integer"/>
<!-- 密码值集合数组,如0123456789 -->
<attr name="passwordView_pwdValues" format="reference"/>
</declare-styleable>
</resources>
4,测试activity:
package com.wansi.leadermanager.ui.activities;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
import com.wansi.leadermanager.R;
import com.wansi.leadermanager.ui.layouts.NineShapePasswordView;
import com.wansi.leadermanager.ui.layouts.NineShapePasswordView.InputPasswordCallback;
public class LoginActivity extends Activity implements InputPasswordCallback {
private NineShapePasswordView pwdView;
private TextView tvInfo;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
pwdView = (NineShapePasswordView) findViewById(R.id.pwdView);
pwdView.setCallback(this);
tvInfo = (TextView) findViewById(R.id.tvInfo);
}
@Override
public void onInputCorrect(String password) {
// TODO Auto-generated method stub
if (validatePwdInDb(password)) {
tvInfo.setText("success pwd:" + password);
// todo start mainactivity.
} else {
tvInfo.setText("pwd error.,please retry");
}
}
private static boolean validatePwdInDb(String password) {
return true;
}
@Override
public void onInputWrong() {
// TODO Auto-generated method stub
tvInfo.setText("format error,please retry");
}
}
5,main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<com.wansi.leadermanager.ui.layouts.NineShapePasswordView
xmlns:leadermanager="http://schemas.android.com/apk/res/com.wansi.leadermanager"
android:id="@+id/pwdView"
android:layout_width="300dp"
android:layout_height="300dp"
android:layout_gravity="center"
android:background="@android:color/transparent"
leadermanager:passwordView_columnsCount="3"
leadermanager:passwordView_pwdValues="@array/pwdValues" />
<TextView
android:id="@+id/tvInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="TextView" />
</LinearLayout>
6,strings.xml:
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">LeaderManager</string>
<string name="hello">Hello World!</string>
<string-array name="pwdValues">
<item>0</item>
<item>1</item>
<item>2</item>
<item>3</item>
<item>4</item>
<item>5</item>
<item>6</item>
<item>7</item>
<item>8</item>
</string-array>
</resources>
==================================end============================================