Android UI组件之自定义控件实现IP地址控件

  趁着时间挺充裕,就多写几篇博客。每一篇都是学习中的教训。今天在做东西的时候突然想到之前在MFC的时候都会有一个IP地址控件,可能是PC端用的比较多,但是在移动端好像基本没什么用处,但是偶尔也会有项目要用到,毕竟还是有些项目不需要接入互联网,只需要接入企业的内部网络。这个时候为了程序的通用性,我想到的第一个就是在程序中去配置一个网络环境,并将它保存到本地中,这样以后程序每次加载直接去本地中获取值。既然没有已有的控件,那么久自定义好了。存储在本地首先想到的就是sqlite和SharedPreferences,很明显这么点小东西还是用SharedPreferences又快又方便。那么按照老套路,先来规划一下程序的流程。

  既然要实现IP地址控件,那么首先就要考虑平常使用的习惯,通常我们输入IP地址,可以通过"."进入下一个编辑区域,也可以通过鼠标来直接跳到下一个区域,同时IP地址对数值的大小也有要求。必须是0-255之间。有了这个考虑就大概知道要处理什么事件。接下来就是开始实现。

  自定义IP地址控件ipedittext.xml,这里采用了四个EditText加三个TextView来实现控件。通过android:layout_weight属性让四个控件均匀的排列。

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
 3     android:layout_width="match_parent"
 4     android:layout_height="wrap_content"
 5     android:layout_marginTop="10dp"
 6     android:layout_marginLeft="10dp"
 7     android:layout_marginRight="10dp"
 8     android:paddingTop="10dp"
 9     android:paddingBottom="10dp"
10     android:orientation="horizontal"
11     android:background="@drawable/record_list_file_bg" >
12     <EditText
13         android:id="@+id/firstIPfield"
14         android:layout_width="0dp"
15         android:layout_height="wrap_content"
16         android:layout_weight="1"
17         android:gravity="center"
18         android:inputType="numberDecimal"
19         />
20     <TextView
21         android:layout_width="wrap_content"
22         android:layout_height="wrap_content"
23         android:text="."/>
24     <EditText
25         android:id="@+id/secondIPfield"
26         android:layout_width="0dp"
27         android:layout_height="wrap_content"
28         android:layout_weight="1"
29         android:gravity="center"
30         android:inputType="numberDecimal"
31         />
32     <TextView
33         android:layout_width="wrap_content"
34         android:layout_height="wrap_content"
35         android:text="."/>
36     <EditText
37         android:id="@+id/thirdIPfield"
38         android:layout_width="0dp"
39         android:layout_height="wrap_content"
40         android:layout_weight="1"
41         android:gravity="center"
42         android:inputType="numberDecimal"
43         />
44     <TextView
45         android:layout_width="wrap_content"
46         android:layout_height="wrap_content"
47         android:text="."/>
48     <EditText
49         android:id="@+id/fourthIPfield"
50         android:layout_width="0dp"
51         android:layout_height="wrap_content"
52         android:layout_weight="1"
53         android:gravity="center"
54         android:inputType="numberDecimal"
55         />
56
57 </LinearLayout>

  有了自定义的控件,接着就是实现自定义控件类IPEditText.java。

  1 import android.content.Context;
  2 import android.text.Editable;
  3 import android.text.TextUtils;
  4 import android.text.TextWatcher;
  5 import android.util.AttributeSet;
  6 import android.util.Log;
  7 import android.view.LayoutInflater;
  8 import android.view.View;
  9 import android.widget.EditText;
 10 import android.widget.LinearLayout;
 11 import android.widget.Toast;
 12
 13 public class IPEditText extends LinearLayout {
 14     private EditText firstIPEdit;
 15     private EditText secondIPEdit;
 16     private EditText thirdIPEdit;
 17     private EditText fourthIPEdit;
 18
 19     private String firstIP;
 20     private String secondIP;
 21     private String thirdIP;
 22     private String fourthIP;
 23
 24     public IPEditText(Context context, AttributeSet attrs) {
 25         super(context, attrs);
 26         // TODO Auto-generated constructor stub
 27         View view = LayoutInflater.from(context).inflate(R.layout.ipedittext, this);
 28
 29         firstIPEdit = (EditText) view.findViewById(R.id.firstIPfield);
 30         secondIPEdit = (EditText) view.findViewById(R.id.secondIPfield);
 31         thirdIPEdit = (EditText) view.findViewById(R.id.thirdIPfield);
 32         fourthIPEdit = (EditText) view.findViewById(R.id.fourthIPfield);
 33
 34         setIPEditTextListener(context);
 35     }
 36
 37     public void setIPEditTextListener(final Context context){
 38         //设置第一个字段的事件监听
 39         firstIPEdit.addTextChangedListener(new TextWatcher() {
 40             @Override
 41             public void onTextChanged(CharSequence s, int start, int before, int count) {
 42                 // TODO Auto-generated method stub
 43                 Log.i("test",s.toString());
 44                 if(null!=s && s.length()>0){
 45                     if(s.length() > 2 || s.toString().trim().contains(".")){
 46                         if(s.toString().trim().contains(".")){
 47                             firstIP = s.toString().trim().substring(0,s.length()-1);
 48                         }else{
 49                             firstIP = s.toString().trim();
 50                         }
 51                         if (Integer.parseInt(firstIP) > 255) {
 52                             Toast.makeText(context, "IP大小在0-255之间",
 53                                     Toast.LENGTH_LONG).show();
 54                             return;
 55                         }
 56                         secondIPEdit.setFocusable(true);
 57                         secondIPEdit.requestFocus();
 58                     }
 59                     else
 60                     {
 61                         firstIP = s.toString().trim();
 62                     }
 63                 }
 64             }
 65             @Override
 66             public void beforeTextChanged(CharSequence s, int start, int count,
 67                     int after) {
 68                 // TODO Auto-generated method stub
 69             }
 70             @Override
 71             public void afterTextChanged(Editable s) {
 72                 // TODO Auto-generated method stub
 73                 firstIPEdit.removeTextChangedListener(this);
 74                 firstIPEdit.setText(firstIP);
 75                 firstIPEdit.setSelection(firstIP.length());
 76                 firstIPEdit.addTextChangedListener(this);
 77             }
 78         });
 79         //设置第二个IP字段的事件监听
 80         secondIPEdit.addTextChangedListener(new TextWatcher() {
 81             @Override
 82             public void onTextChanged(CharSequence s, int start, int before, int count) {
 83                 // TODO Auto-generated method stub
 84                 if(null!=s && s.length()>0){
 85                     if(s.length() > 2 || s.toString().trim().contains(".")){
 86                         if(s.toString().trim().contains(".")){
 87                             secondIP = s.toString().trim().substring(0,s.length()-1);
 88
 89                         }else{
 90                             secondIP = s.toString().trim();
 91                         }
 92                         if (Integer.parseInt(secondIP) > 255) {
 93                             Toast.makeText(context, "IP大小在0-255之间",
 94                                     Toast.LENGTH_LONG).show();
 95                             return;
 96                         }
 97                         thirdIPEdit.setFocusable(true);
 98                         thirdIPEdit.requestFocus();
 99                     }
100                     else
101                     {
102                         secondIP = s.toString().trim();
103                     }
104
105                 }
106             }
107             @Override
108             public void beforeTextChanged(CharSequence s, int start, int count,
109                     int after) {
110                 // TODO Auto-generated method stub
111
112             }
113
114             @Override
115             public void afterTextChanged(Editable s) {
116                 // TODO Auto-generated method stub
117                 secondIPEdit.removeTextChangedListener(this);
118                 secondIPEdit.setText(secondIP);
119                 secondIPEdit.setSelection(secondIP.length());
120                 secondIPEdit.addTextChangedListener(this);
121             }
122         });
123         //设置第三个IP字段的事件监听
124         thirdIPEdit.addTextChangedListener(new TextWatcher() {
125
126             @Override
127             public void onTextChanged(CharSequence s, int start, int before, int count) {
128                 // TODO Auto-generated method stub
129
130                 if(null!=s && s.length()>0){
131                     if(s.length() > 2 || s.toString().trim().contains(".")){
132                         if(s.toString().trim().contains(".")){
133                             thirdIP = s.toString().trim().substring(0,s.length()-1);
134
135                         }else{
136                             thirdIP = s.toString().trim();
137                         }
138                         if (Integer.parseInt(thirdIP) > 255) {
139                             Toast.makeText(context, "IP大小在0-255之间",
140                                     Toast.LENGTH_LONG).show();
141                             return;
142                         }
143                         fourthIPEdit.setFocusable(true);
144                         fourthIPEdit.requestFocus();
145                     }else{
146                         thirdIP = s.toString().trim();
147                     }
148
149                 }
150             }
151             @Override
152             public void beforeTextChanged(CharSequence s, int start, int count,
153                     int after) {
154                 // TODO Auto-generated method stub
155
156             }
157
158             @Override
159             public void afterTextChanged(Editable s) {
160                 // TODO Auto-generated method stub
161                 thirdIPEdit.removeTextChangedListener(this);
162                 thirdIPEdit.setText(thirdIP);
163                 thirdIPEdit.setSelection(thirdIP.length());
164                 thirdIPEdit.addTextChangedListener(this);
165
166             }
167         });
168         //设置第四个IP字段的事件监听
169         fourthIPEdit.addTextChangedListener(new TextWatcher() {
170
171             @Override
172             public void onTextChanged(CharSequence s, int start, int before, int count) {
173                 // TODO Auto-generated method stub
174                 if(null!=s && s.length()>0){
175                     fourthIP = s.toString().trim();
176                     if (Integer.parseInt(fourthIP) > 255) {
177                         Toast.makeText(context, "请输入合法的ip地址", Toast.LENGTH_LONG)
178                                 .show();
179                         return;
180                     }
181                 }
182             }
183
184             @Override
185             public void beforeTextChanged(CharSequence s, int start, int count,
186                     int after) {
187                 // TODO Auto-generated method stub
188
189             }
190
191             @Override
192             public void afterTextChanged(Editable s) {
193                 // TODO Auto-generated method stub
194
195             }
196         });
197     }
198
199     public String getText(Context context) {
200         if (TextUtils.isEmpty(firstIP) || TextUtils.isEmpty(secondIP)
201                 || TextUtils.isEmpty(thirdIP) || TextUtils.isEmpty(fourthIP)) {
202             Toast.makeText(context, "请输入完整的IP", Toast.LENGTH_LONG).show();
203         }
204         return firstIP + "." + secondIP + "." + thirdIP + "." + fourthIP;
205     }
206 }

  代码有点多,那是因为前面三个控件的逻辑比较多,搞定一个另外两个修改一下就好了,最后一个EditText比较简单。简单的说明一下事件处理。通过在EditText上添加addTextChangedListener函数来这是一个监听器,观察控件内容的变化。参数是一个TextWatcher接口,实现他的三个方法就能够对控件内值得变化进行一个处理。

  对于控件中内容发生变化时,首先我们要判断新增的字符是不是".",和长度有没有超过3位,若果是其中的任意一种,就可以处理值并跳转到下一个EditText控件。如果都不是,那就可以直接获取控件的内容并设置显示。逻辑还是比较简单的。那么就这么简单的结束了吗?肯定不是。

  贴出来的代码肯定都是功能完善的代码,但是最初的代码肯定不是这样。对于头脑比较简单的我来说,在最初认为,既然EditText中的内容改变了,那我直接获取改变后的值然后立即通过EditText.setText来设置不就行了吗?所以最初afterTextChanged()中的firstIPEdit.setText(firstIP);其实是放在onTextChanged()中的。然后就出现了红色的error和不幸的程序崩溃了。查看了一下logcat日志,报的错误是stackoverflow(栈溢出),发现原来是函数堆栈溢出,进入了一个函数调用的死循环,这里用循环不恰当,准确的说是连锁调用,导致最终栈溢出,为什么?当一次改动发生后,判断值是合法有效的,然后调用EditText.setText(),因为setText()会去改变控件的内容,而控件此时正在被TextWatch监视着,于是又进入onTextChanged,然后再一次判断,再一次合法,再一次设置内容,再一次判断,于是程序一直向前走,函数堆栈越来越大最终溢出。

  现在知道是因为什么溢出的就来看看这个TextWatcher,既然能够通过addTextChangedListener添加一个TextWatcher进行监听,那么也应该可以remove掉,但是在TextWatcher这个实例的函数中remove它自己能成功吗?于是再一次去SDK中看看remove函数是怎么工作的。发现removeTextChangedListener和我们想的不太一样,他并不是在remove的时候就释放掉那个对象,而只是暂时把他从监视Textview类型(EditText是继承TextView)的对象中的一个队列中移除。函数介绍如下: 

public void removeTextChangedListener (TextWatcher watcher)

Added in API level 1
Removes the specified TextWatcher from the list of those whose methods are called whenever this TextView‘s text changes.

  于是来尝试将setText函数移动到afterTextChanged()函数中并加上removeTextChangedListener和addTextChangedListener。程序果然没有崩掉,但是奇怪的事情发生了。光标居然没有在后面反倒跑到内容前面,并且值得大小也不是123,而是321,在logcat中也能看出来:

    02-04 12:54:56.861: I/test(573): 1
    02-04 12:54:59.392: I/test(573): 21
    02-04 12:55:01.591: I/test(573): 321

  那么为什么会这样呢,原来在有内容的情况下EditText中的光标会在最前面,每一次输入都是往光标后输入,这样就会每输入一次在光标后添加上字符之后,光标又跑到最前面。在网上搜索一下,发现可以通过EditText.setSelection()函数解决,于是尝试之,果然奏效。程序正常。

  接下来就是写入SharedPreferences中,对于SharedPreferences的操作比较简单,首先是通过上下文环境去得到一个SharedPreferences文件的对象,然后通过获取这个对象的editor来对它进行操作。这里为了方便就先把另外两个参数写死。因为我的事件监听的类是单独写在一个包里的,所以上下文环境需要获取,如果是在同一个类里的话就不用我这么麻烦。记住一定要commit。

1 case R.id.config:
2             mAvtivity = MainActivity.get();
3             Context context = mAvtivity;
4             SharedPreferences sp = context.getSharedPreferences("config", Context.MODE_PRIVATE);
5             Editor editor = sp.edit();
6             editor.putString("server_ip", mAvtivity.getmIPEditText().getText(mAvtivity).toString());
7             editor.putInt("server_port", 8080);
8             editor.putBoolean("isserver", true);
9             editor.commit();

  结果如下图,成功保存,这样程序以后只需要在网络处理的类中去读取IP就可以了,端口和网站地址同上。

  最后一句话,保存的SharedPreferences文件本质上是一个XML文件,在/data/data/PACKAGE_NAME/shared_prefs/下,可以通过DDMS中的快捷功能把它从模拟器中拖出来。

  今天就到这吧。该去锻炼了。也祝大家都能有个好身体。

时间: 2024-10-16 08:30:17

Android UI组件之自定义控件实现IP地址控件的相关文章

Qt版IP地址控件

头文件 #ifndef IPADDRESS_H #define IPADDRESS_H #include <QWidget> #include <QLabel> #include <QLineEdit> #include <QHBoxLayout> class ipAddress : public QWidget { Q_OBJECT public: explicit ipAddress(QWidget *parent = 0); bool validChe

Android UI组件之EditText 实现网站注册效果的校验

时间过得太快,还没有什么感觉就到周末了,人生五十载,如梦亦如幻.不过我还没那么老.前两天曾说过,如果需要实现输入IP的功能,那么我们可以整一个自定义控件,然后对他进行事件监听,巴拉巴拉一大堆,好不容易做完了.后来想想,这是为了符合我们平常在PC上的习惯而去自定义的.那么如果只是单纯的为了输入一个IP地址,然后得到结果,不想去这么折腾,有没有什么好办法呢? 很显然是有的,那就今天就来看看Android最常用的组件之一的EditText,虽然之前用的也是EditText,但是侧重点其实是自定义控件,

Android UI组件进阶(2)——仿Windows对话框

Android UI组件进阶(2)--仿Windows对话框 在开始本章前先祝大家中秋节快乐哈,相信很多上班的朋友都是放三天假的哈! 有时间的话回家陪陪父母吧!树欲静而风不止,子欲养而亲不待!岁月不饶人! 好了,道理和祝福语就说到这里了,今天给大家准备的是模仿Windows风格对话框! 效果图: 相信大部分的AlertDialog都是下面这个样子的: 今天给大家讲解的对话框是下面这样的: 对比两种对话框,站在用户的角度,相信你更加钟情于第二种颜色鲜明的对话框 好了下面就开始讲解如何制作模仿win

Android UI编程之自定义控件初步(下)——CustomEditText

概述: 基于对上一篇博客<Android UI编程之自定义控件初步(上)--ImageButton>的学习,我们对自定义控件也有了一个初步的认识.那现在我们可以再试着对EditText进行一些自定义的学习.以下有两种方式的自定义UI编程分享给大家. 示例:带删除按钮的输入框 效果图展示:   基本雏形搭建: 大家可以从上面的效果图上看到两个东西:左侧的EditText和右侧的图片(这里是一个Button).我们在EditText中的输入为空的时候,不显示右侧的清除按钮.一旦EditText中输

Android 高级UI设计笔记08:Android开发者常用的7款Android UI组件(转载)

Android开发是目前最热门的移动开发技术之一,随着开发者的不断努力和Android社区的进步,Android开发技术已经日趋成熟,当然,在Android开源社区中也涌现了很多不错的开源UI项目,它们可以帮助Android开发者更方便快捷地完成想要的功能.本文是Android系列的第一篇,主要是向大家推荐一些常用的Android UI组件,它们都是开源的. 1.图表引擎 -  AChartEngine AChartEngine是一款基于Android的图表绘制引擎,它为Android开发者提供

Android UI 组件 &#187; GifView

GifView 是一个为了解决android中现在没有直接显示gif的view,只能通过mediaplay来显示这个问题的项目,其用法和 ImageView一样,支持gif图片 使用方法: 1-把GifView.jar加入你的项目. 2-在xml中配置GifView的基本属性,GifView继承自View类,和Button.ImageView一样是一个UI控件.如: <com.ant.liao.GifView android:id="@+id/gif2" android:layo

7款Android开发者常用的Android UI组件

7款Android开发者常用的Android UI组件 原文  http://news.cnblogs.com/n/506366/ Android 开发是目前最热门的移动开发技术之一,随着开发者的不断努力和 Android 社区的进步,Android 开发技术已经日趋成熟,当然,在 Android 开源社区中也涌现了很多不错的开源 UI 项目,它们可以帮助 Android 开发者更方便快捷地完成想要的功能.本文是 Android 系列的第一篇,主要是向大家推荐一些常用的 Android UI 组

android手机 ping 虚拟机ubuntu的ip地址

今天使用android手机往虚拟机上ubuntu 上搭建的nginx 和rtmp服务器推送东西的时候,怎么都推不上去. 后来在windows下的cmd里: # adb shell # ping 192.168.0.56 根本就ping不通 虚拟机的 ip ,发现虚拟机的ip和windows主机ip不再一个网端,所以怎么都不会ping通的. 后来发现VMware 的网络适配器选择的是NAT模式,这样是不行的. 需要更改为:桥接模式,然后编辑选项直接自动就行: ===================

Android UI组件----自定义ListView实现动态刷新

[声明] 欢迎转载,但请保留文章原始出处→_→ 生命壹号:http://www.cnblogs.com/smyhvae/ 文章来源:http://www.cnblogs.com/smyhvae/p/3910541.html 联系方式:[email protected] [正文] 一.具体步骤: (1)在activiy_main.xml中加一个ListView控件:再添加一个item的模板activity_main_item.xml,加一个底部加载的视图activity_main_load.xml