介绍几个好用的android自定义控件详解手机开发

首先看效果图,

介绍几个好用的android自定义控件详解手机开发介绍几个好用的android自定义控件详解手机开发

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

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

下面直接看源码FlowRadioGroup了;

介绍几个好用的android自定义控件详解手机开发

复制代码
  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 missing 
135                 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 in 
176      * 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 id 
181      *            the unique id of the radio button to select in this group 
182      *  
183      * @see #getCheckedRadioButtonId() 
184      * @see #clearCheck() 
185      */ 
186     public void check(int id) { 
187         // don't even bother 
188         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. Upon 
220      * empty selection, the returned value is -1. 
221      * </p> 
222      *  
223      * @return the unique id of the selected radio button in this group 
224      *  
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 in 
235      * this group is selected and {@link #getCheckedRadioButtonId()} returns 
236      * 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 changes 
249      * in this group. 
250      * </p> 
251      *  
252      * @param listener 
253      *            the callback to call on checked state change 
254      */ 
255     public void setOnCheckedChangeListener(OnCheckedChangeListener listener) { 
256         mOnCheckedChangeListener = listener; 
257     } 
258  
259     /** 
260      * {@inheritDoc} 
261      */ 
262     @Override 
263     public LayoutParams generateLayoutParams(AttributeSet attrs) { 
264         return new FlowRadioGroup.LayoutParams(getContext(), attrs); 
265     } 
266  
267     /** 
268      * {@inheritDoc} 
269      */ 
270     @Override 
271     protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { 
272         return p instanceof FlowRadioGroup.LayoutParams; 
273     } 
274  
275     @Override 
276     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 the 
284      * children to {@link #WRAP_CONTENT} when they are not specified in the XML 
285      * 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 LinearLayout 
290      * Attributes} for a list of all child view attributes that this class 
291      * 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 to 
334          * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} and the 
335          * child's height to 
336          * {@link android.view.ViewGroup.LayoutParams#WRAP_CONTENT} when not 
337          * specified in the XML file. 
338          * </p> 
339          *  
340          * @param a 
341          *            the styled attributes set 
342          * @param widthAttr 
343          *            the width attribute to fetch 
344          * @param heightAttr 
345          *            the height attribute to fetch 
346          */ 
347         @Override 
348         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 radio 
368      * 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 selection 
375          * is cleared, checkedId is -1. 
376          * </p> 
377          *  
378          * @param group 
379          *            the group in which the checked radio button has changed 
380          * @param checkedId 
381          *            the unique identifier of the newly checked radio button 
382          */ 
383         public void onCheckedChanged(FlowRadioGroup group, int checkedId); 
384     } 
385  
386     private class CheckedStateTracker implements 
387             CompoundButton.OnCheckedChangeListener { 
388         public void onCheckedChanged(CompoundButton buttonView, 
389                 boolean isChecked) { 
390             // prevents from infinite recursion 
391             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 to 
409      * another listener. This allows the table layout to set its own internal 
410      * hierarchy change listener without preventing the user to setup his. 
411      * </p> 
412      */ 
413     private class PassThroughHierarchyChangeListener implements 
414             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 missing 
421                 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.this 
428                     && child instanceof ViewGroup) {// 如果是复合控件 
429                 // 查找并设置id 
430                 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.this 
442                     && 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的源码;

介绍几个好用的android自定义控件详解手机开发

复制代码
  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 Administrator 
105      *  
106      */ 
107     private class DivisionFocusChangeListener implements OnFocusChangeListener { 
108  
109         @Override 
110         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://blog.ytso.com/5676.html

(0)
上一篇 2021年7月17日
下一篇 2021年7月17日

相关推荐

发表回复

登录后才能评论