================================================
FILE: .idea/vcs.xml
================================================
================================================
FILE: README.md
================================================
# PasscodeView
[](https://www.apache.org/licenses/LICENSE-2.0)
[  ](https://bintray.com/iammehedi/Maven/online.devliving%3Apasscodeview/_latestVersion)
An android widget to input passcode.
## Setup
### Maven
```xml
online.devlivingpasscodeview1.0.3pom
```
### Gradle
```groovy
compile 'online.devliving:passcodeview:1.0.3'
```
## Usage
`PasscodeView` is a `ViewGroup` subclass. So it can easily be added in any xml layout file.
```xml
```
### Methods
- `requestToShowKeyboard()` - Request the PasscodeView to be focused programmatically
- `setText(CharSequence text)` - Set Passcode programmatically
- `clearText()` - Clear Passcode
- `getText()` - get entered Passcode
- `setPasscodeEntryListener(PasscodeEntryListener mPasscodeEntryListener)` - Set a listener to get notified when the Passcode has been entered
### Listener:`PasscodeEntryListener`
- `onPasscodeEntered(String passcode)` - Called when all the digits of the passcode has been entered
```java
passcodeView.setPasscodeEntryListener(new PasscodeView.PasscodeEntryListener() {
@Override
public void onPasscodeEntered(String passcode) {
Toast.makeText(SampleActivity.this, "Passcode entered: " + passcode, Toast.LENGTH_SHORT).show();
}
});
```
### XML Attributes
- `numDigits` - Number of passcode digits
- `digitElevation` - Elevation of each digit, only applicable for OS version >= Lollipop
- `digitRadius` - radius for digit circle `16dip` by default
- `digitInnerRadius` - radius for digit inner circle `10dip` by default
- `controlColor` - color of the outer circle in normal state, by default `android:colorControlNormal`
- `controlColorActivated` - color of outer circle when focused, by default `android:colorControlHighlighted`
- `digitColorFilled` - fill color of the inner circle, by default `android:colorPrimary`
- `digitColorBorder` - border color of the inner circle, by default `android:colorPrimaryDark`
## Demo

## License
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.
================================================
FILE: app/.gitignore
================================================
/build
================================================
FILE: app/build.gradle
================================================
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.3"
defaultConfig {
applicationId "com.mhk.android.passcodeviewsample"
minSdkVersion 10
targetSdkVersion 25
versionCode 2
versionName "1.0.1"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.3.1'
compile project(':passcodeview')
}
================================================
FILE: app/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Volumes/Work/SDK/Android/Development/adt bundle/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: app/src/androidTest/java/com/mhk/android/passcodeviewsample/ApplicationTest.java
================================================
package com.mhk.android.passcodeviewsample;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* Testing Fundamentals
*/
public class ApplicationTest extends ApplicationTestCase {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: app/src/main/AndroidManifest.xml
================================================
================================================
FILE: app/src/main/java/com/mhk/android/passcodeviewsample/SampleActivity.java
================================================
package com.mhk.android.passcodeviewsample;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;
import online.devliving.passcodeview.PasscodeView;
/**
* Created by Mehedi Hasan Khan on 9/6/15.
*/
public class SampleActivity extends AppCompatActivity{
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.sample_activity);
final PasscodeView pcView = (PasscodeView) findViewById(R.id.passcode_view);
pcView.postDelayed(new Runnable() {
@Override
public void run() {
pcView.requestToShowKeyboard();
}
}, 400);
pcView.setPasscodeEntryListener(new PasscodeView.PasscodeEntryListener() {
@Override
public void onPasscodeEntered(String passcode) {
Toast.makeText(SampleActivity.this, "Passcode entered: " + passcode, Toast.LENGTH_SHORT).show();
}
});
Log.d("SAMPLE", "Activity created");
}
}
================================================
FILE: app/src/main/res/layout/sample_activity.xml
================================================
================================================
FILE: app/src/main/res/values/dimens.xml
================================================
16dp16dp
================================================
FILE: app/src/main/res/values/strings.xml
================================================
PasscodeView Sample
================================================
FILE: app/src/main/res/values/styles.xml
================================================
================================================
FILE: build.gradle
================================================
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.3.0'
classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1'
classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
jcenter()
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
================================================
FILE: passcodeview/.gitignore
================================================
/build
================================================
FILE: passcodeview/build.gradle
================================================
apply plugin: 'com.android.library'
apply plugin: 'com.github.dcendents.android-maven'
apply plugin: "com.jfrog.bintray"
version = "1.0.3"
android {
compileSdkVersion 25
buildToolsVersion "25.0.3"
defaultConfig {
minSdkVersion 10
targetSdkVersion 25
versionCode 4
versionName version
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com.android.support:appcompat-v7:25.3.1'
}
def siteUrl = "https://github.com/iamMehedi/PasscodeView"
def gitUrl = "https://github.com/iamMehedi/PasscodeView.git"
group = "online.devliving"
install {
repositories.mavenInstaller {
// This generates POM.xml with proper parameters
pom {
project {
packaging 'aar'
// Add your description here
name 'online.devliving:passcodeview'
description = 'An android widget to input passcode.'
url siteUrl
// Set your license
licenses {
license {
name 'The Apache Software License, Version 2.0'
url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
}
}
developers {
developer {
id 'im_mehedi'
name 'Mehedi Hasan Khan'
email 'mehedi.mailing@gmail.com'
}
}
scm {
connection gitUrl
developerConnection gitUrl
url siteUrl
}
}
}
}
}
task sourcesJar(type: Jar) {
from android.sourceSets.main.java.srcDirs
classifier = 'sources'
}
task javadoc(type: Javadoc) {
source = android.sourceSets.main.java.srcDirs
classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
failOnError false
}
task javadocJar(type: Jar, dependsOn: javadoc) {
classifier = 'javadoc'
from javadoc.destinationDir
}
artifacts {
archives javadocJar
archives sourcesJar
}
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
// https://github.com/bintray/gradle-bintray-plugin
bintray {
user = properties.getProperty("bintray.user")
key = properties.getProperty("bintray.apikey")
configurations = ['archives']
pkg {
repo = "Maven"
// it is the name that appears in bintray when logged
name = "online.devliving:passcodeview"
websiteUrl = siteUrl
vcsUrl = gitUrl
licenses = ["Apache-2.0"]
publish = true
version {
gpg {
sign = true //Determines whether to GPG sign the files. The default is false
passphrase = properties.getProperty("bintray.gpg.password")
//Optional. The passphrase for GPG signing'
}
}
}
}
//apply from: 'https://raw.github.com/chrisbanes/gradle-mvn-push/master/gradle-mvn-push.gradle'
================================================
FILE: passcodeview/proguard-rules.pro
================================================
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Volumes/Work/SDK/Android/Development/adt bundle/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
================================================
FILE: passcodeview/src/androidTest/java/online/devliving/passcodeview/ApplicationTest.java
================================================
package online.devliving.passcodeview;
import android.app.Application;
import android.test.ApplicationTestCase;
/**
* Testing Fundamentals
*/
public class ApplicationTest extends ApplicationTestCase {
public ApplicationTest() {
super(Application.class);
}
}
================================================
FILE: passcodeview/src/main/AndroidManifest.xml
================================================
================================================
FILE: passcodeview/src/main/java/online/devliving/passcodeview/PasscodeView.java
================================================
package online.devliving.passcodeview;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
import android.text.TextWatcher;
import android.text.method.DigitsKeyListener;
import android.util.AttributeSet;
import android.util.DisplayMetrics;
import android.util.TypedValue;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
/**
* Created by Mehedi Hasan Khan (mehedi.mailing@gmail.com).
*/
public class PasscodeView extends ViewGroup{
EditText mEditText;
int mDigitCount;
private int mDigitWidth;
private int mDigitRadius;
private int mOuterStrokeWidth;
private int mInnerStrokeWidth;
private int mDigitInnerRadius;
private int mDigitSpacing;
private int mDigitElevation;
private int mControlColor;
private int mHighlightedColor;
private int mInnerColor;
private int mInnerBorderColor;
private OnFocusChangeListener mOnFocusChangeListener;
private PasscodeEntryListener mPasscodeEntryListener;
public PasscodeView(Context context) {
this(context, null);
}
public PasscodeView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public PasscodeView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
// Get style information
TypedArray array = getContext().obtainStyledAttributes(attrs, R.styleable.PasscodeView);
mDigitCount = array.getInt(R.styleable.PasscodeView_numDigits, 4);
// Dimensions
DisplayMetrics metrics = getResources().getDisplayMetrics();
mDigitRadius = array.getDimensionPixelSize(R.styleable.PasscodeView_digitRadius,
(int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, metrics));
mOuterStrokeWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 2, metrics);
mInnerStrokeWidth = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 1, metrics);
mDigitInnerRadius = array.getDimensionPixelSize(R.styleable.PasscodeView_digitInnerRadius,
mDigitRadius - ((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6, metrics)));
if(mDigitInnerRadius > mDigitRadius){
mDigitInnerRadius = mDigitRadius - ((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6, metrics));
}
mDigitWidth = (mDigitRadius + mOuterStrokeWidth) * 2;
mDigitSpacing = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16, metrics);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
mDigitElevation = array.getDimensionPixelSize(R.styleable.PasscodeView_digitElevation, 0);
}
// Get theme to resolve defaults
Resources.Theme theme = getContext().getTheme();
mControlColor = Color.DKGRAY;
// Text colour, default to android:colorControlNormal from theme
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
TypedValue controlColor = new TypedValue();
theme.resolveAttribute(android.R.attr.colorControlNormal, controlColor, true);
mControlColor = controlColor.resourceId > 0 ? getResources().getColor(controlColor.resourceId) :
controlColor.data;
}
mControlColor = array.getColor(R.styleable.PasscodeView_controlColor, mControlColor);
// Accent colour, default to android:colorAccent from theme
mHighlightedColor = Color.LTGRAY;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
TypedValue accentColor = new TypedValue();
theme.resolveAttribute(R.attr.colorControlHighlight, accentColor, true);
mHighlightedColor = accentColor.resourceId > 0 ? getResources().getColor(accentColor.resourceId) :
accentColor.data;
}
mHighlightedColor = array.getColor(R.styleable.PasscodeView_controlColorActivated, mHighlightedColor);
//color for the inner circle
mInnerColor = Color.CYAN;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
TypedValue innerColor = new TypedValue();
theme.resolveAttribute(android.R.attr.colorPrimary, innerColor, true);
mInnerColor = innerColor.resourceId > 0 ? getResources().getColor(innerColor.resourceId) :
innerColor.data;
}
mInnerColor = array.getColor(R.styleable.PasscodeView_digitColorFilled, mInnerColor);
//color for the inner circle border
mInnerBorderColor = Color.GREEN;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
TypedValue innerBorderColor = new TypedValue();
theme.resolveAttribute(android.R.attr.colorPrimaryDark, innerBorderColor, true);
mInnerBorderColor = innerBorderColor.resourceId > 0 ? getResources().getColor(innerBorderColor.resourceId) :
innerBorderColor.data;
}
mInnerBorderColor = array.getColor(R.styleable.PasscodeView_digitColorBorder, mInnerBorderColor);
// Recycle the typed array
array.recycle();
// Add child views
setupViews();
}
@Override
public boolean shouldDelayChildPressedState() {
return false;
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Measure children
for (int i = 0; i < getChildCount(); i ++) {
getChildAt(i).measure(widthMeasureSpec, heightMeasureSpec);
}
// Calculate the size of the view
int width = (mDigitWidth * mDigitCount) + (mDigitSpacing * (mDigitCount - 1));
setMeasuredDimension(
width + getPaddingLeft() + getPaddingRight() + (mDigitElevation * 2),
mDigitWidth + getPaddingTop() + getPaddingBottom() + (mDigitElevation * 2));
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
// Position the child views
for (int i = 0; i < mDigitCount; i++) {
View child = getChildAt(i);
int left = i * mDigitWidth + (i > 0 ? i * mDigitSpacing : 0);
child.layout(
left + getPaddingLeft() + mDigitElevation,
getPaddingTop() + (mDigitElevation / 2),
left + getPaddingLeft() + mDigitElevation + mDigitWidth,
getPaddingTop() + (mDigitElevation / 2) + mDigitWidth);
}
// Add the edit text as a 1px wide view to allow it to focus
getChildAt(mDigitCount).layout(0, 0, 1, getMeasuredHeight());
}
private void setupViews(){
setWillNotDraw(false);
// Add a digit view for each digit
for (int i = 0; i < mDigitCount; i++) {
DigitView digitView = new DigitView(getContext(), i);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
digitView.setElevation(mDigitElevation);
}
addView(digitView);
}
// Add an "invisible" edit text to handle input
mEditText = new EditText(getContext());
mEditText.setBackgroundColor(getResources().getColor(android.R.color.transparent));
mEditText.setTextColor(getResources().getColor(android.R.color.transparent));
mEditText.setCursorVisible(false);
mEditText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(mDigitCount)});
mEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
mEditText.setKeyListener(DigitsKeyListener.getInstance("1234567890"));
mEditText.setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
mEditText.setOnFocusChangeListener(new OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
// Update the selected state of the views
int length = mEditText.getText().length();
updateChilViewSelectionStates(length, hasFocus);
// Make sure the cursor is at the end
mEditText.setSelection(length);
// Provide focus change events to any listener
if (mOnFocusChangeListener != null) {
mOnFocusChangeListener.onFocusChange(PasscodeView.this, hasFocus);
}
}
});
mEditText.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
@Override
public void afterTextChanged(Editable s) {
int length = s.length();
updateChilViewSelectionStates(length, mEditText.hasFocus());
if (length == mDigitCount && mPasscodeEntryListener != null) {
mPasscodeEntryListener.onPasscodeEntered(s.toString());
}
}
});
addView(mEditText);
invalidate();
}
private void updateChilViewSelectionStates(int length, boolean hasFocus){
for (int i = 0; i < mDigitCount; i++) {
getChildAt(i).setSelected(hasFocus && i == length);
}
}
/**
* Get the {@link Editable} from the EditText
*
* @return
*/
public Editable getText() {
return mEditText.getText();
}
/**
* Set text to the EditText
*
* @param text
*/
public void setText(CharSequence text) {
if (text.length() > mDigitCount) {
text = text.subSequence(0, mDigitCount);
}
mEditText.setText(text);
invalidateChildViews();
}
/**
* Clear passcode input
*/
public void clearText() {
mEditText.setText("");
invalidateChildViews();
}
private void invalidateChildViews(){
for(int i =0; i CREATOR =
new Parcelable.Creator() {
@Override
public SavedState createFromParcel(Parcel in) {
return new SavedState(in);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
};
String editTextValue;
public SavedState(Parcelable superState) {
super(superState);
}
private SavedState(Parcel source) {
super(source);
editTextValue = source.readString();
}
@Override
public void writeToParcel(Parcel dest, int flags) {
super.writeToParcel(dest, flags);
dest.writeString(editTextValue);
}
}
class DigitView extends View{
private Paint mOuterPaint, mInnerPaint;
private int mPosition = 0;
public DigitView(Context context, int position)
{
this(context);
mPosition = position;
}
public DigitView(Context context) {
this(context, null);
}
public DigitView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public DigitView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
void init(){
setWillNotDraw(false);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
setLayerType(LAYER_TYPE_SOFTWARE, null);
}
mOuterPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mOuterPaint.setAlpha(255);
mOuterPaint.setDither(true);
mOuterPaint.setStyle(Paint.Style.STROKE);
mOuterPaint.setStrokeWidth(mOuterStrokeWidth);
mOuterPaint.setStrokeCap(Paint.Cap.ROUND);
mOuterPaint.setStrokeJoin(Paint.Join.ROUND);
mOuterPaint.setShadowLayer(2, 0, 0, Color.parseColor("#B4999999"));
mInnerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
mInnerPaint.setAlpha(255);
mInnerPaint.setStyle(Paint.Style.FILL_AND_STROKE);
mInnerPaint.setStrokeWidth(mInnerStrokeWidth);
mInnerPaint.setStrokeCap(Paint.Cap.ROUND);
mInnerPaint.setStrokeJoin(Paint.Join.ROUND);
mInnerPaint.setColor(mInnerColor);
invalidate();
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(mDigitWidth, mDigitWidth);
}
@Override
protected void onDraw(Canvas canvas) {
float center = getWidth()/2;
if(isSelected())
{
mOuterPaint.setColor(mHighlightedColor);
}
else
{
mOuterPaint.setColor(mControlColor);
}
canvas.drawColor(Color.TRANSPARENT);
canvas.drawCircle(center, center, mDigitRadius, mOuterPaint);
if(mEditText.getText().length() > mPosition)
{
canvas.drawCircle(center, center, mDigitInnerRadius, mInnerPaint);
}
}
}
/**
* Listener that gets notified when the complete passcode has been entered
*/
public interface PasscodeEntryListener{
/**
* Called when all the digits of the passcode has been entered
* @param passcode - The entered passcode
*/
void onPasscodeEntered(String passcode);
}
}
================================================
FILE: passcodeview/src/main/res/values/attrs.xml
================================================
================================================
FILE: settings.gradle
================================================
include ':app', ':passcodeview'