The declare-styleable is used to set attributes to components. We have declared style attributes for Lock9View in attrs_lock_9_view.xml as shown above. These style attributes can be set in xml where the Lock9View is placed.
Lock9View.java
package com.android.androiddelight.lock9view.utils;
import java.util.ArrayList;
import java.util.List;
import com.android.androiddelight.lock9view.R;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.Pair;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
public class Lock9View extends ViewGroup {
private Paint paint;
private Bitmap bitmap;
private Canvas canvas;
private List<Pair<NodeView, NodeView>> lineList;
private NodeView currentNode;
|
private
StringBuilder pwdSb;
private
CallBack callBack;
private
Drawable nodeSrc;
private
Drawable nodeOnSrc;
public
Lock9View(Context context) {
this(context, null);
}
public
Lock9View(Context context, AttributeSet attrs) {
this(context, attrs,
0
);
}
public
Lock9View(Context context, AttributeSet attrs, int defStyleAttr) {
this(context, attrs, defStyleAttr,
0
);
}
public
Lock9View(Context context, AttributeSet attrs,
int
defStyleAttr,
int
defStyleRes) {
super(context, attrs, defStyleAttr);
initFromAttributes(attrs, defStyleAttr);
}
private
void initFromAttributes(AttributeSet attrs,
int
defStyleAttr) {
final TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.Lock9View, defStyleAttr,
0
);
nodeSrc = a.getDrawable(R.styleable.Lock9View_nodeSrc);
nodeOnSrc = a.getDrawable(R.styleable.Lock9View_nodeOnSrc);
int lineColor = Color.argb(
0
,
0
,
0
,
0
);
lineColor = a.getColor(R.styleable.Lock9View_lineColor, lineColor);
float lineWidth = 20.0f;
lineWidth = a.getDimension(R.styleable.Lock9View_lineWidth, lineWidth);
a.recycle();
paint = new Paint(Paint.DITHER_FLAG);
paint.setStyle(Style.STROKE);
paint.setStrokeWidth(lineWidth);
paint.setColor(lineColor);
paint.setAntiAlias(true);
DisplayMetrics dm = getResources().getDisplayMetrics();
bitmap = Bitmap.createBitmap(dm.widthPixels, dm.widthPixels, Bitmap.Config.ARGB_8888);
canvas = new Canvas();
canvas.setBitmap(bitmap);
for (int n = 0; n < 9; n++) {
NodeView node = new NodeView(getContext(), n + 1);
addView(node);
}
lineList = new ArrayList<Pair<NodeView,NodeView>>();
pwdSb = new StringBuilder();
setWillNotDraw(false);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(widthMeasureSpec, widthMeasureSpec);
}
@Override
protected
void onLayout(boolean changed, int left, int top, int right, int bottom) {
if (!changed) {
return;
}
int width = right - left;
int nodeWidth = width / 3;
int nodePadding = nodeWidth / 6;
for (int n = 0; n < 9; n++) {
NodeView node = (NodeView) getChildAt(n);
int row = n / 3;
int col = n % 3;
int l = col * nodeWidth + nodePadding;
int t = row * nodeWidth + nodePadding;
int r = col * nodeWidth + nodeWidth - nodePadding;
int b = row * nodeWidth + nodeWidth - nodePadding;
node.layout(l, t, r, b);
}
}
@Override
protected
void onDraw(Canvas canvas) {
canvas.drawBitmap(bitmap, 0, 0, null);
}
@Override
protected
boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
case MotionEvent.ACTION_MOVE:
NodeView nodeAt = getNodeAt(event.getX(), event.getY());
if (nodeAt == null && currentNode == null) {
return true;
} else {
clearScreenAndDrawList();
if (currentNode == null) {
currentNode = nodeAt;
currentNode.setHighLighted(true);
pwdSb.append(currentNode.getNum());
}
else if (nodeAt == null || nodeAt.isHighLighted()) {
canvas.drawLine(currentNode.getCenterX(), currentNode.getCenterY(), event.getX(), event.getY(), paint);
} else {
canvas.drawLine(currentNode.getCenterX(), currentNode.getCenterY(), nodeAt.getCenterX(), nodeAt.getCenterY(), paint);
nodeAt.setHighLighted(true);
Pair<NodeView, NodeView> pair = new Pair<NodeView, NodeView>(currentNode, nodeAt);
lineList.add(pair);
currentNode = nodeAt;
pwdSb.append(currentNode.getNum());
}
invalidate();
}
return true;
case MotionEvent.ACTION_UP:
if (pwdSb.length() <= 0) {
return super.onTouchEvent(event);
}
if (callBack != null) {
callBack.onFinish(pwdSb.toString());
pwdSb.setLength(0);
}
currentNode =
null
;
lineList.clear();
clearScreenAndDrawList();
for (int n = 0; n < getChildCount(); n++) {
NodeView node = (NodeView) getChildAt(n);
node.setHighLighted(false);
}
invalidate();
return true;
}
return super.onTouchEvent(event);
}
private void clearScreenAndDrawList() {
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
for (Pair<NodeView, NodeView> pair : lineList) {
canvas.drawLine(pair.first.getCenterX(), pair.first.getCenterY(), pair.second.getCenterX(), pair.second.getCenterY(), paint);
}
}
private
NodeView getNodeAt(float x,
float
y) {
for (int n = 0; n < getChildCount(); n++) {
NodeView node = (NodeView) getChildAt(n);
if (!(x >= node.getLeft() && x < node.getRight())) {
continue;
}
if (!(y >= node.getTop() && y < node.getBottom())) {
continue
;
}
return node;
}
return null;
}
public void setCallBack(CallBack callBack) {
this.callBack = callBack;
}
public
class NodeView extends View {
private int num;
private
boolean highLighted;
private
NodeView(Context context) {
super(context);
}
public
NodeView(Context context, int num) {
this(context);
this.num = num;
highLighted = false;
if (nodeSrc == null) {
setBackgroundResource(0);
} else {
setBackgroundDrawable(nodeSrc);
}
}
public
boolean isHighLighted() {
return highLighted;
}
public
void setHighLighted(boolean highLighted) {
this.highLighted = highLighted;
if (highLighted) {
if (nodeOnSrc == null) {
setBackgroundResource(0);
} else {
setBackgroundDrawable(nodeOnSrc);
}
} else {
if (nodeSrc == null) {
setBackgroundResource(0);
} else {
setBackgroundDrawable(nodeSrc);
}
}
}
public int getCenterX() {
return (getLeft() + getRight()) / 2;
}
public int getCenterY() {
return
(getTop() + getBottom()) / 2;
}
public int getNum() {
return
num;
}
public void setNum(int num) {
this.num = num;
}
}
public interface CallBack {
public void onFinish(String password);
}
}
Now we are ready with the 9 Pattern View. Next i am going to declare this view inside my main layout that is activity_main.xml.
Step 5 : Open the activity_main.xml under res/layout and fill it with following code.
activity_main.xml
<?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" android:background = "#ff222233">
<!--Include the Lock9View along with the package where it is located -->
<com.android.androiddelight.lock9view.utils.Lock9View android:id = "@+id/lock_9_view" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_gravity = "center"
<!--Note : These attributes are from
declare-styleable
-->
app:nodeSrc = "@drawable/lock_9_view_node_normal" app:nodeOnSrc = "@drawable/lock_9_view_node_highlighted" app:lineColor = "#ff006699" app:lineWidth = "8dp" />
</FrameLayout>
Step 6 : Open the MainActivity.java under src/package we need to declare the Lock9View and reference it in MainActivity. Write the code as follows in MainActivity.
MainActivity.java
package com.android.androiddelight.lock9view;
import com.android.androiddelight.lock9view.utils.Lock9View;
|
public class MainActivity extends ActionBarActivity {
private Lock9View lock9View;
private static String MY_PREFS_NAME = "PatternLock"; private static String PATTERN_KEY;
SharedPreferences prefs;
@Override
public void onCreate (Bundle savedInstanceState ){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
prefs = getSharedPreferences(MY_PREFS_NAME, MODE_PRIVATE);
lock9View = (Lock9View) findViewById(R.id.lock_9_view);
lock9View.setCallBack(new Lock9View.CallBack() {
@Override public void onFinish(String password) { PATTERN_KEY = prefs.getString("Pattern", "invalid"); if(PATTERN_KEY.equals("invalid")){ Toast.makeText(MainActivity.this, "Options --> Create new Pattern", Toast.LENGTH_LONG).show(); }else{ if(password.equals(PATTERN_KEY)){ Intent intent = new Intent(MainActivity.this, WelcomeActivity.class); startActivity(intent); finish(); Toast.makeText(MainActivity.this, "Login success!", Toast.LENGTH_SHORT).show(); }else{ Toast.makeText(MainActivity.this, "Pattern incorrect!", Toast.LENGTH_SHORT).show(); } } } });
Step 7 : We have to create one more activity to change the pattern. So Create a new Activity by going to File ⇒ New ⇒ Other ⇒ Android Activity. Fill all the details and name your activity as ChangeActivity.
In ChangeActivity we need 2 Lock9View 1- for drawing new pattern 2 - for confirm drawing pattern
Step 8 : Open the activity_change.xml under res/layout and fill it with following code.
activity_change.xml
<?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" android:background = "#ff222233">
<!--TextView for prompting the users -->
<TextView android:id = "@+id/tvMsg" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_marginLeft = "5dp" android:layout_marginTop = "20dp" android:textAppearance = "?android:attr/textAppearanceSmall" android:textColor = "#ff006699" />
<!-- FrameLayout to hold the 1st Lock9View -->
<FrameLayout android:id = "@+id/enterPattern" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_gravity = "center" >
<com.android.androiddelight.lock9view.utils.Lock9View android:id = "@+id/lock_9_view" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_gravity = "center"
app:nodeSrc = "@drawable/lock_9_view_node_normal" app:nodeOnSrc = "@drawable/lock_9_view_node_highlighted" app:lineColor = "#ff006699" app:lineWidth = "8dp" />
<
/ FrameLayout
>
<!-- FrameLayout to hold the 2nd Lock9View -->
<FrameLayout android:id = "@+id/confirmPattern" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_gravity = "center"
android:visibility = "gone" >
<com.android.androiddelight.lock9view.utils.Lock9View android:id = "@+id/lock_viewConfirm" android:layout_width = "match_parent" android:layout_height = "wrap_content" android:layout_gravity = "center"
app:nodeSrc = "@drawable/lock_9_view_node_normal" app:nodeOnSrc = "@drawable/lock_9_view_node_highlighted" app:lineColor = "#ff006699" app:lineWidth = "8dp" />
< / FrameLayout
>
</FrameLayout>
The layout is build. Now getting to java code to write business logic.
Step 9 : Open the ChangeActivity.java under src/package and write down the code as follows.
ChangeActivity.java
package com.android.androiddelight.lock9view;
|
public class ChangeActivity extends ActionBarActivity {
private FrameLayout enterPatternContainer, confirmPatternContainer;
private
Lock9View lockViewFirstTry, lockViewConfirm;
private
static String MY_PREFS_NAME = "PatternLock";
private
static String PATTERN_KEY;
private
SharedPreferences prefs;
private
Editor editor;
private
TextView tvMsg;
@Override
protected void onCreate (Bundle savedInstanceState ){
super .onCreate(savedInstanceState);
setContentView(R.layout.activity_change);
prefs = getSharedPreferences(MY_PREFS_NAME, MODE_PRIVATE); editor = getSharedPreferences(MY_PREFS_NAME, MODE_PRIVATE).edit();
tvMsg = (TextView)findViewById(R.id.tvMsg);
tvMsg.setText(getResources().getString(R.string.draw_pattern_msg));
enterPatternContainer = (FrameLayout) findViewById(R.id.enterPattern); confirmPatternContainer = (FrameLayout) findViewById(R.id.confirmPattern);
lockViewFirstTry = (Lock9View) findViewById(R.id.lock_viewFirstTry); lockViewConfirm = (Lock9View) findViewById(R.id.lock_viewConfirm);
//we can get a call back string when ever user interacts
//with the pattern lock view
lockViewFirstTry.setCallBack(new Lock9View.CallBack() {
@Override
public void onFinish(String password) { PATTERN_KEY = password; enterPatternContainer.setVisibility(View.GONE); tvMsg.setText(getResources().getString
(R.string.redraw_confirm_pattern_msg)); confirmPatternContainer.setVisibility(View.VISIBLE); } });
lockViewConfirm.setCallBack(new Lock9View.CallBack() {
@Override
public void onFinish(String password) {
if(password.equals(PATTERN_KEY)){ Toast.makeText(getApplicationContext(),
"Pattern created successfully!",
Toast.LENGTH_SHORT).show(); editor.putString("Pattern", password); editor.commit(); Intent intent = new Intent(ChangeActivity.this, MainActivity.class); startActivity(intent); finish(); }else{ Toast.makeText(getApplicationContext(),
"You have drawn the wrong Pattern", Toast.LENGTH_SHORT).show(); } } });
@Override
public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_change, menu); return true; }
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case R.id.contact: new AlertDialog.Builder(this) .setTitle("Android-Lock9View") .setMessage( "Email : anfersyed@gmail.com" +"\nBlog : http://android-delight.blogspot.in/") .setPositiveButton("OK", null).show(); return true; default: return super.onOptionsItemSelected(item); } }
}
Checkout the live demo :
|
|