Android:MaterialTextField(TextInputLayout之另一种实现)

Google 在 2015 的 IO 大会上,给我们带来了更加详细的 Material Design 设计规范,同时,也给我们带来了全新的 Android Design Support Library,在这个 support 库里面,Google 给我们提供了更加规范的MD 设计风格的控件,这不得不说是一个良心之作。

相信很多小伙子已经体验过其中的一些控件了,但 FRANCE 的一位设计师 Florent Champigny 却发现了更有趣的实现MaterialTextField 。看图

这里写图片描述

比 Google 推荐的 TextInputLayout 动感吧,作为走在进阶道路上的苦逼程序员,不能说拿来能用就行,必须知其然知其所以然,于是 fork 下来一步一步解剖代码。
我打算站在作者的位置上思考,要实现这个 MaterialTextField 具体会怎么去考虑。


需求分析:
既然 Google 推荐了 TextInputLayout,那首先看看它是继承了什么来拓展,里面还有什么可以让我们定义的。分析了一番代码后,尽管能拓展的地方有很多,如输入错误能设置错误提示,框框变颜色等等,但它继承了 LinearLayout ,也就是说布局上限制了能拓展的方向,想在它的身上实现这个动感的输入框效果,可能会很棘手。

在分析代码的时候,有意思地发现 TextInputLayout 展开的时候用到了 ValueAnimatorCompat 来做动画,那就简单了。只要我弄个容器放 label、editText、icon 这些小控件,再让他们用动画展开和收缩就行了吧。思路上没问题之后,就该考虑容器要怎么样定义才方便使用了。

场景使用:
我们是针对开发者的角度来定义吧,那么怎样才能减轻开发者的工作来快速实现这个效果。
在业务上,开发者除了要处理 editText 的内容是否满足某些条件外,其它貌似也不需要他们去处理。那么我们就可以把这些 label、icon 当作成 MaterialTextField 的一个属性,可以直接在 xml 的控件上添加的属性。

下面是见证奇迹的时候
把梦幻变成现实:
第一步自定义属性:
mtf_cardCollapsedHeight 为未展开状态下 label 下面白色那线条的高度;
mtf_hasFocus 是否获得焦点;
mtf_openKeyboardOnFocus 展示是是否需要打开软键盘;
mtf_animationDuration 展开和收缩时动画执行的时间;
这些非必须属性并非在第一时间就想到,而是在逐步编码过程中想到,所以说不要第一时间把事情考虑的太复杂才是最重要的。

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <declare-styleable name="MaterialTextField">
        <attr name="mtf_cardCollapsedHeight" format="dimension"/>
        <attr name="mtf_labelColor" format="color"/>
        <attr name="mtf_image" format="reference"/>
        <attr name="mtf_hasFocus" format="boolean"/>
        <attr name="mtf_animationDuration" format="integer"/>
        <attr name="mtf_openKeyboardOnFocus" format="boolean"/>
    </declare-styleable>
</resources>

第二步处理自定义的属性:

TypedArray styledAttrs = context.obtainStyledAttributes(attrs, R.styleable.MaterialTextField);
ANIMATION_DURATION = styledAttrs.getInteger(R.styleable.MaterialTextField_mtf_animationDuration, 400);
.
.
.

诸如此类获取自定义的属性就不一一贴代码了,大家可以 fork 项目下来慢慢看。
再下来就重写 onFinishInflate() 来获得子控件来初始化一些参数和设置了(具体看看项目源码),相信有些同学不太了解这个函数在什么时候调用,官方对这个周期方法的解释为:
onFinishInflate(): 当View中所有的子控件 均被映射成xml后触发。

其实我们一般使用 View 的流程是在 onCreate 中使用 setContentView 来设置要显示 Layout 文件或直接创建一个 View,在当设置了 ContentView 之后系统会对这个 View 进行解析,然后回调当前视图 View 中的onFinishInflate 方法。只有解析了这个 View 我们才能在这个View容器中获取到拥有Id的组件,同样因为系统解析完 View 之后才会调用 onFinishInflate 方法,所以我们自定义组件时可以 onFinishInflate 方法中获取指定子View的引用。

第三步处理逻辑:
展开的动画、收缩的动画,其实都用了v4包的 ViewCompat 来制作动画。主要是过程中对那些子控件做一些位移、渐变的效果,再有就是对获得焦点是否弹出软键盘(作者的项目对软键盘弹出收回的逻辑处理不太妥当,体验上不太友好)。

 public void expand() {//展开的动画处理
        if (!expanded) {

          ViewCompat.animate(editText).alpha(1f).setDuration(ANIMATION_DURATION);
          ViewCompat.animate(card).scaleY(1f).setDuration(ANIMATION_DURATION);
          ViewCompat.animate(label).alpha(0.4f).scaleX(0.7f).scaleY(0.7f)
                      .translationY(-labelTopMargin).setDuration(ANIMATION_DURATION);                                  
          ViewCompat.animate(image).alpha(1f).scaleX(1f).scaleY(1f)
                      .setDuration(ANIMATION_DURATION);

            if (editText != null) {
                editText.requestFocus();
            }
            if (OPEN_KEYBOARD_ON_FOCUS) {
                ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE)).showSoftInput(editText, InputMethodManager.SHOW_IMPLICIT);
            }

            expanded = true;
        }
    }
  public void reduce() {//收缩的动画处理
        if (expanded) {

            final int heightInitial = getContext().getResources().getDimensionPixelOffset(R.dimen.mtf_cardHeight_final);

            ViewCompat.animate(label).alpha(1).scaleX(1).scaleY(1)
                .translationY(0).setDuration(ANIMATION_DURATION);

            ViewCompat.animate(image).alpha(0).scaleX(0.4f).scaleY(0.4f)
                .setDuration(ANIMATION_DURATION);

            ViewCompat.animate(editText).alpha(1f)
                .setUpdateListener(new ViewPropertyAnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(View view) {
                        float value = ViewCompat.getAlpha(view); //percentage
                        card.getLayoutParams().height = (int) (value * (heightInitial - cardCollapsedHeight) + cardCollapsedHeight);
                        card.requestLayout();
                    }
                })
                .setDuration(ANIMATION_DURATION);

            ViewCompat.animate(card).scaleY(reducedScale).setDuration(ANIMATION_DURATION);

            if (OPEN_KEYBOARD_ON_FOCUS) {
                ((InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE)).toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
            }
            editText.clearFocus();

            expanded = false;
        }
    }

上舞台:
这里写图片描述

其实软键盘的弹出收缩逻辑换成,体验会比较好。但是还是会出现,当收缩状态软键盘还是不消失的小bug,如果再加上一些标记来记录软键盘的状态来控制是否该弹出还是隐藏会好一点。

imm.toggleSoftInput(0, InputMethodManager.HIDE_NOT_ALWAYS);

如果有什么说的不好,可以提出来。来互相伤害吧…


版权声明:本文为snailbaby_soko原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。