Android 屏幕適配(一)百分比布局庫(percent-support-lib) 解析與擴展


轉載請標明出處:
http://blog.csdn.net/lmj623565791/article/details/46695347
本文出自:【張鴻洋的博客】


一、概述

周末游戲打得過猛,於是周天熬夜碼代碼,周一早上渾渾噩噩的發現android-percent-support-lib-sample這個項目,Google終於開始支持百分比的方式布局了,瞬間脈動回來,啊咧咧。對於這種歷史性的時刻,不出篇博客難以表達我內心的激動。

還記得不久前,發了篇博客:Android 屏幕適配方案,這篇博客以Web頁面設計引出一種適配方案,最終的目的就是可以通過百分比控制控件的大小。當然了,存在一些問題,比如:

  • 對於沒有考慮到屏幕尺寸,可能會出現意外的情況;
  • apk的大小會增加;

當然了android-percent-support這個庫,基本可以解決上述問題,是不是有點小激動,稍等,我們先描述下這個support-lib。

這個庫提供了:

  • 兩種布局供大家使用:
    PercentRelativeLayoutPercentFrameLayout,通過名字就可以看出,這是繼承自FrameLayoutRelativeLayout兩個容器類;

  • 支持的屬性有:

layout_widthPercentlayout_heightPercent
layout_marginPercentlayout_marginLeftPercent
layout_marginTopPercentlayout_marginRightPercent
layout_marginBottomPercentlayout_marginStartPercentlayout_marginEndPercent

可以看到支持寬高,以及margin。

也就是說,大家只要在開發過程中使用PercentRelativeLayoutPercentFrameLayout替換FrameLayoutRelativeLayout即可。

是不是很簡單,不過貌似沒有LinearLayout,有人會說LinearLayout有weight屬性呀。但是,weight屬性只能支持一個方向呀~~哈,沒事,剛好給我們一個機會去自定義一個PercentLinearLayout

好了,本文分為3個部分:

  • PercentRelativeLayoutPercentFrameLayout的使用
  • 對上述控件源碼分析
  • 自定義PercentLinearLayout

二、使用

關於使用,其實及其簡單,並且github上也有例子,android-percent-support-lib-sample。我們就簡單過一下:

首先記得在build.gradle添加:

 compile 'com.android.support:percent:22.2.0'

(一)PercentFrameLayout

<?xml version="1.0" encoding="utf-8"?> <android.support.percent.PercentFrameLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:layout_width="0dp" android:layout_height="0dp" android:layout_gravity="left|top" android:background="#44ff0000" android:text="width:30%,height:20%" app:layout_heightPercent="20%" android:gravity="center" app:layout_widthPercent="30%"/> <TextView android:layout_width="0dp" android:layout_height="0dp" android:layout_gravity="right|top" android:gravity="center" android:background="#4400ff00" android:text="width:70%,height:20%" app:layout_heightPercent="20%" app:layout_widthPercent="70%"/> <TextView android:layout_width="0dp" android:layout_height="0dp" android:layout_gravity="bottom" android:background="#770000ff" android:text="width:100%,height:10%" android:gravity="center" app:layout_heightPercent="10%" app:layout_widthPercent="100%"/> </android.support.percent.PercentFrameLayout> 

3個TextView,很簡單,直接看效果圖:


(二) PercentRelativeLayout

<?xml version="1.0" encoding="utf-8"?> <android.support.percent.PercentRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:clickable="true"> <TextView android:id="@+id/row_one_item_one" android:layout_width="0dp" android:layout_height="0dp" android:layout_alignParentTop="true" android:background="#7700ff00" android:text="w:70%,h:20%" android:gravity="center" app:layout_heightPercent="20%" app:layout_widthPercent="70%"/> <TextView android:id="@+id/row_one_item_two" android:layout_width="0dp" android:layout_height="0dp" android:layout_toRightOf="@+id/row_one_item_one" android:background="#396190" android:text="w:30%,h:20%" app:layout_heightPercent="20%" android:gravity="center" app:layout_widthPercent="30%"/> <ImageView android:id="@+id/row_two_item_one" android:layout_width="match_parent" android:layout_height="0dp" android:src="@drawable/tangyan" android:scaleType="centerCrop" android:layout_below="@+id/row_one_item_one" android:background="#d89695" app:layout_heightPercent="70%"/> <TextView android:layout_width="0dp" android:layout_height="0dp" android:layout_below="@id/row_two_item_one" android:background="#770000ff" android:gravity="center" android:text="width:100%,height:10%" app:layout_heightPercent="10%" app:layout_widthPercent="100%"/> </android.support.percent.PercentRelativeLayout> 

ok,依然是直接看效果圖:

使用沒什么好說的,就是直觀的看一下。


三、源碼分析

其實細想一下,Google只是對我們原本熟悉的RelativeLayout和FrameLayout進行的功能的擴展,使其支持了percent相關的屬性。

那么,我們考慮下,如果是我們添加這種擴展,我們會怎么做:

  • 通過LayoutParams獲取child設置的percent相關屬性的值
  • onMeasure的時候,將child的width,height的值,通過獲取的自定義屬性的值進行計算(eg:容器的寬 * fraction ),計算后傳入給child.measure(w,h);

ok,有了上面的猜想,我們直接看PercentFrameLayout的源碼。

public class PercentFrameLayout extends FrameLayout { private final PercentLayoutHelper mHelper = new PercentLayoutHelper(this); //省略了,兩個構造方法 public PercentFrameLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mHelper.handleMeasuredStateTooSmall()) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mHelper.restoreOriginalParams(); } public static class LayoutParams extends FrameLayout.LayoutParams implements PercentLayoutHelper.PercentLayoutParams { private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs); } //省略了一些代碼... @Override public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo() { return mPercentLayoutInfo; } @Override protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr); } } }

代碼是相當的短,可以看到PercentFrameLayout里面首先重寫了generateLayoutParams方法,當然了,由於支持了一些新的layout_屬性,那么肯定需要定義對應的LayoutParams。


(一)percent相關屬性的獲取

可以看到PercentFrameLayout.LayoutParams在原有的FrameLayout.LayoutParams基礎上,實現了PercentLayoutHelper.PercentLayoutParams接口。

這個接口很簡單,只有一個方法:

public interface PercentLayoutParams { PercentLayoutInfo getPercentLayoutInfo(); }

而,這個方法的實現呢,也只有一行:return mPercentLayoutInfo;,那么這個mPercentLayoutInfo在哪完成賦值呢?

看PercentFrameLayout.LayoutParams的構造方法:

public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs); }

可以看到,將attrs傳入給getPercentLayoutInfo方法,那么不用說,這個方法的內部,肯定是獲取自定義屬性的值,然后將其封裝到PercentLayoutInfo對象中,最后返回。

代碼如下:

public static PercentLayoutInfo getPercentLayoutInfo(Context context, AttributeSet attrs) { PercentLayoutInfo info = null; TypedArray array = context.obtainStyledAttributes(attrs, R.styleable.PercentLayout_Layout); float value = array.getFraction(R.styleable.PercentLayout_Layout_layout_widthPercent, 1, 1, -1f); if (value != -1f) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "percent width: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.widthPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_heightPercent, 1, 1, -1f); if (value != -1f) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "percent height: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.heightPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginPercent, 1, 1, -1f); if (value != -1f) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "percent margin: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.leftMarginPercent = value; info.topMarginPercent = value; info.rightMarginPercent = value; info.bottomMarginPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginLeftPercent, 1, 1, -1f); if (value != -1f) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "percent left margin: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.leftMarginPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginTopPercent, 1, 1, -1f); if (value != -1f) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "percent top margin: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.topMarginPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginRightPercent, 1, 1, -1f); if (value != -1f) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "percent right margin: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.rightMarginPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginBottomPercent, 1, 1, -1f); if (value != -1f) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "percent bottom margin: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.bottomMarginPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginStartPercent, 1, 1, -1f); if (value != -1f) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "percent start margin: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.startMarginPercent = value; } value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginEndPercent, 1, 1, -1f); if (value != -1f) { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "percent end margin: " + value); } info = info != null ? info : new PercentLayoutInfo(); info.endMarginPercent = value; } array.recycle(); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "constructed: " + info); } return info; }

是不是和我們平時的取值很類似,所有的值最終封裝到PercentLayoutInfo對象中。

ok,到此我們的屬性獲取就介紹完成,有了這些屬性,是不是onMeasure里面要進行使用呢?


(二) onMeasue中重新計算child的尺寸

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mHelper.handleMeasuredStateTooSmall()) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } }

可以看到onMeasure中的代碼頁很少,看來核心的代碼都被封裝在mHelper的方法中,我們直接看mHelper.adjustChildren方法。

/** * Iterates over children and changes their width and height to one calculated from percentage * values. * @param widthMeasureSpec Width MeasureSpec of the parent ViewGroup. * @param heightMeasureSpec Height MeasureSpec of the parent ViewGroup. */ public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec) { //... int widthHint = View.MeasureSpec.getSize(widthMeasureSpec); int heightHint = View.MeasureSpec.getSize(heightMeasureSpec); for (int i = 0, N = mHost.getChildCount(); i < N; i++) { View view = mHost.getChildAt(i); ViewGroup.LayoutParams params = view.getLayoutParams(); if (params instanceof PercentLayoutParams) { PercentLayoutInfo info = ((PercentLayoutParams) params).getPercentLayoutInfo(); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "using " + info); } if (info != null) { if (params instanceof ViewGroup.MarginLayoutParams) { info.fillMarginLayoutParams((ViewGroup.MarginLayoutParams) params, widthHint, heightHint); } else { info.fillLayoutParams(params, widthHint, heightHint); } } } } } 

通過注釋也能看出,此方法中遍歷所有的孩子,通過百分比的屬性重新設置其寬度和高度。

首先在widthHint、heightHint保存容器的寬、高,然后遍歷所有的孩子,判斷其LayoutParams是否是PercentLayoutParams類型,如果是,通過params.getPercentLayoutInfo拿出info對象。

是否還記得,上面的分析中,PercentLayoutInfo保存了percent相關屬性的值。

如果info不為null,則判斷是否需要處理margin;我們直接看fillLayoutParams方法(處理margin也是類似的)。

 /** * Fills {@code ViewGroup.LayoutParams} dimensions based on percentage values. */ public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint, int heightHint) { // Preserve the original layout params, so we can restore them after the measure step. mPreservedParams.width = params.width; mPreservedParams.height = params.height; if (widthPercent >= 0) { params.width = (int) (widthHint * widthPercent); } if (heightPercent >= 0) { params.height = (int) (heightHint * heightPercent); } if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "after fillLayoutParams: (" + params.width + ", " + params.height + ")"); } }

首先保存原本的width和height,然后重置params的width和height為(int) (widthHint * widthPercent)(int) (heightHint * heightPercent);

到此,其實我們的百分比轉換就結束了,理論上就已經實現了對於百分比的支持,不過Google還考慮了一些細節。

我們回到onMeasure方法:

@Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mHelper.handleMeasuredStateTooSmall()) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } }

下面還有個mHelper.handleMeasuredStateTooSmall的判斷,也就是說,如果你設置的百分比,最終計算出來的MeasuredSize過小的話,會進行一些操作。
代碼如下:

public boolean handleMeasuredStateTooSmall() { boolean needsSecondMeasure = false; for (int i = 0, N = mHost.getChildCount(); i < N; i++) { View view = mHost.getChildAt(i); ViewGroup.LayoutParams params = view.getLayoutParams(); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "should handle measured state too small " + view + " " + params); } if (params instanceof PercentLayoutParams) { PercentLayoutInfo info = ((PercentLayoutParams) params).getPercentLayoutInfo(); if (info != null) { if (shouldHandleMeasuredWidthTooSmall(view, info)) { needsSecondMeasure = true; params.width = ViewGroup.LayoutParams.WRAP_CONTENT; } if (shouldHandleMeasuredHeightTooSmall(view, info)) { needsSecondMeasure = true; params.height = ViewGroup.LayoutParams.WRAP_CONTENT; } } } } if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "should trigger second measure pass: " + needsSecondMeasure); } return needsSecondMeasure; }

首先遍歷所有的孩子,拿出孩子的layoutparams,如果是PercentLayoutParams實例,則取出info。如果info不為null,調用shouldHandleMeasuredWidthTooSmall判斷:

private static boolean shouldHandleMeasuredWidthTooSmall(View view, PercentLayoutInfo info) { int state = ViewCompat.getMeasuredWidthAndState(view) & ViewCompat.MEASURED_STATE_MASK; return state == ViewCompat.MEASURED_STATE_TOO_SMALL && info.widthPercent >= 0 && info.mPreservedParams.width == ViewGroup.LayoutParams.WRAP_CONTENT; }

這里就是判斷,如果你設置的measuredWidth或者measureHeight過小的話,並且你在布局文件中layout_w/h 設置的是WRAP_CONTENT的話,將params.width / height= ViewGroup.LayoutParams.WRAP_CONTENT,然后重新測量。

哈,onMeasure終於結束了~~~現在我覺得應該代碼結束了吧,尺寸都設置好了,還需要干嘛么,but,你會發現onLayout也重寫了,我們又不改變layout規則,在onLayout里面干什么毛線:

@Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); mHelper.restoreOriginalParams(); }

繼續看mHelper.restoreOriginalParams

 /** * Iterates over children and restores their original dimensions that were changed for * percentage values. Calling this method only makes sense if you previously called * {@link PercentLayoutHelper#adjustChildren(int, int)}. */ public void restoreOriginalParams() { for (int i = 0, N = mHost.getChildCount(); i < N; i++) { View view = mHost.getChildAt(i); ViewGroup.LayoutParams params = view.getLayoutParams(); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "should restore " + view + " " + params); } if (params instanceof PercentLayoutParams) { PercentLayoutInfo info = ((PercentLayoutParams) params).getPercentLayoutInfo(); if (Log.isLoggable(TAG, Log.DEBUG)) { Log.d(TAG, "using " + info); } if (info != null) { if (params instanceof ViewGroup.MarginLayoutParams) { info.restoreMarginLayoutParams((ViewGroup.MarginLayoutParams) params); } else { info.restoreLayoutParams(params); } } } } } 

噗,原來是重新恢復原本的尺寸值,也就是說onMeasure里面的對值進行了改變,測量完成后。在這個地方,將值又恢復成如果布局文件中的值,上面寫的都是0。恢復很簡單:


public void restoreLayoutParams(ViewGroup.LayoutParams params) { params.width = mPreservedParams.width; params.height = mPreservedParams.height; }

你應該沒有忘在哪存的把~忘了的話,麻煩Ctrl+F ‘mPreservedParams.width’ 。

也就是說,你去打印上面寫法,布局文件中view的v.getLayoutParams().width,這個值應該是0。

這里感覺略微不爽~這個0沒撒用處呀,還不如不重置~~

好了,到此就分析完了,其實主要就幾個步驟:

  • LayoutParams中屬性的獲取
  • onMeasure中,改變params.width為百分比計算結果,測量
  • 如果測量值過小且設置的w/h是wrap_content,重新測量
  • onLayout中,重置params.w/h為布局文件中編寫的值

可以看到,有了RelativeLayout、FrameLayout的擴展,竟然沒有LinearLayout幾個意思。好在,我們的核心代碼都由PercentLayoutHelper封裝了,自己擴展下LinearLayout也不復雜。


三、實現PercentLinearlayout

可能有人會說,有了weight呀,但是weight能做到寬、高同時百分比賦值嘛?

好了,代碼很簡單,如下:


(一)PercentLinearLayout

package com.juliengenoud.percentsamples; import android.content.Context; import android.content.res.TypedArray; import android.support.percent.PercentLayoutHelper; import android.util.AttributeSet; import android.view.ViewGroup; import android.widget.LinearLayout; /** * Created by zhy on 15/6/30. */ public class PercentLinearLayout extends LinearLayout { private PercentLayoutHelper mPercentLayoutHelper; public PercentLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); mPercentLayoutHelper = new PercentLayoutHelper(this); } @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { mPercentLayoutHelper.adjustChildren(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec); if (mPercentLayoutHelper.handleMeasuredStateTooSmall()) { super.onMeasure(widthMeasureSpec, heightMeasureSpec); } } @Override protected void onLayout(boolean changed, int l, int t, int r, int b) { super.onLayout(changed, l, t, r, b); mPercentLayoutHelper.restoreOriginalParams(); } @Override public LayoutParams generateLayoutParams(AttributeSet attrs) { return new LayoutParams(getContext(), attrs); } public static class LayoutParams extends LinearLayout.LayoutParams implements PercentLayoutHelper.PercentLayoutParams { private PercentLayoutHelper.PercentLayoutInfo mPercentLayoutInfo; public LayoutParams(Context c, AttributeSet attrs) { super(c, attrs); mPercentLayoutInfo = PercentLayoutHelper.getPercentLayoutInfo(c, attrs); } @Override public PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo() { return mPercentLayoutInfo; } @Override protected void setBaseAttributes(TypedArray a, int widthAttr, int heightAttr) { PercentLayoutHelper.fetchWidthAndHeight(this, a, widthAttr, heightAttr); } public LayoutParams(int width, int height) { super(width, height); } public LayoutParams(ViewGroup.LayoutParams source) { super(source); } public LayoutParams(MarginLayoutParams source) { super(source); } } } 

如果你詳細看了上面的源碼分析,這個代碼是不是沒撒解釋的了~


(二)測試布局

<?xml version="1.0" encoding="utf-8"?> <com.juliengenoud.percentsamples.PercentLinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="0dp" android:layout_height="0dp" android:background="#ff44aacc" android:text="width:60%,height:5%" android:textColor="#ffffff" app:layout_heightPercent="5%" app:layout_marginBottomPercent="5%" app:layout_widthPercent="60%"/> <TextView android:layout_width="0dp" android:layout_height="0dp" android:background="#ff4400cc" android:gravity="center" android:textColor="#ffffff" android:text="width:70%,height:10%" app:layout_heightPercent="10%" app:layout_marginBottomPercent="5%" app:layout_widthPercent="70%"/> <TextView android:layout_width="0dp" android:layout_height="0dp" android:background="#ff44aacc" android:gravity="center" android:text="width:80%,height:15%" android:textColor="#ffffff" app:layout_heightPercent="15%" app:layout_marginBottomPercent="5%" app:layout_widthPercent="80%"/> <TextView android:layout_width="0dp" android:layout_height="0dp" android:background="#ff4400cc" android:gravity="center" android:text="width:90%,height:5%" android:textColor="#ffffff" app:layout_heightPercent="20%" app:layout_marginBottomPercent="10%" app:layout_widthPercent="90%"/> <TextView android:layout_width="match_parent" android:layout_height="0dp" android:background="#ff44aacc" android:gravity="center" android:text="width:100%,height:25%" android:textColor="#ffffff" app:layout_heightPercent="25%" app:layout_marginBottomPercent="5%" /> </com.juliengenoud.percentsamples.PercentLinearLayout> 

我們縱向排列的幾個TextView,分別設置寬/高都為百分比,且之間的間隔為5%p。


(三)效果圖

ok,到此,我們使用、源碼分析、擴展PercentLinearLayout就結束了。

添加PercentLinearLayout后的地址:點擊查看

擴展下載:android-percent-support-extend 包含android studio, eclipse項目,以及上述源碼。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM