介绍几个好用的android自定义控件

来源:互联网 发布:mac玩魔兽世界怎么样 编辑:程序博客网 时间:2024/06/05 00:44

首先看效果图,

看下这两个界面,第一个中用到了一个自定义的FlowRadioGroup,支持复合子控件,自定义布局;

第二个界面中看到了输入的数字 自动4位分割了吧;也用到了自定义的DivisionEditText控件。

下面直接看源码FlowRadioGroup了;

复制代码
  1 /*  2  * Copyright (C) 2006 The Android Open Source Project  3  *  4  * Licensed under the Apache License, Version 2.0 (the "License");  5  * you may not use this file except in compliance with the License.  6  * You may obtain a copy of the License at  7  *  8  *      http://www.apache.org/licenses/LICENSE-2.0  9  * 10  * Unless required by applicable law or agreed to in writing, software 11  * distributed under the License is distributed on an "AS IS" BASIS, 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13  * See the License for the specific language governing permissions and 14  * limitations under the License. 15  */ 16  17 package com.newgame.sdk.view; 18  19 import java.util.ArrayList; 20  21 import android.content.Context; 22 import android.content.res.TypedArray; 23 import android.util.AttributeSet; 24 import android.view.View; 25 import android.view.ViewGroup; 26 import android.widget.CompoundButton; 27 import android.widget.LinearLayout; 28 import android.widget.RadioButton; 29  30 /** 可以放多种布局控件,能找到radiobutton */ 31 public class FlowRadioGroup extends LinearLayout { 32     // holds the checked id; the selection is empty by default 33     private int mCheckedId = -1; 34     // tracks children radio buttons checked state 35     private CompoundButton.OnCheckedChangeListener mChildOnCheckedChangeListener; 36     // when true, mOnCheckedChangeListener discards events 37     private boolean mProtectFromCheckedChange = false; 38     private OnCheckedChangeListener mOnCheckedChangeListener; 39     private PassThroughHierarchyChangeListener mPassThroughListener; 40  41     // 存放当前的radioButton 42     private ArrayList<RadioButton> radioButtons; 43  44     public FlowRadioGroup(Context context) { 45         super(context); 46         setOrientation(VERTICAL); 47         init(); 48     } 49  50     public FlowRadioGroup(Context context, AttributeSet attrs) { 51         super(context, attrs); 52         init(); 53     } 54  55     private void init() { 56         mChildOnCheckedChangeListener = new CheckedStateTracker(); 57         mPassThroughListener = new PassThroughHierarchyChangeListener(); 58         super.setOnHierarchyChangeListener(mPassThroughListener); 59         radioButtons = new ArrayList<RadioButton>(); 60     } 61  62     @Override 63     public void setOnHierarchyChangeListener(OnHierarchyChangeListener listener) { 64         // the user listener is delegated to our pass-through listener 65         mPassThroughListener.mOnHierarchyChangeListener = listener; 66     } 67  68     @Override 69     protected void onFinishInflate() { 70         super.onFinishInflate(); 71  72         // checks the appropriate radio button as requested in the XML file 73         if (mCheckedId != -1) { 74             mProtectFromCheckedChange = true; 75             setCheckedStateForView(mCheckedId, true); 76             mProtectFromCheckedChange = false; 77             setCheckedId(mCheckedId); 78         } 79     } 80  81     @Override 82     public void addView(View child, int index, ViewGroup.LayoutParams params) { 83         if (child instanceof RadioButton) { 84             final RadioButton button = (RadioButton) child; 85             radioButtons.add(button); 86  87             if (button.isChecked()) { 88                 mProtectFromCheckedChange = true; 89                 if (mCheckedId != -1) { 90                     setCheckedStateForView(mCheckedId, false); 91                 } 92                 mProtectFromCheckedChange = false; 93                 setCheckedId(button.getId()); 94             } 95         } else if (child instanceof ViewGroup) {// 如果是复合控件 96             // 遍历复合控件 97             ViewGroup vg = ((ViewGroup) child); 98             setCheckedView(vg); 99         }100 101         super.addView(child, index, params);102     }103 104     /** 查找复合控件并设置radiobutton */105     private void setCheckedView(ViewGroup vg) {106         int len = vg.getChildCount();107         for (int i = 0; i < len; i++) {108             if (vg.getChildAt(i) instanceof RadioButton) {// 如果找到了,就设置check状态109                 final RadioButton button = (RadioButton) vg.getChildAt(i);110                 // 添加到容器111                 radioButtons.add(button);112                 if (button.isChecked()) {113                     mProtectFromCheckedChange = true;114                     if (mCheckedId != -1) {115                         setCheckedStateForView(mCheckedId, false);116                     }117                     mProtectFromCheckedChange = false;118                     setCheckedId(button.getId());119                 }120             } else if (vg.getChildAt(i) instanceof ViewGroup) {// 迭代查找并设置121                 ViewGroup childVg = (ViewGroup) vg.getChildAt(i);122                 setCheckedView(childVg);123             }124         }125     }126 127     /** 查找复合控件并设置id */128     private void setCheckedId(ViewGroup vg) {129         int len = vg.getChildCount();130         for (int i = 0; i < len; i++) {131             if (vg.getChildAt(i) instanceof RadioButton) {// 如果找到了,就设置check状态132                 final RadioButton button = (RadioButton) vg.getChildAt(i);133                 int id = button.getId();134                 // generates an id if it's missing135                 if (id == View.NO_ID) {136                     id = button.hashCode();137                     button.setId(id);138                 }139                 button.setOnCheckedChangeListener(mChildOnCheckedChangeListener);140             } else if (vg.getChildAt(i) instanceof ViewGroup) {// 迭代查找并设置141                 ViewGroup childVg = (ViewGroup) vg.getChildAt(i);142                 setCheckedId(childVg);143             }144         }145     }146 147     /** 查找radioButton控件 */148     public RadioButton findRadioButton(ViewGroup group) {149         RadioButton resBtn = null;150         int len = group.getChildCount();151         for (int i = 0; i < len; i++) {152             if (group.getChildAt(i) instanceof RadioButton) {153                 resBtn = (RadioButton) group.getChildAt(i);154             } else if (group.getChildAt(i) instanceof ViewGroup) {155                 resBtn = findRadioButton((ViewGroup) group.getChildAt(i));156                 findRadioButton((ViewGroup) group.getChildAt(i));157                 break;158             }159         }160         return resBtn;161     }162 163     /** 返回当前radiobutton控件的count */164     public int getRadioButtonCount() {165         return radioButtons.size();166     }167 168     /** 返回当前index的radio */169     public RadioButton getRadioButton(int index) {170         return radioButtons.get(index);171     }    172 173     /**174      * <p>175      * Sets the selection to the radio button whose identifier is passed in176      * parameter. Using -1 as the selection identifier clears the selection;177      * such an operation is equivalent to invoking {@link #clearCheck()}.178      * </p>179      * 180      * @param id181      *            the unique id of the radio button to select in this group182      * 183      * @see #getCheckedRadioButtonId()184      * @see #clearCheck()185      */186     public void check(int id) {187         // don't even bother188         if (id != -1 && (id == mCheckedId)) {189             return;190         }191 192         if (mCheckedId != -1) {193             setCheckedStateForView(mCheckedId, false);194         }195 196         if (id != -1) {197             setCheckedStateForView(id, true);198         }199 200         setCheckedId(id);201     }202 203     private void setCheckedId(int id) {204         mCheckedId = id;205         if (mOnCheckedChangeListener != null) {206             mOnCheckedChangeListener.onCheckedChanged(this, mCheckedId);207         }208     }209 210     private void setCheckedStateForView(int viewId, boolean checked) {211         View checkedView = findViewById(viewId);212         if (checkedView != null && checkedView instanceof RadioButton) {213             ((RadioButton) checkedView).setChecked(checked);214         }215     }216 217     /**218      * <p>219      * Returns the identifier of the selected radio button in this group. Upon220      * empty selection, the returned value is -1.221      * </p>222      * 223      * @return the unique id of the selected radio button in this group224      * 225      * @see #check(int)226      * @see #clearCheck()227      */228     public int getCheckedRadioButtonId() {229         return mCheckedId;230     }231 232     /**233      * <p>234      * Clears the selection. When the selection is cleared, no radio button in235      * this group is selected and {@link #getCheckedRadioButtonId()} returns236      * null.237      * </p>238      * 239      * @see #check(int)240      * @see #getCheckedRadioButtonId()241      */242     public void clearCheck() {243         check(-1);244     }245 246     /**247      * <p>248      * Register a callback to be invoked when the checked radio button changes249      * in this group.250      * </p>251      * 252      * @param listener253      *            the callback to call on checked state change254      */255     public void setOnCheckedChangeListener(OnCheckedChangeListener listener) {256         mOnCheckedChangeListener = listener;257     }258 259     /**260      * {@inheritDoc}261      */262     @Override263     public LayoutParams generateLayoutParams(AttributeSet attrs) {264         return new FlowRadioGroup.LayoutParams(getContext(), attrs);265     }266 267     /**268      * {@inheritDoc}269      */270     @Override271     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {272         return p instanceof FlowRadioGroup.LayoutParams;273     }274 275     @Override276     protected LinearLayout.LayoutParams generateDefaultLayoutParams() {277         return new LayoutParams(LayoutParams.WRAP_CONTENT,278                 LayoutParams.WRAP_CONTENT);279     }280 281     /**282      * <p>283      * This set of layout parameters defaults the width and the height of the284      * children to {@link #WRAP_CONTENT} when they are not specified in the XML285      * file. Otherwise, this class ussed the value read from the XML file.286      * </p>287      * 288      * <p>289      * See {@link android.R.styleable#LinearLayout_Layout LinearLayout290      * Attributes} for a list of all child view attributes that this class291      * supports.292      * </p>293      * 294      */295     public static class LayoutParams extends LinearLayout.LayoutParams {296         /**297          * {@inheritDoc}298          */299         public LayoutParams(Context c, AttributeSet attrs) {300             super(c, attrs);301         }302 303         /**304          * {@inheritDoc}305          */306         public LayoutParams(int w, int h) {307             super(w, h);308         }309 310         /**311          * {@inheritDoc}312          */313         public LayoutParams(int w, int h, float initWeight) {314             super(w, h, initWeight);315         }316 317         /**318          * {@inheritDoc}319          */320         public LayoutParams(ViewGroup.LayoutParams p) {321             super(p);322         }323 324         /**325          * {@inheritDoc}326          */327         public LayoutParams(MarginLayoutParams source) {328             super(source);329         }330 331         /**332          * <p>333          * Fixes the child's width to334          * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the335          * child's height to336          * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} when not337          * specified in the XML file.338          * </p>339          * 340          * @param a341          *            the styled attributes set342          * @param widthAttr343          *            the width attribute to fetch344          * @param heightAttr345          *            the height attribute to fetch346          */347         @Override348         protected void setBaseAttributes(TypedArray a, int widthAttr,349                 int heightAttr) {350 351             if (a.hasValue(widthAttr)) {352                 width = a.getLayoutDimension(widthAttr, "layout_width");353             } else {354                 width = WRAP_CONTENT;355             }356 357             if (a.hasValue(heightAttr)) {358                 height = a.getLayoutDimension(heightAttr, "layout_height");359             } else {360                 height = WRAP_CONTENT;361             }362         }363     }364 365     /**366      * <p>367      * Interface definition for a callback to be invoked when the checked radio368      * button changed in this group.369      * </p>370      */371     public interface OnCheckedChangeListener {372         /**373          * <p>374          * Called when the checked radio button has changed. When the selection375          * is cleared, checkedId is -1.376          * </p>377          * 378          * @param group379          *            the group in which the checked radio button has changed380          * @param checkedId381          *            the unique identifier of the newly checked radio button382          */383         public void onCheckedChanged(FlowRadioGroup group, int checkedId);384     }385 386     private class CheckedStateTracker implements387             CompoundButton.OnCheckedChangeListener {388         public void onCheckedChanged(CompoundButton buttonView,389                 boolean isChecked) {390             // prevents from infinite recursion391             if (mProtectFromCheckedChange) {392                 return;393             }394 395             mProtectFromCheckedChange = true;396             if (mCheckedId != -1) {397                 setCheckedStateForView(mCheckedId, false);398             }399             mProtectFromCheckedChange = false;400 401             int id = buttonView.getId();402             setCheckedId(id);403         }404     }405 406     /**407      * <p>408      * A pass-through listener acts upon the events and dispatches them to409      * another listener. This allows the table layout to set its own internal410      * hierarchy change listener without preventing the user to setup his.411      * </p>412      */413     private class PassThroughHierarchyChangeListener implements414             ViewGroup.OnHierarchyChangeListener {415         private ViewGroup.OnHierarchyChangeListener mOnHierarchyChangeListener;416 417         public void onChildViewAdded(View parent, View child) {418             if (parent == FlowRadioGroup.this && child instanceof RadioButton) {419                 int id = child.getId();420                 // generates an id if it's missing421                 if (id == View.NO_ID) {422                     id = child.hashCode();423                     child.setId(id);424                 }425                 ((RadioButton) child)426                         .setOnCheckedChangeListener(mChildOnCheckedChangeListener);427             } else if (parent == FlowRadioGroup.this428                     && child instanceof ViewGroup) {// 如果是复合控件429                 // 查找并设置id430                 setCheckedId((ViewGroup) child);431             }432 433             if (mOnHierarchyChangeListener != null) {434                 mOnHierarchyChangeListener.onChildViewAdded(parent, child);435             }436         }437 438         public void onChildViewRemoved(View parent, View child) {439             if (parent == FlowRadioGroup.this && child instanceof RadioButton) {440                 ((RadioButton) child).setOnCheckedChangeListener(null);441             } else if (parent == FlowRadioGroup.this442                     && child instanceof ViewGroup) {443                 findRadioButton((ViewGroup) child).setOnCheckedChangeListener(444                         null);445             }446             if (mOnHierarchyChangeListener != null) {447                 mOnHierarchyChangeListener.onChildViewRemoved(parent, child);448             }449         }450     }451 }
复制代码

简单讲解下我的实现:

1)在addview方法中,加上判断,当前子控件是否为viewgroup类型

复制代码
@Override    public void addView(View child, int index, ViewGroup.LayoutParams params) {        if (child instanceof RadioButton) {            final RadioButton button = (RadioButton) child;            radioButtons.add(button);//将找到的控件添加到集合中            if (button.isChecked()) {                mProtectFromCheckedChange = true;                if (mCheckedId != -1) {                    setCheckedStateForView(mCheckedId, false);                }                mProtectFromCheckedChange = false;                setCheckedId(button.getId());            }        } else if (child instanceof ViewGroup) {// 如果是复合控件            // 遍历复合控件            ViewGroup vg = ((ViewGroup) child);            setCheckedView(vg);        }        super.addView(child, index, params);    }    /** 查找复合控件并设置radiobutton */    private void setCheckedView(ViewGroup vg) {        int len = vg.getChildCount();        for (int i = 0; i < len; i++) {            if (vg.getChildAt(i) instanceof RadioButton) {// 如果找到了,就设置check状态                final RadioButton button = (RadioButton) vg.getChildAt(i);                // 添加到容器                radioButtons.add(button);                if (button.isChecked()) {                    mProtectFromCheckedChange = true;                    if (mCheckedId != -1) {                        setCheckedStateForView(mCheckedId, false);                    }                    mProtectFromCheckedChange = false;                    setCheckedId(button.getId());                }            } else if (vg.getChildAt(i) instanceof ViewGroup) {// 迭代查找并设置                ViewGroup childVg = (ViewGroup) vg.getChildAt(i);                setCheckedView(childVg);            }        }    }
复制代码

 

2)定义一个数组存放当前所有查到到的radiobutton;

3)在onChildViewAdded方法中,判断新添加的子控件是否为viewgroup类型

1
2
3
4
5
else if (parent == FlowRadioGroup.this
                    && child instanceof ViewGroup) {// 如果是复合控件
                // 查找并设置id
                setCheckedId((ViewGroup) child);
            }

  

复制代码
/** 查找复合控件并设置id */    private void setCheckedId(ViewGroup vg) {        int len = vg.getChildCount();        for (int i = 0; i < len; i++) {            if (vg.getChildAt(i) instanceof RadioButton) {// 如果找到了,就设置check状态                final RadioButton button = (RadioButton) vg.getChildAt(i);                int id = button.getId();                // generates an id if it's missing                if (id == View.NO_ID) {                    id = button.hashCode();                    button.setId(id);                }                button.setOnCheckedChangeListener(mChildOnCheckedChangeListener);            } else if (vg.getChildAt(i) instanceof ViewGroup) {// 迭代查找并设置                ViewGroup childVg = (ViewGroup) vg.getChildAt(i);                setCheckedId(childVg);            }        }    }
复制代码

 

下面是DivisionEditText的源码;

复制代码
  1 package com.newgame.sdk.view;  2   3 import android.content.Context;  4 import android.text.Editable;  5 import android.text.TextWatcher;  6 import android.util.AttributeSet;  7 import android.view.View;  8 import android.widget.EditText;  9  10 /** 11  * 分割输入框 12  *  13  * @author Administrator 14  *  15  */ 16 public class DivisionEditText extends EditText { 17  18     /* 每组的长度 */ 19     private Integer eachLength = 4; 20     /* 分隔符 */ 21     private String delimiter = " "; 22  23     private String text = ""; 24  25     public DivisionEditText(Context context) { 26         super(context); 27         init(); 28     } 29  30     public DivisionEditText(Context context, AttributeSet attrs) { 31         super(context, attrs); 32         init(); 33  34     } 35  36     public DivisionEditText(Context context, AttributeSet attrs, int defStyle) { 37         super(context, attrs, defStyle); 38         init(); 39     } 40  41     /** 42      * 初始化 43      */ 44     public void init() { 45  46         // 内容变化监听 47         this.addTextChangedListener(new DivisionTextWatcher()); 48         // 获取焦点监听 49         this.setOnFocusChangeListener(new DivisionFocusChangeListener()); 50     } 51  52     /** 53      * 文本监听 54      *  55      * @author Administrator 56      *  57      */ 58     private class DivisionTextWatcher implements TextWatcher { 59  60         @Override 61         public void afterTextChanged(Editable s) { 62         } 63  64         @Override 65         public void beforeTextChanged(CharSequence s, int start, int count, 66                 int after) { 67         } 68  69         @Override 70         public void onTextChanged(CharSequence s, int start, int before, 71                 int count) { 72             // 统计个数 73             int len = s.length(); 74             if (len < eachLength)// 长度小于要求的数 75                 return; 76             if (count > 1) { 77                 return; 78             } 79             // 如果包含空格,就清除 80             char[] chars = s.toString().replace(" ", "").toCharArray(); 81             len = chars.length; 82             // 每4个分组,加上空格组合成新的字符串 83             StringBuffer sb = new StringBuffer(); 84             for (int i = 0; i < len; i++) { 85                 if (i % eachLength == 0 && i != 0)// 每次遍历到4的倍数,就添加一个空格 86                 { 87                     sb.append(" "); 88                     sb.append(chars[i]);// 添加字符 89                 } else { 90                     sb.append(chars[i]);// 添加字符 91                 } 92             } 93             // 设置新的字符到文本 94             // System.out.println("*************" + sb.toString()); 95             text = sb.toString(); 96             setText(text); 97             setSelection(text.length()); 98         } 99     }100 101     /**102      * 获取焦点监听103      * 104      * @author Administrator105      * 106      */107     private class DivisionFocusChangeListener implements OnFocusChangeListener {108 109         @Override110         public void onFocusChange(View v, boolean hasFocus) {111             if (hasFocus) {112                 // 设置焦点113                 setSelection(getText().toString().length());114             }115         }116     }117 118     /** 得到每组个数 */119     public Integer getEachLength() {120         return eachLength;121     }122 123     /** 设置每组个数 */124     public void setEachLength(Integer eachLength) {125         this.eachLength = eachLength;126     }127 128     /** 得到间隔符 */129     public String getDelimiter() {130         return delimiter;131     }132 133     /** 设置间隔符 */134     public void setDelimiter(String delimiter) {135         this.delimiter = delimiter;136     }137 138 }
复制代码

上面代码实现逻辑:在TextWatcher的onTextChanged方法中判断当前输入的字符,然后没4位添加一个空格,组成新的字符

复制代码
@Override        public void onTextChanged(CharSequence s, int start, int before,                int count) {            // 统计个数            int len = s.length();            if (len < eachLength)// 长度小于要求的数                return;            if (count > 1) {// 设置新字符串的时候,直接返回                return;            }            // 如果包含空格,就清除            char[] chars = s.toString().replace(" ", "").toCharArray();            len = chars.length;            // 每4个分组,加上空格组合成新的字符串            StringBuffer sb = new StringBuffer();            for (int i = 0; i < len; i++) {                if (i % eachLength == 0 && i != 0)// 每次遍历到4的倍数,就添加一个空格                {                    sb.append(" ");                    sb.append(chars[i]);// 添加字符                } else {                    sb.append(chars[i]);// 添加字符                }            }            // 设置新的字符到文本            // System.out.println("*************" + sb.toString());            text = sb.toString();            setText(text);            setSelection(text.length());        }
复制代码

 

还有其他两个自定义控件也在项目中,这里界面没体现出来,我已经放在项目中了;

欢迎大家找出代码中的存在bug!!!!

最后附上代码下载地址:http://www.eoeandroid.com/forum.php?mod=attachment&aid=MTIwMDM1fDM5NTYzZjQ3fDEzOTY0Mjc4NDF8NzU4MzI1fDMyODQyNw%3D%3D

0 0