Repository: iamMehedi/PasscodeView Branch: master Commit: ef2d27200a5e Files: 27 Total size: 34.7 KB Directory structure: gitextract_ztzyo5fi/ ├── .gitignore ├── .idea/ │ ├── compiler.xml │ ├── copyright/ │ │ └── profiles_settings.xml │ ├── misc.xml │ ├── modules.xml │ ├── runConfigurations.xml │ └── vcs.xml ├── README.md ├── app/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── com/ │ │ └── mhk/ │ │ └── android/ │ │ └── passcodeviewsample/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── com/ │ │ └── mhk/ │ │ └── android/ │ │ └── passcodeviewsample/ │ │ └── SampleActivity.java │ └── res/ │ ├── layout/ │ │ └── sample_activity.xml │ └── values/ │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── passcodeview/ │ ├── .gitignore │ ├── build.gradle │ ├── proguard-rules.pro │ └── src/ │ ├── androidTest/ │ │ └── java/ │ │ └── online/ │ │ └── devliving/ │ │ └── passcodeview/ │ │ └── ApplicationTest.java │ └── main/ │ ├── AndroidManifest.xml │ ├── java/ │ │ └── online/ │ │ └── devliving/ │ │ └── passcodeview/ │ │ └── PasscodeView.java │ └── res/ │ └── values/ │ └── attrs.xml └── settings.gradle ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Created by .ignore support plugin (hsz.mobi) ### Android template # Built application files *.apk *.ap_ # Files for the ART/Dalvik VM *.dex # Java class files *.class # Generated files bin/ gen/ out/ # Gradle files .gradle/ build/ # Local configuration file (sdk path, etc) local.properties # Proguard folder generated by Eclipse proguard/ # Log Files *.log # Android Studio Navigation editor temp files .navigation/ # Android Studio captures folder captures/ # Intellij *.iml .idea/workspace.xml .idea/tasks.xml .idea/gradle.xml .idea/dictionaries .idea/libraries # Keystore files *.jks # External native build folder generated in Android Studio 2.2 and later .externalNativeBuild # Google Services (e.g. APIs or Firebase) google-services.json # Freeline freeline.py freeline/ freeline_project_description.json ================================================ FILE: .idea/compiler.xml ================================================ ================================================ FILE: .idea/copyright/profiles_settings.xml ================================================ ================================================ FILE: .idea/misc.xml ================================================ Android API 22 Platform ================================================ FILE: .idea/modules.xml ================================================ ================================================ FILE: .idea/runConfigurations.xml ================================================ ================================================ FILE: .idea/vcs.xml ================================================ ================================================ FILE: README.md ================================================ # PasscodeView [![License](https://img.shields.io/badge/license-Apache%202-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0) [ ![Download](https://api.bintray.com/packages/iammehedi/Maven/online.devliving%3Apasscodeview/images/download.svg) ](https://bintray.com/iammehedi/Maven/online.devliving%3Apasscodeview/_latestVersion) An android widget to input passcode. ## Setup ### Maven ```xml online.devliving passcodeview 1.0.3 pom ``` ### 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 ![image](demo.gif) ## 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 ================================================ 16dp 16dp ================================================ 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'