IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> Android 自定义View实现TextInputLayout——CofferTextInputLayout -> 正文阅读

[移动开发]Android 自定义View实现TextInputLayout——CofferTextInputLayout

一、简介

很久没有写博客了,前段时间一直忙着找工作面试,上个月总算找到了合适的工作,算是稳定下来了。也有时间去总结学习些东西。

二、需求

最近刚刚接到一个需求,交互那边要求实现一个类似于Android Material Design 里的一个TextInputLayout 的输入框动效交互。涉及到提示文字的动效、输入框的焦点变化导致的文字、输入框的UI变化等,看了下原生的TextInputLayout 的API,发现留给开发者自定义扩展API的太少了,无法满足设计那边设定的颜色、大小等一堆定制点的改造。本想着继承TextInputLayout来实现下,一看这个类2000多行。。。算了,我还是自己造轮子吧。

三、造轮子

先看看Android原生提供的TextInputLayout实现的效果:
在这里插入图片描述

看到这个交互,有三点需要实现。

  1. 自定义设置输入框。原生的EditText里的提示文字是不是View。
  2. 监听焦点状态和动画完结后设置输入框的状态。
  3. 提示文字的两个动画效果,即有焦点平移、缩小和无焦点的平移放大。

就这上面的这三点,我们逐一处理。
首先是自定义设置输入框。因为要涉及到提示文字的动效,因此这里的提示文字没有使用EditText里提供的,而是用TextView + EditText。代码如下:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:id="@+id/tip"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:layout_marginBottom="8dp"
        android:textSize="18sp"
        android:textStyle="bold"
        android:visibility="visible" />

    <EditText
        android:id="@+id/edit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="bottom"
        android:paddingBottom="8dp"
        android:importantForAutofill="no"
        android:background="@drawable/edittext_bg_selector"
        android:textCursorDrawable="@drawable/edit_cursor_drawable"
        android:textSize="18sp"
        android:textStyle="bold" />
</FrameLayout>

接下来就是实现焦点、文字变化状态的监听。
这里会涉及到两个接口类TextChangedListener、OnFocusChangeListener。

mEditText.addTextChangedListener(new DefaultTextWatcher());
mEditText.setOnFocusChangeListener(new DefaultOnFocusChangeListener());

private class DefaultTextWatcher implements TextWatcher{

        @Override
        public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {

            if (mTextWatcher != null){
                mTextWatcher.beforeTextChanged(charSequence,start,count,after);
            }
        }

        @Override
        public void onTextChanged(CharSequence charSequence, int start, int before, int count) {

            if (mTextWatcher != null){
                mTextWatcher.onTextChanged(charSequence,start,before,count);
            }
        }

        @Override
        public void afterTextChanged(Editable editable) {
            String content = editable.toString();
            if (mUseClean){
                if (TextUtils.isEmpty(content)){
                    mIvClose.setVisibility(GONE);
                }else {
                    mIvClose.setVisibility(VISIBLE);
                }
            }
            if (mTextWatcher != null){
                mTextWatcher.afterTextChanged(editable);
            }
        }
    }

    private class DefaultOnFocusChangeListener implements OnFocusChangeListener{

        @Override
        public void onFocusChange(View view, boolean hasFocus) {
            String content = "";
            if (view instanceof EditText){
                content = ((EditText) view).getText().toString();
            }
            if (TextUtils.isEmpty(content)){
                if (hasFocus){
                    // 有焦点,给提示文案做右上角缩放动画
                    showFocusAnim();
                }else {
                    // 没有焦点,上面的动画反过来
                    showNoFocusAnim();
                }
            }

            if (mOnFocusChangeListener != null){
                mOnFocusChangeListener.onFocusChange(view,hasFocus);
            }
        }

最后就是那个动画的实现,其实就是一个组合动画而已。

/**
         * 给提示文案做动画
         * 动画效果:
         * 进行:1、View从左下角上移,2、View变小。变化时间200ms
         * 结束:1、文字颜色变化为输入框之外的颜色,2、文字字体变细(Normal)
         */
        private void showFocusAnim(){
            PropertyValuesHolder[] focus = new PropertyValuesHolder[]{
                  PropertyValuesHolder.ofFloat("scaleX",1f,0.67f),
                  PropertyValuesHolder.ofFloat("scaleY",1f,0.67f),
                  PropertyValuesHolder.ofFloat("translationY",0,
                          -Util.dipToPixel(getContext(),19))
            };
            ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(mTvTip, focus);
            objectAnimator.setDuration(200);
            mTvTip.setPivotX(0);
            mTvTip.setPivotY(0);
            objectAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    mTvTip.setTypeface(null,Typeface.NORMAL);
                    mTvTip.setTextColor(mHintTextTipColor);
                }
            });
            objectAnimator.start();
        }

        /**
         * 给提示文案做动画
         * 动画效果:
         * 进行:1、View从左上角下移,2、View变大。变化时间200ms
         * 结束:1、文字颜色变化为默认颜色,2、文字字体变粗(Bold)
         */
        private void showNoFocusAnim(){
            PropertyValuesHolder[] noFocus = new PropertyValuesHolder[]{
                PropertyValuesHolder.ofFloat("scaleX",0.67f,1f),
                PropertyValuesHolder.ofFloat("scaleY",0.67f,1f),
                PropertyValuesHolder.ofFloat("translationY",mTvTip.getTranslationY(),0)
            };
            ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(mTvTip, noFocus);
            objectAnimator.setDuration(200);
            mTvTip.setPivotX(0);
            mTvTip.setPivotY(0);
            objectAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    mTvTip.setTypeface(null,Typeface.BOLD);
                    mTvTip.setTextColor(mHintTextDefaultColor);
                }
            });
            objectAnimator.start();
        }
    }

最后给大家看下我们最终实现的效果:
在这里插入图片描述

四、完整的源码分享

package coffer.widget;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.PropertyValuesHolder;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Typeface;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import coffer.androidjatpack.R;
import coffer.util.Util;

/**
 * author      : coffer
 * date        : 8/26/21
 * description : 实现交互类似于{@link com.google.android.material.textfield.TextInputLayout}的效果,
 *               TextInputLayout 存在的问题是对外暴露修改的API太少,留给开发者自定义扩展的场景也少,
 *               例如默认提示文案的文字大小、颜色,选中后的颜色,动画的时间等。
 * Reviewer    :
 */
public class CofferTextInputLayout extends RelativeLayout {

    private static final String TAG = "C_INPUT";

    /**
     * 自定义焦点发生改变的回调
     */
    private OnFocusChangeListener mOnFocusChangeListener;
    /**
     * 自定义输入框里的内容状态监听
     */
    private TextWatcher mTextWatcher;
    /**
     * 提示文案默认颜色(在输入框里)
     */
    private int mHintTextDefaultColor;
    /**
     * 提示文案真正提示状态的颜色(在输入框外)
     */
    private int mHintTextTipColor;
    /**
     * 是否使用清空内容
     */
    private boolean mUseClean;


    private EditText mEditText;
    private TextView mTvTip;
    private ImageView mIvClose;
    private ImageView mIvFun;

    public CofferTextInputLayout(Context context) {
        this(context,null);
    }

    public CofferTextInputLayout(Context context, AttributeSet attrs) {
        this(context, attrs,0);
    }

    public CofferTextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context,attrs);
    }

    private void init(Context context,AttributeSet attrs){
        LayoutInflater.from(context).inflate(R.layout.text_input_edit_layout, this);
        mEditText = findViewById(R.id.edit);
        mTvTip = findViewById(R.id.tip);
        mIvClose = findViewById(R.id.close);
        mIvFun = findViewById(R.id.fun);

        TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.CofferTextInputLayout);
        String hintText = typedArray.getString(R.styleable.CofferTextInputLayout_hintTipText);
        mHintTextTipColor = typedArray.getColor(R.styleable.CofferTextInputLayout_hintSelectTextColor,
                context.getResources().getColor(R.color.blue));
        mHintTextDefaultColor = typedArray.getColor(R.styleable.CofferTextInputLayout_hintTextDefaultColor,
                context.getResources().getColor(R.color.text_color_cr_reduce));
        mUseClean = typedArray.getBoolean(R.styleable.CofferTextInputLayout_useClean,false);
        mTvTip.setText(hintText);
        mTvTip.setTextColor(mHintTextDefaultColor);
        typedArray.recycle();
        // 设定属性,绑定相关的监听
        mEditText.addTextChangedListener(new DefaultTextWatcher());
        mEditText.setOnFocusChangeListener(new DefaultOnFocusChangeListener());
        mIvClose.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
                mEditText.setText("");
            }
        });

    }

    public void setHintTextDefaultColor(int defaultColor){
        mHintTextDefaultColor = defaultColor;
    }

    public void setHintTextTipColor(int color){
        mHintTextTipColor = color;
    }

    public EditText getEditText(){
        return mEditText;
    }

    /**
     * 获取输入框最右边的功能图标
     * @return view
     */
    public ImageView getFunIcon(){
        return mIvFun;
    }

    /**
     * 设置输入框最右边的功能图标
     */
    public void setFunIcon(int drawableId){
        mIvFun.setImageResource(drawableId);
    }

    /**
     * 是否设置显示清空内容,功能图标和清空按钮不能同时显示
     * @param state 默认不显TRUE 表示使用
     */
    public void setUseClean(boolean state){
        mUseClean = state;
    }

    /**
     * 是否设置显示功能图标,功能图标和清空按钮不能同时显示
     * @param state TRUE 表示使用
     */
    public void setUseFun(boolean state){
        if (state){
            mIvFun.setVisibility(VISIBLE);
        }else {
            mIvFun.setVisibility(GONE);
        }
    }

    /**
     * 给外面自定义设置
     * @param textWatcher 内容监听
     */
    public void setTextWatcher(TextWatcher textWatcher){
        mTextWatcher = textWatcher;
    }

    /**
     * 给外面自定义设置
     * @param onFocusChangeListener 焦点监听
     */
    public void settOnFocusChangeListener(OnFocusChangeListener onFocusChangeListener){
        mOnFocusChangeListener = onFocusChangeListener;
    }

    private class DefaultTextWatcher implements TextWatcher{

        @Override
        public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {

            if (mTextWatcher != null){
                mTextWatcher.beforeTextChanged(charSequence,start,count,after);
            }
        }

        @Override
        public void onTextChanged(CharSequence charSequence, int start, int before, int count) {

            if (mTextWatcher != null){
                mTextWatcher.onTextChanged(charSequence,start,before,count);
            }
        }

        @Override
        public void afterTextChanged(Editable editable) {
            String content = editable.toString();
            if (mUseClean){
                if (TextUtils.isEmpty(content)){
                    mIvClose.setVisibility(GONE);
                }else {
                    mIvClose.setVisibility(VISIBLE);
                }
            }
            if (mTextWatcher != null){
                mTextWatcher.afterTextChanged(editable);
            }
        }
    }

    private class DefaultOnFocusChangeListener implements OnFocusChangeListener{

        @Override
        public void onFocusChange(View view, boolean hasFocus) {
            String content = "";
            if (view instanceof EditText){
                content = ((EditText) view).getText().toString();
            }
            if (TextUtils.isEmpty(content)){
                if (hasFocus){
                    // 有焦点,给提示文案做右上角缩放动画
                    showFocusAnim();
                }else {
                    // 没有焦点,上面的动画反过来
                    showNoFocusAnim();
                }
            }

            if (mOnFocusChangeListener != null){
                mOnFocusChangeListener.onFocusChange(view,hasFocus);
            }
        }

        /**
         * 给提示文案做动画
         * 动画效果:
         * 进行:1、View从左下角上移,2、View变小。变化时间200ms
         * 结束:1、文字颜色变化为输入框之外的颜色,2、文字字体变细(Normal)
         */
        private void showFocusAnim(){
            PropertyValuesHolder[] focus = new PropertyValuesHolder[]{
                  PropertyValuesHolder.ofFloat("scaleX",1f,0.67f),
                  PropertyValuesHolder.ofFloat("scaleY",1f,0.67f),
                  PropertyValuesHolder.ofFloat("translationY",0,
                          -Util.dipToPixel(getContext(),19))
            };
            ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(mTvTip, focus);
            objectAnimator.setDuration(200);
            mTvTip.setPivotX(0);
            mTvTip.setPivotY(0);
            objectAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    mTvTip.setTypeface(null,Typeface.NORMAL);
                    mTvTip.setTextColor(mHintTextTipColor);
                }
            });
            objectAnimator.start();
        }

        /**
         * 给提示文案做动画
         * 动画效果:
         * 进行:1、View从左上角下移,2、View变大。变化时间200ms
         * 结束:1、文字颜色变化为默认颜色,2、文字字体变粗(Bold)
         */
        private void showNoFocusAnim(){
            PropertyValuesHolder[] noFocus = new PropertyValuesHolder[]{
                PropertyValuesHolder.ofFloat("scaleX",0.67f,1f),
                PropertyValuesHolder.ofFloat("scaleY",0.67f,1f),
                PropertyValuesHolder.ofFloat("translationY",mTvTip.getTranslationY(),0)
            };
            ObjectAnimator objectAnimator = ObjectAnimator.ofPropertyValuesHolder(mTvTip, noFocus);
            objectAnimator.setDuration(200);
            mTvTip.setPivotX(0);
            mTvTip.setPivotY(0);
            objectAnimator.addListener(new AnimatorListenerAdapter() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    super.onAnimationEnd(animation);
                    mTvTip.setTypeface(null,Typeface.BOLD);
                    mTvTip.setTextColor(mHintTextDefaultColor);
                }
            });
            objectAnimator.start();
        }
    }

}

 <!-- TextInput的属性-->
    <declare-styleable name="CofferTextInputLayout">
        <attr name="hintTextDefaultColor" format="color"/>
        <attr name="hintSelectTextColor" format="color"/>
        <attr name="hintTipText" format="string"/>
        <attr name="useClean" format="boolean"/>
    </declare-styleable>
 <coffer.widget.CofferTextInputLayout
        android:id="@+id/c_input_number"
        android:layout_width="match_parent"
        android:layout_height="60dp"
        android:layout_marginLeft="20dp"
        android:layout_marginRight="20dp"
        app:hintSelectTextColor="@color/blue"
        app:hintTextDefaultColor="@color/text_color_cr_reduce"
        app:hintTipText="哈哈" />

五、总结

关于扩展属性,我这里偷懒了,扩展的不多。如果大家有需要可以自行添加,对比原生的复杂实现,我的代码量量只有原生的10%左右,当然,原生的功能会更加强大、多,这个就看个人需求吧。
我差不多有一年没有写一个完整的自定义View了,这次突然写很多东西都忘了,让我着实尴尬,哈哈哈哈。

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-08-29 09:28:21  更:2021-08-29 09:28:24 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 -2025/1/31 6:18:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码