增加九宫格手势解锁样例

This commit is contained in:
zengfantian 2015-10-23 15:55:10 +08:00
parent 78ce533ee0
commit d0abfd9294
18 changed files with 640 additions and 4 deletions

View File

@ -755,6 +755,32 @@
</intent-filter>
</activity>
<!-- 九宫格手势锁示例 -->
<activity
android:name=".sample.gesture.Lock9DemoActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:label="@string/Lock9DemoActivity"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.zftlive.android.SAMPLE_CODE" />
</intent-filter>
</activity>
<activity
android:name=".sample.gesture.NormalActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" >
</activity>
<activity
android:name=".sample.gesture.LStyleActivity"
android:configChanges="keyboardHidden|orientation|screenSize"
android:screenOrientation="portrait"
android:windowSoftInputMode="stateAlwaysHidden|adjustResize" >
</activity>
<!-- 静态注册短信广播接收器 -->
<receiver android:name=".sample.sms.SMSBroadcastReceiver" >
<intent-filter android:priority="2147483647" >

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<translate
android:fromXDelta="-5%"
android:toXDelta="5%"
android:fromYDelta="0%"
android:toYDelta="0%"
android:repeatCount="1"
android:repeatMode="reverse"
android:startOffset="0"
android:duration="150" />
</set>

View File

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<scale
android:pivotX="50%"
android:pivotY="50%"
android:fromXScale="1"
android:toXScale="2"
android:fromYScale="1"
android:toYScale="2"
android:repeatCount="1"
android:repeatMode="reverse"
android:startOffset="0"
android:duration="150" />
</set>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.zftlive.android.library.widget.gesture.Lock9View
android:id="@+id/lock_9_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="24dp"
android:layout_gravity="center"
app:lock9_nodeSrc="@drawable/lock9_node_small"
app:lock9_nodeSize="16dp"
app:lock9_nodeAreaExpand="24dp"
app:lock9_nodeOnAnim="@anim/lock9_node_on_2"
app:lock9_lineColor="@color/blue_light"
app:lock9_lineWidth="4dp"
app:lock9_enableVibrate="true"
app:lock9_vibrateTime="20" />
</FrameLayout>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="8dp"
android:orientation="vertical">
<Button
android:id="@+id/btn_normal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:onClick="onBtnNormalClick"
android:text="@string/normal_demo" />
<Button
android:id="@+id/btn_l_style"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:onClick="onBtnLStyleClick"
android:text="@string/android_l_style" />
</LinearLayout>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.zftlive.android.library.widget.gesture.Lock9View
android:id="@+id/lock_9_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
app:lock9_nodeSrc="@drawable/lock9_node_normal"
app:lock9_nodeOnSrc="@drawable/lock9_node_active"
app:lock9_nodeOnAnim="@anim/lock9_node_on_1"
app:lock9_lineColor="@color/blue_light"
app:lock9_lineWidth="4dp"
app:lock9_padding="28dp"
app:lock9_spacing="28dp" />
</FrameLayout>

View File

@ -189,4 +189,21 @@
</declare-styleable>
<!-- 带数字的进度条(结束) -->
<!-- 九宫格锁屏控件(开始) -->
<declare-styleable name="Lock9View">
<attr name="lock9_nodeSrc" format="color|reference" />
<attr name="lock9_nodeOnSrc" format="color|reference" />
<attr name="lock9_nodeSize" format="dimension" />
<attr name="lock9_nodeAreaExpand" format="dimension" />
<attr name="lock9_nodeOnAnim" format="reference" />
<attr name="lock9_lineColor" format="color" />
<attr name="lock9_lineWidth" format="dimension" />
<attr name="lock9_padding" format="dimension" />
<attr name="lock9_spacing" format="dimension" />
<attr name="lock9_enableVibrate" format="boolean" />
<attr name="lock9_vibrateTime" format="integer" />
</declare-styleable>
<!-- 九宫格锁屏控件(结束) -->
</resources>

View File

@ -46,4 +46,9 @@
<color name="btn_press_color">#66000000</color>
<color name="btn_unpress_color">#22000000</color>
<!-- 手势密码解锁 -->
<color name="red_light">#ffff4444</color>
<color name="green_light">#ff99cc00</color>
<color name="blue_light">#ff33b5e5</color>
</resources>

View File

@ -34,6 +34,7 @@
<string name="NumberPbActivity">带数字的进度条样例</string>
<string name="BaiduLocationActivity">百度地图定位演示DEMO</string>
<string name="AlipayDemoActivity">支付宝支付DEMO</string>
<string name="Lock9DemoActivity">九宫格手势锁示例</string>
<!-- Android样例锦集label(结束) -->
@ -179,4 +180,9 @@
<string name="button_ok">确定</string>
<!-- zxing(end) -->
<!-- 九宫格锁屏控件(开始) -->
<string name="normal_demo">Normal Demo</string>
<string name="android_l_style">Android L Style</string>
<!-- 九宫格锁屏控件(结束) -->
</resources>

View File

@ -0,0 +1,359 @@
/*
* Copyright 2015-2016 TakWolf
*
* 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.zftlive.android.library.widget.gesture;
import java.util.ArrayList;
import java.util.List;
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.graphics.drawable.Drawable;
import android.os.Vibrator;
import android.util.AttributeSet;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.AnimationUtils;
import com.zftlive.android.R;
public class Lock9View extends ViewGroup {
/**
* 节点相关定义
*/
private List<Pair<NodeView, NodeView>> lineList = new ArrayList<>(); // 已经连线的节点链表
private NodeView currentNode; // 最近一个点亮的节点null表示还没有点亮任何节点
private float x; // 当前手指坐标x
private float y; // 当前手指坐标y
/**
* 自定义属性列表
*/
private Drawable nodeSrc;
private Drawable nodeOnSrc;
private float nodeSize; // 节点大小如果不为0则忽略内边距和间距属性
private float nodeAreaExpand; // 对节点的触摸区域进行扩展
private int nodeOnAnim; // 节点点亮时的动画
private int lineColor;
private float lineWidth;
private float padding; // 内边距
private float spacing; // 节点间隔距离
/**
* 震动管理器
*/
private Vibrator vibrator;
private boolean enableVibrate;
private int vibrateTime;
/**
* 画线用的画笔
*/
private Paint paint;
/**
* 密码构建器
*/
private StringBuilder passwordBuilder = new StringBuilder();
/**
* 结果回调监听器接口
*/
private CallBack callBack;
public interface CallBack {
void onFinish(String password);
}
public void setCallBack(CallBack callBack) {
this.callBack = callBack;
}
/**
* 构造函数
*/
public Lock9View(Context context) {
super(context);
initFromAttributes(context, null, 0);
}
public Lock9View(Context context, AttributeSet attrs) {
super(context, attrs);
initFromAttributes(context, attrs, 0);
}
public Lock9View(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initFromAttributes(context, attrs, defStyleAttr);
}
/**
* 初始化
*/
private void initFromAttributes(Context context, AttributeSet attrs, int defStyleAttr) {
// 获取定义的属性
final TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.Lock9View, defStyleAttr, 0);
nodeSrc = a.getDrawable(R.styleable.Lock9View_lock9_nodeSrc);
nodeOnSrc = a.getDrawable(R.styleable.Lock9View_lock9_nodeOnSrc);
nodeSize = a.getDimension(R.styleable.Lock9View_lock9_nodeSize, 0);
nodeAreaExpand = a.getDimension(R.styleable.Lock9View_lock9_nodeAreaExpand, 0);
nodeOnAnim = a.getResourceId(R.styleable.Lock9View_lock9_nodeOnAnim, 0);
lineColor = a.getColor(R.styleable.Lock9View_lock9_lineColor, Color.argb(0, 0, 0, 0));
lineWidth = a.getDimension(R.styleable.Lock9View_lock9_lineWidth, 0);
padding = a.getDimension(R.styleable.Lock9View_lock9_padding, 0);
spacing = a.getDimension(R.styleable.Lock9View_lock9_spacing, 0);
enableVibrate = a.getBoolean(R.styleable.Lock9View_lock9_enableVibrate, false);
vibrateTime = a.getInt(R.styleable.Lock9View_lock9_vibrateTime, 20);
a.recycle();
// 初始化振动器
if (enableVibrate) {
vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
}
// 初始化画笔
paint = new Paint(Paint.DITHER_FLAG);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(lineWidth);
paint.setColor(lineColor);
paint.setAntiAlias(true); // 抗锯齿
// 构建node
for (int n = 0; n < 9; n++) {
NodeView node = new NodeView(getContext(), n + 1);
addView(node);
}
// 清除FLAG否则 onDraw() 不会调用原因是 ViewGroup 默认透明背景不需要调用 onDraw()
setWillNotDraw(false);
}
/**
* TODO 我们让高度等于宽度 - 方法有待验证
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
int size = measureSize(widthMeasureSpec); // 测量宽度
setMeasuredDimension(size, size);
}
/**
* TODO 测量长度
*/
private int measureSize(int measureSpec) {
int specMode = MeasureSpec.getMode(measureSpec); // 得到模式
int specSize = MeasureSpec.getSize(measureSpec); // 得到尺寸
switch (specMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
return specSize;
default:
return 0;
}
}
/**
* 在这里进行node的布局
*/
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (changed) {
if (nodeSize > 0) { // 如果设置nodeSize值则将节点绘制在九等分区域中心
float areaWidth = (right - left) / 3;
for (int n = 0; n < 9; n++) {
NodeView node = (NodeView) getChildAt(n);
// 获取3*3宫格内坐标
int row = n / 3;
int col = n % 3;
// 计算实际的坐标
int l = (int) (col * areaWidth + (areaWidth - nodeSize) / 2);
int t = (int) (row * areaWidth + (areaWidth - nodeSize) / 2);
int r = (int) (l + nodeSize);
int b = (int) (t + nodeSize);
node.layout(l, t, r, b);
}
} else { // 否则按照分割边距布局手动计算节点大小
float nodeSize = (right - left - padding * 2 - spacing * 2) / 3;
for (int n = 0; n < 9; n++) {
NodeView node = (NodeView) getChildAt(n);
// 获取3*3宫格内坐标
int row = n / 3;
int col = n % 3;
// 计算实际的坐标要包括内边距和分割边距
int l = (int) (padding + col * (nodeSize + spacing));
int t = (int) (padding + row * (nodeSize + spacing));
int r = (int) (l + nodeSize);
int b = (int) (t + nodeSize);
node.layout(l, t, r, b);
}
}
}
}
/**
* 在这里处理手势
*/
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
x = event.getX(); // 这里要实时记录手指的坐标
y = event.getY();
NodeView nodeAt = getNodeAt(x, y);
if (currentNode == null) { // 之前没有点
if (nodeAt != null) { // 第一个点
currentNode = nodeAt;
currentNode.setHighLighted(true);
passwordBuilder.append(currentNode.getNum());
invalidate(); // 通知重绘
}
} else { // 之前有点-所以怎么样都要重绘
if (nodeAt != null && !nodeAt.isHighLighted()) { // 当前碰触了新点
nodeAt.setHighLighted(true);
Pair<NodeView, NodeView> pair = new Pair<>(currentNode, nodeAt);
lineList.add(pair);
// 赋值当前的node
currentNode = nodeAt;
passwordBuilder.append(currentNode.getNum());
}
invalidate(); // 通知重绘
}
break;
case MotionEvent.ACTION_UP:
if (passwordBuilder.length() > 0) { // 有触摸点
// 回调结果
if (callBack != null) {
callBack.onFinish(passwordBuilder.toString());
}
// 清空状态
lineList.clear();
currentNode = null;
passwordBuilder.setLength(0);
// 清除高亮
for (int n = 0; n < getChildCount(); n++) {
NodeView node = (NodeView) getChildAt(n);
node.setHighLighted(false);
}
// 通知重绘
invalidate();
}
break;
}
return true;
}
/**
* 系统绘制回调-主要绘制连线
*/
@Override
protected void onDraw(Canvas canvas) {
// 先绘制已有的连线
for (Pair<NodeView, NodeView> pair : lineList) {
canvas.drawLine(pair.first.getCenterX(), pair.first.getCenterY(), pair.second.getCenterX(), pair.second.getCenterY(), paint);
}
// 如果已经有点亮的点则在点亮点和手指位置之间绘制连线
if (currentNode != null) {
canvas.drawLine(currentNode.getCenterX(), currentNode.getCenterY(), x, y, paint);
}
}
/**
* 获取给定坐标点的Node返回null表示当前手指在两个Node之间
*/
private NodeView getNodeAt(float x, float y) {
for (int n = 0; n < getChildCount(); n++) {
NodeView node = (NodeView) getChildAt(n);
if (!(x >= node.getLeft() - nodeAreaExpand && x < node.getRight() + nodeAreaExpand)) {
continue;
}
if (!(y >= node.getTop() - nodeAreaExpand && y < node.getBottom() + nodeAreaExpand)) {
continue;
}
return node;
}
return null;
}
/**
* 节点描述类
*/
private class NodeView extends View {
private int num;
private boolean highLighted = false;
@SuppressWarnings("deprecation")
public NodeView(Context context, int num) {
super(context);
this.num = num;
setBackgroundDrawable(nodeSrc);
}
public boolean isHighLighted() {
return highLighted;
}
@SuppressWarnings("deprecation")
public void setHighLighted(boolean highLighted) {
if (this.highLighted != highLighted) {
this.highLighted = highLighted;
if (nodeOnSrc != null) { // 没有设置高亮图片则不变化
setBackgroundDrawable(highLighted ? nodeOnSrc : nodeSrc);
}
if (nodeOnAnim != 0) { // 播放动画
if (highLighted) {
startAnimation(AnimationUtils.loadAnimation(getContext(), nodeOnAnim));
} else {
clearAnimation();
}
}
if (enableVibrate) { // 震动
if (highLighted) {
vibrator.vibrate(vibrateTime);
}
}
}
}
public int getCenterX() {
return (getLeft() + getRight()) / 2;
}
public int getCenterY() {
return (getTop() + getBottom()) / 2;
}
public int getNum() {
return num;
}
}
}

View File

@ -0,0 +1,45 @@
/*
* Copyright 2015-2016 TakWolf
*
* 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.zftlive.android.sample.gesture;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;
import com.zftlive.android.R;
import com.zftlive.android.library.widget.gesture.Lock9View;
public class LStyleActivity extends Activity {
protected Lock9View lock9View;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_l_style_lock);
lock9View = (Lock9View) findViewById(R.id.lock_9_view);
lock9View.setCallBack(new Lock9View.CallBack() {
@Override
public void onFinish(String password) {
Toast.makeText(LStyleActivity.this, password, Toast.LENGTH_SHORT).show();
}
});
}
}

View File

@ -0,0 +1,42 @@
/*
* Copyright 2015-2016 TakWolf
*
* 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.zftlive.android.sample.gesture;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import com.zftlive.android.R;
public class Lock9DemoActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_lock_main);
}
public void onBtnNormalClick(View v) {
startActivity(new Intent(this, NormalActivity.class));
}
public void onBtnLStyleClick(View v) {
startActivity(new Intent(this, LStyleActivity.class));
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2015-2016 TakWolf
*
* 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.zftlive.android.sample.gesture;
import android.app.Activity;
import android.os.Bundle;
import android.widget.Toast;
import com.zftlive.android.R;
import com.zftlive.android.library.widget.gesture.Lock9View;
public class NormalActivity extends Activity {
protected Lock9View lock9View;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_normal_lock);
lock9View = (Lock9View) findViewById(R.id.lock_9_view);
lock9View.setCallBack(new Lock9View.CallBack() {
@Override
public void onFinish(String password) {
Toast.makeText(NormalActivity.this, password, Toast.LENGTH_SHORT).show();
}
});
}
}