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>
View Code

  有了自定義的控件,接着就是實現自定義控件類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中的快捷功能把它從模擬器中拖出來。

  今天就到這吧。該去鍛煉了。也祝大家都能有個好身體。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM