glomadrian / material-code-input

A material style input for codes
965 stars 156 forks source link

InputType is not managed #3

Open Shusshu opened 9 years ago

Shusshu commented 9 years ago

Cannot choose which keyboard to show

Shusshu commented 9 years ago

Maybe extends EditText instead of View to get all the benefit from EditText

SimonMarquis commented 9 years ago

An easy solution would be to override the onCreateInputConnection() method from View in theCodeInput :

  @Override
  public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
    InputConnection connection =  super.onCreateInputConnection(outAttrs);
    outAttrs.inputType |= InputType.TYPE_CLASS_NUMBER;
    return connection;
  }

But then, depending of the keyboard, user can switch to letters...

Otherwise, it could be done inside the onKeyUp() by filtering the characters (currently A-Z0-9)

Shusshu commented 9 years ago

imeOptions could also be supported that way... But I think it's you get more benefit by extending EditText :)

SimonMarquis commented 9 years ago

I think not extending EditText was done on purpose (to avoid handling the cursor and the whole touch input thing).

Shusshu commented 9 years ago

Yes I know it was on purpose but you then lose a lot of features. I'm currently changing it to EditText and true there are some annoying things but for the moment everything is looking good

Remove cursor: setCursorVisible(false)

Sushil21 commented 9 years ago

Hi Shusshu,

can you please tell me how we can extend to EditText.

Shusshu commented 9 years ago

It's not 100% perfect and was done very quickly:

import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.widget.EditText;

import com.github.glomadrian.codeinputlib.data.FixedStack;
import com.github.glomadrian.codeinputlib.model.Underline;

import java.util.regex.Pattern;

/**
 * @author Adrián García Lomas
 */
public class CodeInput2 extends EditText {

    private static final int DEFAULT_CODES = 6;
    private static final Pattern KEYCODE_PATTERN = Pattern.compile("KEYCODE_(\\w)");
    private FixedStack<Character> characters;
    private Underline underlines[];
    private Paint underlinePaint;
    private Paint underlineSelectedPaint;
    private Paint textPaint;
    private Paint hintPaint;
    private ValueAnimator reductionAnimator;
    private ValueAnimator hintYAnimator;
    private ValueAnimator hintSizeAnimator;
    private float underlineReduction;
    private float underlineStrokeWidth;
    private float underlineWidth;
    private float reduction;
    private float textSize;
    private float textMarginBottom;
    private float hintX;
    private float hintNormalSize;
    private float hintSmallSize;
    private float hintMarginBottom;
    private float hintActualMarginBottom;
    private float viewHeight;
    private long animationDuration;
    private int height;
    private int underlineAmount;
    private int underlineColor;
    private int underlineSelectedColor;
    private int hintColor;
    private int textColor;
    private boolean underlined = true;
    private String hintText;

    public CodeInput2(Context context) {
        super(context);
        init(null);
    }

    public CodeInput2(Context context, AttributeSet attributeset) {
        super(context, attributeset);
        init(attributeset);
    }

    public CodeInput2(Context context, AttributeSet attributeset, int defStyledAttrs) {
        super(context, attributeset, defStyledAttrs);
        init(attributeset);
    }

    private void init(AttributeSet attributeset) {
        initDefaultAttributes();
        initCustomAttributes(attributeset);
        initDataStructures();
        initPaint();
        initAnimator();
        initViewOptions();
        setCursorVisible(false);
    }

    private void initDefaultAttributes() {
        underlineStrokeWidth = getContext().getResources().getDimension(R.dimen.underline_stroke_width);
        underlineWidth = getContext().getResources().getDimension(R.dimen.underline_width);
        underlineReduction = getContext().getResources().getDimension(R.dimen.section_reduction);
        textSize = getContext().getResources().getDimension(R.dimen.text_size);
        textMarginBottom = getContext().getResources().getDimension(R.dimen.text_margin_bottom);
        underlineColor = getContext().getResources().getColor(R.color.underline_default_color);
        underlineSelectedColor = getContext().getResources().getColor(R.color.underline_selected_color);
        hintColor = getContext().getResources().getColor(R.color.hintColor);
        textColor = getContext().getResources().getColor(R.color.textColor);
        hintMarginBottom = getContext().getResources().getDimension(R.dimen.hint_margin_bottom);
        hintNormalSize = getContext().getResources().getDimension(R.dimen.hint_size);
        hintSmallSize = getContext().getResources().getDimension(R.dimen.hint_small_size);
        animationDuration = getContext().getResources().getInteger(R.integer.animation_duration);
        viewHeight = getContext().getResources().getDimension(R.dimen.view_height);
        hintX = 0;
        hintActualMarginBottom = 0;
        underlineAmount = DEFAULT_CODES;
        reduction = 0.0F;
    }

    private void initCustomAttributes(AttributeSet attributeset) {
        TypedArray attributes =
                getContext().obtainStyledAttributes(attributeset, R.styleable.core_area);

        underlineColor = attributes.getColor(R.styleable.core_area_underline_color, underlineColor);
        underlineSelectedColor =
                attributes.getColor(R.styleable.core_area_underline_selected_color, underlineSelectedColor);
        hintColor = attributes.getColor(R.styleable.core_area_underline_color, hintColor);
        hintText = attributes.getString(R.styleable.core_area_hint_text);
        underlineAmount = attributes.getInt(R.styleable.core_area_codes, underlineAmount);
        textColor = attributes.getInt(R.styleable.core_area_text_color, textColor);

        attributes.recycle();
    }

    private void initDataStructures() {
        underlines = new Underline[underlineAmount];
        characters = new FixedStack();
        characters.setMaxSize(underlineAmount);
    }

    private void initPaint() {
        underlinePaint = new Paint();
        underlinePaint.setColor(underlineColor);
        underlinePaint.setStrokeWidth(underlineStrokeWidth);
        underlinePaint.setStyle(android.graphics.Paint.Style.STROKE);
        underlineSelectedPaint = new Paint();
        underlineSelectedPaint.setColor(underlineSelectedColor);
        underlineSelectedPaint.setStrokeWidth(underlineStrokeWidth);
        underlineSelectedPaint.setStyle(android.graphics.Paint.Style.STROKE);
        textPaint = new Paint();
        textPaint.setTextSize(textSize);
        textPaint.setColor(textColor);
        textPaint.setAntiAlias(true);
        textPaint.setTextAlign(Paint.Align.CENTER);
        hintPaint = new Paint();
        hintPaint = new Paint();
        hintPaint.setTextSize(hintNormalSize);
        hintPaint.setAntiAlias(true);
        hintPaint.setColor(underlineColor);
    }

    private void initAnimator() {
        reductionAnimator = ValueAnimator.ofFloat(0, underlineReduction);
        reductionAnimator.setDuration(animationDuration);
        reductionAnimator.addUpdateListener(new ReductionAnimatorListener());
        reductionAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        hintSizeAnimator = ValueAnimator.ofFloat(hintNormalSize, hintSmallSize);
        hintSizeAnimator.setDuration(animationDuration);
        hintSizeAnimator.addUpdateListener(new HintSizeAnimatorListener());
        hintSizeAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
        hintYAnimator = ValueAnimator.ofFloat(0, hintMarginBottom);
        hintYAnimator.setDuration(animationDuration);
        hintYAnimator.addUpdateListener(new HintYAnimatorListener());
        hintYAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
    }

    private void initViewOptions() {
        setFocusable(true);
        setFocusableInTouchMode(true);
    }

    @Override
    protected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
        super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
        if (!gainFocus && characters.size() == 0) {
            reverseAnimation();
        }
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, (int) viewHeight, oldw, oldh);
        height = h;
        initUnderline();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        setMeasuredDimension(getMeasuredWidth(), (int) viewHeight);
    }

    private void initUnderline() {
        for (int i = 0; i < underlineAmount; i++) {
            underlines[i] = createPath(i, underlineWidth);
        }
    }

    private Underline createPath(int position, float sectionWidth) {
        float fromX = sectionWidth * (float) position;
        return new Underline(fromX, height, fromX + sectionWidth, height);
    }

    private void startAnimation() {
        reductionAnimator.start();
        hintSizeAnimator.start();
        hintYAnimator.start();
        underlined = false;
    }

    private void reverseAnimation() {
        reductionAnimator.reverse();
        hintSizeAnimator.reverse();
        hintYAnimator.reverse();
        underlined = true;
    }

//    /**
//     * Detects the del key and delete the numbers
//     */
//    @Override
//    public boolean onKeyDown(int keyCode, KeyEvent keyevent) {
//        if (keyCode == KeyEvent.KEYCODE_DEL && characters.size() != 0) {
//            characters.pop();
//        }
//        return super.onKeyDown(keyCode, keyevent);
//    }
//
//    /**
//     * Capture the keyboard events but only if are A-Z 0-9
//     */
//    @Override
//    public boolean onKeyUp(int keyCode, KeyEvent keyevent) {
//        String text = KeyEvent.keyCodeToString(keyCode);
//        Matcher matcher = KEYCODE_PATTERN.matcher(text);
//        if (matcher.matches()) {
//            char character = matcher.group(1).charAt(0);
//            characters.push(character);
//            return true;
//        } else {
//            return false;
//        }
//    }

    @Override
    protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
        if (characters != null) {
            if (underlined) {
                startAnimation();
            }
            characters.clear();
            for (int i = 0; i < text.length(); i++) {
                char character = text.charAt(i);
                characters.push(character);
            }
            invalidate();
        }
    }

    /**
     * When a touch is detected the view need to focus and animate if is necessary
     */
    @Override
    public boolean onTouchEvent(MotionEvent motionevent) {
        if (motionevent.getAction() == 0) {
//            requestFocus();
            if (underlined) {
                startAnimation();
            }
        }
        return super.onTouchEvent(motionevent);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        for (int i = 0; i < underlines.length; i++) {
            Underline sectionpath = underlines[i];
            float fromX = sectionpath.getFromX() + reduction;
            float fromY = sectionpath.getFromY();
            float toX = sectionpath.getToX() - reduction;
            float toY = sectionpath.getToY();
            drawSection(i, fromX, fromY, toX, toY, canvas);
            if (characters.toArray().length > i && characters.size() != 0) {
                drawCharacter(fromX, toX, characters.get(i), canvas);
            }
        }
        if (hintText != null) {
            drawHint(canvas);
        }

    }

    private void drawSection(int position, float fromX, float fromY, float toX, float toY,
                             Canvas canvas) {
        Paint paint = underlinePaint;
        if (position == characters.size() && !underlined) {
            paint = underlineSelectedPaint;
        }
        canvas.drawLine(fromX, fromY, toX, toY, paint);
    }

    private void drawCharacter(float fromX, float toX, Character character, Canvas canvas) {
        float actualWidth = toX - fromX;
        float centerWidth = actualWidth / 2;
        float centerX = fromX + centerWidth;
        canvas.drawText(character.toString(), centerX, height - textMarginBottom, textPaint);
    }

    private void drawHint(Canvas canvas) {
        canvas.drawText(hintText, hintX, height - textMarginBottom - hintActualMarginBottom, hintPaint);
    }

    public Character[] getCode() {
        return characters.toArray(new Character[underlineAmount]);
    }

    /**
     * Listener to update the reduction of the underline bars
     */
    private class ReductionAnimatorListener implements ValueAnimator.AnimatorUpdateListener {

        public void onAnimationUpdate(ValueAnimator valueanimator) {
            float value = ((Float) valueanimator.getAnimatedValue()).floatValue();
            reduction = value;
            invalidate();
        }
    }

    /**
     * Listener to update the hint y values
     */
    private class HintYAnimatorListener implements ValueAnimator.AnimatorUpdateListener {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            hintActualMarginBottom = (float) animation.getAnimatedValue();
            invalidate();
        }
    }

    /**
     * Listener to update the size of the hint text
     */
    private class HintSizeAnimatorListener implements ValueAnimator.AnimatorUpdateListener {

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            float size = (float) animation.getAnimatedValue();
            hintPaint.setTextSize(size);
            invalidate();
        }
    }
}
Sushil21 commented 9 years ago

thanks its works for me .you have saved my time.

Shusshu commented 8 years ago

@Sushil21 Copy paste does not work with that class. if you get it working properly let me know ;)

Sushil21 commented 8 years ago

you need to change in xml file also like below:

<utils.CodeInput2

    android:id="@+id/et_otp_code"
    android:layout_marginTop="35dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"

    app:underline_color="#000000"
    app:underline_selected_color="#000000"
    app:text_color="#000000"
    app:hint_color="#000000"
    app:hint_text="Enter verification code"
    app:codes="6"

    android:layout_gravity="center_horizontal"
    android:layout_weight="2"
    android:maxLength="6"
    android:background="@android:color/transparent"
    />

NOTE: utils is package name and CodeInput2 is class name.

Shusshu commented 8 years ago

@Sushil21 I meant the copy/paste feature in android does not work for that component

bajian commented 8 years ago

@Shusshu have you solved it ? i only found the way to intercept cut/copy/paste effect but cannot disable the show of clipboard

@Override
    public boolean onTextContextMenuItem(int id) {
        return true;//拦截剪切粘贴事件 consume cut/copy/paste
    }
Shusshu commented 8 years ago

I haven't looked more into it...

yan-lixin commented 6 years ago

If you want to use copy and paste functions, you just need to write this code. codeInputEditText.setOnLongClickListener(view -> false);