溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

怎么在android中使用LabelView實(shí)現(xiàn)一個(gè)標(biāo)簽云效果

發(fā)布時(shí)間:2021-05-14 17:34:11 來源:億速云 閱讀:119 作者:Leah 欄目:移動(dòng)開發(fā)

怎么在android中使用LabelView實(shí)現(xiàn)一個(gè)標(biāo)簽云效果?相信很多沒有經(jīng)驗(yàn)的人對(duì)此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。

public class LabelView extends View { 
 private static final int DIRECTION_LEFT = 0; // 向左 
 private static final int DIRECTION_RIGHT = 1; // 向右 
 private static final int DIRECITON_TOP = 2; // 向上 
 private static final int DIRECTION_BOTTOM = 3; // 向下 
  
 private boolean isStatic; // 是否靜止, 默認(rèn)false, 可用干xml : label:is_static="false" 
  
 private int[][] mLocations; // 每個(gè)label的位置 x/y 
 private int[][] mDirections; // 每個(gè)label的方向 x/y 
 private int[][] mSpeeds; // 每個(gè)label的x/y速度 x/y 
 private int[][] mTextWidthAndHeight; // 每個(gè)labeltext的大小 width/height 
  
 private String[] mLabels; // 設(shè)置的labels 
 private int[] mFontSizes; // 每個(gè)label的字體大小 
 // 默認(rèn)配色方案 
 private int[] mColorSchema = {0XFFFF0000, 0XFF00FF00, 0XFF0000FF, 0XFFCCCCCC, 0XFFFFFFFF}; 
  
 private int mTouchSlop; // 最小touch 
 private int mDownX = -1; 
 private int mDownY = -1; 
 private int mDownIndex = -1; // 點(diǎn)擊的index 
  
 private Paint mPaint; 
  
 private Thread mThread; 
  
 private OnItemClickListener mListener; // item點(diǎn)擊事件 
  
 public LabelView(Context context, AttributeSet attrs) { 
  this(context, attrs, 0); 
 } 
 
 public LabelView(Context context, AttributeSet attrs, int defStyleAttr) { 
  super(context, attrs, defStyleAttr); 
   
  TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.LabelView, defStyleAttr, 0); 
  isStatic = ta.getBoolean(R.styleable.LabelView_is_static, false); 
  ta.recycle(); 
   
  mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop(); 
   
  mPaint = new Paint(); 
  mPaint.setAntiAlias(true); 
 } 
  
 @Override 
 protected void onLayout(boolean changed, int left, int top, int right, 
   int bottom) { 
  super.onLayout(changed, left, top, right, bottom); 
  init(); 
 } 
  
 @Override 
 protected void onDraw(Canvas canvas) { 
  if(!hasContents()) { 
   return; 
  } 
   
  for (int i = 0; i < mLabels.length; i++) { 
   mPaint.setTextSize(mFontSizes[i]); 
    
   if(i < mColorSchema.length) mPaint.setColor(mColorSchema[i]); 
   else mPaint.setColor(mColorSchema[i-mColorSchema.length]); 
    
   canvas.drawText(mLabels[i], mLocations[i][0], mLocations[i][1], mPaint); 
  } 
 } 
  
 @Override 
 public boolean onTouchEvent(MotionEvent ev) { 
  switch (ev.getAction()) { 
  case MotionEvent.ACTION_DOWN: 
   mDownX = (int) ev.getX(); 
   mDownY = (int) ev.getY(); 
   mDownIndex = getClickIndex(); 
   break; 
  case MotionEvent.ACTION_UP: 
   int nowX = (int) ev.getX(); 
   int nowY = (int) ev.getY(); 
   if (nowX - mDownX < mTouchSlop && nowY - mDownY < mTouchSlop 
     && mDownIndex != -1 && mListener != null) { 
    mListener.onItemClick(mDownIndex, mLabels[mDownIndex]); 
   } 
    
   mDownX = mDownY = mDownIndex = -1; 
   break; 
  } 
   
  return true; 
 } 
  
 /** 
  * 獲取當(dāng)前點(diǎn)擊的label的位置 
  * @return label的位置,沒有點(diǎn)中返回-1 
  */ 
 private int getClickIndex() { 
  Rect downRect = new Rect(); 
  Rect locationRect = new Rect(); 
  for(int i=0;i<mLocations.length;i++) { 
   downRect.set(mDownX - mTextWidthAndHeight[i][0], mDownY 
     - mTextWidthAndHeight[i][1], mDownX 
     + mTextWidthAndHeight[i][0], mDownY 
     + mTextWidthAndHeight[i][1]); 
    
   locationRect.set(mLocations[i][0], mLocations[i][1], 
     mLocations[i][0] + mTextWidthAndHeight[i][0], 
     mLocations[i][1] + mTextWidthAndHeight[i][1]); 
    
   if(locationRect.intersect(downRect)) { 
    return i; 
   } 
  } 
  return -1; 
 } 
  
 /** 
  * 開啟子線程不斷刷新位置并postInvalidate 
  */ 
 private void run() { 
  if(mThread != null && mThread.isAlive()) { 
   return; 
  } 
   
  mThread = new Thread(mStartRunning); 
  mThread.start(); 
 } 
  
 private Runnable mStartRunning = new Runnable() { 
  @Override 
  public void run() { 
   for(;;) { 
    SystemClock.sleep(100); 
     
    for (int i = 0; i < mLabels.length; i++) { 
     if (mLocations[i][0] <= getPaddingLeft()) { 
      mDirections[i][0] = DIRECTION_RIGHT; 
     } 
      
     if (mLocations[i][0] >= getMeasuredWidth() 
       - getPaddingRight() - mTextWidthAndHeight[i][0]) { 
      mDirections[i][0] = DIRECTION_LEFT; 
     } 
      
     if(mLocations[i][1] <= getPaddingTop() + mTextWidthAndHeight[i][1]) { 
      mDirections[i][1] = DIRECTION_BOTTOM; 
     } 
      
     if (mLocations[i][1] >= getMeasuredHeight() - getPaddingBottom()) { 
      mDirections[i][1] = DIRECITON_TOP; 
     } 
      
     int xSpeed = 1; 
     int ySpeed = 2; 
      
     if(i < mSpeeds.length) { 
      xSpeed = mSpeeds[i][0]; 
      ySpeed = mSpeeds[i][1]; 
     } 
     else { 
      xSpeed = mSpeeds[i-mSpeeds.length][0]; 
      ySpeed = mSpeeds[i-mSpeeds.length][1]; 
     } 
      
     mLocations[i][0] += mDirections[i][0] == DIRECTION_RIGHT ? xSpeed : -xSpeed; 
     mLocations[i][1] += mDirections[i][1] == DIRECTION_BOTTOM ? ySpeed : -ySpeed; 
    } 
     
    postInvalidate(); 
   } 
  } 
 }; 
  
 /** 
  * 初始化位置、方向、label寬高 
  * 并開啟線程 
  */ 
 private void init() { 
  if(!hasContents()) { 
   return; 
  } 
   
  int minX = getPaddingLeft(); 
  int minY = getPaddingTop(); 
  int maxX = getMeasuredWidth() - getPaddingRight(); 
  int maxY = getMeasuredHeight() - getPaddingBottom(); 
   
  Rect textBounds = new Rect(); 
   
  for (int i = 0; i < mLabels.length; i++) { 
   int[] location = new int[2]; 
   location[0] = minX + (int) (Math.random() * maxX); 
   location[1] = minY + (int) (Math.random() * maxY); 
    
   mLocations[i] = location; 
   mFontSizes[i] = 15 + (int) (Math.random() * 30); 
   mDirections[i][0] = Math.random() > 0.5 ? DIRECTION_RIGHT : DIRECTION_LEFT; 
   mDirections[i][1] = Math.random() > 0.5 ? DIRECTION_BOTTOM : DIRECITON_TOP; 
    
   mPaint.setTextSize(mFontSizes[i]); 
   mPaint.getTextBounds(mLabels[i], 0, mLabels[i].length(), textBounds); 
   mTextWidthAndHeight[i][0] = textBounds.width(); 
   mTextWidthAndHeight[i][1] = textBounds.height(); 
  } 
   
  if(!isStatic) run(); 
 } 
  
 /** 
  * 是否設(shè)置label 
  * @return true or false 
  */ 
 private boolean hasContents() { 
  return mLabels != null && mLabels.length > 0; 
 } 
 
 /** 
  * 設(shè)置labels 
  * @see setLabels(String[] labels) 
  * @param labels 
  */ 
 public void setLabels(List<String> labels) { 
  setLabels((String[]) labels.toArray()); 
 } 
  
 /** 
  * 設(shè)置labels 
  * @param labels 
  */ 
 public void setLabels(String[] labels) { 
  mLabels = labels; 
  mLocations = new int[labels.length][2]; 
  mFontSizes = new int[labels.length]; 
  mDirections = new int[labels.length][2]; 
  mTextWidthAndHeight = new int[labels.length][2]; 
   
  mSpeeds = new int[labels.length][2]; 
  for(int speed[] : mSpeeds) { 
   speed[0] = speed[1] = 1; 
  } 
   
  requestLayout(); 
 } 
  
 /** 
  * 設(shè)置配色方案 
  * @param colorSchema 
  */ 
 public void setColorSchema(int[] colorSchema) { 
  mColorSchema = colorSchema; 
 } 
  
 /** 
  * 設(shè)置每個(gè)item的x/y速度 
  * <p> 
  * speeds.length > labels.length 忽略多余的 
  * <p> 
  * speeds.length < labels.length 將重復(fù)使用 
  * 
  * @param speeds 
  */ 
 public void setSpeeds(int[][] speeds) { 
  mSpeeds = speeds; 
 } 
  
 /** 
  * 設(shè)置item點(diǎn)擊的監(jiān)聽事件 
  * @param l 
  */ 
 public void setOnItemClickListener(OnItemClickListener l) { 
  getParent().requestDisallowInterceptTouchEvent(true); 
  mListener = l; 
 } 
  
 /** 
  * item的點(diǎn)擊監(jiān)聽事件 
  */ 
 public interface OnItemClickListener { 
  public void onItemClick(int index, String label); 
 } 
}

上來先弄了4個(gè)常量上去,干嘛用的呢? 是要判斷每個(gè)item的方向的,因?yàn)楫?dāng)達(dá)到某個(gè)邊界的時(shí)候,item要向相反的方向移動(dòng)。

第二個(gè)構(gòu)造方法中, 獲取了一個(gè)自定義屬性,還有就是初始化的Paint。

繼續(xù)看onLayout, 其實(shí)onLayout我們什么都沒干,只是調(diào)用了init方法, 來看看init方法。

/** 
 * 初始化位置、方向、label寬高 
 * 并開啟線程 
 */ 
private void init() { 
 if(!hasContents()) { 
  return; 
 } 
   
 int minX = getPaddingLeft(); 
 int minY = getPaddingTop(); 
 int maxX = getMeasuredWidth() - getPaddingRight(); 
 int maxY = getMeasuredHeight() - getPaddingBottom(); 
   
 Rect textBounds = new Rect(); 
   
 for (int i = 0; i < mLabels.length; i++) { 
  int[] location = new int[2]; 
  location[0] = minX + (int) (Math.random() * maxX); 
  location[1] = minY + (int) (Math.random() * maxY); 
    
  mLocations[i] = location; 
  mFontSizes[i] = 15 + (int) (Math.random() * 30); 
  mDirections[i][0] = Math.random() > 0.5 ? DIRECTION_RIGHT : DIRECTION_LEFT; 
  mDirections[i][1] = Math.random() > 0.5 ? DIRECTION_BOTTOM : DIRECITON_TOP; 
    
  mPaint.setTextSize(mFontSizes[i]); 
  mPaint.getTextBounds(mLabels[i], 0, mLabels[i].length(), textBounds); 
  mTextWidthAndHeight[i][0] = textBounds.width(); 
  mTextWidthAndHeight[i][1] = textBounds.height(); 
 } 
   
 if(!isStatic) run(); 
}

init方法中,上來先判斷一下,是否設(shè)置了標(biāo)簽,如果沒有設(shè)置直接返回,省得事多。
10~13行,目的就是獲取item在該view中移動(dòng)的上下左右邊界,畢竟item還是要在整個(gè)view中移動(dòng)的嘛,不能超出了view的邊界。

17行,開始一個(gè)for循環(huán),去遍歷所有的標(biāo)簽。

18~20行,是隨機(jī)初始化一個(gè)位置,所以,我們的標(biāo)簽每次出現(xiàn)的位置都是隨機(jī)的,并沒有什么規(guī)律,但接下來的移動(dòng)是有規(guī)律的,總不能到處亂蹦吧。

接著,22行,保存了這個(gè)位置,因?yàn)槲覀兿旅嬉粩嗟娜バ薷倪@個(gè)位置。

23行,隨機(jī)了一個(gè)字體大小,24、25行,隨機(jī)了該標(biāo)簽x/y初始的方向。

27行,去設(shè)置了當(dāng)前標(biāo)簽的字體大小,28行,是獲取標(biāo)簽的寬度和高度,并在下面保存在了一個(gè)二維數(shù)組中,為什么是二維數(shù)組,我們有多個(gè)標(biāo)簽嘛, 每個(gè)標(biāo)簽都要保存它的寬度和高度。

最后,如果我們沒有顯示的聲明labelview是靜止的,則去調(diào)用run方法。

繼續(xù)跟進(jìn)代碼,看看run方法的內(nèi)臟。

/** 
 * 開啟子線程不斷刷新位置并postInvalidate 
 */ 
private void run() { 
 if(mThread != null && mThread.isAlive()) { 
  return; 
 } 
  
 mThread = new Thread(mStartRunning); 
 mThread.start(); 
}

5~7行,如果線程已經(jīng)開啟,直接return 防止多個(gè)線程共存,這樣造成的后果就是標(biāo)簽越來越快。
9、10行,去啟動(dòng)一個(gè)線程,并有一個(gè)mStartRunning的Runnable參數(shù)。

那么我們繼續(xù)來看看這個(gè)Runnable。

private Runnable mStartRunning = new Runnable() { 
 @Override 
 public void run() { 
  for(;;) { 
   SystemClock.sleep(100); 
     
   for (int i = 0; i < mLabels.length; i++) { 
    if (mLocations[i][0] <= getPaddingLeft()) { 
     mDirections[i][0] = DIRECTION_RIGHT; 
    } 
      
    if (mLocations[i][0] >= getMeasuredWidth() 
      - getPaddingRight() - mTextWidthAndHeight[i][0]) { 
     mDirections[i][0] = DIRECTION_LEFT; 
    } 
    
    if(mLocations[i][1] <= getPaddingTop() + mTextWidthAndHeight[i][1]) { 
     mDirections[i][1] = DIRECTION_BOTTOM; 
    } 
      
    if (mLocations[i][1] >= getMeasuredHeight() - getPaddingBottom()) { 
     mDirections[i][1] = DIRECITON_TOP; 
    } 
      
    int xSpeed = 1; 
    int ySpeed = 2; 
      
    if(i < mSpeeds.length) { 
     xSpeed = mSpeeds[i][0]; 
     ySpeed = mSpeeds[i][1]; 
    }else { 
     xSpeed = mSpeeds[i-mSpeeds.length][0]; 
     ySpeed = mSpeeds[i-mSpeeds.length][1]; 
    } 
      
    mLocations[i][0] += mDirections[i][0] == DIRECTION_RIGHT ? xSpeed : -xSpeed; 
    mLocations[i][1] += mDirections[i][1] == DIRECTION_BOTTOM ? ySpeed : -ySpeed; 
   } 
     
   postInvalidate(); 
  } 
 } 
};

這個(gè)Runnable其實(shí)才是標(biāo)簽云實(shí)現(xiàn)的關(guān)鍵,我們就是在這個(gè)線程中去修改每個(gè)標(biāo)簽的位置,并通知view去重繪的。
而且可以看到,在run中是一個(gè)死循環(huán),這樣我們的標(biāo)簽才能無休止的移動(dòng),接下來就是讓線程去休息100ms,總不能一個(gè)勁的去移動(dòng)吧,速度太快了也不好,也要考慮性能問題。

接下來第7行,去遍歷所有的標(biāo)簽,8~23行,通過判斷當(dāng)前的位置是不是達(dá)到了某個(gè)邊界,如果到了,則修改方向?yàn)橄喾吹姆较颍绗F(xiàn)在到了view的最上面,那接下來,這個(gè)標(biāo)簽就得往下移動(dòng)了。

25、26行,默認(rèn)了x/y的速度,為什么是說默認(rèn)了呢, 因?yàn)槊總€(gè)標(biāo)簽的x/y速度我們都可以通過方法去設(shè)置。

接下來28~34行,做了一個(gè)判斷,大體意思就是:如果設(shè)置的那些速度總數(shù)大于當(dāng)前標(biāo)簽在標(biāo)簽s中的位置,則去找對(duì)應(yīng)位置的速度,否則,重新從前面獲取速度。

36、37行就是根據(jù)x/y上的方向去修改當(dāng)前標(biāo)簽的坐標(biāo)了。

最后,調(diào)用了postInvalidate(),通知view去刷新界面,這里是用的postInvalidate()因?yàn)槲覀兪窃诰€程中調(diào)用的,切記。

postInvalidate()后,肯定就要走onDraw()去繪制這些標(biāo)簽了,那么我們就來看看onDraw吧。

@Override 
protected void onDraw(Canvas canvas) { 
 if(!hasContents()) { 
  return; 
 } 
   
 for (int i = 0; i < mLabels.length; i++) { 
  mPaint.setTextSize(mFontSizes[i]); 
    
  if(i < mColorSchema.length) mPaint.setColor(mColorSchema[i]); 
  else mPaint.setColor(mColorSchema[i-mColorSchema.length]); 
    
  canvas.drawText(mLabels[i], mLocations[i][0], mLocations[i][1], mPaint); 
 } 
}

上來還是判斷了一下,如果沒有設(shè)置標(biāo)簽,直接返回。 如果有標(biāo)簽,那么去遍歷所有標(biāo)簽,并設(shè)置對(duì)應(yīng)的字體大小,還記得嗎? 我們?cè)诔跏蓟臅r(shí)候隨機(jī)了每個(gè)標(biāo)簽的字體大小,接下來去設(shè)置該標(biāo)簽的顏色,一個(gè)if else 原理和設(shè)置速度那個(gè)是一樣的,最關(guān)鍵的就是下面,調(diào)用了canvas.drawText()將該標(biāo)簽畫到屏幕上,mLocations中我們是保存了每個(gè)標(biāo)簽的位置,而且是在線程中不斷的去修改這個(gè)位置的。
到這里,其實(shí)我們的LabelView就能動(dòng)起來了,不過那幾個(gè)設(shè)置標(biāo)簽,速度,顏色的方法還有說。其實(shí)很簡(jiǎn)單,來看一下吧。

/** 
 * 設(shè)置labels 
 * @see setLabels(String[] labels) 
 * @param labels 
 */ 
public void setLabels(List<String> labels) { 
 setLabels((String[]) labels.toArray()); 
} 
  
/** 
 * 設(shè)置labels 
 * @param labels 
 */ 
public void setLabels(String[] labels) { 
 mLabels = labels; 
 mLocations = new int[labels.length][2]; 
 mFontSizes = new int[labels.length]; 
 mDirections = new int[labels.length][2]; 
 mTextWidthAndHeight = new int[labels.length][2]; 
   
 mSpeeds = new int[labels.length][2]; 
 for(int speed[] : mSpeeds) { 
  speed[0] = speed[1] = 1; 
 } 
   
 requestLayout(); 
} 
  
/** 
 * 設(shè)置配色方案 
 * @param colorSchema 
 */ 
public void setColorSchema(int[] colorSchema) { 
 mColorSchema = colorSchema; 
} 
  
/** 
 * 設(shè)置每個(gè)item的x/y速度 
 * <p> 
 * speeds.length > labels.length 忽略多余的 
 * <p> 
 * speeds.length < labels.length 將重復(fù)使用 
 * 
 * @param speeds 
 */ 
public void setSpeeds(int[][] speeds) { 
 mSpeeds = speeds; 
}

這幾個(gè)蛋疼的方法中,唯一可說的就是setLabels(String[] labels)了,因?yàn)樵谶@個(gè)方法中還做了點(diǎn)工作。 仔細(xì)觀察,這方法除了設(shè)置了標(biāo)簽s外,其他的就是初始化了幾個(gè)數(shù)組,都表示什么,相信都應(yīng)該很清楚了,還有就是在這里我們初始化了默認(rèn)速度為1。

剛上來做演示的時(shí)候,LabelView還能item點(diǎn)擊,這是怎么做到的呢? 普通的onClick肯定是不行的,因?yàn)槲覀儾⒉恢傈c(diǎn)擊的x/y坐標(biāo),所以只能通過onTouchEvent入手了。

@Override 
public boolean onTouchEvent(MotionEvent ev) { 
 switch (ev.getAction()) { 
 case MotionEvent.ACTION_DOWN: 
  mDownX = (int) ev.getX(); 
  mDownY = (int) ev.getY(); 
  mDownIndex = getClickIndex(); 
  break; 
 case MotionEvent.ACTION_UP: 
  int nowX = (int) ev.getX(); 
  int nowY = (int) ev.getY(); 
  if (nowX - mDownX < mTouchSlop && nowY - mDownY < mTouchSlop 
    && mDownIndex != -1 && mListener != null) { 
   mListener.onItemClick(mDownIndex, mLabels[mDownIndex]); 
  } 
    
  mDownX = mDownY = mDownIndex = -1; 
  break; 
 } 
   
 return true; 
}

在onTouch中我們只關(guān)心了down和up事件,因?yàn)橐淮吸c(diǎn)擊就是down和up的組合嘛。
在down中,我們獲取了當(dāng)前事件發(fā)生的x/y坐標(biāo),并且獲取了當(dāng)前點(diǎn)擊的item,當(dāng)前是通過getClickIndex()方法去獲取的,這個(gè)方法稍候說;再來看看up,在up中,我們通過當(dāng)前的x/y和在down時(shí)的x/y對(duì)比,如果這兩點(diǎn)的距離小于系統(tǒng)認(rèn)為的最小滑動(dòng)距離,才能說明點(diǎn)擊有效,如果你down了以后,拉了一個(gè)長(zhǎng)線,再up,那肯定不是一次有效的點(diǎn)擊,當(dāng)然點(diǎn)擊有效了還不能說明一切,只有命中標(biāo)簽了才行,所以還去判斷了mDownIndex是否為一個(gè)有效的值,然后如果設(shè)置了ItemClick,就去回調(diào)它。

那mDownIndex到底是怎么獲取的呢? 我們來getClickIndex()一探究竟。

/** 
 * 獲取當(dāng)前點(diǎn)擊的label的位置 
 * @return label的位置,沒有點(diǎn)中返回-1 
 */ 
private int getClickIndex() { 
 Rect downRect = new Rect(); 
 Rect locationRect = new Rect(); 
 for(int i=0;i<mLocations.length;i++) { 
  downRect.set(mDownX - mTextWidthAndHeight[i][0], mDownY 
    - mTextWidthAndHeight[i][1], mDownX 
    + mTextWidthAndHeight[i][0], mDownY 
    + mTextWidthAndHeight[i][1]); 
    
  locationRect.set(mLocations[i][0], mLocations[i][1], 
    mLocations[i][0] + mTextWidthAndHeight[i][0], 
    mLocations[i][1] + mTextWidthAndHeight[i][1]); 
    
  if(locationRect.intersect(downRect)) { 
   return i; 
  } 
 } 
 return -1; 
}

Android是什么

Android是一種基于Linux內(nèi)核的自由及開放源代碼的操作系統(tǒng),主要使用于移動(dòng)設(shè)備,如智能手機(jī)和平板電腦,由美國(guó)Google公司和開放手機(jī)聯(lián)盟領(lǐng)導(dǎo)及開發(fā)。

看完上述內(nèi)容,你們掌握怎么在android中使用LabelView實(shí)現(xiàn)一個(gè)標(biāo)簽云效果的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI