private String cacheStr = "";
public class BiuEditText extends EditText {
public BiuEditText(Context context) {
super(context);
}
public BiuEditText(Context context, AttributeSet attrs) {
super(context, attrs);
setlistener();
}
public BiuEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
/**
* 输入文字时的监听
* BiuEditText静态添加进XML布局 所以该监听方法写到第二个构造方法中
*/
private void setlistener() {
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) {
if (cacheStr.length() < s.length()) {
// 输入文字时
char last = s.charAt(s.length() - 1);
update(last, false);
} else if (cacheStr.length() >= 1) {
// 删除文字时
char last = cacheStr.charAt(cacheStr.length() - 1);
update(last, true);
}
cacheStr = s.toString();
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
}
<?xml version="1.0" encoding="utf-8"?>
<resources>
<declare-styleable name="BiuEditStyle">
<attr name="biu_text_color" format="color" />
<attr name="biu_text_start_size" format="dimension" />
<attr name="biu_text_scale" format="float" />
<attr name="biu_duration" format="integer" />
<attr name="biu_type" format="enum">
<enum name="flyup" value="0" />
<enum name="flydown" value="1" />
</attr>
</declare-styleable>
</resources>
private ViewGroup contentContainer;
private int height;
private int biuTextColor;
private float biuTextStartSize;
private float biuTextScale;
private int biuDuration;
private int biuType;
private void init(Context context, AttributeSet attrs) {
if (isInEditMode())
return;
if (null == attrs) {
throw new IllegalArgumentException("Attributes should be provided to this view,");
}
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BiuEditStyle);
biuTextColor = typedArray.getColor(R.styleable.BiuEditStyle_biu_text_color, getResources().getColor(R.color.white));
biuTextStartSize = typedArray.getDimension(R.styleable.BiuEditStyle_biu_text_start_size, getResources().getDimension(R.dimen.biu_text_start_size));
biuTextScale = typedArray.getFloat(R.styleable.BiuEditStyle_biu_text_scale, DEFAULT_SCALE);
biuDuration = typedArray.getInt(R.styleable.BiuEditStyle_biu_duration, DEFAULT_DURATION);
biuType = typedArray.getInt(R.styleable.BiuEditStyle_biu_type, 0);
typedArray.recycle();
/**
* 关键所在-通过findViewById(android.R.id.content)获取Activity根View布局 跳跃文字都是添加在该容器布局内部
* 所以点击键盘时,跳跃的TextView是出现在全局中,而不仅仅局限在自定义View所在的位置
*/
contentContainer = (ViewGroup) ((Activity) getContext()).findViewById(android.R.id.content);
Log.d("根布局id", "contentContainer: " + contentContainer.getId());
WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
height = windowManager.getDefaultDisplay().getHeight(); // 获取屏幕的高度 此方法已被废弃 改为 height = getContext().getResource().getDisplayMeytrics().heightPixels;
}
/**
* 动态添加或者动态删除跳跃的TextView
* @param last
* @param isOpposite
*/
private void update(char last, boolean isOpposite) {
final TextView textView = new TextView(getContext());
textView.setTextColor(Color.BLUE);
textView.setTextSize(biuTextStartSize);
textView.setText(String.valueOf(last));
textView.setGravity(Gravity.CENTER);
contentContainer.addView(textView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT));
textView.measure(0, 0);
playAnaimator(textView, isOpposite, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
contentContainer.removeView(textView);
}
});
}
private void playAnaimator(TextView textView, boolean isOpposite, AnimatorListenerAdapter listenerAdapter) {
switch (biuType) {
case ANIMATION_DEFAULT:
playFlyUp(textView, isOpposite, listenerAdapter);
break;
case ANIMATION_DROPOUT:
playFlyDown(textView, isOpposite, listenerAdapter);
break;
default:
break;
}
}
/**
* TextView下落动画
* @param textView
* @param isOpposite
* @param listenerAdapter
*/
private void playFlyDown(TextView textView, boolean isOpposite, AnimatorListenerAdapter listenerAdapter) {
int pos = getSelectionStart();
Layout layout = getLayout();
float startX = 0;
float startY = 0;
float endX = 0;
float endY = 0;
if (isOpposite) {
endX = new Random().nextInt(contentContainer.getWidth());
endY = 0;
startX = layout.getPrimaryHorizontal(pos) + 100;
startY = getY() - 100;
} else {
startX = layout.getPrimaryHorizontal(pos) + 100;
startY = -100;
endX = startX;
endY = getY() - 100;
}
final AnimatorSet animSet = new AnimatorSet();
ObjectAnimator animX = ObjectAnimator.ofFloat(textView, "translationX", startX, endX);
ObjectAnimator alpha = ObjectAnimator.ofFloat(textView, "alpha", 0, 1);
ObjectAnimator translationY = ObjectAnimator.ofFloat(textView, "translationY", startY, endY);
// 代码家的28种估值器之一 无比炫酷
translationY.setEvaluator(new BounceEaseInOut(biuDuration));
animSet.setDuration(biuDuration);
animSet.addListener(listenerAdapter);
animSet.playTogether(alpha, translationY, animX);
animSet.start();
}
/**
* TextView上浮动画
* @param textView
* @param isOpposite
* @param listenerAdapter
*/
private void playFlyUp(TextView textView, boolean isOpposite, AnimatorListenerAdapter listenerAdapter) {
// TextView的getSelectionStart()方法用以获取光标的位置
int pos = getSelectionStart();
Layout layout = getLayout();
float startX = 0;
float startY = 0;
float endX = 0;
float endY = 0;
if (isOpposite) {
endX = new Random().nextInt(contentContainer.getWidth());
endY = height / 3 * 2;
startX = layout.getPrimaryHorizontal(pos) + 100;
startY = getY();
} else {
// layout.getPrimaryHorizontal获取光标左边的位置 再加上100px偏移量
startX = layout.getPrimaryHorizontal(pos) + 100;
// 屏幕高度的 2/3 这里可以改为用工具类获取键盘的高度
startY = height / 3 * 2;
endX = startX;
endY = getY();
}
final AnimatorSet animSet = new AnimatorSet();
ObjectAnimator animX = ObjectAnimator.ofFloat(textView, "translationX", startX, endX);
ObjectAnimator animY = ObjectAnimator.ofFloat(textView, "translationY", startY, endY);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(textView, "scaleX", 1f, biuTextScale);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(textView, "scaleY", 1f, biuTextScale);
animY.setEvaluator(new ExpoEaseInOut(biuDuration));
animY.setInterpolator(new DecelerateInterpolator());
animSet.setDuration(biuDuration);
animSet.addListener(listenerAdapter);
animSet.playTogether(animX, animY, scaleX, scaleY);
animSet.start();
}
package me.james.biuedittext;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.text.Editable;
import android.text.Layout;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.animation.DecelerateInterpolator;
import android.widget.EditText;
import android.widget.TextView;
import java.util.Random;
import me.james.biuedittext.easing.bounce.BounceEaseInOut;
import me.james.biuedittext.easing.expo.ExpoEaseInOut;
/**
* 作者 james
* 分析 cpf
* 文件 BiuEditText
* 难点 在于如何实现全局添加动态跳跃的TextView
* 解决 通过findViewById(android.R.id.content)获取Activity根View布局 跳跃文字都是添加在该容器布局内部
*/
public class BiuEditText extends EditText {
private ViewGroup contentContainer;
private int height;
private String cacheStr = "";
private static final int ANIMATION_DEFAULT = 0;
private static final int ANIMATION_DROPOUT = 1;
private static final int DEFAULT_DURATION = 600;
private static final float DEFAULT_SCALE = 1.2f;
private int biuTextColor;
private float biuTextStartSize;
private float biuTextScale;
private int biuDuration;
private int biuType;
public BiuEditText(Context context) {
super(context);
}
public BiuEditText(Context context, AttributeSet attrs) {
super(context, attrs);
init(context, attrs);
setlistener();
}
public BiuEditText(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init(context, attrs);
}
private void init(Context context, AttributeSet attrs) {
if (isInEditMode())
return;
if (null == attrs) {
throw new IllegalArgumentException("Attributes should be provided to this view,");
}
final TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.BiuEditStyle);
biuTextColor = typedArray.getColor(R.styleable.BiuEditStyle_biu_text_color, getResources().getColor(R.color.white));
biuTextStartSize = typedArray.getDimension(R.styleable.BiuEditStyle_biu_text_start_size, getResources().getDimension(R.dimen.biu_text_start_size));
biuTextScale = typedArray.getFloat(R.styleable.BiuEditStyle_biu_text_scale, DEFAULT_SCALE);
biuDuration = typedArray.getInt(R.styleable.BiuEditStyle_biu_duration, DEFAULT_DURATION);
biuType = typedArray.getInt(R.styleable.BiuEditStyle_biu_type, 0);
typedArray.recycle();
/**
* 关键所在-通过findViewById(android.R.id.content)获取Activity根View布局 跳跃文字都是添加在该容器布局内部
* 所以点击键盘时,跳跃的TextView是出现在全局中,而不仅仅局限在自定义View所在的位置
*/
contentContainer = (ViewGroup) ((Activity) getContext()).findViewById(android.R.id.content);
Log.d("根布局id", "contentContainer: " + contentContainer.getId());
WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
height = windowManager.getDefaultDisplay().getHeight(); // 获取屏幕的高度 此方法已被废弃 改为 height = getContext().getResource().getDisplayMeytrics().heightPixels;
}
/**
* 输入文字时的监听
* BiuEditText静态添加进XML布局 所以该监听方法写到第二个构造方法中
*/
private void setlistener() {
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) {
if (cacheStr.length() < s.length()) {
// 输入文字时
char last = s.charAt(s.length() - 1);
update(last, false);
} else if (cacheStr.length() >= 1) {
// 删除文字时
char last = cacheStr.charAt(cacheStr.length() - 1);
update(last, true);
}
cacheStr = s.toString();
}
@Override
public void afterTextChanged(Editable s) {
}
});
}
/**
* 动态添加或者动态删除跳跃的TextView
* @param last
* @param isOpposite
*/
private void update(char last, boolean isOpposite) {
final TextView textView = new TextView(getContext());
textView.setTextColor(Color.BLUE);
textView.setTextSize(biuTextStartSize);
textView.setText(String.valueOf(last));
textView.setGravity(Gravity.CENTER);
contentContainer.addView(textView, new ViewGroup.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT));
textView.measure(0, 0);
playAnaimator(textView, isOpposite, new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
contentContainer.removeView(textView);
}
});
}
private void playAnaimator(TextView textView, boolean isOpposite, AnimatorListenerAdapter listenerAdapter) {
switch (biuType) {
case ANIMATION_DEFAULT:
playFlyUp(textView, isOpposite, listenerAdapter);
break;
case ANIMATION_DROPOUT:
playFlyDown(textView, isOpposite, listenerAdapter);
break;
default:
break;
}
}
/**
* TextView下落动画
* @param textView
* @param isOpposite
* @param listenerAdapter
*/
private void playFlyDown(TextView textView, boolean isOpposite, AnimatorListenerAdapter listenerAdapter) {
int pos = getSelectionStart();
Layout layout = getLayout();
float startX = 0;
float startY = 0;
float endX = 0;
float endY = 0;
if (isOpposite) {
endX = new Random().nextInt(contentContainer.getWidth());
endY = 0;
startX = layout.getPrimaryHorizontal(pos) + 100;
startY = getY() - 100;
} else {
startX = layout.getPrimaryHorizontal(pos) + 100;
startY = -100;
endX = startX;
endY = getY() - 100;
}
final AnimatorSet animSet = new AnimatorSet();
ObjectAnimator animX = ObjectAnimator.ofFloat(textView, "translationX", startX, endX);
ObjectAnimator alpha = ObjectAnimator.ofFloat(textView, "alpha", 0, 1);
ObjectAnimator translationY = ObjectAnimator.ofFloat(textView, "translationY", startY, endY);
// 代码家的28种估值器之一 无比炫酷
translationY.setEvaluator(new BounceEaseInOut(biuDuration));
animSet.setDuration(biuDuration);
animSet.addListener(listenerAdapter);
animSet.playTogether(alpha, translationY, animX);
animSet.start();
}
/**
* TextView上浮动画
* @param textView
* @param isOpposite
* @param listenerAdapter
*/
private void playFlyUp(TextView textView, boolean isOpposite, AnimatorListenerAdapter listenerAdapter) {
// TextView的getSelectionStart()方法用以获取光标的位置
int pos = getSelectionStart();
Layout layout = getLayout();
float startX = 0;
float startY = 0;
float endX = 0;
float endY = 0;
if (isOpposite) {
endX = new Random().nextInt(contentContainer.getWidth());
endY = height / 3 * 2;
startX = layout.getPrimaryHorizontal(pos) + 100;
startY = getY();
} else {
// layout.getPrimaryHorizontal获取光标左边的位置 再加上100px偏移量
startX = layout.getPrimaryHorizontal(pos) + 100;
// 屏幕高度的 2/3 这里可以改为用工具类获取键盘的高度
startY = height / 3 * 2;
endX = startX;
endY = getY();
}
final AnimatorSet animSet = new AnimatorSet();
ObjectAnimator animX = ObjectAnimator.ofFloat(textView, "translationX", startX, endX);
ObjectAnimator animY = ObjectAnimator.ofFloat(textView, "translationY", startY, endY);
ObjectAnimator scaleX = ObjectAnimator.ofFloat(textView, "scaleX", 1f, biuTextScale);
ObjectAnimator scaleY = ObjectAnimator.ofFloat(textView, "scaleY", 1f, biuTextScale);
animY.setEvaluator(new ExpoEaseInOut(biuDuration));
animY.setInterpolator(new DecelerateInterpolator());
animSet.setDuration(biuDuration);
animSet.addListener(listenerAdapter);
animSet.playTogether(animX, animY, scaleX, scaleY);
animSet.start();
}
}