当前位置:主页 > 移动开发 > Android代码 >

功能强大的登录界面Android实现代码

时间:2021-12-17 10:05:01 | 栏目:Android代码 | 点击:

前言

     一个好的应用需要一个有良好的用户体验的登录界面,现如今,许多应用的的登录界面都有着用户名,密码一键删除,用户名,密码为空提示,以及需要输入验证码的功能。看着csdn上的大牛们的文章,心里想着也写一个登录界面学习学习,许多东西都是参考别的文章,综合起来的。废话少说,接下来看看是如何实现的。 

ps:由于懒得抠图。所以程序的图标很难看。
程序运行时的图示:

首先是布局文件没有什么难度。

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" 
 xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" 
 android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin" 
 android:paddingRight="@dimen/activity_horizontal_margin" 
 android:paddingTop="@dimen/activity_vertical_margin" 
 android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity"> 
 
 <ImageView 
 android:id="@+id/tv_login" 
 android:src="@drawable/ic_launcher" 
 android:layout_width="match_parent" 
 android:layout_height="wrap_content" 
 android:layout_alignParentTop="true" 
 android:gravity="center" 
 /> 
 <com.example.administrator.texttest.DeletableEditText 
 android:id="@+id/tv_user" 
 android:layout_width="match_parent" 
 android:layout_height="wrap_content" 
 android:textSize="30dp" 
 android:layout_below="@id/tv_login" 
 android:drawableLeft="@drawable/ic_launcher" 
 android:drawableRight="@drawable/ic_launcher" 
 android:hint="请输入账户" 
 android:ems="10"/> 
 <com.example.administrator.texttest.DeletableEditText 
 android:id="@+id/tv_psd" 
 android:layout_width="match_parent" 
 android:layout_height="wrap_content" 
 android:textSize="30dp" 
 android:layout_below="@id/tv_user" 
 android:drawableLeft="@drawable/ic_launcher" 
 android:drawableRight="@drawable/ic_launcher" 
 android:hint="请输入密码" 
 android:inputType="textPassword" 
 android:ems="10"/> 
 <LinearLayout 
 android:id="@+id/lyYanzhengma" 
 android:orientation="horizontal" 
 android:layout_width="match_parent" 
 android:layout_height="wrap_content" 
 
 android:layout_below="@id/tv_psd"> 
 <LinearLayout 
  android:id="@+id/lyVerify" 
  android:orientation="horizontal" 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content"> 
  <TextView 
  android:id="@+id/tvHideA" 
  android:layout_width="70dp" 
  android:layout_height="70dp" 
  android:visibility="gone" 
  android:gravity="center" 
  android:textSize="30dp" 
  /> 
  <TextView 
  android:id="@+id/tvHideB" 
  android:layout_width="70dp" 
  android:layout_height="70dp" 
  android:visibility="gone" 
  android:gravity="center" 
  android:textSize="30dp" 
  /> 
  <TextView 
  android:id="@+id/tvHideC" 
  android:layout_width="70dp" 
  android:layout_height="70dp" 
  android:visibility="gone" 
  android:gravity="center" 
  android:textSize="30dp" 
  /> 
  <TextView 
  android:id="@+id/tvHideD" 
  android:layout_width="70dp" 
  android:layout_height="70dp" 
  android:visibility="gone" 
  android:gravity="center" 
  android:textSize="30dp" 
  /> 
 </LinearLayout> 
 <LinearLayout 
  android:id="@+id/IV_num" 
  android:orientation="horizontal" 
  android:layout_width="wrap_content" 
  android:layout_height="wrap_content"> 
  <ImageView 
  android:layout_height="70dp" 
  android:layout_width="50dp" 
  android:id="@+id/ivNumA"/> 
  <ImageView 
  android:layout_height="70dp" 
  android:layout_width="50dp" 
  android:id="@+id/ivNumB"/> 
  <ImageView 
  android:layout_height="70dp" 
  android:layout_width="50dp" 
  android:id="@+id/ivNumC"/> 
  <ImageView 
  android:layout_height="70dp" 
  android:layout_width="50dp" 
  android:id="@+id/ivNumD"/> 
 </LinearLayout> 
 
 <LinearLayout 
  android:orientation="horizontal" 
  android:layout_height="wrap_content" 
  android:layout_width="match_parent"> 
  <EditText 
  android:layout_height="wrap_content" 
  android:layout_width="120dp" 
  android:textSize="30dp" 
  android:id="@+id/etCheck" 
  android:maxLength="4" 
  android:singleLine="true" 
  android:hint="验证码"/> 
  <TextView 
  android:layout_height="wrap_content" 
  android:layout_width="wrap_content" 
  android:text="结果" 
  android:id="@+id/tvCheck" 
  android:textSize="30dp" 
  android:visibility="gone"/> 
 </LinearLayout> 
 
  
 </LinearLayout> 
 <Button 
 android:id="@+id/bt_login" 
 android:text="登 录" 
 android:textSize="30dp" 
 android:layout_below="@id/lyYanzhengma" 
 android:layout_width="match_parent" 
 android:layout_height="wrap_content" /> 
 
</RelativeLayout> 

然后是加载一些布局文件,和一些控件的初始化

//登录按钮 
 private Button btLogin; 
 //账户 
 private DeletableEditText userEditText; 
 //密码 
 private DeletableEditText psdEditText; 
 
 
 //验证码的数字文本 
 private TextView tvHideA,tvHideB,tvHideC,tvHideD; 
 
 //验证码的图片文本 
 private ImageView ivNumA,ivNumB,ivNumC,ivNumD; 
 //验证码输入文本 
 private EditText etCheck; 
 //验证码的检测显示文本 
 private TextView tvCheck; 
 
 //存储每个验证码的数字 
 private String numStrTmp = ""; 
 //存储整个验证码的数字 
 private String numStr = ""; 
 
 //存储验证码的数组 
 private int[] numArray = new int[4]; 
 //存储颜色的数组 
 private int[] colorArray = new int[6]; 
 
 @Override 
 protected void onCreate(Bundle savedInstanceState) { 
 super.onCreate(savedInstanceState); 
 setContentView(R.layout.activity_main); 
 setupViews(); 
 
 } 
 
 private void setupViews() { 
 btLogin = (Button) findViewById(R.id.bt_login); 
 btLogin.setOnClickListener(new OnClickListenerImpl()); 
 userEditText = (DeletableEditText) findViewById(R.id.tv_user); 
 psdEditText = (DeletableEditText) findViewById(R.id.tv_psd); 
 
 
 tvHideA = (TextView) findViewById(R.id.tvHideA); 
 tvHideB = (TextView) findViewById(R.id.tvHideB); 
 tvHideC = (TextView) findViewById(R.id.tvHideC); 
 tvHideD = (TextView) findViewById(R.id.tvHideD); 
 
 ivNumA = (ImageView) findViewById(R.id.ivNumA); 
 ivNumB = (ImageView) findViewById(R.id.ivNumB); 
 ivNumC = (ImageView) findViewById(R.id.ivNumC); 
 ivNumD = (ImageView) findViewById(R.id.ivNumD); 
 
 ivNumA.setOnClickListener(new OnClickListenerImpl()); 
 ivNumB.setOnClickListener(new OnClickListenerImpl()); 
 ivNumC.setOnClickListener(new OnClickListenerImpl()); 
 ivNumD.setOnClickListener(new OnClickListenerImpl()); 
 
 tvCheck = (TextView) findViewById(R.id.tvCheck); 
 etCheck = (EditText) findViewById(R.id.etCheck); 
 
 setNum(); 

自定义EditText的实现过程:

思路:设置两个EidtText,在这个EditText中各设置图标。左边图标为账户和密码的图标提示,右边图标为一键删除 。因为EditText中的图标没有onClick事件,为了实现点击一键删除效果所以要使用OnTouchEvent回调方法,监听点击事件来判断实现一键删除。当账户和密码没有字符时,右边的一键删除图标设置隐藏,当有字符时,设置图标显示。当点击到右边图标范围时,删除所在行的字符。从而实现一键删除。 此外,当账户和密码为空而要登录时。这两行抖动提示。
废话不多说,直接上代码注释很清楚。

package com.example.administrator.texttest; 
 
import android.content.Context; 
import android.graphics.drawable.Drawable; 
import android.text.Editable; 
import android.text.TextWatcher; 
import android.util.AttributeSet; 
import android.view.MotionEvent; 
import android.view.View; 
import android.view.animation.Animation; 
import android.view.animation.CycleInterpolator; 
import android.view.animation.TranslateAnimation; 
import android.widget.EditText; 
 
/** 
 * Created by Administrator on 2015-10-10. 
 */ 
public class DeletableEditText extends EditText { 
 
 private Drawable mRightDrawable; 
 private boolean isHasFocus; 
 
 
 
 public DeletableEditText(Context context) { 
 this(context, null); 
 } 
 
 public DeletableEditText(Context context, AttributeSet attrs) { 
 this(context, attrs, android.R.attr.editTextStyle); 
 } 
 
 public DeletableEditText(Context context, AttributeSet attrs, int defStyleAttr) { 
 super(context, attrs, defStyleAttr); 
 setupViews(); 
 } 
 
 private void setupViews() { 
 
 //取的view的上下左右边距 
 Drawable[] drawables = this.getCompoundDrawables(); 
 
 // 取得right位置的Drawable 
 // 即我们在布局文件中设置的android:drawableRight 
 mRightDrawable = drawables[2]; 
 
 // 设置焦点变化的监听 
 this.setOnFocusChangeListener(new FocusChangeListenerImpl()); 
 // 设置EditText文字变化的监听 
 this.addTextChangedListener(new TextWatcherImpl()); 
 // 初始化时让右边clean图标不可见 
 setClearDrawableVisible(false); 
 
 
 
 } 
 @Override 
 public boolean onTouchEvent(MotionEvent event) { 
 switch (event.getAction()) { 
  //当点击松开时判断点击的位置。这里只进行了X轴方向的判断。 
  case MotionEvent.ACTION_UP: 
  //判断是否点击到了右边的图标区域 
  boolean isClean = (event.getX() > (getWidth() - getTotalPaddingRight())) 
   && (event.getX() < (getWidth() - getPaddingRight())); 
  if (isClean) { 
   //清除字符 
   setText(""); 
  } 
  break; 
 
  default: 
  break; 
 } 
 return super.onTouchEvent(event); 
 } 
 
 private class FocusChangeListenerImpl implements OnFocusChangeListener { 
 @Override 
 public void onFocusChange(View v, boolean hasFocus) { 
  isHasFocus = hasFocus; 
  if (isHasFocus) { 
  boolean isVisible = getText().toString().length() >= 1; 
  setClearDrawableVisible(isVisible); 
  } else { 
  setClearDrawableVisible(false); 
  } 
 } 
 
 } 
 
 // 当输入结束后判断是否显示右边clean的图标 
 private class TextWatcherImpl implements TextWatcher { 
 @Override 
 public void afterTextChanged(Editable s) { 
  //当有字符时为true 
  boolean isVisible = getText().toString().length() >= 1; 
  //显示右边的图标 
  setClearDrawableVisible(isVisible); 
 } 
 
 @Override 
 public void beforeTextChanged(CharSequence s, int start, int count, 
     int after) { 
 
 } 
 
 @Override 
 public void onTextChanged(CharSequence s, int start, int before, 
     int count) { 
 
 } 
 
 } 
 
 // 隐藏或显示右边clean的图标 
 protected void setClearDrawableVisible(boolean isVisible) { 
 Drawable rightDrawable; 
 if (isVisible) { 
  rightDrawable = mRightDrawable; 
 } else { 
  rightDrawable = null; 
 } 
 // 使用代码设置该控件right处的图标 
 setCompoundDrawables(getCompoundDrawables()[0], 
  getCompoundDrawables()[1], rightDrawable, 
  getCompoundDrawables()[3]); 
 } 
 
 // 显示动画 
 public void setShakeAnimation() { 
 this.startAnimation(shakeAnimation(5)); 
 
 } 
 
 // CycleTimes动画重复的次数 
 public Animation shakeAnimation(int CycleTimes) { 
 //设置偏移动画 其中new TranslateAnimation(0,10,0,10)四个值表示为 X坐标从0-->10,Y坐标从0-->10 
 Animation translateAnimation = new TranslateAnimation(0, 10, 0, 10); 
 //设置动画次数 
 translateAnimation.setInterpolator(new CycleInterpolator(CycleTimes)); 
 //设置动画间隔 
 translateAnimation.setDuration(1000); 
 return translateAnimation; 
 } 
 
} 

其中需要注意的知识:

  1.Drawable[] drawables = this.getCompoundDrawables(); 得到此View的 drawable. getCompoundDrawables()方法得到的有4个Drawable对象,分别对应此View的左,上,右,下的边距

  2.boolean isClean = (event.getX() > (getWidth() - getTotalPaddingRight()))&& (event.getX() < (getWidth() - getPaddingRight()));  判断点击的区域是否为右边图标范围。其中event.getX()为点击的位置的X坐标大小。详细如下图所示:


 3.Animation translateAnimation = new TranslateAnimation(0, 10, 0, 10); 设置偏移动画 其中new TranslateAnimation(0,10,0,10)四个值表示为 X坐标从0-->10,Y坐标从0-->10

 4.this.setOnFocusChangeListener(new FocusChangeListenerImpl());设置焦点变化的目的是为了更人性化。当焦点在此行并且有字符时才显示一键删除图标。不在此行时图标隐藏。

 5.this.addTextChangedListener(new TextWatcherImpl()); 设置text变化监听。new TextWatcher{}中有3个方法。分别是:

1).public void beforeTextChanged(CharSequence s, int start, int count,int after) {}
2).public void onTextChanged(CharSequence s, int start, int before,int count) {}
3).public void afterTextChanged(Editable s) {}
我们只需要在afterTextChanged(){Editable s}{}中添加要实现的方法即可。当监听到text变化时,设置右边图标显示。

TextWatcher {
 @Override
 public void afterTextChanged(Editable s) {
 //当有字符时为true
 boolean isVisible = getText().toString().length() >= 1;
 //显示右边的图标
 setClearDrawableVisible(isVisible);
 }

验证码的实现过程:

思路:设置4个ImageView。 首先随机生成4个10以内的数字存储在数组里。并且记录整个验证码。在利用Bitmap.createBitmap方法讲这4个数字转化为图片并且设置随机颜色。每个数字图片转化的时候在随机设置旋转角度使这4个数字图标倾斜一定的角度。 验证码就生成了。

验证验证码的过程只是模拟下: 将输入的验证码跟记录的验证码作比较。相同 提示正确,不同提示错误。并且重置验证码。

点击验证码图片区域也会重置验证码。
代码的注释很详细。上代码~~~~: 

package com.example.administrator.texttest; 
 
import android.graphics.Bitmap; 
import android.graphics.Canvas; 
import android.graphics.Color; 
import android.graphics.Matrix; 
import android.support.v7.app.AppCompatActivity; 
import android.os.Bundle; 
import android.text.TextUtils; 
import android.view.Menu; 
import android.view.MenuItem; 
import android.view.View; 
import android.widget.Button; 
import android.widget.EditText; 
import android.widget.ImageView; 
import android.widget.TextView; 
import android.widget.Toast; 
 
import java.util.Random; 
 
public class MainActivity extends AppCompatActivity { 
 //登录按钮 
 private Button btLogin; 
 //账户 
 private DeletableEditText userEditText; 
 //密码 
 private DeletableEditText psdEditText; 
 
 
 //验证码的数字文本 
 private TextView tvHideA,tvHideB,tvHideC,tvHideD; 
 
 //验证码的图片文本 
 private ImageView ivNumA,ivNumB,ivNumC,ivNumD; 
 //验证码输入文本 
 private EditText etCheck; 
 //验证码的检测显示文本 
 private TextView tvCheck; 
 
 //存储每个验证码的数字 
 private String numStrTmp = ""; 
 //存储整个验证码的数字 
 private String numStr = ""; 
 
 //存储验证码的数组 
 private int[] numArray = new int[4]; 
 //存储颜色的数组 
 private int[] colorArray = new int[6]; 
 
 @Override 
 protected void onCreate(Bundle savedInstanceState) { 
 super.onCreate(savedInstanceState); 
 setContentView(R.layout.activity_main); 
 setupViews(); 
 
 } 
 
 private void setupViews() { 
 btLogin = (Button) findViewById(R.id.bt_login); 
 btLogin.setOnClickListener(new OnClickListenerImpl()); 
 userEditText = (DeletableEditText) findViewById(R.id.tv_user); 
 psdEditText = (DeletableEditText) findViewById(R.id.tv_psd); 
 
 
 tvHideA = (TextView) findViewById(R.id.tvHideA); 
 tvHideB = (TextView) findViewById(R.id.tvHideB); 
 tvHideC = (TextView) findViewById(R.id.tvHideC); 
 tvHideD = (TextView) findViewById(R.id.tvHideD); 
 
 ivNumA = (ImageView) findViewById(R.id.ivNumA); 
 ivNumB = (ImageView) findViewById(R.id.ivNumB); 
 ivNumC = (ImageView) findViewById(R.id.ivNumC); 
 ivNumD = (ImageView) findViewById(R.id.ivNumD); 
 
 ivNumA.setOnClickListener(new OnClickListenerImpl()); 
 ivNumB.setOnClickListener(new OnClickListenerImpl()); 
 ivNumC.setOnClickListener(new OnClickListenerImpl()); 
 ivNumD.setOnClickListener(new OnClickListenerImpl()); 
 
 
 
 tvCheck = (TextView) findViewById(R.id.tvCheck); 
 etCheck = (EditText) findViewById(R.id.etCheck); 
 
 
 
 
 setNum(); 
 
 
 
 
 } 
 private void setNum() { 
 initNum(); 
 tvHideA.setText("" + numArray[0]); 
 tvHideA.setTextColor(randomColor()); 
 tvHideB.setText("" + numArray[1]); 
 tvHideB.setTextColor(randomColor()); 
 tvHideC.setText("" + numArray[2]); 
 tvHideC.setTextColor(randomColor()); 
 tvHideD.setText("" + numArray[3]); 
 tvHideD.setTextColor(randomColor()); 
 
 
 Matrix matrixA = new Matrix(); 
 //重设矩阵 
 matrixA.reset(); 
 matrixA.setRotate(randomAngle()); 
 Bitmap bmNumA = Bitmap.createBitmap(getBitmapFromView(tvHideA,20,50),0,0,20,50,matrixA,true); 
 ivNumA.setImageBitmap(bmNumA); 
 
 Matrix matrixB = new Matrix(); 
 //重设矩阵 
 matrixB.reset(); 
 matrixB.setRotate(randomAngle()); 
 Bitmap bmNumB = Bitmap.createBitmap(getBitmapFromView(tvHideB,20,50),0,0,20,50,matrixB,true); 
 ivNumB.setImageBitmap(bmNumB); 
 
 Matrix matrixC = new Matrix(); 
 //重设矩阵 
 matrixC.reset(); 
 matrixC.setRotate(randomAngle()); 
 Bitmap bmNumC = Bitmap.createBitmap(getBitmapFromView(tvHideC,20,50),0,0,20,50,matrixC,true); 
 ivNumC.setImageBitmap(bmNumC); 
 
 Matrix matrixD = new Matrix(); 
 //重设矩阵 
 matrixD.reset(); 
 matrixD.setRotate(randomAngle()); 
 Bitmap bmNumD = Bitmap.createBitmap(getBitmapFromView(tvHideD,20,50),0,0,20,50,matrixD,true); 
 ivNumD.setImageBitmap(bmNumD); 
 
 } 
 
 private Bitmap getBitmapFromView(View v,int width,int height ) { 
 int widSpec = View.MeasureSpec.makeMeasureSpec(width,View.MeasureSpec.EXACTLY); 
 int heiSpec = View.MeasureSpec.makeMeasureSpec(height,View.MeasureSpec.EXACTLY); 
 //重新绘制图片大小 
 v.measure(widSpec, heiSpec); 
 // 
 v.layout(0, 0, width, height); 
 Bitmap bitmap = Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888); 
 
 //画出图片 
 Canvas canvas = new Canvas(bitmap); 
 v.draw(canvas); 
 
 return bitmap; 
 
 } 
 
 
 //设置随机倾斜的角度 
 private int randomAngle() { 
 
 
 return 20*(new Random().nextInt(5)-new Random().nextInt(3)); 
 } 
 
 
 
 //随机生成颜色 
 private int randomColor() { 
 colorArray[0]=0xFF000000; //BLACK 
 colorArray[1] = 0xFFFF00FF; // MAGENTA 
 colorArray[2] = 0xFFFF0000; // RED 
 colorArray[3] = 0xFF00FF00; // GREEN 
 colorArray[4] = 0xFF0000FF; // BLUE 
 colorArray[5] = 0xFF00FFFF; // CYAN 
 int randomColoId = new Random().nextInt(5); 
 return colorArray[randomColoId]; 
 
 
 
 
 } 
 //初始化验证码 
 private void initNum() { 
 numStr=""; 
 numStrTmp=""; 
 for (int i = 0; i < numArray.length; i++) { 
  //随机生成10以内数字 
  int numIntTmp = new Random().nextInt(10); 
  //保存各个验证码 
  numStrTmp = String.valueOf(numIntTmp); 
  //保存整个验证码 
  numStr = numStr+numStrTmp; 
  numArray[i] = numIntTmp; 
 } 
 
 } 
 
 private class OnClickListenerImpl implements View.OnClickListener { 
 @Override 
 public void onClick(View v) { 
  //当点击的为登录按钮时 
  if(v==btLogin){ 
  //判断账户字符是否为空, 
  if (TextUtils.isEmpty(userEditText.getText().toString())){ 
   //为空时抖动提示 
   userEditText.setShakeAnimation(); 
   Toast.makeText(MainActivity.this,"账户或密码不能为空",Toast.LENGTH_SHORT).show(); 
  } 
  //判断密码字符是否为空 
  if (TextUtils.isEmpty(psdEditText.getText().toString())){ 
   //为空时抖动提示 
   psdEditText.setShakeAnimation(); 
   Toast.makeText(MainActivity.this,"账户或密码不能为空",Toast.LENGTH_SHORT).show(); 
  } 
 
  //验证输入的验证码是否正确 
  if(etCheck.getText().toString()!=null&&etCheck.getText().toString().trim().length()>0){ 
   tvCheck.setVisibility(View.VISIBLE); 
   if (numStr.equals(etCheck.getText().toString())){ 
   tvCheck.setTextColor(Color.GREEN); 
   tvCheck.setText("验证码正确!"); 
   }else{ 
   tvCheck.setTextColor(Color.RED); 
   tvCheck.setText("验证码错误!"); 
   etCheck.setText(""); 
   setNum(); 
   } 
  } 
  //如果OnClick不是登录按钮时只剩下验证码图片有监听事件。等同于点击验证码图片。改变验证码。 
  }else { 
  setNum(); 
  tvCheck.setVisibility(View.GONE); 
  } 
 
 
 } 
 } 
} 

需要注意的知识:
1.Bitmap.createBitmap(getBitmapFromView(tvHideA,20,50),0,0,20,50,matrixA,true); 
Bitmap.createBitmap(Bitmap source, int x, int y, int width, int height,Matrix m, boolean filter)
Bitmap source:要从中截图的原始位图
int x:  起始x坐标
int y:起始y坐标
int width:  要截的图的宽度
int height:要截的图的高度
boolean filter 当进行的不只是平移变换时,filter参数为true可以进行滤波处理,有助于改善新图像质量;flase时,计算机不做过滤处理。 

2.intwidSpec = View.MeasureSpec.makeMeasureSpec(width,View.MeasureSpec.EXACTLY);
  int heiSpec = View.MeasureSpec.makeMeasureSpec(height,View.MeasureSpec.EXACTLY);
 设置View的宽和高。View.MeasureSpec.EXACTLY 指的是设置为实际View的大小。即前面的width(height)为多大就为多大。

3.Bitmap.createBitmap(width,height, Bitmap.Config.ARGB_8888); create一个图标。 

4.Canvas canvas =newCanvas(bitmap);
v.draw(canvas);  画出图片

5.v.measure(widSpec, heiSpec);
//v.layout(0,0, width, height);  重新绘制图片的大小。 

后面是运行时的图片:

当有输入时右边的一键删除图标显示、当失去焦点时一键删除图标隐藏、点击验证码更新验证码:

后面的就不详细图示了。

源码下载:http://xiazai.jb51.net/201610/yuanma/Androidlogin(jb51.net).rar

您可能感兴趣的文章:

相关文章