|
| 1 | +package com.broooapps.otpedittext2; |
| 2 | + |
| 3 | +/** |
| 4 | + * Created by Swapnil Tiwari on 2019-05-07. |
| 5 | + * swapnil.tiwari@box8.in |
| 6 | + */ |
| 7 | + |
| 8 | +import android.content.Context; |
| 9 | +import android.content.res.TypedArray; |
| 10 | +import android.graphics.Canvas; |
| 11 | +import android.graphics.Color; |
| 12 | +import android.graphics.Paint; |
| 13 | +import android.support.v4.content.ContextCompat; |
| 14 | +import android.support.v7.widget.AppCompatEditText; |
| 15 | +import android.text.Editable; |
| 16 | +import android.text.TextPaint; |
| 17 | +import android.util.AttributeSet; |
| 18 | +import android.view.ActionMode; |
| 19 | +import android.view.Menu; |
| 20 | +import android.view.MenuItem; |
| 21 | +import android.view.View; |
| 22 | + |
| 23 | + |
| 24 | +public class OtpEditText extends AppCompatEditText { |
| 25 | + public static final String XML_NAMESPACE_ANDROID = "http://schemas.android.com/apk/res/android"; |
| 26 | + private int defStyleAttr = 0; |
| 27 | + |
| 28 | + private float mSpace = 8; //24 dp by default, space between the lines |
| 29 | + private float mCharSize; |
| 30 | + private float mNumChars = 6; |
| 31 | + private float mLineSpacing = 10; //8dp by default, height of the text from our lines |
| 32 | + private int mMaxLength = 6; |
| 33 | + |
| 34 | + private OnClickListener mClickListener; |
| 35 | + |
| 36 | + private float mLineStroke = 1; //1dp by default |
| 37 | + private float mLineStrokeSelected = 2; //2dp by default |
| 38 | + private Paint mLinesPaint; |
| 39 | + |
| 40 | + private int mMainColor; |
| 41 | + private int mSecondaryColor; |
| 42 | + private int mTextColor; |
| 43 | + |
| 44 | + private Paint mStrokePaint; |
| 45 | + |
| 46 | + public OtpEditText(Context context) { |
| 47 | + super(context); |
| 48 | + } |
| 49 | + |
| 50 | + public OtpEditText(Context context, AttributeSet attrs) { |
| 51 | + super(context, attrs); |
| 52 | + init(context, attrs); |
| 53 | + } |
| 54 | + |
| 55 | + public OtpEditText(Context context, AttributeSet attrs, int defStyleAttr) { |
| 56 | + super(context, attrs, defStyleAttr); |
| 57 | + this.defStyleAttr = defStyleAttr; |
| 58 | + init(context, attrs); |
| 59 | + } |
| 60 | + |
| 61 | + private void init(Context context, AttributeSet attrs) { |
| 62 | + |
| 63 | + getAttrsFromTypedArray(attrs); |
| 64 | + |
| 65 | + float multi = context.getResources().getDisplayMetrics().density; |
| 66 | + mLineStroke = multi * mLineStroke; |
| 67 | + mLineStrokeSelected = multi * mLineStrokeSelected; |
| 68 | + mLinesPaint = new Paint(getPaint()); |
| 69 | + mStrokePaint = new Paint(getPaint()); |
| 70 | + mStrokePaint.setStrokeWidth(4); |
| 71 | + mStrokePaint.setStyle(Paint.Style.STROKE); |
| 72 | + mLinesPaint.setStrokeWidth(mLineStroke); |
| 73 | + setBackgroundResource(0); |
| 74 | + mSpace = multi * mSpace; //convert to pixels for our density |
| 75 | + mNumChars = mMaxLength; |
| 76 | + |
| 77 | + //Disable copy paste |
| 78 | + super.setCustomSelectionActionModeCallback(new ActionMode.Callback() { |
| 79 | + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { |
| 80 | + return false; |
| 81 | + } |
| 82 | + |
| 83 | + public void onDestroyActionMode(ActionMode mode) { |
| 84 | + } |
| 85 | + |
| 86 | + public boolean onCreateActionMode(ActionMode mode, Menu menu) { |
| 87 | + return false; |
| 88 | + } |
| 89 | + |
| 90 | + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { |
| 91 | + return false; |
| 92 | + } |
| 93 | + }); |
| 94 | + // When tapped, move cursor to end of text. |
| 95 | + super.setOnClickListener(new OnClickListener() { |
| 96 | + @Override |
| 97 | + public void onClick(View v) { |
| 98 | + setSelection(getText().length()); |
| 99 | + if (mClickListener != null) { |
| 100 | + mClickListener.onClick(v); |
| 101 | + } |
| 102 | + } |
| 103 | + }); |
| 104 | + |
| 105 | + } |
| 106 | + |
| 107 | + private void getAttrsFromTypedArray(AttributeSet attributeSet) { |
| 108 | + final TypedArray a = getContext().obtainStyledAttributes(attributeSet, R.styleable.OtpEditText, defStyleAttr, 0); |
| 109 | + |
| 110 | + mMaxLength = attributeSet.getAttributeIntValue(XML_NAMESPACE_ANDROID, "maxLength", 4); |
| 111 | + mMainColor = a.getColor(R.styleable.OtpEditText_oev_primary_color, getResources().getColor(android.R.color.holo_red_dark)); |
| 112 | + mSecondaryColor = a.getColor(R.styleable.OtpEditText_oev_secondary_color, getResources().getColor(R.color.light_gray)); |
| 113 | + mTextColor = a.getColor(R.styleable.OtpEditText_oev_text_color, getResources().getColor(android.R.color.black)); |
| 114 | + |
| 115 | + a.recycle(); |
| 116 | + } |
| 117 | + |
| 118 | + @Override |
| 119 | + public void setOnClickListener(OnClickListener l) { |
| 120 | + mClickListener = l; |
| 121 | + } |
| 122 | + |
| 123 | + @Override |
| 124 | + public void setCustomSelectionActionModeCallback(ActionMode.Callback actionModeCallback) { |
| 125 | + throw new RuntimeException("setCustomSelectionActionModeCallback() not supported."); |
| 126 | + } |
| 127 | + |
| 128 | + @Override |
| 129 | + protected void onDraw(Canvas canvas) { |
| 130 | + int availableWidth = getWidth() - getPaddingRight() - getPaddingLeft(); |
| 131 | + if (mSpace < 0) { |
| 132 | + mCharSize = (availableWidth / (mNumChars * 2 - 1)); |
| 133 | + } else { |
| 134 | + mCharSize = (availableWidth - (mSpace * (mNumChars - 1))) / mNumChars; |
| 135 | + } |
| 136 | + |
| 137 | + mLineSpacing = (float) (getHeight() * .7); |
| 138 | + |
| 139 | + int startX = getPaddingLeft(); |
| 140 | + int bottom = getHeight() - getPaddingBottom(); |
| 141 | + int top = getPaddingTop(); |
| 142 | + |
| 143 | + //Text Width |
| 144 | + Editable text = getText(); |
| 145 | + int textLength = text.length(); |
| 146 | + float[] textWidths = new float[textLength]; |
| 147 | + getPaint().getTextWidths(getText(), 0, textLength, textWidths); |
| 148 | + |
| 149 | + for (int i = 0; i < mNumChars; i++) { |
| 150 | + updateColorForLines(i <= textLength, i == textLength, getText().length(), (int) mNumChars); |
| 151 | + canvas.drawLine(startX, bottom, startX + mCharSize, bottom, mLinesPaint); |
| 152 | + |
| 153 | + try { |
| 154 | + canvas.drawRoundRect(startX, top, startX + mCharSize, bottom, 8, 8, mLinesPaint); |
| 155 | + canvas.drawRoundRect(startX, top, startX + mCharSize, bottom, 8, 8, mStrokePaint); |
| 156 | + } catch (NoSuchMethodError err) { |
| 157 | + canvas.drawRect(startX, top, startX + mCharSize, bottom, mLinesPaint); |
| 158 | + canvas.drawRect(startX, top, startX + mCharSize, bottom, mStrokePaint); |
| 159 | + } |
| 160 | + if (getText().length() > i) { |
| 161 | + float middle = startX + mCharSize / 2; |
| 162 | + canvas.drawText(text, i, i + 1, middle - textWidths[0] / 2, mLineSpacing, getPaint()); |
| 163 | + } |
| 164 | + |
| 165 | + if (mSpace < 0) { |
| 166 | + startX += mCharSize * 2; |
| 167 | + } else { |
| 168 | + startX += mCharSize + mSpace; |
| 169 | + } |
| 170 | + } |
| 171 | + } |
| 172 | + |
| 173 | + /** |
| 174 | + * @param next Is the current char the next character to be input? |
| 175 | + */ |
| 176 | + private void updateColorForLines(boolean next, boolean current, int textSize, int totalSize) { |
| 177 | + if (next) { |
| 178 | + mStrokePaint.setColor(mSecondaryColor); |
| 179 | + mLinesPaint.setColor(mSecondaryColor); |
| 180 | + } else { |
| 181 | + mStrokePaint.setColor(mSecondaryColor); |
| 182 | + mLinesPaint.setColor(ContextCompat.getColor(getContext(), android.R.color.white)); |
| 183 | + } |
| 184 | + if (current) { |
| 185 | + mLinesPaint.setColor(ContextCompat.getColor(getContext(), android.R.color.white)); |
| 186 | + mStrokePaint.setColor(mMainColor); |
| 187 | + } |
| 188 | + } |
| 189 | +} |
0 commit comments