网站建设预算费用农产品品牌推广方案
点击上方“Android技术杂货铺”,选择“置顶公众号”
干货文章,第一时间送达!
作者:Android草根王
链接:https://www.jianshu.com/p/e538b35c68c3
本文经作者授权推送。
项目中很多地方,使用到了自定义控件。
简单点的,如个性控件的定制,多个组件的组合封装等。
我们需要了解自定义控件的基础知识,即可快速实现;
复杂点的,如各种图形报表(例如:股票K线图、分时图控件)。
我们除了自定义控件的基础知识,还需要掌握控件事件的拦截传递机制,事件回调、手势识别、画图、 动画、架构设计等技术。
关于自定义控件,我们逐步深入讲解:
今天,我们先来实现一个简单的自定义控件,后期找时间再讲解股票K线图、分时图控件如何自定义。
需求
实现带动画效果的手机号输入框:
1.输入手机号格式为3-4-4;
2.输入框中默认有hint提示,当开始输入数字时,有动画效果:a) hint平移出输入框,停留在输入框上方指定位置,显示对应的信息;b) 平移过程中,文字也逐渐由大变小;
3.当清空输入框,反效果动画;
4.输入时自动做数字格式校验(非数字不让输入)和长度校验(最多11位手机号)
5.当输入框有值后,最右边出现清空按钮,点击清空输入框
6.输入完成,回调结果;
分析
该输入框效果在多个页面中都会使用到,我们必须对其进行封装,此处最好的封装方案就是自定义控件。
我们APP中,所有页面的手机号输入框输入逻辑完全一样,但是个别页面存在小差异(个别页面输入手机号时,不需要动画效果,或者hint内容、提示消息不一样等等);
1,差异项应可配置:
自定义控件的以下内容应设计成可以配置的属性:
是否有动画效果;hint文本;hint移动到上部显示的文本等;此处的重点:
1. 自定义属性如何配置?如何使用?
2,自定义控件被调用(使用)
应支持在代码中直接new 我们的控件应支持在布局xml中直接使用我们的控件,可配置自定义属性
3,动画
平移效果:Tween动画、属性动画均可实现;
字体伸缩:应使用属性动画,根据字号去伸缩,宽高也会自动变化(注意:Tween动画无法做字号差值变化)
综上所述,应统一使用属性动画实现平移和伸缩的效果,而多个动画同时触发,会用到动画集合;此处的重点:
1. 平移需要原始点、目标点两个坐标(x,y),自定义控件中如何获得对应的值?
2. 字体伸缩,需要伸缩前后的两个字号值,代码中默认获得的字号是px格式,如何与sp转换?
3. 设计点:要实现文本移动和字号伸缩的动画效果,我们可以在布局中放置2个文本控件,
tv_message:作为hint占位,不显示,仅用于获得坐标和字号;
tv_to_message: 作为顶部消息显示,作为hint显示,动画执行在该控件上;当然,如何设计这种动画效果,还有很多其他的方式,大家使用时,可以根据自己的需要,合理设计。
4,手机号的3-4-4格式,就是拦截输入事件,处理字符串,没什么技术点;
5,手机号长度、特殊字符禁止输入验证,可使用正则表达式判断非法字符;
6,其他:略
技术实现分析
属性的定义,需要单独定义在res下的文件中:
res/values目录中,创建attrs.xml文件
<?xml version="1.0" encoding="utf-8"?>
<resources><!--用户-手机号输入控件-自定义属性--><declare-styleable name="user.phone.edittext"><attr name="showTopMessage" format="boolean"/><attr name="topMessage" format="string"/><attr name="hint" format="string"/></declare-styleable>
</resources>
name可以自定义,规范即可;
showTopMessage //自定义属性:是否显示顶端提示信息(true:显示,false:不显示)
topMessage//自定义属性:顶端提示信息内容
hint //自定义属性:输入框提示信息
2 . 自定义控件的布局
使用相对布局,内容包括:
输入框 et_phone(需设置成无背景色,因UI人员已固定输入线的颜色)、
输入框的底部线 View、
输入框的清空按钮 iv_phone_clear、
输入框上面的文本控件 tv_message(用于作为hint位置、字号的占位)
输入框顶部的文本控件 tv_to_message(用于显示提示信息)
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="10dip"android:paddingRight="10dip"><TextViewandroid:id="@+id/tv_to_message"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentTop="true"android:textColor="#999999"android:textSize="14sp"android:text="请输入手机号"android:visibility="visible"/><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="40dp"android:id="@+id/rl"android:layout_below="@+id/tv_to_message"><ImageViewandroid:id="@+id/iv_phone_clear"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_centerInParent="true"android:src="@mipmap/close_white"android:visibility="invisible" /><EditTextandroid:id="@+id/et_phone"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_toLeftOf="@+id/iv_phone_clear"android:background="@null"android:inputType="phone"android:textColor="#2A2A2A"android:textColorHint="#999999"android:textSize="16sp" /><TextViewandroid:id="@+id/tv_message"android:layout_width="wrap_content"android:layout_height="wrap_content"android:clickable="false"android:textColor="#999999"android:gravity="center_vertical"android:layout_centerVertical="true"android:textSize="16sp"android:text="请输入手机号"android:visibility="invisible"/><Viewandroid:layout_width="match_parent"android:layout_height="1dp"android:layout_alignParentBottom="true"android:background="#EBEBEB" /></RelativeLayout>
</RelativeLayout>
3 . 创建自定义控件类(定义成可new ,可直接在xml中使用的控件)
构造函数:
自定义控件,必须使用特定的构造函数:
1. 一个参数的构造函数,可用于其他代码中直接new 当前控件UserPhoneEditText(Context context)
2. 两个以上参数的构造函数,可用于直接在布局xml中使用当前控件,使用AttributeSet 可获得我们在xml中设置的属性;(后面有讲解)
UserPhoneEditText(Context context, AttributeSet attrs)更多构造函数相关的信息,请自行查找资料!!!
代码中解析获得自定义参数:
//获得 在attrs.xml UserPhoneEditText中已定义的属性集合
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.user_phone_edittext);
showTopMessage = typedArray.getBoolean(R.styleable.user_phone_edittext_showTopMessage, false);
topMessage = typedArray.getString(R.styleable.user_phone_edittext_topMessage);
hint = typedArray.getString(R.styleable.user_phone_edittext_hint);
//释放
typedArray.recycle();1. R.styleable.user_phone_edittext是我们在res/attrs.xml中定义的名称,对应自动生成的id
2. 获得参数后,一定记得把TypedArray 释放掉,切记!!!
创建自定义控件并获得自定义参数的详细代码:
/*** 类:UserPhoneEditText* 作者: qxc* 日期:2018/3/2.*/
public class UserPhoneEditText extends RelativeLayout {private Context context;//上下文private boolean showTopMessage;//自定义属性:是否显示顶端提示信息(true:显示,false:不显示)private String topMessage;//自定义属性:顶端提示信息内容private String hint;//输入框提示信息private EditText et_phone;//电话号输入框private ImageView iv_phone_clear;//清空输入框的按钮private TextView tv_message;//输入框内的消息文本private TextView tv_to_message;//输入框外的消息文本public UserPhoneEditText(Context context) {super(context);this.context = context;LoadView(context);}public UserPhoneEditText(Context context, AttributeSet attrs) {super(context, attrs);this.context = context;getAttrs(context,attrs);LoadView(context);}/*** 获得配置的自定义属性* @param context 上下文* @param attrs 属性集合*/private void getAttrs(Context context,AttributeSet attrs){//获得 在attrs.xml UserPhoneEditText中已定义的属性集合TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.user_phone_edittext);showTopMessage = typedArray.getBoolean(R.styleable.user_phone_edittext_showTopMessage, false);topMessage = typedArray.getString(R.styleable.user_phone_edittext_topMessage);hint = typedArray.getString(R.styleable.user_phone_edittext_hint);//释放typedArray.recycle();}/*** 初始化view* @param context 上下文*/private void LoadView(Context context){View view = LayoutInflater.from(context).inflate(R.layout.user_phone_edittext, this);initView(view);//初始化组件initEvent();//初始化事件}/*** 初始化组件*/private void initView(View view){et_phone = (EditText) view.findViewById(R.id.et_phone);iv_phone_clear = (ImageView) view.findViewById(R.id.iv_phone_clear);tv_message = (TextView) view.findViewById(R.id.tv_message);tv_to_message = (TextView) view.findViewById(R.id.tv_to_message);//根据自定义属性,显示组件//设置文本信息if(topMessage!=null){tv_to_message.setText(hint);}}/*** 初始化事件*/private void initEvent(){//清空输入框内容iv_phone_clear.setOnClickListener(new View.OnClickListener(){@Overridepublic void onClick(View view) {et_phone.setText("");}});//输入框内容变更事件//如果输入框开始输入字符,tv_message使用动画移动到tv_to_message的位置//如果输入框变成空,tv_message从tv_to_message的位置再移动回来et_phone.addTextChangedListener(new TextWatcher() {@Overridepublic void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}@Overridepublic void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}@Overridepublic void afterTextChanged(Editable editable) {String text = editable.toString();if (text.length() == 0) {//清空输入框//执行动画}}//如果输入框开始输入字符,tv_message使用动画移动到tv_to_message的位置else if (text.length() == 1 && tvPosition == 0) {//输入框内容变化//执行动画//当输入完11位手机号后,执行结果回调}}});}
}
4 , 动画的执行
动画1:平移动画
需要获得两个坐标点:
坐标点1:hint文本的位置(tv_message)
坐标点2:消息文本的位置( tv_top_message)咱们先定义两个数值,用于存放坐标点坐标
private int[] position1 = new int[2];//tv_message的默认位置坐标
private int[] position2 = new int[2];//tv_to_message的默认位置坐标x要移动、y也要移动,所以使用动画集合AnimatorSet
如果仅是移动,代码如下:
AnimatorSet set = new AnimatorSet();
set.playTogether(ObjectAnimator.ofFloat(tv_to_message , "TranslationX" , startX , endX),ObjectAnimator.ofFloat(tv_to_message , "TranslationY" ,startY, endY));
set.setDuration(duration).start();
注意:
startX 、endX等值不是指屏幕上绝对的坐标地址(例如:坐标(200,200)),而是在x轴上平移的数值变化。
例如:
startX = 0 表示当前控件的X位置变化为0;
endX =100 表示从startX开始,向右移动100像素;
endX =-100 表示从startX开始,向左移动100像素;
ofFloat 后面还可以继续增加X的值,用于表示X轴上移动的路径过程。
我们实际startX 、endX值是由tv_message、tv_top_message的坐标的X相减得来的,也就是求的控件的相对距离,作为动画移动的距离或位置。
动画2:字号变化
需要获得两个文本的字号值(tv_message、tv_top_message)
咱们先定义一个数组,用于存放两个文本的字号值
private float[] fonts = new float[2];//tv_to_message的默认大小动画的执行,如果是边移动边伸缩字号,可以继续使用AnimatorSet,代码也就改造成:
/*** 播放动画* @param startX 开始X* @param endX 目标X* @param startY 开始Y* @param endY 目标Y* @param startFont 开始字号* @param endFont 目标字号*/private void startAnim(float startX, float endX, float startY, float endY, float startFont, float endFont){AnimatorSet set = new AnimatorSet();set.playTogether(ObjectAnimator.ofFloat(tv_to_message , "TranslationX" , startX , endX),ObjectAnimator.ofFloat(tv_to_message , "TranslationY" ,startY, endY),ObjectAnimator.ofFloat(tv_to_message , "TextSize" , startFont, endFont));set.setDuration(duration).start();}
重点:
自定义控件中,如何获取到tv_message、tv_top_message的坐标和字号大小呢??
自定义控件有自己的函数周期,不同的函数做不同的事情,
如onSizeChanged、onMeasure、onLayout、onDraw等。如果不明白这些方法是做什么的,请自行查找资料。
我们先来写个代码做个试验,先来看下自定义控件函数的执行顺序:
自定义个简单的view,测试代码:
package iwangzhe.testcustomview;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
/*** 类:TestView* 作者: qxc* 日期:2018/2/27.*/
public class TestView extends View {final String Tag = "TestView";public TestView(Context context, AttributeSet attrs) {super(context, attrs);Log.i(Tag,"构造函数TestView");}@Overrideprotected void onFinishInflate() {super.onFinishInflate();Log.i(Tag,"onFinishInflate");}@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {super.onMeasure(widthMeasureSpec, heightMeasureSpec);Log.i(Tag,"onMeasure");}@Overrideprotected void onLayout(boolean changed, int left, int top, int right, int bottom) {super.onLayout(changed, left, top, right, bottom);Log.i(Tag,"onLayout");}@Overrideprotected void onSizeChanged(int w, int h, int oldw, int oldh) {super.onSizeChanged(w, h, oldw, oldh);Log.i(Tag,"onSizeChanged");}@Overrideprotected void onDraw(Canvas canvas) {super.onDraw(canvas);Log.i(Tag,"onDraw");}@Overridepublic boolean onTouchEvent(MotionEvent event) {Log.i(Tag,"onTouchEvent");invalidate();return super.onTouchEvent(event);}@Overrideprotected void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);Log.i(Tag,"onFocusChanged");}@Overridepublic void onWindowFocusChanged(boolean hasWindowFocus) {super.onWindowFocusChanged(hasWindowFocus);Log.i(Tag,"onWindowFocusChanged");}@Overrideprotected void onAttachedToWindow() {super.onAttachedToWindow();Log.i(Tag,"onAttachedToWindow");}@Overrideprotected void onDetachedFromWindow() {super.onDetachedFromWindow();Log.i(Tag,"onDetachedFromWindow");}@Overrideprotected void onWindowVisibilityChanged(int visibility) {super.onWindowVisibilityChanged(visibility);Log.i(Tag,"onWindowVisibilityChanged");}
}
输出结果:
03-05 17:38:55.690 23189-23189/iwangzhe.testcustomview I/TestView: 构造函数TestView
03-05 17:38:55.690 23189-23189/iwangzhe.testcustomview I/TestView: onFinishInflate
03-05 17:38:55.770 23189-23189/iwangzhe.testcustomview I/TestView: onAttachedToWindow
03-05 17:38:55.770 23189-23189/iwangzhe.testcustomview I/TestView: onWindowVisibilityChanged
03-05 17:38:55.780 23189-23189/iwangzhe.testcustomview I/TestView: onMeasure
03-05 17:38:55.780 23189-23189/iwangzhe.testcustomview I/TestView: onMeasure
03-05 17:38:55.820 23189-23189/iwangzhe.testcustomview I/TestView: onSizeChanged
03-05 17:38:55.820 23189-23189/iwangzhe.testcustomview I/TestView: onLayout
03-05 17:38:55.830 23189-23189/iwangzhe.testcustomview I/TestView: onDraw
03-05 17:38:55.860 23189-23189/iwangzhe.testcustomview I/TestView: onWindowFocusChanged
03-05 17:38:55.880 23189-23189/iwangzhe.testcustomview I/TestView: onMeasure
03-05 17:38:55.880 23189-23189/iwangzhe.testcustomview I/TestView: onMeasure
03-05 17:38:55.880 23189-23189/iwangzhe.testcustomview I/TestView: onLayout
03-05 17:38:55.880 23189-23189/iwangzhe.testcustomview I/TestView: onDraw
......(重复onMeasure、onLayout、onDraw)
我们看到,页面加载自定义控件,准备完毕后,会执行onWindowFocusChanged方法,那么这个方法之前,已经执行了初始化、计算、布局和绘制显示,控件的位置等信息已经被赋值。所以在onWindowFocusChanged方法中,我们是可以获取到相应属性的,代码如下:
/*** 自定义控件准备完毕,获得各组件的位置等数据*/@Overridepublic void onWindowFocusChanged(boolean hasWindowFocus) {super.onWindowFocusChanged(hasWindowFocus);//获得消息文本的位置信息tv_message.getLocationInWindow(position1);tv_to_message.getLocationOnScreen(position2);//获得消息文本的字号信息fonts[0] = PxUtils.px2sp(context,tv_message.getTextSize());fonts[1] = PxUtils.px2sp(context,tv_to_message.getTextSize());//初始化位置、字号(把tv_to_message设置的与tv_message显示一致)tv_to_message.setTextSize(fonts[0]);tv_to_message.setTranslationX(position1[0]-position2[0]);tv_to_message.setTranslationY(position1[1]-position2[1]);}
5,手机号 3 -4 -4格式
代码比较简单,如下:
/*** 电话3 4 4格式(即:xxx xxxx xxxx)* 电话长度11位数字* @param view 输入框* @param text 文本*/public static void onTextChanged344(EditText view, String text) {if (view== null || text == null || text.length() == 0) return;char space = ' ';int indexSpace1 = 3;int indexSpace2 = 8;StringBuilder sb = new StringBuilder();//1.取出所有字符,去掉' '和非法字符for (int i = 0; i < text.length(); i++) {//如果数字数大于11位,去掉后面的数字if(sb.length() >= 11){break;}//是否合法字符(0~9) (正则表达式)Pattern pattern = Pattern.compile("^[0-9]*$");Matcher matcher = pattern.matcher(String.valueOf(text.charAt(i)));if (text.charAt(i) != space && matcher.matches()) {sb.append(text.charAt(i));}}//2.根据长度追加' 'if(sb.length() > indexSpace1){sb.insert(indexSpace1, space);}if(sb.length() > indexSpace2){sb.insert(indexSpace2, space);}//3.设置文本和光标位置if(!sb.toString().equals(text)){view.setText(sb.toString());view.setSelection(sb.length());}}
完整代码
上面,基本的技术点都解决了,那么我们把代码串起来,并贴出完整的代码吧(后面会给出Demo源码地址)
类1:PxUtils px与sp转换的帮助类
/*** 类:PxUtils* 作者: qxc* 日期:2018/3/5.*/public class PxUtils {/*** px转sp* @param context 上下文* @param pxValue px* @return sp*/public static int px2sp(Context context, float pxValue) {final float fontScale = context.getResources().getDisplayMetrics().scaledDensity;return (int) (pxValue / fontScale + 0.5f);}
}
类2:电话号格式处理类
/*** 类:PhoneFormat* 作者: qxc* 日期:2018/3/5.*/
public class PhoneFormat {/*** 电话3 4 4格式(即:xxx xxxx xxxx)* 电话长度11位数字* @param view 输入框* @param text 文本*/public static void onTextChanged344(EditText view, String text) {if (view== null || text == null || text.length() == 0) return;char space = ' ';int indexSpace1 = 3;int indexSpace2 = 8;StringBuilder sb = new StringBuilder();//1.取出所有字符,去掉' '和非法字符for (int i = 0; i < text.length(); i++) {//如果数字数大于11位,去掉后面的数字if(sb.length() >= 11){break;}//是否合法字符(0~9)Pattern pattern = Pattern.compile("^[0-9]*$");Matcher matcher = pattern.matcher(String.valueOf(text.charAt(i)));if (text.charAt(i) != space && matcher.matches()) {sb.append(text.charAt(i));}}//2.根据长度追加' 'if(sb.length() > indexSpace1){sb.insert(indexSpace1, space);}if(sb.length() > indexSpace2){sb.insert(indexSpace2, space);}//3.设置文本和光标位置if(!sb.toString().equals(text)){view.setText(sb.toString());view.setSelection(sb.length());}}/*** 获得已输入的电话号,不包括空格* @param editText 输入控件* @return 电话号*/public static String getPhoneNumber(EditText editText){if (editText== null || editText.getText() == null) return "";String text = editText.getText().toString();char space = ' ';StringBuilder sb = new StringBuilder();for (int i = 0; i < text.length(); i++) {if (text.charAt(i) != space) {sb.append(text.charAt(i));}}return sb.toString();}
}
类3:自定义控件类(核心类)
/*** 类:UserPhoneEditText* 作者: qxc* 日期:2018/3/2.*/
public class UserPhoneEditText extends RelativeLayout {private Context context;//上下文private boolean showTopMessage;//自定义属性:是否显示顶端提示信息(true:显示,false:不显示)private String topMessage;//自定义属性:顶端提示信息内容private String hint;//输入框提示信息private EditText et_phone;//电话号输入框private ImageView iv_phone_clear;//清空输入框的按钮private TextView tv_message;//输入框内的消息文本private TextView tv_to_message;//输入框外的消息文本private int duration = 200;//动画执行时间private int tvPosition = 0;//tv_message的当前位置,0:在输入框里;1:在tv_to_message的位置(执行动画前判断)private int[] position1 = new int[2];//tv_message的默认位置坐标private int[] position2 = new int[2];//tv_to_message的默认位置坐标private float[] fonts = new float[2];//tv_to_message的默认大小public UserPhoneEditText(Context context) {super(context);this.context = context;LoadView(context);}public UserPhoneEditText(Context context, AttributeSet attrs) {super(context, attrs);this.context = context;getAttrs(context,attrs);LoadView(context);}/*** 获得配置的自定义属性* @param context 上下文* @param attrs 属性集合*/private void getAttrs(Context context,AttributeSet attrs){//获得 在attrs.xml UserPhoneEditText中已定义的属性集合TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.user_phone_edittext);showTopMessage = typedArray.getBoolean(R.styleable.user_phone_edittext_showTopMessage, false);topMessage = typedArray.getString(R.styleable.user_phone_edittext_topMessage);hint = typedArray.getString(R.styleable.user_phone_edittext_hint);//释放typedArray.recycle();}/*** 初始化view* @param context 上下文*/private void LoadView(Context context){View view = LayoutInflater.from(context).inflate(R.layout.user_phone_edittext, this);initView(view);//初始化组件initEvent();//初始化事件}/*** 初始化组件*/private void initView(View view){et_phone = (EditText) view.findViewById(R.id.et_phone);iv_phone_clear = (ImageView) view.findViewById(R.id.iv_phone_clear);tv_message = (TextView) view.findViewById(R.id.tv_message);tv_to_message = (TextView) view.findViewById(R.id.tv_to_message);//根据自定义属性,显示组件//设置文本信息if(topMessage!=null){tv_to_message.setText(hint);}}/*** 初始化事件*/private void initEvent(){//清空输入框内容iv_phone_clear.setOnClickListener(new View.OnClickListener(){@Overridepublic void onClick(View view) {et_phone.setText("");}});//输入框内容变更事件//如果输入框开始输入字符,tv_message使用动画移动到tv_to_message的位置//如果输入框变成空,tv_message从tv_to_message的位置再移动回来et_phone.addTextChangedListener(new TextWatcher() {@Overridepublic void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}@Overridepublic void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}@Overridepublic void afterTextChanged(Editable editable) {String text = editable.toString();//如果输入框变成空,tv_message从tv_to_message的位置再移动回来if (text.length() == 0) {iv_phone_clear.setVisibility(View.INVISIBLE);//如果不显示顶部消息,也就不需要动画效果了if(!showTopMessage) {tv_to_message.setVisibility(VISIBLE);}else {tvPosition = 0;float startX = 0;float endX = position1[0] - position2[0];float startY = 0;float endY = position1[1] - position2[1];//执行动画startAnim(startX, endX, startY, endY, fonts[1], fonts[0]);tv_to_message.setText(hint);}}//如果输入框开始输入字符,tv_message使用动画移动到tv_to_message的位置else if (text.length() == 1 && tvPosition == 0) {iv_phone_clear.setVisibility(View.VISIBLE);if(!showTopMessage) {tv_to_message.setVisibility(INVISIBLE);}else {tvPosition = 1;float startX = position1[0] - position2[0];float endX = 0;float startY = position1[1] - position2[1];float endY = 0;//执行动画startAnim(startX, endX, startY, endY, fonts[0], fonts[1]);tv_to_message.setText(topMessage);}}//344电话格式处理PhoneFormat.onTextChanged344(et_phone,editable.toString());//回调if(et_phone.getText().length()==13&&onSuccessListener!=null){onSuccessListener.onSuccess(et_phone.getText().toString());}}});}/*** 自定义控件准备完毕,获得各组件的位置等数据*/@Overridepublic void onWindowFocusChanged(boolean hasWindowFocus) {super.onWindowFocusChanged(hasWindowFocus);//获得消息文本的位置信息tv_message.getLocationInWindow(position1);tv_to_message.getLocationOnScreen(position2);//获得消息文本的字号信息fonts[0] = PxUtils.px2sp(context,tv_message.getTextSize());fonts[1] = PxUtils.px2sp(context,tv_to_message.getTextSize());//初始化位置、字号(把tv_to_message设置的与tv_message显示一致)tv_to_message.setTextSize(fonts[0]);tv_to_message.setTranslationX(position1[0]-position2[0]);tv_to_message.setTranslationY(position1[1]-position2[1]);}/*** 播放动画* @param startX 开始X* @param endX 目标X* @param startY 开始Y* @param endY 目标Y* @param startFont 开始字号* @param endFont 目标字号*/private void startAnim(float startX, float endX, float startY, float endY, float startFont, float endFont){AnimatorSet set = new AnimatorSet();set.playTogether(ObjectAnimator.ofFloat(tv_to_message , "TranslationX" , startX , endX),ObjectAnimator.ofFloat(tv_to_message , "TranslationY" ,startY, endY),ObjectAnimator.ofFloat(tv_to_message , "TextSize" , startFont, endFont));set.setDuration(duration).start();}/*** 获得输入的电话号* @return 输入的电话号*/public String getPhone(){return PhoneFormat.getPhoneNumber(et_phone);}/*** 获得输入的电话号,用于显示* @return 输入的电话号 334格式*/public String getText(){return et_phone.getText().toString();}/*** 输入完成回调*/public interface OnSuccessListener{/*** 输入完成* @param phone 电话号*/void onSuccess(String phone);}private OnSuccessListener onSuccessListener;/*** 设置监听* @param onSuccessListener*/public void setOnSuccessListener(OnSuccessListener onSuccessListener){this.onSuccessListener = onSuccessListener;}
}
布局:user_phone_edittext.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="wrap_content"android:paddingLeft="10dip"android:paddingRight="10dip"><TextViewandroid:id="@+id/tv_to_message"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentTop="true"android:textColor="#999999"android:textSize="14sp"android:text="请输入手机号"android:visibility="visible"/><RelativeLayoutandroid:layout_width="match_parent"android:layout_height="40dp"android:id="@+id/rl"android:layout_below="@+id/tv_to_message"><ImageViewandroid:id="@+id/iv_phone_clear"android:layout_width="wrap_content"android:layout_height="wrap_content"android:layout_alignParentRight="true"android:layout_centerInParent="true"android:src="@mipmap/close_white"android:visibility="invisible" /><EditTextandroid:id="@+id/et_phone"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_centerVertical="true"android:layout_toLeftOf="@+id/iv_phone_clear"android:background="@null"android:inputType="phone"android:textColor="#2A2A2A"android:textColorHint="#999999"android:textSize="16sp" /><TextViewandroid:id="@+id/tv_message"android:layout_width="wrap_content"android:layout_height="wrap_content"android:clickable="false"android:textColor="#999999"android:gravity="center_vertical"android:layout_centerVertical="true"android:textSize="16sp"android:text="请输入手机号"android:visibility="invisible"/><Viewandroid:layout_width="match_parent"android:layout_height="1dp"android:layout_alignParentBottom="true"android:background="#EBEBEB" /></RelativeLayout>
</RelativeLayout>自定义属性attrs.xml
<?xml version="1.0" encoding="utf-8"?>
<resources><!--用户-手机号输入控件-自定义属性--><declare-styleable name="user.phone.edittext"><attr name="showTopMessage" format="boolean"/><attr name="topMessage" format="string"/><attr name="hint" format="string"/></declare-styleable>
</resources>
测试类MainActivity(测试调用自定义控件)
.xml
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:tools="http://schemas.android.com/tools"xmlns:userphoneedittext="http://schemas.android.com/apk/res-auto"android:id="@+id/activity_main"android:layout_width="match_parent"android:layout_height="match_parent"android:paddingBottom="@dimen/activity_vertical_margin"android:paddingLeft="@dimen/activity_horizontal_margin"android:paddingRight="@dimen/activity_horizontal_margin"android:paddingTop="@dimen/activity_vertical_margin"tools:context="iwangzhe.testcustomview.MainActivity"><Buttonandroid:layout_width="match_parent"android:layout_height="50dp"android:id="@+id/btn2"android:text="B"android:visibility="gone"/><Buttonandroid:layout_width="match_parent"android:layout_height="50dp"android:id="@+id/btn1"android:layout_below="@id/btn2"android:text="A"android:visibility="gone"/><iwangzhe.testcustomview.TestViewandroid:layout_below="@id/btn1"android:layout_width="match_parent"android:layout_height="20dp"android:id="@+id/tv1"/><iwangzhe.testcustomview.userphone.UserPhoneEditTextandroid:layout_width="match_parent"android:layout_height="wrap_content"android:layout_below="@id/tv1"android:id="@+id/upet1"userphoneedittext:showTopMessage="true"userphoneedittext:topMessage="测试消息信息"userphoneedittext:hint="默认hint消息信息"></iwangzhe.testcustomview.userphone.UserPhoneEditText><Buttonandroid:text="验证手机号"android:layout_width="match_parent"android:layout_height="wrap_content"android:layout_marginTop="18dp"android:id="@+id/btnPhone"android:layout_below="@+id/upet1"android:layout_centerHorizontal="true" />
</RelativeLayout>
自定义属性的使用,需要先设置命名控件:xmlns:userphoneedittext="http://schemas.android.com/apk/res-auto";
userphoneedittext可以自己定义,其他格式固定。
public class MainActivity extends BaseActivity {Button btn1;Button btn2;Button btnPhone;UserPhoneEditText upet;@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);btnPhone = (Button) findViewById(R.id.btnPhone);upet = (UserPhoneEditText) findViewById(R.id.upet1);//设置回调监听,获得输入完成的回调数据(被动回调)upet.setOnSuccessListener(new UserPhoneEditText.OnSuccessListener(){@Overridepublic void onSuccess(String phone) {Toast.makeText(MainActivity.this, phone, Toast.LENGTH_SHORT).show();}});//获得自定义控件文本信息(主动获取)btnPhone.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View view) {String text = upet.getPhone();Toast.makeText(MainActivity.this,text,Toast.LENGTH_SHORT).show();}});}
}
还有不明白的请看Demo,如果还是不明白请留言或者自行查询资料。
本文章,主要是为了让大家了解自定义控件的过程,如果想在自己的项目中使用,请根据需要自行调整优化。
Demo地址:
https://pan.baidu.com/s/1g5Ro3ZUWcdLwQezSmYFrBA
往期干货
1
MPAndroidChart 绘制曲线图总结
2
【需求解决系列一】之移动卡片实现答题功能
3
【年后第一篇】如何在复杂业务场景中优雅实现Android指纹验证?