博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
介绍几个工作开发中封装的好用的android自定义控件
阅读量:5142 次
发布时间:2019-06-13

本文共 24125 字,大约阅读时间需要 80 分钟。

首先看效果图,

看下这两个界面,第一个中用到了一个自定义的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
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
(); 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 *

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 *

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 *

219 * Returns the identifier of the selected radio button in this group. Upon220 * empty selection, the returned value is -1.221 *

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 *

234 * Clears the selection. When the selection is cleared, no radio button in235 * this group is selected and {

@link #getCheckedRadioButtonId()} returns236 * null.237 *

238 * 239 * @see #check(int)240 * @see #getCheckedRadioButtonId()241 */242 public void clearCheck() {243 check(-1);244 }245 246 /**247 *

248 * Register a callback to be invoked when the checked radio button changes249 * in this group.250 *

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 *

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 *

287 * 288 *

289 * See {

@link android.R.styleable#LinearLayout_Layout LinearLayout290 * Attributes} for a list of all child view attributes that this class291 * supports.292 *

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 *

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 *

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 *

367 * Interface definition for a callback to be invoked when the checked radio368 * button changed in this group.369 *

370 */371 public interface OnCheckedChangeListener {372 /**373 *

374 * Called when the checked radio button has changed. When the selection375 * is cleared, checkedId is -1.376 *

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 *

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 *

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

转载于:https://www.cnblogs.com/Free-Thinker/p/3644297.html

你可能感兴趣的文章